* $wgDebugDumpSqlLength was removed (deprecated in 1.24).
* $wgDebugDBTransactions was removed (deprecated in 1.20).
* $wgRemoteUploadTarget (added in 1.26) removed, replaced by $wgForeignUploadTargets
+* $wgUseXVO has been removed, as it provides functionality only used by
+ custom Wikimedia patches against Squid 2.x that probably noone uses in
+ production anymore. There is now $wgUseKeyHeader that provides similar
+ functionality but instead of the MediaWiki-specific X-Vary-Options header,
+ uses the draft Key header standard.
+* $wgScriptExtension (and support for '.php5' entry points) was removed. See the
+ deprecation notice in the release notes for version 1.25 for advice on how to
+ preserve support for '.php5' entry points via URL rewriting.
=== New features in 1.27 ===
* $wgDataCenterId and $wgDataCenterRoles where added, which will serve as
+++ /dev/null
-<?php
-/**
- * Version of api.php to be used in web servers that require the .php5
- * extension to execute scripts with the PHP5 engine.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-define( 'MW_ENTRY_PHP5', true );
-
-require './api.php';
'SqliteInstaller' => __DIR__ . '/includes/installer/SqliteInstaller.php',
'SqliteMaintenance' => __DIR__ . '/maintenance/sqlite.php',
'SqliteUpdater' => __DIR__ . '/includes/installer/SqliteUpdater.php',
- 'SquidPurgeClient' => __DIR__ . '/includes/SquidPurgeClient.php',
- 'SquidPurgeClientPool' => __DIR__ . '/includes/SquidPurgeClient.php',
+ 'SquidPurgeClient' => __DIR__ . '/includes/clientpool/SquidPurgeClient.php',
+ 'SquidPurgeClientPool' => __DIR__ . '/includes/clientpool/SquidPurgeClientPool.php',
'SquidUpdate' => __DIR__ . '/includes/deferred/SquidUpdate.php',
'SrConverter' => __DIR__ . '/languages/classes/LanguageSr.php',
'StatsOutput' => __DIR__ . '/maintenance/language/StatOutputs.php',
"ext-iconv": "*",
"liuggio/statsd-php-client": "1.0.16",
"mediawiki/at-ease": "1.1.0",
- "oojs/oojs-ui": "0.12.10",
+ "oojs/oojs-ui": "0.12.11",
"oyejorge/less.php": "1.7.0.9",
"php": ">=5.3.3",
"psr/log": "1.0.0",
+++ /dev/null
-<?php
-/**
- * Version of img_auth.php to be used in web servers that require the .php5
- * extension to execute scripts with the PHP5 engine.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-define( 'MW_ENTRY_PHP5', true );
-
-require './img_auth.php';
* This variable was provided to support those providers.
*
* @since 1.11
- * @deprecated since 1.25; support for '.php5' is being phased out of MediaWiki
+ * @deprecated since 1.25; support for '.php5' has been phased out of MediaWiki
* proper. Backward-compatibility can be maintained by configuring your web
* server to rewrite URLs. See RELEASE-NOTES for details.
*/
/**
* The URL path to index.php.
*
- * Defaults to "{$wgScriptPath}/index{$wgScriptExtension}".
+ * Defaults to "{$wgScriptPath}/index.php".
*/
$wgScript = false;
/**
* The URL path to load.php.
*
- * Defaults to "{$wgScriptPath}/load{$wgScriptExtension}".
+ * Defaults to "{$wgScriptPath}/load.php".
* @since 1.17
*/
$wgLoadScript = false;
* - scriptDirUrl URL of the MediaWiki installation, equivalent to $wgScriptPath, e.g.
* https://en.wikipedia.org/w
* - scriptExtension Script extension of the MediaWiki installation, equivalent to
- * $wgScriptExtension, e.g. .php5 defaults to .php
+ * $wgScriptExtension, e.g. ".php5". Defaults to ".php".
*
* - articleUrl Equivalent to $wgArticlePath, e.g. https://en.wikipedia.org/wiki/$1
* - fetchDescription Fetch the text of the remote file description page. Equivalent to
*
* @par Example:
* @code
- * $wgThumbnailScriptPath = "{$wgScriptPath}/thumb{$wgScriptExtension}";
+ * $wgThumbnailScriptPath = "{$wgScriptPath}/thumb.php";
* @endcode
*/
$wgThumbnailScriptPath = false;
$wgUseESI = false;
/**
- * Send X-Vary-Options header for better caching (requires patched Squid)
+ * Send the Key HTTP header for better caching.
+ * See https://datatracker.ietf.org/doc/draft-fielding-http-key/ for details.
+ * @since 1.27
*/
-$wgUseXVO = false;
+$wgUseKeyHeader = false;
/**
- * Add X-Forwarded-Proto to the Vary and X-Vary-Options headers for API
- * requests and RSS/Atom feeds. Use this if you have an SSL termination setup
+ * Add X-Forwarded-Proto to the Vary and Key headers for API requests and
+ * RSS/Atom feeds. Use this if you have an SSL termination setup
* and need to split the cache between HTTP and HTTPS for API requests,
* feed requests and HTTP redirect responses in order to prevent cache
* pollution. This does not affect 'normal' requests to index.php other than
return $status;
}
- $flags = EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY |
+ $flags = EDIT_AUTOSUMMARY |
( $new ? EDIT_NEW : EDIT_UPDATE ) |
( ( $this->minoredit && !$this->isNew ) ? EDIT_MINOR : 0 ) |
( $bot ? EDIT_FORCE_BOT : 0 );
/**
* Get the path to a specified script file, respecting file
- * extensions; this is a wrapper around $wgScriptExtension etc.
+ * extensions; this is a wrapper around $wgScriptPath etc.
* except for 'index' and 'load' which use $wgScript/$wgLoadScript
*
* @param string $script Script filename, sans extension
* @return string
*/
function wfScript( $script = 'index' ) {
- global $wgScriptPath, $wgScriptExtension, $wgScript, $wgLoadScript;
+ global $wgScriptPath, $wgScript, $wgLoadScript;
if ( $script === 'index' ) {
return $wgScript;
} elseif ( $script === 'load' ) {
return $wgLoadScript;
} else {
- return "{$wgScriptPath}/{$script}{$wgScriptExtension}";
+ return "{$wgScriptPath}/{$script}.php";
}
}
Profiler::instance()->getTransactionProfiler()->resetExpectations();
// Do any deferred jobs
- DeferredUpdates::doUpdates( 'commit' );
+ DeferredUpdates::doUpdates( 'commit', 'enqueue' );
// Make sure any lazy jobs are pushed
JobQueueGroup::pushLazyJobs();
*
* @since 1.17
*
- * @param mixed $params,... Parameters as strings, or a single argument that is
+ * @param mixed ... Parameters as strings, or a single argument that is
* an array of strings.
*
* @return Message $this
}
if ( !$foundVary ) {
header( 'Vary: Accept-Encoding' );
- global $wgUseXVO;
- if ( $wgUseXVO ) {
- header( 'X-Vary-Options: Accept-Encoding;list-contains=gzip' );
+ global $wgUseKeyHeader;
+ if ( $wgUseKeyHeader ) {
+ header( 'Key: Accept-Encoding;match=gzip' );
}
}
return $s;
private $mIndexPolicy = 'index';
private $mFollowPolicy = 'follow';
private $mVaryHeader = array(
- 'Accept-Encoding' => array( 'list-contains=gzip' ),
+ 'Accept-Encoding' => array( 'match=gzip' ),
);
/**
* @return bool
*/
function haveCacheVaryCookies() {
- $cookieHeader = $this->getRequest()->getHeader( 'cookie' );
- if ( $cookieHeader === false ) {
- return false;
- }
- $cvCookies = $this->getCacheVaryCookies();
- foreach ( $cvCookies as $cookieName ) {
- # Check for a simple string match, like the way squid does it
- if ( strpos( $cookieHeader, $cookieName ) !== false ) {
+ $request = $this->getRequest();
+ foreach ( $this->getCacheVaryCookies() as $cookieName ) {
+ if ( $request->getCookie( $cookieName, '', '' ) !== '' ) {
wfDebug( __METHOD__ . ": found $cookieName\n" );
return true;
}
* Add an HTTP header that will influence on the cache
*
* @param string $header Header name
- * @param string[]|null $option Options for X-Vary-Options. Possible options are:
- * - "string-contains=$XXX" varies on whether the header value as a string
- * contains $XXX as a substring.
- * - "list-contains=$XXX" varies on whether the header value as a
- * comma-separated list contains $XXX as one of the list items.
+ * @param string[]|null $option Options for the Key header. See
+ * https://datatracker.ietf.org/doc/draft-fielding-http-key/
+ * for the list of valid options.
*/
public function addVaryHeader( $header, array $option = null ) {
if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
}
/**
- * Get a complete X-Vary-Options header
+ * Get a complete Key header
*
* @return string
*/
- public function getXVO() {
+ public function getKeyHeader() {
$cvCookies = $this->getCacheVaryCookies();
$cookiesOption = array();
foreach ( $cvCookies as $cookieName ) {
- $cookiesOption[] = 'string-contains=' . $cookieName;
+ $cookiesOption[] = 'param=' . $cookieName;
}
$this->addVaryHeader( 'Cookie', $cookiesOption );
}
$headers[] = $newheader;
}
- $xvo = 'X-Vary-Options: ' . implode( ',', $headers );
+ $key = 'Key: ' . implode( ',', $headers );
- return $xvo;
+ return $key;
}
/**
- * bug 21672: Add Accept-Language to Vary and XVO headers
+ * T23672: Add Accept-Language to Vary and Key headers
* if there's no 'variant' parameter existed in GET.
*
* For example:
if ( $variant === $lang->getCode() ) {
continue;
} else {
- $aloption[] = 'string-contains=' . $variant;
+ $aloption[] = 'substr=' . $variant;
// IE and some other browsers use BCP 47 standards in
// their Accept-Language header, like "zh-CN" or "zh-Hant".
// We should handle these too.
$variantBCP47 = wfBCP47( $variant );
if ( $variantBCP47 !== $variant ) {
- $aloption[] = 'string-contains=' . $variantBCP47;
+ $aloption[] = 'substr=' . $variantBCP47;
}
}
}
# maintain different caches for logged-in users and non-logged in ones
$response->header( $this->getVaryHeader() );
- if ( $config->get( 'UseXVO' ) ) {
- # Add an X-Vary-Options header for Squid with Wikimedia patches
- $response->header( $this->getXVO() );
+ if ( $config->get( 'UseKeyHeader' ) ) {
+ $response->header( $this->getKeyHeader() );
}
if ( $this->mEnableClientCache ) {
* @return bool
*/
public function userCanPreview() {
- if ( $this->getRequest()->getVal( 'action' ) != 'submit'
- || !$this->getRequest()->wasPosted()
- || !$this->getUser()->matchEditToken(
- $this->getRequest()->getVal( 'wpEditToken' ) )
- ) {
+ $request = $this->getRequest();
+ if ( $request->getVal( 'action' ) !== 'submit' || !$request->wasPosted() ) {
return false;
}
- if ( !$this->getTitle()->isJsSubpage() && !$this->getTitle()->isCssSubpage() ) {
+
+ $user = $this->getUser();
+ if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
return false;
}
- if ( !$this->getTitle()->isSubpageOf( $this->getUser()->getUserPage() ) ) {
+
+ $title = $this->getTitle();
+ if ( !$title->isJsSubpage() && !$title->isCssSubpage() ) {
+ return false;
+ }
+ if ( !$title->isSubpageOf( $user->getUserPage() ) ) {
// Don't execute another user's CSS or JS on preview (T85855)
return false;
}
- return !count( $this->getTitle()->getUserPermissionsErrors( 'edit', $this->getUser() ) );
+ $errors = $title->getUserPermissionsErrors( 'edit', $user );
+ if ( count( $errors ) !== 0 ) {
+ return false;
+ }
+
+ return true;
}
/**
$ps_default = Profiler::instance()->scopedProfileIn( $fname . '-defaults' );
if ( $wgScript === false ) {
- $wgScript = "$wgScriptPath/index$wgScriptExtension";
+ $wgScript = "$wgScriptPath/index.php";
}
if ( $wgLoadScript === false ) {
- $wgLoadScript = "$wgScriptPath/load$wgScriptExtension";
+ $wgLoadScript = "$wgScriptPath/load.php";
}
if ( $wgArticlePath === false ) {
'name' => 'local',
'directory' => $wgUploadDirectory,
'scriptDirUrl' => $wgScriptPath,
- 'scriptExtension' => $wgScriptExtension,
+ 'scriptExtension' => '.php',
'url' => $wgUploadBaseUrl ? $wgUploadBaseUrl . $wgUploadPath : $wgUploadPath,
'hashLevels' => $wgHashedUploadDirectory ? 2 : 0,
'thumbScriptUrl' => $wgThumbnailScriptPath,
$ps_default2 = Profiler::instance()->scopedProfileIn( $fname . '-defaults2' );
-if ( $wgScriptExtension !== '.php' || defined( 'MW_ENTRY_PHP5' ) ) {
- wfWarn( 'Script extensions other than ".php" are deprecated.' );
-}
-
if ( $wgCanonicalServer === false ) {
$wgCanonicalServer = wfExpandUrl( $wgServer, PROTO_HTTP );
}
+++ /dev/null
-<?php
-/**
- * Squid and Varnish cache purging.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * An HTTP 1.0 client built for the purposes of purging Squid and Varnish.
- * Uses asynchronous I/O, allowing purges to be done in a highly parallel
- * manner.
- *
- * Could be replaced by curl_multi_exec() or some such.
- */
-class SquidPurgeClient {
- /** @var string */
- protected $host;
-
- /** @var int */
- protected $port;
-
- /** @var string|bool */
- protected $ip;
-
- /** @var string */
- protected $readState = 'idle';
-
- /** @var string */
- protected $writeBuffer = '';
-
- /** @var array */
- protected $requests = array();
-
- /** @var mixed */
- protected $currentRequestIndex;
-
- const EINTR = 4;
- const EAGAIN = 11;
- const EINPROGRESS = 115;
- const BUFFER_SIZE = 8192;
-
- /**
- * @var resource|null The socket resource, or null for unconnected, or false
- * for disabled due to error.
- */
- protected $socket;
-
- /** @var string */
- protected $readBuffer;
-
- /** @var int */
- protected $bodyRemaining;
-
- /**
- * @param string $server
- * @param array $options
- */
- public function __construct( $server, $options = array() ) {
- $parts = explode( ':', $server, 2 );
- $this->host = $parts[0];
- $this->port = isset( $parts[1] ) ? $parts[1] : 80;
- }
-
- /**
- * Open a socket if there isn't one open already, return it.
- * Returns false on error.
- *
- * @return bool|resource
- */
- protected function getSocket() {
- if ( $this->socket !== null ) {
- return $this->socket;
- }
-
- $ip = $this->getIP();
- if ( !$ip ) {
- $this->log( "DNS error" );
- $this->markDown();
- return false;
- }
- $this->socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );
- socket_set_nonblock( $this->socket );
- MediaWiki\suppressWarnings();
- $ok = socket_connect( $this->socket, $ip, $this->port );
- MediaWiki\restoreWarnings();
- if ( !$ok ) {
- $error = socket_last_error( $this->socket );
- if ( $error !== self::EINPROGRESS ) {
- $this->log( "connection error: " . socket_strerror( $error ) );
- $this->markDown();
- return false;
- }
- }
-
- return $this->socket;
- }
-
- /**
- * Get read socket array for select()
- * @return array
- */
- public function getReadSocketsForSelect() {
- if ( $this->readState == 'idle' ) {
- return array();
- }
- $socket = $this->getSocket();
- if ( $socket === false ) {
- return array();
- }
- return array( $socket );
- }
-
- /**
- * Get write socket array for select()
- * @return array
- */
- public function getWriteSocketsForSelect() {
- if ( !strlen( $this->writeBuffer ) ) {
- return array();
- }
- $socket = $this->getSocket();
- if ( $socket === false ) {
- return array();
- }
- return array( $socket );
- }
-
- /**
- * Get the host's IP address.
- * Does not support IPv6 at present due to the lack of a convenient interface in PHP.
- * @throws MWException
- * @return string
- */
- protected function getIP() {
- if ( $this->ip === null ) {
- if ( IP::isIPv4( $this->host ) ) {
- $this->ip = $this->host;
- } elseif ( IP::isIPv6( $this->host ) ) {
- throw new MWException( '$wgSquidServers does not support IPv6' );
- } else {
- MediaWiki\suppressWarnings();
- $this->ip = gethostbyname( $this->host );
- if ( $this->ip === $this->host ) {
- $this->ip = false;
- }
- MediaWiki\restoreWarnings();
- }
- }
- return $this->ip;
- }
-
- /**
- * Close the socket and ignore any future purge requests.
- * This is called if there is a protocol error.
- */
- protected function markDown() {
- $this->close();
- $this->socket = false;
- }
-
- /**
- * Close the socket but allow it to be reopened for future purge requests
- */
- public function close() {
- if ( $this->socket ) {
- MediaWiki\suppressWarnings();
- socket_set_block( $this->socket );
- socket_shutdown( $this->socket );
- socket_close( $this->socket );
- MediaWiki\restoreWarnings();
- }
- $this->socket = null;
- $this->readBuffer = '';
- // Write buffer is kept since it may contain a request for the next socket
- }
-
- /**
- * Queue a purge operation
- *
- * @param string $url
- */
- public function queuePurge( $url ) {
- global $wgSquidPurgeUseHostHeader;
- $url = SquidUpdate::expand( str_replace( "\n", '', $url ) );
- $request = array();
- if ( $wgSquidPurgeUseHostHeader ) {
- $url = wfParseUrl( $url );
- $host = $url['host'];
- if ( isset( $url['port'] ) && strlen( $url['port'] ) > 0 ) {
- $host .= ":" . $url['port'];
- }
- $path = $url['path'];
- if ( isset( $url['query'] ) && is_string( $url['query'] ) ) {
- $path = wfAppendQuery( $path, $url['query'] );
- }
- $request[] = "PURGE $path HTTP/1.1";
- $request[] = "Host: $host";
- } else {
- $request[] = "PURGE $url HTTP/1.0";
- }
- $request[] = "Connection: Keep-Alive";
- $request[] = "Proxy-Connection: Keep-Alive";
- $request[] = "User-Agent: " . Http::userAgent() . ' ' . __CLASS__;
- // Two ''s to create \r\n\r\n
- $request[] = '';
- $request[] = '';
-
- $this->requests[] = implode( "\r\n", $request );
- if ( $this->currentRequestIndex === null ) {
- $this->nextRequest();
- }
- }
-
- /**
- * @return bool
- */
- public function isIdle() {
- return strlen( $this->writeBuffer ) == 0 && $this->readState == 'idle';
- }
-
- /**
- * Perform pending writes. Call this when socket_select() indicates that writing will not block.
- */
- public function doWrites() {
- if ( !strlen( $this->writeBuffer ) ) {
- return;
- }
- $socket = $this->getSocket();
- if ( !$socket ) {
- return;
- }
-
- if ( strlen( $this->writeBuffer ) <= self::BUFFER_SIZE ) {
- $buf = $this->writeBuffer;
- $flags = MSG_EOR;
- } else {
- $buf = substr( $this->writeBuffer, 0, self::BUFFER_SIZE );
- $flags = 0;
- }
- MediaWiki\suppressWarnings();
- $bytesSent = socket_send( $socket, $buf, strlen( $buf ), $flags );
- MediaWiki\restoreWarnings();
-
- if ( $bytesSent === false ) {
- $error = socket_last_error( $socket );
- if ( $error != self::EAGAIN && $error != self::EINTR ) {
- $this->log( 'write error: ' . socket_strerror( $error ) );
- $this->markDown();
- }
- return;
- }
-
- $this->writeBuffer = substr( $this->writeBuffer, $bytesSent );
- }
-
- /**
- * Read some data. Call this when socket_select() indicates that the read buffer is non-empty.
- */
- public function doReads() {
- $socket = $this->getSocket();
- if ( !$socket ) {
- return;
- }
-
- $buf = '';
- MediaWiki\suppressWarnings();
- $bytesRead = socket_recv( $socket, $buf, self::BUFFER_SIZE, 0 );
- MediaWiki\restoreWarnings();
- if ( $bytesRead === false ) {
- $error = socket_last_error( $socket );
- if ( $error != self::EAGAIN && $error != self::EINTR ) {
- $this->log( 'read error: ' . socket_strerror( $error ) );
- $this->markDown();
- return;
- }
- } elseif ( $bytesRead === 0 ) {
- // Assume EOF
- $this->close();
- return;
- }
-
- $this->readBuffer .= $buf;
- while ( $this->socket && $this->processReadBuffer() === 'continue' );
- }
-
- /**
- * @throws MWException
- * @return string
- */
- protected function processReadBuffer() {
- switch ( $this->readState ) {
- case 'idle':
- return 'done';
- case 'status':
- case 'header':
- $lines = explode( "\r\n", $this->readBuffer, 2 );
- if ( count( $lines ) < 2 ) {
- return 'done';
- }
- if ( $this->readState == 'status' ) {
- $this->processStatusLine( $lines[0] );
- } else { // header
- $this->processHeaderLine( $lines[0] );
- }
- $this->readBuffer = $lines[1];
- return 'continue';
- case 'body':
- if ( $this->bodyRemaining !== null ) {
- if ( $this->bodyRemaining > strlen( $this->readBuffer ) ) {
- $this->bodyRemaining -= strlen( $this->readBuffer );
- $this->readBuffer = '';
- return 'done';
- } else {
- $this->readBuffer = substr( $this->readBuffer, $this->bodyRemaining );
- $this->bodyRemaining = 0;
- $this->nextRequest();
- return 'continue';
- }
- } else {
- // No content length, read all data to EOF
- $this->readBuffer = '';
- return 'done';
- }
- default:
- throw new MWException( __METHOD__ . ': unexpected state' );
- }
- }
-
- /**
- * @param string $line
- */
- protected function processStatusLine( $line ) {
- if ( !preg_match( '!^HTTP/(\d+)\.(\d+) (\d{3}) (.*)$!', $line, $m ) ) {
- $this->log( 'invalid status line' );
- $this->markDown();
- return;
- }
- list( , , , $status, $reason ) = $m;
- $status = intval( $status );
- if ( $status !== 200 && $status !== 404 ) {
- $this->log( "unexpected status code: $status $reason" );
- $this->markDown();
- return;
- }
- $this->readState = 'header';
- }
-
- /**
- * @param string $line
- */
- protected function processHeaderLine( $line ) {
- if ( preg_match( '/^Content-Length: (\d+)$/i', $line, $m ) ) {
- $this->bodyRemaining = intval( $m[1] );
- } elseif ( $line === '' ) {
- $this->readState = 'body';
- }
- }
-
- protected function nextRequest() {
- if ( $this->currentRequestIndex !== null ) {
- unset( $this->requests[$this->currentRequestIndex] );
- }
- if ( count( $this->requests ) ) {
- $this->readState = 'status';
- $this->currentRequestIndex = key( $this->requests );
- $this->writeBuffer = $this->requests[$this->currentRequestIndex];
- } else {
- $this->readState = 'idle';
- $this->currentRequestIndex = null;
- $this->writeBuffer = '';
- }
- $this->bodyRemaining = null;
- }
-
- /**
- * @param string $msg
- */
- protected function log( $msg ) {
- wfDebugLog( 'squid', __CLASS__ . " ($this->host): $msg" );
- }
-}
-
-class SquidPurgeClientPool {
- /** @var array Array of SquidPurgeClient */
- protected $clients = array();
-
- /** @var int */
- protected $timeout = 5;
-
- /**
- * @param array $options
- */
- function __construct( $options = array() ) {
- if ( isset( $options['timeout'] ) ) {
- $this->timeout = $options['timeout'];
- }
- }
-
- /**
- * @param SquidPurgeClient $client
- * @return void
- */
- public function addClient( $client ) {
- $this->clients[] = $client;
- }
-
- public function run() {
- $done = false;
- $startTime = microtime( true );
- while ( !$done ) {
- $readSockets = $writeSockets = array();
- /**
- * @var $client SquidPurgeClient
- */
- foreach ( $this->clients as $clientIndex => $client ) {
- $sockets = $client->getReadSocketsForSelect();
- foreach ( $sockets as $i => $socket ) {
- $readSockets["$clientIndex/$i"] = $socket;
- }
- $sockets = $client->getWriteSocketsForSelect();
- foreach ( $sockets as $i => $socket ) {
- $writeSockets["$clientIndex/$i"] = $socket;
- }
- }
- if ( !count( $readSockets ) && !count( $writeSockets ) ) {
- break;
- }
- $exceptSockets = null;
- $timeout = min( $startTime + $this->timeout - microtime( true ), 1 );
- MediaWiki\suppressWarnings();
- $numReady = socket_select( $readSockets, $writeSockets, $exceptSockets, $timeout );
- MediaWiki\restoreWarnings();
- if ( $numReady === false ) {
- wfDebugLog( 'squid', __METHOD__ . ': Error in stream_select: ' .
- socket_strerror( socket_last_error() ) . "\n" );
- break;
- }
- // Check for timeout, use 1% tolerance since we aimed at having socket_select()
- // exit at precisely the overall timeout
- if ( microtime( true ) - $startTime > $this->timeout * 0.99 ) {
- wfDebugLog( 'squid', __CLASS__ . ": timeout ({$this->timeout}s)\n" );
- break;
- } elseif ( !$numReady ) {
- continue;
- }
-
- foreach ( $readSockets as $key => $socket ) {
- list( $clientIndex, ) = explode( '/', $key );
- $client = $this->clients[$clientIndex];
- $client->doReads();
- }
- foreach ( $writeSockets as $key => $socket ) {
- list( $clientIndex, ) = explode( '/', $key );
- $client = $this->clients[$clientIndex];
- $client->doWrites();
- }
-
- $done = true;
- foreach ( $this->clients as $client ) {
- if ( !$client->isIdle() ) {
- $done = false;
- }
- }
- }
- foreach ( $this->clients as $client ) {
- $client->close();
- }
- }
-}
* @return bool
*/
public function checkUrlExtension( $extWhitelist = array() ) {
- global $wgScriptExtension;
- $extWhitelist[] = ltrim( $wgScriptExtension, '.' );
+ $extWhitelist[] = 'php';
if ( IEUrlExtension::areServerVarsBad( $_SERVER, $extWhitelist ) ) {
if ( !$this->wasPosted() ) {
$newUrl = IEUrlExtension::fixUrlForIE6(
return ObjectCache::getMainWANInstance()->getWithSetCallback(
self::getCacheKey( $page->getTitle(), $page->getLatest() ),
+ 86400 * 7,
function ( $oldValue, &$ttl, &$setOpts ) use ( $page, $config, $fname ) {
$title = $page->getTitle();
$id = $title->getArticleID();
);
return $result;
- },
- 86400 * 7
+ }
);
}
*/
class ApiDelete extends ApiBase {
/**
- * Extracts the title, token, and reason from the request parameters and invokes
+ * Extracts the title and reason from the request parameters and invokes
* the local delete() function with these as arguments. It does not make use of
* the delete function specified by Article.php. If the deletion succeeds, the
* details of the article deleted and the reason for deletion are added to the
$reason = $params['reason'];
$user = $this->getUser();
+ // Check that the user is allowed to carry out the deletion
+ $errors = $titleObj->getUserPermissionsErrors( 'delete', $user );
+ if ( count( $errors ) ) {
+ $this->dieUsageMsg( $errors[0] );
+ }
+
+ // If change tagging was requested, check that the user is allowed to tag,
+ // and the tags are valid
+ if ( count( $params['tags'] ) ) {
+ $tagStatus = ChangeTags::canAddTagsAccompanyingChange( $params['tags'], $user );
+ if ( !$tagStatus->isOK() ) {
+ $this->dieStatus( $tagStatus );
+ }
+ }
+
if ( $titleObj->getNamespace() == NS_FILE ) {
$status = self::deleteFile(
$pageObj,
$user,
- $params['token'],
$params['oldimage'],
$reason,
false
);
} else {
- $status = self::delete( $pageObj, $user, $params['token'], $reason );
+ $status = self::delete( $pageObj, $user, $reason );
}
if ( is_array( $status ) ) {
}
$this->setWatch( $watch, $titleObj, 'watchdeletion' );
+ // Apply change tags to the log entry, if requested
+ if ( count( $params['tags'] ) ) {
+ ChangeTags::addTags( $params['tags'], null, null, $status->value, null, $user );
+ }
+
$r = array(
'title' => $titleObj->getPrefixedText(),
'reason' => $reason,
$this->getResult()->addValue( null, $this->getModuleName(), $r );
}
- /**
- * @param Title $title
- * @param User $user User doing the action
- * @param string $token
- * @return array
- */
- private static function getPermissionsError( $title, $user, $token ) {
- // Check permissions
- return $title->getUserPermissionsErrors( 'delete', $user );
- }
-
/**
* We have our own delete() function, since Article.php's implementation is split in two phases
*
* @param Page|WikiPage $page Page or WikiPage object to work on
* @param User $user User doing the action
- * @param string $token Delete token (same as edit token)
* @param string|null $reason Reason for the deletion. Autogenerated if null
* @return Status|array
*/
- public static function delete( Page $page, User $user, $token, &$reason = null ) {
+ protected static function delete( Page $page, User $user, &$reason = null ) {
$title = $page->getTitle();
- $errors = self::getPermissionsError( $title, $user, $token );
- if ( count( $errors ) ) {
- return $errors;
- }
// Auto-generate a summary, if necessary
if ( is_null( $reason ) ) {
/**
* @param Page $page Object to work on
* @param User $user User doing the action
- * @param string $token Delete token (same as edit token)
* @param string $oldimage Archive name
* @param string $reason Reason for the deletion. Autogenerated if null.
* @param bool $suppress Whether to mark all deleted versions as restricted
* @return Status|array
*/
- public static function deleteFile( Page $page, User $user, $token, $oldimage,
+ protected static function deleteFile( Page $page, User $user, $oldimage,
&$reason = null, $suppress = false
) {
$title = $page->getTitle();
- $errors = self::getPermissionsError( $title, $user, $token );
- if ( count( $errors ) ) {
- return $errors;
- }
$file = $page->getFile();
if ( !$file->exists() || !$file->isLocal() || $file->getRedirected() ) {
- return self::delete( $page, $user, $token, $reason );
+ return self::delete( $page, $user, $reason );
}
if ( $oldimage ) {
ApiBase::PARAM_TYPE => 'integer'
),
'reason' => null,
+ 'tags' => array(
+ ApiBase::PARAM_TYPE => ChangeTags::listExplicitlyDefinedTags(),
+ ApiBase::PARAM_ISMULTI => true,
+ ),
'watch' => array(
ApiBase::PARAM_DFLT => false,
ApiBase::PARAM_DEPRECATED => true,
return;
}
- $useXVO = $config->get( 'UseXVO' );
+ $useKeyHeader = $config->get( 'UseKeyHeader' );
if ( $this->mCacheMode == 'anon-public-user-private' ) {
$out->addVaryHeader( 'Cookie' );
$response->header( $out->getVaryHeader() );
- if ( $useXVO ) {
- $response->header( $out->getXVO() );
+ if ( $useKeyHeader ) {
+ $response->header( $out->getKeyHeader() );
if ( $out->haveCacheVaryCookies() ) {
// Logged in, mark this request private
$response->header( "Cache-Control: $privateCache" );
$response->header( "Cache-Control: $privateCache" );
return;
- } // else no XVO and anonymous, send public headers below
+ } // else no Key and anonymous, send public headers below
}
// Send public headers
$response->header( $out->getVaryHeader() );
- if ( $useXVO ) {
- $response->header( $out->getXVO() );
+ if ( $useKeyHeader ) {
+ $response->header( $out->getKeyHeader() );
}
// If nobody called setCacheMaxAge(), use the (s)maxage parameters
"apihelp-delete-param-title": "Title of the page to delete. Cannot be used together with <var>$1pageid</var>.",
"apihelp-delete-param-pageid": "Page ID of the page to delete. Cannot be used together with <var>$1title</var>.",
"apihelp-delete-param-reason": "Reason for the deletion. If not set, an automatically generated reason will be used.",
+ "apihelp-delete-param-tags": "Change tags to apply to the entry in the deletion log.",
"apihelp-delete-param-watch": "Add the page to the current user's watchlist.",
"apihelp-delete-param-watchlist": "Unconditionally add or remove the page from the current user's watchlist, use preferences or do not change watch.",
"apihelp-delete-param-unwatch": "Remove the page from the current user's watchlist.",
"apihelp-delete-param-title": "{{doc-apihelp-param|delete|title}}",
"apihelp-delete-param-pageid": "{{doc-apihelp-param|delete|pageid}}",
"apihelp-delete-param-reason": "{{doc-apihelp-param|delete|reason}}",
+ "apihelp-delete-param-tags": "{{doc-apihelp-param|delete|tags}}",
"apihelp-delete-param-watch": "{{doc-apihelp-param|delete|watch}}",
"apihelp-delete-param-watchlist": "{{doc-apihelp-param|delete|watchlist}}",
"apihelp-delete-param-unwatch": "{{doc-apihelp-param|delete|unwatch}}",
public static function listExtensionActivatedTags() {
return ObjectCache::getMainWANInstance()->getWithSetCallback(
wfMemcKey( 'active-tags' ),
+ 300,
function ( $oldValue, &$ttl, array &$setOpts ) {
$setOpts += Database::getCacheSetOptions( wfGetDB( DB_SLAVE ) );
Hooks::run( 'ChangeTagsListActive', array( &$extensionActive ) );
return $extensionActive;
},
- 300,
- array( wfMemcKey( 'active-tags' ) ),
- array( 'lockTSE' => INF )
+ array(
+ 'checkKeys' => array( wfMemcKey( 'active-tags' ) ),
+ 'lockTSE' => INF
+ )
);
}
return ObjectCache::getMainWANInstance()->getWithSetCallback(
wfMemcKey( 'valid-tags-db' ),
+ 300,
function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
$dbr = wfGetDB( DB_SLAVE );
return array_filter( array_unique( $tags ) );
},
- 300,
- array( wfMemcKey( 'valid-tags-db' ) ),
- array( 'lockTSE' => INF )
+ array(
+ 'checkKeys' => array( wfMemcKey( 'valid-tags-db' ) ),
+ 'lockTSE' => INF
+ )
);
}
public static function listExtensionDefinedTags() {
return ObjectCache::getMainWANInstance()->getWithSetCallback(
wfMemcKey( 'valid-tags-hook' ),
+ 300,
function ( $oldValue, &$ttl, array &$setOpts ) {
$setOpts += Database::getCacheSetOptions( wfGetDB( DB_SLAVE ) );
Hooks::run( 'ListDefinedTags', array( &$tags ) );
return array_filter( array_unique( $tags ) );
},
- 300,
- array( wfMemcKey( 'valid-tags-hook' ) ),
- array( 'lockTSE' => INF )
+ array(
+ 'checkKeys' => array( wfMemcKey( 'valid-tags-hook' ) ),
+ 'lockTSE' => INF
+ )
);
}
$fname = __METHOD__;
$cachedStats = ObjectCache::getMainWANInstance()->getWithSetCallback(
wfMemcKey( 'change-tag-statistics' ),
+ 300,
function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
$dbr = wfGetDB( DB_SLAVE, 'vslow' );
return $out;
},
- 300,
- array( wfMemcKey( 'change-tag-statistics' ) ),
- array( 'lockTSE' => INF )
+ array(
+ 'checkKeys' => array( wfMemcKey( 'change-tag-statistics' ) ),
+ 'lockTSE' => INF
+ )
);
return $cachedStats;
--- /dev/null
+<?php
+/**
+ * Squid and Varnish cache purging.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * An HTTP 1.0 client built for the purposes of purging Squid and Varnish.
+ * Uses asynchronous I/O, allowing purges to be done in a highly parallel
+ * manner.
+ *
+ * Could be replaced by curl_multi_exec() or some such.
+ */
+class SquidPurgeClient {
+ /** @var string */
+ protected $host;
+
+ /** @var int */
+ protected $port;
+
+ /** @var string|bool */
+ protected $ip;
+
+ /** @var string */
+ protected $readState = 'idle';
+
+ /** @var string */
+ protected $writeBuffer = '';
+
+ /** @var array */
+ protected $requests = array();
+
+ /** @var mixed */
+ protected $currentRequestIndex;
+
+ const EINTR = 4;
+ const EAGAIN = 11;
+ const EINPROGRESS = 115;
+ const BUFFER_SIZE = 8192;
+
+ /**
+ * @var resource|null The socket resource, or null for unconnected, or false
+ * for disabled due to error.
+ */
+ protected $socket;
+
+ /** @var string */
+ protected $readBuffer;
+
+ /** @var int */
+ protected $bodyRemaining;
+
+ /**
+ * @param string $server
+ * @param array $options
+ */
+ public function __construct( $server, $options = array() ) {
+ $parts = explode( ':', $server, 2 );
+ $this->host = $parts[0];
+ $this->port = isset( $parts[1] ) ? $parts[1] : 80;
+ }
+
+ /**
+ * Open a socket if there isn't one open already, return it.
+ * Returns false on error.
+ *
+ * @return bool|resource
+ */
+ protected function getSocket() {
+ if ( $this->socket !== null ) {
+ return $this->socket;
+ }
+
+ $ip = $this->getIP();
+ if ( !$ip ) {
+ $this->log( "DNS error" );
+ $this->markDown();
+ return false;
+ }
+ $this->socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );
+ socket_set_nonblock( $this->socket );
+ MediaWiki\suppressWarnings();
+ $ok = socket_connect( $this->socket, $ip, $this->port );
+ MediaWiki\restoreWarnings();
+ if ( !$ok ) {
+ $error = socket_last_error( $this->socket );
+ if ( $error !== self::EINPROGRESS ) {
+ $this->log( "connection error: " . socket_strerror( $error ) );
+ $this->markDown();
+ return false;
+ }
+ }
+
+ return $this->socket;
+ }
+
+ /**
+ * Get read socket array for select()
+ * @return array
+ */
+ public function getReadSocketsForSelect() {
+ if ( $this->readState == 'idle' ) {
+ return array();
+ }
+ $socket = $this->getSocket();
+ if ( $socket === false ) {
+ return array();
+ }
+ return array( $socket );
+ }
+
+ /**
+ * Get write socket array for select()
+ * @return array
+ */
+ public function getWriteSocketsForSelect() {
+ if ( !strlen( $this->writeBuffer ) ) {
+ return array();
+ }
+ $socket = $this->getSocket();
+ if ( $socket === false ) {
+ return array();
+ }
+ return array( $socket );
+ }
+
+ /**
+ * Get the host's IP address.
+ * Does not support IPv6 at present due to the lack of a convenient interface in PHP.
+ * @throws MWException
+ * @return string
+ */
+ protected function getIP() {
+ if ( $this->ip === null ) {
+ if ( IP::isIPv4( $this->host ) ) {
+ $this->ip = $this->host;
+ } elseif ( IP::isIPv6( $this->host ) ) {
+ throw new MWException( '$wgSquidServers does not support IPv6' );
+ } else {
+ MediaWiki\suppressWarnings();
+ $this->ip = gethostbyname( $this->host );
+ if ( $this->ip === $this->host ) {
+ $this->ip = false;
+ }
+ MediaWiki\restoreWarnings();
+ }
+ }
+ return $this->ip;
+ }
+
+ /**
+ * Close the socket and ignore any future purge requests.
+ * This is called if there is a protocol error.
+ */
+ protected function markDown() {
+ $this->close();
+ $this->socket = false;
+ }
+
+ /**
+ * Close the socket but allow it to be reopened for future purge requests
+ */
+ public function close() {
+ if ( $this->socket ) {
+ MediaWiki\suppressWarnings();
+ socket_set_block( $this->socket );
+ socket_shutdown( $this->socket );
+ socket_close( $this->socket );
+ MediaWiki\restoreWarnings();
+ }
+ $this->socket = null;
+ $this->readBuffer = '';
+ // Write buffer is kept since it may contain a request for the next socket
+ }
+
+ /**
+ * Queue a purge operation
+ *
+ * @param string $url
+ */
+ public function queuePurge( $url ) {
+ global $wgSquidPurgeUseHostHeader;
+ $url = SquidUpdate::expand( str_replace( "\n", '', $url ) );
+ $request = array();
+ if ( $wgSquidPurgeUseHostHeader ) {
+ $url = wfParseUrl( $url );
+ $host = $url['host'];
+ if ( isset( $url['port'] ) && strlen( $url['port'] ) > 0 ) {
+ $host .= ":" . $url['port'];
+ }
+ $path = $url['path'];
+ if ( isset( $url['query'] ) && is_string( $url['query'] ) ) {
+ $path = wfAppendQuery( $path, $url['query'] );
+ }
+ $request[] = "PURGE $path HTTP/1.1";
+ $request[] = "Host: $host";
+ } else {
+ $request[] = "PURGE $url HTTP/1.0";
+ }
+ $request[] = "Connection: Keep-Alive";
+ $request[] = "Proxy-Connection: Keep-Alive";
+ $request[] = "User-Agent: " . Http::userAgent() . ' ' . __CLASS__;
+ // Two ''s to create \r\n\r\n
+ $request[] = '';
+ $request[] = '';
+
+ $this->requests[] = implode( "\r\n", $request );
+ if ( $this->currentRequestIndex === null ) {
+ $this->nextRequest();
+ }
+ }
+
+ /**
+ * @return bool
+ */
+ public function isIdle() {
+ return strlen( $this->writeBuffer ) == 0 && $this->readState == 'idle';
+ }
+
+ /**
+ * Perform pending writes. Call this when socket_select() indicates that writing will not block.
+ */
+ public function doWrites() {
+ if ( !strlen( $this->writeBuffer ) ) {
+ return;
+ }
+ $socket = $this->getSocket();
+ if ( !$socket ) {
+ return;
+ }
+
+ if ( strlen( $this->writeBuffer ) <= self::BUFFER_SIZE ) {
+ $buf = $this->writeBuffer;
+ $flags = MSG_EOR;
+ } else {
+ $buf = substr( $this->writeBuffer, 0, self::BUFFER_SIZE );
+ $flags = 0;
+ }
+ MediaWiki\suppressWarnings();
+ $bytesSent = socket_send( $socket, $buf, strlen( $buf ), $flags );
+ MediaWiki\restoreWarnings();
+
+ if ( $bytesSent === false ) {
+ $error = socket_last_error( $socket );
+ if ( $error != self::EAGAIN && $error != self::EINTR ) {
+ $this->log( 'write error: ' . socket_strerror( $error ) );
+ $this->markDown();
+ }
+ return;
+ }
+
+ $this->writeBuffer = substr( $this->writeBuffer, $bytesSent );
+ }
+
+ /**
+ * Read some data. Call this when socket_select() indicates that the read buffer is non-empty.
+ */
+ public function doReads() {
+ $socket = $this->getSocket();
+ if ( !$socket ) {
+ return;
+ }
+
+ $buf = '';
+ MediaWiki\suppressWarnings();
+ $bytesRead = socket_recv( $socket, $buf, self::BUFFER_SIZE, 0 );
+ MediaWiki\restoreWarnings();
+ if ( $bytesRead === false ) {
+ $error = socket_last_error( $socket );
+ if ( $error != self::EAGAIN && $error != self::EINTR ) {
+ $this->log( 'read error: ' . socket_strerror( $error ) );
+ $this->markDown();
+ return;
+ }
+ } elseif ( $bytesRead === 0 ) {
+ // Assume EOF
+ $this->close();
+ return;
+ }
+
+ $this->readBuffer .= $buf;
+ while ( $this->socket && $this->processReadBuffer() === 'continue' );
+ }
+
+ /**
+ * @throws MWException
+ * @return string
+ */
+ protected function processReadBuffer() {
+ switch ( $this->readState ) {
+ case 'idle':
+ return 'done';
+ case 'status':
+ case 'header':
+ $lines = explode( "\r\n", $this->readBuffer, 2 );
+ if ( count( $lines ) < 2 ) {
+ return 'done';
+ }
+ if ( $this->readState == 'status' ) {
+ $this->processStatusLine( $lines[0] );
+ } else { // header
+ $this->processHeaderLine( $lines[0] );
+ }
+ $this->readBuffer = $lines[1];
+ return 'continue';
+ case 'body':
+ if ( $this->bodyRemaining !== null ) {
+ if ( $this->bodyRemaining > strlen( $this->readBuffer ) ) {
+ $this->bodyRemaining -= strlen( $this->readBuffer );
+ $this->readBuffer = '';
+ return 'done';
+ } else {
+ $this->readBuffer = substr( $this->readBuffer, $this->bodyRemaining );
+ $this->bodyRemaining = 0;
+ $this->nextRequest();
+ return 'continue';
+ }
+ } else {
+ // No content length, read all data to EOF
+ $this->readBuffer = '';
+ return 'done';
+ }
+ default:
+ throw new MWException( __METHOD__ . ': unexpected state' );
+ }
+ }
+
+ /**
+ * @param string $line
+ */
+ protected function processStatusLine( $line ) {
+ if ( !preg_match( '!^HTTP/(\d+)\.(\d+) (\d{3}) (.*)$!', $line, $m ) ) {
+ $this->log( 'invalid status line' );
+ $this->markDown();
+ return;
+ }
+ list( , , , $status, $reason ) = $m;
+ $status = intval( $status );
+ if ( $status !== 200 && $status !== 404 ) {
+ $this->log( "unexpected status code: $status $reason" );
+ $this->markDown();
+ return;
+ }
+ $this->readState = 'header';
+ }
+
+ /**
+ * @param string $line
+ */
+ protected function processHeaderLine( $line ) {
+ if ( preg_match( '/^Content-Length: (\d+)$/i', $line, $m ) ) {
+ $this->bodyRemaining = intval( $m[1] );
+ } elseif ( $line === '' ) {
+ $this->readState = 'body';
+ }
+ }
+
+ protected function nextRequest() {
+ if ( $this->currentRequestIndex !== null ) {
+ unset( $this->requests[$this->currentRequestIndex] );
+ }
+ if ( count( $this->requests ) ) {
+ $this->readState = 'status';
+ $this->currentRequestIndex = key( $this->requests );
+ $this->writeBuffer = $this->requests[$this->currentRequestIndex];
+ } else {
+ $this->readState = 'idle';
+ $this->currentRequestIndex = null;
+ $this->writeBuffer = '';
+ }
+ $this->bodyRemaining = null;
+ }
+
+ /**
+ * @param string $msg
+ */
+ protected function log( $msg ) {
+ wfDebugLog( 'squid', __CLASS__ . " ($this->host): $msg" );
+ }
+}
--- /dev/null
+<?php
+/**
+ * Squid and Varnish cache purging.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+class SquidPurgeClientPool {
+ /** @var array Array of SquidPurgeClient */
+ protected $clients = array();
+
+ /** @var int */
+ protected $timeout = 5;
+
+ /**
+ * @param array $options
+ */
+ function __construct( $options = array() ) {
+ if ( isset( $options['timeout'] ) ) {
+ $this->timeout = $options['timeout'];
+ }
+ }
+
+ /**
+ * @param SquidPurgeClient $client
+ * @return void
+ */
+ public function addClient( $client ) {
+ $this->clients[] = $client;
+ }
+
+ public function run() {
+ $done = false;
+ $startTime = microtime( true );
+ while ( !$done ) {
+ $readSockets = $writeSockets = array();
+ /**
+ * @var $client SquidPurgeClient
+ */
+ foreach ( $this->clients as $clientIndex => $client ) {
+ $sockets = $client->getReadSocketsForSelect();
+ foreach ( $sockets as $i => $socket ) {
+ $readSockets["$clientIndex/$i"] = $socket;
+ }
+ $sockets = $client->getWriteSocketsForSelect();
+ foreach ( $sockets as $i => $socket ) {
+ $writeSockets["$clientIndex/$i"] = $socket;
+ }
+ }
+ if ( !count( $readSockets ) && !count( $writeSockets ) ) {
+ break;
+ }
+ $exceptSockets = null;
+ $timeout = min( $startTime + $this->timeout - microtime( true ), 1 );
+ MediaWiki\suppressWarnings();
+ $numReady = socket_select( $readSockets, $writeSockets, $exceptSockets, $timeout );
+ MediaWiki\restoreWarnings();
+ if ( $numReady === false ) {
+ wfDebugLog( 'squid', __METHOD__ . ': Error in stream_select: ' .
+ socket_strerror( socket_last_error() ) . "\n" );
+ break;
+ }
+ // Check for timeout, use 1% tolerance since we aimed at having socket_select()
+ // exit at precisely the overall timeout
+ if ( microtime( true ) - $startTime > $this->timeout * 0.99 ) {
+ wfDebugLog( 'squid', __CLASS__ . ": timeout ({$this->timeout}s)\n" );
+ break;
+ } elseif ( !$numReady ) {
+ continue;
+ }
+
+ foreach ( $readSockets as $key => $socket ) {
+ list( $clientIndex, ) = explode( '/', $key );
+ $client = $this->clients[$clientIndex];
+ $client->doReads();
+ }
+ foreach ( $writeSockets as $key => $socket ) {
+ list( $clientIndex, ) = explode( '/', $key );
+ $client = $this->clients[$clientIndex];
+ $client->doWrites();
+ }
+
+ $done = true;
+ foreach ( $this->clients as $client ) {
+ if ( !$client->isIdle() ) {
+ $done = false;
+ }
+ }
+ }
+ foreach ( $this->clients as $client ) {
+ $client->close();
+ }
+ }
+}
/** @var array|stdClass|bool */
protected $currentRow = null;
+ /**
+ * @param array $array
+ */
function __construct( $array ) {
$this->result = $array;
}
/** @var LBFactory */
private static $instance;
+ /**
+ * Construct a factory based on a configuration array (typically from $wgLBFactoryConf)
+ * @param array $conf
+ */
+ public function __construct( array $conf ) {
+ }
+
/**
* Disables all access to the load balancer, will cause all database access
* to throw a DBAccessError
self::$instance = $instance;
}
- /**
- * Construct a factory based on a configuration array (typically from $wgLBFactoryConf)
- * @param array $conf
- */
- abstract public function __construct( array $conf );
-
/**
* Create a new load balancer object. The resulting object will be untracked,
* not chronology-protected, and the caller is responsible for cleaning it up.
* @throws MWException
*/
public function __construct( array $conf ) {
+ parent::__construct( $conf );
+
$this->chronProt = new ChronologyProtector;
$this->conf = $conf;
$required = array( 'sectionsByDB', 'sectionLoads', 'serverTemplate' );
private $loadMonitorClass;
public function __construct( array $conf ) {
+ parent::__construct( $conf );
+
$this->chronProt = new ChronologyProtector;
$this->loadMonitorClass = isset( $conf['loadMonitorClass'] )
? $conf['loadMonitorClass']
/** @var integer Warn when this many connection are held */
const CONN_HELD_WARN_THRESHOLD = 10;
+ /** @var integer Default 'max lag' when unspecified */
+ const MAX_LAG = 30;
/**
* @param array $params Array with keys:
* @param float $maxLag Restrict the maximum allowed lag to this many seconds
* @return bool|int|string
*/
- private function getRandomNonLagged( array $loads, $wiki = false, $maxLag = INF ) {
+ private function getRandomNonLagged( array $loads, $wiki = false, $maxLag = self::MAX_LAG ) {
$lags = $this->getLagTimes( $wiki );
# Unset excessively lagged servers
* @since 1.19
*/
class DeferredUpdates {
- /** @var array Updates to be deferred until the end of the request */
+ /** @var DeferrableUpdate[] Updates to be deferred until the end of the request */
private static $updates = array();
/** @var bool Defer updates fully even in CLI mode */
private static $forceDeferral = false;
* Do any deferred updates and clear the list
*
* @param string $commit Set to 'commit' to commit after every update to
+ * @param string $mode Use "enqueue" to use the job queue when possible [Default: run]
* prevent lock contention
*/
- public static function doUpdates( $commit = '' ) {
+ public static function doUpdates( $commit = '', $mode = 'run' ) {
$updates = self::$updates;
while ( count( $updates ) ) {
self::clearPendingUpdates();
-
- /** @var DeferrableUpdate $update */
+ /** @var DataUpdate[] $dataUpdates */
+ $dataUpdates = array();
+ /** @var DeferrableUpdate[] $otherUpdates */
+ $otherUpdates = array();
foreach ( $updates as $update ) {
+ if ( $update instanceof DataUpdate ) {
+ $dataUpdates[] = $update;
+ } else {
+ $otherUpdates[] = $update;
+ }
+ }
+
+ // Delegate DataUpdate execution to the DataUpdate class
+ DataUpdate::runUpdates( $dataUpdates, $mode );
+ // Execute the non-DataUpdate tasks
+ foreach ( $otherUpdates as $update ) {
try {
$update->doUpdate();
-
if ( $commit === 'commit' ) {
wfGetLBFactory()->commitMasterChanges();
}
*
* @todo document (e.g. one-sentence top-level class description).
*/
-class LinksUpdate extends SqlDataUpdate {
+class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
// @todo make members protected, but make sure extensions don't break
/** @var int Page ID of the article linked from */
);
}
}
+
+ public function getAsJobSpecification() {
+ return array(
+ 'wiki' => $this->mDb->getWikiID(),
+ 'job' => new JobSpecification(
+ 'refreshLinks',
+ array( 'prioritize' => true ),
+ array( 'removeDuplicates' => true ),
+ $this->getTitle()
+ )
+ );
+ }
}
protected $scriptDirUrl;
/** @var string Script extension of the MediaWiki installation, equivalent
- * to $wgScriptExtension, e.g. .php5 defaults to .php */
+ * to the old $wgScriptExtension, e.g. .php5 defaults to .php */
protected $scriptExtension;
/** @var string Equivalent to $wgArticlePath, e.g. https://en.wikipedia.org/wiki/$1 */
* @return Closure
*/
protected function getDBFactory() {
- return function( $index ) {
- return DatabaseBase::factory( $this->dbType,
- array(
- 'host' => $this->dbServer,
- 'user' => $this->dbUser,
- 'password' => $this->dbPassword,
- 'dbname' => $this->dbName,
- 'flags' => $this->dbFlags,
- 'tablePrefix' => $this->tablePrefix,
- 'foreign' => true,
- )
- );
+ $type = $this->dbType;
+ $params = array(
+ 'host' => $this->dbServer,
+ 'user' => $this->dbUser,
+ 'password' => $this->dbPassword,
+ 'dbname' => $this->dbName,
+ 'flags' => $this->dbFlags,
+ 'tablePrefix' => $this->tablePrefix,
+ 'foreign' => true,
+ );
+
+ return function ( $index ) use ( $type, $params ) {
+ return DatabaseBase::factory( $type, $params );
};
}
$that = $this;
$redirDbKey = ObjectCache::getMainWANInstance()->getWithSetCallback(
$memcKey,
+ $expiry,
function ( $oldValue, &$ttl, array &$setOpts ) use ( $that, $title ) {
$dbr = $that->getSlaveDB(); // possibly remote DB
return ( $row && $row->rd_namespace == NS_FILE )
? Title::makeTitle( $row->rd_namespace, $row->rd_title )->getDBkey()
: ''; // negative cache
- },
- $expiry
+ }
);
// @note: also checks " " for b/c
}
public function showDoneMessage() {
- global $wgScriptExtension;
-
$this->startForm();
$regenerate = !$this->getVar( '_ExistingDBSettings' );
if ( $regenerate ) {
$this->parent->getInfoBox(
wfMessage( $msg,
$this->getVar( 'wgServer' ) .
- $this->getVar( 'wgScriptPath' ) . '/index' .
- $wgScriptExtension
+ $this->getVar( 'wgScriptPath' ) . '/index.php'
)->plain(), 'tick-32.png'
)
);
$iwData = ObjectCache::getMainWANInstance()->getWithSetCallback(
wfMemcKey( 'interwiki', $prefix ),
+ $wgInterwikiExpiry,
function ( $oldValue, &$ttl, array &$setOpts ) use ( $prefix ) {
$dbr = wfGetDB( DB_SLAVE );
);
return $row ? (array)$row : '!NONEXISTENT';
- },
- $wgInterwikiExpiry
+ }
);
if ( is_array( $iwData ) ) {
* @param string $key
* @param int $ttl Time-to-live (seconds)
* @param callable $callback Callback that derives the new value
+ * @param integer $flags Bitfield of BagOStuff::READ_* constants [optional]
* @return mixed The cached value if found or the result of $callback otherwise
* @since 1.27
*/
- final public function getWithSetCallback( $key, $ttl, $callback ) {
- $value = $this->get( $key );
+ final public function getWithSetCallback( $key, $ttl, $callback, $flags = 0 ) {
+ $value = $this->get( $key, $flags );
if ( $value === false ) {
if ( !is_callable( $callback ) ) {
* $catInfo = $cache->getWithSetCallback(
* // Key to store the cached value under
* wfMemcKey( 'cat-attributes', $catId ),
+ * // Time-to-live (seconds)
+ * 60,
* // Function that derives the new key value
* function ( $oldValue, &$ttl, array &$setOpts ) {
* $dbr = wfGetDB( DB_SLAVE );
* $setOpts += Database::getCacheSetOptions( $dbr );
*
* return $dbr->selectRow( ... );
- * },
- * // Time-to-live (seconds)
- * 60
+ * }
* );
* @endcode
*
* $catConfig = $cache->getWithSetCallback(
* // Key to store the cached value under
* wfMemcKey( 'site-cat-config' ),
+ * // Time-to-live (seconds)
+ * 86400,
* // Function that derives the new key value
* function ( $oldValue, &$ttl, array &$setOpts ) {
* $dbr = wfGetDB( DB_SLAVE );
* $setOpts += Database::getCacheSetOptions( $dbr );
*
* return CatConfig::newFromRow( $dbr->selectRow( ... ) );
- * },
- * // Time-to-live (seconds)
- * 86400,
- * // Calling touchCheckKey() on this key invalidates the cache
- * wfMemcKey( 'site-cat-config' ),
- * // Try to only let one datacenter thread manage cache updates at a time
- * array( 'lockTSE' => 30 )
+ * },
+ * // Calling touchCheckKey() on this key invalidates the cache
+ * array(
+ * 'checkKeys' => array( wfMemcKey( 'site-cat-config' ) ),
+ * // Try to only let one datacenter thread manage cache updates at a time
+ * 'lockTSE' => 30
+ * )
* );
* @endcode
*
* $catState = $cache->getWithSetCallback(
* // Key to store the cached value under
* wfMemcKey( 'cat-state', $cat->getId() ),
+ * // Time-to-live (seconds)
+ * 900,
* // Function that derives the new key value
* function ( $oldValue, &$ttl, array &$setOpts ) {
* // Determine new value from the DB
* $setOpts += Database::getCacheSetOptions( $dbr );
*
* return CatState::newFromResults( $dbr->select( ... ) );
- * },
- * // Time-to-live (seconds)
- * 900,
- * // The "check" keys that represent things the value depends on;
- * // Calling touchCheckKey() on any of them invalidates the cache
- * array(
- * wfMemcKey( 'sustenance-bowls', $cat->getRoomId() ),
- * wfMemcKey( 'people-present', $cat->getHouseId() ),
- * wfMemcKey( 'cat-laws', $cat->getCityId() ),
+ * },
+ * // The "check" keys that represent things the value depends on;
+ * // Calling touchCheckKey() on any of them invalidates the cache
+ * array(
+ * 'checkKeys' => array(
+ * wfMemcKey( 'sustenance-bowls', $cat->getRoomId() ),
+ * wfMemcKey( 'people-present', $cat->getHouseId() ),
+ * wfMemcKey( 'cat-laws', $cat->getCityId() ),
+ * )
* )
* );
* @endcode
* $lastCatActions = $cache->getWithSetCallback(
* // Key to store the cached value under
* wfMemcKey( 'cat-last-actions', 100 ),
+ * // Time-to-live (seconds)
+ * 10,
* // Function that derives the new key value
* function ( $oldValue, &$ttl, array &$setOpts ) {
* $dbr = wfGetDB( DB_SLAVE );
* // Merge them and get the new "last 100" rows
* return array_slice( array_merge( $new, $list ), 0, 100 );
* },
- * // Time-to-live (seconds)
- * 10,
- * // No "check" keys
- * array(),
* // Try to only let one datacenter thread manage cache updates at a time
* array( 'lockTSE' => 30 )
* );
* @see WANObjectCache::set()
*
* @param string $key Cache key
- * @param callable $callback Value generation function
* @param integer $ttl Seconds to live for key updates. Special values are:
- * - WANObjectCache::TTL_NONE : cache forever
- * - WANObjectCache::TTL_UNCACHEABLE : do not cache at all
- * @param array $checkKeys List of "check" keys
+ * - WANObjectCache::TTL_NONE : Cache forever
+ * - WANObjectCache::TTL_UNCACHEABLE: Do not cache at all
+ * @param callable $callback Value generation function
* @param array $opts Options map:
- * - lowTTL : consider pre-emptive updates when the current TTL (sec)
- * of the key is less than this. It becomes more likely
- * over time, becoming a certainty once the key is expired.
- * [Default: WANObjectCache::LOW_TTL seconds]
- * - lockTSE : if the key is tombstoned or expired (by $checkKeys) less
- * than this many seconds ago, then try to have a single
- * thread handle cache regeneration at any given time.
- * Other threads will try to use stale values if possible.
- * If, on miss, the time since expiration is low, the assumption
- * is that the key is hot and that a stampede is worth avoiding.
- * Setting this above WANObjectCache::HOLDOFF_TTL makes no difference.
- * The higher this is set, the higher the worst-case staleness can be.
- * Use WANObjectCache::TSE_NONE to disable this logic.
- * [Default: WANObjectCache::TSE_NONE]
+ * - checkKeys: List of "check" keys.
+ * - lowTTL: Consider pre-emptive updates when the current TTL (sec) of the key is less than
+ * this. It becomes more likely over time, becoming a certainty once the key is expired.
+ * Default: WANObjectCache::LOW_TTL seconds.
+ * - lockTSE: If the key is tombstoned or expired (by checkKeys) less than this many seconds
+ * ago, then try to have a single thread handle cache regeneration at any given time.
+ * Other threads will try to use stale values if possible. If, on miss, the time since
+ * expiration is low, the assumption is that the key is hot and that a stampede is worth
+ * avoiding. Setting this above WANObjectCache::HOLDOFF_TTL makes no difference. The
+ * higher this is set, the higher the worst-case staleness can be.
+ * Use WANObjectCache::TSE_NONE to disable this logic. Default: WANObjectCache::TSE_NONE.
* @return mixed Value to use for the key
*/
final public function getWithSetCallback(
- $key, $callback, $ttl, array $checkKeys = array(), array $opts = array()
+ $key, $ttl, $callback, array $opts = array(), $oldOpts = array()
) {
+ // Back-compat with 1.26: Swap $ttl and $callback
+ if ( is_int( $callback ) ) {
+ $temp = $ttl;
+ $ttl = $callback;
+ $callback = $temp;
+ }
+ // Back-compat with 1.26: $checkKeys as separate parameter
+ if ( $oldOpts || ( is_array( $opts ) && isset( $opts[0] ) ) ) {
+ $checkKeys = $opts;
+ $opts = $oldOpts;
+ } else {
+ $checkKeys = isset( $opts['checkKeys'] ) ? $opts['checkKeys'] : array();
+ }
+
$lowTTL = isset( $opts['lowTTL'] ) ? $opts['lowTTL'] : min( self::LOW_TTL, $ttl );
$lockTSE = isset( $opts['lockTSE'] ) ? $opts['lockTSE'] : self::TSE_NONE;
$from = self::convertSelectType( $from );
$db = wfGetDB( $from === self::READ_LATEST ? DB_MASTER : DB_SLAVE );
- $row = $db->selectRow( 'page', self::selectFields(), array( 'page_id' => $id ), __METHOD__ );
+ $row = $db->selectRow(
+ 'page', self::selectFields(), array( 'page_id' => $id ), __METHOD__ );
if ( !$row ) {
return null;
}
* Load the object from a database row
*
* @since 1.20
- * @param object $data Database row containing at least fields returned by selectFields()
+ * @param object|bool $data DB row containing fields returned by selectFields() or false
* @param string|int $from One of the following:
* - "fromdb" or WikiPage::READ_NORMAL if the data comes from a slave DB
* - "fromdbmaster" or WikiPage::READ_LATEST if the data comes from the master DB
// SELECT. Thus we need S1 to also gets the revision row FOR UPDATE; otherwise, it
// may not find it since a page row UPDATE and revision row INSERT by S2 may have
// happened after the first S1 SELECT.
- // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read.
+ // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read
$flags = Revision::READ_LOCKING;
} elseif ( $this->mDataLoadedFrom == self::READ_LATEST ) {
// Bug T93976: if page_latest was loaded from the master, fetch the
$conds[] = "rev_user_text != {$dbr->addQuotes( $this->getUserText() )}";
}
- $conds[] = "{$dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER )} = 0"; // username hidden?
+ // Username hidden?
+ $conds[] = "{$dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER )} = 0";
$jconds = array(
'user' => array( 'LEFT JOIN', 'rev_user = user_id' ),
public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
$useParserCache = $this->shouldCheckParserCache( $parserOptions, $oldid );
- wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
+ wfDebug( __METHOD__ .
+ ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
if ( $parserOptions->getStubThreshold() ) {
wfIncrStats( 'pcache.miss.stub' );
}
/**
* Do standard deferred updates after page view (existing or missing page)
* @param User $user The relevant user
- * @param int $oldid The revision id being viewed. If not given or 0, latest revision is assumed.
+ * @param int $oldid Revision id being viewed; if not given or 0, latest revision is assumed
*/
public function doViewUpdates( User $user, $oldid = 0 ) {
if ( wfReadOnly() ) {
return true;
}
-
/**
* Insert a new empty page record for this article.
* This *must* be followed up by creating a revision
* @return int|bool The newly created page_id key; false if the title already existed
*/
public function insertOn( $dbw ) {
- $page_id = $dbw->nextSequenceValue( 'page_page_id_seq' );
- $dbw->insert( 'page', array(
- 'page_id' => $page_id,
- 'page_namespace' => $this->mTitle->getNamespace(),
- 'page_title' => $this->mTitle->getDBkey(),
- 'page_restrictions' => '',
- 'page_is_redirect' => 0, // Will set this shortly...
- 'page_is_new' => 1,
- 'page_random' => wfRandom(),
- 'page_touched' => $dbw->timestamp(),
- 'page_latest' => 0, // Fill this in shortly...
- 'page_len' => 0, // Fill this in shortly...
- ), __METHOD__, 'IGNORE' );
-
- $affected = $dbw->affectedRows();
-
- if ( $affected ) {
+ $dbw->insert(
+ 'page',
+ array(
+ 'page_id' => $dbw->nextSequenceValue( 'page_page_id_seq' ),
+ 'page_namespace' => $this->mTitle->getNamespace(),
+ 'page_title' => $this->mTitle->getDBkey(),
+ 'page_restrictions' => '',
+ 'page_is_redirect' => 0, // Will set this shortly...
+ 'page_is_new' => 1,
+ 'page_random' => wfRandom(),
+ 'page_touched' => $dbw->timestamp(),
+ 'page_latest' => 0, // Fill this in shortly...
+ 'page_len' => 0, // Fill this in shortly...
+ ),
+ __METHOD__,
+ 'IGNORE'
+ );
+
+ if ( $dbw->affectedRows() > 0 ) {
$newid = $dbw->insertId();
$this->mId = $newid;
$this->mTitle->resetArticleID( $newid );
return $newid;
} else {
- return false;
+ return false; // nothing changed
}
}
$this->mLatest = $revision->getId();
$this->mIsRedirect = (bool)$rt;
// Update the LinkCache.
- LinkCache::singleton()->addGoodLinkObj( $this->getId(), $this->mTitle, $len, $this->mIsRedirect,
- $this->mLatest, $revision->getContentModel() );
+ LinkCache::singleton()->addGoodLinkObj(
+ $this->getId(),
+ $this->mTitle,
+ $len,
+ $this->mIsRedirect,
+ $this->mLatest,
+ $revision->getContentModel()
+ );
}
return $result;
* @since 1.21
* @deprecated since 1.24, use replaceSectionAtRev instead
*/
- public function replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle = '',
- $edittime = null ) {
+ public function replaceSectionContent(
+ $sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null
+ ) {
$baseRevId = null;
if ( $edittime && $sectionId !== 'new' ) {
* Do not log the change in recentchanges
* EDIT_FORCE_BOT
* Mark the edit a "bot" edit regardless of user rights
- * EDIT_DEFER_UPDATES
- * Defer some of the updates until the end of index.php
* EDIT_AUTOSUMMARY
* Fill in blank summaries with generated text where possible
*
* Do not log the change in recentchanges
* EDIT_FORCE_BOT
* Mark the edit a "bot" edit regardless of user rights
- * EDIT_DEFER_UPDATES
- * Defer some of the updates until the end of index.php
* EDIT_AUTOSUMMARY
* Fill in blank summaries with generated text where possible
*
return $status;
}
- Hooks::run( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
+ Hooks::run( 'NewRevisionFromEditComplete',
+ array( $this, $revision, $baseRevId, $user ) );
// Update recentchanges
if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
Hooks::run( 'PageContentInsertComplete', $hook_args );
}
- // Do updates right now unless deferral was requested
- if ( !( $flags & EDIT_DEFER_UPDATES ) ) {
- DeferredUpdates::doUpdates();
- }
-
// Return the new revision (or null) to the caller
$status->value['revision'] = $revision;
* @since 1.21
*/
public function prepareContentForEdit(
- Content $content, $revision = null, User $user = null, $serialFormat = null, $useCache = true
+ Content $content, $revision = null, User $user = null,
+ $serialFormat = null, $useCache = true
) {
global $wgContLang, $wgUser, $wgAjaxEditStash;
// itself (such as via self-transclusion). In this case, we need to make sure
// that any such self-references refer to the newly-saved revision, and not
// to the previous one, which could otherwise happen due to slave lag.
- $oldCallback = $edit->popts->setCurrentRevisionCallback(
- function ( $title, $parser = false ) use ( $revision, &$oldCallback ) {
+ $oldCallback = $edit->popts->getCurrentRevisionCallback();
+ $edit->popts->setCurrentRevisionCallback(
+ function ( Title $title, $parser = false ) use ( $revision, &$oldCallback ) {
if ( $title->equals( $revision->getTitle() ) ) {
return $revision;
} else {
- return call_user_func(
- $oldCallback,
- $title,
- $parser
- );
+ return call_user_func( $oldCallback, $title, $parser );
}
}
);
$edit->oldContent = $this->getContent( Revision::RAW );
// NOTE: B/C for hooks! don't use these fields!
- $edit->newText = $edit->newContent ? ContentHandler::getContentText( $edit->newContent ) : '';
- $edit->oldText = $edit->oldContent ? ContentHandler::getContentText( $edit->oldContent ) : '';
+ $edit->newText = $edit->newContent
+ ? ContentHandler::getContentText( $edit->newContent )
+ : '';
+ $edit->oldText = $edit->oldContent
+ ? ContentHandler::getContentText( $edit->oldContent )
+ : '';
$edit->pst = $edit->pstContent ? $edit->pstContent->serialize( $serialFormat ) : '';
$this->mPreparedEdit = $edit;
if ( !$recipient ) {
wfDebug( __METHOD__ . ": invalid username\n" );
} else {
- // Allow extensions to prevent user notification when a new message is added to their talk page
+ // Allow extensions to prevent user notification
+ // when a new message is added to their talk page
if ( Hooks::run( 'ArticleEditUpdateNewTalk', array( &$this, $recipient ) ) ) {
if ( User::isIP( $shortTitle ) ) {
// An anonymous user
* @param bool $minor Whereas it's a minor modification
* @param string $serialFormat Format for storing the content in the database
*/
- public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = false,
- $serialFormat = null
+ public function doQuickEditContent(
+ Content $content, User $user, $comment = '', $minor = false, $serialFormat = null
) {
$serialized = $content->serialize( $serialFormat );
__METHOD__
);
- Hooks::run( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $user ) );
+ Hooks::run( 'NewRevisionFromEditComplete',
+ array( $this, $nullRevision, $latest, $user ) );
Hooks::run( 'ArticleProtectComplete', array( &$this, &$user, $limit, $reason ) );
} else { // Protection of non-existing page (also known as "title protection")
// Cascade protection is meaningless in this case
# with '' filtered out. All possible message keys are listed below:
# * protect-level-autoconfirmed
# * protect-level-sysop
- $restrictionsText = wfMessage( 'protect-level-' . $restrictions )->inContentLanguage()->text();
+ $restrictionsText = wfMessage( 'protect-level-' . $restrictions )
+ ->inContentLanguage()->text();
$expiryText = $this->formatExpiry( $expiry[$action] );
foreach ( array_filter( $limit ) as $action => $restrictions ) {
$expiryText = $this->formatExpiry( $expiry[$action] );
- $protectDescriptionLog .= $wgContLang->getDirMark() . "[$action=$restrictions] ($expiryText)";
+ $protectDescriptionLog .= $wgContLang->getDirMark() .
+ "[$action=$restrictions] ($expiryText)";
}
return trim( $protectDescriptionLog );
*/
protected static function flattenRestrictions( $limit ) {
if ( !is_array( $limit ) ) {
- throw new MWException( 'WikiPage::flattenRestrictions given non-array restriction set' );
+ throw new MWException( __METHOD__ . ' given non-array restriction set' );
}
$bits = array();
$status = Status::newGood();
if ( $this->mTitle->getDBkey() === '' ) {
- $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
+ $status->error( 'cannotdelete',
+ wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
return $status;
}
$user = is_null( $user ) ? $wgUser : $user;
- if ( !Hooks::run( 'ArticleDelete', array( &$this, &$user, &$reason, &$error, &$status ) ) ) {
+ if ( !Hooks::run( 'ArticleDelete',
+ array( &$this, &$user, &$reason, &$error, &$status )
+ ) ) {
if ( $status->isOK() ) {
// Hook aborted but didn't set a fatal status
$status->fatal( 'delete-hook-aborted' );
$dbw->begin( __METHOD__ );
if ( $id == 0 ) {
+ $this->loadPageData( self::READ_LATEST );
+ $id = $this->getID();
// T98706: lock the page from various other updates but avoid using
// WikiPage::READ_LOCKING as that will carry over the FOR UPDATE to
// the revisions queries (which also JOIN on user). Only lock the page
// row and CAS check on page_latest to see if the trx snapshot matches.
- $latest = $this->lock();
-
- $this->loadPageData( WikiPage::READ_LATEST );
- $id = $this->getID();
- if ( $id == 0 || $this->getLatest() != $latest ) {
+ $lockedLatest = $this->lock();
+ if ( $id == 0 || $this->getLatest() != $lockedLatest ) {
// Page not there or trx snapshot is stale
$dbw->rollback( __METHOD__ );
- $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
+ $status->error( 'cannotdelete',
+ wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
return $status;
}
}
if ( !$ok ) {
$dbw->rollback( __METHOD__ );
- $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
+ $status->error( 'cannotdelete',
+ wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
return $status;
}
$this->doDeleteUpdates( $id, $content );
- Hooks::run( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id, $content, $logEntry ) );
+ Hooks::run( 'ArticleDeleteComplete',
+ array( &$this, &$user, $reason, $id, $content, $logEntry ) );
$status->value = $logid;
return $status;
}
/**
- * Lock the page row for this title and return page_latest (or 0)
+ * Lock the page row for this title+id and return page_latest (or 0)
*
- * @return integer
+ * @return integer Returns 0 if no row was found with this title+id
*/
protected function lock() {
return (int)wfGetDB( DB_MASTER )->selectField(
'page',
'page_latest',
array(
+ 'page_id' => $this->getId(),
+ // Typically page_id is enough, but some code might try to do
+ // updates assuming the title is the same, so verify that
'page_namespace' => $this->getTitle()->getNamespace(),
'page_title' => $this->getTitle()->getDBkey()
),
// Delete pagelinks, update secondary indexes, etc
$updates = $this->getDeletionUpdates( $content );
- // Make sure an enqueued jobs run after commit so they see the deletion
- wfGetDB( DB_MASTER )->onTransactionIdle( function() use ( $updates ) {
- DataUpdate::runUpdates( $updates, 'enqueue' );
- } );
+ foreach ( $updates as $update ) {
+ DeferredUpdates::addUpdate( $update );
+ }
// Reparse any pages transcluding this page
LinksUpdate::queueRecursiveJobsForTable( $this->mTitle, 'templatelinks' );
}
// raise error, when the edit is an edit without a new version
- if ( empty( $status->value['revision'] ) ) {
+ $statusRev = isset( $status->value['revision'] )
+ ? $status->value['revision']
+ : null;
+ if ( !( $statusRev instanceof Revision ) ) {
$resultDetails = array( 'current' => $current );
return array( array( 'alreadyrolled',
htmlspecialchars( $this->mTitle->getPrefixedText() ),
) );
}
- $revId = $status->value['revision']->getId();
+ $revId = $statusRev->getId();
Hooks::run( 'ArticleRollbackComplete', array( $this, $guser, $target, $current ) );
return;
}
- if ( !Hooks::run( 'OpportunisticLinksUpdate', array( $this, $this->mTitle, $parserOutput ) ) ) {
+ if ( !Hooks::run( 'OpportunisticLinksUpdate',
+ array( $this, $this->mTitle, $parserOutput )
+ ) ) {
return;
}
*/
public function getDeletionUpdates( Content $content = null ) {
if ( !$content ) {
- // load content object, which may be used to determine the necessary updates
- // XXX: the content may not be needed to determine the updates, then this would be overhead.
+ // load content object, which may be used to determine the necessary updates.
+ // XXX: the content may not be needed to determine the updates.
$content = $this->getContent( Revision::RAW );
}
'wgUrlProtocols' => wfUrlProtocols(),
'wgArticlePath' => $conf->get( 'ArticlePath' ),
'wgScriptPath' => $conf->get( 'ScriptPath' ),
- 'wgScriptExtension' => $conf->get( 'ScriptExtension' ),
+ 'wgScriptExtension' => '.php',
'wgScript' => wfScript(),
'wgSearchType' => $conf->get( 'SearchType' ),
'wgVariantArticlePath' => $conf->get( 'VariantArticlePath' ),
}
/**
- * Gets the type of the site (ie wikipedia).
+ * Gets the group of the site (ie wikipedia).
*
* @since 1.21
*
}
/**
- * Sets the type of the site (ie wikipedia).
+ * Sets the group of the site (ie wikipedia).
*
* @since 1.21
*
$form->setTableId( 'mw-changeemail-table' );
$form->setSubmitTextMsg( 'changeemail-submit' );
$form->addHiddenFields( $this->getRequest()->getValues( 'returnto', 'returntoquery' ) );
+
+ $form->addHeaderText( $this->msg( 'changeemail-header' )->parseAsBlock() );
+ if ( $this->getConfig()->get( 'RequirePasswordforEmailChange' ) ) {
+ $form->addHeaderText( $this->msg( 'changeemail-passwordrequired' )->parseAsBlock() );
+ }
}
public function onSubmit( array $data ) {
}
if ( $title instanceof Title ) {
- $output .= "<li>"
- . Linker::link( $title )
- . ' (' . Linker::link( $title->getTalkPage(), $talk )
- . ")</li>\n";
+ $output .= '<li>' .
+ Linker::link( $title ) . ' ' .
+ $this->msg( 'parentheses' )->rawParams(
+ Linker::link( $title->getTalkPage(), $talk )
+ )->escaped() .
+ "</li>\n";
}
}
$link = '<span class="watchlistredir">' . $link . '</span>';
}
- return $link . " (" . $this->getLanguage()->pipeList( $tools ) . ")";
+ return $link . ' ' .
+ $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList( $tools ) )->escaped();
}
/**
$dbw = $this->repo->getMasterDb();
// Use a quick transaction since we will upload the full temp file into shared
// storage, which takes time for large files. We don't want to hold locks then.
- $dbw->begin( __METHOD__ );
$dbw->update(
'uploadstash',
array(
array( 'us_key' => $this->mFileKey ),
__METHOD__
);
- $dbw->commit( __METHOD__ );
+ $dbw->commit( __METHOD__, 'flush' );
}
/**
+++ /dev/null
-<?php
-/**
- * Version of index.php to be used in web servers that require the .php5
- * extension to execute scripts with the PHP5 engine.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-define( 'MW_ENTRY_PHP5', true );
-
-require './index.php';
"passwordreset-emailerror-capture": "A password reset email was generated, which is shown below, but sending it to the {{GENDER:$2|user}} failed: $1",
"changeemail": "Change or remove email address",
"changeemail-summary": "",
- "changeemail-text": "Complete this form to change your email address. You will need to enter your password to confirm this change. If you would like to remove the association of any email address from your account, leave the new email address blank when submitting the form.",
+ "changeemail-header": "Complete this form to change your email address. If you would like to remove the association of any email address from your account, leave the new email address blank when submitting the form.",
+ "changeemail-passwordrequired": "You will need to enter your password to confirm this change.",
"changeemail-no-info": "You must be logged in to access this page directly.",
"changeemail-oldemail": "Current email address:",
"changeemail-newemail": "New email address:",
"passwordreset-emailerror-capture": "Error message displayed in [[Special:PasswordReset]] when sending an email fails. Parameters:\n* $1 - error message\n* $2 - username, used for GENDER\nSee also:\n* {{msg-mw|Passwordreset-emailsent}}\n* {{msg-mw|Passwordreset-emailsent-capture}}",
"changeemail": "Title of [[Special:ChangeEmail|special page]]. This page also allows removing the user's email address.",
"changeemail-summary": "{{ignored}}",
- "changeemail-text": "Text of [[Special:ChangeEmail]].",
+ "changeemail-header": "Text of [[Special:ChangeEmail]].",
+ "changeemail-passwordrequired": "Shown on [[Special:ChangeEmail]] if users are required to enter their password to change their email address..",
"changeemail-no-info": "Error message for [[Special:ChangeEmail]].\n\nParameters:\n* $1 (unused) - a link to [[Special:UserLogin]] with {{msg-mw|loginreqlink}} as link description",
"changeemail-oldemail": "Label for e-mail address field in [[Special:ChangeEmail]].",
"changeemail-newemail": "Label for e-mail address field in [[Special:ChangeEmail]]. See also {{msg-mw|changeemail-newemail-help}}",
'Allmessages' => array( 'Messages_système', 'Messages_systeme', 'Messagessystème', 'Messagessysteme' ),
'Allpages' => array( 'Toutes_les_pages', 'ToutesLesPages' ),
'Ancientpages' => array( 'Pages_anciennes', 'PagesAnciennes', 'Anciennes_pages', 'AnciennesPages' ),
- 'Badtitle' => array( 'MauvaisTitre', 'Mauvais_titre' ),
+ 'Badtitle' => array( 'Mauvais_titre', 'MauvaisTitre' ),
'Blankpage' => array( 'Page_blanche', 'PageBlanche' ),
'Block' => array( 'Bloquer', 'Blocage' ),
'Booksources' => array( 'Ouvrages_de_référence', 'Ouvrages_de_reference', 'Ouvragesderéférence', 'Ouvragesdereference', 'Recherche_ISBN', 'Recherche_isbn', 'RechercheISBN', 'Rechercheisbn' ),
'BrokenRedirects' => array( 'Redirections_cassées', 'RedirectionCassées', 'Redirections_cassees', 'RedirectionsCassees' ),
'Categories' => array( 'Catégories' ),
- 'ChangeEmail' => array( 'ChangerCouriel', 'Changer_courrielw' ),
+ 'ChangeEmail' => array( 'Changer_courriel', 'ChangerCourriel', 'ChangerCouriel' ),
'ChangePassword' => array( 'Changement_du_mot_de_passe', 'ChangementDuMotDePasse' ),
'ComparePages' => array( 'Comparer_des_pages' ),
'Confirmemail' => array( 'Confirmer_l\'adresse_de_contact', 'Confirmer_le_courriel', 'ConfirmerLeCourriel' ),
'Mostrevisions' => array( 'Pages_les_plus_modifiées', 'PagesLesPlusModifiées', 'Pages_les_plus_modifiees', 'PagesLesPlusModifiees', 'Les_plus_modifiés', 'LesPlusModifiés', 'Les_plus_modifies', 'LesPlusModifies' ),
'Movepage' => array( 'Renommer_une_page', 'Renommer', 'Renommage' ),
'Mycontributions' => array( 'Mes_contributions', 'Mescontributions' ),
- 'MyLanguage' => array( 'MaLangue', 'Ma_langue' ),
+ 'MyLanguage' => array( 'Ma_langue', 'MaLangue' ),
'Mypage' => array( 'Ma_page', 'Mapage' ),
'Mytalk' => array( 'Mes_discussions', 'Mesdiscussions' ),
'Newimages' => array( 'Nouveaux_fichiers', 'NouveauxFichiers', 'Nouvelles_images', 'NouvellesImages' ),
'Newpages' => array( 'Nouvelles_pages', 'NouvellesPages', 'Pages_récentes', 'PagesRécentes', 'Pages_recentes', 'PagesRecentes' ),
'PagesWithProp' => array( 'Pages_avec_la_propriété' ),
'PasswordReset' => array( 'Réinitialisation_du_mot_de_passe', 'RéinitialisationDuMotDePasse' ),
- 'PermanentLink' => array( 'LienPermanent', 'Lien_permanent' ),
+ 'PermanentLink' => array( 'Lien_permanent', 'LienPermanent' ),
'Preferences' => array( 'Préférences' ),
'Prefixindex' => array( 'Index', 'Préfixes', 'Prefixes' ),
'Protectedpages' => array( 'Pages_protégées', 'PagesProtégées', 'Pages_protegees', 'PagesProtegees' ),
+++ /dev/null
-<?php
-/**
- * Version of load.php to be used in web servers that require the .php5
- * extension to execute scripts with the PHP5 engine.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-define( 'MW_ENTRY_PHP5', true );
-
-require './load.php';
+++ /dev/null
-<?php
-/**
- * Version of opensearch_desc.php to be used in web servers that require the .php5
- * extension to execute scripts with the PHP5 engine.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-define( 'MW_ENTRY_PHP5', true );
-
-require './opensearch_desc.php';
+++ /dev/null
-<?php
-/**
- * Version of profileinfo.php to be used in web servers that require the .php5
- * extension to execute scripts with the PHP5 engine.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-define( 'MW_ENTRY_PHP5', true );
-
-require './profileinfo.php';
"@metadata": {
"authors": [
"Gitartha.bordoloi",
- "Dibya Dutta"
+ "Dibya Dutta",
+ "IKHazarika"
]
},
"ooui-outline-control-move-down": "সমল তললৈ স্থানান্তৰ কৰক",
"ooui-dialog-process-dismiss": "বাতিল",
"ooui-dialog-process-retry": "পুনৰ চেষ্টা কৰক",
"ooui-dialog-process-continue": "অব্যাহত ৰাখক",
+ "ooui-selectfile-button-select": "ফাইল নিৰ্বাচন কৰক",
"ooui-selectfile-not-supported": "নথি নিৰ্বাচন সমৰ্থন কৰা নাই",
- "ooui-selectfile-placeholder": "কোনো নথি নিৰ্বাচিত কৰা হোৱা নাই"
+ "ooui-selectfile-placeholder": "কোনো নথি নিৰ্বাচিত কৰা হোৱা নাই",
+ "ooui-selectfile-dragdrop-placeholder": "ইয়াত ফাইল এৰক"
}
"ooui-dialog-process-error": "Eitthvað mistókst",
"ooui-dialog-process-dismiss": "Loka",
"ooui-dialog-process-retry": "Reyna aftur",
- "ooui-dialog-process-continue": "Halda áfram"
+ "ooui-dialog-process-continue": "Halda áfram",
+ "ooui-selectfile-button-select": "Velja skrá",
+ "ooui-selectfile-not-supported": "Skráar val er ekki stutt.",
+ "ooui-selectfile-placeholder": "Engin skrá er valin",
+ "ooui-selectfile-dragdrop-placeholder": "Slepptu skránni hérna"
}
"ooui-dialog-message-accept": "OK",
"ooui-dialog-message-reject": "Anuluj",
"ooui-dialog-process-error": "Coś poszło nie tak",
- "ooui-dialog-process-dismiss": "Ukryj",
+ "ooui-dialog-process-dismiss": "Powrót",
"ooui-dialog-process-retry": "Spróbuj ponownie",
"ooui-dialog-process-continue": "Kontynuuj",
"ooui-selectfile-button-select": "Wybierz plik",
"ooui-dialog-process-error": "Нешто је пошло наопако",
"ooui-dialog-process-dismiss": "Одбаци",
"ooui-dialog-process-retry": "Покушај поново",
- "ooui-dialog-process-continue": "Настави"
+ "ooui-dialog-process-continue": "Настави",
+ "ooui-selectfile-button-select": "Изабери датотеку",
+ "ooui-selectfile-placeholder": "Није изабрана ниједна датотека"
}
"ooui-dialog-process-dismiss": "Bỏ qua",
"ooui-dialog-process-retry": "Thử lại",
"ooui-dialog-process-continue": "Tiếp tục",
+ "ooui-selectfile-button-select": "Chọn tập tin",
"ooui-selectfile-not-supported": "Không hỗ trợ việc chọn tập tin",
"ooui-selectfile-placeholder": "Không có tập tin nào được chọn",
"ooui-selectfile-dragdrop-placeholder": "Thả tập tin vào đây"
/*!
- * OOjs UI v0.12.10
+ * OOjs UI v0.12.11
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2015 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2015-09-29T21:20:47Z
+ * Date: 2015-10-07T20:48:23Z
*/
@-webkit-keyframes oo-ui-progressBarWidget-slide {
from {
/*!
- * OOjs UI v0.12.10
+ * OOjs UI v0.12.11
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2015 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2015-09-29T21:20:38Z
+ * Date: 2015-10-07T20:48:15Z
*/
/**
* @class
/*!
- * OOjs UI v0.12.10
+ * OOjs UI v0.12.11
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2015 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2015-09-29T21:20:47Z
+ * Date: 2015-10-07T20:48:23Z
*/
@-webkit-keyframes oo-ui-progressBarWidget-slide {
from {
border: 1px solid #aaaaaa;
border-radius: 0.2em;
background-color: #ffffff;
- box-shadow: 0 0.15em 0 0 rgba(204, 204, 204, 0.5);
+ box-shadow: 0 0.15em 0 0 rgba(0, 0, 0, 0.15);
}
.oo-ui-popupWidget-anchored .oo-ui-popupWidget-popup {
margin-top: 9px;
-moz-box-sizing: border-box;
box-sizing: border-box;
border: 1px solid #cccccc;
+ border-radius: 0.1em;
+ padding-left: 1em;
+ vertical-align: middle;
}
.oo-ui-dropdownInputWidget.oo-ui-widget-enabled select:hover,
.oo-ui-dropdownInputWidget.oo-ui-widget-enabled select:focus {
margin-top: -1px;
border: 1px solid #aaaaaa;
border-radius: 0 0 0.2em 0.2em;
- box-shadow: 0 0.15em 0 0 rgba(204, 204, 204, 0.5);
+ box-shadow: 0 0.15em 0 0 rgba(0, 0, 0, 0.15);
}
.oo-ui-menuSelectWidget input {
position: absolute;
margin-right: 0;
}
.oo-ui-dropdownWidget-handle {
- padding: 0.5em 0;
+ padding: 0.3em 0;
+ height: 2.275em;
border: 1px solid #cccccc;
border-radius: 0.1em;
}
/*!
- * OOjs UI v0.12.10
+ * OOjs UI v0.12.11
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2015 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2015-09-29T21:20:38Z
+ * Date: 2015-10-07T20:48:15Z
*/
/**
* @class
/*!
- * OOjs UI v0.12.10
+ * OOjs UI v0.12.11
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2015 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2015-09-29T21:20:38Z
+ * Date: 2015-10-07T20:48:15Z
*/
( function ( OO ) {
return;
}
// Only change the focus if is not already in the current page
- if ( !page.$element.find( ':focus' ).length ) {
- OO.ui.findFocusable( page.$element ).focus();
+ if ( !OO.ui.contains( page.$element[ 0 ], this.getElementDocument().activeElement, true ) ) {
+ page.focus();
}
};
if ( !card ) {
return;
}
- // Only change the focus if is not already in the current card
- if ( !card.$element.find( ':focus' ).length ) {
- OO.ui.findFocusable( card.$element ).focus();
+ // Only change the focus if is not already in the current page
+ if ( !OO.ui.contains( card.$element[ 0 ], this.getElementDocument().activeElement, true ) ) {
+ card.focus();
}
};
OO.inheritClass( OO.ui.PanelLayout, OO.ui.Layout );
+/* Methods */
+
+/**
+ * Focus the panel layout
+ *
+ * The default implementation just focuses the first focusable element in the panel
+ */
+OO.ui.PanelLayout.prototype.focus = function () {
+ OO.ui.findFocusable( this.$element ).focus();
+};
+
/**
* CardLayouts are used within {@link OO.ui.IndexLayout index layouts} to create cards that users can select and display
* from the index's optional {@link OO.ui.TabSelectWidget tab} navigation. Cards are usually not instantiated directly,
}
if ( config.autocomplete === false ) {
this.$input.attr( 'autocomplete', 'off' );
+ // Turning off autocompletion also disables "form caching" when the user navigates to a
+ // different page and then clicks "Back". Re-enable it when leaving. Borrowed from jQuery UI.
+ $( window ).on( {
+ beforeunload: function () {
+ this.$input.removeAttr( 'autocomplete' );
+ }.bind( this ),
+ pageshow: function () {
+ // Browsers don't seem to actually fire this event on "Back", they instead just reload the
+ // whole page... it shouldn't hurt, though.
+ this.$input.attr( 'autocomplete', 'off' );
+ }.bind( this )
+ } );
}
if ( this.multiline && config.rows ) {
this.$input.attr( 'rows', config.rows );
focusout: this.onBlur.bind( this )
} );
this.calendar.$element.on( {
+ click: this.onCalendarClick.bind( this ),
keypress: this.onCalendarKeyPress.bind( this )
} );
this.$handle.on( {
}
};
+ /**
+ * Handle calendar click events.
+ *
+ * @private
+ * @param {jQuery.Event} e Mouse click event
+ */
+ mw.widgets.DateInputWidget.prototype.onCalendarClick = function ( e ) {
+ if (
+ !this.isDisabled() &&
+ e.which === 1 &&
+ $( e.target ).hasClass( 'mw-widget-calendarWidget-day' )
+ ) {
+ this.deactivate();
+ this.$handle.focus();
+ return false;
+ }
+ };
+
/**
* Handle text input enter events.
*
this.upload.addCategories( this.categoriesWidget.getItemsData() );
return this.upload.getText();
};
+
+ /* Setters */
+
+ /**
+ * @inheritdoc
+ */
+ mw.ForeignStructuredUpload.BookletLayout.prototype.clear = function () {
+ mw.ForeignStructuredUpload.BookletLayout.parent.prototype.clear.call( this );
+
+ this.ownWorkCheckbox.setSelected( false );
+ this.categoriesWidget.setItemsFromData( [] );
+ this.dateWidget.setValue( '' ).setValidityFlag( true );
+ };
+
}( jQuery, mediaWiki ) );
} else if ( str === 'load' ) {
return mw.config.get( 'wgLoadScript' );
} else {
- return mw.config.get( 'wgScriptPath' ) + '/' + str +
- mw.config.get( 'wgScriptExtension' );
+ return mw.config.get( 'wgScriptPath' ) + '/' + str + '.php';
}
},
* @dataProvider provideVaryHeaders
* @covers OutputPage::addVaryHeader
* @covers OutputPage::getVaryHeader
- * @covers OutputPage::getXVO
+ * @covers OutputPage::getKeyHeader
*/
- public function testVaryHeaders( $calls, $vary, $xvo ) {
+ public function testVaryHeaders( $calls, $vary, $key ) {
// get rid of default Vary fields
$outputPage = $this->getMockBuilder( 'OutputPage' )
->setConstructorArgs( array( new RequestContext() ) )
call_user_func_array( array( $outputPage, 'addVaryHeader' ), $call );
}
$this->assertEquals( $vary, $outputPage->getVaryHeader(), 'Vary:' );
- $this->assertEquals( $xvo, $outputPage->getXVO(), 'X-Vary-Options:' );
+ $this->assertEquals( $key, $outputPage->getKeyHeader(), 'Key:' );
}
public function provideVaryHeaders() {
- // note: getXVO() automatically adds Vary: Cookie
+ // note: getKeyHeader() automatically adds Vary: Cookie
return array(
array( // single header
array(
array( 'Cookie' ),
),
'Vary: Cookie',
- 'X-Vary-Options: Cookie',
+ 'Key: Cookie',
),
array( // non-unique headers
array(
array( 'Cookie' ),
),
'Vary: Cookie, Accept-Language',
- 'X-Vary-Options: Cookie,Accept-Language',
+ 'Key: Cookie,Accept-Language',
),
array( // two headers with single options
array(
- array( 'Cookie', array( 'string-contains=phpsessid' ) ),
- array( 'Accept-Language', array( 'string-contains=en' ) ),
+ array( 'Cookie', array( 'param=phpsessid' ) ),
+ array( 'Accept-Language', array( 'substr=en' ) ),
),
'Vary: Cookie, Accept-Language',
- 'X-Vary-Options: Cookie;string-contains=phpsessid,Accept-Language;string-contains=en',
+ 'Key: Cookie;param=phpsessid,Accept-Language;substr=en',
),
array( // one header with multiple options
array(
- array( 'Cookie', array( 'string-contains=phpsessid', 'string-contains=userId' ) ),
+ array( 'Cookie', array( 'param=phpsessid', 'param=userId' ) ),
),
'Vary: Cookie',
- 'X-Vary-Options: Cookie;string-contains=phpsessid;string-contains=userId',
+ 'Key: Cookie;param=phpsessid;param=userId',
),
array( // Duplicate option
array(
- array( 'Cookie', array( 'string-contains=phpsessid' ) ),
- array( 'Cookie', array( 'string-contains=phpsessid' ) ),
- array( 'Accept-Language', array( 'string-contains=en', 'string-contains=en' ) ),
+ array( 'Cookie', array( 'param=phpsessid' ) ),
+ array( 'Cookie', array( 'param=phpsessid' ) ),
+ array( 'Accept-Language', array( 'substr=en', 'substr=en' ) ),
),
'Vary: Cookie, Accept-Language',
- 'X-Vary-Options: Cookie;string-contains=phpsessid,Accept-Language;string-contains=en',
+ 'Key: Cookie;param=phpsessid,Accept-Language;substr=en',
),
array( // Same header, different options
array(
- array( 'Cookie', array( 'string-contains=phpsessid' ) ),
- array( 'Cookie', array( 'string-contains=userId' ) ),
+ array( 'Cookie', array( 'param=phpsessid' ) ),
+ array( 'Cookie', array( 'param=userId' ) ),
),
'Vary: Cookie',
- 'X-Vary-Options: Cookie;string-contains=phpsessid;string-contains=userId',
+ 'Key: Cookie;param=phpsessid;param=userId',
),
);
}
+
+ /**
+ * @covers OutputPage::haveCacheVaryCookies
+ */
+ function testHaveCacheVaryCookies() {
+ $request = new FauxRequest();
+ $context = new RequestContext();
+ $context->setRequest( $request );
+ $outputPage = new OutputPage( $context );
+
+ // No cookies are set.
+ $this->assertFalse( $outputPage->haveCacheVaryCookies() );
+
+ // 'Token' is present but empty, so it shouldn't count.
+ $request->setCookie( 'Token', '' );
+ $this->assertFalse( $outputPage->haveCacheVaryCookies() );
+
+ // 'Token' present and nonempty.
+ $request->setCookie( 'Token', '123' );
+ $this->assertTrue( $outputPage->haveCacheVaryCookies() );
+ }
}
/**
$this->cache = ObjectCache::getWANInstance( $name );
} else {
$this->cache = new WANObjectCache( array(
- 'cache' => new HashBagOStuff(),
- 'pool' => 'testcache-hash',
+ 'cache' => new HashBagOStuff(),
+ 'pool' => 'testcache-hash',
'relayer' => new EventRelayerNull( array() )
) );
}
+
+ $wanCache = TestingAccessWrapper::newFromObject( $this->cache );
+ $this->internalCache = $wanCache->cache;
}
/**
$this->assertGreaterThanOrEqual( 19, $curTTL, 'Current TTL between 19-20 (overriden)' );
$wasSet = 0;
- $v = $cache->getWithSetCallback( $key, $func, 30, array(), array( 'lockTSE' => 5 ) );
+ $v = $cache->getWithSetCallback( $key, $func, 30, array(), array(
+ 'lowTTL' => 0,
+ 'lockTSE' => 5,
+ ) );
$this->assertEquals( $v, $value );
$this->assertEquals( 0, $wasSet, "Value not regenerated" );
$this->assertLessThanOrEqual( 0, $curTTL, "Value has current TTL < 0 due to check keys" );
}
+ /**
+ * @covers WANObjectCache::getWithSetCallback()
+ */
+ public function testLockTSE() {
+ $cache = $this->cache;
+ $key = wfRandomString();
+ $value = wfRandomString();
+
+ $calls = 0;
+ $func = function() use ( &$calls, $value ) {
+ ++$calls;
+ return $value;
+ };
+
+ $cache->delete( $key );
+ $ret = $cache->getWithSetCallback( $key, 30, $func, array(), array( 'lockTSE' => 5 ) );
+ $this->assertEquals( $value, $ret );
+ $this->assertEquals( 1, $calls, 'Value was populated' );
+
+ // Acquire a lock to verify that getWithSetCallback uses lockTSE properly
+ $this->internalCache->lock( $key, 0 );
+ $ret = $cache->getWithSetCallback( $key, 30, $func, array(), array( 'lockTSE' => 5 ) );
+ $this->assertEquals( $value, $ret );
+ $this->assertEquals( 1, $calls, 'Callback was not used' );
+ }
+
/**
* @covers WANObjectCache::getMulti()
*/
$key = wfRandomString();
$priorTime = microtime( true );
- usleep( 1 );
+ usleep( 100 );
$t0 = $this->cache->getCheckKeyTime( $key );
$this->assertGreaterThanOrEqual( $priorTime, $t0, 'Check key auto-created' );
$priorTime = microtime( true );
- usleep( 1 );
+ usleep( 100 );
$this->cache->touchCheckKey( $key );
$t1 = $this->cache->getCheckKeyTime( $key );
$this->assertGreaterThanOrEqual( $priorTime, $t1, 'Check key created' );
$t2 = $this->cache->getCheckKeyTime( $key );
$this->assertEquals( $t1, $t2, 'Check key time did not change' );
- usleep( 1 );
+ usleep( 100 );
$this->cache->touchCheckKey( $key );
$t3 = $this->cache->getCheckKeyTime( $key );
$this->assertGreaterThan( $t2, $t3, 'Check key time increased' );
$t4 = $this->cache->getCheckKeyTime( $key );
$this->assertEquals( $t3, $t4, 'Check key time did not change' );
- usleep( 1 );
+ usleep( 100 );
$this->cache->resetCheckKey( $key );
$t5 = $this->cache->getCheckKeyTime( $key );
$this->assertGreaterThan( $t4, $t5, 'Check key time increased' );
mw.config.set( {
wgScript: '/w/i.php', // customized wgScript for bug 39103
wgLoadScript: '/w/l.php', // customized wgLoadScript for bug 39103
- wgScriptPath: '/w',
- wgScriptExtension: '.php'
+ wgScriptPath: '/w'
} );
assert.equal( mw.util.wikiScript(), mw.config.get( 'wgScript' ),
+++ /dev/null
-<?php
-/**
- * Version of thumb.php to be used in web servers that require the .php5
- * extension to execute scripts with the PHP5 engine.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-define( 'MW_ENTRY_PHP5', true );
-
-require './thumb.php';
+++ /dev/null
-<?php
-/**
- * Version of thumb_handler.php to be used in web servers that require the .php5
- * extension to execute scripts with the PHP5 engine.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Media
- */
-
-define( 'MW_ENTRY_PHP5', true );
-
-require './thumb_handler.php';