* The type string for the parameter $lang of DateFormatter::getInstance is
deprecated.
* Wikimedia\Rdbms\SavepointPostgres is deprecated.
+* The DO_MAINTENANCE constant is deprecated. RUN_MAINTENANCE_IF_MAIN should be
+ used instead.
=== Other changes in 1.31 ===
* Browser support for Internet Explorer 10 was lowered from Grade A to Grade C.
// RACE: $name was already in the db, probably just inserted, so load from master
// Use DBO_TRX to avoid missing inserts due to other threads or REPEATABLE-READs
$table = $this->loadTable(
- $this->getDBConnection( DB_MASTER, LoadBalancer::CONN_TRX_AUTO )
+ $this->getDBConnection( DB_MASTER, LoadBalancer::CONN_TRX_AUTOCOMMIT )
);
$searchResult = array_search( $name, $table, true );
if ( $searchResult === false ) {
if ( $params['onlypst'] ) {
// Build a result and bail out
$result_array = [];
- if ( $this->contentIsDeleted ) {
- $result_array['textdeleted'] = true;
- }
- if ( $this->contentIsSuppressed ) {
- $result_array['textsuppressed'] = true;
- }
$result_array['text'] = $this->pstContent->serialize( $format );
$result_array[ApiResult::META_BC_SUBELEMENTS][] = 'text';
if ( isset( $prop['wikitext'] ) ) {
}
if ( isset( $prop['displaytitle'] ) ) {
- $result_array['displaytitle'] = $p_result->getDisplayTitle() ?:
- $titleObj->getPrefixedText();
+ $result_array['displaytitle'] = $p_result->getDisplayTitle() !== false
+ ? $p_result->getDisplayTitle() : $titleObj->getPrefixedText();
}
if ( isset( $prop['headitems'] ) ) {
}
$wgParser->startExternalParse( $titleObj, $popts, Parser::OT_PREPROCESS );
- $dom = $wgParser->preprocessToDom( $this->content->getNativeData() );
- if ( is_callable( [ $dom, 'saveXML' ] ) ) {
- $xml = $dom->saveXML();
- } else {
- $xml = $dom->__toString();
- }
+ $xml = $wgParser->preprocessToDom( $this->content->getNativeData() )->__toString();
$result_array['parsetree'] = $xml;
$result_array[ApiResult::META_BC_SUBELEMENTS][] = 'parsetree';
}
} else {
$this->content = $page->getContent( Revision::FOR_THIS_USER, $this->getUser() );
if ( !$this->content ) {
- $this->dieWithError( [ 'apierror-missingcontent-pageid', $pageId ] );
+ $this->dieWithError( [ 'apierror-missingcontent-pageid', $page->getId() ] );
}
}
$this->contentIsDeleted = $isDeleted;
$pout = $page->getParserOutput( $popts, $revId, $suppressCache );
}
if ( !$pout ) {
- $this->dieWithError( [ 'apierror-nosuchrevid', $revId ?: $page->getLatest() ] );
+ $this->dieWithError( [ 'apierror-nosuchrevid', $revId ?: $page->getLatest() ] ); // @codeCoverageIgnore
}
return $pout;
if ( isset( $show['unpatrolled'] ) ) {
// See ChangesList::isUnpatrolled
if ( $user->useRCPatrol() ) {
- $this->addWhere( 'rc_patrolled = 0' );
+ $this->addWhere( 'rc_patrolled = ' . RecentChange::PRC_UNPATROLLED );
} elseif ( $user->useNPPatrol() ) {
- $this->addWhere( 'rc_patrolled = 0' );
+ $this->addWhere( 'rc_patrolled = ' . RecentChange::PRC_UNPATROLLED );
$this->addWhereFld( 'rc_type', RC_NEW );
}
}
- $this->addWhereIf( 'rc_patrolled != 2', isset( $show['!autopatrolled'] ) );
- $this->addWhereIf( 'rc_patrolled = 2', isset( $show['autopatrolled'] ) );
+ $this->addWhereIf(
+ 'rc_patrolled != ' . RecentChange::PRC_AUTOPATROLLED,
+ isset( $show['!autopatrolled'] )
+ );
+ $this->addWhereIf(
+ 'rc_patrolled = ' . RecentChange::PRC_AUTOPATROLLED,
+ isset( $show['autopatrolled'] )
+ );
// Don't throw log entries out the window here
$this->addWhereIf(
/* Add the patrolled flag */
if ( $this->fld_patrolled ) {
- $vals['patrolled'] = $row->rc_patrolled != 0;
+ $vals['patrolled'] = $row->rc_patrolled != RecentChange::PRC_UNPATROLLED;
$vals['unpatrolled'] = ChangesList::isUnpatrolled( $row, $user );
- $vals['autopatrolled'] = $row->rc_patrolled == 2;
+ $vals['autopatrolled'] = $row->rc_patrolled == RecentChange::PRC_AUTOPATROLLED;
}
if ( $this->fld_loginfo && $row->rc_type == RC_LOG ) {
$this->addWhereIf( 'rev_minor_edit = 0', isset( $show['!minor'] ) );
$this->addWhereIf( 'rev_minor_edit != 0', isset( $show['minor'] ) );
- $this->addWhereIf( 'rc_patrolled = 0', isset( $show['!patrolled'] ) );
- $this->addWhereIf( 'rc_patrolled != 0', isset( $show['patrolled'] ) );
- $this->addWhereIf( 'rc_patrolled != 2', isset( $show['!autopatrolled'] ) );
- $this->addWhereIf( 'rc_patrolled = 2', isset( $show['autopatrolled'] ) );
+ $this->addWhereIf(
+ 'rc_patrolled = ' . RecentChange::PRC_UNPATROLLED,
+ isset( $show['!patrolled'] )
+ );
+ $this->addWhereIf(
+ 'rc_patrolled != ' . RecentChange::PRC_UNPATROLLED,
+ isset( $show['patrolled'] )
+ );
+ $this->addWhereIf(
+ 'rc_patrolled != ' . RecentChange::PRC_AUTOPATROLLED,
+ isset( $show['!autopatrolled'] )
+ );
+ $this->addWhereIf(
+ 'rc_patrolled = ' . RecentChange::PRC_AUTOPATROLLED,
+ isset( $show['autopatrolled'] )
+ );
$this->addWhereIf( $idField . ' != page_latest', isset( $show['!top'] ) );
$this->addWhereIf( $idField . ' = page_latest', isset( $show['top'] ) );
$this->addWhereIf( 'rev_parent_id != 0', isset( $show['!new'] ) );
/* Add the patrolled flag */
if ( $this->fld_patrol ) {
- $vals['patrolled'] = $recentChangeInfo['rc_patrolled'] != 0;
+ $vals['patrolled'] = $recentChangeInfo['rc_patrolled'] != RecentChange::PRC_UNPATROLLED;
$vals['unpatrolled'] = ChangesList::isUnpatrolled( (object)$recentChangeInfo, $user );
- $vals['autopatrolled'] = $recentChangeInfo['rc_patrolled'] == 2;
+ $vals['autopatrolled'] = $recentChangeInfo['rc_patrolled'] == RecentChange::PRC_AUTOPATROLLED;
}
if ( $this->fld_loginfo && $recentChangeInfo['rc_type'] == RC_LOG ) {
$params = $this->extractRequestParams();
// Figure out expiry times from the input
- // $params['expiry'] may not be set in subclasses
+ // $params['expiry'] is not set in CentralAuth's ApiGlobalUserRights subclass
if ( isset( $params['expiry'] ) ) {
$expiry = (array)$params['expiry'];
} else {
$expiry = [ 'infinity' ];
}
$add = (array)$params['add'];
- if ( count( $expiry ) !== count( $add ) ) {
+ if ( !$add ) {
+ $expiry = [];
+ } elseif ( count( $expiry ) !== count( $add ) ) {
if ( count( $expiry ) === 1 ) {
$expiry = array_fill( 0, count( $add ), $expiry[0] );
} else {
ApiBase::PARAM_ISMULTI => true
],
];
+ // CentralAuth's ApiGlobalUserRights subclass can't handle expiries
if ( !$this->getUserRightsPage()->canProcessExpiries() ) {
unset( $a['expiry'] );
}
$dbw->update(
'recentchanges',
[
- 'rc_patrolled' => 1
+ 'rc_patrolled' => self::PRC_PATROLLED
],
[
'rc_id' => $this->getAttribute( 'rc_id' )
'rc_last_oldid' => 0,
'rc_bot' => $user->isAllowed( 'bot' ) ? (int)$wgRequest->getBool( 'bot', true ) : 0,
'rc_ip' => self::checkIPAddress( $ip ),
- 'rc_patrolled' => $markPatrolled ? 1 : 0,
+ 'rc_patrolled' => $markPatrolled ? self::PRC_PATROLLED : self::PRC_UNPATROLLED,
'rc_new' => 0, # obsolete
'rc_old_len' => null,
'rc_new_len' => null,
'rc_last_oldid' => $oldRevId,
'rc_bot' => $bot ? 1 : 0,
'rc_ip' => self::checkIPAddress( $ip ),
- 'rc_patrolled' => 1, // Always patrolled, just like log entries
+ 'rc_patrolled' => self::PRC_PATROLLED, // Always patrolled, just like log entries
'rc_new' => 0, # obsolete
'rc_old_len' => null,
'rc_new_len' => null,
[
'rc_timestamp' => $db->timestamp( $this->mNewRev->getTimestamp() ),
'rc_this_oldid' => $this->mNewid,
- 'rc_patrolled' => 0
+ 'rc_patrolled' => RecentChange::PRC_UNPATROLLED
],
__METHOD__
);
__METHOD__,
[ 'FOR UPDATE' ]
);
- $oldRowCount = $dbw->selectField(
+ $oldRowCount = $dbw->selectRowCount(
'oldimage',
- 'COUNT(*)',
+ '*',
[ 'oi_name' => $this->oldName ],
__METHOD__,
[ 'FOR UPDATE' ]
// If the connection is busy with a transaction, then defer the job writes
// until right before the main round commit step. Any errors that bubble
// up will rollback the main commit round.
- // b) mysql/postgres; DB connection is generally a separate CONN_TRX_AUTO handle.
+ // b) mysql/postgres; DB connection is generally a separate CONN_TRX_AUTOCOMMIT handle.
// No transaction is active nor will be started by writes, so enqueue the jobs
// now so that any errors will show up immediately as the interface expects. Any
// errors that bubble up will rollback the main commit round.
return ( $lb->getServerType( $lb->getWriterIndex() ) !== 'sqlite' )
// Keep a separate connection to avoid contention and deadlocks;
// However, SQLite has the opposite behavior due to DB-level locking.
- ? $lb->getConnectionRef( $index, [], $this->wiki, $lb::CONN_TRX_AUTO )
+ ? $lb->getConnectionRef( $index, [], $this->wiki, $lb::CONN_TRX_AUTOCOMMIT )
// Jobs insertion will be defered until the PRESEND stage to reduce contention.
: $lb->getConnectionRef( $index, [], $this->wiki );
}
const DOMAIN_ANY = '';
/** @var int DB handle should have DBO_TRX disabled and the caller will leave it as such */
+ const CONN_TRX_AUTOCOMMIT = 1;
+ /** @var int Alias for CONN_TRX_AUTOCOMMIT for b/c; deprecated since 1.31 */
const CONN_TRX_AUTO = 1;
/**
/**
* Get a connection handle by server index
*
- * The CONN_TRX_AUTO flag is ignored for databases with ATTR_DB_LEVEL_LOCKING
+ * The CONN_TRX_AUTOCOMMIT flag is ignored for databases with ATTR_DB_LEVEL_LOCKING
* (e.g. sqlite) in order to avoid deadlocks. ILoadBalancer::getServerAttributes()
* can be used to check such flags beforehand.
*
- * If the caller uses $domain or sets CONN_TRX_AUTO in $flags, then it must also
+ * If the caller uses $domain or sets CONN_TRX_AUTOCOMMIT in $flags, then it must also
* call ILoadBalancer::reuseConnection() on the handle when finished using it.
* In all other cases, this is not necessary, though not harmful either.
*
*
* The handle's methods simply wrap those of a Database handle
*
- * The CONN_TRX_AUTO flag is ignored for databases with ATTR_DB_LEVEL_LOCKING
+ * The CONN_TRX_AUTOCOMMIT flag is ignored for databases with ATTR_DB_LEVEL_LOCKING
* (e.g. sqlite) in order to avoid deadlocks. ILoadBalancer::getServerAttributes()
* can be used to check such flags beforehand.
*
* @param int $i Server index or DB_MASTER/DB_REPLICA
* @param array|string|bool $groups Query group(s), or false for the generic reader
* @param string|bool $domain Domain ID, or false for the current domain
- * @param int $flags Bitfield of CONN_* class constants (e.g. CONN_TRX_AUTO)
+ * @param int $flags Bitfield of CONN_* class constants (e.g. CONN_TRX_AUTOCOMMIT)
* @return DBConnRef
*/
public function getConnectionRef( $i, $groups = [], $domain = false, $flags = 0 );
*
* The handle's methods simply wrap those of a Database handle
*
- * The CONN_TRX_AUTO flag is ignored for databases with ATTR_DB_LEVEL_LOCKING
+ * The CONN_TRX_AUTOCOMMIT flag is ignored for databases with ATTR_DB_LEVEL_LOCKING
* (e.g. sqlite) in order to avoid deadlocks. ILoadBalancer::getServerAttributes()
* can be used to check such flags beforehand.
*
* @param int $i Server index or DB_MASTER/DB_REPLICA
* @param array|string|bool $groups Query group(s), or false for the generic reader
* @param string|bool $domain Domain ID, or false for the current domain
- * @param int $flags Bitfield of CONN_* class constants (e.g. CONN_TRX_AUTO)
+ * @param int $flags Bitfield of CONN_* class constants (e.g. CONN_TRX_AUTOCOMMIT)
* @return DBConnRef
*/
public function getLazyConnectionRef( $i, $groups = [], $domain = false, $flags = 0 );
*
* The handle's methods simply wrap those of a Database handle
*
- * The CONN_TRX_AUTO flag is ignored for databases with ATTR_DB_LEVEL_LOCKING
+ * The CONN_TRX_AUTOCOMMIT flag is ignored for databases with ATTR_DB_LEVEL_LOCKING
* (e.g. sqlite) in order to avoid deadlocks. ILoadBalancer::getServerAttributes()
* can be used to check such flags beforehand.
*
* @param int $db Server index or DB_MASTER/DB_REPLICA
* @param array|string|bool $groups Query group(s), or false for the generic reader
* @param string|bool $domain Domain ID, or false for the current domain
- * @param int $flags Bitfield of CONN_* class constants (e.g. CONN_TRX_AUTO)
+ * @param int $flags Bitfield of CONN_* class constants (e.g. CONN_TRX_AUTOCOMMIT)
* @return MaintainableDBConnRef
*/
public function getMaintenanceConnectionRef( $db, $groups = [], $domain = false, $flags = 0 );
* The index must be an actual index into the array. If a connection to the server is
* already open and not considered an "in use" foreign connection, this simply returns it.
*
- * Avoid using CONN_TRX_AUTO for databases with ATTR_DB_LEVEL_LOCKING (e.g. sqlite) in
+ * Avoid using CONN_TRX_AUTOCOMMIT for databases with ATTR_DB_LEVEL_LOCKING (e.g. sqlite) in
* order to avoid deadlocks. ILoadBalancer::getServerAttributes() can be used to check
* such flags beforehand.
*
- * If the caller uses $domain or sets CONN_TRX_AUTO in $flags, then it must also
+ * If the caller uses $domain or sets CONN_TRX_AUTOCOMMIT in $flags, then it must also
* call ILoadBalancer::reuseConnection() on the handle when finished using it.
* In all other cases, this is not necessary, though not harmful either.
*
*
* @param int $i Server index (does not support DB_MASTER/DB_REPLICA)
* @param string|bool $domain Domain ID, or false for the current domain
- * @param int $flags Bitfield of CONN_* class constants (e.g. CONN_TRX_AUTO)
+ * @param int $flags Bitfield of CONN_* class constants (e.g. CONN_TRX_AUTOCOMMIT)
* @return Database|bool Returns false on errors
* @throws DBAccessError
*/
if ( !empty( $connsByServer[$i] ) ) {
/** @var IDatabase[] $serverConns */
$serverConns = $connsByServer[$i];
-
return reset( $serverConns );
}
}
$domain = false; // local connection requested
}
- if ( ( $flags & self::CONN_TRX_AUTO ) === self::CONN_TRX_AUTO ) {
+ if ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) === self::CONN_TRX_AUTOCOMMIT ) {
// Assuming all servers are of the same type (or similar), which is overwhelmingly
// the case, use the master server information to get the attributes. The information
// for $i cannot be used since it might be DB_REPLICA, which might require connection
// rows (e.g. FOR UPDATE) or (b) make small commits during a larger transactions
// to reduce lock contention. None of these apply for sqlite and using separate
// connections just causes self-deadlocks.
- $flags &= ~self::CONN_TRX_AUTO;
- $this->connLogger->info( __METHOD__ . ': ignoring CONN_TRX_AUTO to avoid deadlocks.' );
+ $flags &= ~self::CONN_TRX_AUTOCOMMIT;
+ $this->connLogger->info( __METHOD__ .
+ ': ignoring CONN_TRX_AUTOCOMMIT to avoid deadlocks.' );
}
}
// main set of DB connections but rather its own pool since:
// a) those are usually set to implicitly use transaction rounds via DBO_TRX
// b) those must support the use of explicit transaction rounds via beginMasterChanges()
- $autoCommit = ( ( $flags & self::CONN_TRX_AUTO ) == self::CONN_TRX_AUTO );
+ $autoCommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
if ( $domain !== false ) {
// Connection is to a foreign domain
$domainInstance = DatabaseDomain::newFromId( $domain );
$dbName = $domainInstance->getDatabase();
$prefix = $domainInstance->getTablePrefix();
- $autoCommit = ( ( $flags & self::CONN_TRX_AUTO ) == self::CONN_TRX_AUTO );
+ $autoCommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
if ( $autoCommit ) {
$connFreeKey = self::KEY_FOREIGN_FREE_NOROUND;
}
public function closeConnection( IDatabase $conn ) {
- $serverIndex = $conn->getLBInfo( 'serverIndex' ); // second index level of mConns
+ $serverIndex = $conn->getLBInfo( 'serverIndex' );
foreach ( $this->conns as $type => $connsByServer ) {
if ( !isset( $connsByServer[$serverIndex] ) ) {
continue;
// Log the autopatrol if the log entry is patrollable
if ( $this->getIsPatrollable() &&
- $rc->getAttribute( 'rc_patrolled' ) === 2
+ $rc->getAttribute( 'rc_patrolled' ) === RecentChange::PRC_AUTOPATROLLED
) {
PatrolLog::record( $rc, true, $this->getPerformer() );
}
$index = $this->replicaOnly ? DB_REPLICA : DB_MASTER;
if ( $lb->getServerType( $lb->getWriterIndex() ) !== 'sqlite' ) {
// Keep a separate connection to avoid contention and deadlocks
- $db = $lb->getConnection( $index, [], false, $lb::CONN_TRX_AUTO );
+ $db = $lb->getConnection( $index, [], false, $lb::CONN_TRX_AUTOCOMMIT );
// @TODO: Use a blank trx profiler to ignore expections as this is a cache
} else {
// However, SQLite has the opposite behavior due to DB-level locking.
if ( $wgUseRCPatrol ) {
// Mark all reverted edits as patrolled
- $set['rc_patrolled'] = 1;
+ $set['rc_patrolled'] = RecentChange::PRC_PATROLLED;
}
if ( count( $set ) ) {
// Same format as filterGroupDefinitions, but for a single group (reviewStatus)
// that is registered conditionally.
+ private $legacyReviewStatusFilterGroupDefinition;
+
+ // Single filter group registered conditionally
private $reviewStatusFilterGroupDefinition;
- // Single filter registered conditionally
+ // Single filter group registered conditionally
private $hideCategorizationFilterDefinition;
/**
]
],
- // reviewStatus (conditional)
+ // significance (conditional)
[
'name' => 'significance',
];
- $this->reviewStatusFilterGroupDefinition = [
+ $this->legacyReviewStatusFilterGroupDefinition = [
[
- 'name' => 'reviewStatus',
+ 'name' => 'legacyReviewStatus',
'title' => 'rcfilters-filtergroup-reviewstatus',
'class' => ChangesListBooleanFilterGroup::class,
- 'priority' => -5,
'filters' => [
[
'name' => 'hidepatrolled',
- 'label' => 'rcfilters-filter-patrolled-label',
- 'description' => 'rcfilters-filter-patrolled-description',
// rcshowhidepatr-show, rcshowhidepatr-hide
// wlshowhidepatr
'showHideSuffix' => 'showhidepatr',
) {
$conds[] = 'rc_patrolled = 0';
},
- 'cssClassSuffix' => 'patrolled',
- 'isRowApplicableCallable' => function ( $ctx, $rc ) {
- return $rc->getAttribute( 'rc_patrolled' );
- },
+ 'isReplacedInStructuredUi' => true,
],
[
'name' => 'hideunpatrolled',
- 'label' => 'rcfilters-filter-unpatrolled-label',
- 'description' => 'rcfilters-filter-unpatrolled-description',
'default' => false,
'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
&$query_options, &$join_conds
) {
$conds[] = 'rc_patrolled != 0';
},
- 'cssClassSuffix' => 'unpatrolled',
+ 'isReplacedInStructuredUi' => true,
+ ],
+ ],
+ ]
+ ];
+
+ $this->reviewStatusFilterGroupDefinition = [
+ [
+ 'name' => 'reviewStatus',
+ 'title' => 'rcfilters-filtergroup-reviewstatus',
+ 'class' => ChangesListStringOptionsFilterGroup::class,
+ 'isFullCoverage' => true,
+ 'priority' => -5,
+ 'filters' => [
+ [
+ 'name' => 'unpatrolled',
+ 'label' => 'rcfilters-filter-reviewstatus-unpatrolled-label',
+ 'description' => 'rcfilters-filter-reviewstatus-unpatrolled-description',
+ 'cssClassSuffix' => 'reviewstatus-unpatrolled',
+ 'isRowApplicableCallable' => function ( $ctx, $rc ) {
+ return $rc->getAttribute( 'rc_patrolled' ) == RecentChange::PRC_UNPATROLLED;
+ },
+ ],
+ [
+ 'name' => 'manual',
+ 'label' => 'rcfilters-filter-reviewstatus-manual-label',
+ 'description' => 'rcfilters-filter-reviewstatus-manual-description',
+ 'cssClassSuffix' => 'reviewstatus-manual',
'isRowApplicableCallable' => function ( $ctx, $rc ) {
- return !$rc->getAttribute( 'rc_patrolled' );
+ return $rc->getAttribute( 'rc_patrolled' ) == RecentChange::PRC_PATROLLED;
+ },
+ ],
+ [
+ 'name' => 'auto',
+ 'label' => 'rcfilters-filter-reviewstatus-auto-label',
+ 'description' => 'rcfilters-filter-reviewstatus-auto-description',
+ 'cssClassSuffix' => 'reviewstatus-auto',
+ 'isRowApplicableCallable' => function ( $ctx, $rc ) {
+ return $rc->getAttribute( 'rc_patrolled' ) == RecentChange::PRC_AUTOPATROLLED;
},
],
],
+ 'default' => ChangesListStringOptionsFilterGroup::NONE,
+ 'queryCallable' => function ( $specialPageClassName, $ctx, $dbr,
+ &$tables, &$fields, &$conds, &$query_options, &$join_conds, $selected
+ ) {
+ if ( $selected === [] ) {
+ return;
+ }
+ $rcPatrolledValues = [
+ 'unpatrolled' => RecentChange::PRC_UNPATROLLED,
+ 'manual' => RecentChange::PRC_PATROLLED,
+ 'auto' => RecentChange::PRC_AUTOPATROLLED,
+ ];
+ // e.g. rc_patrolled IN (0, 2)
+ $conds['rc_patrolled'] = array_map( function ( $s ) use ( $rcPatrolledValues ) {
+ return $rcPatrolledValues[ $s ];
+ }, $selected );
+ }
]
];
// information to all users just because the user that saves the edit can
// patrol or is logged in)
if ( !$this->including() && $this->getUser()->useRCPatrol() ) {
+ $this->registerFiltersFromDefinitions( $this->legacyReviewStatusFilterGroupDefinition );
$this->registerFiltersFromDefinitions( $this->reviewStatusFilterGroupDefinition );
}
}
/**
- * Replace old options 'hideanons' or 'hideliu' with structured UI equivalent
+ * Replace old options with their structured UI equivalents
*
* @param FormOptions $opts
* @return bool True if the change was made
return false;
}
+ $changed = false;
+
// At this point 'hideanons' and 'hideliu' cannot be both true,
// because fixBackwardsCompatibilityOptions resets (at least) 'hideanons' in such case
if ( $opts[ 'hideanons' ] ) {
$opts->reset( 'hideanons' );
$opts[ 'userExpLevel' ] = 'registered';
- return true;
+ $changed = true;
}
if ( $opts[ 'hideliu' ] ) {
$opts->reset( 'hideliu' );
$opts[ 'userExpLevel' ] = 'unregistered';
- return true;
+ $changed = true;
}
- return false;
+ if ( $this->getFilterGroup( 'legacyReviewStatus' ) ) {
+ if ( $opts[ 'hidepatrolled' ] ) {
+ $opts->reset( 'hidepatrolled' );
+ $opts[ 'reviewStatus' ] = 'unpatrolled';
+ $changed = true;
+ }
+
+ if ( $opts[ 'hideunpatrolled' ] ) {
+ $opts->reset( 'hideunpatrolled' );
+ $opts[ 'reviewStatus' ] = implode(
+ ChangesListStringOptionsFilterGroup::SEPARATOR,
+ [ 'manual', 'auto' ]
+ );
+ $changed = true;
+ }
+ }
+
+ return $changed;
}
/**
$reviewStatus = $this->getFilterGroup( 'reviewStatus' );
if ( $reviewStatus !== null ) {
// Conditional on feature being available and rights
- $hidePatrolled = $reviewStatus->getFilter( 'hidepatrolled' );
- $hidePatrolled->setDefault( $user->getBoolOption( 'hidepatrolled' ) );
+ if ( $user->getBoolOption( 'hidepatrolled' ) ) {
+ $reviewStatus->setDefault( 'unpatrolled' );
+ $legacyReviewStatus = $this->getFilterGroup( 'legacyReviewStatus' );
+ $legacyHidePatrolled = $legacyReviewStatus->getFilter( 'hidepatrolled' );
+ $legacyHidePatrolled->setDefault( true );
+ }
}
$changeType = $this->getFilterGroup( 'changeType' );
$reviewStatus = $this->getFilterGroup( 'reviewStatus' );
if ( $reviewStatus !== null ) {
// Conditional on feature being available and rights
- $hidePatrolled = $reviewStatus->getFilter( 'hidepatrolled' );
- $hidePatrolled->setDefault( $user->getBoolOption( 'watchlisthidepatrolled' ) );
+ if ( $user->getBoolOption( 'watchlisthidepatrolled' ) ) {
+ $reviewStatus->setDefault( 'unpatrolled' );
+ $legacyReviewStatus = $this->getFilterGroup( 'legacyReviewStatus' );
+ $legacyHidePatrolled = $legacyReviewStatus->getFilter( 'hidepatrolled' );
+ $legacyHidePatrolled->setDefault( true );
+ }
}
$authorship = $this->getFilterGroup( 'authorship' );
* Get the user's current setting for a given option.
*
* @param string $oname The option to check
- * @param string $defaultOverride A default value returned if the option does not exist
+ * @param string|array $defaultOverride A default value returned if the option does not exist
* @param bool $ignoreHidden Whether to ignore the effects of $wgHiddenPrefs
* @return string|array|int|null User's current value for the option
* @see getBoolOption()
"rcfilters-filter-humans-label": "Human (not bot)",
"rcfilters-filter-humans-description": "Edits made by human editors.",
"rcfilters-filtergroup-reviewstatus": "Review status",
- "rcfilters-filter-patrolled-label": "Patrolled",
- "rcfilters-filter-patrolled-description": "Edits marked as patrolled.",
- "rcfilters-filter-unpatrolled-label": "Unpatrolled",
- "rcfilters-filter-unpatrolled-description": "Edits not marked as patrolled.",
+ "rcfilters-filter-reviewstatus-unpatrolled-description": "Edits not manually or automatically marked as patrolled.",
+ "rcfilters-filter-reviewstatus-unpatrolled-label": "Unpatrolled",
+ "rcfilters-filter-reviewstatus-manual-description": "Edits manually marked as patrolled.",
+ "rcfilters-filter-reviewstatus-manual-label": "Manually patrolled",
+ "rcfilters-filter-reviewstatus-auto-description": "Edits by advanced users whose work is automatically marked as patrolled.",
+ "rcfilters-filter-reviewstatus-auto-label": "Autopatrolled",
"rcfilters-filtergroup-significance": "Significance",
"rcfilters-filter-minor-label": "Minor edits",
"rcfilters-filter-minor-description": "Edits the author labeled as minor.",
"rcfilters-filter-humans-label": "Label for the filter for showing edits made by human editors.",
"rcfilters-filter-humans-description": "Description for the filter for showing edits made by human editors.",
"rcfilters-filtergroup-reviewstatus": "Title for the filter group about review status (in core this is whether it's been patrolled)",
- "rcfilters-filter-patrolled-label": "Label for the filter for showing patrolled edits",
- "rcfilters-filter-patrolled-description": "Label for the filter showing patrolled edits",
- "rcfilters-filter-unpatrolled-label": "Label for the filter for showing unpatrolled edits",
- "rcfilters-filter-unpatrolled-description": "Description for the filter for showing unpatrolled edits",
+ "rcfilters-filter-reviewstatus-manual-description": "Description for the filter showing manually patrolled edits",
+ "rcfilters-filter-reviewstatus-manual-label": "Label for the filter showing manually patrolled edits",
+ "rcfilters-filter-reviewstatus-auto-description": "Description for the filter showing automatically patrolled edits",
+ "rcfilters-filter-reviewstatus-auto-label": "Label for the filter showing automatically patrolled edits",
+ "rcfilters-filter-reviewstatus-unpatrolled-description": "Description for the filter for showing unpatrolled edits",
+ "rcfilters-filter-reviewstatus-unpatrolled-label": "Label for the filter for showing unpatrolled edits",
"rcfilters-filtergroup-significance": "Title for the filter group for edit significance.\n{{Identical|Significance}}",
"rcfilters-filter-minor-label": "Label for the filter for showing edits marked as minor.",
"rcfilters-filter-minor-description": "Description for the filter for showing edits marked as minor.",
// Define this so scripts can easily find doMaintenance.php
define( 'RUN_MAINTENANCE_IF_MAIN', __DIR__ . '/doMaintenance.php' );
+
+/**
+ * @deprecated since 1.31
+ */
define( 'DO_MAINTENANCE', RUN_MAINTENANCE_IF_MAIN ); // original name, harmless
$maintClass = false;
}
define( 'MW_DB', $bits[0] );
define( 'MW_PREFIX', $bits[1] );
+ } elseif ( isset( $this->mOptions['server'] ) ) {
+ // Provide the option for site admins to detect and configure
+ // multiple wikis based on server names. This offers --server
+ // as alternative to --wiki.
+ // See https://www.mediawiki.org/wiki/Manual:Wiki_family
+ $_SERVER['SERVER_NAME'] = $this->mOptions['server'];
}
if ( !is_readable( $settingsFile ) ) {
"must exist and be readable in the source directory.\n" .
"Use --conf to specify it." );
}
- if ( isset( $this->mOptions['server'] ) ) {
- $_SERVER['SERVER_NAME'] = $this->mOptions['server'];
- }
$wgCommandLineMode = true;
return $settingsFile;
min-width: 20em;
}
+/* Hide empty live-log textarea */
+#config-live-log textarea:empty {
+ display: none;
+}
+
/* tooltip styles */
.config-help-field-hint {
display: none;
-ms-user-select: none;
user-select: none;
}
+.mw-collapsible-toggle:before {
+ content: '[';
+}
+.mw-collapsible-toggle:after {
+ content: ']';
+}
/* Align the toggle based on the direction of the content language */
/* @noflip */
.mw-content-ltr .mw-collapsible-toggle,
* @class jQuery.plugin.makeCollapsible
*/
( function ( $, mw ) {
-
/**
* Handler for a click on a collapsible toggler.
*
role: 'button',
tabindex: 0
} )
- .prepend( '<span>[</span>' )
- .append( '<span>]</span>' )
.on( 'click.mw-collapsible keypress.mw-collapsible', actionHandler );
};
// Parent constructor
mw.widgets.TitleOptionWidget.parent.call( this, config );
+ // Remove check icon
+ this.checkIcon.$element.remove();
+
// Initialization
this.$label.attr( 'href', config.url );
this.$element.addClass( 'mw-widget-titleOptionWidget' );
return false;
} );// hooks::register
+ // Reset the service in case any other tests already cached some prefixes.
+ MediaWikiServices::getInstance()->resetServiceForTesting( 'InterwikiLookup' );
+
return function () {
// Tear down
Hooks::clear( 'InterwikiLoadPrefix' );
+ MediaWikiServices::getInstance()->resetServiceForTesting( 'InterwikiLookup' );
};
}
MediaWikiServices::forceGlobalInstance( $oldServices );
$newServices->destroy();
+
+ // No exception was thrown, avoid being risky
+ $this->assertTrue( true );
}
public function testResetChildProcessServices() {
public static function clear() {
self::$testUsers = [];
}
+
+ /**
+ * @todo It would be nice if this were a non-static method of TestUser
+ * instead, but that doesn't seem possible without friends?
+ *
+ * @return bool True if it's safe to modify the user
+ */
+ public static function isMutable( User $user ) {
+ foreach ( self::$testUsers as $key => $testUser ) {
+ if ( $user === $testUser->getUser() ) {
+ return false;
+ }
+ }
+ return true;
+ }
}
*/
private function getNonInternalApiMain( array $requestData, array $headers = [] ) {
$req = $this->getMockBuilder( WebRequest::class )
- ->setMethods( [ 'response', 'getIP' ] )
+ ->setMethods( [ 'response', 'getRawIP' ] )
->getMock();
$response = new FauxResponse();
$req->method( 'response' )->willReturn( $response );
protected static $revIds = [];
public function addDBDataOnce() {
- $user = static::getTestSysop()->getUser();
$title = Title::newFromText( __CLASS__ );
- $page = WikiPage::factory( $title );
- $status = $page->doEditContent(
- ContentHandler::makeContent( 'Test for revdel', $title, CONTENT_MODEL_WIKITEXT ),
- __METHOD__ . ' Test for revdel', 0, false, $user
- );
- if ( !$status->isOK() ) {
- $this->fail( "Failed to create $title: " . $status->getWikiText( false, false, 'en' ) );
- }
+ $status = $this->editPage( __CLASS__, 'Test for revdel' );
self::$pageId = $status->value['revision']->getPage();
self::$revIds['revdel'] = $status->value['revision']->getId();
- $status = $page->doEditContent(
- ContentHandler::makeContent( 'Test for oldid', $title, CONTENT_MODEL_WIKITEXT ),
- __METHOD__ . ' Test for oldid', 0, false, $user
- );
- if ( !$status->isOK() ) {
- $this->fail( "Failed to edit $title: " . $status->getWikiText( false, false, 'en' ) );
- }
+ $status = $this->editPage( __CLASS__, 'Test for suppressed' );
+ self::$revIds['suppressed'] = $status->value['revision']->getId();
+
+ $status = $this->editPage( __CLASS__, 'Test for oldid' );
self::$revIds['oldid'] = $status->value['revision']->getId();
- $status = $page->doEditContent(
- ContentHandler::makeContent( 'Test for latest', $title, CONTENT_MODEL_WIKITEXT ),
- __METHOD__ . ' Test for latest', 0, false, $user
+ $status = $this->editPage( __CLASS__, 'Test for latest' );
+ self::$revIds['latest'] = $status->value['revision']->getId();
+
+ $this->revisionDelete( self::$revIds['revdel'] );
+ $this->revisionDelete(
+ self::$revIds['suppressed'],
+ [ Revision::DELETED_TEXT => 1, Revision::DELETED_RESTRICTED => 1 ]
);
- if ( !$status->isOK() ) {
- $this->fail( "Failed to edit $title: " . $status->getWikiText( false, false, 'en' ) );
+
+ Title::clearCaches(); // Otherwise it has the wrong latest revision for some reason
+ }
+
+ /**
+ * Assert that the given result of calling $this->doApiRequest() with
+ * action=parse resulted in $html, accounting for the boilerplate that the
+ * parser adds around the parsed page. Also asserts that warnings match
+ * the provided $warning.
+ *
+ * @param string $html Expected HTML
+ * @param array $res Returned from doApiRequest()
+ * @param string|null $warnings Exact value of expected warnings, null for
+ * no warnings
+ */
+ protected function assertParsedTo( $expected, array $res, $warnings = null ) {
+ $this->doAssertParsedTo( $expected, $res, $warnings, [ $this, 'assertSame' ] );
+ }
+
+ /**
+ * Same as above, but asserts that the HTML matches a regexp instead of a
+ * literal string match.
+ *
+ * @param string $html Expected HTML
+ * @param array $res Returned from doApiRequest()
+ * @param string|null $warnings Exact value of expected warnings, null for
+ * no warnings
+ */
+ protected function assertParsedToRegExp( $expected, array $res, $warnings = null ) {
+ $this->doAssertParsedTo( $expected, $res, $warnings, [ $this, 'assertRegExp' ] );
+ }
+
+ private function doAssertParsedTo( $expected, array $res, $warnings, callable $callback ) {
+ $html = $res[0]['parse']['text'];
+
+ $expectedStart = '<div class="mw-parser-output">';
+ $this->assertSame( $expectedStart, substr( $html, 0, strlen( $expectedStart ) ) );
+
+ $html = substr( $html, strlen( $expectedStart ) );
+
+ if ( $res[1]->getBool( 'disablelimitreport' ) ) {
+ $expectedEnd = "</div>";
+ $this->assertSame( $expectedEnd, substr( $html, -strlen( $expectedEnd ) ) );
+
+ $html = substr( $html, 0, strlen( $html ) - strlen( $expectedEnd ) );
+ } else {
+ $expectedEnd = '#\n<!-- \nNewPP limit report\n(?>.+?\n-->)\n' .
+ '<!--\nTransclusion expansion time report \(%,ms,calls,template\)\n(?>.*?\n-->)\n' .
+ '</div>(\n<!-- Saved in parser cache (?>.*?\n -->)\n)?$#s';
+ $this->assertRegExp( $expectedEnd, $html );
+
+ $html = preg_replace( $expectedEnd, '', $html );
}
- self::$revIds['latest'] = $status->value['revision']->getId();
- RevisionDeleter::createList(
- 'revision', RequestContext::getMain(), $title, [ self::$revIds['revdel'] ]
- )->setVisibility( [
- 'value' => [
- Revision::DELETED_TEXT => 1,
+ call_user_func( $callback, $expected, $html );
+
+ if ( $warnings === null ) {
+ $this->assertCount( 1, $res[0] );
+ } else {
+ $this->assertCount( 2, $res[0] );
+ // This deliberately fails if there are extra warnings
+ $this->assertSame( [ 'parse' => [ 'warnings' => $warnings ] ], $res[0]['warnings'] );
+ }
+ }
+
+ /**
+ * Set up an interwiki entry for testing.
+ */
+ protected function setupInterwiki() {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->insert(
+ 'interwiki',
+ [
+ 'iw_prefix' => 'madeuplanguage',
+ 'iw_url' => "https://example.com/wiki/$1",
+ 'iw_api' => '',
+ 'iw_wikiid' => '',
+ 'iw_local' => false,
],
- 'comment' => 'Test for revdel',
- ] );
+ __METHOD__,
+ 'IGNORE'
+ );
- Title::clearCaches(); // Otherwise it has the wrong latest revision for some reason
+ $this->setMwGlobals( 'wgExtraInterlanguageLinkPrefixes', [ 'madeuplanguage' ] );
+ $this->tablesUsed[] = 'interwiki';
+ }
+
+ /**
+ * Set up a skin for testing.
+ *
+ * @todo Should this code be in MediaWikiTestCase or something?
+ */
+ protected function setupSkin() {
+ $factory = new SkinFactory();
+ $factory->register( 'testing', 'Testing', function () {
+ $skin = $this->getMockBuilder( SkinFallback::class )
+ ->setMethods( [ 'getDefaultModules', 'setupSkinUserCss' ] )
+ ->getMock();
+ $skin->expects( $this->once() )->method( 'getDefaultModules' )
+ ->willReturn( [
+ 'core' => [ 'foo', 'bar' ],
+ 'content' => [ 'baz' ]
+ ] );
+ $skin->expects( $this->once() )->method( 'setupSkinUserCss' )
+ ->will( $this->returnCallback( function ( OutputPage $out ) {
+ $out->addModuleStyles( 'foo.styles' );
+ } ) );
+ return $skin;
+ } );
+ $this->setService( 'SkinFactory', $factory );
}
public function testParseByName() {
'action' => 'parse',
'page' => __CLASS__,
] );
- $this->assertContains( 'Test for latest', $res[0]['parse']['text'] );
+ $this->assertParsedTo( "<p>Test for latest\n</p>", $res );
$res = $this->doApiRequest( [
'action' => 'parse',
'page' => __CLASS__,
'disablelimitreport' => 1,
] );
- $this->assertContains( 'Test for latest', $res[0]['parse']['text'] );
+ $this->assertParsedTo( "<p>Test for latest\n</p>", $res );
}
public function testParseById() {
'action' => 'parse',
'pageid' => self::$pageId,
] );
- $this->assertContains( 'Test for latest', $res[0]['parse']['text'] );
+ $this->assertParsedTo( "<p>Test for latest\n</p>", $res );
}
public function testParseByOldId() {
'action' => 'parse',
'oldid' => self::$revIds['oldid'],
] );
- $this->assertContains( 'Test for oldid', $res[0]['parse']['text'] );
+ $this->assertParsedTo( "<p>Test for oldid\n</p>", $res );
$this->assertArrayNotHasKey( 'textdeleted', $res[0]['parse'] );
$this->assertArrayNotHasKey( 'textsuppressed', $res[0]['parse'] );
}
- public function testParseRevDel() {
- $user = static::getTestUser()->getUser();
- $sysop = static::getTestSysop()->getUser();
-
- try {
- $this->doApiRequest( [
- 'action' => 'parse',
- 'oldid' => self::$revIds['revdel'],
- ], null, null, $user );
- $this->fail( "API did not return an error as expected" );
- } catch ( ApiUsageException $ex ) {
- $this->assertTrue( ApiTestCase::apiExceptionHasCode( $ex, 'permissiondenied' ),
- "API failed with error 'permissiondenied'" );
- }
-
+ public function testRevDel() {
$res = $this->doApiRequest( [
'action' => 'parse',
'oldid' => self::$revIds['revdel'],
- ], null, null, $sysop );
- $this->assertContains( 'Test for revdel', $res[0]['parse']['text'] );
+ ] );
+
+ $this->assertParsedTo( "<p>Test for revdel\n</p>", $res );
$this->assertArrayHasKey( 'textdeleted', $res[0]['parse'] );
$this->assertArrayNotHasKey( 'textsuppressed', $res[0]['parse'] );
}
- public function testParseNonexistentPage() {
+ public function testRevDelNoPermission() {
+ $this->setExpectedException( ApiUsageException::class,
+ "You don't have permission to view deleted revision text." );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'oldid' => self::$revIds['revdel'],
+ ], null, null, static::getTestUser()->getUser() );
+ }
+
+ public function testSuppressed() {
+ $this->setGroupPermissions( 'sysop', 'viewsuppressed', true );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'oldid' => self::$revIds['suppressed']
+ ] );
+
+ $this->assertParsedTo( "<p>Test for suppressed\n</p>", $res );
+ $this->assertArrayHasKey( 'textsuppressed', $res[0]['parse'] );
+ $this->assertArrayHasKey( 'textdeleted', $res[0]['parse'] );
+ }
+
+ public function testNonexistentPage() {
try {
$this->doApiRequest( [
'action' => 'parse',
}
}
- public function testSkinModules() {
- $factory = new SkinFactory();
- $factory->register( 'testing', 'Testing', function () {
- $skin = $this->getMockBuilder( SkinFallback::class )
- ->setMethods( [ 'getDefaultModules', 'setupSkinUserCss' ] )
- ->getMock();
- $skin->expects( $this->once() )->method( 'getDefaultModules' )
- ->willReturn( [
- 'core' => [ 'foo', 'bar' ],
- 'content' => [ 'baz' ]
- ] );
- $skin->expects( $this->once() )->method( 'setupSkinUserCss' )
- ->will( $this->returnCallback( function ( OutputPage $out ) {
- $out->addModuleStyles( 'foo.styles' );
- } ) );
- return $skin;
- } );
- $this->setService( 'SkinFactory', $factory );
+ public function testTitleProvided() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => 'Some interesting page',
+ 'text' => '{{PAGENAME}} has attracted my attention',
+ ] );
+
+ $this->assertParsedTo( "<p>Some interesting page has attracted my attention\n</p>", $res );
+ }
+
+ public function testSection() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name,
+ "Intro\n\n== Section 1 ==\n\nContent 1\n\n== Section 2 ==\n\nContent 2" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => $name,
+ 'section' => 1,
+ ] );
+
+ $this->assertParsedToRegExp( '!<h2>.*Section 1.*</h2>\n<p>Content 1\n</p>!', $res );
+ }
+
+ public function testInvalidSection() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'The "section" parameter must be a valid section ID or "new".' );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'section' => 'T-new',
+ ] );
+ }
+
+ public function testSectionNoContent() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $status = $this->editPage( $name,
+ "Intro\n\n== Section 1 ==\n\nContent 1\n\n== Section 2 ==\n\nContent 2" );
+
+ $this->setExpectedException( ApiUsageException::class,
+ "Missing content for page ID {$status->value['revision']->getPage()}." );
+
+ $this->db->delete( 'revision', [ 'rev_id' => $status->value['revision']->getId() ] );
+
+ // Suppress warning in WikiPage::getContentModel
+ Wikimedia\suppressWarnings();
+ try {
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => $name,
+ 'section' => 1,
+ ] );
+ } finally {
+ Wikimedia\restoreWarnings();
+ }
+ }
+
+ public function testNewSectionWithPage() {
+ $this->setExpectedException( ApiUsageException::class,
+ '"section=new" cannot be combined with the "oldid", "pageid" or "page" ' .
+ 'parameters. Please use "title" and "text".' );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => __CLASS__,
+ 'section' => 'new',
+ ] );
+ }
+
+ public function testNonexistentOldId() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'There is no revision with ID 2147483647.' );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'oldid' => pow( 2, 31 ) - 1,
+ ] );
+ }
+
+ public function testUnfollowedRedirect() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, "#REDIRECT [[$name 2]]" );
+ $this->editPage( "$name 2", "Some ''text''" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => $name,
+ ] );
+
+ // Can't use assertParsedTo because the parser output is different for
+ // redirects
+ $this->assertRegExp( "/Redirect to:.*$name 2/", $res[0]['parse']['text'] );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testFollowedRedirect() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, "#REDIRECT [[$name 2]]" );
+ $this->editPage( "$name 2", "Some ''text''" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => $name,
+ 'redirects' => true,
+ ] );
+
+ $this->assertParsedTo( "<p>Some <i>text</i>\n</p>", $res );
+ }
+
+ public function testFollowedRedirectById() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $id = $this->editPage( $name, "#REDIRECT [[$name 2]]" )->value['revision']->getPage();
+ $this->editPage( "$name 2", "Some ''text''" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'pageid' => $id,
+ 'redirects' => true,
+ ] );
+
+ $this->assertParsedTo( "<p>Some <i>text</i>\n</p>", $res );
+ }
+
+ public function testInvalidTitle() {
+ $this->setExpectedException( ApiUsageException::class, 'Bad title "|".' );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => '|',
+ ] );
+ }
+
+ public function testTitleWithNonexistentRevId() {
+ $this->setExpectedException( ApiUsageException::class,
+ 'There is no revision with ID 2147483647.' );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'revid' => pow( 2, 31 ) - 1,
+ ] );
+ }
+
+ public function testTitleWithNonMatchingRevId() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => $name,
+ 'revid' => self::$revIds['latest'],
+ 'text' => 'Some text',
+ ] );
+
+ $this->assertParsedTo( "<p>Some text\n</p>", $res,
+ 'r' . self::$revIds['latest'] . " is not a revision of $name." );
+ }
+
+ public function testRevId() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'revid' => self::$revIds['latest'],
+ 'text' => 'My revid is {{REVISIONID}}!',
+ ] );
+
+ $this->assertParsedTo( "<p>My revid is " . self::$revIds['latest'] . "!\n</p>", $res );
+ }
+
+ public function testTitleNoText() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => 'Special:AllPages',
+ ] );
+
+ $this->assertParsedTo( '', $res,
+ '"title" used without "text", and parsed page properties were requested. ' .
+ 'Did you mean to use "page" instead of "title"?' );
+ }
+
+ public function testRevidNoText() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'revid' => self::$revIds['latest'],
+ ] );
+
+ $this->assertParsedTo( '', $res,
+ '"revid" used without "text", and parsed page properties were requested. ' .
+ 'Did you mean to use "oldid" instead of "revid"?' );
+ }
+
+ public function testTextNoContentModel() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'text' => "Some ''text''",
+ ] );
+
+ $this->assertParsedTo( "<p>Some <i>text</i>\n</p>", $res,
+ 'No "title" or "contentmodel" was given, assuming wikitext.' );
+ }
+
+ public function testSerializationError() {
+ $this->setExpectedException( APIUsageException::class,
+ 'Content serialization failed: Could not unserialize content' );
+
+ $this->mergeMwGlobalArrayValue( 'wgContentHandlers',
+ [ 'testing-serialize-error' => 'DummySerializeErrorContentHandler' ] );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'text' => "Some ''text''",
+ 'contentmodel' => 'testing-serialize-error',
+ ] );
+ }
+
+ public function testNewSection() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'section' => 'new',
+ 'sectiontitle' => 'Title',
+ 'text' => 'Content',
+ ] );
+
+ $this->assertParsedToRegExp( '!<h2>.*Title.*</h2>\n<p>Content\n</p>!', $res );
+ }
+
+ public function testExistingSection() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'section' => 1,
+ 'text' => "Intro\n\n== Section 1 ==\n\nContent\n\n== Section 2 ==\n\nMore content",
+ ] );
+
+ $this->assertParsedToRegExp( '!<h2>.*Section 1.*</h2>\n<p>Content\n</p>!', $res );
+ }
+
+ public function testNoPst() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( "Template:$name", "Template ''text''" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'text' => "{{subst:$name}}",
+ 'contentmodel' => 'wikitext',
+ ] );
+
+ $this->assertParsedTo( "<p>{{subst:$name}}\n</p>", $res );
+ }
+
+ public function testPst() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( "Template:$name", "Template ''text''" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'pst' => '',
+ 'text' => "{{subst:$name}}",
+ 'contentmodel' => 'wikitext',
+ 'prop' => 'text|wikitext',
+ ] );
+
+ $this->assertParsedTo( "<p>Template <i>text</i>\n</p>", $res );
+ $this->assertSame( "{{subst:$name}}", $res[0]['parse']['wikitext'] );
+ }
+
+ public function testOnlyPst() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( "Template:$name", "Template ''text''" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'onlypst' => '',
+ 'text' => "{{subst:$name}}",
+ 'contentmodel' => 'wikitext',
+ 'prop' => 'text|wikitext',
+ 'summary' => 'Summary',
+ ] );
+
+ $this->assertSame(
+ [ 'parse' => [
+ 'text' => "Template ''text''",
+ 'wikitext' => "{{subst:$name}}",
+ 'parsedsummary' => 'Summary',
+ ] ],
+ $res[0]
+ );
+ }
+
+ public function testHeadHtml() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => __CLASS__,
+ 'prop' => 'headhtml',
+ ] );
+
+ // Just do a rough sanity check
+ $this->assertRegExp( '#<!DOCTYPE.*<html.*<head.*</head>.*<body#s',
+ $res[0]['parse']['headhtml'] );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testCategoriesHtml() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( $name, "[[Category:$name]]" );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'page' => $name,
+ 'prop' => 'categorieshtml',
+ ] );
+
+ $this->assertRegExp( "#Category.*Category:$name.*$name#",
+ $res[0]['parse']['categorieshtml'] );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testEffectiveLangLinks() {
+ $hookRan = false;
+ $this->setTemporaryHook( 'LanguageLinks',
+ function () use ( &$hookRan ) {
+ $hookRan = true;
+ }
+ );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'text' => '[[zh:' . __CLASS__ . ']]',
+ 'effectivelanglinks' => '',
+ ] );
+
+ $this->assertTrue( $hookRan );
+ $this->assertSame( 'The parameter "effectivelanglinks" has been deprecated.',
+ $res[0]['warnings']['parse']['warnings'] );
+ }
+
+ /**
+ * @param array $arr Extra params to add to API request
+ */
+ private function doTestLangLinks( array $arr = [] ) {
+ $this->setupInterwiki();
+
+ $res = $this->doApiRequest( array_merge( [
+ 'action' => 'parse',
+ 'title' => 'Omelette',
+ 'text' => '[[madeuplanguage:Omelette]]',
+ 'prop' => 'langlinks',
+ ], $arr ) );
+
+ $langLinks = $res[0]['parse']['langlinks'];
+
+ $this->assertCount( 1, $langLinks );
+ $this->assertSame( 'madeuplanguage', $langLinks[0]['lang'] );
+ $this->assertSame( 'Omelette', $langLinks[0]['title'] );
+ $this->assertSame( 'https://example.com/wiki/Omelette', $langLinks[0]['url'] );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testLangLinks() {
+ $this->doTestLangLinks();
+ }
+
+ public function testLangLinksWithSkin() {
+ $this->setupSkin();
+ $this->doTestLangLinks( [ 'useskin' => 'testing' ] );
+ }
+
+ public function testHeadItems() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'text' => '',
+ 'prop' => 'headitems',
+ ] );
+
+ $this->assertSame( [], $res[0]['parse']['headitems'] );
+ $this->assertSame(
+ '"prop=headitems" is deprecated since MediaWiki 1.28. ' .
+ 'Use "prop=headhtml" when creating new HTML documents, ' .
+ 'or "prop=modules|jsconfigvars" when updating a document client-side.',
+ $res[0]['warnings']['parse']['warnings']
+ );
+ }
+
+ public function testHeadItemsWithSkin() {
+ $this->setupSkin();
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'text' => '',
+ 'prop' => 'headitems',
+ 'useskin' => 'testing',
+ ] );
+
+ $this->assertSame( [], $res[0]['parse']['headitems'] );
+ $this->assertSame(
+ '"prop=headitems" is deprecated since MediaWiki 1.28. ' .
+ 'Use "prop=headhtml" when creating new HTML documents, ' .
+ 'or "prop=modules|jsconfigvars" when updating a document client-side.',
+ $res[0]['warnings']['parse']['warnings']
+ );
+ }
+
+ public function testModules() {
+ $this->setTemporaryHook( 'ParserAfterParse',
+ function ( $parser ) {
+ $output = $parser->getOutput();
+ $output->addModules( [ 'foo', 'bar' ] );
+ $output->addModuleScripts( [ 'baz', 'quuz' ] );
+ $output->addModuleStyles( [ 'aaa', 'zzz' ] );
+ $output->addJsConfigVars( [ 'x' => 'y', 'z' => -3 ] );
+ }
+ );
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'text' => 'Content',
+ 'prop' => 'modules|jsconfigvars|encodedjsconfigvars',
+ ] );
+
+ $this->assertSame( [ 'foo', 'bar' ], $res[0]['parse']['modules'] );
+ $this->assertSame( [ 'baz', 'quuz' ], $res[0]['parse']['modulescripts'] );
+ $this->assertSame( [ 'aaa', 'zzz' ], $res[0]['parse']['modulestyles'] );
+ $this->assertSame( [ 'x' => 'y', 'z' => -3 ], $res[0]['parse']['jsconfigvars'] );
+ $this->assertSame( '{"x":"y","z":-3}', $res[0]['parse']['encodedjsconfigvars'] );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testModulesWithSkin() {
+ $this->setupSkin();
$res = $this->doApiRequest( [
'action' => 'parse',
$res[0]['parse']['modulestyles'],
'resp.parse.modulestyles'
);
+ $this->assertSame(
+ [ 'parse' =>
+ [ 'warnings' =>
+ 'Property "modules" was set but not "jsconfigvars" or ' .
+ '"encodedjsconfigvars". Configuration variables are necessary for ' .
+ 'proper module usage.'
+ ]
+ ],
+ $res[0]['warnings']
+ );
+ }
+
+ public function testIndicators() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'text' =>
+ '<indicator name="b">BBB!</indicator>Some text<indicator name="a">aaa</indicator>',
+ 'prop' => 'indicators',
+ ] );
+
+ $this->assertSame(
+ // It seems we return in markup order and not display order
+ [ 'b' => 'BBB!', 'a' => 'aaa' ],
+ $res[0]['parse']['indicators']
+ );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testIndicatorsWithSkin() {
+ $this->setupSkin();
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'text' =>
+ '<indicator name="b">BBB!</indicator>Some text<indicator name="a">aaa</indicator>',
+ 'prop' => 'indicators',
+ 'useskin' => 'testing',
+ ] );
+
+ $this->assertSame(
+ // Now we return in display order rather than markup order
+ [ 'a' => 'aaa', 'b' => 'BBB!' ],
+ $res[0]['parse']['indicators']
+ );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testIwlinks() {
+ $this->setupInterwiki();
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => 'Omelette',
+ 'text' => '[[:madeuplanguage:Omelette]][[madeuplanguage:Spaghetti]]',
+ 'prop' => 'iwlinks',
+ ] );
+
+ $iwlinks = $res[0]['parse']['iwlinks'];
+
+ $this->assertCount( 1, $iwlinks );
+ $this->assertSame( 'madeuplanguage', $iwlinks[0]['prefix'] );
+ $this->assertSame( 'https://example.com/wiki/Omelette', $iwlinks[0]['url'] );
+ $this->assertSame( 'madeuplanguage:Omelette', $iwlinks[0]['title'] );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testLimitReports() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'pageid' => self::$pageId,
+ 'prop' => 'limitreportdata|limitreporthtml',
+ ] );
+
+ // We don't bother testing the actual values here
+ $this->assertInternalType( 'array', $res[0]['parse']['limitreportdata'] );
+ $this->assertInternalType( 'string', $res[0]['parse']['limitreporthtml'] );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testParseTreeNonWikitext() {
+ $this->setExpectedException( ApiUsageException::class,
+ '"prop=parsetree" is only supported for wikitext content.' );
+
+ $this->doApiRequest( [
+ 'action' => 'parse',
+ 'text' => '',
+ 'contentmodel' => 'json',
+ 'prop' => 'parsetree',
+ ] );
+ }
+
+ public function testParseTree() {
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'text' => "Some ''text'' is {{nice|to have|i=think}}",
+ 'contentmodel' => 'wikitext',
+ 'prop' => 'parsetree',
+ ] );
+
+ // Preprocessor_DOM and Preprocessor_Hash give different results here,
+ // so we'll accept either
+ $this->assertRegExp(
+ '#^<root>Some \'\'text\'\' is <template><title>nice</title>' .
+ '<part><name index="1"/><value>to have</value></part>' .
+ '<part><name>i</name>(?:<equals>)?=(?:</equals>)?<value>think</value></part>' .
+ '</template></root>$#',
+ $res[0]['parse']['parsetree']
+ );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ public function testDisableTidy() {
+ $this->setMwGlobals( 'wgTidyConfig', [ 'driver' => 'RemexHtml' ] );
+
+ // Check that disabletidy doesn't have an effect just because tidying
+ // doesn't work for some other reason
+ $res1 = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'text' => "<b>Mixed <i>up</b></i>",
+ 'contentmodel' => 'wikitext',
+ ] );
+ $this->assertParsedTo( "<p><b>Mixed <i>up</i></b>\n</p>", $res1 );
+
+ $res2 = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'text' => "<b>Mixed <i>up</b></i>",
+ 'contentmodel' => 'wikitext',
+ 'disabletidy' => '',
+ ] );
+
+ $this->assertParsedTo( "<p><b>Mixed <i>up</b></i>\n</p>", $res2 );
+ }
+
+ public function testFormatCategories() {
+ $name = ucfirst( __FUNCTION__ );
+
+ $this->editPage( "Category:$name", 'Content' );
+ $this->editPage( 'Category:Hidden', '__HIDDENCAT__' );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'parse',
+ 'title' => __CLASS__,
+ 'text' => "[[Category:$name]][[Category:Foo|Sort me]][[Category:Hidden]]",
+ 'prop' => 'categories',
+ ] );
+
+ $this->assertSame(
+ [ [ 'sortkey' => '', 'category' => $name ],
+ [ 'sortkey' => 'Sort me', 'category' => 'Foo', 'missing' => true ],
+ [ 'sortkey' => '', 'category' => 'Hidden', 'hidden' => true ] ],
+ $res[0]['parse']['categories']
+ );
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
}
}
return $page->doEditContent( ContentHandler::makeContent( $text, $title ), $summary );
}
+ /**
+ * Revision-deletes a revision.
+ *
+ * @param Revision|int $rev Revision to delete
+ * @param array $value Keys are Revision::DELETED_* flags. Values are 1 to set the bit, 0 to
+ * clear, -1 to leave alone. (All other values also clear the bit.)
+ * @param string $comment Deletion comment
+ */
+ protected function revisionDelete(
+ $rev, array $value = [ Revision::DELETED_TEXT => 1 ], $comment = ''
+ ) {
+ if ( is_int( $rev ) ) {
+ $rev = Revision::newFromId( $rev );
+ }
+ RevisionDeleter::createList(
+ 'revision', RequestContext::getMain(), $rev->getTitle(), [ $rev->getId() ]
+ )->setVisibility( [
+ 'value' => $value,
+ 'comment' => $comment,
+ ] );
+ }
+
/**
* Does the API request and returns the result.
*
}
if ( $tokenType !== null ) {
+ if ( $tokenType === 'auto' ) {
+ $tokenType = ( new ApiMain() )->getModuleManager()
+ ->getModule( $params['action'], 'action' )->needsToken();
+ }
$params['token'] = ApiQueryTokens::getToken(
$wgUser, $sessionObj, ApiQueryTokens::getTokenTypeSalts()[$tokenType]
)->toString();
* @return array Result of the API call
*/
protected function doApiRequestWithToken( array $params, array $session = null,
- User $user = null, $tokenType = 'csrf'
+ User $user = null, $tokenType = 'auto'
) {
return $this->doApiRequest( $params, $session, false, $user, $tokenType );
}
--- /dev/null
+<?php
+
+/**
+ * @group API
+ * @group Database
+ * @group medium
+ *
+ * @covers ApiUserrights
+ */
+class ApiUserrightsTest extends ApiTestCase {
+ /**
+ * Unsets $wgGroupPermissions['bureaucrat']['userrights'], and sets
+ * $wgAddGroups['bureaucrat'] and $wgRemoveGroups['bureaucrat'] to the
+ * specified values.
+ *
+ * @param array|bool $add Groups bureaucrats should be allowed to add, true for all
+ * @param array|bool $remove Groups bureaucrats should be allowed to remove, true for all
+ */
+ protected function setPermissions( $add = [], $remove = [] ) {
+ global $wgAddGroups, $wgRemoveGroups;
+
+ $this->setGroupPermissions( 'bureaucrat', 'userrights', false );
+
+ if ( $add ) {
+ $this->stashMwGlobals( 'wgAddGroups' );
+ $wgAddGroups['bureaucrat'] = $add;
+ }
+ if ( $remove ) {
+ $this->stashMwGlobals( 'wgRemoveGroups' );
+ $wgRemoveGroups['bureaucrat'] = $remove;
+ }
+ }
+
+ /**
+ * Perform an API userrights request that's expected to be successful.
+ *
+ * @param array|string $expectedGroups Group(s) that the user is expected
+ * to have after the API request
+ * @param array $params Array to pass to doApiRequestWithToken(). 'action'
+ * => 'userrights' is implicit. If no 'user' or 'userid' is specified,
+ * we add a 'user' parameter. If no 'add' or 'remove' is specified, we
+ * add 'add' => 'sysop'.
+ * @param User|null $user The user that we're modifying. The user must be
+ * mutable, because we're going to change its groups! null means that
+ * we'll make up our own user to modify, and doesn't make sense if 'user'
+ * or 'userid' is specified in $params.
+ */
+ protected function doSuccessfulRightsChange(
+ $expectedGroups = 'sysop', array $params = [], User $user = null
+ ) {
+ $expectedGroups = (array)$expectedGroups;
+ $params['action'] = 'userrights';
+
+ if ( !$user ) {
+ $user = $this->getMutableTestUser()->getUser();
+ }
+
+ $this->assertTrue( TestUserRegistry::isMutable( $user ),
+ 'Immutable user passed to doSuccessfulRightsChange!' );
+
+ if ( !isset( $params['user'] ) && !isset( $params['userid'] ) ) {
+ $params['user'] = $user->getName();
+ }
+ if ( !isset( $params['add'] ) && !isset( $params['remove'] ) ) {
+ $params['add'] = 'sysop';
+ }
+
+ $res = $this->doApiRequestWithToken( $params );
+
+ $user->clearInstanceCache();
+ $this->assertSame( $expectedGroups, $user->getGroups() );
+
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ /**
+ * Perform an API userrights request that's expected to fail.
+ *
+ * @param string $expectedException Expected exception text
+ * @param array $params As for doSuccessfulRightsChange()
+ * @param User|null $user As for doSuccessfulRightsChange(). If there's no
+ * user who will possibly be affected (such as if an invalid username is
+ * provided in $params), pass null.
+ */
+ protected function doFailedRightsChange(
+ $expectedException, array $params = [], User $user = null
+ ) {
+ $params['action'] = 'userrights';
+
+ $this->setExpectedException( ApiUsageException::class, $expectedException );
+
+ if ( !$user ) {
+ // If 'user' or 'userid' is specified and $user was not specified,
+ // the user we're creating now will have nothing to do with the API
+ // request, but that's okay, since we're just testing that it has
+ // no groups.
+ $user = $this->getMutableTestUser()->getUser();
+ }
+
+ $this->assertTrue( TestUserRegistry::isMutable( $user ),
+ 'Immutable user passed to doFailedRightsChange!' );
+
+ if ( !isset( $params['user'] ) && !isset( $params['userid'] ) ) {
+ $params['user'] = $user->getName();
+ }
+ if ( !isset( $params['add'] ) && !isset( $params['remove'] ) ) {
+ $params['add'] = 'sysop';
+ }
+ $expectedGroups = $user->getGroups();
+
+ try {
+ $this->doApiRequestWithToken( $params );
+ } finally {
+ $user->clearInstanceCache();
+ $this->assertSame( $expectedGroups, $user->getGroups() );
+ }
+ }
+
+ public function testAdd() {
+ $this->doSuccessfulRightsChange();
+ }
+
+ public function testBlockedWithUserrights() {
+ global $wgUser;
+
+ $block = new Block( [ 'address' => $wgUser, 'by' => $wgUser->getId(), ] );
+ $block->insert();
+
+ try {
+ $this->doSuccessfulRightsChange();
+ } finally {
+ $block->delete();
+ $wgUser->clearInstanceCache();
+ }
+ }
+
+ public function testBlockedWithoutUserrights() {
+ $user = $this->getTestSysop()->getUser();
+
+ $this->setPermissions( true, true );
+
+ $block = new Block( [ 'address' => $user, 'by' => $user->getId() ] );
+ $block->insert();
+
+ try {
+ $this->doFailedRightsChange( 'You have been blocked from editing.' );
+ } finally {
+ $block->delete();
+ $user->clearInstanceCache();
+ }
+ }
+
+ public function testAddMultiple() {
+ $this->doSuccessfulRightsChange(
+ [ 'bureaucrat', 'sysop' ],
+ [ 'add' => 'bureaucrat|sysop' ]
+ );
+ }
+
+ public function testTooFewExpiries() {
+ $this->doFailedRightsChange(
+ '2 expiry timestamps were provided where 3 were needed.',
+ [ 'add' => 'sysop|bureaucrat|bot', 'expiry' => 'infinity|tomorrow' ]
+ );
+ }
+
+ public function testTooManyExpiries() {
+ $this->doFailedRightsChange(
+ '3 expiry timestamps were provided where 2 were needed.',
+ [ 'add' => 'sysop|bureaucrat', 'expiry' => 'infinity|tomorrow|never' ]
+ );
+ }
+
+ public function testInvalidExpiry() {
+ $this->doFailedRightsChange( 'Invalid expiry time', [ 'expiry' => 'yummy lollipops!' ] );
+ }
+
+ public function testMultipleInvalidExpiries() {
+ $this->doFailedRightsChange(
+ 'Invalid expiry time "foo".',
+ [ 'add' => 'sysop|bureaucrat', 'expiry' => 'foo|bar' ]
+ );
+ }
+
+ public function testWithTag() {
+ ChangeTags::defineTag( 'custom tag' );
+
+ $user = $this->getMutableTestUser()->getUser();
+
+ $this->doSuccessfulRightsChange( 'sysop', [ 'tags' => 'custom tag' ], $user );
+
+ $dbr = wfGetDB( DB_REPLICA );
+ $this->assertSame(
+ 'custom tag',
+ $dbr->selectField(
+ [ 'change_tag', 'logging' ],
+ 'ct_tag',
+ [
+ 'ct_log_id = log_id',
+ 'log_namespace' => NS_USER,
+ 'log_title' => strtr( $user->getName(), ' ', '_' )
+ ],
+ __METHOD__
+ )
+ );
+ }
+
+ public function testWithoutTagPermission() {
+ global $wgGroupPermissions;
+
+ ChangeTags::defineTag( 'custom tag' );
+
+ $this->stashMwGlobals( 'wgGroupPermissions' );
+ $wgGroupPermissions['user']['applychangetags'] = false;
+
+ $this->doFailedRightsChange(
+ 'You do not have permission to apply change tags along with your changes.',
+ [ 'tags' => 'custom tag' ]
+ );
+ }
+
+ public function testNonexistentUser() {
+ $this->doFailedRightsChange(
+ 'There is no user by the name "Nonexistent user". Check your spelling.',
+ [ 'user' => 'Nonexistent user' ]
+ );
+ }
+
+ public function testWebToken() {
+ $sysop = $this->getTestSysop()->getUser();
+ $user = $this->getMutableTestUser()->getUser();
+
+ $token = $sysop->getEditToken( $user->getName() );
+
+ $res = $this->doApiRequest( [
+ 'action' => 'userrights',
+ 'user' => $user->getName(),
+ 'add' => 'sysop',
+ 'token' => $token,
+ ] );
+
+ $user->clearInstanceCache();
+ $this->assertSame( [ 'sysop' ], $user->getGroups() );
+
+ $this->assertArrayNotHasKey( 'warnings', $res[0] );
+ }
+
+ /**
+ * Helper for testCanProcessExpiries that returns a mock ApiUserrights that either can or cannot
+ * process expiries. Although the regular page can process expiries, we use a mock here to
+ * ensure that it's the result of canProcessExpiries() that makes a difference, and not some
+ * error in the way we construct the mock.
+ *
+ * @param bool $canProcessExpiries
+ */
+ private function getMockForProcessingExpiries( $canProcessExpiries ) {
+ $sysop = $this->getTestSysop()->getUser();
+ $user = $this->getMutableTestUser()->getUser();
+
+ $token = $sysop->getEditToken( 'userrights' );
+
+ $main = new ApiMain( new FauxRequest( [
+ 'action' => 'userrights',
+ 'user' => $user->getName(),
+ 'add' => 'sysop',
+ 'token' => $token,
+ ] ) );
+
+ $mockUserRightsPage = $this->getMockBuilder( UserrightsPage::class )
+ ->setMethods( [ 'canProcessExpiries' ] )
+ ->getMock();
+ $mockUserRightsPage->method( 'canProcessExpiries' )->willReturn( $canProcessExpiries );
+
+ $mockApi = $this->getMockBuilder( ApiUserrights::class )
+ ->setConstructorArgs( [ $main, 'userrights' ] )
+ ->setMethods( [ 'getUserRightsPage' ] )
+ ->getMock();
+ $mockApi->method( 'getUserRightsPage' )->willReturn( $mockUserRightsPage );
+
+ return $mockApi;
+ }
+
+ public function testCanProcessExpiries() {
+ $mock1 = $this->getMockForProcessingExpiries( true );
+ $this->assertArrayHasKey( 'expiry', $mock1->getAllowedParams() );
+
+ $mock2 = $this->getMockForProcessingExpiries( false );
+ $this->assertArrayNotHasKey( 'expiry', $mock2->getAllowedParams() );
+ }
+
+ /**
+ * Tests adding and removing various groups with various permissions.
+ *
+ * @dataProvider addAndRemoveGroupsProvider
+ * @param array|null $permissions [ [ $wgAddGroups, $wgRemoveGroups ] ] or null for 'userrights'
+ * to be set in $wgGroupPermissions
+ * @param array $groupsToChange [ [ groups to add ], [ groups to remove ] ]
+ * @param array $expectedGroups Array of expected groups
+ */
+ public function testAddAndRemoveGroups(
+ array $permissions = null, array $groupsToChange, array $expectedGroups
+ ) {
+ if ( $permissions !== null ) {
+ $this->setPermissions( $permissions[0], $permissions[1] );
+ }
+
+ $params = [
+ 'add' => implode( '|', $groupsToChange[0] ),
+ 'remove' => implode( '|', $groupsToChange[1] ),
+ ];
+
+ // We'll take a bot so we have a group to remove
+ $user = $this->getMutableTestUser( [ 'bot' ] )->getUser();
+
+ $this->doSuccessfulRightsChange( $expectedGroups, $params, $user );
+ }
+
+ public function addAndRemoveGroupsProvider() {
+ return [
+ 'Simple add' => [
+ [ [ 'sysop' ], [] ],
+ [ [ 'sysop' ], [] ],
+ [ 'bot', 'sysop' ]
+ ], 'Add with only remove permission' => [
+ [ [], [ 'sysop' ] ],
+ [ [ 'sysop' ], [] ],
+ [ 'bot' ],
+ ], 'Add with global remove permission' => [
+ [ [], true ],
+ [ [ 'sysop' ], [] ],
+ [ 'bot' ],
+ ], 'Simple remove' => [
+ [ [], [ 'bot' ] ],
+ [ [], [ 'bot' ] ],
+ [],
+ ], 'Remove with only add permission' => [
+ [ [ 'bot' ], [] ],
+ [ [], [ 'bot' ] ],
+ [ 'bot' ],
+ ], 'Remove with global add permission' => [
+ [ true, [] ],
+ [ [], [ 'bot' ] ],
+ [ 'bot' ],
+ ], 'Add and remove same new group' => [
+ null,
+ [ [ 'sysop' ], [ 'sysop' ] ],
+ // The userrights code does removals before adds, so it doesn't remove the sysop
+ // group here and only adds it.
+ [ 'bot', 'sysop' ],
+ ], 'Add and remove same existing group' => [
+ null,
+ [ [ 'bot' ], [ 'bot' ] ],
+ // But here it first removes the existing group and then re-adds it.
+ [ 'bot' ],
+ ],
+ ];
+ }
+}
);
$mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key ) );
- $mocks[$key . '2'] = $this->getMockForAbstractClass(
- "MediaWiki\\Auth\\$class", [], "Mock$class"
- );
+ $mocks[$key . '2'] = $this->getMockForAbstractClass( "MediaWiki\\Auth\\$class" );
$mocks[$key . '2']->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key . '2' ) );
- $mocks[$key . '3'] = $this->getMockForAbstractClass(
- "MediaWiki\\Auth\\$class", [], "Mock$class"
- );
+ $mocks[$key . '3'] = $this->getMockForAbstractClass( "MediaWiki\\Auth\\$class" );
$mocks[$key . '3']->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key . '3' ) );
}
) );
for ( $i = 2; $i <= 3; $i++ ) {
- $mocks[$key . $i] = $this->getMockForAbstractClass(
- "MediaWiki\\Auth\\$class", [], "Mock$class"
- );
+ $mocks[$key . $i] = $this->getMockForAbstractClass( "MediaWiki\\Auth\\$class" );
$mocks[$key . $i]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key . $i ) );
$mocks[$key . $i]->expects( $this->any() )->method( 'testUserForCreation' )
$mocks = [];
foreach ( [ 'pre', 'primary', 'secondary' ] as $key ) {
$class = ucfirst( $key ) . 'AuthenticationProvider';
- $mocks[$key] = $this->getMockForAbstractClass(
- "MediaWiki\\Auth\\$class", [], "Mock$class"
- );
+ $mocks[$key] = $this->getMockForAbstractClass( "MediaWiki\\Auth\\$class" );
$mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key ) );
}
$mocks = [];
foreach ( [ 'pre', 'primary', 'secondary' ] as $key ) {
$class = ucfirst( $key ) . 'AuthenticationProvider';
- $mocks[$key] = $this->getMockForAbstractClass(
- "MediaWiki\\Auth\\$class", [], "Mock$class"
- );
+ $mocks[$key] = $this->getMockBuilder( "MediaWiki\\Auth\\$class" )
+ ->setMethods( [
+ 'getUniqueId', 'getAuthenticationRequests', 'providerAllowsAuthenticationDataChange',
+ ] )
+ ->getMockForAbstractClass();
$mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key ) );
$mocks[$key]->expects( $this->any() )->method( 'getAuthenticationRequests' )
PrimaryAuthenticationProvider::TYPE_LINK
] as $type ) {
$class = 'PrimaryAuthenticationProvider';
- $mocks["primary-$type"] = $this->getMockForAbstractClass(
- "MediaWiki\\Auth\\$class", [], "Mock$class"
- );
+ $mocks["primary-$type"] = $this->getMockBuilder( "MediaWiki\\Auth\\$class" )
+ ->setMethods( [
+ 'getUniqueId', 'accountCreationType', 'getAuthenticationRequests',
+ 'providerAllowsAuthenticationDataChange',
+ ] )
+ ->getMockForAbstractClass();
$mocks["primary-$type"]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( "primary-$type" ) );
$mocks["primary-$type"]->expects( $this->any() )->method( 'accountCreationType' )
$this->primaryauthMocks[] = $mocks["primary-$type"];
}
- $mocks['primary2'] = $this->getMockForAbstractClass(
- PrimaryAuthenticationProvider::class, [], "MockPrimaryAuthenticationProvider"
- );
+ $mocks['primary2'] = $this->getMockBuilder( PrimaryAuthenticationProvider::class )
+ ->setMethods( [
+ 'getUniqueId', 'accountCreationType', 'getAuthenticationRequests',
+ 'providerAllowsAuthenticationDataChange',
+ ] )
+ ->getMockForAbstractClass();
$mocks['primary2']->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( 'primary2' ) );
$mocks['primary2']->expects( $this->any() )->method( 'accountCreationType' )
$mocks = [];
foreach ( [ 'primary', 'secondary' ] as $key ) {
$class = ucfirst( $key ) . 'AuthenticationProvider';
- $mocks[$key] = $this->getMockForAbstractClass(
- "MediaWiki\\Auth\\$class", [], "Mock$class"
- );
+ $mocks[$key] = $this->getMockForAbstractClass( "MediaWiki\\Auth\\$class" );
$mocks[$key]->expects( $this->any() )->method( 'getUniqueId' )
->will( $this->returnValue( $key ) );
$mocks[$key]->expects( $this->any() )->method( 'providerAllowsPropertyChange' )
public function testAutoCreateFailOnLogin() {
$username = self::usernameForCreation();
- $mock = $this->getMockForAbstractClass(
- PrimaryAuthenticationProvider::class, [], "MockPrimaryAuthenticationProvider" );
+ $mock = $this->getMockForAbstractClass( PrimaryAuthenticationProvider::class );
$mock->expects( $this->any() )->method( 'getUniqueId' )->will( $this->returnValue( 'primary' ) );
$mock->expects( $this->any() )->method( 'beginPrimaryAuthentication' )
->will( $this->returnValue( AuthenticationResponse::newPass( $username ) ) );
$this->assertTrue( $dbr->getLBInfo( 'master' ), 'DB_REPLICA also gets the master' );
$this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on replica" );
- $dbwAuto = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTO );
- $this->assertFalse( $dbwAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTO" );
+ $dbwAuto = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
+ $this->assertFalse(
+ $dbwAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
$this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on master" );
- $this->assertNotEquals( $dbw, $dbwAuto, "CONN_TRX_AUTO uses separate connection" );
+ $this->assertNotEquals( $dbw, $dbwAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
- $dbrAuto = $lb->getConnection( DB_REPLICA, [], false, $lb::CONN_TRX_AUTO );
- $this->assertFalse( $dbrAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTO" );
+ $dbrAuto = $lb->getConnection( DB_REPLICA, [], false, $lb::CONN_TRX_AUTOCOMMIT );
+ $this->assertFalse(
+ $dbrAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
$this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on replica" );
- $this->assertNotEquals( $dbr, $dbrAuto, "CONN_TRX_AUTO uses separate connection" );
+ $this->assertNotEquals( $dbr, $dbrAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
- $dbwAuto2 = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTO );
- $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTO reuses connections" );
+ $dbwAuto2 = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
+ $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTOCOMMIT reuses connections" );
$lb->closeAll();
}
$this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX set on replica" );
$this->assertWriteForbidden( $dbr );
- $dbwAuto = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTO );
- $this->assertFalse( $dbwAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTO" );
+ $dbwAuto = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
+ $this->assertFalse(
+ $dbwAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
$this->assertTrue( $dbw->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on master" );
- $this->assertNotEquals( $dbw, $dbwAuto, "CONN_TRX_AUTO uses separate connection" );
+ $this->assertNotEquals( $dbw, $dbwAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
- $dbrAuto = $lb->getConnection( DB_REPLICA, [], false, $lb::CONN_TRX_AUTO );
- $this->assertFalse( $dbrAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTO" );
+ $dbrAuto = $lb->getConnection( DB_REPLICA, [], false, $lb::CONN_TRX_AUTOCOMMIT );
+ $this->assertFalse(
+ $dbrAuto->getFlag( $dbw::DBO_TRX ), "No DBO_TRX with CONN_TRX_AUTOCOMMIT" );
$this->assertTrue( $dbr->getFlag( $dbw::DBO_TRX ), "DBO_TRX still set on replica" );
- $this->assertNotEquals( $dbr, $dbrAuto, "CONN_TRX_AUTO uses separate connection" );
+ $this->assertNotEquals( $dbr, $dbrAuto, "CONN_TRX_AUTOCOMMIT uses separate connection" );
- $dbwAuto2 = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTO );
- $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTO reuses connections" );
+ $dbwAuto2 = $lb->getConnection( DB_MASTER, [], false, $lb::CONN_TRX_AUTOCOMMIT );
+ $this->assertEquals( $dbwAuto2, $dbwAuto, "CONN_TRX_AUTOCOMMIT reuses connections" );
$lb->closeAll();
}
->method( 'send' )
->will( $this->returnValue( true ) );
// evil hax
- TestingAccessWrapper::newFromObject( $mockMethod )->matcher->parametersMatcher =
+ $matcher = TestingAccessWrapper::newFromObject( $mockMethod )->matcher;
+ TestingAccessWrapper::newFromObject( $matcher )->parametersMatcher =
new \PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters( [
[ $this->anything(), $this->anything(), [ 'words' ] ],
[ $this->anything(), $this->anything(), [ 'lines' ] ]
class JobQueueDBSingle extends JobQueueDB {
protected function getDB( $index ) {
$lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
- // Override to not use CONN_TRX_AUTO so that we see the same temporary `job` table
+ // Override to not use CONN_TRX_AUTOCOMMIT so that we see the same temporary `job` table
return $lb->getConnection( $index, [], $this->wiki );
}
}
$lb->expects( $this->once() )
->method( 'getConnection' )
- ->with( DB_MASTER, [ 'test' ], 'dummy', ILoadBalancer::CONN_TRX_AUTO )
+ ->with( DB_MASTER, [ 'test' ], 'dummy', ILoadBalancer::CONN_TRX_AUTOCOMMIT )
->willReturnCallback(
function () {
return $this->getDatabaseMock();
$ref = new DBConnRef(
$lb,
- [ DB_MASTER, [ 'test' ], 'dummy', ILoadBalancer::CONN_TRX_AUTO ]
+ [ DB_MASTER, [ 'test' ], 'dummy', ILoadBalancer::CONN_TRX_AUTOCOMMIT ]
);
$this->assertInstanceOf( ResultWrapper::class, $ref->select( 'whatever', '*' ) );
class VersionCheckerTest extends PHPUnit\Framework\TestCase {
use MediaWikiCoversValidator;
+ use PHPUnit4And6Compat;
/**
* @dataProvider provideCheck
$user = $this->getTestSysop()->getUser();
$this->assertConditions(
[ # expected
- 'rc_patrolled = 0',
+ 'rc_patrolled' => 0,
],
[
'hidepatrolled' => 1,
$user = $this->getTestSysop()->getUser();
$this->assertConditions(
[ # expected
- 'rc_patrolled != 0',
+ 'rc_patrolled' => [ 1, 2 ],
],
[
'hideunpatrolled' => 1,
);
}
+ public function testRcReviewStatusFilter() {
+ $user = $this->getTestSysop()->getUser();
+ $this->assertConditions(
+ [ #expected
+ 'rc_patrolled' => 1,
+ ],
+ [
+ 'reviewStatus' => 'manual'
+ ],
+ "rc conditions: reviewStatus=manual",
+ $user
+ );
+ $this->assertConditions(
+ [ #expected
+ 'rc_patrolled' => [ 0, 2 ],
+ ],
+ [
+ 'reviewStatus' => 'unpatrolled;auto'
+ ],
+ "rc conditions: reviewStatus=unpatrolled;auto",
+ $user
+ );
+ }
+
public function testRcHideminorFilter() {
$this->assertConditions(
[ # expected
class BatchRowUpdateTest extends MediaWikiTestCase {
public function testWriterBasicFunctionality() {
- $db = $this->mockDb();
+ $db = $this->mockDb( [ 'update' ] );
$writer = new BatchRowWriter( $db, 'echo_event' );
$updates = [
}
public function testReaderBasicIterate() {
- $db = $this->mockDb();
$batchSize = 2;
- $reader = new BatchRowIterator( $db, 'some_table', 'id_field', $batchSize );
-
$response = $this->genSelectResult( $batchSize, /*numRows*/ 5, function () {
static $i = 0;
return [ 'id_field' => ++$i ];
} );
- $db->expects( $this->exactly( count( $response ) ) )
- ->method( 'select' )
- ->will( $this->consecutivelyReturnFromSelect( $response ) );
+ $db = $this->mockDbConsecutiveSelect( $response );
+ $reader = new BatchRowIterator( $db, 'some_table', 'id_field', $batchSize );
$pos = 0;
foreach ( $reader as $rows ) {
public function testReaderSetFetchColumns(
$message, array $columns, array $primaryKeys, array $fetchColumns
) {
- $db = $this->mockDb();
+ $db = $this->mockDb( [ 'select' ] );
$db->expects( $this->once() )
->method( 'select' )
// only testing second parameter of Database::select
}
protected function mockDbConsecutiveSelect( array $retvals ) {
- $db = $this->mockDb();
+ $db = $this->mockDb( [ 'select', 'addQuotes' ] );
$db->expects( $this->any() )
->method( 'select' )
->will( $this->consecutivelyReturnFromSelect( $retvals ) );
return $res;
}
- protected function mockDb() {
+ protected function mockDb( $methods = [] ) {
// @TODO: mock from Database
// FIXME: the constructor normally sets mAtomicLevels and mSrvCache
$databaseMysql = $this->getMockBuilder( Wikimedia\Rdbms\DatabaseMysqli::class )
->disableOriginalConstructor()
+ ->setMethods( array_merge( [ 'isOpen', 'getApproximateLagStatus' ], $methods ) )
->getMock();
$databaseMysql->expects( $this->any() )
->method( 'isOpen' )
private function getMockCache() {
$mock = $this->getMockBuilder( HashBagOStuff::class )
->disableOriginalConstructor()
+ ->setMethods( [ 'get', 'set', 'delete', 'makeKey' ] )
->getMock();
$mock->expects( $this->any() )
->method( 'makeKey' )
->method( 'selectRow' );
$mockCache = $this->getMockCache();
- $mockDb->expects( $this->never() )
- ->method( 'get' );
- $mockDb->expects( $this->never() )
- ->method( 'set' );
- $mockDb->expects( $this->never() )
- ->method( 'delete' );
+ $mockCache->expects( $this->never() )->method( 'get' );
+ $mockCache->expects( $this->never() )->method( 'set' );
+ $mockCache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with( '0:SomeDbKey:1' );
$store = $this->newWatchedItemStore(
$this->getMockLoadBalancer( $mockDb ),
->method( 'selectRow' );
$mockCache = $this->getMockCache();
- $mockDb->expects( $this->never() )
- ->method( 'get' );
- $mockDb->expects( $this->never() )
- ->method( 'set' );
- $mockDb->expects( $this->never() )
- ->method( 'delete' );
+ $mockCache->expects( $this->never() )->method( 'get' );
+ $mockCache->expects( $this->never() )->method( 'set' );
+ $mockCache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with( '0:SomeTitle:1' );
$store = $this->newWatchedItemStore(
$this->getMockLoadBalancer( $mockDb ),
) );
$mockCache = $this->getMockCache();
- $mockDb->expects( $this->never() )
- ->method( 'get' );
- $mockDb->expects( $this->never() )
- ->method( 'set' );
- $mockDb->expects( $this->never() )
- ->method( 'delete' );
+ $mockCache->expects( $this->never() )->method( 'get' );
+ $mockCache->expects( $this->once() )
+ ->method( 'set' )
+ ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
+ $mockCache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with( '0:SomeDbKey:1' );
$store = $this->newWatchedItemStore(
$this->getMockLoadBalancer( $mockDb ),
->will( $this->returnValue( false ) );
$mockCache = $this->getMockCache();
- $mockDb->expects( $this->never() )
- ->method( 'get' );
- $mockDb->expects( $this->never() )
- ->method( 'set' );
- $mockDb->expects( $this->never() )
- ->method( 'delete' );
+ $mockCache->expects( $this->never() )->method( 'get' );
+ $mockCache->expects( $this->never() )->method( 'set' );
+ $mockCache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with( '0:SomeDbKey:1' );
$store = $this->newWatchedItemStore(
$this->getMockLoadBalancer( $mockDb ),
) );
$mockCache = $this->getMockCache();
- $mockDb->expects( $this->never() )
- ->method( 'get' );
- $mockDb->expects( $this->never() )
- ->method( 'set' );
- $mockDb->expects( $this->never() )
- ->method( 'delete' );
+ $mockCache->expects( $this->never() )->method( 'get' );
+ $mockCache->expects( $this->once() )
+ ->method( 'set' )
+ ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
+ $mockCache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with( '0:SomeDbKey:1' );
$store = $this->newWatchedItemStore(
$this->getMockLoadBalancer( $mockDb ),
) );
$mockCache = $this->getMockCache();
- $mockDb->expects( $this->never() )
- ->method( 'get' );
- $mockDb->expects( $this->never() )
- ->method( 'set' );
- $mockDb->expects( $this->never() )
- ->method( 'delete' );
+ $mockCache->expects( $this->never() )->method( 'get' );
+ $mockCache->expects( $this->once() )
+ ->method( 'set' )
+ ->with( '0:SomeDbKey:1', $this->isType( 'object' ) );
+ $mockCache->expects( $this->once() )
+ ->method( 'delete' )
+ ->with( '0:SomeDbKey:1' );
$store = $this->newWatchedItemStore(
$this->getMockLoadBalancer( $mockDb ),