Merge "Add release-notes for message escaping"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Sun, 29 Mar 2015 03:25:13 +0000 (03:25 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Sun, 29 Mar 2015 03:25:13 +0000 (03:25 +0000)
22 files changed:
.jshintrc
autoload.php
includes/LinkFilter.php
includes/Setup.php
includes/api/ApiStashEdit.php
includes/api/i18n/en.json
includes/debug/logger/monolog/SamplingHandler.php [deleted file]
includes/resourceloader/ResourceLoaderSpecialCharacterDataModule.php [new file with mode: 0644]
includes/specials/SpecialUserlogin.php
languages/i18n/en.json
languages/i18n/qqq.json
maintenance/jsduck/categories.json
maintenance/jsduck/eg-iframe.html
package.json
resources/Resources.php
resources/src/mediawiki.language/mediawiki.language.js
resources/src/mediawiki.language/specialcharacters.json [new file with mode: 0644]
resources/src/mediawiki/mediawiki.errorLogger.js [new file with mode: 0644]
resources/src/mediawiki/mediawiki.js
tests/phpunit/includes/TitleTest.php
tests/qunit/QUnitTestResources.php
tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js [new file with mode: 0644]

index d77ffb8..4bb2440 100644 (file)
--- a/.jshintrc
+++ b/.jshintrc
@@ -22,6 +22,7 @@
                "mediaWiki": true,
                "JSON": true,
                "jQuery": false,
-               "QUnit": false
+               "QUnit": false,
+               "sinon": false
        }
 }
index e0b91df..dcd7879 100644 (file)
@@ -705,7 +705,6 @@ $wgAutoloadLocalClasses = array(
        'MWLoggerMonologHandler' => __DIR__ . '/includes/debug/logger/monolog/Handler.php',
        'MWLoggerMonologLegacyFormatter' => __DIR__ . '/includes/debug/logger/monolog/LegacyFormatter.php',
        'MWLoggerMonologProcessor' => __DIR__ . '/includes/debug/logger/monolog/Processor.php',
-       'MWLoggerMonologSamplingHandler' => __DIR__ . '/includes/debug/logger/monolog/SamplingHandler.php',
        'MWLoggerMonologSpi' => __DIR__ . '/includes/debug/logger/monolog/Spi.php',
        'MWLoggerMonologSyslogHandler' => __DIR__ . '/includes/debug/logger/monolog/SyslogHandler.php',
        'MWLoggerNullSpi' => __DIR__ . '/includes/debug/logger/NullSpi.php',
@@ -989,6 +988,7 @@ $wgAutoloadLocalClasses = array(
        'ResourceLoaderModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderModule.php',
        'ResourceLoaderSiteModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderSiteModule.php',
        'ResourceLoaderSkinModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderSkinModule.php',
+       'ResourceLoaderSpecialCharacterDataModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderSpecialCharacterDataModule.php',
        'ResourceLoaderStartUpModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderStartUpModule.php',
        'ResourceLoaderUserCSSPrefsModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserCSSPrefsModule.php',
        'ResourceLoaderUserDefaultsModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserDefaultsModule.php',
index 340ae8f..99aaaa0 100644 (file)
@@ -92,7 +92,7 @@ class LinkFilter {
         * @return array Array to be passed to DatabaseBase::buildLike() or false on error
         */
        public static function makeLikeArray( $filterEntry, $protocol = 'http://' ) {
-               $db = wfGetDB( DB_MASTER );
+               $db = wfGetDB( DB_SLAVE );
 
                $target = $protocol . $filterEntry;
                $bits = wfParseUrl( $target );
index 7c0c7c4..e281768 100644 (file)
@@ -362,10 +362,15 @@ if ( $wgMetaNamespace === false ) {
        $wgMetaNamespace = str_replace( ' ', '_', $wgSitename );
 }
 
-// Default value is either the suhosin limit or -1 for unlimited
+// Default value is 2000 or the suhosin limit if it is between 1 and 2000
 if ( $wgResourceLoaderMaxQueryLength === false ) {
-       $maxValueLength = ini_get( 'suhosin.get.max_value_length' );
-       $wgResourceLoaderMaxQueryLength = $maxValueLength > 0 ? $maxValueLength : -1;
+       $suhosinMaxValueLength = (int) ini_get( 'suhosin.get.max_value_length' );
+       if ( $suhosinMaxValueLength > 0 && $suhosinMaxValueLength < 2000 ) {
+               $wgResourceLoaderMaxQueryLength = $suhosinMaxValueLength;
+       } else {
+               $wgResourceLoaderMaxQueryLength = 2000;
+       }
+       unset($suhosinMaxValueLength);
 }
 
 /**
index 3457670..c4b717c 100644 (file)
  * @since 1.25
  */
 class ApiStashEdit extends ApiBase {
+       const ERROR_NONE = 'stashed';
+       const ERROR_PARSE = 'error_parse';
+       const ERROR_CACHE = 'error_cache';
+       const ERROR_UNCACHEABLE = 'uncacheable';
+
        public function execute() {
                global $wgMemc;
 
@@ -105,41 +110,56 @@ class ApiStashEdit extends ApiBase {
                $key = self::getStashKey( $title, $content, $user );
                // De-duplicate requests on the same key
                if ( $user->pingLimiter( 'stashedit' ) ) {
-                       $editInfo = false;
                        $status = 'ratelimited';
                } elseif ( $wgMemc->lock( $key, 0, 30 ) ) {
-                       $format = $content->getDefaultFormat();
-                       $editInfo = $page->prepareContentForEdit( $content, null, $user, $format, false );
-                       $status = 'error'; // default
                        $unlocker = new ScopedCallback( function() use ( $key ) {
                                global $wgMemc;
                                $wgMemc->unlock( $key );
                        } );
+                       $status = self::parseAndStash( $page, $content, $user );
                } else {
-                       $editInfo = false;
                        $status = 'busy';
                }
 
+               $this->getResult()->addValue( null, $this->getModuleName(), array( 'status' => $status ) );
+       }
+
+       /**
+        * @param WikiPage $page
+        * @param Content $content
+        * @param User $user
+        * @return integer ApiStashEdit::ERROR_* constant
+        * @since 1.25
+        */
+       public static function parseAndStash( WikiPage $page, Content $content, User $user ) {
+               global $wgMemc;
+
+               $format = $content->getDefaultFormat();
+               $editInfo = $page->prepareContentForEdit( $content, null, $user, $format, false );
+
                if ( $editInfo && $editInfo->output ) {
+                       $key = self::getStashKey( $page->getTitle(), $content, $user );
+
                        list( $stashInfo, $ttl ) = self::buildStashValue(
                                $editInfo->pstContent, $editInfo->output, $editInfo->timestamp
                        );
+
                        if ( $stashInfo ) {
                                $ok = $wgMemc->set( $key, $stashInfo, $ttl );
                                if ( $ok ) {
-                                       $status = 'stashed';
                                        wfDebugLog( 'StashEdit', "Cached parser output for key '$key'." );
+                                       return self::ERROR_NONE;
                                } else {
-                                       $status = 'error';
                                        wfDebugLog( 'StashEdit', "Failed to cache parser output for key '$key'." );
+                                       return self::ERROR_CACHE;
                                }
                        } else {
-                               $status = 'uncacheable';
                                wfDebugLog( 'StashEdit', "Uncacheable parser output for key '$key'." );
+                               return self::ERROR_UNCACHEABLE;
                        }
                }
 
-               $this->getResult()->addValue( null, $this->getModuleName(), array( 'status' => $status ) );
+               return self::ERROR_PARSE;
        }
 
        /**
index 1c6af66..9d0663c 100644 (file)
        "apihelp-paraminfo-param-querymodules": "List of query module names (value of <var>prop</var>, <var>meta</var> or <var>list</var> parameter). Use <kbd>$1modules=query+foo</kbd> instead of <kbd>$1querymodules=foo</kbd>.",
        "apihelp-paraminfo-param-mainmodule": "Get information about the main (top-level) module as well. Use <kbd>$1modules=main</kbd> instead.",
        "apihelp-paraminfo-param-pagesetmodule": "Get information about the pageset module (providing titles= and friends) as well.",
-       "apihelp-paraminfo-param-formatmodules": "List of format module names (value of <var>format</var> parameter). Use <var>$1modules</kbd> instead.",
+       "apihelp-paraminfo-param-formatmodules": "List of format module names (value of <var>format</var> parameter). Use <var>$1modules</var> instead.",
        "apihelp-paraminfo-example-1": "Show info for <kbd>[[Special:ApiHelp/parse|action=parse]]</kbd>, <kbd>[[Special:ApiHelp/jsonfm|format=jsonfm]]</kbd>, <kbd>[[Special:ApiHelp/query+allpages|action=query&list=allpages]]</kbd>, and <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd>.",
 
        "apihelp-parse-description": "Parses content and returns parser output.\n\nSee the various prop-modules of <kbd>[[Special:ApiHelp/query|action=query]]</kbd> to get information from the current version of a page.\n\nThere are several ways to specify the text to parse:\n# Specify a page or revision, using <var>$1page</var>, <var>$1pageid</var>, or <var>$1oldid</var>.\n# Specify content explicitly, using <var>$1text</var>, <var>$1title</var>, and <var>$1contentmodel</var>.\n# Specify only a summary to parse. <var>$1prop</var> should be given an empty value.",
diff --git a/includes/debug/logger/monolog/SamplingHandler.php b/includes/debug/logger/monolog/SamplingHandler.php
deleted file mode 100644 (file)
index a9f83b0..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-<?php
-/**
- * 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
- */
-
-use Monolog\Handler\HandlerInterface;
-use Monolog\Formatter\FormatterInterface;
-
-/**
- * Wrapper for another HandlerInterface that will only handle a percentage of
- * records offered to it.
- *
- * When HandlerInterface::handle() is called for a given record, it will be
- * handled or ignored with a one in N chance based on the sample factor given
- * for the handler.
- *
- * Usage with MWLoggerMonologSpi:
- * @code
- * $wgMWLoggerDefaultSpi = array(
- *   'class' => 'MWLoggerMonologSpi',
- *   'args' => array( array(
- *     'handlers' => array(
- *       'some-handler' => array( ... ),
- *       'sampled-some-handler' => array(
- *         'class' => 'MWLoggerMonologSamplingHandler',
- *         'args' => array(
- *           function() {
- *             return MWLoggerFactory::getProvider()->getHandler( 'some-handler');
- *           },
- *           2, // emit logs with a 1:2 chance
- *         ),
- *       ),
- *     ),
- *   ) ),
- * );
- * @endcode
- *
- * A sampled event stream can be useful for logging high frequency events in
- * a production environment where you only need an idea of what is happening
- * and are not concerned with capturing every occurence. Since the decision to
- * handle or not handle a particular event is determined randomly, the
- * resulting sampled log is not guaranteed to contain 1/N of the events that
- * occurred in the application but based on [[Law of large numbers]] it will
- * tend to be close to this ratio with a large number of attempts.
- *
- * @since 1.25
- * @author Bryan Davis <bd808@wikimedia.org>
- * @copyright © 2014 Bryan Davis and Wikimedia Foundation.
- */
-class MWLoggerMonologSamplingHandler implements HandlerInterface {
-
-       /**
-        * @var HandlerInterface $delegate
-        */
-       protected $delegate;
-
-       /**
-        * @var int $factor
-        */
-       protected $factor;
-
-       /**
-        * @param HandlerInterface $handler Wrapped handler
-        * @param int $factor Sample factor
-        */
-       public function __construct( HandlerInterface $handler, $factor ) {
-               $this->delegate = $handler;
-               $this->factor = $factor;
-       }
-
-       public function isHandling( array $record ) {
-               return $this->delegate->isHandling( $record );
-       }
-
-       public function handle( array $record ) {
-               if ( $this->isHandling( $record )
-                       && mt_rand( 1, $this->factor ) === 1
-               ) {
-                       return $this->delegate->handle( $record );
-               }
-               return false;
-       }
-
-       public function handleBatch( array $records ) {
-               foreach ( $records as $record ) {
-                       $this->handle( $record );
-               }
-       }
-
-       public function pushProcessor( $callback ) {
-               $this->delegate->pushProcessor( $callback );
-               return $this;
-       }
-
-       public function popProcessor() {
-               return $this->delegate->popProcessor();
-       }
-
-       public function setFormatter( FormatterInterface $formatter ) {
-               $this->delegate->setFormatter( $formatter );
-               return $this;
-       }
-
-       public function getFormatter() {
-               return $this->delegate->getFormatter();
-       }
-
-}
diff --git a/includes/resourceloader/ResourceLoaderSpecialCharacterDataModule.php b/includes/resourceloader/ResourceLoaderSpecialCharacterDataModule.php
new file mode 100644 (file)
index 0000000..5c91709
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+/**
+ * Resource loader module for populating special characters data for some
+ * editing extensions to use.
+ *
+ * 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
+ */
+
+/**
+ * Resource loader module for populating special characters data for some
+ * editing extensions to use.
+ */
+class ResourceLoaderSpecialCharacterDataModule extends ResourceLoaderModule {
+       private $path = "resources/src/mediawiki.language/specialcharacters.json";
+       protected $targets = array( 'desktop', 'mobile' );
+
+       /**
+        * Get all the dynamic data.
+        *
+        * @return array
+        */
+       protected function getData() {
+               return json_decode( file_get_contents( $this->path ) );
+       }
+
+       /**
+        * @param ResourceLoaderContext $context
+        * @return string JavaScript code
+        */
+       public function getScript( ResourceLoaderContext $context ) {
+               return Xml::encodeJsCall(
+                       'mw.language.setSpecialCharacters',
+                       array(
+                               $this->getData()
+                       ),
+                       ResourceLoader::inDebugMode()
+               );
+       }
+
+       /**
+        * @param ResourceLoaderContext $context
+        * @return int UNIX timestamp
+        */
+       public function getModifiedTime( ResourceLoaderContext $context ) {
+               return static::safeFilemtime( $this->path );
+       }
+
+       /**
+        * @param ResourceLoaderContext $context
+        * @return string Hash
+        */
+       public function getModifiedHash( ResourceLoaderContext $context ) {
+               return md5( serialize( $this->getData() ) );
+       }
+
+       /**
+        * @return array
+        */
+       public function getDependencies() {
+               return array( 'mediawiki.language' );
+       }
+
+       /**
+        * @return array
+        */
+       public function getMessages() {
+               return array(
+                       'special-characters-group-latin',
+                       'special-characters-group-latinextended',
+                       'special-characters-group-ipa',
+                       'special-characters-group-symbols',
+                       'special-characters-group-greek',
+                       'special-characters-group-cyrillic',
+                       'special-characters-group-arabic',
+                       'special-characters-group-arabicextended',
+                       'special-characters-group-persian',
+                       'special-characters-group-hebrew',
+                       'special-characters-group-bangla',
+                       'special-characters-group-tamil',
+                       'special-characters-group-telugu',
+                       'special-characters-group-sinhala',
+                       'special-characters-group-devanagari',
+                       'special-characters-group-gujarati',
+                       'special-characters-group-thai',
+                       'special-characters-group-lao',
+                       'special-characters-group-khmer',
+                       'special-characters-title-endash',
+                       'special-characters-title-emdash',
+                       'special-characters-title-minus'
+               );
+       }
+}
index f2f6b69..44240d8 100644 (file)
@@ -916,7 +916,7 @@ class LoginForm extends SpecialPage {
                        case self::SUCCESS:
                                # We've verified now, update the real record
                                $user = $this->getUser();
-                               $user->invalidateCache();
+                               $user->touch();
 
                                if ( $user->requiresHTTPS() ) {
                                        $this->mStickHTTPS = true;
index be318e6..f81567f 100644 (file)
        "json-error-inf-or-nan": "One or more NAN or INF values in the value to be encoded",
        "json-error-unsupported-type": "A value of a type that cannot be encoded was given",
        "headline-anchor-title": "Link to this section",
-       "section-symbol": "§"
+       "special-characters-group-latin": "Latin",
+       "special-characters-group-latinextended": "Latin extended",
+       "special-characters-group-ipa": "IPA",
+       "special-characters-group-symbols": "Symbols",
+       "special-characters-group-greek": "Greek",
+       "special-characters-group-cyrillic": "Cyrillic",
+       "special-characters-group-arabic": "Arabic",
+       "special-characters-group-arabicextended": "Arabic extended",
+       "special-characters-group-persian": "Persian",
+       "special-characters-group-hebrew": "Hebrew",
+       "special-characters-group-bangla": "Bangla",
+       "special-characters-group-tamil": "Tamil",
+       "special-characters-group-telugu": "Telugu",
+       "special-characters-group-sinhala": "Sinhala",
+       "special-characters-group-gujarati": "Gujarati",
+       "special-characters-group-devanagari": "Devanagari",
+       "special-characters-group-thai": "Thai",
+       "special-characters-group-lao": "Lao",
+       "special-characters-group-khmer": "Khmer",
+       "special-characters-title-endash": "en dash",
+       "special-characters-title-emdash": "em dash",
+       "special-characters-title-minus": "minus sign"
 }
index ffd48c7..ad4981e 100644 (file)
        "json-error-utf8": "User error message when there are malformed UTF-8 characters, possibly incorrectly encoded.\nSee http://php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
        "json-error-recursion": "PHP JSON encoding/decoding error. See http://php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
        "json-error-inf-or-nan": "PHP JSON encoding/decoding error. See http://php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
-       "json-error-unsupported-type": "PHP JSON encoding/decoding error. See http://php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}"
+       "json-error-unsupported-type": "PHP JSON encoding/decoding error. See http://php.net/manual/en/function.json-last-error.php\n{{Related|Json-error}}",
+       "headline-anchor-title": "Title tooltip for the section anchor symbol",
+       "special-characters-group-latin": "This is the name of a script, or alphabet, not a language.",
+       "special-characters-group-latinextended": "The name of the Latin Extended character set.",
+       "special-characters-group-ipa": "IPA means a script: \"international phonetic alphabet\" here, and not \"international phonetic association\", the organization behind it.",
+       "special-characters-group-symbols": "The section name for symbols\n\n{{Identical|Symbol}}",
+       "special-characters-group-greek": "This is the name of a script, or alphabet, not a language.",
+       "special-characters-group-cyrillic": "This is the name of a script, or a group of alphabets, used mainly in Eastern Europe and North and Central Asia.",
+       "special-characters-group-arabic": "This is the name of a script, or alphabet, not a language.",
+       "special-characters-group-arabicextended": "This is a description of the additional group of Arabic script characters for languages such as a Persian, Urdu, Pashto and others. This message is supposed to be similar to {{msg-mw|special-characters-group-latinextended}}.",
+       "special-characters-group-persian": "The name of the Persian character set (alphabet).",
+       "special-characters-group-hebrew": "This is the name of a script, or alphabet, not a language.",
+       "special-characters-group-bangla": "The name of the [[w:Bengali alphabet|Bangla]] (a.k.a. Bengali) character set (alphabet).",
+       "special-characters-group-tamil": "The name of the [[w:Tamil_Script#Numerals_and_symbols|Tamil]] numerals and symbols.",
+       "special-characters-group-telugu": "The name of the [[w:Telugu language#Alphabet|Telugu]] character set (alphabet).",
+       "special-characters-group-sinhala": "The name of the [[w:Sinhala alphabet|Sinhala]] character set (alphabet).",
+       "special-characters-group-gujarati": "The name of the [[w:Gujarati alphabet|Gujarati]] character set (alphabet).",
+       "special-characters-group-devanagari": "The name of the [[w:Devanagari]] character set (alphabet).",
+       "special-characters-group-thai": "The name of the [[w:Thai alphabet|Thai]] character set (alphabet).",
+       "special-characters-group-lao": "{{Identical|Lao}}",
+       "special-characters-group-khmer": "{{Identical|Khmer}}",
+       "special-characters-title-endash": "Title tooltip for the en dash character (–); See https://en.wikipedia.org/wiki/Dash",
+       "special-characters-title-emdash": "Title tooltip for the em dash character (—); See https://en.wikipedia.org/wiki/Dash",
+       "special-characters-title-minus": "Title tooltip for the minus sign character (−), not to be confused with a hyphen"
 }
index c0d0499..732bdc0 100644 (file)
@@ -14,7 +14,8 @@
                                        "mw.html.Cdata",
                                        "mw.html.Raw",
                                        "mw.hook",
-                                       "mw.template"
+                                       "mw.template",
+                                       "mw.errorLogger"
                                ]
                        },
                        {
index 4e61140..fca839d 100644 (file)
@@ -41,6 +41,7 @@
        </script>
        <script src="modules/lib/jquery/jquery.js"></script>
        <script src="modules/src/mediawiki/mediawiki.js"></script>
+       <script src="modules/src/mediawiki/mediawiki.errorLogger.js"></script>
        <script src="modules/src/mediawiki/mediawiki.startUp.js"></script>
        <style>
                .mw-jsduck-log {
index 933212c..fc2bd3a 100644 (file)
@@ -9,7 +9,7 @@
   "devDependencies": {
     "grunt": "0.4.5",
     "grunt-cli": "0.1.13",
-    "grunt-banana-checker": "0.2.0",
+    "grunt-banana-checker": "0.2.1",
     "grunt-contrib-copy": "0.8.0",
     "grunt-contrib-jshint": "0.11.0",
     "grunt-contrib-watch": "0.6.1",
index 1096cbc..1ae76a0 100644 (file)
@@ -775,6 +775,7 @@ return array(
                // Keep maintenance/jsduck/eg-iframe.html in sync
                'scripts' => array(
                        'resources/src/mediawiki/mediawiki.js',
+                       'resources/src/mediawiki/mediawiki.errorLogger.js',
                        'resources/src/mediawiki/mediawiki.startUp.js',
                ),
                'debugScripts' => 'resources/src/mediawiki/mediawiki.log.js',
@@ -1259,6 +1260,10 @@ return array(
 
        'mediawiki.language.names' => array( 'class' => 'ResourceLoaderLanguageNamesModule' ),
 
+       'mediawiki.language.specialCharacters' => array(
+               'class' => 'ResourceLoaderSpecialCharacterDataModule'
+       ),
+
        /* MediaWiki Libs */
 
        'mediawiki.libs.jpegmeta' => array(
index bb8a84b..78e3919 100644 (file)
@@ -146,6 +146,10 @@ $.extend( mw.language, {
                        }
                }
                return text;
+       },
+
+       setSpecialCharacters: function ( data ) {
+               this.specialCharacters = data;
        }
 } );
 
diff --git a/resources/src/mediawiki.language/specialcharacters.json b/resources/src/mediawiki.language/specialcharacters.json
new file mode 100644 (file)
index 0000000..bab92a1
--- /dev/null
@@ -0,0 +1 @@
+{"latin":["Á","á","À","à","Â","â","Ä","ä","Ã","ã","Ǎ","ǎ","Ā","ā","Ă","ă","Ą","ą","Å","å","Ć","ć","Ĉ","ĉ","Ç","ç","Č","č","Ċ","ċ","Đ","đ","Ď","ď","É","é","È","è","Ê","ê","Ë","ë","Ě","ě","Ē","ē","Ĕ","ĕ","Ė","ė","Ę","ę","Ĝ","ĝ","Ģ","ģ","Ğ","ğ","Ġ","ġ","Ĥ","ĥ","Ħ","ħ","Í","í","Ì","ì","Î","î","Ï","ï","Ĩ","ĩ","Ǐ","ǐ","Ī","ī","Ĭ","ĭ","İ","ı","Į","į","Ĵ","ĵ","Ķ","ķ","Ĺ","ĺ","Ļ","ļ","Ľ","ľ","Ł","ł","Ń","ń","Ñ","ñ","Ņ","ņ","Ň","ň","Ó","ó","Ò","ò","Ô","ô","Ö","ö","Õ","õ","Ǒ","ǒ","Ō","ō","Ŏ","ŏ","Ǫ","ǫ","Ő","ő","Ŕ","ŕ","Ŗ","ŗ","Ř","ř","Ś","ś","Ŝ","ŝ","Ş","ş","Š","š","Ș","ș","Ț","ț","Ť","ť","Ú","ú","Ù","ù","Û","û","Ü","ü","Ũ","ũ","Ů","ů","Ǔ","ǔ","Ū","ū","ǖ","ǘ","ǚ","ǜ","Ŭ","ŭ","Ų","ų","Ű","ű","Ŵ","ŵ","Ý","ý","Ŷ","ŷ","Ÿ","ÿ","Ȳ","ȳ","Ź","ź","Ž","ž","Ż","ż","Æ","æ","Ǣ","ǣ","Ø","ø","Œ","œ","ß","Ð","ð","Þ","þ","Ə","ə"],"latinextended":["Ḁ","ḁ","ẚ","Ạ","ạ","Ả","ả","Ấ","ấ","Ầ","ầ","Ẩ","ẩ","Ẫ","ẫ","Ậ","ậ","Ắ","ắ","Ằ","ằ","Ẳ","ẳ","Ẵ","ẵ","Ặ","ặ","Ḃ","ḃ","Ḅ","ḅ","Ḇ","ḇ","Ḉ","ḉ","Ḋ","ḋ","Ḍ","ḍ","Ḏ","ḏ","Ḑ","ḑ","Ḓ","ḓ","Ḕ","ḕ","Ḗ","ḗ","Ḙ","ḙ","Ḛ","ḛ","Ḝ","ḝ","Ẹ","ẹ","Ẻ","ẻ","Ẽ","ẽ","Ế","ế","Ề","ề","Ể","ể","Ễ","ễ","Ệ","ệ","Ḟ","ḟ","Ḡ","ḡ","Ḣ","ḣ","Ḥ","ḥ","Ḧ","ḧ","Ḩ","ḩ","Ḫ","ḫ","ẖ","Ḭ","ḭ","Ḯ","ḯ","Ỉ","ỉ","Ị","ị","Ḱ","ḱ","Ḳ","ḳ","Ḵ","ḵ","Ḷ","ḷ","Ḹ","ḹ","Ḻ","ḻ","Ḽ","ḽ","Ỻ","ỻ","Ḿ","ḿ","Ṁ","ṁ","Ṃ","ṃ","Ṅ","ṅ","Ṇ","ṇ","Ṉ","ṉ","Ṋ","ṋ","Ṍ","ṍ","Ṏ","ṏ","Ṑ","ṑ","Ṓ","ṓ","Ọ","ọ","Ỏ","ỏ","Ố","ố","Ồ","ồ","Ổ","ổ","Ỗ","ỗ","Ộ","ộ","Ớ","ớ","Ờ","ờ","Ở","ở","Ỡ","ỡ","Ợ","ợ","Ǿ","ǿ","Ơ","ơ","Ṕ","ṕ","Ṗ","ṗ","Ṙ","ṙ","Ṛ","ṛ","Ṝ","ṝ","Ṟ","ṟ","Ṡ","ṡ","ẛ","Ṣ","ṣ","Ṥ","ṥ","Ṧ","ṧ","Ṩ","ṩ","ẜ","ẝ","Ṫ","ṫ","Ṭ","ṭ","Ṯ","ṯ","Ṱ","ṱ","ẗ","Ṳ","ṳ","Ṵ","ṵ","Ṷ","ṷ","Ṹ","ṹ","Ṻ","ṻ","Ụ","ụ","Ủ","ủ","Ứ","ứ","Ừ","ừ","Ử","ử","Ữ","ữ","Ự","ự","Ư","ư","Ǖ","Ǘ","Ǚ","Ǜ","Ṽ","ṽ","Ṿ","ṿ","Ỽ","ỽ","Ẁ","ẁ","Ẃ","ẃ","Ẅ","ẅ","Ẇ","ẇ","Ẉ","ẉ","ẘ","Ẋ","ẋ","Ẍ","ẍ","Ẏ","ẏ","ẙ","Ỳ","ỳ","Ỵ","ỵ","Ỷ","ỷ","Ỹ","ỹ","Ỿ","ỿ","Ẑ","ẑ","Ẓ","ẓ","Ẕ","ẕ","Ǽ","ǽ","ẞ","ẟ"],"ipa":["p","t̪","t","ʈ","c","k","q","ʡ","ʔ","b","d̪","d","ɖ","ɟ","ɡ","ɢ","ɓ","ɗ","ʄ","ɠ","ʛ","t͡s","t͡ʃ","t͡ɕ","d͡z","d͡ʒ","d͡ʑ","ɸ","f","θ","s","ʃ","ʅ","ʆ","ʂ","ɕ","ç","ɧ","x","χ","ħ","ʜ","h","β","v","ʍ","ð","z","ʒ","ʓ","ʐ","ʑ","ʝ","ɣ","ʁ","ʕ","ʖ","ʢ","ɦ","ɬ","ɮ","m","m̩","ɱ","ɱ̩","ɱ̍","n̪","n̪̍","n","n̩","ɳ","ɳ̩","ɲ","ɲ̩","ŋ","ŋ̍","ŋ̩","ɴ","ɴ̩","ʙ","ʙ̩","r","r̩","ʀ","ʀ̩","ɾ","ɽ","ɿ","ɺ","l̪","l̪̩","l","l̩","ɫ","ɫ̩","ɭ","ɭ̩","ʎ","ʎ̩","ʟ","ʟ̩","w","ɥ","ʋ","ɹ","ɻ","j","ɰ","ʘ","ǂ","ǀ","!","ǁ","ʰ","ʱ","ʷ","ʸ","ʲ","ʳ","ⁿ","ˡ","ʴ","ʵ","ˢ","ˣ","ˠ","ʶ","ˤ","ˁ","ˀ","ʼ","i","i̯","ĩ","y","y̯","ỹ","ɪ","ɪ̯","ɪ̃","ʏ","ʏ̯","ʏ̃","ɨ","ɨ̯","ɨ̃","ʉ","ʉ̯","ʉ̃","ɯ","ɯ̯","ɯ̃","u","u̯","ũ","ʊ","ʊ̯","ʊ̃","e","e̯","ẽ","ø","ø̯","ø̃","ɘ","ɘ̯","ɘ̃","ɵ","ɵ̯","ɵ̃","ɤ","ɤ̯","ɤ̃","o","o̯","õ","ɛ","ɛ̯","ɛ̃","œ","œ̯","œ̃","ɜ","ɜ̯","ɜ̃","ə","ə̯","ə̃","ɞ","ɞ̯","ɞ̃","ʌ","ʌ̯","ʌ̃","ɔ","ɔ̯","ɔ̃","æ","æ̯","æ̃","ɶ","ɶ̯","ɶ̃","a","a̯","ã","ɐ","ɐ̯","ɐ̃","ɑ","ɑ̯","ɑ̃","ɒ","ɒ̯","ɒ̃","ˈ","ˌ","ː","ˑ","˘",".","‿","|","‖","ɚ","ɝ"],"symbols":["~","|","¡","¿","†","‡","↔","↑","↓","•","¶","#","½","⅓","⅔","¼","¾","⅛","⅜","⅝","⅞","∞","‘","’",{"label":"“”","action":{"type":"encapsulate","options":{"pre":"“","post":"”"}}},{"label":"„“","action":{"type":"encapsulate","options":{"pre":"„","post":"“"}}},{"label":"„”","action":{"type":"encapsulate","options":{"pre":"„","post":"”"}}},{"label":"«»","action":{"type":"encapsulate","options":{"pre":"«","post":"»"}}},"¤","₳","฿","₵","¢","₡","₢","$","₫","₯","€","₠","₣","ƒ","₴","₭","₤","ℳ","₥","₦","№","₧","₰","£","៛","₨","₪","৳","₮","₩","¥","♠","♣","♥","♦","m²","m³",{"label":"–","titleMsg":"special-characters-title-endash","action":{"type":"replace","options":{"peri":"–","selectPeri":false}}},{"label":"—","titleMsg":"special-characters-title-emdash","action":{"type":"replace","options":{"peri":"—","selectPeri":false}}},"…","‘","’","“","”","°","′","″","≈","≠","≤","≥","±",{"label":"−","titleMsg":"special-characters-title-minus","action":{"type":"replace","options":{"peri":"−","selectPeri":false}}},"×","÷","←","→","·","§","‽"],"greek":["Α","Ά","α","ά","Β","β","Γ","γ","Δ","δ","Ε","Έ","ε","έ","Ζ","ζ","Η","Ή","η","ή","Θ","θ","Ι","Ί","ι","ί","Κ","κ","Λ","λ","Μ","μ","Ν","ν","Ξ","ξ","Ο","Ό","ο","ό","Π","π","Ρ","ρ","Σ","σ","ς","Τ","τ","Υ","Ύ","υ","ύ","Φ","φ","Χ","χ","Ψ","ψ","Ω","Ώ","ω","ώ"],"cyrillic":["А","а","Ӑ","ӑ","Ӓ","ӓ","Ә","ә","Ӛ","ӛ","Б","б","В","в","Г","г","Ґ","ґ","Ӷ","ӷ","Ѓ","ѓ","Ӻ","ӻ","Ғ","ғ","Ҕ","ҕ","Д","д","Ԁ","ԁ","Ԃ","ԃ","Ђ","ђ","Е","е","Ѐ","ѐ","Є","є","Ё","ё","Ӗ","ӗ","Ҽ","ҽ","Ҿ","ҿ","Ж","ж","Җ","җ","Ӂ","ӂ","Ӝ","ӝ","З","з","Ҙ","ҙ","Ӟ","ӟ","Ԑ","ԑ","Ӡ","ӡ","Ѕ","ѕ","Ԅ","ԅ","Ԇ","ԇ","И","и","І","і","Ї","ї",["◌Ӏ","Ӏ"],["◌ӏ","ӏ"],"Й","й","Ӣ","ӣ","Ѝ","ѝ","Ҋ","ҋ","Ӥ","ӥ","Ј","ј","К","к","Ќ","ќ","Қ","қ","Ҝ","ҝ","Ҟ","ҟ","Ҡ","ҡ","Ӄ","ӄ","Ԛ","ԛ","Л","л","Љ","љ","Ԉ","ԉ","Ԓ","ԓ","Ӆ","ӆ","М","м","Ӎ","ӎ","Н","н","Њ","њ","Ң","ң","Ҥ","ҥ","Ӈ","ӈ","Ԋ","ԋ","Ӊ","ӊ","О","о","Ҩ","ҩ","Ӧ","ӧ","Ө","ө","Ӫ","ӫ","П","п","Ԥ","ԥ","Ҧ","ҧ","Р","р","Ҏ","ҏ","С","с","Ҫ","ҫ","Т","т","Ћ","ћ","Ԍ","ԍ","Ҭ","ҭ","Ԏ","ԏ","У","у","Ў","ў","Ӯ","ӯ","Ӱ","ӱ","Ӳ","ӳ","Ү","ү","Ұ","ұ","Ф","ф","Х","х","Ҳ","ҳ","Ӽ","ӽ","Ӿ","ӿ","Һ","һ","Ц","ц","Ч","ч","Ҵ","ҵ","Ҷ","ҷ","Ҹ","ҹ","Ӌ","ӌ","Ӵ","ӵ","Џ","џ","Ш","ш","Щ","щ","Ъ","ъ","Ы","ы","Ӹ","ӹ","Ь","ь","Ҍ","ҍ","Э","э","Ӭ","ӭ","Ю","ю","Я","я","Ԝ","ԝ","Ѡ","ѡ","Ѣ","ѣ","Ѥ","ѥ","Ѧ","ѧ","Ѩ","ѩ","Ѫ","ѫ","Ѭ","ѭ","Ѯ","ѯ","Ѱ","ѱ","Ѳ","ѳ","Ѵ","ѵ","Ѷ","ѷ","Ѹ","ѹ","Ѻ","ѻ","Ѽ","ѽ","Ѿ","ѿ","Ҁ","ҁ"],"arabic":["ا","ب","ت","ث","ج","ح","خ","د","ذ","ر","ز","س","ش","ص","ض","ط","ظ","ع","غ","ف","ق","ك","ل","م","ن","ه","و","ي","ء","آ","أ","إ","ٱ","ؤ","ئ","ى","ة","َ","ُ","ِ","ً","ٌ","ٍ","ّ","ْ","ٰ","،","؛","؟","ـ","٠","١","٢","٣","٤","٥","٦","٧","٨","٩","٪","٫","٬","٭",["ZWNJ","‌"],["ZWJ","‍"]],"arabicextended":["ٲ","ٳ","ٴ","ٵ","ݳ","ݴ","ٮ","ٻ","پ","ڀ","ݐ","ݑ","ݒ","ݓ","ݔ","ݕ","ݖ","ٹ","ٺ","ټ","ٽ","ٿ","ځ","ڂ","ڃ","ڄ","څ","چ","ڇ","ڿ","ݗ","ݘ","ݮ","ݯ","ݲ","ݼ","ڈ","ډ","ڊ","ڋ","ڌ","ڍ","ڎ","ڏ","ڐ","ۮ","ݙ","ݚ","ڑ","ڒ","ړ","ڔ","ڕ","ږ","ڗ","ژ","ڙ","ۯ","ݛ","ݫ","ݬ","ݱ","ښ","ڛ","ڜ","ݽ","ۺ","ݜ","ݭ","ݰ","ݾ","ڝ","ڞ","ۻ","ڟ","ڠ","ݝ","ݞ","ݟ","ۼ","ڡ","ڢ","ڣ","ڤ","ڥ","ڦ","ݠ","ݡ","ٯ","ڧ","ڨ","ػ","ؼ","ک","ڪ","ګ","ڬ","ڭ","ڮ","گ","ڰ","ڱ","ڲ","ڳ","ڴ","ݢ","ݣ","ݤ","ݿ","ڵ","ڶ","ڷ","ڸ","ݪ","ݥ","ݦ","ڹ","ں","ڻ","ڼ","ڽ","ݧ","ݨ","ݩ","ھ","ۀ","ہ","ۂ","ۃ","ە","ۿ","ٶ","ٷ","ۄ","ۅ","ۆ","ۇ","ۈ","ۉ","ۊ","ۋ","ۏ","ݸ","ݹ","ؠ","ؽ","ؾ","ؿ","ٸ","ی","ۍ","ێ","ې","ۑ","ے","ۓ","ݵ","ݶ","ݷ","ݺ","ݻ","ٖ","ٗ","٘","ٙ","ٚ","ٛ","ٜ","ٝ","ٞ","ٟ","۔","۽","۾","۰","۱","۲","۳","۴","۵","۶","۷","۸","۹"],"hebrew":["א","ב","ג","ד","ה","ו","ז","ח","ט","י","כ","ך","ל","מ","ם","נ","ן","ס","ע","פ","ף","צ","ץ","ק","ר","ש","ת","װ","ױ","ײ","׳","״","־","–",{"label":"„”","action":{"type":"encapsulate","options":{"pre":"„","post":"”"}}},{"label":"‚’","action":{"type":"encapsulate","options":{"pre":"‚","post":"’"}}},["◌ְ","ְ"],["◌ֱ","ֱ"],["◌ֲ","ֲ"],["◌ֳ","ֳ"],["◌ִ","ִ"],["◌ֵ","ֵ"],["◌ֶ","ֶ"],["◌ַ","ַ"],["◌ָ","ָ"],["◌ֹ","ֹ"],["◌ֻ","ֻ"],["◌ּ","ּ"],["◌ׁ","ׁ"],["◌ׂ","ׂ"],["◌ׇ","ׇ"],["◌֑","֑"],["◌֒","֒"],["◌֓","֓"],["◌֔","֔"],["◌֕","֕"],["◌֖","֖"],["◌֗","֗"],["◌֘","֘"],["◌֙","֙"],["◌֚","֚"],["◌֛","֛"],["◌֜","֜"],["◌֝","֝"],["◌֞","֞"],["◌֟","֟"],["◌֠","֠"],["◌֡","֡"],["◌֢","֢"],["◌֣","֣"],["◌֤","֤"],["◌֥","֥"],["◌֦","֦"],["◌֧","֧"],["◌֨","֨"],["◌֩","֩"],["◌֪","֪"],["◌֫","֫"],["◌֬","֬"],["◌֭","֭"],["◌֮","֮"],["◌֯","֯"],["◌ֿ","ֿ"],["◌׀","׀"],["◌׃","׃"]],"bangla":["অ","আ","ই","ঈ","উ","ঊ","ঋ","এ","ঐ","ও","ঔ","া","ি","ী","ু","ূ","ৃ","ে","ৈ","ো","ৌ","ক","খ","গ","ঘ","ঙ","চ","ছ","জ","ঝ","ঞ","ট","ঠ","ড","ঢ","ণ","ত","থ","দ","ধ","ন","প","ফ","ব","ভ","ম","য","র","ল","শ","ষ","স","হ","ড়","ঢ়","য়","ৎ","ং","ঃ","ঁ","্","১","২","৩","৪","৫","৬","৭","৮","৯","০"],"tamil":["௦","௧","௨","௩","௪","௫","௬","௭","௮","௯","௰","௱","௲","௳","௴","௵","௶","௷","௸","௹","௺","ௐ"],"telugu":["ఁ","ం","ః","అ","ఆ","ఇ","ఈ","ఉ","ఊ","ఋ","ౠ","ఌ","ౡ","ఎ","ఏ","ఐ","ఒ","ఓ","ఔ","క","ఖ","గ","ఘ","ఙ","చ","ఛ","జ","ఝ","ఞ","ట","ఠ","డ","ఢ","ణ","త","థ","ద","ధ","న","ప","ఫ","బ","భ","మ","య","ర","ఱ","ల","ళ","వ","శ","ష","స","హ","ా","ి","ీ","ు","ూ","ృ","ౄ","ె","ే","ై","ొ","ో","ౌ","్","ౢ","ౣ","ౘ","ౙ","౦","౧","౨","౩","౪","౫","౬","౭","౮","౯","ఽ","౸","౹","౺","౻","౼","౽","౾","౿"],"sinhala":["අ","ආ","ඇ","ඈ","ඉ","ඊ","උ","ඌ","ඍ","ඎ","ඏ","ඐ","එ","ඒ","ඓ","ඔ","ඕ","ඖ","ක","ඛ","ග","ඝ","ඞ","ඟ","ච","ඡ","ජ","ඣ","ඤ","ඥ","ඦ","ට","ඨ","ඩ","ඪ","ණ","ඬ","ත","ථ","ද","ධ","න","ඳ","ප","ඵ","බ","භ","ම","ඹ","ය","ර","ල","ව","ශ","ෂ","ස","හ","ළ","ෆ",["◌ා","ා"],["◌ැ","ැ"],["◌ෑ","ෑ"],["◌ි","ි"],["◌ී","ී"],["◌ු","ු"],["◌ූ","ූ"],["◌ෘ","ෘ"],["◌ෲ","ෲ"],["◌ෟ","ෟ"],["◌ෳ","ෳ"],["◌ෙ","ෙ"],["◌ේ","ේ"],["◌ො","ො"],["◌ෝ","ෝ"],["◌ෞ","ෞ"],["◌්","්"]],"devanagari":["ऀ","ँ","ं","ः","ऄ","अ","आ","इ","ई","उ","ऊ","ऋ","ऌ","ऍ","ऎ","ए","ऐ","ऑ","ऒ","ओ","औ","क","ख","ग","घ","ङ","च","छ","ज","झ","ञ","ट","ठ","ड","ढ","ण","त","थ","द","ध","न","ऩ","प","फ","ब","भ","म","य","र","ऱ","ल","ळ","ऴ","व","श","ष","स","ह","ऺ","ऻ","़","ऽ","ा","ि","ी","ु","ू","ृ","ॄ","ॅ","ॆ","े","ै","ॉ","ॊ","ो","ौ","्","ॎ","ॏ","ॐ","॑","॒","॓","॔","ॕ","ॖ","ॗ","क़","ख़","ग़","ज़","ड़","ढ़","फ़","य़","ॠ","ॡ","ॢ","ॣ","।","॥","०","१","२","३","४","५","६","७","८","९","॰","ॱ","ॲ","ॳ","ॴ","ॵ","ॶ","ॷ","ॹ","ॺ","ॻ","ॼ","ॽ","ॾ","ॿ"],"gujarati":["ૐ","ઁ","ં","ઃ","અ","આ","ઇ","ઈ","ઉ","ઊ","એ","ઐ","ઓ","ઔ","અં","ઋ","ઍ","ઑ","ઌ","ૠ","ૡ","ક","ખ","ગ","ઘ","ઙ","ચ","છ","જ","ઝ","ઞ","ટ","ઠ","ડ","ઢ","ણ","ત","થ","દ","ધ","ન","પ","ફ","બ","ભ","મ","ય","ર","લ","ળ","વ","શ","ષ","સ","હ","ક્ષ","જ્ઞ","ઽ","ા","િ","ી","ી","ુ","ૂ","ૃ","ૄ","ૅ","ે","ૈ","ૉ","ો","ૌ","ૢ","ૣ","્","૦","૧","૨","૩","૪","૫","૬","૭","૮","૯","૱"],"thai":["ก","ข","ฃ","ค","ฅ","ฆ","ง","จ","ฉ","ช","ซ","ฌ","ญ","ฎ","ฏ","ฐ","ฑ","ฒ","ณ","ด","ต","ถ","ท","ธ","น","บ","ป","ผ","ฝ","พ","ฟ","ภ","ม","ย","ร","ฤ","ล","ฦ","ว","ศ","ษ","ส","ห","ฬ","อ","ฮ","ะ","ั","า","ๅ","ำ","ิ","ี","ึ","ื","ุ","ู","เ","แ","โ","ใ","ไ","็","่","้","๊","๋","์","ํ","ฺ","๎","๐","๑","๒","๓","๔","๕","๖","๗","๘","๙","฿","ๆ","ฯ","๚","๏","๛"],"lao":["ກ","ຂ","ຄ","ງ","ຈ","ສ","ຊ","ຍ","ດ","ຕ","ຖ","ທ","ນ","ບ","ປ","ຜ","ຝ","ພ","ຟ","ມ","ຢ","ລ","ວ","ຫ","ອ","ຮ","ຣ","ໜ","ໝ","ຼ","ຽ","ະ","ັ","າ","ຳ","ິ","ີ","ຶ","ື","ຸ","ູ","ົ","ເ","ແ","ໂ","ໃ","ໄ","່","້","໊","໋","໌","ໍ","໐","໑","໒","໓","໔","໕","໖","໗","໘","໙","₭","ໆ","ຯ"],"khmer":["ក","ខ","គ","ឃ","ង","ច","ឆ","ជ","ឈ","ញ","ដ","ឋ","ឌ","ឍ","ណ","ត","ថ","ទ","ធ","ន","ប","ផ","ព","ភ","ម","យ","រ","ល","វ","ស","ហ","ឡ","អ","ឣ","ឤ","ឥ","ឦ","ឧ","ឨ","ឩ","ឪ","ឫ","ឬ","ឭ","ឮ","ឯ","ឰ","ឱ","ឲ","ឳ","្","឴","឵","ា","ិ","ី","ឹ","ឺ","ុ","ូ","ួ","ើ","ឿ","ៀ","េ","ែ","ៃ","ោ","ៅ","ំ","ះ","ៈ","៉","៊","់","៌","៍","៎","៏","័","៑","៓","៝","ៜ","០","១","២","៣","៤","៥","៦","៧","៨","៩","៛","។","៕","៖","ៗ","៘","៙","៚","៰","៱","៲","៳","៴","៵","៶","៷","៸","៹","᧠","᧡","᧢","᧣","᧤","᧥","᧦","᧧","᧨","᧩","᧪","᧫","᧬","᧭","᧮","᧯","᧰","᧱","᧲","᧳","᧴","᧵","᧶","᧷","᧸","᧹","᧺","᧻","᧼","᧽","᧾","᧿"]}
\ No newline at end of file
diff --git a/resources/src/mediawiki/mediawiki.errorLogger.js b/resources/src/mediawiki/mediawiki.errorLogger.js
new file mode 100644 (file)
index 0000000..9f4f19d
--- /dev/null
@@ -0,0 +1,49 @@
+/**
+ * Try to catch errors in modules which don't do their own error handling.
+ * @class mw.errorLogger
+ * @singleton
+ */
+( function ( mw ) {
+       'use strict';
+
+       mw.errorLogger = {
+               /**
+                * Fired via mw.track when an error is not handled by local code and is caught by the
+                * window.onerror handler.
+                *
+                * @event global_error
+                * @param {string} errorMessage Error errorMessage.
+                * @param {string} url URL where error was raised.
+                * @param {number} lineNumber Line number where error was raised.
+                * @param {number} [columnNumber] Line number where error was raised. Not all browsers
+                *   support this.
+                * @param {Error|Mixed} [errorObject] The error object. Typically an instance of Error, but anything
+                *   (even a primitive value) passed to a throw clause will end up here.
+                */
+
+               /**
+                * Install a window.onerror handler that will report via mw.track, while preserving
+                * any previous handler.
+                * @param {Object} window
+                */
+               installGlobalHandler: function ( window ) {
+                       // We will preserve the return value of the previous handler. window.onerror works the
+                       // opposite way than normal event handlers (returning true will prevent the default
+                       // action, returning false will let the browser handle the error normally, by e.g.
+                       // logging to the console), so our fallback old handler needs to return false.
+                       var oldHandler = window.onerror || function () { return false; };
+
+                       /**
+                        * Dumb window.onerror handler which forwards the errors via mw.track.
+                        * @fires global_error
+                        */
+                       window.onerror = function ( errorMessage, url, lineNumber, columnNumber, errorObject ) {
+                               mw.track( 'global.error', { errorMessage: errorMessage, url: url,
+                                       lineNumber: lineNumber, columnNumber: columnNumber, errorObject: errorObject } );
+                               return oldHandler.apply( this, arguments );
+                       };
+               }
+       };
+
+       mw.errorLogger.installGlobalHandler( window );
+}( mediaWiki ) );
index 2b48c85..ee57c21 100644 (file)
                                        };
                                        // Split module batch by source and by group.
                                        splits = {};
-                                       maxQueryLength = mw.config.get( 'wgResourceLoaderMaxQueryLength', -1 );
+                                       maxQueryLength = mw.config.get( 'wgResourceLoaderMaxQueryLength', 2000 );
 
                                        // Appends a list of modules from the queue to the batch
                                        for ( q = 0; q < queue.length; q += 1 ) {
                                                                        moduleMap = {};
                                                                        async = true;
                                                                        l = currReqBaseLength + 9;
+                                                                       mw.track( 'resourceloader.splitRequest', { maxQueryLength: maxQueryLength } );
                                                                }
                                                                if ( !hasOwn.call( moduleMap, prefix ) ) {
                                                                        moduleMap[prefix] = [];
index 01c2578..d55f958 100644 (file)
@@ -325,36 +325,19 @@ class TitleTest extends MediaWikiTestCase {
                        $whitelistRegexp = array( $whitelistRegexp );
                }
 
+               $this->setMwGlobals( array(
+                       // So User::isEveryoneAllowed( 'read' ) === false
+                       'wgGroupPermissions' => array( '*' => array( 'read' => false ) ),
+                       'wgWhitelistRead' => array( 'some random non sense title' ),
+                       'wgWhitelistReadRegexp' => $whitelistRegexp,
+               ) );
+
                $title = Title::newFromDBkey( $source );
 
-               global $wgGroupPermissions;
-               $oldPermissions = $wgGroupPermissions;
-               // Disallow all so we can ensure our regex works
-               $wgGroupPermissions = array();
-               $wgGroupPermissions['*']['read'] = false;
-
-               global $wgWhitelistRead;
-               $oldWhitelist = $wgWhitelistRead;
-               // Undo any LocalSettings explicite whitelists so they won't cause a
-               // failing test to succeed. Set it to some random non sense just
-               // to make sure we properly test Title::checkReadPermissions()
-               $wgWhitelistRead = array( 'some random non sense title' );
-
-               global $wgWhitelistReadRegexp;
-               $oldWhitelistRegexp = $wgWhitelistReadRegexp;
-               $wgWhitelistReadRegexp = $whitelistRegexp;
-
-               // Just use $wgUser which in test is a user object for '127.0.0.1'
-               global $wgUser;
-               // Invalidate user rights cache to take in account $wgGroupPermissions
-               // change above.
-               $wgUser->clearInstanceCache();
-               $errors = $title->userCan( $action, $wgUser );
-
-               // Restore globals
-               $wgGroupPermissions = $oldPermissions;
-               $wgWhitelistRead = $oldWhitelist;
-               $wgWhitelistReadRegexp = $oldWhitelistRegexp;
+               // New anonymous user with no rights
+               $user = new User;
+               $user->mRights = array();
+               $errors = $title->userCan( $action, $user );
 
                if ( is_bool( $expected ) ) {
                        # Forge the assertion message depending on the assertion expectation
index 8430413..d73f679 100644 (file)
@@ -62,6 +62,7 @@ return array(
                        'tests/qunit/suites/resources/jquery/jquery.tablesorter.parsers.test.js',
                        'tests/qunit/suites/resources/jquery/jquery.textSelection.test.js',
                        'tests/qunit/data/mediawiki.jqueryMsg.data.js',
+                       'tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.jqueryMsg.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js',
                        'tests/qunit/suites/resources/mediawiki/mediawiki.test.js',
diff --git a/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js b/tests/qunit/suites/resources/mediawiki/mediawiki.errorLogger.test.js
new file mode 100644 (file)
index 0000000..7c3f1ec
--- /dev/null
@@ -0,0 +1,42 @@
+( function ( $, mw ) {
+       QUnit.module( 'mediawiki.errorLogger', QUnit.newMwEnvironment() );
+
+       QUnit.test( 'installGlobalHandler', 7, function ( assert ) {
+               var w = {},
+                       errorMessage = 'Foo',
+                       errorUrl = 'http://example.com',
+                       errorLine = '123',
+                       errorColumn = '45',
+                       errorObject = new Error( 'Foo'),
+                       oldHandler = this.sandbox.stub();
+
+               this.sandbox.stub( mw, 'track' );
+
+               mw.errorLogger.installGlobalHandler( w );
+
+               assert.ok( w.onerror, 'Global handler has been installed' );
+               assert.strictEqual( w.onerror( errorMessage, errorUrl, errorLine ), false,
+                       'Global handler returns false when there is no previous handler' );
+               sinon.assert.calledWithExactly( mw.track, 'global.error',
+                       sinon.match( { errorMessage: errorMessage, url: errorUrl, lineNumber: errorLine } ) );
+
+               mw.track.reset();
+               w.onerror( errorMessage, errorUrl, errorLine, errorColumn, errorObject );
+               sinon.assert.calledWithExactly( mw.track, 'global.error',
+                       sinon.match( { errorMessage: errorMessage, url: errorUrl, lineNumber: errorLine,
+                       columnNumber: errorColumn, errorObject: errorObject } ) );
+
+               w = { onerror: oldHandler };
+
+               mw.errorLogger.installGlobalHandler( w );
+               w.onerror( errorMessage, errorUrl, errorLine );
+               sinon.assert.calledWithExactly( oldHandler, errorMessage, errorUrl, errorLine );
+
+               oldHandler.returns( false );
+               assert.strictEqual( w.onerror( errorMessage, errorUrl, errorLine ), false,
+                       'Global handler preserves false return from previous handler' );
+               oldHandler.returns( true );
+               assert.strictEqual( w.onerror( errorMessage, errorUrl, errorLine ), true,
+                       'Global handler preserves true return from previous handler' );
+       } );
+}( jQuery, mediaWiki ) );