be a good idea, but will log out all current sessions.
* $wgEventRelayerConfig was added, for managing PubSub event relay configuration,
specifically for reliable CDN url purges.
+* Requests have unique IDs, equal to the UNIQUE_ID environment variable (when
+ MediaWiki is behind Apache+mod_unique_id or something similar) or a randomly-
+ generated 24-character string. This request ID is used to annotate log records
+ and error messages. It is available client-side via mw.config.get( 'wgRequestId' ).
+ The request ID supplants exception IDs. Accordingly, MWExceptionHandler::getLogId()
+ is deprecated.
=== External library changes in 1.27 ===
* %h will be replaced by the short SHA-1 (7 first chars) and %H by the
* full SHA-1 of the HEAD revision.
* %r will be replaced with a URL-encoded version of $1.
+ * %R will be replaced with $1 and no URL-encoding
*
* @since 1.20
*/
$wgGitRepositoryViewers = [
'https://(?:[a-z0-9_]+@)?gerrit.wikimedia.org/r/(?:p/)?(.*)' =>
- 'https://git.wikimedia.org/tree/%r/%H',
+ 'https://phabricator.wikimedia.org/r/revision/%R;%H',
'ssh://(?:[a-z0-9_]+@)?gerrit.wikimedia.org:29418/(.*)' =>
- 'https://git.wikimedia.org/tree/%r/%H',
+ 'https://phabricator.wikimedia.org/r/revision/%R;%H',
];
/** @} */ # End of maintenance }
if ( $url === false ) {
return false;
}
- if ( substr( $url, -4 ) !== '.git' ) {
- $url .= '.git';
- }
foreach ( self::getViewers() as $repo => $viewer ) {
$pattern = '#^' . $repo . '$#';
if ( preg_match( $pattern, $url, $matches ) ) {
'%h' => substr( $headSHA1, 0, 7 ),
'%H' => $headSHA1,
'%r' => urlencode( $matches[1] ),
+ '%R' => $matches[1],
];
return strtr( $viewerUrl, $replacements );
}
$uri
);
}
+
+ /**
+ * Gets the relevant proxy from $wgHTTPProxy/http_proxy (when set).
+ *
+ * @return mixed The proxy address or an empty string if not set.
+ */
+ public static function getProxy() {
+ global $wgHTTPProxy;
+
+ if ( $wgHTTPProxy ) {
+ return $wgHTTPProxy;
+ }
+
+ $envHttpProxy = getenv( "http_proxy" );
+ if ( $envHttpProxy ) {
+ return $envHttpProxy;
+ }
+
+ return "";
+ }
}
/**
* @return void
*/
public function proxySetup() {
- global $wgHTTPProxy;
-
// If there is an explicit proxy set and proxies are not disabled, then use it
if ( $this->proxy && !$this->noProxy ) {
return;
// local URL and proxies are not disabled
if ( Http::isLocalURL( $this->url ) || $this->noProxy ) {
$this->proxy = '';
- } elseif ( $wgHTTPProxy ) {
- $this->proxy = $wgHTTPProxy;
- } elseif ( getenv( "http_proxy" ) ) {
- $this->proxy = getenv( "http_proxy" );
+ } else {
+ $this->proxy = Http::getProxy();
}
}
'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
'wgRelevantArticleId' => $relevantTitle->getArticleID(),
+ 'wgRequestId' => WebRequest::getRequestId(),
];
if ( $user->isLoggedIn() ) {
*/
public static function batchAddWatch( array $items ) {
// wfDeprecated( __METHOD__, '1.27' );
- $userTargetCombinations = [];
+ if ( !$items ) {
+ return false;
+ }
+
+ $targets = [];
+ $users = [];
/** @var WatchedItem $watchedItem */
foreach ( $items as $watchedItem ) {
- if ( $watchedItem->checkRights && !$watchedItem->getUser()->isAllowed( 'editmywatchlist' ) ) {
+ $user = $watchedItem->getUser();
+ if ( $watchedItem->checkRights && !$user->isAllowed( 'editmywatchlist' ) ) {
continue;
}
- $userTargetCombinations[] = [
- $watchedItem->getUser(),
- $watchedItem->getTitle()->getSubjectPage()
- ];
- $userTargetCombinations[] = [
- $watchedItem->getUser(),
- $watchedItem->getTitle()->getTalkPage()
- ];
+ $userId = $user->getId();
+ $users[$userId] = $user;
+ $targets[$userId][] = $watchedItem->getTitle()->getSubjectPage();
+ $targets[$userId][] = $watchedItem->getTitle()->getTalkPage();
}
+
$store = WatchedItemStore::getDefaultInstance();
- return $store->addWatchBatch( $userTargetCombinations );
+ $success = true;
+ foreach ( $users as $userId => $user ) {
+ $success &= $store->addWatchBatchForUser( $user, $targets[$userId] );
+ }
+
+ return $success;
}
/**
* @param LinkTarget $target
*/
public function addWatch( User $user, LinkTarget $target ) {
- $this->addWatchBatch( [ [ $user, $target ] ] );
+ $this->addWatchBatchForUser( $user, [ $target ] );
}
/**
- * @param array[] $userTargetCombinations array of arrays containing [0] => User [1] => LinkTarget
+ * @param User $user
+ * @param LinkTarget[] $targets
*
* @return bool success
*/
- public function addWatchBatch( array $userTargetCombinations ) {
+ public function addWatchBatchForUser( User $user, array $targets ) {
if ( $this->loadBalancer->getReadOnlyReason() !== false ) {
return false;
}
+ // Only loggedin user can have a watchlist
+ if ( $user->isAnon() ) {
+ return false;
+ }
+
+ if ( !$targets ) {
+ return true;
+ }
$rows = [];
- foreach ( $userTargetCombinations as list( $user, $target ) ) {
- /**
- * @var User $user
- * @var LinkTarget $target
- */
-
- // Only loggedin user can have a watchlist
- if ( $user->isAnon() ) {
- continue;
- }
+ foreach ( $targets as $target ) {
$rows[] = [
'wl_user' => $user->getId(),
'wl_namespace' => $target->getNamespace(),
$this->uncache( $user, $target );
}
- if ( !$rows ) {
- return false;
- }
-
$dbw = $this->getConnection( DB_MASTER );
foreach ( array_chunk( $rows, 100 ) as $toInsert ) {
// Use INSERT IGNORE to avoid overwriting the notification timestamp
return microtime( true ) - $this->requestTime;
}
+ /**
+ * Get the unique request ID.
+ * This is either the value of the UNIQUE_ID envvar (if present) or a
+ * randomly-generated 24-character string.
+ *
+ * @return string
+ * @since 1.27
+ */
+ public static function getRequestId() {
+ static $reqId;
+
+ if ( !$reqId ) {
+ $reqId = isset( $_SERVER['UNIQUE_ID'] )
+ ? $_SERVER['UNIQUE_ID'] : wfRandomString( 24 );
+ }
+
+ return $reqId;
+ }
+
/**
* Get the current URL protocol (http or https)
* @return string
$errMessage = [
'code' => 'internal_api_error_' . get_class( $e ),
- 'info' => '[' . MWExceptionHandler::getLogId( $e ) . '] ' . $info,
+ 'info' => '[' . WebRequest::getRequestId() . '] ' . $info,
];
}
return $errMessage;
namespace MediaWiki\Logger\Monolog;
/**
- * Injects `wfHostname()`, `wfWikiID()` and `$wgVersion` in all records.
+ * Annotate log records with request-global metadata, such as the hostname,
+ * wiki / request ID, and MediaWiki version.
*
* @since 1.25
* @author Bryan Davis <bd808@wikimedia.org>
'host' => wfHostname(),
'wiki' => wfWikiID(),
'mwversion' => $wgVersion,
+ 'reqId' => \WebRequest::getRequestId(),
]
);
return $record;
nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $this ) ) ) .
"</p>\n";
} else {
- $logId = MWExceptionHandler::getLogId( $this );
+ $logId = WebRequest::getRequestId();
$type = get_class( $this );
return "<div class=\"errorbox\">" .
'[' . $logId . '] ' .
* $wgShowExceptionDetails is set to false), to the entry in the debug log.
*
* @since 1.22
+ * @deprecated since 1.27: Exception IDs are synonymous with request IDs.
* @param Exception|Throwable $e
* @return string
*/
public static function getLogId( $e ) {
- if ( !isset( $e->_mwLogId ) ) {
- $e->_mwLogId = wfRandomString( 8 );
- }
- return $e->_mwLogId;
+ wfDeprecated( __METHOD__, '1.27' );
+ return WebRequest::getRequestId();
}
/**
* @return string
*/
public static function getLogMessage( $e ) {
- $id = self::getLogId( $e );
+ $id = WebRequest::getRequestId();
$type = get_class( $e );
$file = $e->getFile();
$line = $e->getLine();
}
public static function getPublicLogMessage( Exception $e ) {
- $logId = self::getLogId( $e );
+ $reqId = WebRequest::getRequestId();
$type = get_class( $e );
- return '[' . $logId . '] '
+ return '[' . $reqId . '] '
. gmdate( 'Y-m-d H:i:s' ) . ': '
. 'Fatal exception of type ' . $type;
}
public static function getLogContext( $e ) {
return [
'exception' => $e,
- 'exception_id' => static::getLogId( $e ),
+ 'exception_id' => WebRequest::getRequestId(),
];
}
public static function getStructuredExceptionData( $e ) {
global $wgLogExceptionBacktrace;
$data = [
- 'id' => self::getLogId( $e ),
+ 'id' => WebRequest::getRequestId(),
'type' => get_class( $e ),
'file' => $e->getFile(),
'line' => $e->getLine(),
}
protected function getDimensionInfo( File $file ) {
- $that = $this;
-
- return ObjectCache::getMainWANInstance()->getWithSetCallback(
- wfMemcKey( 'file-djvu', 'dimensions', $file->getSha1() ),
- WANObjectCache::TTL_INDEFINITE,
- function () use ( $that, $file ) {
- $tree = $that->getMetaTree( $file );
+ $cache = ObjectCache::getMainWANInstance();
+ return $cache->getWithSetCallback(
+ $cache->makeKey( 'file-djvu', 'dimensions', $file->getSha1() ),
+ $cache::TTL_INDEFINITE,
+ function () use ( $file ) {
+ $tree = $this->getMetaTree( $file );
if ( !$tree ) {
return false;
}
$dimsByPage = [];
$count = count( $tree->xpath( '//OBJECT' ) );
- for ( $i = 0; $i < $count; ++$i ) {
+ for ( $i = 0; $i < $count; $i++ ) {
$o = $tree->BODY[0]->OBJECT[$i];
if ( $o ) {
$dimsByPage[$i] = [
'width' => (int)$o['width'],
- 'height' => (int)$o['height']
+ 'height' => (int)$o['height'],
];
} else {
$dimsByPage[$i] = false;
return [ 'pageCount' => $count, 'dimensionsByPage' => $dimsByPage ];
},
- [ 'pcTTL' => WANObjectCache::TTL_INDEFINITE ]
+ [ 'pcTTL' => $cache::TTL_INDEFINITE ]
);
}
$title = $this->getTitle();
$context = $this->getContext();
$user = $context->getUser();
+ $request = $context->getRequest();
# Check permissions
$permissionErrors = $title->getUserPermissionsErrors( 'delete', $user );
}
# Better double-check that it hasn't been deleted yet!
- $this->mPage->loadPageData( 'fromdbmaster' );
+ $this->mPage->loadPageData(
+ $request->wasPosted() ? WikiPage::READ_LATEST : WikiPage::READ_NORMAL
+ );
if ( !$this->mPage->exists() ) {
$deleteLogPage = new LogPage( 'delete' );
$outputPage = $context->getOutput();
return;
}
- $request = $context->getRequest();
$deleteReasonList = $request->getText( 'wpDeleteReasonList', 'other' );
$deleteReason = $request->getText( 'wpReason' );
*/
public function setText( $text, $setTitle = true ) {
$this->text = $text;
- if ( $setTitle && $text ) {
+ if ( $setTitle && $text !== '' && $text !== null ) {
$this->setSuggestedTitle( Title::makeTitle( 0, $text ) );
}
}
private $badItems = [];
+ /**
+ * @var TitleParser
+ */
+ private $titleParser;
+
public function __construct() {
parent::__construct( 'EditWatchlist', 'editmywatchlist' );
}
+ /**
+ * Initialize any services we'll need (unless it has already been provided via a setter).
+ * This allows for dependency injection even though we don't control object creation.
+ */
+ private function initServices() {
+ if ( !$this->titleParser ) {
+ $lang = $this->getContext()->getLanguage();
+ $this->titleParser = new MediaWikiTitleCodec( $lang, GenderCache::singleton() );
+ }
+ }
+
public function doesWrites() {
return true;
}
* @param int $mode
*/
public function execute( $mode ) {
+ $this->initServices();
$this->setHeaders();
# Anons don't get a watchlist
}
/**
- * Add a list of titles to a user's watchlist
- *
- * $titles can be an array of strings or Title objects; the former
- * is preferred, since Titles are very memory-heavy
+ * Add a list of targets to a user's watchlist
*
- * @param array $titles Array of strings, or Title objects
+ * @param string[]|LinkTarget[] $targets
*/
- private function watchTitles( $titles ) {
- $dbw = wfGetDB( DB_MASTER );
- $rows = [];
-
- foreach ( $titles as $title ) {
- if ( !$title instanceof Title ) {
- $title = Title::newFromText( $title );
+ private function watchTitles( $targets ) {
+ $expandedTargets = [];
+ foreach ( $targets as $target ) {
+ if ( !$target instanceof LinkTarget ) {
+ try {
+ $target = $this->titleParser->parseTitle( $target, NS_MAIN );
+ }
+ catch ( MalformedTitleException $e ) {
+ continue;
+ }
}
- if ( $title instanceof Title ) {
- $rows[] = [
- 'wl_user' => $this->getUser()->getId(),
- 'wl_namespace' => MWNamespace::getSubject( $title->getNamespace() ),
- 'wl_title' => $title->getDBkey(),
- 'wl_notificationtimestamp' => null,
- ];
- $rows[] = [
- 'wl_user' => $this->getUser()->getId(),
- 'wl_namespace' => MWNamespace::getTalk( $title->getNamespace() ),
- 'wl_title' => $title->getDBkey(),
- 'wl_notificationtimestamp' => null,
- ];
- }
+ $ns = $target->getNamespace();
+ $dbKey = $target->getDBkey();
+ $expandedTargets[] = new TitleValue( MWNamespace::getSubject( $ns ), $dbKey );
+ $expandedTargets[] = new TitleValue( MWNamespace::getTalk( $ns ), $dbKey );
}
- $dbw->insert( 'watchlist', $rows, __METHOD__, 'IGNORE' );
+ WatchedItemStore::getDefaultInstance()->addWatchBatchForUser(
+ $this->getUser(),
+ $expandedTargets
+ );
}
/**
*/
public function addWatch( $title, $checkRights = self::CHECK_USER_RIGHTS ) {
if ( !$checkRights || $this->isAllowed( 'editmywatchlist' ) ) {
- WatchedItemStore::getDefaultInstance()->addWatchBatch( [
- [ $this, $title->getSubjectPage() ],
- [ $this, $title->getTalkPage() ],
- ]
+ WatchedItemStore::getDefaultInstance()->addWatchBatchForUser(
+ $this,
+ [ $title->getSubjectPage(), $title->getTalkPage() ]
);
}
$this->invalidateCache();
* @return array
* @since 1.19
*/
- function getFallbackLanguages() {
+ public function getFallbackLanguages() {
return self::getFallbacksFor( $this->mCode );
}
* Exports $wgBookstoreListEn
* @return array
*/
- function getBookstoreList() {
+ public function getBookstoreList() {
return self::$dataCache->getItem( $this->mCode, 'bookstoreList' );
}
*
* @return array
*/
- function getFormattedNamespaces() {
+ public function getFormattedNamespaces() {
$ns = $this->getNamespaces();
foreach ( $ns as $k => $v ) {
$ns[$k] = strtr( $v, '_', ' ' );
* @param int $index The array key of the namespace to return
* @return string|bool String if the namespace value exists, otherwise false
*/
- function getNsText( $index ) {
+ public function getNsText( $index ) {
$ns = $this->getNamespaces();
return isset( $ns[$index] ) ? $ns[$index] : false;
}
* @param int $index The array key of the namespace to return
* @return string Namespace name without underscores (empty string if namespace does not exist)
*/
- function getFormattedNsText( $index ) {
+ public function getFormattedNsText( $index ) {
$ns = $this->getNsText( $index );
return strtr( $ns, '_', ' ' );
}
* @return string
* @since 1.18
*/
- function getGenderNsText( $index, $gender ) {
+ public function getGenderNsText( $index, $gender ) {
global $wgExtraGenderNamespaces;
$ns = $wgExtraGenderNamespaces +
* @return bool
* @since 1.18
*/
- function needsGenderDistinction() {
+ public function needsGenderDistinction() {
global $wgExtraGenderNamespaces, $wgExtraNamespaces;
if ( count( $wgExtraGenderNamespaces ) > 0 ) {
// $wgExtraGenderNamespaces overrides everything
/**
* @return array
*/
- function getNamespaceAliases() {
+ public function getNamespaceAliases() {
if ( is_null( $this->namespaceAliases ) ) {
$aliases = self::$dataCache->getItem( $this->mCode, 'namespaceAliases' );
if ( !$aliases ) {
/**
* @return array
*/
- function getNamespaceIds() {
+ public function getNamespaceIds() {
if ( is_null( $this->mNamespaceIds ) ) {
global $wgNamespaceAliases;
# Put namespace names and aliases into a hashtable.
* @param string $text
* @return int|bool An integer if $text is a valid value otherwise false
*/
- function getNsIndex( $text ) {
+ public function getNsIndex( $text ) {
$lctext = $this->lc( $text );
$ns = MWNamespace::getCanonicalIndex( $lctext );
if ( $ns !== null ) {
* @param bool $usemsg Use the "variantname-xyz" message if it exists
* @return string
*/
- function getVariantname( $code, $usemsg = true ) {
+ public function getVariantname( $code, $usemsg = true ) {
$msg = "variantname-$code";
if ( $usemsg && wfMessage( $msg )->exists() ) {
return $this->getMessageFromDB( $msg );
/**
* @return array
*/
- function getDatePreferences() {
+ public function getDatePreferences() {
return self::$dataCache->getItem( $this->mCode, 'datePreferences' );
}
/**
* @return array|string
*/
- function getDefaultDateFormat() {
+ public function getDefaultDateFormat() {
$df = self::$dataCache->getItem( $this->mCode, 'defaultDateFormat' );
if ( $df === 'dmy or mdy' ) {
global $wgAmericanDates;
/**
* @return array
*/
- function getDatePreferenceMigrationMap() {
+ public function getDatePreferenceMigrationMap() {
return self::$dataCache->getItem( $this->mCode, 'datePreferenceMigrationMap' );
}
* @return array
* @since 1.24
*/
- function getImageFiles() {
+ public function getImageFiles() {
return self::$dataCache->getItem( $this->mCode, 'imageFiles' );
}
/**
* @return array
*/
- function getExtraUserToggles() {
+ public function getExtraUserToggles() {
return (array)self::$dataCache->getItem( $this->mCode, 'extraUserToggles' );
}
* @param string $msg Message name
* @return string
*/
- function getMessageFromDB( $msg ) {
+ public function getMessageFromDB( $msg ) {
return $this->msg( $msg )->text();
}
* @param string $key
* @return string
*/
- function getMonthName( $key ) {
+ public function getMonthName( $key ) {
return $this->getMessageFromDB( self::$mMonthMsgs[$key - 1] );
}
/**
* @return array
*/
- function getMonthNamesArray() {
+ public function getMonthNamesArray() {
$monthNames = [ '' ];
for ( $i = 1; $i < 13; $i++ ) {
$monthNames[] = $this->getMonthName( $i );
* @param string $key
* @return string
*/
- function getMonthNameGen( $key ) {
+ public function getMonthNameGen( $key ) {
return $this->getMessageFromDB( self::$mMonthGenMsgs[$key - 1] );
}
'sk' => 'resources/lib/jquery.ui/i18n/jquery.ui.datepicker-sk.js',
'sl' => 'resources/lib/jquery.ui/i18n/jquery.ui.datepicker-sl.js',
'sq' => 'resources/lib/jquery.ui/i18n/jquery.ui.datepicker-sq.js',
- 'sr-sr' => 'resources/lib/jquery.ui/i18n/jquery.ui.datepicker-sr-SR.js',
+ 'sr-el' => 'resources/lib/jquery.ui/i18n/jquery.ui.datepicker-sr-SR.js',
'sr' => 'resources/lib/jquery.ui/i18n/jquery.ui.datepicker-sr.js',
'sv' => 'resources/lib/jquery.ui/i18n/jquery.ui.datepicker-sv.js',
'ta' => 'resources/lib/jquery.ui/i18n/jquery.ui.datepicker-ta.js',
* @private
*/
PageExistenceCache.prototype.processExistenceCheckQueue = function () {
- var queue, titles;
+ var queue, titles,
+ cache = this;
if ( this.currentRequest ) {
// Don't fire off a million requests at the same time
this.currentRequest.always( function () {
- this.currentRequest = null;
- this.processExistenceCheckQueueDebounced();
- }.bind( this ) );
+ cache.currentRequest = null;
+ cache.processExistenceCheckQueueDebounced();
+ } );
return;
}
queue = this.existenceCheckQueue;
this.existenceCheckQueue = {};
titles = Object.keys( queue ).filter( function ( title ) {
- if ( this.existenceCache.hasOwnProperty( title ) ) {
- queue[ title ].resolve( this.existenceCache[ title ] );
+ if ( cache.existenceCache.hasOwnProperty( title ) ) {
+ queue[ title ].resolve( cache.existenceCache[ title ] );
}
- return !this.existenceCache.hasOwnProperty( title );
- }.bind( this ) );
+ return !cache.existenceCache.hasOwnProperty( title );
+ } );
if ( !titles.length ) {
return;
}
} ).done( function ( response ) {
$.each( response.query.pages, function ( index, page ) {
var title = new ForeignTitle( page.title ).getPrefixedText();
- this.existenceCache[ title ] = !page.missing;
- queue[ title ].resolve( this.existenceCache[ title ] );
- }.bind( this ) );
- }.bind( this ) );
+ cache.existenceCache[ title ] = !page.missing;
+ queue[ title ].resolve( cache.existenceCache[ title ] );
+ } );
+ } );
};
/**
* @cfg {string} [apiUrl] API URL, if not the current wiki's API
*/
mw.widgets.CategoryCapsuleItemWidget = function MWWCategoryCapsuleItemWidget( config ) {
+ var widget = this;
// Parent constructor
mw.widgets.CategoryCapsuleItemWidget.parent.call( this, $.extend( {
data: config.title.getMainText(),
this.constructor.static.pageExistenceCaches[ this.apiUrl ]
.checkPageExistence( new ForeignTitle( this.title.getPrefixedText() ) )
.done( function ( exists ) {
- this.setMissing( !exists );
- }.bind( this ) );
+ widget.setMissing( !exists );
+ } );
/*jshint +W024*/
};
*/
( function ( $, mw ) {
- var interwikiPrefixes = [],
- interwikiPrefixesPromise = new mw.Api().get( {
+ var interwikiPrefixesPromise = new mw.Api().get( {
action: 'query',
meta: 'siteinfo',
siprop: 'interwikimap'
- } ).done( function ( data ) {
- $.each( data.query.interwikimap, function ( index, interwiki ) {
- interwikiPrefixes.push( interwiki.prefix );
+ } ).then( function ( data ) {
+ return $.map( data.query.interwikimap, function ( interwiki ) {
+ return interwiki.prefix;
} );
} );
} };
if ( mw.Title.newFromText( query ) ) {
- return interwikiPrefixesPromise.then( function () {
+ return interwikiPrefixesPromise.then( function ( interwikiPrefixes ) {
var params,
interwiki = query.substring( 0, query.indexOf( ':' ) );
if (
);
}
+ /**
+ * @covers Http::getProxy
+ */
+ public function testGetProxy() {
+ $this->setMwGlobals( 'wgHTTPProxy', 'proxy.domain.tld' );
+ $this->assertEquals(
+ 'proxy.domain.tld',
+ Http::getProxy()
+ );
+ }
+
/**
* Feeds URI to test a long regular expression in Http::isValidURI
*/
);
}
- public function testAddWatchBatch_nonAnonymousUser() {
+ public function testAddWatchBatchForUser_nonAnonymousUser() {
$mockDb = $this->getMockDb();
$mockDb->expects( $this->once() )
->method( 'insert' )
$mockUser = $this->getMockNonAnonUserWithId( 1 );
$this->assertTrue(
- $store->addWatchBatch(
- [
- [ $mockUser, new TitleValue( 0, 'Some_Page' ) ],
- [ $mockUser, new TitleValue( 1, 'Some_Page' ) ],
- ]
- )
- );
- }
-
- public function testAddWatchBatch_anonymousUserCombinationsAreSkipped() {
- $mockDb = $this->getMockDb();
- $mockDb->expects( $this->once() )
- ->method( 'insert' )
- ->with(
- 'watchlist',
- [
- [
- 'wl_user' => 1,
- 'wl_namespace' => 0,
- 'wl_title' => 'Some_Page',
- 'wl_notificationtimestamp' => null,
- ]
- ]
- );
-
- $mockCache = $this->getMockCache();
- $mockCache->expects( $this->once() )
- ->method( 'delete' )
- ->with( '0:Some_Page:1' );
-
- $store = new WatchedItemStore(
- $this->getMockLoadBalancer( $mockDb ),
- $mockCache
- );
-
- $this->assertTrue(
- $store->addWatchBatch(
- [
- [ $this->getMockNonAnonUserWithId( 1 ), new TitleValue( 0, 'Some_Page' ) ],
- [ $this->getAnonUser(), new TitleValue( 0, 'Other_Page' ) ],
- ]
+ $store->addWatchBatchForUser(
+ $mockUser,
+ [ new TitleValue( 0, 'Some_Page' ), new TitleValue( 1, 'Some_Page' ) ]
)
);
}
- public function testAddWatchBatchReturnsFalse_whenOnlyGivenAnonymousUserCombinations() {
+ public function testAddWatchBatchForUser_anonymousUsersAreSkipped() {
$mockDb = $this->getMockDb();
$mockDb->expects( $this->never() )
->method( 'insert' );
$mockCache
);
- $anonUser = $this->getAnonUser();
$this->assertFalse(
- $store->addWatchBatch(
- [
- [ $anonUser, new TitleValue( 0, 'Some_Page' ) ],
- [ $anonUser, new TitleValue( 1, 'Other_Page' ) ],
- ]
+ $store->addWatchBatchForUser(
+ $this->getAnonUser(),
+ [ new TitleValue( 0, 'Other_Page' ) ]
)
);
}
- public function testAddWatchBatchReturnsFalse_whenGivenEmptyList() {
+ public function testAddWatchBatchReturnsTrue_whenGivenEmptyList() {
+ $user = $this->getMockNonAnonUserWithId( 1 );
$mockDb = $this->getMockDb();
$mockDb->expects( $this->never() )
->method( 'insert' );
$mockCache
);
- $this->assertFalse(
- $store->addWatchBatch( [] )
+ $this->assertTrue(
+ $store->addWatchBatchForUser( $user, [] )
);
}
}
public function testBatchAddWatch() {
- /** @var WatchedItem[] $items */
- $items = [
- new WatchedItem( User::newFromId( 1 ), new TitleValue( 0, 'Title1' ), null ),
- new WatchedItem( User::newFromId( 3 ), Title::newFromText( 'Title2' ), '20150101010101' ),
- ];
-
- $userTargetCombinations = [];
- foreach ( $items as $item ) {
- $userTargetCombinations[] = [ $item->getUser(), $item->getTitle()->getSubjectPage() ];
- $userTargetCombinations[] = [ $item->getUser(), $item->getTitle()->getTalkPage() ];
- }
+ $itemOne = new WatchedItem( User::newFromId( 1 ), new TitleValue( 0, 'Title1' ), null );
+ $itemTwo = new WatchedItem(
+ User::newFromId( 3 ),
+ Title::newFromText( 'Title2' ),
+ '20150101010101'
+ );
$store = $this->getMockWatchedItemStore();
- $store->expects( $this->once() )
- ->method( 'addWatchBatch' )
- ->with( $userTargetCombinations );
+ $store->expects( $this->exactly( 2 ) )
+ ->method( 'addWatchBatchForUser' );
+ $store->expects( $this->at( 0 ) )
+ ->method( 'addWatchBatchForUser' )
+ ->with(
+ $itemOne->getUser(),
+ [
+ $itemOne->getTitle()->getSubjectPage(),
+ $itemOne->getTitle()->getTalkPage(),
+ ]
+ );
+ $store->expects( $this->at( 1 ) )
+ ->method( 'addWatchBatchForUser' )
+ ->with(
+ $itemTwo->getUser(),
+ [
+ $itemTwo->getTitle()->getSubjectPage(),
+ $itemTwo->getTitle()->getTalkPage(),
+ ]
+ );
$scopedOverride = WatchedItemStore::overrideDefaultInstance( $store );
- WatchedItem::batchAddWatch( $items );
+ WatchedItem::batchAddWatch( [ $itemOne, $itemTwo ] );
ScopedCallback::consume( $scopedOverride );
}