Merge "Try to reduce stale cache updates in User::saveSettings()"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 16 Apr 2015 16:40:02 +0000 (16:40 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 16 Apr 2015 16:40:02 +0000 (16:40 +0000)
autoload.php
includes/ChangeTags.php
includes/DefaultSettings.php
includes/MessageBlobStore.php [deleted file]
includes/cache/MessageBlobStore.php [new file with mode: 0644]
includes/specials/SpecialVersion.php
languages/i18n/en.json
languages/messages/MessagesBgn.php
languages/messages/MessagesEn.php
tests/phpunit/maintenance/backupTextPassTest.php
tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js

index 93f8e43..54e4f6c 100644 (file)
@@ -770,7 +770,7 @@ $wgAutoloadLocalClasses = array(
        'MergeLogFormatter' => __DIR__ . '/includes/logging/MergeLogFormatter.php',
        'MergeMessageFileList' => __DIR__ . '/maintenance/mergeMessageFileList.php',
        'Message' => __DIR__ . '/includes/Message.php',
-       'MessageBlobStore' => __DIR__ . '/includes/MessageBlobStore.php',
+       'MessageBlobStore' => __DIR__ . '/includes/cache/MessageBlobStore.php',
        'MessageCache' => __DIR__ . '/includes/cache/MessageCache.php',
        'MessageContent' => __DIR__ . '/includes/content/MessageContent.php',
        'MessageSpecifier' => __DIR__ . '/includes/libs/MessageSpecifier.php',
index e2ef75e..43f957c 100644 (file)
@@ -1197,7 +1197,7 @@ class ChangeTags {
 
                $out = array();
 
-               $dbr = wfGetDB( DB_SLAVE );
+               $dbr = wfGetDB( DB_SLAVE, 'vslow' );
                $res = $dbr->select(
                        'change_tag',
                        array( 'ct_tag', 'hitcount' => 'count(*)' ),
index d5801cb..c2d6c95 100644 (file)
@@ -4297,6 +4297,7 @@ $wgReservedUsernames = array(
        'msg:double-redirect-fixer', // Automatic double redirect fix
        'msg:usermessage-editor', // Default user for leaving user messages
        'msg:proxyblocker', // For $wgProxyList and Special:Blockme (removed in 1.22)
+       'msg:spambot_username', // Used by cleanupSpam.php
 );
 
 /**
diff --git a/includes/MessageBlobStore.php b/includes/MessageBlobStore.php
deleted file mode 100644 (file)
index 011cae6..0000000
+++ /dev/null
@@ -1,390 +0,0 @@
-<?php
-/**
- * Resource message blobs storage used by the resource loader.
- *
- * 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
- * @author Roan Kattouw
- * @author Trevor Parscal
- */
-
-/**
- * This class provides access to the resource message blobs storage used by
- * the ResourceLoader.
- *
- * A message blob is a JSON object containing the interface messages for a
- * certain resource in a certain language. These message blobs are cached
- * in the msg_resource table and automatically invalidated when one of their
- * constituent messages or the resource itself is changed.
- */
-class MessageBlobStore {
-       /**
-        * Get the singleton instance
-        *
-        * @since 1.24
-        * @deprecated since 1.25
-        * @return MessageBlobStore
-        */
-       public static function getInstance() {
-               wfDeprecated( __METHOD__, '1.25' );
-               return new self;
-       }
-
-       /**
-        * Get the message blobs for a set of modules
-        *
-        * @param ResourceLoader $resourceLoader
-        * @param array $modules Array of module objects keyed by module name
-        * @param string $lang Language code
-        * @return array An array mapping module names to message blobs
-        */
-       public function get( ResourceLoader $resourceLoader, $modules, $lang ) {
-               if ( !count( $modules ) ) {
-                       return array();
-               }
-               // Try getting from the DB first
-               $blobs = $this->getFromDB( $resourceLoader, array_keys( $modules ), $lang );
-
-               // Generate blobs for any missing modules and store them in the DB
-               $missing = array_diff( array_keys( $modules ), array_keys( $blobs ) );
-               foreach ( $missing as $name ) {
-                       $blob = $this->insertMessageBlob( $name, $modules[$name], $lang );
-                       if ( $blob ) {
-                               $blobs[$name] = $blob;
-                       }
-               }
-
-               return $blobs;
-       }
-
-       /**
-        * Generate and insert a new message blob. If the blob was already
-        * present, it is not regenerated; instead, the preexisting blob
-        * is fetched and returned.
-        *
-        * @param string $name Module name
-        * @param ResourceLoaderModule $module
-        * @param string $lang Language code
-        * @return mixed Message blob or false if the module has no messages
-        */
-       public function insertMessageBlob( $name, ResourceLoaderModule $module, $lang ) {
-               $blob = $this->generateMessageBlob( $module, $lang );
-
-               if ( !$blob ) {
-                       return false;
-               }
-
-               try {
-                       $dbw = wfGetDB( DB_MASTER );
-                       $success = $dbw->insert( 'msg_resource', array(
-                                       'mr_lang' => $lang,
-                                       'mr_resource' => $name,
-                                       'mr_blob' => $blob,
-                                       'mr_timestamp' => $dbw->timestamp()
-                               ),
-                               __METHOD__,
-                               array( 'IGNORE' )
-                       );
-
-                       if ( $success ) {
-                               if ( $dbw->affectedRows() == 0 ) {
-                                       // Blob was already present, fetch it
-                                       $blob = $dbw->selectField( 'msg_resource', 'mr_blob', array(
-                                                       'mr_resource' => $name,
-                                                       'mr_lang' => $lang,
-                                               ),
-                                               __METHOD__
-                                       );
-                               } else {
-                                       // Update msg_resource_links
-                                       $rows = array();
-
-                                       foreach ( $module->getMessages() as $key ) {
-                                               $rows[] = array(
-                                                       'mrl_resource' => $name,
-                                                       'mrl_message' => $key
-                                               );
-                                       }
-                                       $dbw->insert( 'msg_resource_links', $rows,
-                                               __METHOD__, array( 'IGNORE' )
-                                       );
-                               }
-                       }
-               } catch ( DBError $e ) {
-                       wfDebug( __METHOD__ . " failed to update DB: $e\n" );
-               }
-               return $blob;
-       }
-
-       /**
-        * Update the message blob for a given module in a given language
-        *
-        * @param string $name Module name
-        * @param ResourceLoaderModule $module
-        * @param string $lang Language code
-        * @return string Regenerated message blob, or null if there was no blob for
-        *   the given module/language pair.
-        */
-       public function updateModule( $name, ResourceLoaderModule $module, $lang ) {
-               $dbw = wfGetDB( DB_MASTER );
-               $row = $dbw->selectRow( 'msg_resource', 'mr_blob',
-                       array( 'mr_resource' => $name, 'mr_lang' => $lang ),
-                       __METHOD__
-               );
-               if ( !$row ) {
-                       return null;
-               }
-
-               // Save the old and new blobs for later
-               $oldBlob = $row->mr_blob;
-               $newBlob = $this->generateMessageBlob( $module, $lang );
-
-               try {
-                       $newRow = array(
-                               'mr_resource' => $name,
-                               'mr_lang' => $lang,
-                               'mr_blob' => $newBlob,
-                               'mr_timestamp' => $dbw->timestamp()
-                       );
-
-                       $dbw->replace( 'msg_resource',
-                               array( array( 'mr_resource', 'mr_lang' ) ),
-                               $newRow, __METHOD__
-                       );
-
-                       // Figure out which messages were added and removed
-                       $oldMessages = array_keys( FormatJson::decode( $oldBlob, true ) );
-                       $newMessages = array_keys( FormatJson::decode( $newBlob, true ) );
-                       $added = array_diff( $newMessages, $oldMessages );
-                       $removed = array_diff( $oldMessages, $newMessages );
-
-                       // Delete removed messages, insert added ones
-                       if ( $removed ) {
-                               $dbw->delete( 'msg_resource_links', array(
-                                               'mrl_resource' => $name,
-                                               'mrl_message' => $removed
-                                       ), __METHOD__
-                               );
-                       }
-
-                       $newLinksRows = array();
-
-                       foreach ( $added as $message ) {
-                               $newLinksRows[] = array(
-                                       'mrl_resource' => $name,
-                                       'mrl_message' => $message
-                               );
-                       }
-
-                       if ( $newLinksRows ) {
-                               $dbw->insert( 'msg_resource_links', $newLinksRows, __METHOD__,
-                                       array( 'IGNORE' ) // just in case
-                               );
-                       }
-               } catch ( Exception $e ) {
-                       wfDebug( __METHOD__ . " failed to update DB: $e\n" );
-               }
-               return $newBlob;
-       }
-
-       /**
-        * Update a single message in all message blobs it occurs in.
-        *
-        * @param string $key Message key
-        */
-       public function updateMessage( $key ) {
-               try {
-                       $dbw = wfGetDB( DB_MASTER );
-
-                       // Keep running until the updates queue is empty.
-                       // Due to update conflicts, the queue might not be emptied
-                       // in one iteration.
-                       $updates = null;
-                       do {
-                               $updates = $this->getUpdatesForMessage( $key, $updates );
-
-                               foreach ( $updates as $k => $update ) {
-                                       // Update the row on the condition that it
-                                       // didn't change since we fetched it by putting
-                                       // the timestamp in the WHERE clause.
-                                       $success = $dbw->update( 'msg_resource',
-                                               array(
-                                                       'mr_blob' => $update['newBlob'],
-                                                       'mr_timestamp' => $dbw->timestamp() ),
-                                               array(
-                                                       'mr_resource' => $update['resource'],
-                                                       'mr_lang' => $update['lang'],
-                                                       'mr_timestamp' => $update['timestamp'] ),
-                                               __METHOD__
-                                       );
-
-                                       // Only requeue conflicted updates.
-                                       // If update() returned false, don't retry, for
-                                       // fear of getting into an infinite loop
-                                       if ( !( $success && $dbw->affectedRows() == 0 ) ) {
-                                               // Not conflicted
-                                               unset( $updates[$k] );
-                                       }
-                               }
-                       } while ( count( $updates ) );
-
-                       // No need to update msg_resource_links because we didn't add
-                       // or remove any messages, we just changed their contents.
-               } catch ( Exception $e ) {
-                       wfDebug( __METHOD__ . " failed to update DB: $e\n" );
-               }
-       }
-
-       public function clear() {
-               // TODO: Give this some more thought
-               try {
-                       // Not using TRUNCATE, because that needs extra permissions,
-                       // which maybe not granted to the database user.
-                       $dbw = wfGetDB( DB_MASTER );
-                       $dbw->delete( 'msg_resource', '*', __METHOD__ );
-                       $dbw->delete( 'msg_resource_links', '*', __METHOD__ );
-               } catch ( Exception $e ) {
-                       wfDebug( __METHOD__ . " failed to update DB: $e\n" );
-               }
-       }
-
-       /**
-        * Create an update queue for updateMessage()
-        *
-        * @param string $key Message key
-        * @param array $prevUpdates Updates queue to refresh or null to build a fresh update queue
-        * @return array Updates queue
-        */
-       private function getUpdatesForMessage( $key, $prevUpdates = null ) {
-               $dbw = wfGetDB( DB_MASTER );
-
-               if ( is_null( $prevUpdates ) ) {
-                       // Fetch all blobs referencing $key
-                       $res = $dbw->select(
-                               array( 'msg_resource', 'msg_resource_links' ),
-                               array( 'mr_resource', 'mr_lang', 'mr_blob', 'mr_timestamp' ),
-                               array( 'mrl_message' => $key, 'mr_resource=mrl_resource' ),
-                               __METHOD__
-                       );
-               } else {
-                       // Refetch the blobs referenced by $prevUpdates
-
-                       // Reorganize the (resource, lang) pairs in the format
-                       // expected by makeWhereFrom2d()
-                       $twoD = array();
-
-                       foreach ( $prevUpdates as $update ) {
-                               $twoD[$update['resource']][$update['lang']] = true;
-                       }
-
-                       $res = $dbw->select( 'msg_resource',
-                               array( 'mr_resource', 'mr_lang', 'mr_blob', 'mr_timestamp' ),
-                               $dbw->makeWhereFrom2d( $twoD, 'mr_resource', 'mr_lang' ),
-                               __METHOD__
-                       );
-               }
-
-               // Build the new updates queue
-               $updates = array();
-
-               foreach ( $res as $row ) {
-                       $updates[] = array(
-                               'resource' => $row->mr_resource,
-                               'lang' => $row->mr_lang,
-                               'timestamp' => $row->mr_timestamp,
-                               'newBlob' => $this->reencodeBlob( $row->mr_blob, $key, $row->mr_lang )
-                       );
-               }
-
-               return $updates;
-       }
-
-       /**
-        * Reencode a message blob with the updated value for a message
-        *
-        * @param string $blob Message blob (JSON object)
-        * @param string $key Message key
-        * @param string $lang Language code
-        * @return string Message blob with $key replaced with its new value
-        */
-       private function reencodeBlob( $blob, $key, $lang ) {
-               $decoded = FormatJson::decode( $blob, true );
-               $decoded[$key] = wfMessage( $key )->inLanguage( $lang )->plain();
-
-               return FormatJson::encode( (object)$decoded );
-       }
-
-       /**
-        * Get the message blobs for a set of modules from the database.
-        * Modules whose blobs are not in the database are silently dropped.
-        *
-        * @param ResourceLoader $resourceLoader
-        * @param array $modules Array of module names
-        * @param string $lang Language code
-        * @throws MWException
-        * @return array Array mapping module names to blobs
-        */
-       private function getFromDB( ResourceLoader $resourceLoader, $modules, $lang ) {
-               $config = $resourceLoader->getConfig();
-               $retval = array();
-               $dbr = wfGetDB( DB_SLAVE );
-               $res = $dbr->select( 'msg_resource',
-                       array( 'mr_blob', 'mr_resource', 'mr_timestamp' ),
-                       array( 'mr_resource' => $modules, 'mr_lang' => $lang ),
-                       __METHOD__
-               );
-
-               foreach ( $res as $row ) {
-                       $module = $resourceLoader->getModule( $row->mr_resource );
-                       if ( !$module ) {
-                               // This shouldn't be possible
-                               throw new MWException( __METHOD__ . ' passed an invalid module name' );
-                       }
-
-                       // Update the module's blobs if the set of messages changed or if the blob is
-                       // older than the CacheEpoch setting
-                       $keys = array_keys( FormatJson::decode( $row->mr_blob, true ) );
-                       $values = array_values( array_unique( $module->getMessages() ) );
-                       if ( $keys !== $values
-                               || wfTimestamp( TS_MW, $row->mr_timestamp ) <= $config->get( 'CacheEpoch' )
-                       ) {
-                               $retval[$row->mr_resource] = $this->updateModule( $row->mr_resource, $module, $lang );
-                       } else {
-                               $retval[$row->mr_resource] = $row->mr_blob;
-                       }
-               }
-
-               return $retval;
-       }
-
-       /**
-        * Generate the message blob for a given module in a given language.
-        *
-        * @param ResourceLoaderModule $module
-        * @param string $lang Language code
-        * @return string JSON object
-        */
-       private function generateMessageBlob( ResourceLoaderModule $module, $lang ) {
-               $messages = array();
-
-               foreach ( $module->getMessages() as $key ) {
-                       $messages[$key] = wfMessage( $key )->inLanguage( $lang )->plain();
-               }
-
-               return FormatJson::encode( (object)$messages );
-       }
-}
diff --git a/includes/cache/MessageBlobStore.php b/includes/cache/MessageBlobStore.php
new file mode 100644 (file)
index 0000000..011cae6
--- /dev/null
@@ -0,0 +1,390 @@
+<?php
+/**
+ * Resource message blobs storage used by the resource loader.
+ *
+ * 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
+ * @author Roan Kattouw
+ * @author Trevor Parscal
+ */
+
+/**
+ * This class provides access to the resource message blobs storage used by
+ * the ResourceLoader.
+ *
+ * A message blob is a JSON object containing the interface messages for a
+ * certain resource in a certain language. These message blobs are cached
+ * in the msg_resource table and automatically invalidated when one of their
+ * constituent messages or the resource itself is changed.
+ */
+class MessageBlobStore {
+       /**
+        * Get the singleton instance
+        *
+        * @since 1.24
+        * @deprecated since 1.25
+        * @return MessageBlobStore
+        */
+       public static function getInstance() {
+               wfDeprecated( __METHOD__, '1.25' );
+               return new self;
+       }
+
+       /**
+        * Get the message blobs for a set of modules
+        *
+        * @param ResourceLoader $resourceLoader
+        * @param array $modules Array of module objects keyed by module name
+        * @param string $lang Language code
+        * @return array An array mapping module names to message blobs
+        */
+       public function get( ResourceLoader $resourceLoader, $modules, $lang ) {
+               if ( !count( $modules ) ) {
+                       return array();
+               }
+               // Try getting from the DB first
+               $blobs = $this->getFromDB( $resourceLoader, array_keys( $modules ), $lang );
+
+               // Generate blobs for any missing modules and store them in the DB
+               $missing = array_diff( array_keys( $modules ), array_keys( $blobs ) );
+               foreach ( $missing as $name ) {
+                       $blob = $this->insertMessageBlob( $name, $modules[$name], $lang );
+                       if ( $blob ) {
+                               $blobs[$name] = $blob;
+                       }
+               }
+
+               return $blobs;
+       }
+
+       /**
+        * Generate and insert a new message blob. If the blob was already
+        * present, it is not regenerated; instead, the preexisting blob
+        * is fetched and returned.
+        *
+        * @param string $name Module name
+        * @param ResourceLoaderModule $module
+        * @param string $lang Language code
+        * @return mixed Message blob or false if the module has no messages
+        */
+       public function insertMessageBlob( $name, ResourceLoaderModule $module, $lang ) {
+               $blob = $this->generateMessageBlob( $module, $lang );
+
+               if ( !$blob ) {
+                       return false;
+               }
+
+               try {
+                       $dbw = wfGetDB( DB_MASTER );
+                       $success = $dbw->insert( 'msg_resource', array(
+                                       'mr_lang' => $lang,
+                                       'mr_resource' => $name,
+                                       'mr_blob' => $blob,
+                                       'mr_timestamp' => $dbw->timestamp()
+                               ),
+                               __METHOD__,
+                               array( 'IGNORE' )
+                       );
+
+                       if ( $success ) {
+                               if ( $dbw->affectedRows() == 0 ) {
+                                       // Blob was already present, fetch it
+                                       $blob = $dbw->selectField( 'msg_resource', 'mr_blob', array(
+                                                       'mr_resource' => $name,
+                                                       'mr_lang' => $lang,
+                                               ),
+                                               __METHOD__
+                                       );
+                               } else {
+                                       // Update msg_resource_links
+                                       $rows = array();
+
+                                       foreach ( $module->getMessages() as $key ) {
+                                               $rows[] = array(
+                                                       'mrl_resource' => $name,
+                                                       'mrl_message' => $key
+                                               );
+                                       }
+                                       $dbw->insert( 'msg_resource_links', $rows,
+                                               __METHOD__, array( 'IGNORE' )
+                                       );
+                               }
+                       }
+               } catch ( DBError $e ) {
+                       wfDebug( __METHOD__ . " failed to update DB: $e\n" );
+               }
+               return $blob;
+       }
+
+       /**
+        * Update the message blob for a given module in a given language
+        *
+        * @param string $name Module name
+        * @param ResourceLoaderModule $module
+        * @param string $lang Language code
+        * @return string Regenerated message blob, or null if there was no blob for
+        *   the given module/language pair.
+        */
+       public function updateModule( $name, ResourceLoaderModule $module, $lang ) {
+               $dbw = wfGetDB( DB_MASTER );
+               $row = $dbw->selectRow( 'msg_resource', 'mr_blob',
+                       array( 'mr_resource' => $name, 'mr_lang' => $lang ),
+                       __METHOD__
+               );
+               if ( !$row ) {
+                       return null;
+               }
+
+               // Save the old and new blobs for later
+               $oldBlob = $row->mr_blob;
+               $newBlob = $this->generateMessageBlob( $module, $lang );
+
+               try {
+                       $newRow = array(
+                               'mr_resource' => $name,
+                               'mr_lang' => $lang,
+                               'mr_blob' => $newBlob,
+                               'mr_timestamp' => $dbw->timestamp()
+                       );
+
+                       $dbw->replace( 'msg_resource',
+                               array( array( 'mr_resource', 'mr_lang' ) ),
+                               $newRow, __METHOD__
+                       );
+
+                       // Figure out which messages were added and removed
+                       $oldMessages = array_keys( FormatJson::decode( $oldBlob, true ) );
+                       $newMessages = array_keys( FormatJson::decode( $newBlob, true ) );
+                       $added = array_diff( $newMessages, $oldMessages );
+                       $removed = array_diff( $oldMessages, $newMessages );
+
+                       // Delete removed messages, insert added ones
+                       if ( $removed ) {
+                               $dbw->delete( 'msg_resource_links', array(
+                                               'mrl_resource' => $name,
+                                               'mrl_message' => $removed
+                                       ), __METHOD__
+                               );
+                       }
+
+                       $newLinksRows = array();
+
+                       foreach ( $added as $message ) {
+                               $newLinksRows[] = array(
+                                       'mrl_resource' => $name,
+                                       'mrl_message' => $message
+                               );
+                       }
+
+                       if ( $newLinksRows ) {
+                               $dbw->insert( 'msg_resource_links', $newLinksRows, __METHOD__,
+                                       array( 'IGNORE' ) // just in case
+                               );
+                       }
+               } catch ( Exception $e ) {
+                       wfDebug( __METHOD__ . " failed to update DB: $e\n" );
+               }
+               return $newBlob;
+       }
+
+       /**
+        * Update a single message in all message blobs it occurs in.
+        *
+        * @param string $key Message key
+        */
+       public function updateMessage( $key ) {
+               try {
+                       $dbw = wfGetDB( DB_MASTER );
+
+                       // Keep running until the updates queue is empty.
+                       // Due to update conflicts, the queue might not be emptied
+                       // in one iteration.
+                       $updates = null;
+                       do {
+                               $updates = $this->getUpdatesForMessage( $key, $updates );
+
+                               foreach ( $updates as $k => $update ) {
+                                       // Update the row on the condition that it
+                                       // didn't change since we fetched it by putting
+                                       // the timestamp in the WHERE clause.
+                                       $success = $dbw->update( 'msg_resource',
+                                               array(
+                                                       'mr_blob' => $update['newBlob'],
+                                                       'mr_timestamp' => $dbw->timestamp() ),
+                                               array(
+                                                       'mr_resource' => $update['resource'],
+                                                       'mr_lang' => $update['lang'],
+                                                       'mr_timestamp' => $update['timestamp'] ),
+                                               __METHOD__
+                                       );
+
+                                       // Only requeue conflicted updates.
+                                       // If update() returned false, don't retry, for
+                                       // fear of getting into an infinite loop
+                                       if ( !( $success && $dbw->affectedRows() == 0 ) ) {
+                                               // Not conflicted
+                                               unset( $updates[$k] );
+                                       }
+                               }
+                       } while ( count( $updates ) );
+
+                       // No need to update msg_resource_links because we didn't add
+                       // or remove any messages, we just changed their contents.
+               } catch ( Exception $e ) {
+                       wfDebug( __METHOD__ . " failed to update DB: $e\n" );
+               }
+       }
+
+       public function clear() {
+               // TODO: Give this some more thought
+               try {
+                       // Not using TRUNCATE, because that needs extra permissions,
+                       // which maybe not granted to the database user.
+                       $dbw = wfGetDB( DB_MASTER );
+                       $dbw->delete( 'msg_resource', '*', __METHOD__ );
+                       $dbw->delete( 'msg_resource_links', '*', __METHOD__ );
+               } catch ( Exception $e ) {
+                       wfDebug( __METHOD__ . " failed to update DB: $e\n" );
+               }
+       }
+
+       /**
+        * Create an update queue for updateMessage()
+        *
+        * @param string $key Message key
+        * @param array $prevUpdates Updates queue to refresh or null to build a fresh update queue
+        * @return array Updates queue
+        */
+       private function getUpdatesForMessage( $key, $prevUpdates = null ) {
+               $dbw = wfGetDB( DB_MASTER );
+
+               if ( is_null( $prevUpdates ) ) {
+                       // Fetch all blobs referencing $key
+                       $res = $dbw->select(
+                               array( 'msg_resource', 'msg_resource_links' ),
+                               array( 'mr_resource', 'mr_lang', 'mr_blob', 'mr_timestamp' ),
+                               array( 'mrl_message' => $key, 'mr_resource=mrl_resource' ),
+                               __METHOD__
+                       );
+               } else {
+                       // Refetch the blobs referenced by $prevUpdates
+
+                       // Reorganize the (resource, lang) pairs in the format
+                       // expected by makeWhereFrom2d()
+                       $twoD = array();
+
+                       foreach ( $prevUpdates as $update ) {
+                               $twoD[$update['resource']][$update['lang']] = true;
+                       }
+
+                       $res = $dbw->select( 'msg_resource',
+                               array( 'mr_resource', 'mr_lang', 'mr_blob', 'mr_timestamp' ),
+                               $dbw->makeWhereFrom2d( $twoD, 'mr_resource', 'mr_lang' ),
+                               __METHOD__
+                       );
+               }
+
+               // Build the new updates queue
+               $updates = array();
+
+               foreach ( $res as $row ) {
+                       $updates[] = array(
+                               'resource' => $row->mr_resource,
+                               'lang' => $row->mr_lang,
+                               'timestamp' => $row->mr_timestamp,
+                               'newBlob' => $this->reencodeBlob( $row->mr_blob, $key, $row->mr_lang )
+                       );
+               }
+
+               return $updates;
+       }
+
+       /**
+        * Reencode a message blob with the updated value for a message
+        *
+        * @param string $blob Message blob (JSON object)
+        * @param string $key Message key
+        * @param string $lang Language code
+        * @return string Message blob with $key replaced with its new value
+        */
+       private function reencodeBlob( $blob, $key, $lang ) {
+               $decoded = FormatJson::decode( $blob, true );
+               $decoded[$key] = wfMessage( $key )->inLanguage( $lang )->plain();
+
+               return FormatJson::encode( (object)$decoded );
+       }
+
+       /**
+        * Get the message blobs for a set of modules from the database.
+        * Modules whose blobs are not in the database are silently dropped.
+        *
+        * @param ResourceLoader $resourceLoader
+        * @param array $modules Array of module names
+        * @param string $lang Language code
+        * @throws MWException
+        * @return array Array mapping module names to blobs
+        */
+       private function getFromDB( ResourceLoader $resourceLoader, $modules, $lang ) {
+               $config = $resourceLoader->getConfig();
+               $retval = array();
+               $dbr = wfGetDB( DB_SLAVE );
+               $res = $dbr->select( 'msg_resource',
+                       array( 'mr_blob', 'mr_resource', 'mr_timestamp' ),
+                       array( 'mr_resource' => $modules, 'mr_lang' => $lang ),
+                       __METHOD__
+               );
+
+               foreach ( $res as $row ) {
+                       $module = $resourceLoader->getModule( $row->mr_resource );
+                       if ( !$module ) {
+                               // This shouldn't be possible
+                               throw new MWException( __METHOD__ . ' passed an invalid module name' );
+                       }
+
+                       // Update the module's blobs if the set of messages changed or if the blob is
+                       // older than the CacheEpoch setting
+                       $keys = array_keys( FormatJson::decode( $row->mr_blob, true ) );
+                       $values = array_values( array_unique( $module->getMessages() ) );
+                       if ( $keys !== $values
+                               || wfTimestamp( TS_MW, $row->mr_timestamp ) <= $config->get( 'CacheEpoch' )
+                       ) {
+                               $retval[$row->mr_resource] = $this->updateModule( $row->mr_resource, $module, $lang );
+                       } else {
+                               $retval[$row->mr_resource] = $row->mr_blob;
+                       }
+               }
+
+               return $retval;
+       }
+
+       /**
+        * Generate the message blob for a given module in a given language.
+        *
+        * @param ResourceLoaderModule $module
+        * @param string $lang Language code
+        * @return string JSON object
+        */
+       private function generateMessageBlob( ResourceLoaderModule $module, $lang ) {
+               $messages = array();
+
+               foreach ( $module->getMessages() as $key ) {
+                       $messages[$key] = wfMessage( $key )->inLanguage( $lang )->plain();
+               }
+
+               return FormatJson::encode( (object)$messages );
+       }
+}
index c1a9593..9a1c5e5 100644 (file)
@@ -265,8 +265,8 @@ class SpecialVersion extends SpecialPage {
                        $version = $wgVersion . ' ' .
                                wfMessage(
                                        'version-svn-revision',
-                                       isset( $info['directory-rev'] ) ? $info['directory-rev'] : '',
-                                       $info['checkout-rev']
+                                       isset( $svnInfo['directory-rev'] ) ? $svnInfo['directory-rev'] : '',
+                                       isset( $svnInfo['checkout-rev'] ) ? $svnInfo['checkout-rev'] : ''
                                )->text();
                }
 
index ff16dda..1dfe21d 100644 (file)
        "tags-edit-success": "<strong>The changes were successfully applied.</strong>",
        "tags-edit-failure": "<strong>The changes could not be applied:</strong>\n$1",
        "tags-edit-nooldid-title": "Invalid target revision",
-       "tags-edit-nooldid-text": "You have either not specified a target revision(s) to perform this function, or the specified revision does not exist.",
+       "tags-edit-nooldid-text": "You have either not specified any target revision on which to perform this function, or the specified revision does not exist.",
        "tags-edit-none-selected": "Please select at least one tag to add or remove.",
        "comparepages": "Compare pages",
        "comparepages-summary": "",
index 027e55e..924f6c2 100644 (file)
@@ -144,4 +144,6 @@ $specialPageAliases = array(
        'Watchlist'                 => array( 'پدگیر_ئی_لیست' ),
        'Whatlinkshere'             => array( 'لینک_په_ای_دیما' ),
        'Withoutinterwiki'          => array( 'بئ_شه_مانیجین_ویکی_ئا' ),
-);
\ No newline at end of file
+);
+
+$linkTrail = "/^([اآأبپتثجچحخدڈذرڑزژسشصضطظعغفقکگلمنوۆؤھهئیێ‌]+)(.*)$/sDu";
\ No newline at end of file
index a2c1873..23b702c 100644 (file)
@@ -405,6 +405,7 @@ $specialPageAliases = array(
        'DeletedContributions'      => array( 'DeletedContributions' ),
        'Diff'                      => array( 'Diff' ),
        'DoubleRedirects'           => array( 'DoubleRedirects' ),
+       'EditTags'                  => array( 'EditTags' ),
        'EditWatchlist'             => array( 'EditWatchlist' ),
        'Emailuser'                 => array( 'EmailUser', 'Email' ),
        'ExpandTemplates'           => array( 'ExpandTemplates' ),
index a5ef762..7fc9997 100644 (file)
@@ -434,7 +434,10 @@ class TextPassDumperDatabaseTest extends DumpTestCase {
         * PHP extensions, we go for gzip instead, which triggers the same relevant code
         * paths while still being testable on more systems.
         *
+        * Broken per T70653.
+        *
         * @group large
+        * @group Broken
         */
        function testCheckpointGzip() {
                $this->checkHasGzip();
index 7e23e2f..5dcd941 100644 (file)
        /**
         * @param {Function[]} tasks List of functions that perform tasks
         *  that may be asynchronous. Invoke the callback parameter when done.
-        * @param {Function} done When all tasks are done.
+        * @param {Function} complete Called when all tasks are done, or when the sequence is aborted.
         * @return
         */
-       function process( tasks, done ) {
-               function run() {
+       function process( tasks, complete ) {
+               function abort() {
+                       tasks.splice( 0, tasks.length );
+                       next();
+               }
+               function next() {
+                       if ( !tasks ) {
+                               // This happens if after the process is completed, one of our callbacks is
+                               // invoked. This can happen if a test timed out but the process was still
+                               // running. In that case, ignore it. Don't invoke complete() a second time.
+                               return;
+                       }
                        var task = tasks.shift();
                        if ( task ) {
-                               task( run );
+                               task( next, abort );
                        } else {
-                               done();
+                               // Remove tasks list to indicate the process is final.
+                               tasks = null;
+                               complete();
                        }
                }
-               run();
+               next();
        }
 
        QUnit.test( 'Replace', 16, function ( assert ) {
        QUnit.test( 'Match PHP parser', mw.libs.phpParserData.tests.length, function ( assert ) {
                mw.messages.set( mw.libs.phpParserData.messages );
                var tasks = $.map( mw.libs.phpParserData.tests, function ( test ) {
-                       return function ( next ) {
+                       return function ( next, abort ) {
                                getMwLanguage( test.lang )
-                                       .done( function ( langClass ) {
+                                       .then( function ( langClass ) {
                                                mw.config.set( 'wgUserLanguage', test.lang );
                                                var parser = new mw.jqueryMsg.parser( { language: langClass } );
                                                assert.equal(
                                                        test.result,
                                                        test.name
                                                );
-                                       } )
-                                       .fail( function () {
+                                       }, function () {
                                                assert.ok( false, 'Language "' + test.lang + '" failed to load.' );
                                        } )
-                                       .always( next );
+                                       .then( next, abort );
                        };
                } );
 
                mw.messages.set( 'formatnum-msg', '{{formatnum:$1}}' );
                mw.messages.set( 'formatnum-msg-int', '{{formatnum:$1|R}}' );
                var queue = $.map( formatnumTests, function ( test ) {
-                       return function ( next ) {
+                       return function ( next, abort ) {
                                getMwLanguage( test.lang )
-                                       .done( function ( langClass ) {
+                                       .then( function ( langClass ) {
                                                mw.config.set( 'wgUserLanguage', test.lang );
                                                var parser = new mw.jqueryMsg.parser( { language: langClass } );
                                                assert.equal(
                                                        test.result,
                                                        test.description
                                                );
-                                       } )
-                                       .fail( function () {
+                                       }, function () {
                                                assert.ok( false, 'Language "' + test.lang + '" failed to load' );
                                        } )
-                                       .always( next );
+                                       .then( next, abort );
                        };
                } );
                QUnit.stop();