* Title::isCssJsSubpage – use ::isUserConfigPage
* Title::isCssSubpage – use ::isUserCssConfigPage
* Title::isJsSubpage – use ::isUserJsConfigPage
-* The following variables and method in EditPage, deprecated in MediaWiki 1.30, were removed:
+* The following variables and methods in EditPage, deprecated in MediaWiki 1.30, were removed:
* $isCssJsSubpage — use ::isUserConfigPage()
* $isCssSubpage — use ::isUserCssConfigPage()
* $isJsSubpage — use ::isUserJsConfigPage()
* $isWrongCaseCssJsPage – use ::isWrongCaseUserConfigPage()
+ * ::getSummaryInput() – use ::getSummaryInputWidget()
+ * ::getSummaryInputOOUI() – use ::getSummaryInputWidget()
+ * ::getCheckboxes() – use ::getCheckboxesWidget() or ::getCheckboxesDefinition()
+ * ::getCheckboxesOOUI() – use ::getCheckboxesWidget() or ::getCheckboxesDefinition()
* The method ResourceLoaderModule::getPosition(), deprecated in 1.29, has been removed.
* The DeferredStringifier class is deprecated, use Message::listParam() instead.
* The type string for the parameter $lang of DateFormatter::getInstance is
deprecated.
+* In User, the cookie-related methods which were wrappers for the functions on the response
+ object, and were deprecated in 1.27, have been removed:
+ * ::setCookie()
+ * ::clearCookie()
+ * ::setExtendedLoginCookie()
+ Note that User::setCookies() remains, and is not deprecated.
* The global functions wfProfileIn and wfProfileOut, deprecated in 1.25, have been removed.
== Compatibility ==
];
}
- /**
- * Standard summary input and label (wgSummary), abstracted so EditPage
- * subclasses may reorganize the form.
- * Note that you do not need to worry about the label's for=, it will be
- * inferred by the id given to the input. You can remove them both by
- * passing [ 'id' => false ] to $userInputAttrs.
- *
- * @deprecated since 1.30 Use getSummaryInputWidget() instead
- * @param string $summary The value of the summary input
- * @param string $labelText The html to place inside the label
- * @param array $inputAttrs Array of attrs to use on the input
- * @param array $spanLabelAttrs Array of attrs to use on the span inside the label
- * @return array An array in the format [ $label, $input ]
- */
- public function getSummaryInput( $summary = "", $labelText = null,
- $inputAttrs = null, $spanLabelAttrs = null
- ) {
- wfDeprecated( __METHOD__, '1.30' );
- $inputAttrs = $this->getSummaryInputAttributes( $inputAttrs );
- $inputAttrs += Linker::tooltipAndAccesskeyAttribs( 'summary' );
-
- $spanLabelAttrs = ( is_array( $spanLabelAttrs ) ? $spanLabelAttrs : [] ) + [
- 'class' => $this->missingSummary ? 'mw-summarymissed' : 'mw-summary',
- 'id' => "wpSummaryLabel"
- ];
-
- $label = null;
- if ( $labelText ) {
- $label = Xml::tags(
- 'label',
- $inputAttrs['id'] ? [ 'for' => $inputAttrs['id'] ] : null,
- $labelText
- );
- $label = Xml::tags( 'span', $spanLabelAttrs, $label );
- }
-
- $input = Html::input( 'wpSummary', $summary, 'text', $inputAttrs );
-
- return [ $label, $input ];
- }
-
- /**
- * Builds a standard summary input with a label.
- *
- * @deprecated since 1.30 Use getSummaryInputWidget() instead
- * @param string $summary The value of the summary input
- * @param string $labelText The html to place inside the label
- * @param array $inputAttrs Array of attrs to use on the input
- *
- * @return OOUI\FieldLayout OOUI FieldLayout with Label and Input
- */
- function getSummaryInputOOUI( $summary = "", $labelText = null, $inputAttrs = null ) {
- wfDeprecated( __METHOD__, '1.30' );
- return $this->getSummaryInputWidget( $summary, $labelText, $inputAttrs );
- }
-
/**
* Builds a standard summary input with a label.
*
return $checkboxes;
}
- /**
- * Returns an array of html code of the following checkboxes old style:
- * minor and watch
- *
- * @deprecated since 1.30 Use getCheckboxesWidget() or getCheckboxesDefinition() instead
- * @param int &$tabindex Current tabindex
- * @param array $checked See getCheckboxesDefinition()
- * @return array
- */
- public function getCheckboxes( &$tabindex, $checked ) {
- wfDeprecated( __METHOD__, '1.30' );
- $checkboxes = [];
- $checkboxesDef = $this->getCheckboxesDefinition( $checked );
-
- // Backwards-compatibility for the EditPageBeforeEditChecks hook
- if ( !$this->isNew ) {
- $checkboxes['minor'] = '';
- }
- $checkboxes['watch'] = '';
-
- foreach ( $checkboxesDef as $name => $options ) {
- $legacyName = isset( $options['legacy-name'] ) ? $options['legacy-name'] : $name;
- $label = $this->context->msg( $options['label-message'] )->parse();
- $attribs = [
- 'tabindex' => ++$tabindex,
- 'id' => $options['id'],
- ];
- $labelAttribs = [
- 'for' => $options['id'],
- ];
- if ( isset( $options['tooltip'] ) ) {
- $attribs['accesskey'] = $this->context->msg( "accesskey-{$options['tooltip']}" )->text();
- $labelAttribs['title'] = Linker::titleAttrib( $options['tooltip'], 'withaccess' );
- }
- if ( isset( $options['title-message'] ) ) {
- $labelAttribs['title'] = $this->context->msg( $options['title-message'] )->text();
- }
- if ( isset( $options['label-id'] ) ) {
- $labelAttribs['id'] = $options['label-id'];
- }
- $checkboxHtml =
- Xml::check( $name, $options['default'], $attribs ) .
- ' ' .
- Xml::tags( 'label', $labelAttribs, $label );
-
- $checkboxes[ $legacyName ] = $checkboxHtml;
- }
-
- // Avoid PHP 7.1 warning of passing $this by reference
- $editPage = $this;
- Hooks::run( 'EditPageBeforeEditChecks', [ &$editPage, &$checkboxes, &$tabindex ], '1.29' );
- return $checkboxes;
- }
-
- /**
- * Returns an array of checkboxes for the edit form, including 'minor' and 'watch' checkboxes and
- * any other added by extensions.
- *
- * @deprecated since 1.30 Use getCheckboxesWidget() or getCheckboxesDefinition() instead
- * @param int &$tabindex Current tabindex
- * @param array $checked Array of checkbox => bool, where bool indicates the checked
- * status of the checkbox
- *
- * @return array Associative array of string keys to OOUI\FieldLayout instances
- */
- public function getCheckboxesOOUI( &$tabindex, $checked ) {
- wfDeprecated( __METHOD__, '1.30' );
- return $this->getCheckboxesWidget( $tabindex, $checked );
- }
-
/**
* Returns an array of checkboxes for the edit form, including 'minor' and 'watch' checkboxes and
* any other added by extensions.
* @deprecated Since 1.31; use ::isSiteConfigPage() instead
*/
public function isCssOrJsPage() {
- // wfDeprecated( __METHOD__, '1.31' );
+ wfDeprecated( __METHOD__, '1.31' );
return ( NS_MEDIAWIKI == $this->mNamespace
&& ( $this->hasContentModel( CONTENT_MODEL_CSS )
|| $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );
* @deprecated Since 1.31; use ::isUserConfigPage() instead
*/
public function isCssJsSubpage() {
- // wfDeprecated( __METHOD__, '1.31' );
+ wfDeprecated( __METHOD__, '1.31' );
return ( NS_USER == $this->mNamespace && $this->isSubpage()
&& ( $this->hasContentModel( CONTENT_MODEL_CSS )
|| $this->hasContentModel( CONTENT_MODEL_JAVASCRIPT ) ) );
* @return bool
*/
public function isCssSubpage() {
- // wfDeprecated( __METHOD__, '1.31' );
+ wfDeprecated( __METHOD__, '1.31' );
return $this->isUserCssConfigPage();
}
}
/**
- * @deprecated Since 1.31; use ::isUserCssConfigPage()
+ * @deprecated Since 1.31; use ::isUserJsConfigPage()
* @return bool
*/
public function isJsSubpage() {
- // wfDeprecated( __METHOD__, '1.31' );
+ wfDeprecated( __METHOD__, '1.31' );
return $this->isUserJsConfigPage();
}
return $this->procCache['config'][$name];
}
+ public function getModifiedIndex() {
+ $this->load();
+ return $this->procCache['modifiedIndex'];
+ }
+
/**
* @throws ConfigException
*/
// refresh the cache from etcd, using a mutex to reduce stampedes...
if ( $this->srvCache->lock( $key, 0, $this->baseCacheTTL ) ) {
try {
- list( $config, $error, $retry ) = $this->fetchAllFromEtcd();
- if ( is_array( $config ) ) {
+ $etcdResponse = $this->fetchAllFromEtcd();
+ $error = $etcdResponse['error'];
+ if ( is_array( $etcdResponse['config'] ) ) {
// Avoid having all servers expire cache keys at the same time
$expiry = microtime( true ) + $this->baseCacheTTL;
$expiry += mt_rand( 0, 1e6 ) / 1e6 * $this->skewCacheTTL;
-
- $data = [ 'config' => $config, 'expires' => $expiry ];
+ $data = [
+ 'config' => $etcdResponse['config'],
+ 'expires' => $expiry,
+ 'modifiedIndex' => $etcdResponse['modifiedIndex']
+ ];
$this->srvCache->set( $key, $data, BagOStuff::TTL_INDEFINITE );
$this->logger->info( "Refreshed stale etcd configuration cache." );
return WaitConditionLoop::CONDITION_REACHED;
} else {
$this->logger->error( "Failed to fetch configuration: $error" );
- if ( !$retry ) {
+ if ( !$etcdResponse['retry'] ) {
// Fail fast since the error is likely to keep happening
return WaitConditionLoop::CONDITION_FAILED;
}
}
/**
- * @return array (config array or null, error string, allow retries)
+ * @return array (containing the keys config, error, retry, modifiedIndex)
*/
public function fetchAllFromEtcd() {
+ // TODO: inject DnsSrvDiscoverer in order to be able to test this method
$dsd = new DnsSrvDiscoverer( $this->host );
$servers = $dsd->getServers();
if ( !$servers ) {
$server = $dsd->pickServer( $servers );
$host = IP::combineHostAndPort( $server['target'], $server['port'] );
// Try to load the config from this particular server
- list( $config, $error, $retry ) = $this->fetchAllFromEtcdServer( $host );
- if ( is_array( $config ) || !$retry ) {
+ $response = $this->fetchAllFromEtcdServer( $host );
+ if ( is_array( $response['config'] ) || $response['retry'] ) {
break;
}
$servers = $dsd->removeServer( $server, $servers );
} while ( $servers );
- return [ $config, $error, $retry ];
+ return $response;
}
/**
* @param string $address Host and port
- * @return array (config array or null, error string, whether to allow retries)
+ * @return array (containing the keys config, error, retry, modifiedIndex)
*/
protected function fetchAllFromEtcdServer( $address ) {
// Retrieve all the values under the MediaWiki config directory
'headers' => [ 'content-type' => 'application/json' ]
] );
+ $response = [ 'config' => null, 'error' => null, 'retry' => false, 'modifiedIndex' => 0 ];
+
static $terminalCodes = [ 404 => true ];
if ( $rcode < 200 || $rcode > 399 ) {
- return [
- null,
- strlen( $rerr ) ? $rerr : "HTTP $rcode ($rdesc)",
- empty( $terminalCodes[$rcode] )
- ];
+ $response['error'] = strlen( $rerr ) ? $rerr : "HTTP $rcode ($rdesc)";
+ $response['retry'] = empty( $terminalCodes[$rcode] );
+ return $response;
}
+
try {
- return [ $this->parseResponse( $rbody ), null, false ];
+ $parsedResponse = $this->parseResponse( $rbody );
} catch ( EtcdConfigParseError $e ) {
- return [ null, $e->getMessage(), false ];
+ $parsedResponse = [ 'error' => $e->getMessage() ];
}
+ return array_merge( $response, $parsedResponse );
}
/**
"Unexpected JSON response: Missing or invalid node at top level." );
}
$config = [];
- $this->parseDirectory( '', $info['node'], $config );
- return $config;
+ $lastModifiedIndex = $this->parseDirectory( '', $info['node'], $config );
+ return [ 'modifiedIndex' => $lastModifiedIndex, 'config' => $config ];
}
/**
* @param string $dirName The relative directory name
* @param array $dirNode The decoded directory node
* @param array &$config The output array
+ * @return int lastModifiedIndex The maximum last modified index across all keys in the directory
*/
protected function parseDirectory( $dirName, $dirNode, &$config ) {
+ $lastModifiedIndex = 0;
if ( !isset( $dirNode['nodes'] ) ) {
throw new EtcdConfigParseError(
"Unexpected JSON response in dir '$dirName'; missing 'nodes' list." );
$baseName = basename( $node['key'] );
$fullName = $dirName === '' ? $baseName : "$dirName/$baseName";
if ( !empty( $node['dir'] ) ) {
- $this->parseDirectory( $fullName, $node, $config );
+ $lastModifiedIndex = max(
+ $this->parseDirectory( $fullName, $node, $config ),
+ $lastModifiedIndex );
} else {
$value = $this->unserialize( $node['value'] );
if ( !is_array( $value ) || !array_key_exists( 'val', $value ) ) {
throw new EtcdConfigParseError( "Failed to parse value for '$fullName'." );
}
-
+ $lastModifiedIndex = max( $node['modifiedIndex'], $lastModifiedIndex );
$config[$fullName] = $value['val'];
}
}
+ return $lastModifiedIndex;
}
/**
* Sets the number of active users in the site_stats table
*/
protected function doActiveUsersInit() {
- $activeUsers = $this->db->selectField( 'site_stats', 'ss_active_users', false, __METHOD__ );
+ $activeUsers = $this->db->selectField( 'site_stats', 'ss_active_users', '', __METHOD__ );
if ( $activeUsers == -1 ) {
$activeUsers = $this->db->selectField( 'recentchanges',
'COUNT( DISTINCT rc_user_text )',
list( $startOpts, $useIndex, $preLimitTail, $postLimitTail, $ignoreIndex ) =
$this->makeSelectOptions( $options );
- if ( !empty( $conds ) ) {
- if ( is_array( $conds ) ) {
- $conds = $this->makeList( $conds, self::LIST_AND );
- }
+ if ( is_array( $conds ) ) {
+ $conds = $this->makeList( $conds, self::LIST_AND );
+ }
+
+ if ( $conds === null || $conds === false ) {
+ $this->queryLogger->warning(
+ __METHOD__
+ . ' called from '
+ . $fname
+ . ' with incorrect parameters: $conds must be a string or an array'
+ );
+ $conds = '';
+ }
+
+ if ( $conds === '' ) {
+ $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex $preLimitTail";
+ } elseif ( is_string( $conds ) ) {
$sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex " .
"WHERE $conds $preLimitTail";
} else {
- $sql = "SELECT $startOpts $vars $from $useIndex $ignoreIndex $preLimitTail";
+ throw new DBUnexpectedError( $this, __METHOD__ . ' called with incorrect parameters' );
}
if ( isset( $options['LIMIT'] ) ) {
return $this->query( $sql, $fname );
}
- public function insertSelect(
+ final public function insertSelect(
$destTable, $srcTable, $varMap, $conds,
$fname = __METHOD__, $insertOptions = [], $selectOptions = [], $selectJoinConds = []
) {
- if ( $this->cliMode ) {
+ static $hints = [ 'NO_AUTO_COLUMNS' ];
+
+ $insertOptions = (array)$insertOptions;
+ $selectOptions = (array)$selectOptions;
+
+ if ( $this->cliMode && $this->isInsertSelectSafe( $insertOptions, $selectOptions ) ) {
// For massive migrations with downtime, we don't want to select everything
// into memory and OOM, so do all this native on the server side if possible.
return $this->nativeInsertSelect(
$varMap,
$conds,
$fname,
- $insertOptions,
+ array_diff( $insertOptions, $hints ),
$selectOptions,
$selectJoinConds
);
$varMap,
$conds,
$fname,
- $insertOptions,
+ array_diff( $insertOptions, $hints ),
$selectOptions,
$selectJoinConds
);
}
+ /**
+ * @param array $insertOptions INSERT options
+ * @param array $selectOptions SELECT options
+ * @return bool Whether an INSERT SELECT with these options will be replication safe
+ * @since 1.31
+ */
+ protected function isInsertSelectSafe( array $insertOptions, array $selectOptions ) {
+ return true;
+ }
+
/**
* Implementation of insertSelect() based on select() and insert()
*
$fname = __METHOD__,
$insertOptions = [], $selectOptions = [], $selectJoinConds = []
) {
- $insertOptions = array_diff( (array)$insertOptions, [ 'NO_AUTO_COLUMNS' ] );
-
// For web requests, do a locking SELECT and then INSERT. This puts the SELECT burden
// on only the master (without needing row-based-replication). It also makes it easy to
// know how big the INSERT is going to be.
if ( !is_array( $insertOptions ) ) {
$insertOptions = [ $insertOptions ];
}
- $insertOptions = array_diff( (array)$insertOptions, [ 'NO_AUTO_COLUMNS' ] );
$insertOptions = $this->makeInsertOptions( $insertOptions );
private $serverVersion = null;
/** @var bool|null */
private $insertSelectIsSafe = null;
+ /** @var stdClass|null */
+ private $replicationInfoRow = null;
/**
* Additional $params include:
return $this->nativeReplace( $table, $rows, $fname );
}
- protected function nativeInsertSelect(
- $destTable, $srcTable, $varMap, $conds,
- $fname = __METHOD__, $insertOptions = [], $selectOptions = [], $selectJoinConds = []
- ) {
- $isSafe = in_array( 'NO_AUTO_COLUMNS', $insertOptions, true );
- if ( !$isSafe && $this->insertSelectIsSafe === null ) {
- // In MySQL, an INSERT SELECT is only replication safe with row-based
- // replication or if innodb_autoinc_lock_mode is 0. When those
- // conditions aren't met, use non-native mode.
- // While we could try to determine if the insert is safe anyway by
- // checking if the target table has an auto-increment column that
- // isn't set in $varMap, that seems unlikely to be worth the extra
- // complexity.
- $row = $this->selectRow(
+ protected function isInsertSelectSafe( array $insertOptions, array $selectOptions ) {
+ $row = $this->getReplicationSafetyInfo();
+ // For row-based-replication, the resulting changes will be relayed, not the query
+ if ( $row->binlog_format === 'ROW' ) {
+ return true;
+ }
+ // LIMIT requires ORDER BY on a unique key or it is non-deterministic
+ if ( isset( $selectOptions['LIMIT'] ) ) {
+ return false;
+ }
+ // In MySQL, an INSERT SELECT is only replication safe with row-based
+ // replication or if innodb_autoinc_lock_mode is 0. When those
+ // conditions aren't met, use non-native mode.
+ // While we could try to determine if the insert is safe anyway by
+ // checking if the target table has an auto-increment column that
+ // isn't set in $varMap, that seems unlikely to be worth the extra
+ // complexity.
+ return ( (int)$row->innodb_autoinc_lock_mode === 0 );
+ }
+
+ /**
+ * @return stdClass Process cached row
+ */
+ private function getReplicationSafetyInfo() {
+ if ( $this->replicationInfoRow === null ) {
+ $this->replicationInfoRow = $this->selectRow(
false,
[
'innodb_autoinc_lock_mode' => '@@innodb_autoinc_lock_mode',
[],
__METHOD__
);
- $this->insertSelectIsSafe = $row->binlog_format === 'ROW' ||
- (int)$row->innodb_autoinc_lock_mode === 0;
- }
-
- if ( !$isSafe && !$this->insertSelectIsSafe ) {
- return $this->nonNativeInsertSelect(
- $destTable,
- $srcTable,
- $varMap,
- $conds,
- $fname,
- $insertOptions,
- $selectOptions,
- $selectJoinConds
- );
- } else {
- return parent::nativeInsertSelect(
- $destTable,
- $srcTable,
- $varMap,
- $conds,
- $fname,
- $insertOptions,
- $selectOptions,
- $selectJoinConds
- );
}
+
+ return $this->replicationInfoRow;
}
/**
}
/**
- * Returns JS code which runs given JS code if the client-side framework is
- * present.
+ * Wraps JavaScript code to run after startup and base modules.
*
- * @deprecated since 1.25; use makeInlineScript instead
* @param string $script JavaScript code
* @return string JavaScript code
*/
}
/**
- * Construct an inline script tag with given JS code.
+ * Returns an HTML script tag that runs given JS code after startup and base modules.
*
- * The code will be wrapped in a closure, and it will be executed by ResourceLoader
- * only if the client has adequate support for MediaWiki JavaScript code.
+ * The code will be wrapped in a closure, and it will be executed by ResourceLoader's
+ * startup module if the client has adequate support for MediaWiki JavaScript code.
*
* @param string $script JavaScript code
* @return WrappedString HTML
*/
public function checkLastModified() {
$dbr = $this->getDB();
- $lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', false, __METHOD__ );
+ $lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', '', __METHOD__ );
return $lastmod;
}
];
if ( $this->contribs == 'newbie' ) {
- $max = $this->mDb->selectField( 'user', 'max(user_id)', false, __METHOD__ );
+ $max = $this->mDb->selectField( 'user', 'max(user_id)', '', __METHOD__ );
$queryInfo['conds'][] = $revQuery['fields']['rev_user'] . ' >' . (int)( $max - $max / 100 );
# ignore local groups with the bot right
# @todo FIXME: Global groups may have 'bot' rights
if ( $opts->getValue( 'newbies' ) ) {
// newbie = most recent 1% of users
$dbr = wfGetDB( DB_REPLICA );
- $max = $dbr->selectField( 'user', 'max(user_id)', false, __METHOD__ );
+ $max = $dbr->selectField( 'user', 'max(user_id)', '', __METHOD__ );
$conds[] = $imgQuery['fields']['img_user'] . ' >' . (int)( $max - $max / 100 );
// there's no point in looking for new user activity in a far past;
}
}
- /**
- * Set a cookie on the user's client. Wrapper for
- * WebResponse::setCookie
- * @deprecated since 1.27
- * @param string $name Name of the cookie to set
- * @param string $value Value to set
- * @param int $exp Expiration time, as a UNIX time value;
- * if 0 or not specified, use the default $wgCookieExpiration
- * @param bool $secure
- * true: Force setting the secure attribute when setting the cookie
- * false: Force NOT setting the secure attribute when setting the cookie
- * null (default): Use the default ($wgCookieSecure) to set the secure attribute
- * @param array $params Array of options sent passed to WebResponse::setcookie()
- * @param WebRequest|null $request WebRequest object to use; $wgRequest will be used if null
- * is passed.
- */
- protected function setCookie(
- $name, $value, $exp = 0, $secure = null, $params = [], $request = null
- ) {
- wfDeprecated( __METHOD__, '1.27' );
- if ( $request === null ) {
- $request = $this->getRequest();
- }
- $params['secure'] = $secure;
- $request->response()->setCookie( $name, $value, $exp, $params );
- }
-
- /**
- * Clear a cookie on the user's client
- * @deprecated since 1.27
- * @param string $name Name of the cookie to clear
- * @param bool $secure
- * true: Force setting the secure attribute when setting the cookie
- * false: Force NOT setting the secure attribute when setting the cookie
- * null (default): Use the default ($wgCookieSecure) to set the secure attribute
- * @param array $params Array of options sent passed to WebResponse::setcookie()
- */
- protected function clearCookie( $name, $secure = null, $params = [] ) {
- wfDeprecated( __METHOD__, '1.27' );
- $this->setCookie( $name, '', time() - 86400, $secure, $params );
- }
-
- /**
- * Set an extended login cookie on the user's client. The expiry of the cookie
- * is controlled by the $wgExtendedLoginCookieExpiration configuration
- * variable.
- *
- * @see User::setCookie
- *
- * @deprecated since 1.27
- * @param string $name Name of the cookie to set
- * @param string $value Value to set
- * @param bool $secure
- * true: Force setting the secure attribute when setting the cookie
- * false: Force NOT setting the secure attribute when setting the cookie
- * null (default): Use the default ($wgCookieSecure) to set the secure attribute
- */
- protected function setExtendedLoginCookie( $name, $value, $secure ) {
- global $wgExtendedLoginCookieExpiration, $wgCookieExpiration;
-
- wfDeprecated( __METHOD__, '1.27' );
-
- $exp = time();
- $exp += $wgExtendedLoginCookieExpiration !== null
- ? $wgExtendedLoginCookieExpiration
- : $wgCookieExpiration;
-
- $this->setCookie( $name, $value, $exp, $secure );
- }
-
/**
* Persist this user's session (e.g. set cookies)
*
*/
function sync( $srcTable, $dstTable ) {
$batchSize = 1000;
- $minTs = $this->dbw->selectField( $srcTable, 'MIN(log_timestamp)', false, __METHOD__ );
+ $minTs = $this->dbw->selectField( $srcTable, 'MIN(log_timestamp)', '', __METHOD__ );
$minTsUnix = wfTimestamp( TS_UNIX, $minTs );
$numRowsCopied = 0;
while ( true ) {
- $maxTs = $this->dbw->selectField( $srcTable, 'MAX(log_timestamp)', false, __METHOD__ );
- $copyPos = $this->dbw->selectField( $dstTable, 'MAX(log_timestamp)', false, __METHOD__ );
+ $maxTs = $this->dbw->selectField( $srcTable, 'MAX(log_timestamp)', '', __METHOD__ );
+ $copyPos = $this->dbw->selectField( $dstTable, 'MAX(log_timestamp)', '', __METHOD__ );
$maxTsUnix = wfTimestamp( TS_UNIX, $maxTs );
$copyPosUnix = wfTimestamp( TS_UNIX, $copyPos );
public function execute() {
global $wgLocalDatabases, $wgMemc;
$dbr = $this->getDB( DB_REPLICA );
- $res = $dbr->select( 'interwiki', [ 'iw_prefix' ], false );
+ $res = $dbr->select( 'interwiki', [ 'iw_prefix' ], '', __METHOD__ );
$prefixes = [];
foreach ( $res as $row ) {
$prefixes[] = $row->iw_prefix;
$this->output( "Creating actor entries for all registered users\n" );
$end = 0;
$dbw = $this->getDB( DB_MASTER );
- $max = $dbw->selectField( 'user', 'MAX(user_id)', false, __METHOD__ );
+ $max = $dbw->selectField( 'user', 'MAX(user_id)', '', __METHOD__ );
$count = 0;
while ( $end < $max ) {
$start = $end + 1;
$start = $this->getOption( 'lastUpdatedId' );
if ( !$start ) {
- $start = $db->selectField( 'page', 'MIN(page_id)', false, __METHOD__ );
+ $start = $db->selectField( 'page', 'MIN(page_id)', '', __METHOD__ );
}
if ( !$start ) {
$this->output( "Nothing to do." );
return false;
}
- $end = $db->selectField( 'page', 'MAX(page_id)', false, __METHOD__ );
+ $end = $db->selectField( 'page', 'MAX(page_id)', '', __METHOD__ );
$batchSize = $this->getBatchSize();
# Do remaining chunk
}
$this->output( "Populating fa_sha1 field from fa_storage_key\n" );
- $endId = $dbw->selectField( $table, 'MAX(fa_id)', false, __METHOD__ );
+ $endId = $dbw->selectField( $table, 'MAX(fa_id)', '', __METHOD__ );
$batchSize = $this->getBatchSize();
$done = 0;
$start = $this->getOption( 'rev-id', 0 );
$end = $maxRevId > 0
? $maxRevId
- : $dbw->selectField( 'revision', 'MAX(rev_id)', false, __METHOD__ );
+ : $dbw->selectField( 'revision', 'MAX(rev_id)', '', __METHOD__ );
if ( empty( $end ) ) {
$this->output( "No revisions found, aborting.\n" );
return false;
}
- $start = $db->selectField( 'logging', 'MIN(log_id)', false, __FUNCTION__ );
+ $start = $db->selectField( 'logging', 'MIN(log_id)', '', __FUNCTION__ );
if ( !$start ) {
$this->output( "Nothing to do.\n" );
return true;
}
- $end = $db->selectField( 'logging', 'MAX(log_id)', false, __FUNCTION__ );
+ $end = $db->selectField( 'logging', 'MAX(log_id)', '', __FUNCTION__ );
# Do remaining chunk
$end += $batchSize - 1;
protected function doDBUpdates() {
$batchSize = $this->getBatchSize();
$db = $this->getDB( DB_MASTER );
- $start = $db->selectField( 'logging', 'MIN(log_id)', false, __METHOD__ );
+ $start = $db->selectField( 'logging', 'MIN(log_id)', '', __METHOD__ );
if ( !$start ) {
$this->output( "Nothing to do.\n" );
return true;
}
- $end = $db->selectField( 'logging', 'MAX(log_id)', false, __METHOD__ );
+ $end = $db->selectField( 'logging', 'MAX(log_id)', '', __METHOD__ );
// If this is being run during an upgrade from 1.16 or earlier, this
// will be run before the actor table change and should continue. But
return false;
}
$this->output( "Populating rev_parent_id column\n" );
- $start = $db->selectField( 'revision', 'MIN(rev_id)', false, __FUNCTION__ );
- $end = $db->selectField( 'revision', 'MAX(rev_id)', false, __FUNCTION__ );
+ $start = $db->selectField( 'revision', 'MIN(rev_id)', '', __FUNCTION__ );
+ $end = $db->selectField( 'revision', 'MAX(rev_id)', '', __FUNCTION__ );
if ( is_null( $start ) || is_null( $end ) ) {
$this->output( "...revision table seems to be empty, nothing to do.\n" );
$this->error( 'rc_source field in recentchanges table does not exist.' );
}
- $start = $dbw->selectField( 'recentchanges', 'MIN(rc_id)', false, __METHOD__ );
+ $start = $dbw->selectField( 'recentchanges', 'MIN(rc_id)', '', __METHOD__ );
if ( !$start ) {
$this->output( "Nothing to do.\n" );
return true;
}
- $end = $dbw->selectField( 'recentchanges', 'MAX(rc_id)', false, __METHOD__ );
+ $end = $dbw->selectField( 'recentchanges', 'MAX(rc_id)', '', __METHOD__ );
$end += $batchSize - 1;
$blockStart = $start;
$blockEnd = $start + $batchSize - 1;
$dbr = $this->getDB( DB_REPLICA );
$dbw = $this->getDB( DB_MASTER );
$batchSize = $this->getBatchSize();
- $start = $dbw->selectField( $table, "MIN($idCol)", false, __METHOD__ );
- $end = $dbw->selectField( $table, "MAX($idCol)", false, __METHOD__ );
+ $start = $dbw->selectField( $table, "MIN($idCol)", '', __METHOD__ );
+ $end = $dbw->selectField( $table, "MAX($idCol)", '', __METHOD__ );
if ( !$start || !$end ) {
$this->output( "...$table table seems to be empty.\n" );
protected function doSha1Updates( $table, $idCol, $queryInfo, $prefix ) {
$db = $this->getDB( DB_MASTER );
$batchSize = $this->getBatchSize();
- $start = $db->selectField( $table, "MIN($idCol)", false, __METHOD__ );
- $end = $db->selectField( $table, "MAX($idCol)", false, __METHOD__ );
+ $start = $db->selectField( $table, "MIN($idCol)", '', __METHOD__ );
+ $end = $db->selectField( $table, "MAX($idCol)", '', __METHOD__ );
if ( !$start || !$end ) {
$this->output( "...$table table seems to be empty.\n" );
$overwrite = $this->hasOption( 'overwrite' );
$start = ( $start > 0 )
? $start
- : $dbr->selectField( 'page', 'MIN(page_id)', false, __METHOD__ );
+ : $dbr->selectField( 'page', 'MIN(page_id)', '', __METHOD__ );
$end = ( $end > 0 )
? $end
- : $dbr->selectField( 'page', 'MAX(page_id)', false, __METHOD__ );
+ : $dbr->selectField( 'page', 'MAX(page_id)', '', __METHOD__ );
if ( !$start ) {
$this->fatalError( "Nothing to do." );
}
}
} else {
if ( !$end ) {
- $maxPage = $dbr->selectField( 'page', 'max(page_id)', false );
- $maxRD = $dbr->selectField( 'redirect', 'max(rd_from)', false );
+ $maxPage = $dbr->selectField( 'page', 'max(page_id)', '', __METHOD__ );
+ $maxRD = $dbr->selectField( 'redirect', 'max(rd_from)', '', __METHOD__ );
$end = max( $maxPage, $maxRD );
}
$this->output( "Refreshing redirects table.\n" );
} else {
print "Checking...\n";
}
- $maxRevId = $dbr->selectField( 'revision', 'MAX(rev_id)', false, __METHOD__ );
+ $maxRevId = $dbr->selectField( 'revision', 'MAX(rev_id)', '', __METHOD__ );
$chunkSize = 1000;
$flagStats = [];
$objectStats = [];
$numFixed = 0;
$numBad = 0;
- $totalRevs = $dbr->selectField( 'text', 'MAX(old_id)', false, __METHOD__ );
+ $totalRevs = $dbr->selectField( 'text', 'MAX(old_id)', '', __METHOD__ );
// In MySQL 4.1+, the binary field old_text has a non-working LOWER() function
$lowerLeft = 'LOWER(CONVERT(LEFT(old_text,22) USING latin1))';
if ( isset( $options['e'] ) ) {
$maxID = $options['e'];
} else {
- $maxID = $dbw->selectField( 'text', 'MAX(old_id)', false, $fname );
+ $maxID = $dbw->selectField( 'text', 'MAX(old_id)', '', $fname );
}
$minID = isset( $options['s'] ) ? $options['s'] : 1;
if ( !$dbr->tableExists( 'blob_orphans' ) ) {
$this->fatalError( "blob_orphans doesn't seem to exist, need to run trackBlobs.php first" );
}
- $res = $dbr->select( 'blob_orphans', '*', false, __METHOD__ );
+ $res = $dbr->select( 'blob_orphans', '*', '', __METHOD__ );
$num = 0;
$totalSize = 0;
$fname = 'resolveStubs';
$dbr = wfGetDB( DB_REPLICA );
- $maxID = $dbr->selectField( 'text', 'MAX(old_id)', false, $fname );
+ $maxID = $dbr->selectField( 'text', 'MAX(old_id)', '', $fname );
$blockSize = 10000;
$numBlocks = intval( $maxID / $blockSize ) + 1;
function execute() {
$dbr = $this->getDB( DB_REPLICA );
- $endId = $dbr->selectField( 'text', 'MAX(old_id)', false, __METHOD__ );
+ $endId = $dbr->selectField( 'text', 'MAX(old_id)', '', __METHOD__ );
if ( !$endId ) {
echo "No text rows!\n";
exit( 1 );
$textClause = $this->getTextClause();
$startId = 0;
- $endId = $dbr->selectField( 'revision', 'MAX(rev_id)', false, __METHOD__ );
+ $endId = $dbr->selectField( 'revision', 'MAX(rev_id)', '', __METHOD__ );
$batchesDone = 0;
$rowsInserted = 0;
$textClause = $this->getTextClause( $this->clusters );
$startId = 0;
- $endId = $dbr->selectField( 'text', 'MAX(old_id)', false, __METHOD__ );
+ $endId = $dbr->selectField( 'text', 'MAX(old_id)', '', __METHOD__ );
$rowsInserted = 0;
$batchesDone = 0;
$startId = 0;
$batchesDone = 0;
$actualBlobs = gmp_init( 0 );
- $endId = $extDB->selectField( $table, 'MAX(blob_id)', false, __METHOD__ );
+ $endId = $extDB->selectField( $table, 'MAX(blob_id)', '', __METHOD__ );
// Build a bitmap of actual blob rows
while ( true ) {
$this->fatalError( "page_restrictions table does not exist" );
}
- $start = $db->selectField( 'page', 'MIN(page_id)', false, __METHOD__ );
+ $start = $db->selectField( 'page', 'MIN(page_id)', '', __METHOD__ );
if ( !$start ) {
$this->fatalError( "Nothing to do." );
}
- $end = $db->selectField( 'page', 'MAX(page_id)', false, __METHOD__ );
+ $end = $db->selectField( 'page', 'MAX(page_id)', '', __METHOD__ );
# Do remaining chunk
$end += $batchSize - 1;
->getMock();
}
- private function createSimpleConfigMock( array $config ) {
+ private static function createEtcdResponse( array $response ) {
+ $baseResponse = [
+ 'config' => null,
+ 'error' => null,
+ 'retry' => false,
+ 'modifiedIndex' => 0,
+ ];
+ return array_merge( $baseResponse, $response );
+ }
+
+ private function createSimpleConfigMock( array $config, $index = 0 ) {
$mock = $this->createConfigMock();
$mock->expects( $this->once() )->method( 'fetchAllFromEtcd' )
- ->willReturn( [
- $config,
- null, // error
- false // retry?
- ] );
+ ->willReturn( self::createEtcdResponse( [
+ 'config' => $config,
+ 'modifiedIndex' => $index,
+ ] ) );
return $mock;
}
$config->get( 'unknown' );
}
+ /**
+ * @covers EtcdConfig::getModifiedIndex
+ */
+ public function testGetModifiedIndex() {
+ $config = $this->createSimpleConfigMock(
+ [ 'some' => 'value' ],
+ 123
+ );
+ $this->assertSame( 123, $config->getModifiedIndex() );
+ }
+
/**
* @covers EtcdConfig::__construct
*/
->willReturn( [
'config' => [ 'known' => 'from-cache' ],
'expires' => INF,
+ 'modifiedIndex' => 123
] );
$config = $this->createConfigMock( [ 'cache' => $cache ] );
'class' => HashBagOStuff::class
] ] );
$config->expects( $this->once() )->method( 'fetchAllFromEtcd' )
- ->willReturn( [
- [ 'known' => 'from-fetch' ],
- null, // error
- false // retry?
- ] );
+ ->willReturn( self::createEtcdResponse(
+ [ 'config' => [ 'known' => 'from-fetch' ], ] ) );
$this->assertSame( 'from-fetch', $config->get( 'known' ) );
}
'cache' => $cache,
] );
$mock->expects( $this->once() )->method( 'fetchAllFromEtcd' )
- ->willReturn( [ [ 'known' => 'from-fetch' ], null, false ] );
+ ->willReturn(
+ self::createEtcdResponse( [ 'config' => [ 'known' => 'from-fetch' ] ] ) );
$this->assertSame( 'from-fetch', $mock->get( 'known' ) );
}
'cache' => $cache,
] );
$mock->expects( $this->once() )->method( 'fetchAllFromEtcd' )
- ->willReturn( [ null, 'Fake error', false ] );
+ ->willReturn( self::createEtcdResponse( [ 'error' => 'Fake error', ] ) );
$this->setExpectedException( ConfigException::class );
$mock->get( 'key' );
[
'config' => [ 'known' => 'from-cache' ],
'expires' => INF,
+ 'modifiedIndex' => 123
]
) );
// .. misses lock
->willReturn( [
'config' => [ 'known' => 'from-cache' ],
'expires' => INF,
+ 'modifiedIndex' => 0,
] );
$cache->expects( $this->never() )->method( 'lock' );
->willReturn( [
'config' => [ 'known' => 'from-cache' ],
'expires' => INF,
+ 'modifiedIndex' => 0,
] );
$cache->expects( $this->never() )->method( 'lock' );
[
'config' => [ 'known' => 'from-cache-expired' ],
'expires' => -INF,
+ 'modifiedIndex' => 0,
]
);
// .. gets lock
'cache' => $cache,
] );
$mock->expects( $this->once() )->method( 'fetchAllFromEtcd' )
- ->willReturn( [ [ 'known' => 'from-fetch' ], null, false ] );
+ ->willReturn( self::createEtcdResponse( [ 'config' => [ 'known' => 'from-fetch' ] ] ) );
$this->assertSame( 'from-fetch', $mock->get( 'known' ) );
}
[
'config' => [ 'known' => 'from-cache-expired' ],
'expires' => -INF,
+ 'modifiedIndex' => 0,
]
);
// .. gets lock
'cache' => $cache,
] );
$mock->expects( $this->once() )->method( 'fetchAllFromEtcd' )
- ->willReturn( [ null, 'Fake failure', true ] );
+ ->willReturn( self::createEtcdResponse( [ 'error' => 'Fake failure', 'retry' => true ] ) );
$this->assertSame( 'from-cache-expired', $mock->get( 'known' ) );
}
->willReturn( [
'config' => [ 'known' => 'from-cache-expired' ],
'expires' => -INF,
+ 'modifiedIndex' => 0,
] );
// .. misses lock
$cache->expects( $this->once() )->method( 'lock' )
'body' => json_encode( [ 'node' => [ 'nodes' => [
[
'key' => '/example/foo',
- 'value' => json_encode( [ 'val' => true ] )
+ 'value' => json_encode( [ 'val' => true ] ),
+ 'modifiedIndex' => 123
],
] ] ] ),
'error' => '',
],
- 'expect' => [
- [ 'foo' => true ], // data
- null,
- false // retry
- ],
+ 'expect' => self::createEtcdResponse( [
+ 'config' => [ 'foo' => true ], // data
+ 'modifiedIndex' => 123
+ ] ),
],
'200 OK - Empty dir' => [
'http' => [
'body' => json_encode( [ 'node' => [ 'nodes' => [
[
'key' => '/example/foo',
- 'value' => json_encode( [ 'val' => true ] )
+ 'value' => json_encode( [ 'val' => true ] ),
+ 'modifiedIndex' => 123
],
[
'key' => '/example/sub',
'dir' => true,
+ 'modifiedIndex' => 234,
'nodes' => [],
],
[
'key' => '/example/bar',
- 'value' => json_encode( [ 'val' => false ] )
+ 'value' => json_encode( [ 'val' => false ] ),
+ 'modifiedIndex' => 125
],
] ] ] ),
'error' => '',
],
- 'expect' => [
- [ 'foo' => true, 'bar' => false ], // data
- null,
- false // retry
- ],
+ 'expect' => self::createEtcdResponse( [
+ 'config' => [ 'foo' => true, 'bar' => false ], // data
+ 'modifiedIndex' => 125 // largest modified index
+ ] ),
],
'200 OK - Recursive' => [
'http' => [
[
'key' => '/example/a',
'dir' => true,
+ 'modifiedIndex' => 124,
'nodes' => [
[
'key' => 'b',
'value' => json_encode( [ 'val' => true ] ),
+ 'modifiedIndex' => 123,
+
],
[
'key' => 'c',
'value' => json_encode( [ 'val' => false ] ),
+ 'modifiedIndex' => 123,
],
],
],
] ] ] ),
'error' => '',
],
- 'expect' => [
- [ 'a/b' => true, 'a/c' => false ], // data
- null,
- false // retry
- ],
+ 'expect' => self::createEtcdResponse( [
+ 'config' => [ 'a/b' => true, 'a/c' => false ], // data
+ 'modifiedIndex' => 123 // largest modified index
+ ] ),
],
'200 OK - Missing nodes at second level' => [
'http' => [
[
'key' => '/example/a',
'dir' => true,
+ 'modifiedIndex' => 0,
],
] ] ] ),
'error' => '',
],
- 'expect' => [
- null,
- "Unexpected JSON response in dir 'a'; missing 'nodes' list.",
- false // retry
- ],
+ 'expect' => self::createEtcdResponse( [
+ 'error' => "Unexpected JSON response in dir 'a'; missing 'nodes' list.",
+ ] ),
],
'200 OK - Directory with non-array "nodes" key' => [
'http' => [
] ] ] ),
'error' => '',
],
- 'expect' => [
- null,
- "Unexpected JSON response in dir 'a'; 'nodes' is not an array.",
- false // retry
- ],
+ 'expect' => self::createEtcdResponse( [
+ 'error' => "Unexpected JSON response in dir 'a'; 'nodes' is not an array.",
+ ] ),
],
'200 OK - Correctly encoded garbage response' => [
'http' => [
'body' => json_encode( [ 'foo' => 'bar' ] ),
'error' => '',
],
- 'expect' => [
- null,
- "Unexpected JSON response: Missing or invalid node at top level.",
- false // retry
- ],
+ 'expect' => self::createEtcdResponse( [
+ 'error' => "Unexpected JSON response: Missing or invalid node at top level.",
+ ] ),
],
'200 OK - Bad value' => [
'http' => [
'body' => json_encode( [ 'node' => [ 'nodes' => [
[
'key' => '/example/foo',
- 'value' => ';"broken{value'
+ 'value' => ';"broken{value',
+ 'modifiedIndex' => 123,
]
] ] ] ),
'error' => '',
],
- 'expect' => [
- null, // data
- "Failed to parse value for 'foo'.",
- false // retry
- ],
+ 'expect' => self::createEtcdResponse( [
+ 'error' => "Failed to parse value for 'foo'.",
+ ] ),
],
'200 OK - Empty node list' => [
'http' => [
'code' => 200,
'reason' => 'OK',
'headers' => [],
- 'body' => '{"node":{"nodes":[]}}',
+ 'body' => '{"node":{"nodes":[], "modifiedIndex": 12 }}',
'error' => '',
],
- 'expect' => [
- [], // data
- null,
- false // retry
- ],
+ 'expect' => self::createEtcdResponse( [
+ 'config' => [], // data
+ ] ),
],
'200 OK - Invalid JSON' => [
'http' => [
'body' => '',
'error' => '(curl error: no status set)',
],
- 'expect' => [
- null, // data
- "Error unserializing JSON response.",
- false // retry
- ],
+ 'expect' => self::createEtcdResponse( [
+ 'error' => "Error unserializing JSON response.",
+ ] ),
],
'404 Not Found' => [
'http' => [
'body' => '',
'error' => '',
],
- 'expect' => [
- null, // data
- 'HTTP 404 (Not Found)',
- false // retry
- ],
+ 'expect' => self::createEtcdResponse( [
+ 'error' => 'HTTP 404 (Not Found)',
+ ] ),
],
'400 Bad Request - custom error' => [
'http' => [
'body' => '',
'error' => 'No good reason',
],
- 'expect' => [
- null, // data
- 'No good reason',
- true // retry
- ],
+ 'expect' => self::createEtcdResponse( [
+ 'error' => 'No good reason',
+ 'retry' => true, // retry
+ ] ),
],
];
}
"FROM table " .
"WHERE alias = 'text'"
],
+ [
+ [
+ 'tables' => 'table',
+ 'fields' => [ 'field', 'alias' => 'field2' ],
+ 'conds' => 'alias = \'text\'',
+ ],
+ "SELECT field,field2 AS alias " .
+ "FROM table " .
+ "WHERE alias = 'text'"
+ ],
+ [
+ [
+ 'tables' => 'table',
+ 'fields' => [ 'field', 'alias' => 'field2' ],
+ 'conds' => [],
+ ],
+ "SELECT field,field2 AS alias " .
+ "FROM table"
+ ],
+ [
+ [
+ 'tables' => 'table',
+ 'fields' => [ 'field', 'alias' => 'field2' ],
+ 'conds' => '',
+ ],
+ "SELECT field,field2 AS alias " .
+ "FROM table"
+ ],
+ [
+ [
+ 'tables' => 'table',
+ 'fields' => [ 'field', 'alias' => 'field2' ],
+ 'conds' => '0', // T188314
+ ],
+ "SELECT field,field2 AS alias " .
+ "FROM table " .
+ "WHERE 0"
+ ],
[
[
// 'tables' with space prepended indicates pre-escaped table name