ApiQueryBase::keyPartToTitle() all removed (deprecated since 1.24).
* ApiQueryBase::checkRowCount() was removed (deprecated since 1.24).
* ApiQueryBase::getDirectionDescription() was removed (deprecated since 1.25).
+* ApiQuery::getGenerators() was removed (deprecated since 1.21).
* ApiQuery::getModules() was removed (deprecated since 1.21).
+* ApiQuery::getModuleType() was removed (deprecated since 1.21).
+* ApiQuery::setGeneratorContinue() was removed (deprecated since 1.24).
* ApiMain::getModules() was removed (deprecated since 1.21).
* ApiBase::getVersion() was removed (deprecated since 1.21).
/**
* Directory for caching data in the local filesystem. Should not be accessible
- * from the web. Set this to false to not use any local caches.
+ * from the web.
*
* Note: if multiple wikis share the same localisation cache directory, they
* must all have the same set of extensions. You can set a directory just for
$msg = ApiBase::makeMessage( $msg, $this->getContext(),
[ $prefix, $param, $name, $path ] );
if ( !$msg ) {
- $this->dieDebug( __METHOD__,
+ self::dieDebug( __METHOD__,
'Value in ApiBase::PARAM_HELP_MSG is not valid' );
}
$msgs[$param] = [ $msg ];
if ( isset( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] ) ) {
if ( !is_array( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] ) ) {
- $this->dieDebug( __METHOD__,
+ self::dieDebug( __METHOD__,
'ApiBase::PARAM_HELP_MSG_PER_VALUE is not valid' );
}
if ( !is_array( $settings[ApiBase::PARAM_TYPE] ) ) {
- $this->dieDebug( __METHOD__,
+ self::dieDebug( __METHOD__,
'ApiBase::PARAM_HELP_MSG_PER_VALUE may only be used when ' .
'ApiBase::PARAM_TYPE is an array' );
}
);
$msgs[$param][] = $m->setContext( $this->getContext() );
} else {
- $this->dieDebug( __METHOD__,
+ self::dieDebug( __METHOD__,
"Value in ApiBase::PARAM_HELP_MSG_PER_VALUE for $value is not valid" );
}
}
if ( isset( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) {
if ( !is_array( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) {
- $this->dieDebug( __METHOD__,
+ self::dieDebug( __METHOD__,
'Value for ApiBase::PARAM_HELP_MSG_APPEND is not an array' );
}
foreach ( $settings[ApiBase::PARAM_HELP_MSG_APPEND] as $m ) {
if ( $m ) {
$msgs[$param][] = $m;
} else {
- $this->dieDebug( __METHOD__,
+ self::dieDebug( __METHOD__,
'Value in ApiBase::PARAM_HELP_MSG_APPEND is not valid' );
}
}
* Initialize the printer function and prepare the output headers.
* @param bool $unused Always false since 1.25
*/
- function initPrinter( $unused = false ) {
+ public function initPrinter( $unused = false ) {
if ( $this->mDisabled ) {
return;
}
);
}
- function addXslt() {
+ protected function addXslt() {
$nt = Title::newFromText( $this->mXslt );
if ( is_null( $nt ) || !$nt->exists() ) {
$this->setWarning( 'Invalid or non-existent stylesheet specified' );
* @param array $pageInfo
* @return void
*/
- function reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo ) {
+ public function reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo ) {
// Add a result entry
$r = [];
parent::reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo );
}
- function getData() {
+ public function getData() {
return $this->mResultArr;
}
}
case LoginForm::THROTTLED:
$result['result'] = 'Throttled';
- $throttle = $this->getConfig()->get( 'PasswordAttemptThrottle' );
$result['wait'] = intval( $loginForm->mThrottleWait );
break;
$result_array = [];
$result_array['title'] = $titleObj->getPrefixedText();
- $result_array['pageid'] = $pageid ? $pageid : $pageObj->getId();
+ $result_array['pageid'] = $pageid ?: $pageObj->getId();
if ( !is_null( $oldid ) ) {
$result_array['revid'] = intval( $oldid );
}
if ( isset( $prop['displaytitle'] ) ) {
- $result_array['displaytitle'] = $p_result->getDisplayTitle() ?
- $p_result->getDisplayTitle() :
+ $result_array['displaytitle'] = $p_result->getDisplayTitle() ?:
$titleObj->getPrefixedText();
}
return $this->mPageSet;
}
- /**
- * Get the generators array mapping module names to class names
- * @deprecated since 1.21, list of generators is maintained by ApiPageSet
- * @return array Array(modulename => classname)
- */
- public function getGenerators() {
- wfDeprecated( __METHOD__, '1.21' );
- $gens = [];
- foreach ( $this->mModuleMgr->getNamesWithClasses() as $name => $class ) {
- if ( is_subclass_of( $class, 'ApiQueryGeneratorBase' ) ) {
- $gens[$name] = $class;
- }
- }
-
- return $gens;
- }
-
- /**
- * Get whether the specified module is a prop, list or a meta query module
- * @deprecated since 1.21, use getModuleManager()->getModuleGroup()
- * @param string $moduleName Name of the module to find type for
- * @return string|null
- */
- function getModuleType( $moduleName ) {
- return $this->getModuleManager()->getModuleGroup( $moduleName );
- }
-
/**
* @return ApiFormatRaw|null
*/
}
}
- /**
- * This method is called by the generator base when generator in the smart-continue
- * mode tries to set 'query-continue' value. ApiQuery stores those values separately
- * until the post-processing when it is known if the generation should continue or repeat.
- * @deprecated since 1.24
- * @param ApiQueryGeneratorBase $module Generator module
- * @param string $paramName
- * @param mixed $paramValue
- * @return bool True if processed, false if this is a legacy continue
- */
- public function setGeneratorContinue( $module, $paramName, $paramValue ) {
- wfDeprecated( __METHOD__, '1.24' );
- $this->getContinuationManager()->addGeneratorContinueParam( $module, $paramName, $paramValue );
- return !$this->getParameter( 'rawcontinue' );
- }
-
/**
* @param ApiPageSet $pageSet Pages to be exported
* @param ApiResult $result Result to output to
$this->dieContinueUsageIf( count( $cont ) != count( $sortby ) );
$where = '';
$i = count( $sortby ) - 1;
- $cont_ns = 0;
- $cont_title = '';
foreach ( array_reverse( $sortby, true ) as $field => $type ) {
$v = $cont[$i];
switch ( $type ) {
case 'ns':
- $cont_ns = (int)$v;
- /* fall through */
case 'int':
$v = (int)$v;
$this->dieContinueUsageIf( $v != $cont[$i] );
break;
-
- case 'title':
- $cont_title = $v;
- /* fall through */
default:
$v = $db->addQuotes( $v );
break;
* 'revdelUser': User to use when checking whether to show revision-deleted fields.
* @return array Result array
*/
- static function getInfo( $file, $prop, $result, $thumbParams = null, $opts = false ) {
+ public static function getInfo( $file, $prop, $result, $thumbParams = null, $opts = false ) {
global $wgContLang;
$anyHidden = false;
}
if ( $text === null ) {
- $format = $this->contentFormat ? $this->contentFormat : $content->getDefaultFormat();
+ $format = $this->contentFormat ?: $content->getDefaultFormat();
$model = $content->getModel();
if ( !$content->isSupportedFormat( $format ) ) {
}
$data = [
- 'url' => $url ? $url : '',
- 'text' => $text ? $text : ''
+ 'url' => $url ?: '',
+ 'text' => $text ?: ''
];
return $this->getResult()->addValue( 'query', $property, $data );
];
}
- function needsToken() {
+ public function needsToken() {
return 'csrf';
}
- function mustBePosted() {
+ public function mustBePosted() {
return true;
}
- function isWriteMode() {
+ public function isWriteMode() {
return true;
}
- function isInternal() {
+ public function isInternal() {
return true;
}
}
*
* @param Title $title
*/
- public function setTitle( Title $title ) {
+ public function setTitle( Title $title = null ) {
$this->title = $title;
// Erase the WikiPage so a new one with the new title gets created.
$this->wikipage = null;
return $approxLag;
}
- /**
- * Wait for the slave to catch up to a given master position.
- * @todo Return values for this and base class are rubbish
- *
- * @param DBMasterPos|MySQLMasterPos $pos
- * @param int $timeout The maximum number of seconds to wait for synchronisation
- * @return int Zero if the slave was past that position already,
- * greater than zero if we waited for some period of time, less than
- * zero if we timed out.
- */
function masterPosWait( DBMasterPos $pos, $timeout ) {
+ if ( !( $pos instanceof MySQLMasterPos ) ) {
+ throw new InvalidArgumentException( "Position not an instance of MySQLMasterPos" );
+ }
+
if ( $this->lastKnownSlavePos && $this->lastKnownSlavePos->hasReached( $pos ) ) {
- return '0'; // http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html
+ return 0;
}
# Commit any open transactions
# Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
$encFile = $this->addQuotes( $pos->file );
$encPos = intval( $pos->pos );
- $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
- $res = $this->doQuery( $sql );
-
- $status = false;
- if ( $res ) {
- $row = $this->fetchRow( $res );
- if ( $row ) {
- $status = $row[0]; // can be NULL, -1, or 0+ per the MySQL manual
- if ( ctype_digit( $status ) ) { // success
- $this->lastKnownSlavePos = $pos;
- }
+ $res = $this->doQuery( "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)" );
+
+ $row = $res ? $this->fetchRow( $res ) : false;
+ if ( !$row ) {
+ throw new DBExpectedError( $this, "Failed to query MASTER_POS_WAIT()" );
+ }
+
+ // Result can be NULL (error), -1 (timeout), or 0+ per the MySQL manual
+ $status = ( $row[0] !== null ) ? intval( $row[0] ) : null;
+ if ( $status === null ) {
+ // T126436: jobs programmed to wait on master positions might be referencing binlogs
+ // with an old master hostname. Such calls make MASTER_POS_WAIT() return null. Try
+ // to detect this and treat the slave as having reached the position; a proper master
+ // switchover already requires that the new master be caught up before the switch.
+ $slavePos = $this->getSlavePos();
+ if ( $slavePos && !$slavePos->channelsMatch( $pos ) ) {
+ $this->lastKnownSlavePos = $slavePos;
+ $status = 0;
}
+ } elseif ( $status >= 0 ) {
+ // Remember that this position was reached to save queries next time
+ $this->lastKnownSlavePos = $pos;
}
return $status;
return ( $thisPos && $thatPos && $thisPos >= $thatPos );
}
+ function channelsMatch( DBMasterPos $pos ) {
+ if ( !( $pos instanceof self ) ) {
+ throw new InvalidArgumentException( "Position not an instance of " . __CLASS__ );
+ }
+
+ $thisBinlog = $this->getBinlogName();
+ $thatBinlog = $pos->getBinlogName();
+
+ return ( $thisBinlog !== false && $thisBinlog === $thatBinlog );
+ }
+
function __toString() {
// e.g db1034-bin.000976/843431247
return "{$this->file}/{$this->pos}";
}
+ /**
+ * @return string|bool
+ */
+ protected function getBinlogName() {
+ $m = [];
+ if ( preg_match( '!^(.+)\.(\d+)/(\d+)$!', (string)$this, $m ) ) {
+ return $m[1];
+ }
+
+ return false;
+ }
+
/**
* @return array|bool (int, int)
*/
*/
public function hasReached( DBMasterPos $pos );
+ /**
+ * @param DBMasterPos $pos
+ * @return bool Whether this position appears to be for the same channel as another
+ * @since 1.27
+ */
+ public function channelsMatch( DBMasterPos $pos );
+
/**
* @return string
* @since 1.27
public function wasReadOnlyError();
/**
- * Wait for the slave to catch up to a given master position.
+ * Wait for the slave to catch up to a given master position
*
* @param DBMasterPos $pos
- * @param int $timeout The maximum number of seconds to wait for
- * synchronisation
- * @return int Zero if the slave was past that position already,
+ * @param int $timeout The maximum number of seconds to wait for synchronisation
+ * @return int|null Zero if the slave was past that position already,
* greater than zero if we waited for some period of time, less than
- * zero if we timed out.
+ * zero if it timed out, and null on error
*/
public function masterPosWait( DBMasterPos $pos, $timeout );
if ( $this->asyncWrites && !$this->hasVolatileSources( $ops ) ) {
// Bind $scopeLock to the callback to preserve locks
DeferredUpdates::addCallableUpdate(
- function() use ( $backend, $realOps, $opts, $scopeLock ) {
+ function() use ( $backend, $realOps, $opts, $scopeLock, $relevantPaths ) {
+ wfDebugLog( 'FileOperationReplication',
+ "'{$backend->getName()}' async replication; paths: " .
+ FormatJson::encode( $relevantPaths ) );
$backend->doOperations( $realOps, $opts );
}
);
} else {
+ wfDebugLog( 'FileOperationReplication',
+ "'{$backend->getName()}' sync replication; paths: " .
+ FormatJson::encode( $relevantPaths ) );
$status->merge( $backend->doOperations( $realOps, $opts ) );
}
}
* @param array $srcPaths Ordered list of source virtual URLs/storage paths
* @param string $dstPath Target file system path
* @param int $flags Bitwise combination of the following flags:
- * self::DELETE_SOURCE Delete the source files
+ * self::DELETE_SOURCE Delete the source files on success
* @return FileRepoStatus
*/
public function concatenate( array $srcPaths, $dstPath, $flags = 0 ) {
* @see JobQueue::doAck()
* @param Job $job
* @throws MWException
- * @return Job|bool
*/
protected function doAck( Job $job ) {
if ( !isset( $job->metadata['id'] ) ) {
} catch ( DBError $e ) {
$this->throwDBException( $e );
}
-
- return true;
}
/**
class JobQueueFederated extends JobQueue {
/** @var HashRing */
protected $partitionRing;
- /** @var array (partition name => JobQueue) reverse sorted by weight */
+ /** @var JobQueue[] (partition name => JobQueue) reverse sorted by weight */
protected $partitionQueues = [];
/** @var int Maximum number of partitions to try */
throw new MWException( "The given job has no defined partition name." );
}
- return $this->partitionQueues[$job->metadata['QueuePartition']]->ack( $job );
+ $this->partitionQueues[$job->metadata['QueuePartition']]->ack( $job );
}
protected function doIsRootJobOldDuplicate( Job $job ) {
* @return string HTML fragment
*/
public static function capturePath( Title $title, IContextSource $context ) {
- global $wgOut, $wgTitle, $wgRequest, $wgUser, $wgLang;
-
- // Save current globals
- $oldTitle = $wgTitle;
- $oldOut = $wgOut;
- $oldRequest = $wgRequest;
- $oldUser = $wgUser;
- $oldLang = $wgLang;
-
- // Set the globals to the current context
+ global $wgTitle, $wgOut, $wgRequest, $wgUser, $wgLang;
+ $main = RequestContext::getMain();
+
+ // Save current globals and main context
+ $glob = [
+ 'title' => $wgTitle,
+ 'output' => $wgOut,
+ 'request' => $wgRequest,
+ 'user' => $wgUser,
+ 'language' => $wgLang,
+ ];
+ $ctx = [
+ 'title' => $main->getTitle(),
+ 'output' => $main->getOutput(),
+ 'request' => $main->getRequest(),
+ 'user' => $main->getUser(),
+ 'language' => $main->getLanguage(),
+ ];
+
+ // Override
$wgTitle = $title;
$wgOut = $context->getOutput();
$wgRequest = $context->getRequest();
$wgUser = $context->getUser();
$wgLang = $context->getLanguage();
+ $main->setTitle( $title );
+ $main->setOutput( $context->getOutput() );
+ $main->setRequest( $context->getRequest() );
+ $main->setUser( $context->getUser() );
+ $main->setLanguage( $context->getLanguage() );
// The useful part
$ret = self::executePath( $title, $context, true );
- // And restore the old globals
- $wgTitle = $oldTitle;
- $wgOut = $oldOut;
- $wgRequest = $oldRequest;
- $wgUser = $oldUser;
- $wgLang = $oldLang;
+ // Restore old globals and context
+ $wgTitle = $glob['title'];
+ $wgOut = $glob['output'];
+ $wgRequest = $glob['request'];
+ $wgUser = $glob['user'];
+ $wgLang = $glob['language'];
+ $main->setTitle( $ctx['title'] );
+ $main->setOutput( $ctx['output'] );
+ $main->setRequest( $ctx['request'] );
+ $main->setUser( $ctx['user'] );
+ $main->setLanguage( $ctx['language'] );
return $ret;
}
* @param array $pageInfo
* @return void
*/
- function reportPage( $title, $foreignTitle, $revisionCount,
+ public function reportPage( $title, $foreignTitle, $revisionCount,
$successCount, $pageInfo ) {
$args = func_get_args();
call_user_func_array( $this->mOriginalPageOutCallback, $args );
if ( !$status->isOk() ) {
return $status;
}
+
wfDebugLog( 'fileconcatenate', "Combined $i chunks in $tAmount seconds." );
// File system path
];
}
- function testMasterPos() {
- $pos1 = new MySQLMasterPos( 'db1034-bin.000976', '843431247' );
- $pos2 = new MySQLMasterPos( 'db1034-bin.000976', '843431248' );
-
- $this->assertTrue( $pos1->hasReached( $pos1 ) );
- $this->assertTrue( $pos2->hasReached( $pos2 ) );
- $this->assertTrue( $pos2->hasReached( $pos1 ) );
- $this->assertFalse( $pos1->hasReached( $pos2 ) );
+ /**
+ * @dataProvider provideComparePositions
+ */
+ function testHasReached( MySQLMasterPos $lowerPos, MySQLMasterPos $higherPos ) {
+ $this->assertTrue( $higherPos->hasReached( $lowerPos ) );
+ $this->assertTrue( $higherPos->hasReached( $higherPos ) );
+ $this->assertTrue( $lowerPos->hasReached( $lowerPos ) );
+ $this->assertFalse( $lowerPos->hasReached( $higherPos ) );
+ }
+
+ function provideComparePositions() {
+ return [
+ [
+ new MySQLMasterPos( 'db1034-bin.000976', '843431247' ),
+ new MySQLMasterPos( 'db1034-bin.000976', '843431248' )
+ ],
+ [
+ new MySQLMasterPos( 'db1034-bin.000976', '999' ),
+ new MySQLMasterPos( 'db1034-bin.000976', '1000' )
+ ],
+ [
+ new MySQLMasterPos( 'db1034-bin.000976', '999' ),
+ new MySQLMasterPos( 'db1035-bin.000976', '1000' )
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideChannelPositions
+ */
+ function testChannelsMatch( MySQLMasterPos $pos1, MySQLMasterPos $pos2, $matches ) {
+ $this->assertEquals( $matches, $pos1->channelsMatch( $pos2 ) );
+ $this->assertEquals( $matches, $pos2->channelsMatch( $pos1 ) );
+ }
+
+ function provideChannelPositions() {
+ return [
+ [
+ new MySQLMasterPos( 'db1034-bin.000876', '44' ),
+ new MySQLMasterPos( 'db1034-bin.000976', '74' ),
+ true
+ ],
+ [
+ new MySQLMasterPos( 'db1052-bin.000976', '999' ),
+ new MySQLMasterPos( 'db1052-bin.000976', '1000' ),
+ true
+ ],
+ [
+ new MySQLMasterPos( 'db1066-bin.000976', '9999' ),
+ new MySQLMasterPos( 'db1035-bin.000976', '10000' ),
+ false
+ ],
+ [
+ new MySQLMasterPos( 'db1066-bin.000976', '9999' ),
+ new MySQLMasterPos( 'trump2016.000976', '10000' ),
+ false
+ ],
+ ];
}
/**
* in an instance property rather than APC.
*/
class ArrayBackedMemoizedCallable extends MemoizedCallable {
- public $cache = [];
+ private $cache = [];
protected function fetchResult( $key, &$success ) {
if ( array_key_exists( $key, $this->cache ) ) {
$this->readAttribute( $a, 'callableName' ),
$this->readAttribute( $b, 'callableName' )
);
+
+ $c = new ArrayBackedMemoizedCallable( function () {
+ return rand();
+ } );
+ $this->assertEquals( $c->invokeArgs(), $c->invokeArgs(), 'memoized random' );
}
/**