Create TalkPageNotificationManager service
* The hook that's being deprecated is not used anywhere in MW ecosystem. * The getNewMessageLinks/getNewMessageRevisionId wasn't ported to the service, only the DB lookup. The interface of these two methods is extremelly weird, the idea is that they should eventually be able to do cross-wiki lookups. This doesn't belong in the service - with only a single caller, these methods should be moved out of User and inlined into the caller instead. * There's been a little bit of preparation done to T146585#4233276 as the interface of setNewTalk was split into set and remove with the idea that we gotta require Revision to be passed to setUserHasNewMessages eventually. B/C is still maintained though since service-conversion patches are not a right place for making behavioural changes * The tests are only integration tests since most of the logic in the manager is tied up to the database anyway. Bug: T239640 Change-Id: Ia0a52865970c11066d1089196251f62ffeaa53bb
This commit is contained in:
parent
4f85537dd1
commit
c2a1e0f7e5
9 changed files with 514 additions and 131 deletions
|
|
@ -1002,6 +1002,9 @@ because of Phabricator reports.
|
|||
* The $user parameter for both the getForm() and getFormDescriptor() methods of
|
||||
PreferencesFactory has been deprecated in favour of a new
|
||||
PreferencesFactory::setUser( User $user ) method.
|
||||
* UserRetrieveNewTalks hook was deprecated without replacement.
|
||||
* User::getNewtalk and ::setNewtalk were deprecated. Use service
|
||||
TalkPageNotificationManager instead.
|
||||
* …
|
||||
|
||||
=== Other changes in 1.35 ===
|
||||
|
|
|
|||
|
|
@ -3886,8 +3886,8 @@ $user: the User (object) whose preferences are being reset
|
|||
$options: array of the user's old preferences
|
||||
$resetKinds: array containing the kinds of preferences to reset
|
||||
|
||||
'UserRetrieveNewTalks': Called when retrieving "You have new messages!"
|
||||
message(s).
|
||||
'UserRetrieveNewTalks': DEPRECATED since 1.35!
|
||||
Called when retrieving "You have new messages!" message(s).
|
||||
[&]$user: user retrieving new talks messages
|
||||
&$talks: array of new talks page(s)
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ class DeprecatedHooks {
|
|||
'ParserSectionCreate' => [ 'deprecatedVersion' => '1.35' ],
|
||||
'RevisionInsertComplete' => [ 'deprecatedVersion' => '1.31' ],
|
||||
'UndeleteShowRevision' => [ 'deprecatedVersion' => '1.35' ],
|
||||
'UserRetrieveNewTalks' => [ 'deprecatedVersion' => '1.35' ],
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ use MediaWiki\Storage\BlobStoreFactory;
|
|||
use MediaWiki\Storage\NameTableStore;
|
||||
use MediaWiki\Storage\NameTableStoreFactory;
|
||||
use MediaWiki\Storage\PageEditStash;
|
||||
use MediaWiki\User\TalkPageNotificationManager;
|
||||
use MediaWiki\User\UserNameUtils;
|
||||
use MediaWiki\User\UserOptionsLookup;
|
||||
use MediaWiki\User\UserOptionsManager;
|
||||
|
|
@ -1172,6 +1173,14 @@ class MediaWikiServices extends ServiceContainer {
|
|||
return $this->getService( 'StatsdDataFactory' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.35
|
||||
* @return TalkPageNotificationManager
|
||||
*/
|
||||
public function getTalkPageNotificationManager() : TalkPageNotificationManager {
|
||||
return $this->getService( 'TalkPageNotificationManager' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.34
|
||||
* @return TempFSFileFactory
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ use MediaWiki\Storage\NameTableStoreFactory;
|
|||
use MediaWiki\Storage\PageEditStash;
|
||||
use MediaWiki\Storage\SqlBlobStore;
|
||||
use MediaWiki\User\DefaultOptionsManager;
|
||||
use MediaWiki\User\TalkPageNotificationManager;
|
||||
use MediaWiki\User\UserNameUtils;
|
||||
use MediaWiki\User\UserOptionsLookup;
|
||||
use MediaWiki\User\UserOptionsManager;
|
||||
|
|
@ -1060,6 +1061,19 @@ return [
|
|||
);
|
||||
},
|
||||
|
||||
'TalkPageNotificationManager' => function (
|
||||
MediaWikiServices $services
|
||||
) : TalkPageNotificationManager {
|
||||
return new TalkPageNotificationManager(
|
||||
new ServiceOptions(
|
||||
TalkPageNotificationManager::CONSTRUCTOR_OPTIONS, $services->getMainConfig()
|
||||
),
|
||||
$services->getDBLoadBalancer(),
|
||||
$services->getReadOnlyMode(),
|
||||
$services->getRevisionLookup()
|
||||
);
|
||||
},
|
||||
|
||||
'TempFSFileFactory' => function ( MediaWikiServices $services ) : TempFSFileFactory {
|
||||
return new TempFSFileFactory( $services->getMainConfig()->get( 'TmpDirectory' ) );
|
||||
},
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace MediaWiki\User\Hook;
|
|||
use User;
|
||||
|
||||
/**
|
||||
* @stable for implementation
|
||||
* @deprecated since 1.35
|
||||
* @ingroup Hooks
|
||||
*/
|
||||
interface UserRetrieveNewTalksHook {
|
||||
|
|
|
|||
282
includes/user/TalkPageNotificationManager.php
Normal file
282
includes/user/TalkPageNotificationManager.php
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
namespace MediaWiki\User;
|
||||
|
||||
use MediaWiki\Config\ServiceOptions;
|
||||
use MediaWiki\Revision\RevisionLookup;
|
||||
use MediaWiki\Revision\RevisionRecord;
|
||||
use MWTimestamp;
|
||||
use ReadOnlyMode;
|
||||
use Wikimedia\Rdbms\ILoadBalancer;
|
||||
|
||||
/**
|
||||
* Manages user talk page notifications
|
||||
* @since 1.35
|
||||
*/
|
||||
class TalkPageNotificationManager {
|
||||
|
||||
public const CONSTRUCTOR_OPTIONS = [
|
||||
'DisableAnonTalk'
|
||||
];
|
||||
|
||||
/** @var array */
|
||||
private $userMessagesCache = [];
|
||||
|
||||
/** @var bool */
|
||||
private $disableAnonTalk;
|
||||
|
||||
/** @var ILoadBalancer */
|
||||
private $loadBalancer;
|
||||
|
||||
/** @var ReadOnlyMode */
|
||||
private $readOnlyMode;
|
||||
|
||||
/** @var RevisionLookup */
|
||||
private $revisionLookup;
|
||||
|
||||
/**
|
||||
* @param ServiceOptions $serviceOptions
|
||||
* @param ILoadBalancer $loadBalancer
|
||||
* @param ReadOnlyMode $readOnlyMode
|
||||
* @param RevisionLookup $revisionLookup
|
||||
*/
|
||||
public function __construct(
|
||||
ServiceOptions $serviceOptions,
|
||||
ILoadBalancer $loadBalancer,
|
||||
ReadOnlyMode $readOnlyMode,
|
||||
RevisionLookup $revisionLookup
|
||||
) {
|
||||
$serviceOptions->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
|
||||
$this->disableAnonTalk = $serviceOptions->get( 'DisableAnonTalk' );
|
||||
$this->loadBalancer = $loadBalancer;
|
||||
$this->readOnlyMode = $readOnlyMode;
|
||||
$this->revisionLookup = $revisionLookup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user has new messages.
|
||||
* @param UserIdentity $user
|
||||
* @return bool whether the user has new messages
|
||||
*/
|
||||
public function userHasNewMessages( UserIdentity $user ) : bool {
|
||||
$userKey = $this->getCacheKey( $user );
|
||||
|
||||
// Load the newtalk status if it is unloaded
|
||||
if ( !isset( $this->userMessagesCache[$userKey] ) ) {
|
||||
if ( $this->isTalkDisabled( $user ) ) {
|
||||
// Anon disabled by configuration.
|
||||
$this->userMessagesCache[$userKey] = false;
|
||||
} else {
|
||||
$this->userMessagesCache[$userKey] = $this->dbCheckNewUserMessages( $user );
|
||||
}
|
||||
}
|
||||
|
||||
return (bool)$this->userMessagesCache[$userKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the talk page messages status.
|
||||
*
|
||||
* @param UserIdentity $user
|
||||
* @param RevisionRecord|null $curRev New, as yet unseen revision of the user talk page.
|
||||
* Passing null is deprecated since 1.35
|
||||
*/
|
||||
public function setUserHasNewMessages(
|
||||
UserIdentity $user,
|
||||
RevisionRecord $curRev = null
|
||||
) : void {
|
||||
if ( $this->isTalkDisabled( $user ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$userKey = $this->getCacheKey( $user );
|
||||
$this->userMessagesCache[$userKey] = true;
|
||||
$this->dbUpdateNewUserMessages( $user, $curRev );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the new messages status
|
||||
* @param UserIdentity $user
|
||||
*/
|
||||
public function removeUserHasNewMessages( UserIdentity $user ) : void {
|
||||
if ( $this->isTalkDisabled( $user ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$userKey = $this->getCacheKey( $user );
|
||||
$this->userMessagesCache[$userKey] = false;
|
||||
|
||||
$this->dbDeleteNewUserMessages( $user );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the timestamp of the latest revision of the user talkpage
|
||||
* that the user has already seen in TS_MW format.
|
||||
* If the user has no new messages, returns null
|
||||
*
|
||||
* @param UserIdentity $user
|
||||
* @return string|null
|
||||
*/
|
||||
public function getLatestSeenMessageTimestamp( UserIdentity $user ) : ?string {
|
||||
$userKey = $this->getCacheKey( $user );
|
||||
// Don't use self::userHasNewMessages here to avoid an extra DB query
|
||||
// in case the value is not cached already
|
||||
if ( $this->isTalkDisabled( $user ) ||
|
||||
isset( $this->userMessagesCache[$userKey] ) && !$this->userMessagesCache[$userKey]
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$dbr = $this->loadBalancer->getConnectionRef( DB_REPLICA );
|
||||
list( $field, $id ) = $this->getQueryFieldAndId( $user );
|
||||
// Get the "last viewed rev" timestamp from the oldest message notification
|
||||
$timestamp = $dbr->selectField(
|
||||
'user_newtalk',
|
||||
'MIN(user_last_timestamp)',
|
||||
[ $field => $id ],
|
||||
__METHOD__
|
||||
);
|
||||
if ( $timestamp ) {
|
||||
// TODO: Once passing 'null' as Revision setUserHasNewMessages is removed,
|
||||
// null $timestamp would mean no new messages, so negatives can be cached too.
|
||||
$this->userMessagesCache[$userKey] = true;
|
||||
}
|
||||
return $timestamp !== null ? MWTimestamp::convert( TS_MW, $timestamp ) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the cached newtalk status for the given user
|
||||
* @internal There should be no need to call this other than from User::clearInstanceCache
|
||||
* @param UserIdentity $user
|
||||
*/
|
||||
public function clearInstanceCache( UserIdentity $user ) : void {
|
||||
$userKey = $this->getCacheKey( $user );
|
||||
$this->userMessagesCache[$userKey] = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the talk page is disabled for a user
|
||||
* @param UserIdentity $user
|
||||
* @return bool
|
||||
*/
|
||||
private function isTalkDisabled( UserIdentity $user ) : bool {
|
||||
return !$user->isRegistered() && $this->disableAnonTalk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal uncached check for new messages
|
||||
* @param UserIdentity $user
|
||||
* @return bool True if the user has new messages
|
||||
*/
|
||||
private function dbCheckNewUserMessages( UserIdentity $user ) : bool {
|
||||
$dbr = $this->loadBalancer->getConnectionRef( DB_REPLICA );
|
||||
list( $field, $id ) = $this->getQueryFieldAndId( $user );
|
||||
$ok = $dbr->selectField(
|
||||
'user_newtalk',
|
||||
$field,
|
||||
[ $field => $id ],
|
||||
__METHOD__
|
||||
);
|
||||
return (bool)$ok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or update the new messages flag
|
||||
* @param UserIdentity $user
|
||||
* @param RevisionRecord|null $curRev New, as yet unseen revision of the
|
||||
* user talk page. Ignored if null.
|
||||
* @return bool True if successful, false otherwise
|
||||
*/
|
||||
private function dbUpdateNewUserMessages(
|
||||
UserIdentity $user,
|
||||
RevisionRecord $curRev = null
|
||||
) : bool {
|
||||
if ( $this->readOnlyMode->isReadOnly() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $curRev ) {
|
||||
$prevRev = $this->revisionLookup->getPreviousRevision( $curRev );
|
||||
$ts = $prevRev ? $prevRev->getTimestamp() : null;
|
||||
} else {
|
||||
$ts = null;
|
||||
}
|
||||
|
||||
// Mark the user as having new messages since this revision
|
||||
$dbw = $this->loadBalancer->getConnectionRef( DB_MASTER );
|
||||
list( $field, $id ) = $this->getQueryFieldAndId( $user );
|
||||
$dbw->insert(
|
||||
'user_newtalk',
|
||||
[
|
||||
$field => $id,
|
||||
'user_last_timestamp' => $dbw->timestampOrNull( $ts )
|
||||
],
|
||||
__METHOD__,
|
||||
[ 'IGNORE' ]
|
||||
);
|
||||
return (bool)$dbw->affectedRows();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the new messages flag for the given user
|
||||
* @param UserIdentity $user
|
||||
* @return bool True if successful, false otherwise
|
||||
*/
|
||||
private function dbDeleteNewUserMessages( UserIdentity $user ) : bool {
|
||||
if ( $this->readOnlyMode->isReadOnly() ) {
|
||||
return false;
|
||||
}
|
||||
$dbw = $this->loadBalancer->getConnectionRef( DB_MASTER );
|
||||
list( $field, $id ) = $this->getQueryFieldAndId( $user );
|
||||
$dbw->delete(
|
||||
'user_newtalk',
|
||||
[ $field => $id ],
|
||||
__METHOD__
|
||||
);
|
||||
return (bool)$dbw->affectedRows();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the field name and id for the user_newtalk table query
|
||||
* @param UserIdentity $user
|
||||
* @return array ( string $field, string|int $id )
|
||||
*/
|
||||
private function getQueryFieldAndId( UserIdentity $user ) : array {
|
||||
if ( $user->isRegistered() ) {
|
||||
$field = 'user_id';
|
||||
$id = $user->getId();
|
||||
} else {
|
||||
$field = 'user_ip';
|
||||
$id = $user->getName();
|
||||
}
|
||||
return [ $field, $id ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a unique key for various caches.
|
||||
* @param UserIdentity $user
|
||||
* @return string
|
||||
*/
|
||||
private function getCacheKey( UserIdentity $user ) : string {
|
||||
return $user->isRegistered() ? "u:{$user->getId()}" : "anon:{$user->getName()}";
|
||||
}
|
||||
}
|
||||
|
|
@ -167,8 +167,6 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
/**
|
||||
* Lazy-initialized variables, invalidated with clearInstanceCache
|
||||
*/
|
||||
/** @var int|bool */
|
||||
protected $mNewtalk;
|
||||
/** @var string */
|
||||
protected $mDatePreference;
|
||||
/**
|
||||
|
|
@ -1562,7 +1560,6 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
public function clearInstanceCache( $reloadFrom = false ) {
|
||||
global $wgFullyInitialised;
|
||||
|
||||
$this->mNewtalk = -1;
|
||||
$this->mDatePreference = null;
|
||||
$this->mBlockedby = -1; # Unset
|
||||
$this->mHash = false;
|
||||
|
|
@ -1577,6 +1574,9 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
$this
|
||||
);
|
||||
MediaWikiServices::getInstance()->getUserOptionsManager()->clearUserOptionsCache( $this );
|
||||
MediaWikiServices::getInstance()
|
||||
->getTalkPageNotificationManager()
|
||||
->clearInstanceCache( $this );
|
||||
}
|
||||
|
||||
if ( $reloadFrom ) {
|
||||
|
|
@ -2235,31 +2235,13 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
|
||||
/**
|
||||
* Check if the user has new messages.
|
||||
* @deprecated since 1.35 Use TalkPageNotificationManager::userHasNewMessages instead
|
||||
* @return bool True if the user has new messages
|
||||
*/
|
||||
public function getNewtalk() {
|
||||
$this->load();
|
||||
|
||||
// Load the newtalk status if it is unloaded (mNewtalk=-1)
|
||||
if ( $this->mNewtalk === -1 ) {
|
||||
$this->mNewtalk = false; # reset talk page status
|
||||
|
||||
// Check memcached separately for anons, who have no
|
||||
// entire User object stored in there.
|
||||
if ( !$this->mId ) {
|
||||
global $wgDisableAnonTalk;
|
||||
if ( $wgDisableAnonTalk ) {
|
||||
// Anon newtalk disabled by configuration.
|
||||
$this->mNewtalk = false;
|
||||
} else {
|
||||
$this->mNewtalk = $this->checkNewtalk( 'user_ip', $this->getName() );
|
||||
}
|
||||
} else {
|
||||
$this->mNewtalk = $this->checkNewtalk( 'user_id', $this->mId );
|
||||
}
|
||||
}
|
||||
|
||||
return (bool)$this->mNewtalk;
|
||||
return MediaWikiServices::getInstance()
|
||||
->getTalkPageNotificationManager()
|
||||
->userHasNewMessages( $this );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2279,7 +2261,7 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
// Avoid PHP 7.1 warning of passing $this by reference
|
||||
$user = $this;
|
||||
$talks = [];
|
||||
if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$user, &$talks ] ) ) {
|
||||
if ( !Hooks::run( 'UserRetrieveNewTalks', [ &$user, &$talks ], '1.35' ) ) {
|
||||
return $talks;
|
||||
}
|
||||
|
||||
|
|
@ -2287,12 +2269,9 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
return [];
|
||||
}
|
||||
$utp = $this->getTalkPage();
|
||||
$dbr = wfGetDB( DB_REPLICA );
|
||||
// Get the "last viewed rev" timestamp from the oldest message notification
|
||||
$timestamp = $dbr->selectField( 'user_newtalk',
|
||||
'MIN(user_last_timestamp)',
|
||||
$this->isAnon() ? [ 'user_ip' => $this->getName() ] : [ 'user_id' => $this->getId() ],
|
||||
__METHOD__ );
|
||||
$timestamp = MediaWikiServices::getInstance()
|
||||
->getTalkPageNotificationManager()
|
||||
->getLatestSeenMessageTimestamp( $this );
|
||||
$rev = null;
|
||||
if ( $timestamp ) {
|
||||
$revRecord = MediaWikiServices::getInstance()
|
||||
|
|
@ -2335,115 +2314,27 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
return $newMessageRevisionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal uncached check for new messages
|
||||
*
|
||||
* @see getNewtalk()
|
||||
* @param string $field 'user_ip' for anonymous users, 'user_id' otherwise
|
||||
* @param string|int $id User's IP address for anonymous users, User ID otherwise
|
||||
* @return bool True if the user has new messages
|
||||
*/
|
||||
protected function checkNewtalk( $field, $id ) {
|
||||
$dbr = wfGetDB( DB_REPLICA );
|
||||
|
||||
$ok = $dbr->selectField( 'user_newtalk', $field, [ $field => $id ], __METHOD__ );
|
||||
|
||||
return $ok !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or update the new messages flag
|
||||
* @param string $field 'user_ip' for anonymous users, 'user_id' otherwise
|
||||
* @param string|int $id User's IP address for anonymous users, User ID otherwise
|
||||
* @param RevisionRecord|Revision|null $curRev New, as yet unseen revision of the
|
||||
* user talk page. Ignored if null; passing a Revision is deprecated since 1.35.
|
||||
* @return bool True if successful, false otherwise
|
||||
*/
|
||||
protected function updateNewtalk( $field, $id, $curRev = null ) {
|
||||
// Get timestamp of the talk page revision prior to the current one
|
||||
if ( $curRev && $curRev instanceof Revision ) {
|
||||
wfDeprecated( __METHOD__ . ' with a Revision object', '1.35' );
|
||||
$curRev = $curRev->getRevisionRecord();
|
||||
}
|
||||
if ( $curRev ) {
|
||||
$prevRev = MediaWikiServices::getInstance()
|
||||
->getRevisionLookup()
|
||||
->getPreviousRevision( $curRev );
|
||||
$ts = $prevRev ? $prevRev->getTimestamp() : null;
|
||||
} else {
|
||||
$ts = null;
|
||||
}
|
||||
|
||||
// Mark the user as having new messages since this revision
|
||||
$dbw = wfGetDB( DB_MASTER );
|
||||
$dbw->insert( 'user_newtalk',
|
||||
[ $field => $id, 'user_last_timestamp' => $dbw->timestampOrNull( $ts ) ],
|
||||
__METHOD__,
|
||||
[ 'IGNORE' ] );
|
||||
if ( $dbw->affectedRows() ) {
|
||||
wfDebug( __METHOD__ . ": set on ($field, $id)\n" );
|
||||
return true;
|
||||
}
|
||||
|
||||
wfDebug( __METHOD__ . " already set ($field, $id)\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the new messages flag for the given user
|
||||
* @param string $field 'user_ip' for anonymous users, 'user_id' otherwise
|
||||
* @param string|int $id User's IP address for anonymous users, User ID otherwise
|
||||
* @return bool True if successful, false otherwise
|
||||
*/
|
||||
protected function deleteNewtalk( $field, $id ) {
|
||||
$dbw = wfGetDB( DB_MASTER );
|
||||
$dbw->delete( 'user_newtalk',
|
||||
[ $field => $id ],
|
||||
__METHOD__ );
|
||||
if ( $dbw->affectedRows() ) {
|
||||
wfDebug( __METHOD__ . ": killed on ($field, $id)\n" );
|
||||
return true;
|
||||
}
|
||||
|
||||
wfDebug( __METHOD__ . ": already gone ($field, $id)\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the 'You have new messages!' status.
|
||||
* @param bool $val Whether the user has new messages
|
||||
* @param RevisionRecord|Revision|null $curRev New, as yet unseen revision of the
|
||||
* user talk page. Ignored if null or !$val; passing a Revision is deprecated since 1.35.
|
||||
* @deprecated since 1.35 Use TalkPageNotificationManager::setUserHasNewMessages or
|
||||
* TalkPageNotificationManager::removeUserHasNewMessages
|
||||
*/
|
||||
public function setNewtalk( $val, $curRev = null ) {
|
||||
if ( $curRev && $curRev instanceof Revision ) {
|
||||
wfDeprecated( __METHOD__ . ' with a Revision object', '1.35' );
|
||||
$curRev = $curRev->getRevisionRecord();
|
||||
}
|
||||
|
||||
if ( wfReadOnly() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->load();
|
||||
$this->mNewtalk = $val;
|
||||
|
||||
if ( $this->isAnon() ) {
|
||||
$field = 'user_ip';
|
||||
$id = $this->getName();
|
||||
} else {
|
||||
$field = 'user_id';
|
||||
$id = $this->getId();
|
||||
}
|
||||
|
||||
if ( $val ) {
|
||||
$changed = $this->updateNewtalk( $field, $id, $curRev );
|
||||
MediaWikiServices::getInstance()
|
||||
->getTalkPageNotificationManager()
|
||||
->setUserHasNewMessages( $this, $curRev );
|
||||
} else {
|
||||
$changed = $this->deleteNewtalk( $field, $id );
|
||||
}
|
||||
|
||||
if ( $changed ) {
|
||||
$this->invalidateCache();
|
||||
MediaWikiServices::getInstance()
|
||||
->getTalkPageNotificationManager()
|
||||
->removeUserHasNewMessages( $this );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
183
tests/phpunit/includes/user/TalkPageNotificationManagerTest.php
Normal file
183
tests/phpunit/includes/user/TalkPageNotificationManagerTest.php
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
<?php
|
||||
|
||||
use MediaWiki\Config\ServiceOptions;
|
||||
use MediaWiki\MediaWikiServices;
|
||||
use MediaWiki\Revision\RevisionLookup;
|
||||
use MediaWiki\Revision\RevisionRecord;
|
||||
use MediaWiki\User\TalkPageNotificationManager;
|
||||
use PHPUnit\Framework\AssertionFailedError;
|
||||
|
||||
/**
|
||||
* @covers \MediaWiki\User\TalkPageNotificationManager
|
||||
* @group Database
|
||||
*/
|
||||
class TalkPageNotificationManagerTest extends MediaWikiIntegrationTestCase {
|
||||
use MediaWikiCoversValidator;
|
||||
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->tablesUsed[] = 'user_newtalk';
|
||||
}
|
||||
|
||||
private function editUserTalk( User $user, string $text ) : RevisionRecord {
|
||||
$userTalk = $user->getTalkPage();
|
||||
$status = $this->editPage(
|
||||
$userTalk->getPrefixedText(),
|
||||
$text,
|
||||
'',
|
||||
NS_MAIN,
|
||||
$this->getTestSysop()->getUser()
|
||||
);
|
||||
$this->assertTrue( $status->isGood(), 'Sanity: create revision of user talk' );
|
||||
return $status->getValue()['revision-record'];
|
||||
}
|
||||
|
||||
private function getManager(
|
||||
bool $disableAnonTalk = false,
|
||||
ReadOnlyMode $readOnlyMode = null,
|
||||
RevisionLookup $revisionLookup = null
|
||||
) {
|
||||
$services = MediaWikiServices::getInstance();
|
||||
return new TalkPageNotificationManager(
|
||||
new ServiceOptions(
|
||||
TalkPageNotificationManager::CONSTRUCTOR_OPTIONS,
|
||||
new HashConfig( [
|
||||
'DisableAnonTalk' => $disableAnonTalk
|
||||
] )
|
||||
),
|
||||
$services->getDBLoadBalancer(),
|
||||
$readOnlyMode ?? $services->getReadOnlyMode(),
|
||||
$revisionLookup ?? $services->getRevisionLookup()
|
||||
);
|
||||
}
|
||||
|
||||
private function doTestUserHasNewMessages( User $user ) {
|
||||
$manager = $this->getManager();
|
||||
$this->assertFalse( $manager->userHasNewMessages( $user ),
|
||||
'Should be false before updated' );
|
||||
$revRecord = $this->editUserTalk( $user, __METHOD__ );
|
||||
$manager->setUserHasNewMessages( $user, $revRecord );
|
||||
$this->assertTrue( $manager->userHasNewMessages( $user ),
|
||||
'Should be true after updated' );
|
||||
$manager->clearInstanceCache( $user );
|
||||
$this->assertTrue( $manager->userHasNewMessages( $user ),
|
||||
'Should be true after cache cleared' );
|
||||
$manager->removeUserHasNewMessages( $user );
|
||||
$this->assertFalse( $manager->userHasNewMessages( $user ),
|
||||
'Should be false after updated' );
|
||||
$manager->clearInstanceCache( $user );
|
||||
$this->assertFalse( $manager->userHasNewMessages( $user ),
|
||||
'Should be false after cache cleared' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \MediaWiki\User\TalkPageNotificationManager::userHasNewMessages
|
||||
* @covers \MediaWiki\User\TalkPageNotificationManager::setUserHasNewMessages
|
||||
* @covers \MediaWiki\User\TalkPageNotificationManager::clearInstanceCache
|
||||
* @covers \MediaWiki\User\TalkPageNotificationManager::removeUserHasNewMessages
|
||||
*/
|
||||
public function testUserHasNewMessagesRegistered() {
|
||||
$this->doTestUserHasNewMessages( $this->getTestUser()->getUser() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \MediaWiki\User\TalkPageNotificationManager::userHasNewMessages
|
||||
* @covers \MediaWiki\User\TalkPageNotificationManager::setUserHasNewMessages
|
||||
* @covers \MediaWiki\User\TalkPageNotificationManager::clearInstanceCache
|
||||
* @covers \MediaWiki\User\TalkPageNotificationManager::removeUserHasNewMessages
|
||||
*/
|
||||
public function testUserHasNewMessagesAnon() {
|
||||
$this->doTestUserHasNewMessages( User::newFromName( 'testUserHasNewMessagesAnon' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \MediaWiki\User\TalkPageNotificationManager::userHasNewMessages
|
||||
* @covers \MediaWiki\User\TalkPageNotificationManager::setUserHasNewMessages
|
||||
*/
|
||||
public function testUserHasNewMessagesDisabledAnon() {
|
||||
$user = User::newFromName( 'testUserHasNewMessagesAnon' );
|
||||
$revRecord = $this->editUserTalk( $user, __METHOD__ );
|
||||
$manager = $this->getManager( true );
|
||||
$this->assertFalse( $manager->userHasNewMessages( $user ),
|
||||
'New anon should have no new messages' );
|
||||
$manager->setUserHasNewMessages( $user, $revRecord );
|
||||
$this->assertFalse( $manager->userHasNewMessages( $user ),
|
||||
'Must not set new messages for anon if disabled' );
|
||||
$manager->clearInstanceCache( $user );
|
||||
$this->assertFalse( $manager->userHasNewMessages( $user ),
|
||||
'Must not set to database if anon messages disabled' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \MediaWiki\User\TalkPageNotificationManager::getLatestSeenMessageTimestamp
|
||||
*/
|
||||
public function testGetLatestSeenMessageTimestamp() {
|
||||
$user = $this->getTestUser()->getUser();
|
||||
$firstRev = $this->editUserTalk( $user, __METHOD__ . ' 1' );
|
||||
$secondRev = $this->editUserTalk( $user, __METHOD__ . ' 2' );
|
||||
$manager = $this->getManager();
|
||||
$manager->setUserHasNewMessages( $user, $secondRev );
|
||||
$this->assertSame( $firstRev->getTimestamp(), $manager->getLatestSeenMessageTimestamp( $user ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \MediaWiki\User\TalkPageNotificationManager::getLatestSeenMessageTimestamp
|
||||
*/
|
||||
public function testGetLatestSeenMessageTimestampOutOfOrderRevision() {
|
||||
$user = $this->getTestUser()->getUser();
|
||||
$firstRev = $this->editUserTalk( $user, __METHOD__ . ' 1' );
|
||||
$secondRev = $this->editUserTalk( $user, __METHOD__ . ' 2' );
|
||||
$thirdRev = $this->editUserTalk( $user, __METHOD__ . ' 3' );
|
||||
$veryOldTimestamp = MWTimestamp::convert( TS_MW, 1 );
|
||||
$mockOldRev = $this->createMock( RevisionRecord::class );
|
||||
$mockOldRev->method( 'getTimestamp' )
|
||||
->willReturn( $veryOldTimestamp );
|
||||
$mockRevLookup = $this->getMockForAbstractClass( RevisionLookup::class );
|
||||
$mockRevLookup->method( 'getPreviousRevision' )
|
||||
->willReturnCallback( function ( RevisionRecord $rev )
|
||||
use ( $firstRev, $secondRev, $thirdRev, $mockOldRev )
|
||||
{
|
||||
if ( $rev === $secondRev ) {
|
||||
return $firstRev;
|
||||
}
|
||||
if ( $rev === $thirdRev ) {
|
||||
return $mockOldRev;
|
||||
}
|
||||
throw new AssertionFailedError(
|
||||
'RevisionLookup::getPreviousRevision called with wrong rev ' . $rev->getId()
|
||||
);
|
||||
} );
|
||||
$manager = $this->getManager( false, null, $mockRevLookup );
|
||||
$manager->setUserHasNewMessages( $user, $thirdRev );
|
||||
$this->assertSame( $veryOldTimestamp, $manager->getLatestSeenMessageTimestamp( $user ) );
|
||||
$manager->setUserHasNewMessages( $user, $secondRev );
|
||||
$this->assertSame( $veryOldTimestamp, $manager->getLatestSeenMessageTimestamp( $user ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \MediaWiki\User\TalkPageNotificationManager::getLatestSeenMessageTimestamp
|
||||
*/
|
||||
public function testGetLatestSeenMessageTimestampNoNewMessages() {
|
||||
$user = $this->getTestUser()->getUser();
|
||||
$manager = $this->getManager();
|
||||
$this->assertNull( $manager->getLatestSeenMessageTimestamp( $user ),
|
||||
'Must be null if no new messages' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \MediaWiki\User\TalkPageNotificationManager::userHasNewMessages
|
||||
* @covers \MediaWiki\User\TalkPageNotificationManager::setUserHasNewMessages
|
||||
* @covers \MediaWiki\User\TalkPageNotificationManager::removeUserHasNewMessages
|
||||
*/
|
||||
public function testDoesNotCrashOnReadOnly() {
|
||||
$user = $this->getTestUser()->getUser();
|
||||
$this->editUserTalk( $user, __METHOD__ );
|
||||
$mockReadOnly = $this->createMock( ReadOnlyMode::class );
|
||||
$mockReadOnly->method( 'isReadOnly' )->willReturn( true );
|
||||
|
||||
$manager = $this->getManager( false, $mockReadOnly );
|
||||
$this->assertTrue( $manager->userHasNewMessages( $user ) );
|
||||
$manager->removeUserHasNewMessages( $user );
|
||||
$this->assertFalse( $manager->userHasNewMessages( $user ) );
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue