Merge "Build Special:Search's title without escaping"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 20 Dec 2013 20:01:58 +0000 (20:01 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 20 Dec 2013 20:01:58 +0000 (20:01 +0000)
19 files changed:
includes/DefaultSettings.php
includes/GlobalFunctions.php
includes/ProxyTools.php [deleted file]
includes/Setup.php
includes/api/ApiQueryRandom.php
includes/cache/LocalisationCache.php
includes/content/TextContent.php
includes/job/JobQueueGroup.php
includes/specials/SpecialRandompage.php
includes/specials/SpecialRedirect.php
includes/utils/IP.php
languages/messages/MessagesEn.php
languages/messages/MessagesQqq.php
maintenance/generateJsonI18n.php [new file with mode: 0644]
maintenance/language/messages.inc
maintenance/mergeMessageFileList.php
maintenance/nextJobDB.php
maintenance/showJobs.php
skins/common/commonPrint.css

index b1265c1..f0c0ee5 100644 (file)
@@ -5933,6 +5933,16 @@ $wgExtensionFunctions = array();
  * Variables defined in extensions will override conflicting variables defined
  * in the core.
  *
+ * Since MediaWiki 1.23, use of this variable to define messages is discouraged; instead, store
+ * messages in JSON format and use $wgMessagesDirs. For setting other variables than
+ * $messages, $wgExtensionMessagesFiles should still be used.
+ *
+ * If there is an entry in $wgMessagesDirs with the same key as one in
+ * $wgExtensionMessagesFiles, then any $messages variables set in the $wgExtensionMessagesFiles file
+ * will be ignored. This means an extension that only provides messages can be backwards compatible
+ * by using both $wgExtensionMessagesFiles and $wgMessagesDirs, and only one of the two
+ * will be used depending on what the version of MediaWiki supports.
+ *
  * @par Example:
  * @code
  *    $wgExtensionMessagesFiles['ConfirmEdit'] = __DIR__.'/ConfirmEdit.i18n.php';
@@ -5940,6 +5950,32 @@ $wgExtensionFunctions = array();
  */
 $wgExtensionMessagesFiles = array();
 
+/**
+ * Extension messages directories.
+ *
+ * Associative array mapping extension name to the path of the directory where message files can
+ * be found. The message files are expected to be JSON files named for their language code, e.g.
+ * en.json, de.json, etc. Extensions with messages in multiple places may specify an array of
+ * message directories.
+ *
+ * @par Simple example:
+ * @code
+ *    $wgMessagesDirs['ConfirmEdit'] = __DIR__ . '/i18n';
+ * @endcode
+ *
+ * @par Complex example:
+ * @code
+ *    $wgMessagesDirs['VisualEditor'] = array(
+ *        __DIR__ . '/i18n',
+ *        __DIR__ . '/modules/ve-core/i18n',
+ *        __DIR__ . '/modules/qunit/localisation',
+ *        __DIR__ . '/modules/oojs-ui/messages',
+ *    )
+ * @endcode
+ * @since 1.23
+ */
+$wgMessagesDirs = array();
+
 /**
  * Array of files with list(s) of extension entry points to be used in
  * maintenance/mergeMessageFileList.php
index 5a1ef6c..b2a8f5e 100644 (file)
@@ -4160,3 +4160,56 @@ function wfCanIPUseHTTPS( $ip ) {
        wfRunHooks( 'CanIPUseHTTPS', array( $ip, &$canDo ) );
        return !!$canDo;
 }
+
+/**
+ * Work out the IP address based on various globals
+ * For trusted proxies, use the XFF client IP (first of the chain)
+ *
+ * @deprecated in 1.19; call $wgRequest->getIP() directly.
+ * @return string
+ */
+function wfGetIP() {
+       wfDeprecated( __METHOD__, '1.19' );
+       global $wgRequest;
+       return $wgRequest->getIP();
+}
+
+/**
+ * Checks if an IP is a trusted proxy provider.
+ * Useful to tell if X-Forwarded-For data is possibly bogus.
+ * Squid cache servers for the site are whitelisted.
+ *
+ * @param $ip String
+ * @return bool
+ */
+function wfIsTrustedProxy( $ip ) {
+       $trusted = wfIsConfiguredProxy( $ip );
+       wfRunHooks( 'IsTrustedProxy', array( &$ip, &$trusted ) );
+       return $trusted;
+}
+
+/**
+ * Checks if an IP matches a proxy we've configured.
+ * @param $ip String
+ * @return bool
+ * @since 1.23 Supports CIDR ranges in $wgSquidServersNoPurge
+ */
+function wfIsConfiguredProxy( $ip ) {
+       global $wgSquidServers, $wgSquidServersNoPurge;
+
+       // quick check of known proxy servers
+       $trusted = in_array( $ip, $wgSquidServers )
+               || in_array( $ip, $wgSquidServersNoPurge );
+
+       if ( !$trusted ) {
+               // slightly slower check to see if the ip is listed directly or in a CIDR
+               // block in $wgSquidServersNoPurge
+               foreach ( $wgSquidServersNoPurge as $block ) {
+                       if ( strpos( $block, '/' ) !== false && IP::isInRange( $ip, $block ) ) {
+                               $trusted = true;
+                               break;
+                       }
+               }
+       }
+       return $trusted;
+}
diff --git a/includes/ProxyTools.php b/includes/ProxyTools.php
deleted file mode 100644 (file)
index a0f9e5f..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-<?php
-/**
- * Functions for dealing with proxies.
- *
- * 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
- */
-
-/**
- * Extracts the XFF string from the request header
- * Note: headers are spoofable
- *
- * @deprecated in 1.19; use $wgRequest->getHeader( 'X-Forwarded-For' ) instead.
- * @return string
- */
-function wfGetForwardedFor() {
-       wfDeprecated( __METHOD__, '1.19' );
-       global $wgRequest;
-       return $wgRequest->getHeader( 'X-Forwarded-For' );
-}
-
-/**
- * Returns the browser/OS data from the request header
- * Note: headers are spoofable
- *
- * @deprecated in 1.18; use $wgRequest->getHeader( 'User-Agent' ) instead.
- * @return string
- */
-function wfGetAgent() {
-       wfDeprecated( __METHOD__, '1.18' );
-       global $wgRequest;
-       return $wgRequest->getHeader( 'User-Agent' );
-}
-
-/**
- * Work out the IP address based on various globals
- * For trusted proxies, use the XFF client IP (first of the chain)
- *
- * @deprecated in 1.19; call $wgRequest->getIP() directly.
- * @return string
- */
-function wfGetIP() {
-       wfDeprecated( __METHOD__, '1.19' );
-       global $wgRequest;
-       return $wgRequest->getIP();
-}
-
-/**
- * Checks if an IP is a trusted proxy provider.
- * Useful to tell if X-Forwarded-For data is possibly bogus.
- * Squid cache servers for the site are whitelisted.
- *
- * @param $ip String
- * @return bool
- */
-function wfIsTrustedProxy( $ip ) {
-       $trusted = wfIsConfiguredProxy( $ip );
-       wfRunHooks( 'IsTrustedProxy', array( &$ip, &$trusted ) );
-       return $trusted;
-}
-
-/**
- * Checks if an IP matches a proxy we've configured.
- * @param $ip String
- * @return bool
- * @since 1.23 Supports CIDR ranges in $wgSquidServersNoPurge
- */
-function wfIsConfiguredProxy( $ip ) {
-       global $wgSquidServers, $wgSquidServersNoPurge;
-
-       // quick check of known proxy servers
-       $trusted = in_array( $ip, $wgSquidServers )
-               || in_array( $ip, $wgSquidServersNoPurge );
-
-       if ( !$trusted ) {
-               // slightly slower check to see if the ip is listed directly or in a CIDR
-               // block in $wgSquidServersNoPurge
-               foreach ( $wgSquidServersNoPurge as $block ) {
-                       if ( strpos( $block, '/' ) !== false && IP::isInRange( $ip, $block ) ) {
-                               $trusted = true;
-                               break;
-                       }
-               }
-       }
-       return $trusted;
-}
index eef5ccb..17d5446 100644 (file)
@@ -426,7 +426,6 @@ wfProfileOut( $fname . '-exception' );
 wfProfileIn( $fname . '-includes' );
 require_once "$IP/includes/normal/UtfNormalUtil.php";
 require_once "$IP/includes/GlobalFunctions.php";
-require_once "$IP/includes/ProxyTools.php";
 require_once "$IP/includes/normal/UtfNormalDefines.php";
 wfProfileOut( $fname . '-includes' );
 
index e990386..0d54ffe 100644 (file)
@@ -60,7 +60,6 @@ class ApiQueryRandom extends ApiQueryGeneratorBase {
                $this->addWhereFld( 'page_namespace', $namespace );
                $this->addWhereRange( 'page_random', 'newer', $randstr, null );
                $this->addWhereFld( 'page_is_redirect', $redirect );
-               $this->addOption( 'USE INDEX', 'page_random' );
                if ( is_null( $resultPageSet ) ) {
                        $this->addFields( array( 'page_id', 'page_title', 'page_namespace' ) );
                } else {
index ccb94a2..41f24dc 100644 (file)
@@ -527,6 +527,36 @@ class LocalisationCache {
                return $data;
        }
 
+       /**
+        * Read a JSON file containing localisation messages.
+        * @param string $fileName Name of file to read
+        * @throws MWException if there is a syntax error in the JSON file
+        * @return array with a 'messages' key, or empty array if the file doesn't exist
+        */
+       protected function readJSONFile( $fileName ) {
+               wfProfileIn( __METHOD__ );
+               if ( !is_readable( $fileName ) ) {
+                       return array();
+               }
+
+               $json = file_get_contents( $fileName );
+               if ( $json === false ) {
+                       return array();
+               }
+               $data = FormatJson::decode( $json, true );
+               if ( $data === null ) {
+                       throw new MWException( __METHOD__ . ": Invalid JSON file: $fileName" );
+               }
+               // Remove keys starting with '@', they're reserved for metadata and non-message data
+               foreach ( $data as $key => $unused ) {
+                       if ( $key === '' || $key[0] === '@' ) {
+                               unset( $data[$key] );
+                       }
+               }
+               // The JSON format only supports messages, none of the other variables, so wrap the data
+               return array( 'messages' => $data );
+       }
+
        /**
         * Get the compiled plural rules for a given language from the XML files.
         * @since 1.20
@@ -736,7 +766,7 @@ class LocalisationCache {
         * @throws MWException
         */
        public function recache( $code ) {
-               global $wgExtensionMessagesFiles;
+               global $wgExtensionMessagesFiles, $wgMessagesDirs;
                wfProfileIn( __METHOD__ );
 
                if ( !$code ) {
@@ -810,11 +840,33 @@ class LocalisationCache {
                # like site-specific message overrides.
                wfProfileIn( __METHOD__ . '-extensions' );
                $allData = $initialData;
-               foreach ( $wgExtensionMessagesFiles as $fileName ) {
+               foreach ( $wgMessagesDirs as $dirs ) {
+                       foreach ( (array)$dirs as $dir ) {
+                               foreach ( $codeSequence as $csCode ) {
+                                       $fileName = "$dir/$csCode.json";
+                                       $data = $this->readJSONFile( $fileName );
+
+                                       foreach ( $data as $key => $item ) {
+                                               $this->mergeItem( $key, $allData[$key], $item );
+                                       }
+
+                                       $deps[] = new FileDependency( $fileName );
+                               }
+                       }
+               }
+
+               foreach ( $wgExtensionMessagesFiles as $extension => $fileName ) {
                        $data = $this->readPHPFile( $fileName, 'extension' );
                        $used = false;
 
                        foreach ( $data as $key => $item ) {
+                               if ( $key === 'messages' && isset( $wgMessagesDirs[$extension] ) ) {
+                                       # For backwards compatibility, ignore messages from extensions in
+                                       # $wgExtensionMessagesFiles that are also present in $wgMessagesDirs.
+                                       # This allows extensions to use both and be backwards compatible.
+                                       # Variables other than $messages still need to be supported though.
+                                       continue;
+                               }
                                if ( $this->mergeExtensionItem( $codeSequence, $key, $allData[$key], $item ) ) {
                                        $used = true;
                                }
@@ -833,6 +885,7 @@ class LocalisationCache {
 
                # Add cache dependencies for any referenced globals
                $deps['wgExtensionMessagesFiles'] = new GlobalDependency( 'wgExtensionMessagesFiles' );
+               $deps['wgMessagesDirs'] = new GlobalDependency( 'wgMessagesDirs' );
                $deps['version'] = new ConstantDependency( 'MW_LC_VERSION' );
 
                # Add dependencies to the cache entry
index ffb5cf2..068b150 100644 (file)
@@ -38,7 +38,7 @@ class TextContent extends AbstractContent {
 
                if ( $text === null || $text === false ) {
                        wfWarn( "TextContent constructed with \$text = " . var_export( $text, true ) . "! "
-                               . "This may indicate an error in the caller's scope." );
+                               . "This may indicate an error in the caller's scope.", 2 );
 
                        $text = '';
                }
index cf0c290..9d206a3 100644 (file)
@@ -155,9 +155,6 @@ class JobQueueGroup {
         */
        public function pop( $qtype = self::TYPE_DEFAULT, $flags = 0 ) {
                if ( is_string( $qtype ) ) { // specific job type
-                       if ( ( $flags & self::USE_PRIORITY ) && $this->isQueueDeprioritized( $qtype ) ) {
-                               return false; // back off
-                       }
                        $job = $this->get( $qtype )->pop();
                        if ( !$job ) {
                                JobQueueAggregator::singleton()->notifyQueueEmpty( $this->wiki, $qtype );
@@ -180,9 +177,6 @@ class JobQueueGroup {
                        shuffle( $types ); // avoid starvation
 
                        foreach ( $types as $type ) { // for each queue...
-                               if ( ( $flags & self::USE_PRIORITY ) && $this->isQueueDeprioritized( $type ) ) {
-                                       continue; // back off
-                               }
                                $job = $this->get( $type )->pop();
                                if ( $job ) { // found
                                        return $job;
@@ -330,32 +324,6 @@ class JobQueueGroup {
                return $this->coalescedQueues;
        }
 
-       /**
-        * Check if jobs should not be popped of a queue right now.
-        * This is only used for performance, such as to avoid spamming
-        * the queue with many sub-jobs before they actually get run.
-        *
-        * @param string $type
-        * @return bool
-        */
-       public function isQueueDeprioritized( $type ) {
-               if ( $this->cache->has( 'isDeprioritized', $type, 5 ) ) {
-                       return $this->cache->get( 'isDeprioritized', $type );
-               }
-               if ( $type === 'refreshLinks2' ) {
-                       // Don't keep converting refreshLinksPartition => refreshLinks jobs if the
-                       // later jobs have not been done yet. This helps throttle queue spam.
-                       // @TODO: this is mostly a WMF-specific hack and should be removed when
-                       // refreshLinks2 jobs are drained.
-                       $deprioritized = $this->get( 'refreshLinks' )->getSize() > 10000;
-                       $this->cache->set( 'isDeprioritized', $type, $deprioritized );
-
-                       return $deprioritized;
-               }
-
-               return false;
-       }
-
        /**
         * Execute any due periodic queue maintenance tasks for all queues.
         *
index c94d2b3..086193e 100644 (file)
@@ -144,7 +144,6 @@ class RandomPage extends SpecialPage {
                        ), $this->extra ),
                        'options' => array(
                                'ORDER BY' => 'page_random',
-                               'USE INDEX' => 'page_random',
                                'LIMIT' => 1,
                        ),
                        'join_conds' => array()
index f05dacb..8ec6f85 100644 (file)
@@ -135,6 +135,25 @@ class SpecialRedirect extends FormSpecialPage {
                ) );
        }
 
+       /**
+        * Handle Special:Redirect/page/xxx (by redirecting to index.php?curid=xxx)
+        *
+        * @return string|null url to redirect to, or null if $mValue is invalid.
+        */
+       function dispatchPage() {
+               $curid = $this->mValue;
+               if ( !ctype_digit( $curid ) ) {
+                       return null;
+               }
+               $curid = (int)$curid;
+               if ( $curid === 0 ) {
+                       return null;
+               }
+               return wfAppendQuery( wfScript( 'index' ), array(
+                       'curid' => $curid
+               ) );
+       }
+
        /**
         * Use appropriate dispatch* method to obtain a redirection URL,
         * and either: redirect, set a 404 error code and error message,
@@ -155,6 +174,9 @@ class SpecialRedirect extends FormSpecialPage {
                case 'revision':
                        $url = $this->dispatchRevision();
                        break;
+               case 'page':
+                       $url = $this->dispatchPage();
+                       break;
                default:
                        $this->getOutput()->setStatusCode( 404 );
                        $url = null;
@@ -177,8 +199,10 @@ class SpecialRedirect extends FormSpecialPage {
                $mp = $this->getMessagePrefix();
                $ns = array(
                        // subpage => message
-                       // Messages: redirect-user, redirect-revision, redirect-file
+                       // Messages: redirect-user, redirect-page, redirect-revision,
+                       // redirect-file
                        'user' => $mp . '-user',
+                       'page' => $mp . '-page',
                        'revision' => $mp . '-revision',
                        'file' => $mp . '-file',
                );
index 002dcd9..2686e11 100644 (file)
@@ -367,7 +367,6 @@ class IP {
        /**
         * Determine if an IP address really is an IP address, and if it is public,
         * i.e. not RFC 1918 or similar
-        * Comes from ProxyTools.php
         *
         * @param $ip String
         * @return Boolean
@@ -482,7 +481,6 @@ class IP {
        /**
         * Given an IP address in dotted-quad/octet notation, returns an unsigned integer.
         * Like ip2long() except that it actually works and has a consistent error return value.
-        * Comes from ProxyTools.php
         *
         * @param string $ip quad dotted IP address.
         * @return Mixed: string/int/false
index a4c2056..f2ca82c 100644 (file)
@@ -4839,15 +4839,16 @@ You should have received [{{SERVER}}{{SCRIPTPATH}}/COPYING a copy of the GNU Gen
 'version-entrypoints-load-php'          => '[https://www.mediawiki.org/wiki/Manual:load.php load.php]', # do not translate or duplicate this message to other languages
 
 # Special:Redirect
-'redirect'            => 'Redirect by file, user, or revision ID',
+'redirect'            => 'Redirect by file, user, page or revision ID',
 'redirect-legend'     => 'Redirect to a file or page',
 'redirect-text'       => '', # do not translate or duplicate this message to other languages
-'redirect-summary'    => 'This special page redirects to a file (given the file name), a page (given a revision ID), or a user page (given a numeric user ID). Usage: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], or [[{{#Special:Redirect}}/user/101]].',
+'redirect-summary'    => 'This special page redirects to a file (given the file name), a page (given a revision ID or page ID), or a user page (given a numeric user ID). Usage: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/page/64308]], [[{{#Special:Redirect}}/revision/328429]], or [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit'     => 'Go',
 'redirect-lookup'     => 'Lookup:',
 'redirect-value'      => 'Value:',
 'redirect-user'       => 'User ID',
 'redirect-revision'   => 'Page revision',
+'redirect-page'       => 'Page ID',
 'redirect-file'       => 'File name',
 'redirect-not-exists' => 'Value not found',
 
index 1e2bc6c..8a6a2c8 100644 (file)
@@ -9959,7 +9959,7 @@ A short description of the script path entry point. Links to the mediawiki.org d
 
 # Special:Redirect
 'redirect' => "{{doc-special|Redirect}}
-This means \"Redirect by file'''name''', user '''ID''', or revision ID\".",
+This means \"Redirect by file'''name''', user '''ID''', page ID, or revision ID\".",
 'redirect-legend' => 'Legend of fieldset around input box in [[Special:Redirect]]',
 'redirect-summary' => 'Shown at top of [[Special:Redirect]]',
 'redirect-submit' => 'Button label in [[Special:Redirect]].
@@ -9968,12 +9968,14 @@ This means \"Redirect by file'''name''', user '''ID''', or revision ID\".",
 
 Followed by the select box which has the following options:
 * {{msg-mw|Redirect-user}}
+* {{msg-mw|Redirect-page}}
 * {{msg-mw|Redirect-revision}}
 * {{msg-mw|Redirect-file}}',
 'redirect-value' => 'Second field label in [[Special:Redirect]]
 {{Identical|Value}}',
 'redirect-user' => 'Description of lookup type for [[Special:Redirect]].
 {{Identical|User ID}}',
+'redirect-page' => 'Description of lookup type for [[Special:Redirect]].',
 'redirect-revision' => "Description of lookup type for [[Special:Redirect]].
 
 This means \"Page revision '''ID'''\".",
diff --git a/maintenance/generateJsonI18n.php b/maintenance/generateJsonI18n.php
new file mode 100644 (file)
index 0000000..f3f8943
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+
+/**
+ * Convert a PHP messages file to a set of JSON messages files.
+ *
+ * Usage:
+ *    php generateJsonI18n.php ExtensionName.i18n.php i18n/
+ *
+ * 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 Maintenance
+ */
+
+require_once __DIR__ . '/Maintenance.php';
+
+/**
+ * Maintenance script to generate JSON i18n files from a PHP i18n file.
+ *
+ * @ingroup Maintenance
+ */
+class GenerateJsonI18n extends Maintenance {
+       public function __construct() {
+               parent::__construct();
+               $this->mDescription = "Build JSON messages files from a PHP messages file";
+               $this->addArg( 'phpfile', 'PHP file defining a $messages array', true );
+               $this->addArg( 'jsondir', 'Directory to write JSON files to', true );
+               $this->addOption( 'langcode', 'Language code; only needed for converting core i18n files',
+                       false, true );
+       }
+
+       public function execute() {
+               $phpfile = $this->getArg( 0 );
+               $jsondir = $this->getArg( 1 );
+
+               if ( !is_readable( $phpfile ) ) {
+                       $this->error( "Error reading $phpfile\n", 1 );
+               }
+               include $phpfile;
+               $phpfileContents = file_get_contents( $phpfile );
+
+               if ( !isset( $messages ) ) {
+                       $this->error( "PHP file $phpfile does not define \$messages array\n", 1 );
+               }
+
+               $extensionStyle = true;
+               if ( !isset( $messages['en'] ) || !is_array( $messages['en'] ) ) {
+                       if ( !$this->hasOption( 'langcode' ) ) {
+                               $this->error( "PHP file $phpfile does not set language codes, --langcode " .
+                                       "is required.\n", 1 );
+                       }
+                       $extensionStyle = false;
+                       $langcode = $this->getOption( 'langcode' );
+                       $messages = array( $langcode => $messages );
+               } else if ( $this->hasOption( 'langcode' ) ) {
+                       $this->output( "Warning: --langcode option set but will not be used.\n" );
+               }
+
+               foreach ( $messages as $langcode => $langmsgs ) {
+                       $authors = $this->getAuthorsFromComment( $this->findCommentBefore(
+                               $extensionStyle ? "\$messages['$langcode'] =" : '$messages =',
+                               $phpfileContents
+                       ) );
+                       // Make sure the @metadata key is the first key in the output
+                       $langmsgs = array_merge(
+                               array( '@metadata' => array( 'authors' => $authors ) ),
+                               $langmsgs
+                       );
+
+                       $jsonfile = "$jsondir/$langcode.json";
+                       $success = file_put_contents(
+                               $jsonfile,
+                               FormatJson::encode( $langmsgs, true, FormatJson::ALL_OK )
+                       );
+                       if ( $success === false ) {
+                               $this->error( "FAILED to write $jsonfile", 1 );
+                       }
+                       $this->output( "$jsonfile\n" );
+               }
+               $this->output( "All done.\n" );
+       }
+
+       /**
+        * Find the documentation comment immediately before a given search string
+        * @param string $needle String to search for
+        * @param string $haystack String to search in
+        * @return string Substring of $haystack starting at '/**' ending right before $needle, or empty
+        */
+       protected function findCommentBefore( $needle, $haystack ) {
+               $needlePos = strpos( $haystack, $needle );
+               if ( $needlePos === false ) {
+                       return '';
+               }
+               // Need to pass a negative offset to strrpos() so it'll search backwards from the
+               // offset
+               $startPos = strrpos( $haystack, '/**', $needlePos - strlen( $haystack ) );
+               if ( $startPos === false ) {
+                       return '';
+               }
+
+               return substr( $haystack, $startPos, $needlePos - $startPos );
+       }
+
+       /**
+        * Get an array of author names from a documentation comment containing @author declarations.
+        * @param string $comment Documentation comment
+        * @return Array of author names (strings)
+        */
+       protected function getAuthorsFromComment( $comment ) {
+               $matches = null;
+               preg_match_all( '/@author (.*?)$/m', $comment, $matches );
+               return $matches && $matches[1] ? $matches[1] : array();
+       }
+}
+
+$maintClass = "GenerateJsonI18n";
+require_once RUN_MAINTENANCE_IF_MAIN;
index dab977e..662c3d3 100644 (file)
@@ -3686,6 +3686,7 @@ $wgMessageStructure = array(
                'redirect-lookup',
                'redirect-value',
                'redirect-user',
+               'redirect-page',
                'redirect-revision',
                'redirect-file',
                'redirect-not-exists',
index 5bf04c6..a63c45b 100644 (file)
@@ -46,8 +46,8 @@ class MergeMessageFileList extends Maintenance {
                $this->addOption( 'list-file', 'A file containing a list of extension setup files, one per line.', false, true );
                $this->addOption( 'extensions-dir', 'Path where extensions can be found.', false, true );
                $this->addOption( 'output', 'Send output to this file (omit for stdout)', false, true );
-               $this->mDescription = 'Merge $wgExtensionMessagesFiles from various extensions to produce a ' .
-                       'single array containing all message files.';
+               $this->mDescription = 'Merge $wgExtensionMessagesFiles and $wgMessagesDirs from ' .
+                       ' various extensions to produce a single file listing all message files and dirs.';
        }
 
        public function execute() {
@@ -158,7 +158,8 @@ $s =
        "<" . "?php\n" .
        "## This file is generated by mergeMessageFileList.php. Do not edit it directly.\n\n" .
        "if ( defined( 'MW_NO_EXTENSION_MESSAGES' ) ) return;\n\n" .
-       '$wgExtensionMessagesFiles = ' . var_export( $wgExtensionMessagesFiles, true ) . ";\n\n";
+       '$wgExtensionMessagesFiles = ' . var_export( $wgExtensionMessagesFiles, true ) . ";\n\n" .
+       '$wgMessagesDirs = ' . var_export( $wgMessagesDirs, true ) . ";\n\n";
 
 $dirs = array(
        $IP,
index 06ccccb..9b4d194 100644 (file)
@@ -78,10 +78,6 @@ class NextJobDB extends Maintenance {
                        }
 
                        list( $type, $db ) = $candidates[mt_rand( 0, count( $candidates ) - 1 )];
-                       if ( JobQueueGroup::singleton( $db )->isQueueDeprioritized( $type ) ) {
-                               $pendingDBs[$type] = array_diff( $pendingDBs[$type], array( $db ) );
-                               $again = true;
-                       }
                } while ( $again );
 
                if ( $this->hasOption( 'types' ) ) {
index afd7c74..5e21130 100644 (file)
@@ -56,14 +56,16 @@ class ShowJobs extends Maintenance {
                } elseif ( $this->hasOption( 'group' ) ) {
                        foreach ( $group->getQueueTypes() as $type ) {
                                $queue = $group->get( $type );
+                               $delayed = $queue->getDelayedCount();
                                $pending = $queue->getSize();
                                $claimed = $queue->getAcquiredCount();
                                $abandoned = $queue->getAbandonedCount();
                                $active = max( 0, $claimed - $abandoned );
-                               if ( ( $pending + $claimed ) > 0 ) {
+                               if ( ( $pending + $claimed + $delayed ) > 0 ) {
                                        $this->output(
                                                "{$type}: $pending queued; " .
-                                               "$claimed claimed ($active active, $abandoned abandoned)\n"
+                                               "$claimed claimed ($active active, $abandoned abandoned); " .
+                                               "$delayed delayed\n"
                                        );
                                }
                        }
index c7804a0..2d45653 100644 (file)
@@ -17,6 +17,12 @@ a.new {
        border: 1px solid #aaaaaa;
        background-color: #f9f9f9;
        padding: 5px;
+       display: -moz-inline-block;
+       display: inline-block;
+       display: table;
+       /* IE7 and earlier */
+       zoom: 1;
+       *display: inline;
 }
 
 /* images */