Merge "mediawiki.Upload.BookletLayout: Add setFile method"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 8 Oct 2015 17:25:32 +0000 (17:25 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 8 Oct 2015 17:25:32 +0000 (17:25 +0000)
68 files changed:
RELEASE-NOTES-1.27
api.php5 [deleted file]
autoload.php
composer.json
img_auth.php5 [deleted file]
includes/DefaultSettings.php
includes/EditPage.php
includes/GlobalFunctions.php
includes/MediaWiki.php
includes/Message.php
includes/OutputHandler.php
includes/OutputPage.php
includes/Setup.php
includes/SquidPurgeClient.php [deleted file]
includes/WebRequest.php
includes/actions/InfoAction.php
includes/api/ApiDelete.php
includes/api/ApiMain.php
includes/api/i18n/en.json
includes/api/i18n/qqq.json
includes/changetags/ChangeTags.php
includes/clientpool/SquidPurgeClient.php [new file with mode: 0644]
includes/clientpool/SquidPurgeClientPool.php [new file with mode: 0644]
includes/db/DatabaseUtility.php
includes/db/loadbalancer/LBFactory.php
includes/db/loadbalancer/LBFactoryMulti.php
includes/db/loadbalancer/LBFactorySimple.php
includes/db/loadbalancer/LoadBalancer.php
includes/deferred/DeferredUpdates.php
includes/deferred/LinksUpdate.php
includes/filerepo/FileRepo.php
includes/filerepo/ForeignDBRepo.php
includes/filerepo/LocalRepo.php
includes/installer/WebInstallerPage.php
includes/interwiki/Interwiki.php
includes/libs/objectcache/BagOStuff.php
includes/libs/objectcache/WANObjectCache.php
includes/page/WikiPage.php
includes/resourceloader/ResourceLoaderStartUpModule.php
includes/site/Site.php
includes/specials/SpecialChangeEmail.php
includes/specials/SpecialEditWatchlist.php
includes/upload/UploadFromChunks.php
index.php5 [deleted file]
languages/i18n/en.json
languages/i18n/qqq.json
languages/messages/MessagesFr.php
load.php5 [deleted file]
opensearch_desc.php5 [deleted file]
profileinfo.php5 [deleted file]
resources/lib/oojs-ui/i18n/as.json
resources/lib/oojs-ui/i18n/is.json
resources/lib/oojs-ui/i18n/pl.json
resources/lib/oojs-ui/i18n/sr-ec.json
resources/lib/oojs-ui/i18n/vi.json
resources/lib/oojs-ui/oojs-ui-apex-noimages.css
resources/lib/oojs-ui/oojs-ui-apex.js
resources/lib/oojs-ui/oojs-ui-mediawiki-noimages.css
resources/lib/oojs-ui/oojs-ui-mediawiki.js
resources/lib/oojs-ui/oojs-ui.js
resources/src/mediawiki.widgets/mw.widgets.DateInputWidget.js
resources/src/mediawiki/mediawiki.ForeignStructuredUpload.BookletLayout.js
resources/src/mediawiki/mediawiki.util.js
tests/phpunit/includes/OutputPageTest.php
tests/phpunit/includes/objectcache/WANObjectCacheTest.php
tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js
thumb.php5 [deleted file]
thumb_handler.php5 [deleted file]

index c96eed2..5fc2018 100644 (file)
@@ -17,6 +17,14 @@ production.
 * $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
diff --git a/api.php5 b/api.php5
deleted file mode 100644 (file)
index 7512cde..0000000
--- a/api.php5
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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';
index a2f432f..269b142 100644 (file)
@@ -1212,8 +1212,8 @@ $wgAutoloadLocalClasses = array(
        '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',
index 6f859bd..46ff59c 100644 (file)
@@ -21,7 +21,7 @@
                "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",
diff --git a/img_auth.php5 b/img_auth.php5
deleted file mode 100644 (file)
index 456c4f2..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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';
index 7733c93..9eff602 100644 (file)
@@ -168,7 +168,7 @@ $wgUsePathInfo = ( strpos( PHP_SAPI, 'cgi' ) === false ) &&
  * 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.
  */
@@ -197,14 +197,14 @@ $wgScriptExtension = '.php';
 /**
  * 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;
@@ -484,7 +484,7 @@ $wgImgAuthUrlPathMap = array();
  *   - 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
@@ -747,7 +747,7 @@ $wgUploadMissingFileUrl = false;
  *
  * @par Example:
  * @code
- *   $wgThumbnailScriptPath = "{$wgScriptPath}/thumb{$wgScriptExtension}";
+ *   $wgThumbnailScriptPath = "{$wgScriptPath}/thumb.php";
  * @endcode
  */
 $wgThumbnailScriptPath = false;
@@ -2544,13 +2544,15 @@ $wgUseSquid = 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
index dac482c..23a7de4 100644 (file)
@@ -1942,7 +1942,7 @@ class EditPage {
                        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 );
index 496992b..c24aaec 100644 (file)
@@ -3713,20 +3713,20 @@ function wfQueriesMustScale() {
 
 /**
  * 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";
        }
 }
 
index 418ed8b..aee6ee1 100644 (file)
@@ -690,7 +690,7 @@ class MediaWiki {
                Profiler::instance()->getTransactionProfiler()->resetExpectations();
 
                // Do any deferred jobs
-               DeferredUpdates::doUpdates( 'commit' );
+               DeferredUpdates::doUpdates( 'commit', 'enqueue' );
 
                // Make sure any lazy jobs are pushed
                JobQueueGroup::pushLazyJobs();
index 54abfd1..54efd26 100644 (file)
@@ -440,7 +440,7 @@ class Message implements MessageSpecifier, Serializable {
         *
         * @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
index c6209ee..39716ca 100644 (file)
@@ -137,9 +137,9 @@ function wfGzipHandler( $s ) {
        }
        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;
index 03ae8c9..d85c60c 100644 (file)
@@ -273,7 +273,7 @@ class OutputPage extends ContextSource {
        private $mIndexPolicy = 'index';
        private $mFollowPolicy = 'follow';
        private $mVaryHeader = array(
-               'Accept-Encoding' => array( 'list-contains=gzip' ),
+               'Accept-Encoding' => array( 'match=gzip' ),
        );
 
        /**
@@ -2002,14 +2002,9 @@ class OutputPage extends ContextSource {
         * @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;
                        }
@@ -2022,11 +2017,9 @@ class OutputPage extends ContextSource {
         * 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 ) ) {
@@ -2049,16 +2042,16 @@ class OutputPage extends ContextSource {
        }
 
        /**
-        * 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 );
 
@@ -2070,13 +2063,13 @@ class OutputPage extends ContextSource {
                        }
                        $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:
@@ -2097,14 +2090,14 @@ class OutputPage extends ContextSource {
                                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;
                                        }
                                }
                        }
@@ -2179,9 +2172,8 @@ class OutputPage extends ContextSource {
                # 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 ) {
@@ -3320,22 +3312,31 @@ class OutputPage extends ContextSource {
         * @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;
        }
 
        /**
index e79d13c..67c99c9 100644 (file)
@@ -48,10 +48,10 @@ if ( !isset( $wgVersion ) ) {
 $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 ) {
@@ -186,7 +186,7 @@ if ( !$wgLocalFileRepo ) {
                'name' => 'local',
                'directory' => $wgUploadDirectory,
                'scriptDirUrl' => $wgScriptPath,
-               'scriptExtension' => $wgScriptExtension,
+               'scriptExtension' => '.php',
                'url' => $wgUploadBaseUrl ? $wgUploadBaseUrl . $wgUploadPath : $wgUploadPath,
                'hashLevels' => $wgHashedUploadDirectory ? 2 : 0,
                'thumbScriptUrl' => $wgThumbnailScriptPath,
@@ -503,10 +503,6 @@ require_once "$IP/includes/compat/normal/UtfNormalUtil.php";
 
 $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 );
 }
diff --git a/includes/SquidPurgeClient.php b/includes/SquidPurgeClient.php
deleted file mode 100644 (file)
index ca8f11a..0000000
+++ /dev/null
@@ -1,484 +0,0 @@
-<?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();
-               }
-       }
-}
index bd80c79..824e684 100644 (file)
@@ -965,8 +965,7 @@ class WebRequest {
         * @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(
index 78dd5fe..7389ae2 100644 (file)
@@ -675,6 +675,7 @@ class InfoAction extends FormlessAction {
 
                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();
@@ -818,8 +819,7 @@ class InfoAction extends FormlessAction {
                                );
 
                                return $result;
-                       },
-                       86400 * 7
+                       }
                );
        }
 
index bdf02bf..acb260c 100644 (file)
@@ -32,7 +32,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
@@ -52,17 +52,31 @@ class ApiDelete extends ApiBase {
                $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 ) ) {
@@ -84,6 +98,11 @@ class ApiDelete extends ApiBase {
                }
                $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,
@@ -92,32 +111,16 @@ class ApiDelete extends ApiBase {
                $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 ) ) {
@@ -139,24 +142,19 @@ class ApiDelete extends ApiBase {
        /**
         * @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 ) {
@@ -191,6 +189,10 @@ class ApiDelete extends ApiBase {
                                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,
index 8e0ba8b..5d2db47 100644 (file)
@@ -760,12 +760,12 @@ class ApiMain extends ApiBase {
                        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" );
@@ -778,13 +778,13 @@ class ApiMain extends ApiBase {
                                $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
index 5127f47..90d7fa7 100644 (file)
@@ -69,6 +69,7 @@
        "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.",
index e2b09ea..d89fa01 100644 (file)
@@ -71,6 +71,7 @@
        "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}}",
index 5531245..55fc104 100644 (file)
@@ -1090,6 +1090,7 @@ class ChangeTags {
        public static function listExtensionActivatedTags() {
                return ObjectCache::getMainWANInstance()->getWithSetCallback(
                        wfMemcKey( 'active-tags' ),
+                       300,
                        function ( $oldValue, &$ttl, array &$setOpts ) {
                                $setOpts += Database::getCacheSetOptions( wfGetDB( DB_SLAVE ) );
 
@@ -1098,9 +1099,10 @@ class ChangeTags {
                                Hooks::run( 'ChangeTagsListActive', array( &$extensionActive ) );
                                return $extensionActive;
                        },
-                       300,
-                       array( wfMemcKey( 'active-tags' ) ),
-                       array( 'lockTSE' => INF )
+                       array(
+                               'checkKeys' => array( wfMemcKey( 'active-tags' ) ),
+                               'lockTSE' => INF
+                       )
                );
        }
 
@@ -1132,6 +1134,7 @@ class ChangeTags {
 
                return ObjectCache::getMainWANInstance()->getWithSetCallback(
                        wfMemcKey( 'valid-tags-db' ),
+                       300,
                        function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
                                $dbr = wfGetDB( DB_SLAVE );
 
@@ -1141,9 +1144,10 @@ class ChangeTags {
 
                                return array_filter( array_unique( $tags ) );
                        },
-                       300,
-                       array( wfMemcKey( 'valid-tags-db' ) ),
-                       array( 'lockTSE' => INF )
+                       array(
+                               'checkKeys' => array( wfMemcKey( 'valid-tags-db' ) ),
+                               'lockTSE' => INF
+                       )
                );
        }
 
@@ -1159,6 +1163,7 @@ class ChangeTags {
        public static function listExtensionDefinedTags() {
                return ObjectCache::getMainWANInstance()->getWithSetCallback(
                        wfMemcKey( 'valid-tags-hook' ),
+                       300,
                        function ( $oldValue, &$ttl, array &$setOpts ) {
                                $setOpts += Database::getCacheSetOptions( wfGetDB( DB_SLAVE ) );
 
@@ -1166,9 +1171,10 @@ class ChangeTags {
                                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
+                       )
                );
        }
 
@@ -1218,6 +1224,7 @@ class ChangeTags {
                $fname = __METHOD__;
                $cachedStats = ObjectCache::getMainWANInstance()->getWithSetCallback(
                        wfMemcKey( 'change-tag-statistics' ),
+                       300,
                        function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
                                $dbr = wfGetDB( DB_SLAVE, 'vslow' );
 
@@ -1238,9 +1245,10 @@ class ChangeTags {
 
                                return $out;
                        },
-                       300,
-                       array( wfMemcKey( 'change-tag-statistics' ) ),
-                       array( 'lockTSE' => INF )
+                       array(
+                               'checkKeys' => array( wfMemcKey( 'change-tag-statistics' ) ),
+                               'lockTSE' => INF
+                       )
                );
 
                return $cachedStats;
diff --git a/includes/clientpool/SquidPurgeClient.php b/includes/clientpool/SquidPurgeClient.php
new file mode 100644 (file)
index 0000000..91100e9
--- /dev/null
@@ -0,0 +1,396 @@
+<?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" );
+       }
+}
diff --git a/includes/clientpool/SquidPurgeClientPool.php b/includes/clientpool/SquidPurgeClientPool.php
new file mode 100644 (file)
index 0000000..feb80df
--- /dev/null
@@ -0,0 +1,108 @@
+<?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();
+               }
+       }
+}
index 9a520ff..969ed5e 100644 (file)
@@ -221,6 +221,9 @@ class FakeResultWrapper extends ResultWrapper {
        /** @var array|stdClass|bool */
        protected $currentRow = null;
 
+       /**
+        * @param array $array
+        */
        function __construct( $array ) {
                $this->result = $array;
        }
index a06d826..bad04f9 100644 (file)
@@ -29,6 +29,13 @@ abstract class LBFactory {
        /** @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
@@ -105,12 +112,6 @@ abstract class LBFactory {
                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.
index 6397bca..2655659 100644 (file)
@@ -158,6 +158,8 @@ class LBFactoryMulti extends LBFactory {
         * @throws MWException
         */
        public function __construct( array $conf ) {
+               parent::__construct( $conf );
+
                $this->chronProt = new ChronologyProtector;
                $this->conf = $conf;
                $required = array( 'sectionsByDB', 'sectionLoads', 'serverTemplate' );
index 90c33b0..e328727 100644 (file)
@@ -36,6 +36,8 @@ class LBFactorySimple extends LBFactory {
        private $loadMonitorClass;
 
        public function __construct( array $conf ) {
+               parent::__construct( $conf );
+
                $this->chronProt = new ChronologyProtector;
                $this->loadMonitorClass = isset( $conf['loadMonitorClass'] )
                        ? $conf['loadMonitorClass']
index 3350d19..fbc8c8c 100644 (file)
@@ -63,6 +63,8 @@ class LoadBalancer {
 
        /** @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:
@@ -155,7 +157,7 @@ class LoadBalancer {
         * @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
index 7d02a7a..53312cd 100644 (file)
@@ -44,7 +44,7 @@ interface DeferrableUpdate {
  * @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;
@@ -95,19 +95,32 @@ class DeferredUpdates {
         * 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();
                                        }
index be5aff3..d996870 100644 (file)
@@ -25,7 +25,7 @@
  *
  * @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 */
@@ -934,4 +934,16 @@ class LinksUpdate extends SqlDataUpdate {
                        );
                }
        }
+
+       public function getAsJobSpecification() {
+               return array(
+                       'wiki' => $this->mDb->getWikiID(),
+                       'job'  => new JobSpecification(
+                               'refreshLinks',
+                               array( 'prioritize' => true ),
+                               array( 'removeDuplicates' => true ),
+                               $this->getTitle()
+                       )
+               );
+       }
 }
index c0ae3b6..e79c06b 100644 (file)
@@ -79,7 +79,7 @@ class FileRepo {
        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 */
index f3a560b..283576a 100644 (file)
@@ -94,18 +94,19 @@ class ForeignDBRepo extends LocalRepo {
         * @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 );
                };
        }
 
index 02d859f..e93a4a8 100644 (file)
@@ -202,6 +202,7 @@ class LocalRepo extends FileRepo {
                $that = $this;
                $redirDbKey = ObjectCache::getMainWANInstance()->getWithSetCallback(
                        $memcKey,
+                       $expiry,
                        function ( $oldValue, &$ttl, array &$setOpts ) use ( $that, $title ) {
                                $dbr = $that->getSlaveDB(); // possibly remote DB
 
@@ -225,8 +226,7 @@ class LocalRepo extends FileRepo {
                                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
index 8715d51..0d11463 100644 (file)
@@ -669,8 +669,6 @@ class WebInstallerUpgrade extends WebInstallerPage {
        }
 
        public function showDoneMessage() {
-               global $wgScriptExtension;
-
                $this->startForm();
                $regenerate = !$this->getVar( '_ExistingDBSettings' );
                if ( $regenerate ) {
@@ -683,8 +681,7 @@ class WebInstallerUpgrade extends WebInstallerPage {
                        $this->parent->getInfoBox(
                                wfMessage( $msg,
                                        $this->getVar( 'wgServer' ) .
-                                       $this->getVar( 'wgScriptPath' ) . '/index' .
-                                       $wgScriptExtension
+                                       $this->getVar( 'wgScriptPath' ) . '/index.php'
                                )->plain(), 'tick-32.png'
                        )
                );
index 7a49f9b..bd8291f 100644 (file)
@@ -218,6 +218,7 @@ class Interwiki {
 
                $iwData = ObjectCache::getMainWANInstance()->getWithSetCallback(
                        wfMemcKey( 'interwiki', $prefix ),
+                       $wgInterwikiExpiry,
                        function ( $oldValue, &$ttl, array &$setOpts ) use ( $prefix ) {
                                $dbr = wfGetDB( DB_SLAVE );
 
@@ -231,8 +232,7 @@ class Interwiki {
                                );
 
                                return $row ? (array)$row : '!NONEXISTENT';
-                       },
-                       $wgInterwikiExpiry
+                       }
                );
 
                if ( is_array( $iwData ) ) {
index ecdf48f..8cbd48a 100644 (file)
@@ -95,11 +95,12 @@ abstract class BagOStuff implements LoggerAwareInterface {
         * @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 ) ) {
index 4beb627..6c69b14 100644 (file)
@@ -528,6 +528,8 @@ class WANObjectCache {
         *     $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 );
@@ -535,9 +537,7 @@ class WANObjectCache {
         *             $setOpts += Database::getCacheSetOptions( $dbr );
         *
         *             return $dbr->selectRow( ... );
-        *        },
-        *        // Time-to-live (seconds)
-        *        60
+        *        }
         *     );
         * @endcode
         *
@@ -546,6 +546,8 @@ class WANObjectCache {
         *     $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 );
@@ -553,13 +555,13 @@ class WANObjectCache {
         *             $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
         *
@@ -568,6 +570,8 @@ class WANObjectCache {
         *     $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
@@ -576,15 +580,15 @@ class WANObjectCache {
         *             $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
@@ -594,6 +598,8 @@ class WANObjectCache {
         *     $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 );
@@ -608,10 +614,6 @@ class WANObjectCache {
         *             // 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 )
         *     );
@@ -621,31 +623,41 @@ class WANObjectCache {
         * @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;
 
index e47e06c..d656bad 100644 (file)
@@ -143,7 +143,8 @@ class WikiPage implements Page, IDBAccessObject {
 
                $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;
                }
@@ -387,7 +388,7 @@ class WikiPage implements Page, IDBAccessObject {
         * 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
@@ -606,7 +607,7 @@ class WikiPage implements Page, IDBAccessObject {
                        // 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
@@ -1010,7 +1011,8 @@ class WikiPage implements Page, IDBAccessObject {
                        $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' ),
@@ -1102,7 +1104,8 @@ class WikiPage implements Page, IDBAccessObject {
        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' );
                }
@@ -1127,7 +1130,7 @@ class WikiPage implements Page, IDBAccessObject {
        /**
         * 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() ) {
@@ -1186,7 +1189,6 @@ class WikiPage implements Page, IDBAccessObject {
 
                return true;
        }
-
        /**
         * Insert a new empty page record for this article.
         * This *must* be followed up by creating a revision
@@ -1198,30 +1200,32 @@ class WikiPage implements Page, IDBAccessObject {
         * @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
                }
        }
 
@@ -1285,8 +1289,14 @@ class WikiPage implements Page, IDBAccessObject {
                        $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;
@@ -1478,8 +1488,9 @@ class WikiPage implements Page, IDBAccessObject {
         * @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' ) {
@@ -1588,8 +1599,6 @@ class WikiPage implements Page, IDBAccessObject {
         *          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
         *
@@ -1650,8 +1659,6 @@ class WikiPage implements Page, IDBAccessObject {
         *          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
         *
@@ -1820,7 +1827,8 @@ class WikiPage implements Page, IDBAccessObject {
                                        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 ) ) {
@@ -1945,11 +1953,6 @@ class WikiPage implements Page, IDBAccessObject {
                        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;
 
@@ -2023,7 +2026,8 @@ class WikiPage implements Page, IDBAccessObject {
         * @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;
 
@@ -2094,16 +2098,13 @@ class WikiPage implements Page, IDBAccessObject {
                                // 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 );
                                                }
                                        }
                                );
@@ -2117,8 +2118,12 @@ class WikiPage implements Page, IDBAccessObject {
                $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;
@@ -2227,7 +2232,8 @@ class WikiPage implements Page, IDBAccessObject {
                        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
@@ -2288,8 +2294,8 @@ class WikiPage implements Page, IDBAccessObject {
         * @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 );
@@ -2494,7 +2500,8 @@ class WikiPage implements Page, IDBAccessObject {
                                __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
@@ -2651,7 +2658,8 @@ class WikiPage implements Page, IDBAccessObject {
                        # 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] );
 
@@ -2684,7 +2692,8 @@ class WikiPage implements Page, IDBAccessObject {
 
                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 );
@@ -2701,7 +2710,7 @@ class WikiPage implements Page, IDBAccessObject {
         */
        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();
@@ -2764,12 +2773,15 @@ class WikiPage implements Page, IDBAccessObject {
                $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' );
@@ -2781,18 +2793,18 @@ class WikiPage implements Page, IDBAccessObject {
                $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;
                        }
                }
@@ -2863,7 +2875,8 @@ class WikiPage implements Page, IDBAccessObject {
 
                if ( !$ok ) {
                        $dbw->rollback( __METHOD__ );
-                       $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
+                       $status->error( 'cannotdelete',
+                               wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
                        return $status;
                }
 
@@ -2898,21 +2911,25 @@ class WikiPage implements Page, IDBAccessObject {
 
                $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()
                        ),
@@ -2935,10 +2952,9 @@ class WikiPage implements Page, IDBAccessObject {
 
                // 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' );
@@ -3151,7 +3167,10 @@ class WikiPage implements Page, IDBAccessObject {
                }
 
                // 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() ),
@@ -3160,7 +3179,7 @@ class WikiPage implements Page, IDBAccessObject {
                        ) );
                }
 
-               $revId = $status->value['revision']->getId();
+               $revId = $statusRev->getId();
 
                Hooks::run( 'ArticleRollbackComplete', array( $this, $guser, $target, $current ) );
 
@@ -3427,7 +3446,9 @@ class WikiPage implements Page, IDBAccessObject {
                        return;
                }
 
-               if ( !Hooks::run( 'OpportunisticLinksUpdate', array( $this, $this->mTitle, $parserOutput ) ) ) {
+               if ( !Hooks::run( 'OpportunisticLinksUpdate',
+                       array( $this, $this->mTitle, $parserOutput )
+               ) ) {
                        return;
                }
 
@@ -3523,8 +3544,8 @@ class WikiPage implements Page, IDBAccessObject {
         */
        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 );
                }
 
index bc9a5b8..4a672f2 100644 (file)
@@ -67,7 +67,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                        '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' ),
index fafb14c..5aa39c7 100644 (file)
@@ -172,7 +172,7 @@ class Site implements Serializable {
        }
 
        /**
-        * Gets the type of the site (ie wikipedia).
+        * Gets the group of the site (ie wikipedia).
         *
         * @since 1.21
         *
@@ -183,7 +183,7 @@ class Site implements Serializable {
        }
 
        /**
-        * Sets the type of the site (ie wikipedia).
+        * Sets the group of the site (ie wikipedia).
         *
         * @since 1.21
         *
index 65fa01f..361b7b1 100644 (file)
@@ -116,6 +116,11 @@ class SpecialChangeEmail extends FormSpecialPage {
                $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 ) {
index 64a93a0..952ae0e 100644 (file)
@@ -282,10 +282,12 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
                        }
 
                        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";
                        }
                }
 
@@ -659,7 +661,8 @@ class SpecialEditWatchlist extends UnlistedSpecialPage {
                        $link = '<span class="watchlistredir">' . $link . '</span>';
                }
 
-               return $link . " (" . $this->getLanguage()->pipeList( $tools ) . ")";
+               return $link . ' ' .
+                       $this->msg( 'parentheses' )->rawParams( $this->getLanguage()->pipeList( $tools ) )->escaped();
        }
 
        /**
index cc9f5c8..fe8b949 100644 (file)
@@ -237,7 +237,6 @@ class UploadFromChunks extends UploadFromFile {
                $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(
@@ -248,7 +247,7 @@ class UploadFromChunks extends UploadFromFile {
                        array( 'us_key' => $this->mFileKey ),
                        __METHOD__
                );
-               $dbw->commit( __METHOD__ );
+               $dbw->commit( __METHOD__, 'flush' );
        }
 
        /**
diff --git a/index.php5 b/index.php5
deleted file mode 100644 (file)
index 2a65c71..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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';
index 25781d1..5631c2a 100644 (file)
        "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:",
index e04b0bf..6f68586 100644 (file)
        "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}}",
index b64922e..fa4b509 100644 (file)
@@ -47,13 +47,13 @@ $specialPageAliases = array(
        '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' ),
@@ -92,14 +92,14 @@ $specialPageAliases = array(
        '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' ),
diff --git a/load.php5 b/load.php5
deleted file mode 100644 (file)
index af16169..0000000
--- a/load.php5
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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';
diff --git a/opensearch_desc.php5 b/opensearch_desc.php5
deleted file mode 100644 (file)
index 874920e..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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';
diff --git a/profileinfo.php5 b/profileinfo.php5
deleted file mode 100644 (file)
index 6d430f6..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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';
index c373601..5053280 100644 (file)
@@ -2,7 +2,8 @@
        "@metadata": {
                "authors": [
                        "Gitartha.bordoloi",
-                       "Dibya Dutta"
+                       "Dibya Dutta",
+                       "IKHazarika"
                ]
        },
        "ooui-outline-control-move-down": "সমল তললৈ স্থানান্তৰ কৰক",
@@ -17,6 +18,8 @@
        "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": "ইয়াত ফাইল এৰক"
 }
index 3a4e145..574c592 100644 (file)
@@ -16,5 +16,9 @@
        "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"
 }
index 486e87f..9ceb96f 100644 (file)
@@ -29,7 +29,7 @@
        "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",
index c827554..de52812 100644 (file)
@@ -17,5 +17,7 @@
        "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": "Није изабрана ниједна датотека"
 }
index 27cef23..fd63081 100644 (file)
@@ -20,6 +20,7 @@
        "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"
index fdfbffb..6fb7be6 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * 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 {
index 2e3c409..d0c8e51 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * 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
index e4e0d36..4b65ad4 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * 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;
 }
index 4c6c77c..60acdd2 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * 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
index 1a7d406..e433e91 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * 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 ) {
 
@@ -9832,8 +9832,8 @@ OO.ui.BookletLayout.prototype.focus = function ( itemIndex ) {
                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();
        }
 };
 
@@ -10338,9 +10338,9 @@ OO.ui.IndexLayout.prototype.focus = function ( itemIndex ) {
        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();
        }
 };
 
@@ -10664,6 +10664,17 @@ OO.ui.PanelLayout = function OoUiPanelLayout( config ) {
 
 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,
@@ -15530,6 +15541,18 @@ OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) {
        }
        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 );
index 9231fcc..b1e5151 100644 (file)
                        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.
         *
index 4e3f771..c8877fd 100644 (file)
                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 ) );
index 50fd0b4..4cec813 100644 (file)
                        } 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';
                        }
                },
 
index 51a19c6..5f21e07 100644 (file)
@@ -266,9 +266,9 @@ class OutputPageTest extends MediaWikiTestCase {
         * @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() ) )
@@ -283,18 +283,18 @@ class OutputPageTest extends MediaWikiTestCase {
                        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(
@@ -303,42 +303,63 @@ class OutputPageTest extends MediaWikiTestCase {
                                        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() );
+       }
 }
 
 /**
index 742b737..4195216 100644 (file)
@@ -13,11 +13,14 @@ class WANObjectCacheTest extends MediaWikiTestCase {
                        $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;
        }
 
        /**
@@ -111,7 +114,10 @@ class WANObjectCacheTest extends MediaWikiTestCase {
                $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" );
 
@@ -142,6 +148,32 @@ class WANObjectCacheTest extends MediaWikiTestCase {
                $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()
         */
@@ -239,12 +271,12 @@ class WANObjectCacheTest extends MediaWikiTestCase {
                $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' );
@@ -252,7 +284,7 @@ class WANObjectCacheTest extends MediaWikiTestCase {
                $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' );
@@ -260,7 +292,7 @@ class WANObjectCacheTest extends MediaWikiTestCase {
                $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' );
index c1f1484..d40c00a 100644 (file)
                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' ),
diff --git a/thumb.php5 b/thumb.php5
deleted file mode 100644 (file)
index 5bfe328..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-<?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';
diff --git a/thumb_handler.php5 b/thumb_handler.php5
deleted file mode 100644 (file)
index c9e10cd..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-<?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';