Merge "Rename wfIsHipHop() to wfIsHHVM()"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 4 Nov 2013 18:32:39 +0000 (18:32 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 4 Nov 2013 18:32:39 +0000 (18:32 +0000)
234 files changed:
.gitignore
CREDITS
FAQ
HISTORY
RELEASE-NOTES-1.22
RELEASE-NOTES-1.23
cache/.htaccess [changed mode: 0755->0644]
composer-example.json [new file with mode: 0644]
composer.json [deleted file]
docs/hooks.txt
includes/ArrayUtils.php [deleted file]
includes/AuthPlugin.php
includes/AutoLoader.php
includes/CallableUpdate.php [deleted file]
includes/Cdb.php [deleted file]
includes/Cdb_PHP.php [deleted file]
includes/ConfEditor.php [deleted file]
includes/DataUpdate.php [deleted file]
includes/DefaultSettings.php
includes/DeferredUpdates.php [deleted file]
includes/DeprecatedGlobal.php
includes/Exception.php
includes/GlobalFunctions.php
includes/HashRing.php [deleted file]
includes/HistoryBlob.php
includes/IP.php [deleted file]
includes/LinksUpdate.php [deleted file]
includes/MWCryptRand.php [deleted file]
includes/MWFunction.php [deleted file]
includes/MappedIterator.php [deleted file]
includes/Metadata.php
includes/PHPVersionError.php
includes/Sanitizer.php
includes/ScopedCallback.php [deleted file]
includes/ScopedPHPTimeout.php [deleted file]
includes/SiteStats.php
includes/SkinTemplate.php
includes/SqlDataUpdate.php [deleted file]
includes/Status.php
includes/StringUtils.php [deleted file]
includes/UIDGenerator.php [deleted file]
includes/ViewCountUpdate.php [deleted file]
includes/Wiki.php
includes/XmlTypeCheck.php [deleted file]
includes/ZipDirectoryReader.php [deleted file]
includes/api/ApiMain.php
includes/api/ApiQueryImageInfo.php [changed mode: 0644->0755]
includes/api/ApiQuerySiteinfo.php
includes/cache/HTMLCacheUpdate.php [deleted file]
includes/cache/LocalisationCache.php
includes/cache/SquidUpdate.php [deleted file]
includes/db/Database.php
includes/db/DatabaseError.php
includes/db/LoadMonitor.php
includes/deferred/CallableUpdate.php [new file with mode: 0644]
includes/deferred/DataUpdate.php [new file with mode: 0644]
includes/deferred/DeferredUpdates.php [new file with mode: 0644]
includes/deferred/HTMLCacheUpdate.php [new file with mode: 0644]
includes/deferred/LinksUpdate.php [new file with mode: 0644]
includes/deferred/SearchUpdate.php [new file with mode: 0644]
includes/deferred/SiteStatsUpdate.php [new file with mode: 0644]
includes/deferred/SqlDataUpdate.php [new file with mode: 0644]
includes/deferred/SquidUpdate.php [new file with mode: 0644]
includes/deferred/ViewCountUpdate.php [new file with mode: 0644]
includes/diff/DifferenceEngine.php
includes/filerepo/FileRepoStatus.php
includes/filerepo/ForeignAPIRepo.php [changed mode: 0644->0755]
includes/filerepo/file/File.php
includes/filerepo/file/ForeignAPIFile.php [changed mode: 0644->0755]
includes/installer/DatabaseUpdater.php
includes/installer/Installer.i18n.php
includes/installer/MysqlUpdater.php
includes/installer/OracleUpdater.php
includes/installer/SqliteUpdater.php
includes/job/JobQueueGroup.php
includes/libs/ScopedPHPTimeout.php [new file with mode: 0644]
includes/libs/XmlTypeCheck.php [new file with mode: 0644]
includes/limit.sh
includes/logging/LogPager.php
includes/media/ExifBitmap.php
includes/media/FormatMetadata.php [changed mode: 0644->0755]
includes/media/GIF.php
includes/media/MediaHandler.php [changed mode: 0644->0755]
includes/media/PNG.php
includes/media/SVG.php
includes/normal/UtfNormalTest.php
includes/parser/LinkHolderArray.php
includes/parser/Parser.php
includes/resourceloader/ResourceLoaderStartUpModule.php
includes/search/SearchUpdate.php [deleted file]
includes/specials/SpecialChangePassword.php
includes/specials/SpecialPreferences.php
includes/specials/SpecialRecentchanges.php
includes/specials/SpecialRecentchangeslinked.php
includes/specials/SpecialSearch.php
includes/upload/UploadFromChunks.php
includes/upload/UploadFromStash.php
includes/upload/UploadStash.php
includes/utils/ArrayUtils.php [new file with mode: 0644]
includes/utils/Cdb.php [new file with mode: 0644]
includes/utils/Cdb_PHP.php [new file with mode: 0644]
includes/utils/ConfEditor.php [new file with mode: 0644]
includes/utils/HashRing.php [new file with mode: 0644]
includes/utils/IP.php [new file with mode: 0644]
includes/utils/MWCryptRand.php [new file with mode: 0644]
includes/utils/MWFunction.php [new file with mode: 0644]
includes/utils/MappedIterator.php [new file with mode: 0644]
includes/utils/README [new file with mode: 0644]
includes/utils/ScopedCallback.php [new file with mode: 0644]
includes/utils/StringUtils.php [new file with mode: 0644]
includes/utils/UIDGenerator.php [new file with mode: 0644]
includes/utils/ZipDirectoryReader.php [new file with mode: 0644]
languages/Language.php
languages/Names.php
languages/messages/MessagesAce.php
languages/messages/MessagesAr.php
languages/messages/MessagesArn.php
languages/messages/MessagesAry.php
languages/messages/MessagesAst.php
languages/messages/MessagesAz.php
languages/messages/MessagesBa.php
languages/messages/MessagesBcl.php
languages/messages/MessagesBe_tarask.php
languages/messages/MessagesCa.php
languages/messages/MessagesCe.php
languages/messages/MessagesCrh_cyrl.php
languages/messages/MessagesCrh_latn.php
languages/messages/MessagesCs.php
languages/messages/MessagesCu.php
languages/messages/MessagesCv.php
languages/messages/MessagesCy.php
languages/messages/MessagesDa.php
languages/messages/MessagesDe.php
languages/messages/MessagesDiq.php
languages/messages/MessagesEn.php
languages/messages/MessagesEo.php
languages/messages/MessagesEs.php
languages/messages/MessagesEu.php
languages/messages/MessagesFa.php
languages/messages/MessagesFi.php
languages/messages/MessagesFr.php
languages/messages/MessagesFrr.php
languages/messages/MessagesGa.php
languages/messages/MessagesGd.php
languages/messages/MessagesGl.php
languages/messages/MessagesGsw.php
languages/messages/MessagesGu.php
languages/messages/MessagesHe.php
languages/messages/MessagesIa.php
languages/messages/MessagesId.php
languages/messages/MessagesIs.php
languages/messages/MessagesIt.php
languages/messages/MessagesJa.php
languages/messages/MessagesKa.php
languages/messages/MessagesKaa.php
languages/messages/MessagesKiu.php
languages/messages/MessagesKm.php
languages/messages/MessagesKo.php
languages/messages/MessagesKsh.php
languages/messages/MessagesLb.php
languages/messages/MessagesLtg.php
languages/messages/MessagesLv.php
languages/messages/MessagesMk.php
languages/messages/MessagesMl.php
languages/messages/MessagesMs.php
languages/messages/MessagesMt.php
languages/messages/MessagesNe.php
languages/messages/MessagesNl.php
languages/messages/MessagesNn.php
languages/messages/MessagesOc.php
languages/messages/MessagesPa.php
languages/messages/MessagesPl.php
languages/messages/MessagesPms.php
languages/messages/MessagesPs.php
languages/messages/MessagesQqq.php
languages/messages/MessagesRo.php
languages/messages/MessagesRoa_tara.php
languages/messages/MessagesRu.php
languages/messages/MessagesSah.php
languages/messages/MessagesSc.php
languages/messages/MessagesSi.php
languages/messages/MessagesSr_ec.php
languages/messages/MessagesSr_el.php
languages/messages/MessagesSv.php
languages/messages/MessagesSzl.php
languages/messages/MessagesTa.php
languages/messages/MessagesTl.php
languages/messages/MessagesTt_cyrl.php
languages/messages/MessagesTt_latn.php
languages/messages/MessagesUdm.php
languages/messages/MessagesUg_arab.php
languages/messages/MessagesUk.php
languages/messages/MessagesUz.php
languages/messages/MessagesVo.php
languages/messages/MessagesWar.php
languages/messages/MessagesYi.php
languages/messages/MessagesZh_hans.php
languages/messages/MessagesZh_hant.php
maintenance/cleanupUploadStash.php
maintenance/jsduck/categories.json
maintenance/language/messageTypes.inc
maintenance/language/messages.inc
maintenance/oracle/tables.sql
resources/Resources.php
resources/mediawiki.special/mediawiki.special.preferences.css
resources/mediawiki.special/mediawiki.special.vforms.css
resources/mediawiki/mediawiki.inspect.js
resources/mediawiki/mediawiki.js
skins/common/IEFixes.js
skins/common/commonElements.css
skins/common/shared.css
skins/common/wikibits.js
skins/vector/collapsibleNav.js
skins/vector/images/preferences-break.png [deleted file]
skins/vector/images/preferences-fade.png [deleted file]
skins/vector/images/preferences/break.png [new file with mode: 0644]
skins/vector/images/preferences/fade.png [new file with mode: 0644]
skins/vector/screen.less
skins/vector/special.preferences.less [new file with mode: 0644]
skins/vector/styles-beta.less
skins/vector/styles.less
tests/parser/parserTests.txt
tests/phpunit/data/media/README
tests/phpunit/data/media/Tux.svg [new file with mode: 0644]
tests/phpunit/includes/MWExceptionHandlerTest.php [new file with mode: 0644]
tests/phpunit/includes/SanitizerTest.php
tests/phpunit/includes/diff/DifferenceEngineTest.php [new file with mode: 0644]
tests/phpunit/includes/media/FormatMetadataTest.php
tests/phpunit/includes/media/GIFTest.php
tests/phpunit/includes/media/JpegTest.php
tests/phpunit/includes/media/PNGTest.php
tests/phpunit/includes/media/SVGMetadataExtractorTest.php
tests/phpunit/includes/media/SVGTest.php [new file with mode: 0644]
tests/phpunit/structure/ResourcesTest.php

index 9c0c3b6..7d1a309 100644 (file)
@@ -41,6 +41,7 @@ sftp-config.json
 
 # Building & testing
 node_modules/
+.sass-cache/
 
 # Composer
 /vendor
diff --git a/CREDITS b/CREDITS
index 7927c3d..23636ae 100644 (file)
--- a/CREDITS
+++ b/CREDITS
@@ -1,4 +1,4 @@
-MediaWiki 1.22 is a collaborative project released under the
+MediaWiki 1.23 is a collaborative project released under the
 GNU General Public License v2. We would like to recognize the
 following names for their contribution to the product.
 
diff --git a/FAQ b/FAQ
index 0aedb7e..cfacf14 100644 (file)
--- a/FAQ
+++ b/FAQ
@@ -1,2 +1,2 @@
 The MediaWiki FAQ can be found at:
-http://www.mediawiki.org/wiki/Manual:FAQ
+https://www.mediawiki.org/wiki/Manual:FAQ
diff --git a/HISTORY b/HISTORY
index 45eab2e..88cc906 100644 (file)
--- a/HISTORY
+++ b/HISTORY
@@ -1,4 +1,4 @@
-Change notes from older releases. For current info see RELEASE-NOTES-1.22.
+Change notes from older releases. For current info see RELEASE-NOTES-1.23.
 
 == MediaWiki 1.21 ==
 
index 92699be..25d5c42 100644 (file)
@@ -227,8 +227,6 @@ production.
 * Add deferrable update support for callback/closure.
 * Add TitleMove hook before page renames.
 * Revision deletion backend code is moved out of SpecialRevisiondelete
-* Add a variable (wgRedactedFunctionArguments) to redact the values sent as certain function
-  parameters from exception stack traces.
 * Added {{REVISIONSIZE}} variable to get the current size of a revision.
 * Add support for the LESS stylesheet language to ResourceLoader. LESS is a
   stylesheet language that compiles into CSS. ResourceLoader file modules may
@@ -264,6 +262,7 @@ production.
   archive.ar_len field.
 
 === Bug fixes in 1.22 ===
+* (bug 47271) $wgContentHandlerUseDB should be set to false during the upgrade
 * Disable Special:PasswordReset when $wgEnableEmail is false. Previously one
   could still navigate to the page by entering the URL directly.
 * (bug 47138) Fixed a fatal error when a blocked user tries to automatically
@@ -527,6 +526,13 @@ changes to languages because of Bugzilla reports.
   to addModules() and made protected.
 * Methods WatchAction::doWatch and WatchAction::doUnwatch now return a Status
   object instead of a boolean.
+* Information boxes (CSS classes errorbox, warningbox, successbox) have been
+  made more subtle.
+* BREAKING CHANGE: The module 'mediawiki.legacy.IEFixes' has been removed as it was
+  unused. The file skins/common/IEFixes.js remains but is only used by wikibits.
+  The file never contained any re-usable components. To use it in a skin, load
+  'mediawiki.legacy.wikibits' (which IEFixes depends on) and that will import
+  IEFixes automatically if user agent conditions are met.
 
 == Compatibility ==
 
index ec7b898..c9b56b9 100644 (file)
@@ -11,6 +11,13 @@ production.
 === Configuration changes in 1.23 ===
 
 === New features in 1.23 ===
+* ResourceLoader can utilize the Web Storage API to cache modules client-side.
+  Compared to the browser cache, caching in Web Storage allows ResourceLoader
+  to be more granular about evicting stale modules from the cache while
+  retaining the ability to retrieve multiple modules in a single HTTP request.
+  This capability can be enabled by setting $wgResourceLoaderStorageEnabled to
+  true. This feature is currently considered experimental and should only be
+  enabled with care.
 
 === Bug fixes in 1.23 ===
 * (bug 41759) The "updated since last visit" markers (on history pages, recent
@@ -27,6 +34,7 @@ regularly. Below only new and removed languages are listed, as well as
 changes to languages because of Bugzilla reports.
 
 === Other changes in 1.23 ===
+* The global variable $wgArticle has been removed after a lengthy deprecation
 
 == Compatibility ==
 
old mode 100755 (executable)
new mode 100644 (file)
diff --git a/composer-example.json b/composer-example.json
new file mode 100644 (file)
index 0000000..6c4d37f
--- /dev/null
@@ -0,0 +1,11 @@
+{
+       "require": {
+               "php": ">=5.3.2"
+       },
+       "suggest": {
+               "ext-fileinfo": "*",
+               "ext-mbstring": "*",
+               "ext-wikidiff2": "*",
+               "ext-apc": "*"
+       }
+}
diff --git a/composer.json b/composer.json
deleted file mode 100644 (file)
index ded3365..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-{
-       "name": "mediawiki/core",
-       "description": "Free software wiki application developed by the Wikimedia Foundation and others",
-       "keywords": ["mediawiki", "wiki"],
-       "homepage": "https://www.mediawiki.org/",
-       "authors": [
-               {
-                       "name": "MediaWiki Community",
-                       "homepage": "https://www.mediawiki.org/wiki/Special:Version/Credits"
-               }
-       ],
-       "license": "GPL-2.0",
-       "support": {
-               "issues": "https://bugzilla.wikimedia.org/",
-               "irc": "irc://irc.freenode.net/mediawiki",
-               "wiki": "https://www.mediawiki.org/"
-       },
-       "require": {
-               "php": ">=5.3.2"
-       },
-       "require-dev": {
-               "phpunit/phpunit": "*"
-       },
-       "suggest": {
-               "ext-fileinfo": "*",
-               "ext-mbstring": "*",
-               "ext-wikidiff2": "*",
-               "ext-apc": "*"
-       }
-}
index 2671dd8..96a72df 100644 (file)
@@ -1157,6 +1157,16 @@ $title: Title object that we need to get a sortkey for
 underscore) magic words. Called by MagicWord.
 &$doubleUnderscoreIDs: array of strings
 
+'GetExtendedMetadata': Get extended file metadata for the API
+&$combinedMeta: Array of the form: 'MetadataPropName' => array(
+'value' => prop value, 'source' => 'name of hook' ).
+$file: File object of file in question
+$context: RequestContext (including language to use)
+$single: Only extract the current language; if false, the prop value should
+be in the metadata multi-language array format:
+mediawiki.org/wiki/Manual:File_metadata_handling#Multi-language_array_format
+&$maxCacheTime: how long the results can be cached
+
 'GetFullURL': Modify fully-qualified URLs used in redirects/export/offsite data.
 $title: Title object of page
 $url: string value as output (out parameter, can modify)
@@ -1598,7 +1608,7 @@ $wcOnlySysopsCanPatrol: config setting indicating whether the user must be a
 something completely different, after the basic globals have been set up, but
 before ordinary actions take place.
 $output: $wgOut
-$article: $wgArticle
+$article: Article on which the action will be performed
 $title: $wgTitle
 $user: $wgUser
 $request: $wgRequest
@@ -2744,6 +2754,12 @@ $userId: User id of the current user
 $userText: User name of the current user
 &$items: Array of user tool links as HTML fragments
 
+'ValidateExtendedMetadataCache': Called to validate the cached metadata in
+FormatMetadata::getExtendedMeta (return false means cache will be
+invalidated and GetExtendedMetadata hook called again).
+$timestamp: The timestamp metadata was generated
+$file: The file the metadata is for
+
 'WantedPages::getQueryInfo': Called in WantedPagesPage::getQueryInfo(), can be
 used to alter the SQL query which gets the list of wanted pages.
 &$wantedPages: WantedPagesPage object
diff --git a/includes/ArrayUtils.php b/includes/ArrayUtils.php
deleted file mode 100644 (file)
index 985271f..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-<?php
-
-class ArrayUtils {
-       /**
-        * Sort the given array in a pseudo-random order which depends only on the
-        * given key and each element value. This is typically used for load
-        * balancing between servers each with a local cache.
-        *
-        * Keys are preserved. The input array is modified in place.
-        *
-        * Note: Benchmarking on PHP 5.3 and 5.4 indicates that for small
-        * strings, md5() is only 10% slower than hash('joaat',...) etc.,
-        * since the function call overhead dominates. So there's not much
-        * justification for breaking compatibility with installations
-        * compiled with ./configure --disable-hash.
-        *
-        * @param $array The array to sort
-        * @param $key The string key
-        * @param $separator A separator used to delimit the array elements and the
-        *     key. This can be chosen to provide backwards compatibility with
-        *     various consistent hash implementations that existed before this
-        *     function was introduced.
-        */
-       public static function consistentHashSort( &$array, $key, $separator = "\000" ) {
-               $hashes = array();
-               foreach ( $array as $elt ) {
-                       $hashes[$elt] = md5( $elt . $separator . $key );
-               }
-               uasort( $array, function ( $a, $b ) use ( $hashes ) {
-                       return strcmp( $hashes[$a], $hashes[$b] );
-               } );
-       }
-
-       /**
-        * Given an array of non-normalised probabilities, this function will select
-        * an element and return the appropriate key
-        *
-        * @param $weights array
-        *
-        * @return bool|int|string
-        */
-       public static function pickRandom( $weights ) {
-               if ( !is_array( $weights ) || count( $weights ) == 0 ) {
-                       return false;
-               }
-
-               $sum = array_sum( $weights );
-               if ( $sum == 0 ) {
-                       # No loads on any of them
-                       # In previous versions, this triggered an unweighted random selection,
-                       # but this feature has been removed as of April 2006 to allow for strict
-                       # separation of query groups.
-                       return false;
-               }
-               $max = mt_getrandmax();
-               $rand = mt_rand( 0, $max ) / $max * $sum;
-
-               $sum = 0;
-               foreach ( $weights as $i => $w ) {
-                       $sum += $w;
-                       # Do not return keys if they have 0 weight.
-                       # Note that the "all 0 weight" case is handed above
-                       if ( $w > 0 && $sum >= $rand ) {
-                               break;
-                       }
-               }
-               return $i;
-       }
-}
index 84cf3d5..c7b0c97 100644 (file)
@@ -34,7 +34,6 @@
  * someone logs in who can be authenticated externally.
  */
 class AuthPlugin {
-
        /**
         * @var string
         */
@@ -46,7 +45,7 @@ class AuthPlugin {
         * you might need to munge it (for instance, for lowercase initial
         * letters).
         *
-        * @param string $username username.
+        * @param string $username Username.
         * @return bool
         */
        public function userExists( $username ) {
@@ -60,8 +59,8 @@ class AuthPlugin {
         * you might need to munge it (for instance, for lowercase initial
         * letters).
         *
-        * @param string $username username.
-        * @param string $password user password.
+        * @param string $username Username.
+        * @param string $password User password.
         * @return bool
         */
        public function authenticate( $username, $password ) {
@@ -72,7 +71,7 @@ class AuthPlugin {
        /**
         * Modify options in the login template.
         *
-        * @param $template UserLoginTemplate object.
+        * @param UserLoginTemplate $template
         * @param string $type 'signup' or 'login'. Added in 1.16.
         */
        public function modifyUITemplate( &$template, &$type ) {
@@ -83,7 +82,7 @@ class AuthPlugin {
        /**
         * Set the domain this plugin is supposed to use when authenticating.
         *
-        * @param string $domain authentication domain.
+        * @param string $domain Authentication domain.
         */
        public function setDomain( $domain ) {
                $this->domain = $domain;
@@ -105,7 +104,7 @@ class AuthPlugin {
        /**
         * Check to see if the specific domain is a valid domain.
         *
-        * @param string $domain authentication domain.
+        * @param string $domain Authentication domain.
         * @return bool
         */
        public function validDomain( $domain ) {
@@ -121,7 +120,7 @@ class AuthPlugin {
         * The User object is passed by reference so it can be modified; don't
         * forget the & on your function declaration.
         *
-        * @param $user User object
+        * @param User $user
         * @return bool
         */
        public function updateUser( &$user ) {
@@ -140,7 +139,7 @@ class AuthPlugin {
         *
         * This is just a question, and shouldn't perform any actions.
         *
-        * @return Boolean
+        * @return bool
         */
        public function autoCreate() {
                return false;
@@ -151,9 +150,9 @@ class AuthPlugin {
         * and use the same keys. 'Realname' 'Emailaddress' and 'Nickname'
         * all reference this.
         *
-        * @param $prop string
+        * @param string $prop
         *
-        * @return Boolean
+        * @return bool
         */
        public function allowPropChange( $prop = '' ) {
                if ( $prop == 'realname' && is_callable( array( $this, 'allowRealNameChange' ) ) ) {
@@ -193,8 +192,8 @@ class AuthPlugin {
         *
         * Return true if successful.
         *
-        * @param $user User object.
-        * @param string $password password.
+        * @param User $user
+        * @param string $password Password.
         * @return bool
         */
        public function setPassword( $user, $password ) {
@@ -216,10 +215,10 @@ class AuthPlugin {
         * Update user groups in the external authentication database.
         * Return true if successful.
         *
-        * @param $user User object.
-        * @param $addgroups Groups to add.
-        * @param $delgroups Groups to remove.
-        * @return Boolean
+        * @param User $user
+        * @param array $addgroups Groups to add.
+        * @param array $delgroups Groups to remove.
+        * @return bool
         */
        public function updateExternalDBGroups( $user, $addgroups, $delgroups = array() ) {
                return true;
@@ -228,7 +227,7 @@ class AuthPlugin {
        /**
         * Check to see if external accounts can be created.
         * Return true if external accounts can be created.
-        * @return Boolean
+        * @return bool
         */
        public function canCreateAccounts() {
                return false;
@@ -238,11 +237,11 @@ class AuthPlugin {
         * Add a user to the external authentication database.
         * Return true if successful.
         *
-        * @param $user User: only the name should be assumed valid at this point
-        * @param $password String
-        * @param $email String
-        * @param $realname String
-        * @return Boolean
+        * @param User $user Only the name should be assumed valid at this point
+        * @param string $password
+        * @param string $email
+        * @param string $realname
+        * @return bool
         */
        public function addUser( $user, $password, $email = '', $realname = '' ) {
                return true;
@@ -254,7 +253,7 @@ class AuthPlugin {
         *
         * This is just a question, and shouldn't perform any actions.
         *
-        * @return Boolean
+        * @return bool
         */
        public function strict() {
                return false;
@@ -264,8 +263,8 @@ class AuthPlugin {
         * Check if a user should authenticate locally if the global authentication fails.
         * If either this or strict() returns true, local authentication is not used.
         *
-        * @param string $username username.
-        * @return Boolean
+        * @param string $username Username.
+        * @return bool
         */
        public function strictUserAuth( $username ) {
                return false;
@@ -279,8 +278,8 @@ class AuthPlugin {
         * The User object is passed by reference so it can be modified; don't
         * forget the & on your function declaration.
         *
-        * @param $user User object.
-        * @param $autocreate Boolean: True if user is being autocreated on login
+        * @param User $user
+        * @param bool $autocreate True if user is being autocreated on login
         */
        public function initUser( &$user, $autocreate = false ) {
                # Override this to do something.
@@ -289,7 +288,7 @@ class AuthPlugin {
        /**
         * If you want to munge the case of an account name before the final
         * check, now is your chance.
-        * @param $username string
+        * @param string $username
         * @return string
         */
        public function getCanonicalName( $username ) {
@@ -299,7 +298,7 @@ class AuthPlugin {
        /**
         * Get an instance of a User object
         *
-        * @param $user User
+        * @param User $user
         *
         * @return AuthPluginUser
         */
index dbba500..1417c77 100644 (file)
@@ -33,7 +33,6 @@ $wgAutoloadLocalClasses = array(
        'AjaxDispatcher' => 'includes/AjaxDispatcher.php',
        'AjaxResponse' => 'includes/AjaxResponse.php',
        'AlphabeticPager' => 'includes/Pager.php',
-       'ArrayUtils' => 'includes/ArrayUtils.php',
        'Article' => 'includes/Article.php',
        'AtomFeed' => 'includes/Feed.php',
        'AuthPlugin' => 'includes/AuthPlugin.php',
@@ -47,31 +46,17 @@ $wgAutoloadLocalClasses = array(
        'Categoryfinder' => 'includes/Categoryfinder.php',
        'CategoryPage' => 'includes/CategoryPage.php',
        'CategoryViewer' => 'includes/CategoryViewer.php',
-       'CdbFunctions' => 'includes/Cdb_PHP.php',
-       'CdbReader' => 'includes/Cdb.php',
-       'CdbReader_DBA' => 'includes/Cdb.php',
-       'CdbReader_PHP' => 'includes/Cdb_PHP.php',
-       'CdbWriter' => 'includes/Cdb.php',
-       'CdbWriter_DBA' => 'includes/Cdb.php',
-       'CdbWriter_PHP' => 'includes/Cdb_PHP.php',
        'ChangesFeed' => 'includes/ChangesFeed.php',
        'ChangeTags' => 'includes/ChangeTags.php',
        'ChannelFeed' => 'includes/Feed.php',
        'Collation' => 'includes/Collation.php',
        'ConcatenatedGzipHistoryBlob' => 'includes/HistoryBlob.php',
-       'ConfEditor' => 'includes/ConfEditor.php',
-       'ConfEditorParseError' => 'includes/ConfEditor.php',
-       'ConfEditorToken' => 'includes/ConfEditor.php',
        'Cookie' => 'includes/Cookie.php',
        'CookieJar' => 'includes/Cookie.php',
        'CurlHttpRequest' => 'includes/HttpFunctions.php',
-       'DeferrableUpdate' => 'includes/DeferredUpdates.php',
-       'DeferredUpdates' => 'includes/DeferredUpdates.php',
-       'MWCallableUpdate' => 'includes/CallableUpdate.php',
        'DeprecatedGlobal' => 'includes/DeprecatedGlobal.php',
        'DerivativeRequest' => 'includes/WebRequest.php',
        'DiffHistoryBlob' => 'includes/HistoryBlob.php',
-       'DoubleReplacer' => 'includes/StringUtils.php',
        'DummyLinker' => 'includes/Linker.php',
        'Dump7ZipOutput' => 'includes/Export.php',
        'DumpBZip2Output' => 'includes/Export.php',
@@ -87,7 +72,6 @@ $wgAutoloadLocalClasses = array(
        'EditPage' => 'includes/EditPage.php',
        'EmailNotification' => 'includes/UserMailer.php',
        'ErrorPageError' => 'includes/Exception.php',
-       'ExplodeIterator' => 'includes/StringUtils.php',
        'FakeTitle' => 'includes/FakeTitle.php',
        'Fallback' => 'includes/Fallback.php',
        'FatalError' => 'includes/Exception.php',
@@ -102,8 +86,6 @@ $wgAutoloadLocalClasses = array(
        'FormOptions' => 'includes/FormOptions.php',
        'FormSpecialPage' => 'includes/SpecialPage.php',
        'GitInfo' => 'includes/GitInfo.php',
-       'HashRing' => 'includes/HashRing.php',
-       'HashtableReplacer' => 'includes/StringUtils.php',
        'HistoryBlob' => 'includes/HistoryBlob.php',
        'HistoryBlobCurStub' => 'includes/HistoryBlob.php',
        'HistoryBlobStub' => 'includes/HistoryBlob.php',
@@ -145,7 +127,6 @@ $wgAutoloadLocalClasses = array(
        'IncludableSpecialPage' => 'includes/SpecialPage.php',
        'IndexPager' => 'includes/Pager.php',
        'Interwiki' => 'includes/interwiki/Interwiki.php',
-       'IP' => 'includes/IP.php',
        'LCStore' => 'includes/cache/LocalisationCache.php',
        'LCStore_Accel' => 'includes/cache/LocalisationCache.php',
        'LCStore_CDB' => 'includes/cache/LocalisationCache.php',
@@ -155,23 +136,18 @@ $wgAutoloadLocalClasses = array(
        'Licenses' => 'includes/Licenses.php',
        'Linker' => 'includes/Linker.php',
        'LinkFilter' => 'includes/LinkFilter.php',
-       'LinksUpdate' => 'includes/LinksUpdate.php',
-       'LinksDeletionUpdate' => 'includes/LinksUpdate.php',
        'LocalisationCache' => 'includes/cache/LocalisationCache.php',
        'LocalisationCache_BulkLoad' => 'includes/cache/LocalisationCache.php',
        'MagicWord' => 'includes/MagicWord.php',
        'MagicWordArray' => 'includes/MagicWord.php',
        'MailAddress' => 'includes/UserMailer.php',
-       'MappedIterator' => 'includes/MappedIterator.php',
        'MediaWiki' => 'includes/Wiki.php',
        'MediaWiki_I18N' => 'includes/SkinTemplate.php',
        'Message' => 'includes/Message.php',
        'MessageBlobStore' => 'includes/MessageBlobStore.php',
        'MimeMagic' => 'includes/MimeMagic.php',
-       'MWCryptRand' => 'includes/MWCryptRand.php',
        'MWException' => 'includes/Exception.php',
        'MWExceptionHandler' => 'includes/Exception.php',
-       'MWFunction' => 'includes/MWFunction.php',
        'MWHookException' => 'includes/Hooks.php',
        'MWHttpRequest' => 'includes/HttpFunctions.php',
        'MWInit' => 'includes/Init.php',
@@ -201,9 +177,6 @@ $wgAutoloadLocalClasses = array(
        'ReadOnlyError' => 'includes/Exception.php',
        'RedirectSpecialArticle' => 'includes/SpecialPage.php',
        'RedirectSpecialPage' => 'includes/SpecialPage.php',
-       'RegexlikeReplacer' => 'includes/StringUtils.php',
-       'ReplacementArray' => 'includes/StringUtils.php',
-       'Replacer' => 'includes/StringUtils.php',
        'ReverseChronologicalPager' => 'includes/Pager.php',
        'RevisionItem' => 'includes/RevisionList.php',
        'RevisionItemBase' => 'includes/RevisionList.php',
@@ -212,14 +185,9 @@ $wgAutoloadLocalClasses = array(
        'RevisionList' => 'includes/RevisionList.php',
        'RSSFeed' => 'includes/Feed.php',
        'Sanitizer' => 'includes/Sanitizer.php',
-       'DataUpdate' => 'includes/DataUpdate.php',
-       'SqlDataUpdate' => 'includes/SqlDataUpdate.php',
-       'ScopedCallback' => 'includes/ScopedCallback.php',
-       'ScopedPHPTimeout' => 'includes/ScopedPHPTimeout.php',
        'SiteConfiguration' => 'includes/SiteConfiguration.php',
        'SiteStats' => 'includes/SiteStats.php',
        'SiteStatsInit' => 'includes/SiteStats.php',
-       'SiteStatsUpdate' => 'includes/SiteStats.php',
        'Skin' => 'includes/Skin.php',
        'SkinTemplate' => 'includes/SkinTemplate.php',
        'SpecialCreateAccount' => 'includes/SpecialPage.php',
@@ -238,7 +206,6 @@ $wgAutoloadLocalClasses = array(
        'StatCounter' => 'includes/StatCounter.php',
        'Status' => 'includes/Status.php',
        'StreamFile' => 'includes/StreamFile.php',
-       'StringUtils' => 'includes/StringUtils.php',
        'StubContLang' => 'includes/StubObject.php',
        'StubObject' => 'includes/StubObject.php',
        'StubUserLang' => 'includes/StubObject.php',
@@ -249,7 +216,6 @@ $wgAutoloadLocalClasses = array(
        'TitleArray' => 'includes/TitleArray.php',
        'TitleArrayFromResult' => 'includes/TitleArray.php',
        'ThrottledError' => 'includes/Exception.php',
-       'UIDGenerator' => 'includes/UIDGenerator.php',
        'UnlistedSpecialPage' => 'includes/SpecialPage.php',
        'UploadSourceAdapter' => 'includes/Import.php',
        'UppercaseCollation' => 'includes/Collation.php',
@@ -261,7 +227,6 @@ $wgAutoloadLocalClasses = array(
        'UserCache' => 'includes/cache/UserCache.php',
        'UserMailer' => 'includes/UserMailer.php',
        'UserRightsProxy' => 'includes/UserRightsProxy.php',
-       'ViewCountUpdate' => 'includes/ViewCountUpdate.php',
        'WantedQueryPage' => 'includes/QueryPage.php',
        'WatchedItem' => 'includes/WatchedItem.php',
        'WebRequest' => 'includes/WebRequest.php',
@@ -283,25 +248,7 @@ $wgAutoloadLocalClasses = array(
        'XmlJsCode' => 'includes/Xml.php',
        'XMLReader2' => 'includes/Import.php',
        'XmlSelect' => 'includes/Xml.php',
-       'XmlTypeCheck' => 'includes/XmlTypeCheck.php',
        'ZhClient' => 'includes/ZhClient.php',
-       'ZipDirectoryReader' => 'includes/ZipDirectoryReader.php',
-       'ZipDirectoryReaderError' => 'includes/ZipDirectoryReader.php',
-
-       # content handler
-       'AbstractContent' => 'includes/content/AbstractContent.php',
-       'ContentHandler' => 'includes/content/ContentHandler.php',
-       'Content' => 'includes/content/Content.php',
-       'CssContentHandler' => 'includes/content/CssContentHandler.php',
-       'CssContent' => 'includes/content/CssContent.php',
-       'JavaScriptContentHandler' => 'includes/content/JavaScriptContentHandler.php',
-       'JavaScriptContent' => 'includes/content/JavaScriptContent.php',
-       'MessageContent' => 'includes/content/MessageContent.php',
-       'MWContentSerializationException' => 'includes/content/ContentHandler.php',
-       'TextContentHandler' => 'includes/content/TextContentHandler.php',
-       'TextContent' => 'includes/content/TextContent.php',
-       'WikitextContentHandler' => 'includes/content/WikitextContentHandler.php',
-       'WikitextContent' => 'includes/content/WikitextContent.php',
 
        # includes/actions
        'CachedAction' => 'includes/actions/CachedAction.php',
@@ -440,7 +387,6 @@ $wgAutoloadLocalClasses = array(
        'FileDependency' => 'includes/cache/CacheDependency.php',
        'GenderCache' => 'includes/cache/GenderCache.php',
        'GlobalDependency' => 'includes/cache/CacheDependency.php',
-       'HTMLCacheUpdate' => 'includes/cache/HTMLCacheUpdate.php',
        'HTMLFileCache' => 'includes/cache/HTMLFileCache.php',
        'LinkBatch' => 'includes/cache/LinkBatch.php',
        'LinkCache' => 'includes/cache/LinkCache.php',
@@ -448,7 +394,6 @@ $wgAutoloadLocalClasses = array(
        'ObjectFileCache' => 'includes/cache/ObjectFileCache.php',
        'ProcessCacheLRU' => 'includes/cache/ProcessCacheLRU.php',
        'ResourceFileCache' => 'includes/cache/ResourceFileCache.php',
-       'SquidUpdate' => 'includes/cache/SquidUpdate.php',
        'TitleDependency' => 'includes/cache/CacheDependency.php',
        'TitleListDependency' => 'includes/cache/CacheDependency.php',
 
@@ -463,6 +408,21 @@ $wgAutoloadLocalClasses = array(
        'RedisConnectionPool' => 'includes/clientpool/RedisConnectionPool.php',
        'RedisConnRef' => 'includes/clientpool/RedisConnectionPool.php',
 
+       # includes/content
+       'AbstractContent' => 'includes/content/AbstractContent.php',
+       'ContentHandler' => 'includes/content/ContentHandler.php',
+       'Content' => 'includes/content/Content.php',
+       'CssContentHandler' => 'includes/content/CssContentHandler.php',
+       'CssContent' => 'includes/content/CssContent.php',
+       'JavaScriptContentHandler' => 'includes/content/JavaScriptContentHandler.php',
+       'JavaScriptContent' => 'includes/content/JavaScriptContent.php',
+       'MessageContent' => 'includes/content/MessageContent.php',
+       'MWContentSerializationException' => 'includes/content/ContentHandler.php',
+       'TextContentHandler' => 'includes/content/TextContentHandler.php',
+       'TextContent' => 'includes/content/TextContent.php',
+       'WikitextContentHandler' => 'includes/content/WikitextContentHandler.php',
+       'WikitextContent' => 'includes/content/WikitextContent.php',
+
        # includes/context
        'ContextSource' => 'includes/context/ContextSource.php',
        'DerivativeContext' => 'includes/context/DerivativeContext.php',
@@ -530,6 +490,20 @@ $wgAutoloadLocalClasses = array(
        # includes/debug
        'MWDebug' => 'includes/debug/Debug.php',
 
+       # includes/deferred
+       'DataUpdate' => 'includes/deferred/DataUpdate.php',
+       'DeferrableUpdate' => 'includes/deferred/DeferredUpdates.php',
+       'DeferredUpdates' => 'includes/deferred/DeferredUpdates.php',
+       'HTMLCacheUpdate' => 'includes/deferred/HTMLCacheUpdate.php',
+       'LinksDeletionUpdate' => 'includes/deferred/LinksUpdate.php',
+       'LinksUpdate' => 'includes/deferred/LinksUpdate.php',
+       'MWCallableUpdate' => 'includes/deferred/CallableUpdate.php',
+       'SearchUpdate' => 'includes/deferred/SearchUpdate.php',
+       'SiteStatsUpdate' => 'includes/deferred/SiteStatsUpdate.php',
+       'SqlDataUpdate' => 'includes/deferred/SqlDataUpdate.php',
+       'SquidUpdate' => 'includes/deferred/SquidUpdate.php',
+       'ViewCountUpdate' => 'includes/deferred/ViewCountUpdate.php',
+
        # includes/diff
        '_DiffEngine' => 'includes/diff/DairikiDiff.php',
        '_DiffOp' => 'includes/diff/DairikiDiff.php',
@@ -708,6 +682,8 @@ $wgAutoloadLocalClasses = array(
        'JSParser' => 'includes/libs/jsminplus.php',
        'JSToken' => 'includes/libs/jsminplus.php',
        'JSTokenizer' => 'includes/libs/jsminplus.php',
+       'ScopedPHPTimeout' => 'includes/libs/ScopedPHPTimeout.php',
+       'XmlTypeCheck' => 'includes/libs/XmlTypeCheck.php',
 
        # includes/libs/lessphp
        'lessc' => 'includes/libs/lessc.inc.php',
@@ -909,7 +885,6 @@ $wgAutoloadLocalClasses = array(
        'SearchResultSet' => 'includes/search/SearchEngine.php',
        'SearchResultTooMany' => 'includes/search/SearchEngine.php',
        'SearchSqlite' => 'includes/search/SearchSqlite.php',
-       'SearchUpdate' => 'includes/search/SearchUpdate.php',
        'SqliteSearchResultSet' => 'includes/search/SearchSqlite.php',
        'SqlSearchResultSet' => 'includes/search/SearchEngine.php',
 
@@ -1067,6 +1042,35 @@ $wgAutoloadLocalClasses = array(
        'UploadStashWrongOwnerException' => 'includes/upload/UploadStash.php',
        'UploadStashNoSuchKeyException' => 'includes/upload/UploadStash.php',
 
+       # includes/utils
+       'ArrayUtils' => 'includes/utils/ArrayUtils.php',
+       'CdbFunctions' => 'includes/utils/Cdb_PHP.php',
+       'CdbReader' => 'includes/utils/Cdb.php',
+       'CdbReader_DBA' => 'includes/utils/Cdb.php',
+       'CdbReader_PHP' => 'includes/utils/Cdb_PHP.php',
+       'CdbWriter' => 'includes/utils/Cdb.php',
+       'CdbWriter_DBA' => 'includes/utils/Cdb.php',
+       'CdbWriter_PHP' => 'includes/utils/Cdb_PHP.php',
+       'ConfEditor' => 'includes/utils/ConfEditor.php',
+       'ConfEditorParseError' => 'includes/utils/ConfEditor.php',
+       'ConfEditorToken' => 'includes/utils/ConfEditor.php',
+       'DoubleReplacer' => 'includes/utils/StringUtils.php',
+       'ExplodeIterator' => 'includes/utils/StringUtils.php',
+       'HashRing' => 'includes/utils/HashRing.php',
+       'HashtableReplacer' => 'includes/utils/StringUtils.php',
+       'IP' => 'includes/utils/IP.php',
+       'MWCryptRand' => 'includes/utils/MWCryptRand.php',
+       'MWFunction' => 'includes/utils/MWFunction.php',
+       'MappedIterator' => 'includes/utils/MappedIterator.php',
+       'RegexlikeReplacer' => 'includes/utils/StringUtils.php',
+       'ReplacementArray' => 'includes/utils/StringUtils.php',
+       'Replacer' => 'includes/utils/StringUtils.php',
+       'ScopedCallback' => 'includes/utils/ScopedCallback.php',
+       'StringUtils' => 'includes/utils/StringUtils.php',
+       'UIDGenerator' => 'includes/utils/UIDGenerator.php',
+       'ZipDirectoryReader' => 'includes/utils/ZipDirectoryReader.php',
+       'ZipDirectoryReaderError' => 'includes/utils/ZipDirectoryReader.php',
+
        # languages
        'ConverterRule' => 'languages/LanguageConverter.php',
        'FakeConverter' => 'languages/Language.php',
diff --git a/includes/CallableUpdate.php b/includes/CallableUpdate.php
deleted file mode 100644 (file)
index 6eb5541..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-<?php
-
-/**
- * Deferrable Update for closure/callback
- */
-class MWCallableUpdate implements DeferrableUpdate {
-
-       /**
-        * @var closure/callabck
-        */
-       private $callback;
-
-       /**
-        * @param callable $callback
-        */
-       public function __construct( $callback ) {
-               if ( !is_callable( $callback ) ) {
-                       throw new MWException( 'Not a valid callback/closure!' );
-               }
-               $this->callback = $callback;
-       }
-
-       /**
-        * Run the update
-        */
-       public function doUpdate() {
-               call_user_func( $this->callback );
-       }
-
-}
diff --git a/includes/Cdb.php b/includes/Cdb.php
deleted file mode 100644 (file)
index 81c0afe..0000000
+++ /dev/null
@@ -1,184 +0,0 @@
-<?php
-/**
- * Native CDB file reader and writer.
- *
- * 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
- */
-
-/**
- * Read from a CDB file.
- * Native and pure PHP implementations are provided.
- * http://cr.yp.to/cdb.html
- */
-abstract class CdbReader {
-       /**
-        * Open a file and return a subclass instance
-        *
-        * @param $fileName string
-        *
-        * @return CdbReader
-        */
-       public static function open( $fileName ) {
-               if ( self::haveExtension() ) {
-                       return new CdbReader_DBA( $fileName );
-               } else {
-                       wfDebug( "Warning: no dba extension found, using emulation.\n" );
-                       return new CdbReader_PHP( $fileName );
-               }
-       }
-
-       /**
-        * Returns true if the native extension is available
-        *
-        * @return bool
-        */
-       public static function haveExtension() {
-               if ( !function_exists( 'dba_handlers' ) ) {
-                       return false;
-               }
-               $handlers = dba_handlers();
-               if ( !in_array( 'cdb', $handlers ) || !in_array( 'cdb_make', $handlers ) ) {
-                       return false;
-               }
-               return true;
-       }
-
-       /**
-        * Construct the object and open the file
-        */
-       abstract function __construct( $fileName );
-
-       /**
-        * Close the file. Optional, you can just let the variable go out of scope.
-        */
-       abstract function close();
-
-       /**
-        * Get a value with a given key. Only string values are supported.
-        *
-        * @param $key string
-        */
-       abstract public function get( $key );
-}
-
-/**
- * Write to a CDB file.
- * Native and pure PHP implementations are provided.
- */
-abstract class CdbWriter {
-       /**
-        * Open a writer and return a subclass instance.
-        * The user must have write access to the directory, for temporary file creation.
-        *
-        * @param $fileName string
-        *
-        * @return CdbWriter_DBA|CdbWriter_PHP
-        */
-       public static function open( $fileName ) {
-               if ( CdbReader::haveExtension() ) {
-                       return new CdbWriter_DBA( $fileName );
-               } else {
-                       wfDebug( "Warning: no dba extension found, using emulation.\n" );
-                       return new CdbWriter_PHP( $fileName );
-               }
-       }
-
-       /**
-        * Create the object and open the file
-        *
-        * @param $fileName string
-        */
-       abstract function __construct( $fileName );
-
-       /**
-        * Set a key to a given value. The value will be converted to string.
-        * @param $key string
-        * @param $value string
-        */
-       abstract public function set( $key, $value );
-
-       /**
-        * Close the writer object. You should call this function before the object
-        * goes out of scope, to write out the final hashtables.
-        */
-       abstract public function close();
-}
-
-/**
- * Reader class which uses the DBA extension
- */
-class CdbReader_DBA {
-       var $handle;
-
-       function __construct( $fileName ) {
-               $this->handle = dba_open( $fileName, 'r-', 'cdb' );
-               if ( !$this->handle ) {
-                       throw new MWException( 'Unable to open CDB file "' . $fileName . '"' );
-               }
-       }
-
-       function close() {
-               if ( isset( $this->handle ) ) {
-                       dba_close( $this->handle );
-               }
-               unset( $this->handle );
-       }
-
-       function get( $key ) {
-               return dba_fetch( $key, $this->handle );
-       }
-}
-
-/**
- * Writer class which uses the DBA extension
- */
-class CdbWriter_DBA {
-       var $handle, $realFileName, $tmpFileName;
-
-       function __construct( $fileName ) {
-               $this->realFileName = $fileName;
-               $this->tmpFileName = $fileName . '.tmp.' . mt_rand( 0, 0x7fffffff );
-               $this->handle = dba_open( $this->tmpFileName, 'n', 'cdb_make' );
-               if ( !$this->handle ) {
-                       throw new MWException( 'Unable to open CDB file for write "' . $fileName . '"' );
-               }
-       }
-
-       function set( $key, $value ) {
-               return dba_insert( $key, $value, $this->handle );
-       }
-
-       function close() {
-               if ( isset( $this->handle ) ) {
-                       dba_close( $this->handle );
-               }
-               if ( wfIsWindows() ) {
-                       unlink( $this->realFileName );
-               }
-               if ( !rename( $this->tmpFileName, $this->realFileName ) ) {
-                       throw new MWException( 'Unable to move the new CDB file into place.' );
-               }
-               unset( $this->handle );
-       }
-
-       function __destruct() {
-               if ( isset( $this->handle ) ) {
-                       $this->close();
-               }
-       }
-}
diff --git a/includes/Cdb_PHP.php b/includes/Cdb_PHP.php
deleted file mode 100644 (file)
index a38b9a8..0000000
+++ /dev/null
@@ -1,493 +0,0 @@
-<?php
-/**
- * This is a port of D.J. Bernstein's CDB to PHP. It's based on the copy that
- * appears in PHP 5.3. Changes are:
- *    * Error returns replaced with exceptions
- *    * Exception thrown if sizes or offsets are between 2GB and 4GB
- *    * Some variables renamed
- *
- * 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
- */
-
-/**
- * Common functions for readers and writers
- */
-class CdbFunctions {
-       /**
-        * Take a modulo of a signed integer as if it were an unsigned integer.
-        * $b must be less than 0x40000000 and greater than 0
-        *
-        * @param $a
-        * @param $b
-        *
-        * @return int
-        */
-       public static function unsignedMod( $a, $b ) {
-               if ( $a & 0x80000000 ) {
-                       $m = ( $a & 0x7fffffff ) % $b + 2 * ( 0x40000000 % $b );
-                       return $m % $b;
-               } else {
-                       return $a % $b;
-               }
-       }
-
-       /**
-        * Shift a signed integer right as if it were unsigned
-        * @param $a
-        * @param $b
-        * @return int
-        */
-       public static function unsignedShiftRight( $a, $b ) {
-               if ( $b == 0 ) {
-                       return $a;
-               }
-               if ( $a & 0x80000000 ) {
-                       return ( ( $a & 0x7fffffff ) >> $b ) | ( 0x40000000 >> ( $b - 1 ) );
-               } else {
-                       return $a >> $b;
-               }
-       }
-
-       /**
-        * The CDB hash function.
-        *
-        * @param $s string
-        *
-        * @return
-        */
-       public static function hash( $s ) {
-               $h = 5381;
-               for ( $i = 0; $i < strlen( $s ); $i++ ) {
-                       $h5 = ( $h << 5 ) & 0xffffffff;
-                       // Do a 32-bit sum
-                       // Inlined here for speed
-                       $sum = ( $h & 0x3fffffff ) + ( $h5 & 0x3fffffff );
-                       $h =
-                               (
-                                       ( $sum & 0x40000000 ? 1 : 0 )
-                                       + ( $h & 0x80000000 ? 2 : 0 )
-                                       + ( $h & 0x40000000 ? 1 : 0 )
-                                       + ( $h5 & 0x80000000 ? 2 : 0 )
-                                       + ( $h5 & 0x40000000 ? 1 : 0 )
-                               ) << 30
-                               | ( $sum & 0x3fffffff );
-                       $h ^= ord( $s[$i] );
-                       $h &= 0xffffffff;
-               }
-               return $h;
-       }
-}
-
-/**
- * CDB reader class
- */
-class CdbReader_PHP extends CdbReader {
-       /** The filename */
-       var $fileName;
-
-       /** The file handle */
-       var $handle;
-
-       /* number of hash slots searched under this key */
-       var $loop;
-
-       /* initialized if loop is nonzero */
-       var $khash;
-
-       /* initialized if loop is nonzero */
-       var $kpos;
-
-       /* initialized if loop is nonzero */
-       var $hpos;
-
-       /* initialized if loop is nonzero */
-       var $hslots;
-
-       /* initialized if findNext() returns true */
-       var $dpos;
-
-       /* initialized if cdb_findnext() returns 1 */
-       var $dlen;
-
-       /**
-        * @param $fileName string
-        * @throws MWException
-        */
-       function __construct( $fileName ) {
-               $this->fileName = $fileName;
-               $this->handle = fopen( $fileName, 'rb' );
-               if ( !$this->handle ) {
-                       throw new MWException( 'Unable to open CDB file "' . $this->fileName . '".' );
-               }
-               $this->findStart();
-       }
-
-       function close() {
-               if ( isset( $this->handle ) ) {
-                       fclose( $this->handle );
-               }
-               unset( $this->handle );
-       }
-
-       /**
-        * @param $key
-        * @return bool|string
-        */
-       public function get( $key ) {
-               // strval is required
-               if ( $this->find( strval( $key ) ) ) {
-                       return $this->read( $this->dlen, $this->dpos );
-               } else {
-                       return false;
-               }
-       }
-
-       /**
-        * @param $key
-        * @param $pos
-        * @return bool
-        */
-       protected function match( $key, $pos ) {
-               $buf = $this->read( strlen( $key ), $pos );
-               return $buf === $key;
-       }
-
-       protected function findStart() {
-               $this->loop = 0;
-       }
-
-       /**
-        * @throws MWException
-        * @param $length
-        * @param $pos
-        * @return string
-        */
-       protected function read( $length, $pos ) {
-               if ( fseek( $this->handle, $pos ) == -1 ) {
-                       // This can easily happen if the internal pointers are incorrect
-                       throw new MWException(
-                               'Seek failed, file "' . $this->fileName . '" may be corrupted.' );
-               }
-
-               if ( $length == 0 ) {
-                       return '';
-               }
-
-               $buf = fread( $this->handle, $length );
-               if ( $buf === false || strlen( $buf ) !== $length ) {
-                       throw new MWException(
-                               'Read from CDB file failed, file "' . $this->fileName . '" may be corrupted.' );
-               }
-               return $buf;
-       }
-
-       /**
-        * Unpack an unsigned integer and throw an exception if it needs more than 31 bits
-        * @param $s
-        * @throws MWException
-        * @return mixed
-        */
-       protected function unpack31( $s ) {
-               $data = unpack( 'V', $s );
-               if ( $data[1] > 0x7fffffff ) {
-                       throw new MWException(
-                               'Error in CDB file "' . $this->fileName . '", integer too big.' );
-               }
-               return $data[1];
-       }
-
-       /**
-        * Unpack a 32-bit signed integer
-        * @param $s
-        * @return int
-        */
-       protected function unpackSigned( $s ) {
-               $data = unpack( 'va/vb', $s );
-               return $data['a'] | ( $data['b'] << 16 );
-       }
-
-       /**
-        * @param $key
-        * @return bool
-        */
-       protected function findNext( $key ) {
-               if ( !$this->loop ) {
-                       $u = CdbFunctions::hash( $key );
-                       $buf = $this->read( 8, ( $u << 3 ) & 2047 );
-                       $this->hslots = $this->unpack31( substr( $buf, 4 ) );
-                       if ( !$this->hslots ) {
-                               return false;
-                       }
-                       $this->hpos = $this->unpack31( substr( $buf, 0, 4 ) );
-                       $this->khash = $u;
-                       $u = CdbFunctions::unsignedShiftRight( $u, 8 );
-                       $u = CdbFunctions::unsignedMod( $u, $this->hslots );
-                       $u <<= 3;
-                       $this->kpos = $this->hpos + $u;
-               }
-
-               while ( $this->loop < $this->hslots ) {
-                       $buf = $this->read( 8, $this->kpos );
-                       $pos = $this->unpack31( substr( $buf, 4 ) );
-                       if ( !$pos ) {
-                               return false;
-                       }
-                       $this->loop += 1;
-                       $this->kpos += 8;
-                       if ( $this->kpos == $this->hpos + ( $this->hslots << 3 ) ) {
-                               $this->kpos = $this->hpos;
-                       }
-                       $u = $this->unpackSigned( substr( $buf, 0, 4 ) );
-                       if ( $u === $this->khash ) {
-                               $buf = $this->read( 8, $pos );
-                               $keyLen = $this->unpack31( substr( $buf, 0, 4 ) );
-                               if ( $keyLen == strlen( $key ) && $this->match( $key, $pos + 8 ) ) {
-                                       // Found
-                                       $this->dlen = $this->unpack31( substr( $buf, 4 ) );
-                                       $this->dpos = $pos + 8 + $keyLen;
-                                       return true;
-                               }
-                       }
-               }
-               return false;
-       }
-
-       /**
-        * @param $key
-        * @return bool
-        */
-       protected function find( $key ) {
-               $this->findStart();
-               return $this->findNext( $key );
-       }
-}
-
-/**
- * CDB writer class
- */
-class CdbWriter_PHP extends CdbWriter {
-       var $handle, $realFileName, $tmpFileName;
-
-       var $hplist;
-       var $numentries, $pos;
-
-       /**
-        * @param $fileName string
-        */
-       function __construct( $fileName ) {
-               $this->realFileName = $fileName;
-               $this->tmpFileName = $fileName . '.tmp.' . mt_rand( 0, 0x7fffffff );
-               $this->handle = fopen( $this->tmpFileName, 'wb' );
-               if ( !$this->handle ) {
-                       $this->throwException(
-                               'Unable to open CDB file "' . $this->tmpFileName . '" for write.' );
-               }
-               $this->hplist = array();
-               $this->numentries = 0;
-               $this->pos = 2048; // leaving space for the pointer array, 256 * 8
-               if ( fseek( $this->handle, $this->pos ) == -1 ) {
-                       $this->throwException( 'fseek failed in file "' . $this->tmpFileName . '".' );
-               }
-       }
-
-       function __destruct() {
-               if ( isset( $this->handle ) ) {
-                       $this->close();
-               }
-       }
-
-       /**
-        * @param $key
-        * @param $value
-        * @return
-        */
-       public function set( $key, $value ) {
-               if ( strval( $key ) === '' ) {
-                       // DBA cross-check hack
-                       return;
-               }
-               $this->addbegin( strlen( $key ), strlen( $value ) );
-               $this->write( $key );
-               $this->write( $value );
-               $this->addend( strlen( $key ), strlen( $value ), CdbFunctions::hash( $key ) );
-       }
-
-       /**
-        * @throws MWException
-        */
-       public function close() {
-               $this->finish();
-               if ( isset( $this->handle ) ) {
-                       fclose( $this->handle );
-               }
-               if ( wfIsWindows() && file_exists( $this->realFileName ) ) {
-                       unlink( $this->realFileName );
-               }
-               if ( !rename( $this->tmpFileName, $this->realFileName ) ) {
-                       $this->throwException( 'Unable to move the new CDB file into place.' );
-               }
-               unset( $this->handle );
-       }
-
-       /**
-        * @throws MWException
-        * @param $buf
-        */
-       protected function write( $buf ) {
-               $len = fwrite( $this->handle, $buf );
-               if ( $len !== strlen( $buf ) ) {
-                       $this->throwException( 'Error writing to CDB file "' . $this->tmpFileName . '".' );
-               }
-       }
-
-       /**
-        * @throws MWException
-        * @param $len
-        */
-       protected function posplus( $len ) {
-               $newpos = $this->pos + $len;
-               if ( $newpos > 0x7fffffff ) {
-                       $this->throwException(
-                               'A value in the CDB file "' . $this->tmpFileName . '" is too large.' );
-               }
-               $this->pos = $newpos;
-       }
-
-       /**
-        * @param $keylen
-        * @param $datalen
-        * @param $h
-        */
-       protected function addend( $keylen, $datalen, $h ) {
-               $this->hplist[] = array(
-                       'h' => $h,
-                       'p' => $this->pos
-               );
-
-               $this->numentries++;
-               $this->posplus( 8 );
-               $this->posplus( $keylen );
-               $this->posplus( $datalen );
-       }
-
-       /**
-        * @throws MWException
-        * @param $keylen
-        * @param $datalen
-        */
-       protected function addbegin( $keylen, $datalen ) {
-               if ( $keylen > 0x7fffffff ) {
-                       $this->throwException( 'Key length too long in file "' . $this->tmpFileName . '".' );
-               }
-               if ( $datalen > 0x7fffffff ) {
-                       $this->throwException( 'Data length too long in file "' . $this->tmpFileName . '".' );
-               }
-               $buf = pack( 'VV', $keylen, $datalen );
-               $this->write( $buf );
-       }
-
-       /**
-        * @throws MWException
-        */
-       protected function finish() {
-               // Hack for DBA cross-check
-               $this->hplist = array_reverse( $this->hplist );
-
-               // Calculate the number of items that will be in each hashtable
-               $counts = array_fill( 0, 256, 0 );
-               foreach ( $this->hplist as $item ) {
-                       ++ $counts[255 & $item['h']];
-               }
-
-               // Fill in $starts with the *end* indexes
-               $starts = array();
-               $pos = 0;
-               for ( $i = 0; $i < 256; ++$i ) {
-                       $pos += $counts[$i];
-                       $starts[$i] = $pos;
-               }
-
-               // Excessively clever and indulgent code to simultaneously fill $packedTables
-               // with the packed hashtables, and adjust the elements of $starts
-               // to actually point to the starts instead of the ends.
-               $packedTables = array_fill( 0, $this->numentries, false );
-               foreach ( $this->hplist as $item ) {
-                       $packedTables[--$starts[255 & $item['h']]] = $item;
-               }
-
-               $final = '';
-               for ( $i = 0; $i < 256; ++$i ) {
-                       $count = $counts[$i];
-
-                       // The size of the hashtable will be double the item count.
-                       // The rest of the slots will be empty.
-                       $len = $count + $count;
-                       $final .= pack( 'VV', $this->pos, $len );
-
-                       $hashtable = array();
-                       for ( $u = 0; $u < $len; ++$u ) {
-                               $hashtable[$u] = array( 'h' => 0, 'p' => 0 );
-                       }
-
-                       // Fill the hashtable, using the next empty slot if the hashed slot
-                       // is taken.
-                       for ( $u = 0; $u < $count; ++$u ) {
-                               $hp = $packedTables[$starts[$i] + $u];
-                               $where = CdbFunctions::unsignedMod(
-                                       CdbFunctions::unsignedShiftRight( $hp['h'], 8 ), $len );
-                               while ( $hashtable[$where]['p'] ) {
-                                       if ( ++$where == $len ) {
-                                               $where = 0;
-                                       }
-                               }
-                               $hashtable[$where] = $hp;
-                       }
-
-                       // Write the hashtable
-                       for ( $u = 0; $u < $len; ++$u ) {
-                               $buf = pack( 'vvV',
-                                       $hashtable[$u]['h'] & 0xffff,
-                                       CdbFunctions::unsignedShiftRight( $hashtable[$u]['h'], 16 ),
-                                       $hashtable[$u]['p'] );
-                               $this->write( $buf );
-                               $this->posplus( 8 );
-                       }
-               }
-
-               // Write the pointer array at the start of the file
-               rewind( $this->handle );
-               if ( ftell( $this->handle ) != 0 ) {
-                       $this->throwException( 'Error rewinding to start of file "' . $this->tmpFileName . '".' );
-               }
-               $this->write( $final );
-       }
-
-       /**
-        * Clean up the temp file and throw an exception
-        *
-        * @param $msg string
-        * @throws MWException
-        */
-       protected function throwException( $msg ) {
-               if ( $this->handle ) {
-                       fclose( $this->handle );
-                       unlink( $this->tmpFileName );
-               }
-               throw new MWException( $msg );
-       }
-}
diff --git a/includes/ConfEditor.php b/includes/ConfEditor.php
deleted file mode 100644 (file)
index 67cb87d..0000000
+++ /dev/null
@@ -1,1109 +0,0 @@
-<?php
-/**
- * Configuration file editor.
- *
- * 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
- */
-
-/**
- * This is a state machine style parser with two internal stacks:
- *   * A next state stack, which determines the state the machine will progress to next
- *   * A path stack, which keeps track of the logical location in the file.
- *
- * Reference grammar:
- *
- * file = T_OPEN_TAG *statement
- * statement = T_VARIABLE "=" expression ";"
- * expression = array / scalar / T_VARIABLE
- * array = T_ARRAY "(" [ element *( "," element ) [ "," ] ] ")"
- * element = assoc-element / expression
- * assoc-element = scalar T_DOUBLE_ARROW expression
- * scalar = T_LNUMBER / T_DNUMBER / T_STRING / T_CONSTANT_ENCAPSED_STRING
- */
-class ConfEditor {
-       /** The text to parse */
-       var $text;
-
-       /** The token array from token_get_all() */
-       var $tokens;
-
-       /** The current position in the token array */
-       var $pos;
-
-       /** The current 1-based line number */
-       var $lineNum;
-
-       /** The current 1-based column number */
-       var $colNum;
-
-       /** The current 0-based byte number */
-       var $byteNum;
-
-       /** The current ConfEditorToken object */
-       var $currentToken;
-
-       /** The previous ConfEditorToken object */
-       var $prevToken;
-
-       /**
-        * The state machine stack. This is an array of strings where the topmost
-        * element will be popped off and become the next parser state.
-        */
-       var $stateStack;
-
-       /**
-        * The path stack is a stack of associative arrays with the following elements:
-        *    name              The name of top level of the path
-        *    level             The level (number of elements) of the path
-        *    startByte         The byte offset of the start of the path
-        *    startToken        The token offset of the start
-        *    endByte           The byte offset of thee
-        *    endToken          The token offset of the end, plus one
-        *    valueStartToken   The start token offset of the value part
-        *    valueStartByte    The start byte offset of the value part
-        *    valueEndToken     The end token offset of the value part, plus one
-        *    valueEndByte      The end byte offset of the value part, plus one
-        *    nextArrayIndex    The next numeric array index at this level
-        *    hasComma          True if the array element ends with a comma
-        *    arrowByte         The byte offset of the "=>", or false if there isn't one
-        */
-       var $pathStack;
-
-       /**
-        * The elements of the top of the pathStack for every path encountered, indexed
-        * by slash-separated path.
-        */
-       var $pathInfo;
-
-       /**
-        * Next serial number for whitespace placeholder paths (\@extra-N)
-        */
-       var $serial;
-
-       /**
-        * Editor state. This consists of the internal copy/insert operations which
-        * are applied to the source string to obtain the destination string.
-        */
-       var $edits;
-
-       /**
-        * Simple entry point for command-line testing
-        *
-        * @param $text string
-        *
-        * @return string
-        */
-       static function test( $text ) {
-               try {
-                       $ce = new self( $text );
-                       $ce->parse();
-               } catch ( ConfEditorParseError $e ) {
-                       return $e->getMessage() . "\n" . $e->highlight( $text );
-               }
-               return "OK";
-       }
-
-       /**
-        * Construct a new parser
-        */
-       public function __construct( $text ) {
-               $this->text = $text;
-       }
-
-       /**
-        * Edit the text. Returns the edited text.
-        * @param array $ops of operations.
-        *
-        * Operations are given as an associative array, with members:
-        *    type:     One of delete, set, append or insert (required)
-        *    path:     The path to operate on (required)
-        *    key:      The array key to insert/append, with PHP quotes
-        *    value:    The value, with PHP quotes
-        *
-        * delete
-        *    Deletes an array element or statement with the specified path.
-        *    e.g.
-        *        array('type' => 'delete', 'path' => '$foo/bar/baz' )
-        *    is equivalent to the runtime PHP code:
-        *        unset( $foo['bar']['baz'] );
-        *
-        * set
-        *    Sets the value of an array element. If the element doesn't exist, it
-        *    is appended to the array. If it does exist, the value is set, with
-        *    comments and indenting preserved.
-        *
-        * append
-        *    Appends a new element to the end of the array. Adds a trailing comma.
-        *    e.g.
-        *        array( 'type' => 'append', 'path', '$foo/bar',
-        *            'key' => 'baz', 'value' => "'x'" )
-        *    is like the PHP code:
-        *        $foo['bar']['baz'] = 'x';
-        *
-        * insert
-        *    Insert a new element at the start of the array.
-        *
-        * @throws MWException
-        * @return string
-        */
-       public function edit( $ops ) {
-               $this->parse();
-
-               $this->edits = array(
-                       array( 'copy', 0, strlen( $this->text ) )
-               );
-               foreach ( $ops as $op ) {
-                       $type = $op['type'];
-                       $path = $op['path'];
-                       $value = isset( $op['value'] ) ? $op['value'] : null;
-                       $key = isset( $op['key'] ) ? $op['key'] : null;
-
-                       switch ( $type ) {
-                       case 'delete':
-                               list( $start, $end ) = $this->findDeletionRegion( $path );
-                               $this->replaceSourceRegion( $start, $end, false );
-                               break;
-                       case 'set':
-                               if ( isset( $this->pathInfo[$path] ) ) {
-                                       list( $start, $end ) = $this->findValueRegion( $path );
-                                       $encValue = $value; // var_export( $value, true );
-                                       $this->replaceSourceRegion( $start, $end, $encValue );
-                                       break;
-                               }
-                               // No existing path, fall through to append
-                               $slashPos = strrpos( $path, '/' );
-                               $key = var_export( substr( $path, $slashPos + 1 ), true );
-                               $path = substr( $path, 0, $slashPos );
-                               // Fall through
-                       case 'append':
-                               // Find the last array element
-                               $lastEltPath = $this->findLastArrayElement( $path );
-                               if ( $lastEltPath === false ) {
-                                       throw new MWException( "Can't find any element of array \"$path\"" );
-                               }
-                               $lastEltInfo = $this->pathInfo[$lastEltPath];
-
-                               // Has it got a comma already?
-                               if ( strpos( $lastEltPath, '@extra' ) === false && !$lastEltInfo['hasComma'] ) {
-                                       // No comma, insert one after the value region
-                                       list( , $end ) = $this->findValueRegion( $lastEltPath );
-                                       $this->replaceSourceRegion( $end - 1, $end - 1, ',' );
-                               }
-
-                               // Make the text to insert
-                               list( $start, $end ) = $this->findDeletionRegion( $lastEltPath );
-
-                               if ( $key === null ) {
-                                       list( $indent, ) = $this->getIndent( $start );
-                                       $textToInsert = "$indent$value,";
-                               } else {
-                                       list( $indent, $arrowIndent ) =
-                                               $this->getIndent( $start, $key, $lastEltInfo['arrowByte'] );
-                                       $textToInsert = "$indent$key$arrowIndent=> $value,";
-                               }
-                               $textToInsert .= ( $indent === false ? ' ' : "\n" );
-
-                               // Insert the item
-                               $this->replaceSourceRegion( $end, $end, $textToInsert );
-                               break;
-                       case 'insert':
-                               // Find first array element
-                               $firstEltPath = $this->findFirstArrayElement( $path );
-                               if ( $firstEltPath === false ) {
-                                       throw new MWException( "Can't find array element of \"$path\"" );
-                               }
-                               list( $start, ) = $this->findDeletionRegion( $firstEltPath );
-                               $info = $this->pathInfo[$firstEltPath];
-
-                               // Make the text to insert
-                               if ( $key === null ) {
-                                       list( $indent, ) = $this->getIndent( $start );
-                                       $textToInsert = "$indent$value,";
-                               } else {
-                                       list( $indent, $arrowIndent ) =
-                                               $this->getIndent( $start, $key, $info['arrowByte'] );
-                                       $textToInsert = "$indent$key$arrowIndent=> $value,";
-                               }
-                               $textToInsert .= ( $indent === false ? ' ' : "\n" );
-
-                               // Insert the item
-                               $this->replaceSourceRegion( $start, $start, $textToInsert );
-                               break;
-                       default:
-                               throw new MWException( "Unrecognised operation: \"$type\"" );
-                       }
-               }
-
-               // Do the edits
-               $out = '';
-               foreach ( $this->edits as $edit ) {
-                       if ( $edit[0] == 'copy' ) {
-                               $out .= substr( $this->text, $edit[1], $edit[2] - $edit[1] );
-                       } else { // if ( $edit[0] == 'insert' )
-                               $out .= $edit[1];
-                       }
-               }
-
-               // Do a second parse as a sanity check
-               $this->text = $out;
-               try {
-                       $this->parse();
-               } catch ( ConfEditorParseError $e ) {
-                       throw new MWException(
-                               "Sorry, ConfEditor broke the file during editing and it won't parse anymore: " .
-                               $e->getMessage() );
-               }
-               return $out;
-       }
-
-       /**
-        * Get the variables defined in the text
-        * @return array( varname => value )
-        */
-       function getVars() {
-               $vars = array();
-               $this->parse();
-               foreach ( $this->pathInfo as $path => $data ) {
-                       if ( $path[0] != '$' ) {
-                               continue;
-                       }
-                       $trimmedPath = substr( $path, 1 );
-                       $name = $data['name'];
-                       if ( $name[0] == '@' ) {
-                               continue;
-                       }
-                       if ( $name[0] == '$' ) {
-                               $name = substr( $name, 1 );
-                       }
-                       $parentPath = substr( $trimmedPath, 0,
-                               strlen( $trimmedPath ) - strlen( $name ) );
-                       if ( substr( $parentPath, -1 ) == '/' ) {
-                               $parentPath = substr( $parentPath, 0, -1 );
-                       }
-
-                       $value = substr( $this->text, $data['valueStartByte'],
-                               $data['valueEndByte'] - $data['valueStartByte']
-                       );
-                       $this->setVar( $vars, $parentPath, $name,
-                               $this->parseScalar( $value ) );
-               }
-               return $vars;
-       }
-
-       /**
-        * Set a value in an array, unless it's set already. For instance,
-        * setVar( $arr, 'foo/bar', 'baz', 3 ); will set
-        * $arr['foo']['bar']['baz'] = 3;
-        * @param $array array
-        * @param string $path slash-delimited path
-        * @param $key mixed Key
-        * @param $value mixed Value
-        */
-       function setVar( &$array, $path, $key, $value ) {
-               $pathArr = explode( '/', $path );
-               $target =& $array;
-               if ( $path !== '' ) {
-                       foreach ( $pathArr as $p ) {
-                               if ( !isset( $target[$p] ) ) {
-                                       $target[$p] = array();
-                               }
-                               $target =& $target[$p];
-                       }
-               }
-               if ( !isset( $target[$key] ) ) {
-                       $target[$key] = $value;
-               }
-       }
-
-       /**
-        * Parse a scalar value in PHP
-        * @return mixed Parsed value
-        */
-       function parseScalar( $str ) {
-               if ( $str !== '' && $str[0] == '\'' ) {
-                       // Single-quoted string
-                       // @todo FIXME: trim() call is due to mystery bug where whitespace gets
-                       // appended to the token; without it we ended up reading in the
-                       // extra quote on the end!
-                       return strtr( substr( trim( $str ), 1, -1 ),
-                               array( '\\\'' => '\'', '\\\\' => '\\' ) );
-               }
-               if ( $str !== '' && $str[0] == '"' ) {
-                       // Double-quoted string
-                       // @todo FIXME: trim() call is due to mystery bug where whitespace gets
-                       // appended to the token; without it we ended up reading in the
-                       // extra quote on the end!
-                       return stripcslashes( substr( trim( $str ), 1, -1 ) );
-               }
-               if ( substr( $str, 0, 4 ) == 'true' ) {
-                       return true;
-               }
-               if ( substr( $str, 0, 5 ) == 'false' ) {
-                       return false;
-               }
-               if ( substr( $str, 0, 4 ) == 'null' ) {
-                       return null;
-               }
-               // Must be some kind of numeric value, so let PHP's weak typing
-               // be useful for a change
-               return $str;
-       }
-
-       /**
-        * Replace the byte offset region of the source with $newText.
-        * Works by adding elements to the $this->edits array.
-        */
-       function replaceSourceRegion( $start, $end, $newText = false ) {
-               // Split all copy operations with a source corresponding to the region
-               // in question.
-               $newEdits = array();
-               foreach ( $this->edits as $edit ) {
-                       if ( $edit[0] !== 'copy' ) {
-                               $newEdits[] = $edit;
-                               continue;
-                       }
-                       $copyStart = $edit[1];
-                       $copyEnd = $edit[2];
-                       if ( $start >= $copyEnd || $end <= $copyStart ) {
-                               // Outside this region
-                               $newEdits[] = $edit;
-                               continue;
-                       }
-                       if ( ( $start < $copyStart && $end > $copyStart )
-                               || ( $start < $copyEnd && $end > $copyEnd )
-                       ) {
-                               throw new MWException( "Overlapping regions found, can't do the edit" );
-                       }
-                       // Split the copy
-                       $newEdits[] = array( 'copy', $copyStart, $start );
-                       if ( $newText !== false ) {
-                               $newEdits[] = array( 'insert', $newText );
-                       }
-                       $newEdits[] = array( 'copy', $end, $copyEnd );
-               }
-               $this->edits = $newEdits;
-       }
-
-       /**
-        * Finds the source byte region which you would want to delete, if $pathName
-        * was to be deleted. Includes the leading spaces and tabs, the trailing line
-        * break, and any comments in between.
-        * @param $pathName
-        * @throws MWException
-        * @return array
-        */
-       function findDeletionRegion( $pathName ) {
-               if ( !isset( $this->pathInfo[$pathName] ) ) {
-                       throw new MWException( "Can't find path \"$pathName\"" );
-               }
-               $path = $this->pathInfo[$pathName];
-               // Find the start
-               $this->firstToken();
-               while ( $this->pos != $path['startToken'] ) {
-                       $this->nextToken();
-               }
-               $regionStart = $path['startByte'];
-               for ( $offset = -1; $offset >= -$this->pos; $offset-- ) {
-                       $token = $this->getTokenAhead( $offset );
-                       if ( !$token->isSkip() ) {
-                               // If there is other content on the same line, don't move the start point
-                               // back, because that will cause the regions to overlap.
-                               $regionStart = $path['startByte'];
-                               break;
-                       }
-                       $lfPos = strrpos( $token->text, "\n" );
-                       if ( $lfPos === false ) {
-                               $regionStart -= strlen( $token->text );
-                       } else {
-                               // The line start does not include the LF
-                               $regionStart -= strlen( $token->text ) - $lfPos - 1;
-                               break;
-                       }
-               }
-               // Find the end
-               while ( $this->pos != $path['endToken'] ) {
-                       $this->nextToken();
-               }
-               $regionEnd = $path['endByte']; // past the end
-               for ( $offset = 0; $offset < count( $this->tokens ) - $this->pos; $offset++ ) {
-                       $token = $this->getTokenAhead( $offset );
-                       if ( !$token->isSkip() ) {
-                               break;
-                       }
-                       $lfPos = strpos( $token->text, "\n" );
-                       if ( $lfPos === false ) {
-                               $regionEnd += strlen( $token->text );
-                       } else {
-                               // This should point past the LF
-                               $regionEnd += $lfPos + 1;
-                               break;
-                       }
-               }
-               return array( $regionStart, $regionEnd );
-       }
-
-       /**
-        * Find the byte region in the source corresponding to the value part.
-        * This includes the quotes, but does not include the trailing comma
-        * or semicolon.
-        *
-        * The end position is the past-the-end (end + 1) value as per convention.
-        * @param $pathName
-        * @throws MWException
-        * @return array
-        */
-       function findValueRegion( $pathName ) {
-               if ( !isset( $this->pathInfo[$pathName] ) ) {
-                       throw new MWException( "Can't find path \"$pathName\"" );
-               }
-               $path = $this->pathInfo[$pathName];
-               if ( $path['valueStartByte'] === false || $path['valueEndByte'] === false ) {
-                       throw new MWException( "Can't find value region for path \"$pathName\"" );
-               }
-               return array( $path['valueStartByte'], $path['valueEndByte'] );
-       }
-
-       /**
-        * Find the path name of the last element in the array.
-        * If the array is empty, this will return the \@extra interstitial element.
-        * If the specified path is not found or is not an array, it will return false.
-        * @return bool|int|string
-        */
-       function findLastArrayElement( $path ) {
-               // Try for a real element
-               $lastEltPath = false;
-               foreach ( $this->pathInfo as $candidatePath => $info ) {
-                       $part1 = substr( $candidatePath, 0, strlen( $path ) + 1 );
-                       $part2 = substr( $candidatePath, strlen( $path ) + 1, 1 );
-                       if ( $part2 == '@' ) {
-                               // Do nothing
-                       } elseif ( $part1 == "$path/" ) {
-                               $lastEltPath = $candidatePath;
-                       } elseif ( $lastEltPath !== false ) {
-                               break;
-                       }
-               }
-               if ( $lastEltPath !== false ) {
-                       return $lastEltPath;
-               }
-
-               // Try for an interstitial element
-               $extraPath = false;
-               foreach ( $this->pathInfo as $candidatePath => $info ) {
-                       $part1 = substr( $candidatePath, 0, strlen( $path ) + 1 );
-                       if ( $part1 == "$path/" ) {
-                               $extraPath = $candidatePath;
-                       } elseif ( $extraPath !== false ) {
-                               break;
-                       }
-               }
-               return $extraPath;
-       }
-
-       /**
-        * Find the path name of first element in the array.
-        * If the array is empty, this will return the \@extra interstitial element.
-        * If the specified path is not found or is not an array, it will return false.
-        * @return bool|int|string
-        */
-       function findFirstArrayElement( $path ) {
-               // Try for an ordinary element
-               foreach ( $this->pathInfo as $candidatePath => $info ) {
-                       $part1 = substr( $candidatePath, 0, strlen( $path ) + 1 );
-                       $part2 = substr( $candidatePath, strlen( $path ) + 1, 1 );
-                       if ( $part1 == "$path/" && $part2 != '@' ) {
-                               return $candidatePath;
-                       }
-               }
-
-               // Try for an interstitial element
-               foreach ( $this->pathInfo as $candidatePath => $info ) {
-                       $part1 = substr( $candidatePath, 0, strlen( $path ) + 1 );
-                       if ( $part1 == "$path/" ) {
-                               return $candidatePath;
-                       }
-               }
-               return false;
-       }
-
-       /**
-        * Get the indent string which sits after a given start position.
-        * Returns false if the position is not at the start of the line.
-        * @return array
-        */
-       function getIndent( $pos, $key = false, $arrowPos = false ) {
-               $arrowIndent = ' ';
-               if ( $pos == 0 || $this->text[$pos - 1] == "\n" ) {
-                       $indentLength = strspn( $this->text, " \t", $pos );
-                       $indent = substr( $this->text, $pos, $indentLength );
-               } else {
-                       $indent = false;
-               }
-               if ( $indent !== false && $arrowPos !== false ) {
-                       $arrowIndentLength = $arrowPos - $pos - $indentLength - strlen( $key );
-                       if ( $arrowIndentLength > 0 ) {
-                               $arrowIndent = str_repeat( ' ', $arrowIndentLength );
-                       }
-               }
-               return array( $indent, $arrowIndent );
-       }
-
-       /**
-        * Run the parser on the text. Throws an exception if the string does not
-        * match our defined subset of PHP syntax.
-        */
-       public function parse() {
-               $this->initParse();
-               $this->pushState( 'file' );
-               $this->pushPath( '@extra-' . ( $this->serial++ ) );
-               $token = $this->firstToken();
-
-               while ( !$token->isEnd() ) {
-                       $state = $this->popState();
-                       if ( !$state ) {
-                               $this->error( 'internal error: empty state stack' );
-                       }
-
-                       switch ( $state ) {
-                       case 'file':
-                               $this->expect( T_OPEN_TAG );
-                               $token = $this->skipSpace();
-                               if ( $token->isEnd() ) {
-                                       break 2;
-                               }
-                               $this->pushState( 'statement', 'file 2' );
-                               break;
-                       case 'file 2':
-                               $token = $this->skipSpace();
-                               if ( $token->isEnd() ) {
-                                       break 2;
-                               }
-                               $this->pushState( 'statement', 'file 2' );
-                               break;
-                       case 'statement':
-                               $token = $this->skipSpace();
-                               if ( !$this->validatePath( $token->text ) ) {
-                                       $this->error( "Invalid variable name \"{$token->text}\"" );
-                               }
-                               $this->nextPath( $token->text );
-                               $this->expect( T_VARIABLE );
-                               $this->skipSpace();
-                               $arrayAssign = false;
-                               if ( $this->currentToken()->type == '[' ) {
-                                       $this->nextToken();
-                                       $token = $this->skipSpace();
-                                       if ( !$token->isScalar() ) {
-                                               $this->error( "expected a string or number for the array key" );
-                                       }
-                                       if ( $token->type == T_CONSTANT_ENCAPSED_STRING ) {
-                                               $text = $this->parseScalar( $token->text );
-                                       } else {
-                                               $text = $token->text;
-                                       }
-                                       if ( !$this->validatePath( $text ) ) {
-                                               $this->error( "Invalid associative array name \"$text\"" );
-                                       }
-                                       $this->pushPath( $text );
-                                       $this->nextToken();
-                                       $this->skipSpace();
-                                       $this->expect( ']' );
-                                       $this->skipSpace();
-                                       $arrayAssign = true;
-                               }
-                               $this->expect( '=' );
-                               $this->skipSpace();
-                               $this->startPathValue();
-                               if ( $arrayAssign ) {
-                                       $this->pushState( 'expression', 'array assign end' );
-                               } else {
-                                       $this->pushState( 'expression', 'statement end' );
-                               }
-                               break;
-                       case 'array assign end':
-                       case 'statement end':
-                               $this->endPathValue();
-                               if ( $state == 'array assign end' ) {
-                                       $this->popPath();
-                               }
-                               $this->skipSpace();
-                               $this->expect( ';' );
-                               $this->nextPath( '@extra-' . ( $this->serial++ ) );
-                               break;
-                       case 'expression':
-                               $token = $this->skipSpace();
-                               if ( $token->type == T_ARRAY ) {
-                                       $this->pushState( 'array' );
-                               } elseif ( $token->isScalar() ) {
-                                       $this->nextToken();
-                               } elseif ( $token->type == T_VARIABLE ) {
-                                       $this->nextToken();
-                               } else {
-                                       $this->error( "expected simple expression" );
-                               }
-                               break;
-                       case 'array':
-                               $this->skipSpace();
-                               $this->expect( T_ARRAY );
-                               $this->skipSpace();
-                               $this->expect( '(' );
-                               $this->skipSpace();
-                               $this->pushPath( '@extra-' . ( $this->serial++ ) );
-                               if ( $this->isAhead( ')' ) ) {
-                                       // Empty array
-                                       $this->pushState( 'array end' );
-                               } else {
-                                       $this->pushState( 'element', 'array end' );
-                               }
-                               break;
-                       case 'array end':
-                               $this->skipSpace();
-                               $this->popPath();
-                               $this->expect( ')' );
-                               break;
-                       case 'element':
-                               $token = $this->skipSpace();
-                               // Look ahead to find the double arrow
-                               if ( $token->isScalar() && $this->isAhead( T_DOUBLE_ARROW, 1 ) ) {
-                                       // Found associative element
-                                       $this->pushState( 'assoc-element', 'element end' );
-                               } else {
-                                       // Not associative
-                                       $this->nextPath( '@next' );
-                                       $this->startPathValue();
-                                       $this->pushState( 'expression', 'element end' );
-                               }
-                               break;
-                       case 'element end':
-                               $token = $this->skipSpace();
-                               if ( $token->type == ',' ) {
-                                       $this->endPathValue();
-                                       $this->markComma();
-                                       $this->nextToken();
-                                       $this->nextPath( '@extra-' . ( $this->serial++ ) );
-                                       // Look ahead to find ending bracket
-                                       if ( $this->isAhead( ")" ) ) {
-                                               // Found ending bracket, no continuation
-                                               $this->skipSpace();
-                                       } else {
-                                               // No ending bracket, continue to next element
-                                               $this->pushState( 'element' );
-                                       }
-                               } elseif ( $token->type == ')' ) {
-                                       // End array
-                                       $this->endPathValue();
-                               } else {
-                                       $this->error( "expected the next array element or the end of the array" );
-                               }
-                               break;
-                       case 'assoc-element':
-                               $token = $this->skipSpace();
-                               if ( !$token->isScalar() ) {
-                                       $this->error( "expected a string or number for the array key" );
-                               }
-                               if ( $token->type == T_CONSTANT_ENCAPSED_STRING ) {
-                                       $text = $this->parseScalar( $token->text );
-                               } else {
-                                       $text = $token->text;
-                               }
-                               if ( !$this->validatePath( $text ) ) {
-                                       $this->error( "Invalid associative array name \"$text\"" );
-                               }
-                               $this->nextPath( $text );
-                               $this->nextToken();
-                               $this->skipSpace();
-                               $this->markArrow();
-                               $this->expect( T_DOUBLE_ARROW );
-                               $this->skipSpace();
-                               $this->startPathValue();
-                               $this->pushState( 'expression' );
-                               break;
-                       }
-               }
-               if ( count( $this->stateStack ) ) {
-                       $this->error( 'unexpected end of file' );
-               }
-               $this->popPath();
-       }
-
-       /**
-        * Initialise a parse.
-        */
-       protected function initParse() {
-               $this->tokens = token_get_all( $this->text );
-               $this->stateStack = array();
-               $this->pathStack = array();
-               $this->firstToken();
-               $this->pathInfo = array();
-               $this->serial = 1;
-       }
-
-       /**
-        * Set the parse position. Do not call this except from firstToken() and
-        * nextToken(), there is more to update than just the position.
-        */
-       protected function setPos( $pos ) {
-               $this->pos = $pos;
-               if ( $this->pos >= count( $this->tokens ) ) {
-                       $this->currentToken = ConfEditorToken::newEnd();
-               } else {
-                       $this->currentToken = $this->newTokenObj( $this->tokens[$this->pos] );
-               }
-               return $this->currentToken;
-       }
-
-       /**
-        * Create a ConfEditorToken from an element of token_get_all()
-        * @return ConfEditorToken
-        */
-       function newTokenObj( $internalToken ) {
-               if ( is_array( $internalToken ) ) {
-                       return new ConfEditorToken( $internalToken[0], $internalToken[1] );
-               } else {
-                       return new ConfEditorToken( $internalToken, $internalToken );
-               }
-       }
-
-       /**
-        * Reset the parse position
-        */
-       function firstToken() {
-               $this->setPos( 0 );
-               $this->prevToken = ConfEditorToken::newEnd();
-               $this->lineNum = 1;
-               $this->colNum = 1;
-               $this->byteNum = 0;
-               return $this->currentToken;
-       }
-
-       /**
-        * Get the current token
-        */
-       function currentToken() {
-               return $this->currentToken;
-       }
-
-       /**
-        * Advance the current position and return the resulting next token
-        */
-       function nextToken() {
-               if ( $this->currentToken ) {
-                       $text = $this->currentToken->text;
-                       $lfCount = substr_count( $text, "\n" );
-                       if ( $lfCount ) {
-                               $this->lineNum += $lfCount;
-                               $this->colNum = strlen( $text ) - strrpos( $text, "\n" );
-                       } else {
-                               $this->colNum += strlen( $text );
-                       }
-                       $this->byteNum += strlen( $text );
-               }
-               $this->prevToken = $this->currentToken;
-               $this->setPos( $this->pos + 1 );
-               return $this->currentToken;
-       }
-
-       /**
-        * Get the token $offset steps ahead of the current position.
-        * $offset may be negative, to get tokens behind the current position.
-        * @return ConfEditorToken
-        */
-       function getTokenAhead( $offset ) {
-               $pos = $this->pos + $offset;
-               if ( $pos >= count( $this->tokens ) || $pos < 0 ) {
-                       return ConfEditorToken::newEnd();
-               } else {
-                       return $this->newTokenObj( $this->tokens[$pos] );
-               }
-       }
-
-       /**
-        * Advances the current position past any whitespace or comments
-        */
-       function skipSpace() {
-               while ( $this->currentToken && $this->currentToken->isSkip() ) {
-                       $this->nextToken();
-               }
-               return $this->currentToken;
-       }
-
-       /**
-        * Throws an error if the current token is not of the given type, and
-        * then advances to the next position.
-        */
-       function expect( $type ) {
-               if ( $this->currentToken && $this->currentToken->type == $type ) {
-                       return $this->nextToken();
-               } else {
-                       $this->error( "expected " . $this->getTypeName( $type ) .
-                               ", got " . $this->getTypeName( $this->currentToken->type ) );
-               }
-       }
-
-       /**
-        * Push a state or two on to the state stack.
-        */
-       function pushState( $nextState, $stateAfterThat = null ) {
-               if ( $stateAfterThat !== null ) {
-                       $this->stateStack[] = $stateAfterThat;
-               }
-               $this->stateStack[] = $nextState;
-       }
-
-       /**
-        * Pop a state from the state stack.
-        * @return mixed
-        */
-       function popState() {
-               return array_pop( $this->stateStack );
-       }
-
-       /**
-        * Returns true if the user input path is valid.
-        * This exists to allow "/" and "@" to be reserved for string path keys
-        * @return bool
-        */
-       function validatePath( $path ) {
-               return strpos( $path, '/' ) === false && substr( $path, 0, 1 ) != '@';
-       }
-
-       /**
-        * Internal function to update some things at the end of a path region. Do
-        * not call except from popPath() or nextPath().
-        */
-       function endPath() {
-               $key = '';
-               foreach ( $this->pathStack as $pathInfo ) {
-                       if ( $key !== '' ) {
-                               $key .= '/';
-                       }
-                       $key .= $pathInfo['name'];
-               }
-               $pathInfo['endByte'] = $this->byteNum;
-               $pathInfo['endToken'] = $this->pos;
-               $this->pathInfo[$key] = $pathInfo;
-       }
-
-       /**
-        * Go up to a new path level, for example at the start of an array.
-        */
-       function pushPath( $path ) {
-               $this->pathStack[] = array(
-                       'name' => $path,
-                       'level' => count( $this->pathStack ) + 1,
-                       'startByte' => $this->byteNum,
-                       'startToken' => $this->pos,
-                       'valueStartToken' => false,
-                       'valueStartByte' => false,
-                       'valueEndToken' => false,
-                       'valueEndByte' => false,
-                       'nextArrayIndex' => 0,
-                       'hasComma' => false,
-                       'arrowByte' => false
-               );
-       }
-
-       /**
-        * Go down a path level, for example at the end of an array.
-        */
-       function popPath() {
-               $this->endPath();
-               array_pop( $this->pathStack );
-       }
-
-       /**
-        * Go to the next path on the same level. This ends the current path and
-        * starts a new one. If $path is \@next, the new path is set to the next
-        * numeric array element.
-        */
-       function nextPath( $path ) {
-               $this->endPath();
-               $i = count( $this->pathStack ) - 1;
-               if ( $path == '@next' ) {
-                       $nextArrayIndex =& $this->pathStack[$i]['nextArrayIndex'];
-                       $this->pathStack[$i]['name'] = $nextArrayIndex;
-                       $nextArrayIndex++;
-               } else {
-                       $this->pathStack[$i]['name'] = $path;
-               }
-               $this->pathStack[$i] =
-                       array(
-                               'startByte' => $this->byteNum,
-                               'startToken' => $this->pos,
-                               'valueStartToken' => false,
-                               'valueStartByte' => false,
-                               'valueEndToken' => false,
-                               'valueEndByte' => false,
-                               'hasComma' => false,
-                               'arrowByte' => false,
-                       ) + $this->pathStack[$i];
-       }
-
-       /**
-        * Mark the start of the value part of a path.
-        */
-       function startPathValue() {
-               $path =& $this->pathStack[count( $this->pathStack ) - 1];
-               $path['valueStartToken'] = $this->pos;
-               $path['valueStartByte'] = $this->byteNum;
-       }
-
-       /**
-        * Mark the end of the value part of a path.
-        */
-       function endPathValue() {
-               $path =& $this->pathStack[count( $this->pathStack ) - 1];
-               $path['valueEndToken'] = $this->pos;
-               $path['valueEndByte'] = $this->byteNum;
-       }
-
-       /**
-        * Mark the comma separator in an array element
-        */
-       function markComma() {
-               $path =& $this->pathStack[count( $this->pathStack ) - 1];
-               $path['hasComma'] = true;
-       }
-
-       /**
-        * Mark the arrow separator in an associative array element
-        */
-       function markArrow() {
-               $path =& $this->pathStack[count( $this->pathStack ) - 1];
-               $path['arrowByte'] = $this->byteNum;
-       }
-
-       /**
-        * Generate a parse error
-        */
-       function error( $msg ) {
-               throw new ConfEditorParseError( $this, $msg );
-       }
-
-       /**
-        * Get a readable name for the given token type.
-        * @return string
-        */
-       function getTypeName( $type ) {
-               if ( is_int( $type ) ) {
-                       return token_name( $type );
-               } else {
-                       return "\"$type\"";
-               }
-       }
-
-       /**
-        * Looks ahead to see if the given type is the next token type, starting
-        * from the current position plus the given offset. Skips any intervening
-        * whitespace.
-        * @return bool
-        */
-       function isAhead( $type, $offset = 0 ) {
-               $ahead = $offset;
-               $token = $this->getTokenAhead( $offset );
-               while ( !$token->isEnd() ) {
-                       if ( $token->isSkip() ) {
-                               $ahead++;
-                               $token = $this->getTokenAhead( $ahead );
-                               continue;
-                       } elseif ( $token->type == $type ) {
-                               // Found the type
-                               return true;
-                       } else {
-                               // Not found
-                               return false;
-                       }
-               }
-               return false;
-       }
-
-       /**
-        * Get the previous token object
-        */
-       function prevToken() {
-               return $this->prevToken;
-       }
-
-       /**
-        * Echo a reasonably readable representation of the tokenizer array.
-        */
-       function dumpTokens() {
-               $out = '';
-               foreach ( $this->tokens as $token ) {
-                       $obj = $this->newTokenObj( $token );
-                       $out .= sprintf( "%-28s %s\n",
-                               $this->getTypeName( $obj->type ),
-                               addcslashes( $obj->text, "\0..\37" ) );
-               }
-               echo "<pre>" . htmlspecialchars( $out ) . "</pre>";
-       }
-}
-
-/**
- * Exception class for parse errors
- */
-class ConfEditorParseError extends MWException {
-       var $lineNum, $colNum;
-       function __construct( $editor, $msg ) {
-               $this->lineNum = $editor->lineNum;
-               $this->colNum = $editor->colNum;
-               parent::__construct( "Parse error on line {$editor->lineNum} " .
-                       "col {$editor->colNum}: $msg" );
-       }
-
-       function highlight( $text ) {
-               $lines = StringUtils::explode( "\n", $text );
-               foreach ( $lines as $lineNum => $line ) {
-                       if ( $lineNum == $this->lineNum - 1 ) {
-                               return "$line\n" . str_repeat( ' ', $this->colNum - 1 ) . "^\n";
-                       }
-               }
-               return '';
-       }
-
-}
-
-/**
- * Class to wrap a token from the tokenizer.
- */
-class ConfEditorToken {
-       var $type, $text;
-
-       static $scalarTypes = array( T_LNUMBER, T_DNUMBER, T_STRING, T_CONSTANT_ENCAPSED_STRING );
-       static $skipTypes = array( T_WHITESPACE, T_COMMENT, T_DOC_COMMENT );
-
-       static function newEnd() {
-               return new self( 'END', '' );
-       }
-
-       function __construct( $type, $text ) {
-               $this->type = $type;
-               $this->text = $text;
-       }
-
-       function isSkip() {
-               return in_array( $this->type, self::$skipTypes );
-       }
-
-       function isScalar() {
-               return in_array( $this->type, self::$scalarTypes );
-       }
-
-       function isEnd() {
-               return $this->type == 'END';
-       }
-}
diff --git a/includes/DataUpdate.php b/includes/DataUpdate.php
deleted file mode 100644 (file)
index 7b9ac28..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-<?php
-/**
- * Base code for update jobs that do something with some secondary
- * data extracted from article.
- *
- * 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
- */
-
-/**
- * Abstract base class for update jobs that do something with some secondary
- * data extracted from article.
- *
- * @note: subclasses should NOT start or commit transactions in their doUpdate() method,
- *        a transaction will automatically be wrapped around the update. If need be,
- *        subclasses can override the beginTransaction() and commitTransaction() methods.
- */
-abstract class DataUpdate implements DeferrableUpdate {
-
-       /**
-        * Constructor
-        */
-       public function __construct() {
-               # noop
-       }
-
-       /**
-        * Begin an appropriate transaction, if any.
-        * This default implementation does nothing.
-        */
-       public function beginTransaction() {
-               //noop
-       }
-
-       /**
-        * Commit the transaction started via beginTransaction, if any.
-        * This default implementation does nothing.
-        */
-       public function commitTransaction() {
-               //noop
-       }
-
-       /**
-        * Abort / roll back the transaction started via beginTransaction, if any.
-        * This default implementation does nothing.
-        */
-       public function rollbackTransaction() {
-               //noop
-       }
-
-       /**
-        * Convenience method, calls doUpdate() on every DataUpdate in the array.
-        *
-        * This methods supports transactions logic by first calling beginTransaction()
-        * on all updates in the array, then calling doUpdate() on each, and, if all goes well,
-        * then calling commitTransaction() on each update. If an error occurs,
-        * rollbackTransaction() will be called on any update object that had beginTransaction()
-        * called but not yet commitTransaction().
-        *
-        * This allows for limited transactional logic across multiple backends for storing
-        * secondary data.
-        *
-        * @param array $updates a list of DataUpdate instances
-        * @throws Exception|null
-        */
-       public static function runUpdates( $updates ) {
-               if ( empty( $updates ) ) {
-                       return; # nothing to do
-               }
-
-               $open_transactions = array();
-               $exception = null;
-
-               /**
-                * @var $update DataUpdate
-                * @var $trans DataUpdate
-                */
-
-               try {
-                       // begin transactions
-                       foreach ( $updates as $update ) {
-                               $update->beginTransaction();
-                               $open_transactions[] = $update;
-                       }
-
-                       // do work
-                       foreach ( $updates as $update ) {
-                               $update->doUpdate();
-                       }
-
-                       // commit transactions
-                       while ( count( $open_transactions ) > 0 ) {
-                               $trans = array_pop( $open_transactions );
-                               $trans->commitTransaction();
-                       }
-               } catch ( Exception $ex ) {
-                       $exception = $ex;
-                       wfDebug( "Caught exception, will rethrow after rollback: " . $ex->getMessage() );
-               }
-
-               // rollback remaining transactions
-               while ( count( $open_transactions ) > 0 ) {
-                       $trans = array_pop( $open_transactions );
-                       $trans->rollbackTransaction();
-               }
-
-               if ( $exception ) {
-                       throw $exception; // rethrow after cleanup
-               }
-       }
-
-}
index bf2d2fd..ebae110 100644 (file)
@@ -3322,6 +3322,22 @@ $wgResourceLoaderLESSImportPaths = array(
        "$IP/resources/mediawiki.less/",
 );
 
+/**
+ * Whether ResourceLoader should attempt to persist modules in localStorage on
+ * browsers that support the Web Storage API.
+ *
+ * @since 1.23 - Client-side module persistence is experimental. Exercise care.
+ */
+$wgResourceLoaderStorageEnabled = false;
+
+/**
+ * Cache version for client-side ResourceLoader module storage. You can trigger
+ * invalidation of the contents of the module store by incrementing this value.
+ *
+ * @since 1.23
+ */
+$wgResourceLoaderStorageVersion = 1;
+
 /** @} */ # End of resource loader settings }
 
 /*************************************************************************//**
@@ -4924,37 +4940,6 @@ $wgShowSQLErrors = false;
  */
 $wgShowExceptionDetails = false;
 
-/**
- * Array of functions which need parameters redacted from stack traces shown to
- * clients and logged. Keys are in the format '[class::]function', and the
- * values should be either an integer or an array of integers. These are the
- * indexes of the parameters which need to be kept secret.
- * @since 1.22
- */
-$wgRedactedFunctionArguments = array(
-       'AuthPlugin::setPassword' => 1,
-       'AuthPlugin::authenticate' => 1,
-       'AuthPlugin::addUser' => 1,
-
-       'DatabaseBase::__construct' => 2,
-       'DatabaseBase::open' => 2,
-
-       'SpecialChangeEmail::attemptChange' => 1,
-       'SpecialChangePassword::attemptReset' => 0,
-
-       'User::setPassword' => 0,
-       'User::setInternalPassword' => 0,
-       'User::checkPassword' => 0,
-       'User::setNewpassword' => 0,
-       'User::comparePasswords' => array( 0, 1 ),
-       'User::checkTemporaryPassword' => 0,
-       'User::setToken' => 0,
-       'User::crypt' => 0,
-       'User::oldCrypt' => 0,
-       'User::getPasswordValidity' => 0,
-       'User::isValidPassword' => 0,
-);
-
 /**
  * If true, show a backtrace for database errors
  */
diff --git a/includes/DeferredUpdates.php b/includes/DeferredUpdates.php
deleted file mode 100644 (file)
index a321414..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-<?php
-/**
- * Interface and manager for deferred updates.
- *
- * 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
- */
-
-/**
- * Interface that deferrable updates should implement. Basically required so we
- * can validate input on DeferredUpdates::addUpdate()
- *
- * @since 1.19
- */
-interface DeferrableUpdate {
-       /**
-        * Perform the actual work
-        */
-       function doUpdate();
-}
-
-/**
- * Class for managing the deferred updates.
- *
- * @since 1.19
- */
-class DeferredUpdates {
-       /**
-        * Store of updates to be deferred until the end of the request.
-        */
-       private static $updates = array();
-
-       /**
-        * Add an update to the deferred list
-        * @param $update DeferrableUpdate Some object that implements doUpdate()
-        */
-       public static function addUpdate( DeferrableUpdate $update ) {
-               array_push( self::$updates, $update );
-       }
-
-       /**
-        * HTMLCacheUpdates are the most common deferred update people use. This
-        * is a shortcut method for that.
-        * @see HTMLCacheUpdate::__construct()
-        * @param $title
-        * @param $table
-        */
-       public static function addHTMLCacheUpdate( $title, $table ) {
-               self::addUpdate( new HTMLCacheUpdate( $title, $table ) );
-       }
-
-       /**
-        * Add a callable update.  In a lot of cases, we just need a callback/closure,
-        * defining a new DeferrableUpdate object is not necessary
-        * @see MWCallableUpdate::__construct()
-        * @param callable $callable
-        */
-       public static function addCallableUpdate( $callable ) {
-               self::addUpdate( new MWCallableUpdate( $callable ) );
-       }
-
-       /**
-        * Do any deferred updates and clear the list
-        *
-        * @param string $commit set to 'commit' to commit after every update to
-        *                prevent lock contention
-        */
-       public static function doUpdates( $commit = '' ) {
-               global $wgDeferredUpdateList;
-
-               wfProfileIn( __METHOD__ );
-
-               $updates = array_merge( $wgDeferredUpdateList, self::$updates );
-
-               // No need to get master connections in case of empty updates array
-               if ( !count( $updates ) ) {
-                       wfProfileOut( __METHOD__ );
-                       return;
-               }
-
-               $doCommit = $commit == 'commit';
-               if ( $doCommit ) {
-                       $dbw = wfGetDB( DB_MASTER );
-               }
-
-               foreach ( $updates as $update ) {
-                       try {
-                               $update->doUpdate();
-
-                               if ( $doCommit && $dbw->trxLevel() ) {
-                                       $dbw->commit( __METHOD__, 'flush' );
-                               }
-                       } catch ( MWException $e ) {
-                               // We don't want exceptions thrown during deferred updates to
-                               // be reported to the user since the output is already sent.
-                               // Instead we just log them.
-                               if ( !$e instanceof ErrorPageError ) {
-                                       wfDebugLog( 'exception', $e->getLogMessage() );
-                               }
-                       }
-               }
-
-               self::clearPendingUpdates();
-               wfProfileOut( __METHOD__ );
-       }
-
-       /**
-        * Clear all pending updates without performing them. Generally, you don't
-        * want or need to call this. Unit tests need it though.
-        */
-       public static function clearPendingUpdates() {
-               global $wgDeferredUpdateList;
-               $wgDeferredUpdateList = self::$updates = array();
-       }
-}
index d48bd0b..8a63786 100644 (file)
@@ -23,7 +23,6 @@
 /**
  * Class to allow throwing wfDeprecated warnings
  * when people use globals that we do not want them to.
- * (For example like $wgArticle)
  */
 
 class DeprecatedGlobal extends StubObject {
index a91f865..ac98564 100644 (file)
@@ -30,7 +30,6 @@
  * @ingroup Exception
  */
 class MWException extends Exception {
-
        /**
         * Should the exception use $wgOut to output the error?
         *
@@ -43,6 +42,16 @@ class MWException extends Exception {
                        !empty( $GLOBALS['wgTitle'] );
        }
 
+       /**
+        * Whether to log this exception in the exception debug log.
+        *
+        * @since 1.23
+        * @return boolean
+        */
+       function isLoggable() {
+               return true;
+       }
+
        /**
         * Can the extension use the Message class/wfMessage to get i18n-ed messages?
         *
@@ -74,7 +83,9 @@ class MWException extends Exception {
                        return null; // Just silently ignore
                }
 
-               if ( !array_key_exists( $name, $wgExceptionHooks ) || !is_array( $wgExceptionHooks[$name] ) ) {
+               if ( !array_key_exists( $name, $wgExceptionHooks ) ||
+                       !is_array( $wgExceptionHooks[$name] )
+               ) {
                        return null;
                }
 
@@ -82,7 +93,11 @@ class MWException extends Exception {
                $callargs = array_merge( array( $this ), $args );
 
                foreach ( $hooks as $hook ) {
-                       if ( is_string( $hook ) || ( is_array( $hook ) && count( $hook ) >= 2 && is_string( $hook[0] ) ) ) { // 'function' or array( 'class', hook' )
+                       if (
+                               is_string( $hook ) ||
+                               ( is_array( $hook ) && count( $hook ) >= 2 && is_string( $hook[0] ) )
+                       ) {
+                               // 'function' or array( 'class', hook' )
                                $result = call_user_func_array( $hook, $callargs );
                        } else {
                                $result = null;
@@ -125,8 +140,8 @@ class MWException extends Exception {
                global $wgShowExceptionDetails;
 
                if ( $wgShowExceptionDetails ) {
-                       return '<p>' . nl2br( htmlspecialchars( $this->getMessage() ) ) .
-                               '</p><p>Backtrace:</p><p>' . nl2br( htmlspecialchars( MWExceptionHandler::formatRedactedTrace( $this ) ) ) .
+                       return '<p>' . nl2br( htmlspecialchars( MWExceptionHandler::getLogMessage( $this ) ) ) .
+                               '</p><p>Backtrace:</p><p>' . nl2br( htmlspecialchars( MWExceptionHandler::getRedactedTraceAsString( $this ) ) ) .
                                "</p>\n";
                } else {
                        return "<div class=\"errorbox\">" .
@@ -150,8 +165,8 @@ class MWException extends Exception {
                global $wgShowExceptionDetails;
 
                if ( $wgShowExceptionDetails ) {
-                       return $this->getMessage() .
-                               "\nBacktrace:\n" . MWExceptionHandler::formatRedactedTrace( $this ) . "\n";
+                       return MWExceptionHandler::getLogMessage( $this ) .
+                               "\nBacktrace:\n" . MWExceptionHandler::getRedactedTraceAsString( $this ) . "\n";
                } else {
                        return "Set \$wgShowExceptionDetails = true; " .
                                "in LocalSettings.php to show detailed debugging information.\n";
@@ -164,7 +179,8 @@ class MWException extends Exception {
         * @return string
         */
        function getPageTitle() {
-               return $this->msg( 'internalerror', "Internal error" );
+               global $wgSitename;
+               return $this->msg( 'pagetitle', "$1 - $wgSitename", $this->msg( 'internalerror', 'Internal error' ) );
        }
 
        /**
@@ -209,13 +225,14 @@ class MWException extends Exception {
 
                        $wgOut->output();
                } else {
-                       header( "Content-Type: text/html; charset=utf-8" );
-                       echo "<!doctype html>\n" .
+                       header( 'Content-Type: text/html; charset=utf-8' );
+                       echo "<!DOCTYPE html>\n" .
                                '<html><head>' .
                                '<title>' . htmlspecialchars( $this->getPageTitle() ) . '</title>' .
+                               '<style>body { font-family: sans-serif; margin: 0; padding: 0.5em 2em; }</style>' .
                                "</head><body>\n";
 
-                       $hookResult = $this->runHooks( get_class( $this ) . "Raw" );
+                       $hookResult = $this->runHooks( get_class( $this ) . 'Raw' );
                        if ( $hookResult ) {
                                echo $hookResult;
                        } else {
@@ -242,8 +259,8 @@ class MWException extends Exception {
                } elseif ( self::isCommandLine() ) {
                        MWExceptionHandler::printError( $this->getText() );
                } else {
-                       header( "HTTP/1.1 500 MediaWiki exception" );
-                       header( "Status: 500 MediaWiki exception", true );
+                       header( 'HTTP/1.1 500 MediaWiki exception' );
+                       header( 'Status: 500 MediaWiki exception', true );
                        header( "Content-Type: $wgMimeType; charset=utf-8", true );
 
                        $this->reportHTML();
@@ -480,11 +497,11 @@ class UserBlockedError extends ErrorPageError {
 class UserNotLoggedIn extends ErrorPageError {
 
        /**
-        * @param $reasonMsg A message key containing the reason for the error.
+        * @param string $reasonMsg A message key containing the reason for the error.
         *        Optional, default: 'exception-nologin-text'
-        * @param $titleMsg A message key to set the page title.
+        * @param string $titleMsg A message key to set the page title.
         *        Optional, default: 'exception-nologin'
-        * @param $params Parameters to wfMessage().
+        * @param array $params Parameters to wfMessage().
         *        Optional, default: null
         */
        public function __construct(
@@ -601,8 +618,10 @@ class MWExceptionHandler {
                                $message = "MediaWiki internal error.\n\n";
 
                                if ( $wgShowExceptionDetails ) {
-                                       $message .= 'Original exception: ' . self::formatRedactedTrace( $e ) . "\n\n" .
-                                               'Exception caught inside exception handler: ' . $e2->__toString();
+                                       $message .= 'Original exception: ' . self::getLogMessage( $e ) .
+                                                "\nBacktrace:\n" . self::getRedactedTraceAsString( $e ) .
+                                                "\n\nException caught inside exception handler: " . self::getLogMessage( $e2 ) .
+                                                "\nBacktrace:\n" . self::getRedactedTraceAsString( $e2 );
                                } else {
                                        $message .= "Exception caught inside exception handler.\n\n" .
                                                "Set \$wgShowExceptionDetails = true; at the bottom of LocalSettings.php " .
@@ -618,10 +637,12 @@ class MWExceptionHandler {
                                }
                        }
                } else {
-                       $message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\"";
+                       $message = "Unexpected non-MediaWiki exception encountered, of type \"" .
+                               get_class( $e ) . "\"";
 
                        if ( $wgShowExceptionDetails ) {
-                               $message .= "\nexception '" . get_class( $e ) . "' in " . $e->getFile() . ":" . $e->getLine() . "\nStack trace:\n" . self::formatRedactedTrace( $e ) . "\n";
+                               $message .= "\n" . MWExceptionHandler::getLogMessage( $e ) . "\nBacktrace:\n" .
+                                       self::getRedactedTraceAsString( $e ) . "\n";
                        }
 
                        if ( $cmdLine ) {
@@ -639,8 +660,9 @@ class MWExceptionHandler {
         * @param string $message Failure text
         */
        public static function printError( $message ) {
-               # NOTE: STDERR may not be available, especially if php-cgi is used from the command line (bug #15602).
-               #      Try to produce meaningful output anyway. Using echo may corrupt output to STDOUT though.
+               # NOTE: STDERR may not be available, especially if php-cgi is used from the
+               # command line (bug #15602). Try to produce meaningful output anyway. Using
+               # echo may corrupt output to STDOUT though.
                if ( defined( 'STDERR' ) ) {
                        fwrite( STDERR, $message );
                } else {
@@ -678,69 +700,67 @@ class MWExceptionHandler {
        }
 
        /**
-        * Get the stack trace from the exception as a string, redacting certain function arguments in the process
-        * @param Exception $e The exception
-        * @return string The stack trace as a string
+        * Generate a string representation of an exception's stack trace
+        *
+        * Like Exception::getTraceAsString, but replaces argument values with
+        * argument type or class name.
+        *
+        * @param Exception $e
+        * @return string
         */
-       public static function formatRedactedTrace( Exception $e ) {
-               global $wgRedactedFunctionArguments;
-               $finalExceptionText = '';
+       public static function getRedactedTraceAsString( Exception $e ) {
+               $text = '';
 
-               // Unique value to indicate redacted parameters
-               $redacted = new stdClass();
-
-               foreach ( $e->getTrace() as $i => $call ) {
-                       $checkFor = array();
-                       if ( isset( $call['class'] ) ) {
-                               $checkFor[] = $call['class'] . '::' . $call['function'];
-                               foreach ( class_parents( $call['class'] ) as $parent ) {
-                                       $checkFor[] = $parent . '::' . $call['function'];
-                               }
-                       } else {
-                               $checkFor[] = $call['function'];
-                       }
-
-                       foreach ( $checkFor as $check ) {
-                               if ( isset( $wgRedactedFunctionArguments[$check] ) ) {
-                                       foreach ( (array)$wgRedactedFunctionArguments[$check] as $argNo ) {
-                                               $call['args'][$argNo] = $redacted;
-                                       }
-                               }
-                       }
-
-                       if ( isset( $call['file'] ) && isset( $call['line'] ) ) {
-                               $finalExceptionText .= "#{$i} {$call['file']}({$call['line']}): ";
+               foreach ( self::getRedactedTrace( $e ) as $level => $frame ) {
+                       if ( isset( $frame['file'] ) && isset( $frame['line'] ) ) {
+                               $text .= "#{$level} {$frame['file']}({$frame['line']}): ";
                        } else {
                                // 'file' and 'line' are unset for calls via call_user_func (bug 55634)
                                // This matches behaviour of Exception::getTraceAsString to instead
                                // display "[internal function]".
-                               $finalExceptionText .= "#{$i} [internal function]: ";
+                               $text .= "#{$level} [internal function]: ";
                        }
 
-                       if ( isset( $call['class'] ) ) {
-                               $finalExceptionText .= $call['class'] . $call['type'] . $call['function'];
+                       if ( isset( $frame['class'] ) ) {
+                               $text .= $frame['class'] . $frame['type'] . $frame['function'];
                        } else {
-                               $finalExceptionText .= $call['function'];
+                               $text .= $frame['function'];
                        }
-                       $args = array();
-                       if ( isset( $call['args'] ) ) {
-                               foreach ( $call['args'] as $arg ) {
-                                       if ( $arg === $redacted ) {
-                                               $args[] = 'REDACTED';
-                                       } elseif ( is_object( $arg ) ) {
-                                               $args[] = 'Object(' . get_class( $arg ) . ')';
-                                       } elseif( is_array( $arg ) ) {
-                                               $args[] = 'Array';
-                                       } else {
-                                               $args[] = var_export( $arg, true );
-                                       }
-                               }
+
+                       if ( isset( $frame['args'] ) ) {
+                               $text .= '(' . implode( ', ', $frame['args'] ) . ")\n";
+                       } else {
+                               $text .= "()\n";
                        }
-                       $finalExceptionText .=  '(' . implode( ', ', $args ) . ")\n";
                }
-               return $finalExceptionText . '#' . ( $i + 1 ) . ' {main}';
+
+               $level = $level + 1;
+               $text .= "#{$level} {main}";
+
+               return $text;
        }
 
+       /**
+        * Return a copy of an exception's backtrace as an array.
+        *
+        * Like Exception::getTrace, but replaces each element in each frame's
+        * argument array with the name of its class (if the element is an object)
+        * or its type (if the element is a PHP primitive).
+        *
+        * @since 1.22
+        * @param Exception $e
+        * @return array
+        */
+       public static function getRedactedTrace( Exception $e ) {
+               return array_map( function ( $frame ) {
+                       if ( isset( $frame['args'] ) ) {
+                               $frame['args'] = array_map( function ( $arg ) {
+                                       return is_object( $arg ) ? get_class( $arg ) : gettype( $arg );
+                               }, $frame['args'] );
+                       }
+                       return $frame;
+               }, $e->getTrace() );
+       }
 
        /**
         * Get the ID for this error.
@@ -799,10 +819,10 @@ class MWExceptionHandler {
        public static function logException( Exception $e ) {
                global $wgLogExceptionBacktrace;
 
-               $log = self::getLogMessage( $e );
-               if ( $log ) {
+               if ( !( $e instanceof MWException ) || $e->isLoggable() ) {
+                       $log = self::getLogMessage( $e );
                        if ( $wgLogExceptionBacktrace ) {
-                               wfDebugLog( 'exception', $log . "\n" . self::formatRedactedTrace( $e ) . "\n" );
+                               wfDebugLog( 'exception', $log . "\n" . $e->getTraceAsString() . "\n" );
                        } else {
                                wfDebugLog( 'exception', $log );
                        }
index 3a8d077..ff91ba0 100644 (file)
@@ -81,7 +81,6 @@ if ( !function_exists( 'mb_strpos' ) ) {
        function mb_strpos( $haystack, $needle, $offset = 0, $encoding = '' ) {
                return Fallback::mb_strpos( $haystack, $needle, $offset, $encoding );
        }
-
 }
 
 if ( !function_exists( 'mb_strrpos' ) ) {
@@ -283,8 +282,8 @@ function wfRandom() {
        # The maximum random value is "only" 2^31-1, so get two random
        # values to reduce the chance of dupes
        $max = mt_getrandmax() + 1;
-       $rand = number_format( ( mt_rand() * $max + mt_rand() )
-               / $max / $max, 12, '.', '' );
+       $rand = number_format( ( mt_rand() * $max + mt_rand() ) / $max / $max, 12, '.', '' );
+
        return $rand;
 }
 
@@ -330,6 +329,7 @@ function wfRandomString( $length = 32 ) {
  */
 function wfUrlencode( $s ) {
        static $needle;
+
        if ( is_null( $s ) ) {
                $needle = null;
                return '';
@@ -337,7 +337,9 @@ function wfUrlencode( $s ) {
 
        if ( is_null( $needle ) ) {
                $needle = array( '%3B', '%40', '%24', '%21', '%2A', '%28', '%29', '%2C', '%2F' );
-               if ( !isset( $_SERVER['SERVER_SOFTWARE'] ) || ( strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/7' ) === false ) ) {
+               if ( !isset( $_SERVER['SERVER_SOFTWARE'] ) ||
+                       ( strpos( $_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/7' ) === false )
+               ) {
                        $needle[] = '%3A';
                }
        }
@@ -470,15 +472,17 @@ function wfAppendQuery( $url, $query ) {
 }
 
 /**
- * Expand a potentially local URL to a fully-qualified URL.  Assumes $wgServer
+ * Expand a potentially local URL to a fully-qualified URL. Assumes $wgServer
  * is correct.
  *
  * The meaning of the PROTO_* constants is as follows:
  * PROTO_HTTP: Output a URL starting with http://
  * PROTO_HTTPS: Output a URL starting with https://
  * PROTO_RELATIVE: Output a URL starting with // (protocol-relative URL)
- * PROTO_CURRENT: Output a URL starting with either http:// or https:// , depending on which protocol was used for the current incoming request
- * PROTO_CANONICAL: For URLs without a domain, like /w/index.php , use $wgCanonicalServer. For protocol-relative URLs, use the protocol of $wgCanonicalServer
+ * PROTO_CURRENT: Output a URL starting with either http:// or https:// , depending
+ *    on which protocol was used for the current incoming request
+ * PROTO_CANONICAL: For URLs without a domain, like /w/index.php , use $wgCanonicalServer.
+ *    For protocol-relative URLs, use the protocol of $wgCanonicalServer
  * PROTO_INTERNAL: Like PROTO_CANONICAL, but uses $wgInternalServer instead of $wgCanonicalServer
  *
  * @todo this won't work with current-path-relative URLs
@@ -486,10 +490,9 @@ function wfAppendQuery( $url, $query ) {
  *
  * @param string $url either fully-qualified or a local path + query
  * @param $defaultProto Mixed: one of the PROTO_* constants. Determines the
- *                             protocol to use if $url or $wgServer is
- *                             protocol-relative
+ *    protocol to use if $url or $wgServer is protocol-relative
  * @return string Fully-qualified URL, current-path-relative URL or false if
- *                no valid URL can be constructed
+ *    no valid URL can be constructed
  */
 function wfExpandUrl( $url, $defaultProto = PROTO_CURRENT ) {
        global $wgServer, $wgCanonicalServer, $wgInternalServer;
@@ -513,8 +516,9 @@ function wfExpandUrl( $url, $defaultProto = PROTO_CURRENT ) {
                if ( $serverHasProto ) {
                        $defaultProto = $bits['scheme'] . '://';
                } else {
-                       // $wgCanonicalServer or $wgInternalServer doesn't have a protocol. This really isn't supposed to happen
-                       // Fall back to HTTP in this ridiculous case
+                       // $wgCanonicalServer or $wgInternalServer doesn't have a protocol.
+                       // This really isn't supposed to happen. Fall back to HTTP in this
+                       // ridiculous case.
                        $defaultProto = PROTO_HTTP;
                }
        }
@@ -524,7 +528,8 @@ function wfExpandUrl( $url, $defaultProto = PROTO_CURRENT ) {
        if ( substr( $url, 0, 2 ) == '//' ) {
                $url = $defaultProtoWithoutSlashes . $url;
        } elseif ( substr( $url, 0, 1 ) == '/' ) {
-               // If $serverUrl is protocol-relative, prepend $defaultProtoWithoutSlashes, otherwise leave it alone
+               // If $serverUrl is protocol-relative, prepend $defaultProtoWithoutSlashes,
+               // otherwise leave it alone.
                $url = ( $serverHasProto ? '' : $defaultProtoWithoutSlashes ) . $serverUrl . $url;
        }
 
@@ -739,9 +744,10 @@ function wfUrlProtocolsWithoutProtRel() {
 /**
  * parse_url() work-alike, but non-broken.  Differences:
  *
- * 1) Does not raise warnings on bad URLs (just returns false)
- * 2) Handles protocols that don't use :// (e.g., mailto: and news: , as well as protocol-relative URLs) correctly
- * 3) Adds a "delimiter" element to the array, either '://', ':' or '//' (see (2))
+ * 1) Does not raise warnings on bad URLs (just returns false).
+ * 2) Handles protocols that don't use :// (e.g., mailto: and news:, as well as
+ *    protocol-relative URLs) correctly.
+ * 3) Adds a "delimiter" element to the array, either '://', ':' or '//' (see (2)).
  *
  * @param string $url a URL to parse
  * @return Array: bits of the URL in an associative array, per PHP docs
@@ -749,8 +755,9 @@ function wfUrlProtocolsWithoutProtRel() {
 function wfParseUrl( $url ) {
        global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
 
-       // Protocol-relative URLs are handled really badly by parse_url(). It's so bad that the easiest
-       // way to handle them is to just prepend 'http:' and strip the protocol out later
+       // Protocol-relative URLs are handled really badly by parse_url(). It's so
+       // bad that the easiest way to handle them is to just prepend 'http:' and
+       // strip the protocol out later.
        $wasRelative = substr( $url, 0, 2 ) == '//';
        if ( $wasRelative ) {
                $url = "http:$url";
@@ -816,7 +823,11 @@ function wfParseUrl( $url ) {
  * @return string
  */
 function wfExpandIRI( $url ) {
-       return preg_replace_callback( '/((?:%[89A-F][0-9A-F])+)/i', 'wfExpandIRI_callback', wfExpandUrl( $url ) );
+       return preg_replace_callback(
+               '/((?:%[89A-F][0-9A-F])+)/i',
+               'wfExpandIRI_callback',
+               wfExpandUrl( $url )
+       );
 }
 
 /**
@@ -1053,7 +1064,8 @@ function wfLogDBError( $text ) {
  * Throws a warning that $function is deprecated
  *
  * @param $function String
- * @param string|bool $version Version of MediaWiki that the function was deprecated in (Added in 1.19).
+ * @param string|bool $version Version of MediaWiki that the function
+ *    was deprecated in (Added in 1.19).
  * @param string|bool $component Added in 1.19.
  * @param $callerOffset integer: How far up the call stack is the original
  *    caller. 2 = function that called the function that called
@@ -2325,7 +2337,15 @@ function wfSuppressWarnings( $end = false ) {
                }
        } else {
                if ( !$suppressCount ) {
-                       $originalLevel = error_reporting( E_ALL & ~( E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE | E_DEPRECATED | E_USER_DEPRECATED | E_STRICT ) );
+                       $originalLevel = error_reporting( E_ALL & ~(
+                               E_WARNING |
+                               E_NOTICE |
+                               E_USER_WARNING |
+                               E_USER_NOTICE |
+                               E_DEPRECATED |
+                               E_USER_DEPRECATED |
+                               E_STRICT
+                       ) );
                }
                ++$suppressCount;
        }
@@ -2656,12 +2676,14 @@ function wfEscapeShellArg() {
 
                if ( wfIsWindows() ) {
                        // Escaping for an MSVC-style command line parser and CMD.EXE
+                       // @codingStandardsIgnoreStart For long URLs
                        // Refs:
                        //  * http://web.archive.org/web/20020708081031/http://mailman.lyra.org/pipermail/scite-interest/2002-March/000436.html
                        //  * http://technet.microsoft.com/en-us/library/cc723564.aspx
                        //  * Bug #13518
                        //  * CR r63214
                        // Double the backslashes before any double quotes. Escape the double quotes.
+                       // @codingStandardsIgnoreEnd
                        $tokens = preg_split( '/(\\\\*")/', $arg, -1, PREG_SPLIT_DELIM_CAPTURE );
                        $arg = '';
                        $iteration = 0;
@@ -2710,9 +2732,9 @@ function wfShellExecDisabled() {
                        $functions = explode( ',', ini_get( 'disable_functions' ) );
                        $functions = array_map( 'trim', $functions );
                        $functions = array_map( 'strtolower', $functions );
-                       if ( in_array( 'passthru', $functions ) ) {
-                               wfDebug( "passthru is in disabled_functions\n" );
-                               $disabled = 'passthru';
+                       if ( in_array( 'proc_open', $functions ) ) {
+                               wfDebug( "proc_open is in disabled_functions\n" );
+                               $disabled = 'disabled';
                        }
                }
        }
@@ -2724,16 +2746,21 @@ function wfShellExecDisabled() {
  * configuration if supported.
  * @param string $cmd Command line, properly escaped for shell.
  * @param &$retval null|Mixed optional, will receive the program's exit code.
- *                 (non-zero is usually failure)
+ *                 (non-zero is usually failure). If there is an error from
+ *                 read, select, or proc_open(), this will be set to -1.
  * @param array $environ optional environment variables which should be
  *                 added to the executed command environment.
  * @param array $limits optional array with limits(filesize, memory, time, walltime)
  *                 this overwrites the global wgShellMax* limits.
- * @param array $options Array of options. Only one is "duplicateStderr" => true, which
- *                 Which duplicates stderr to stdout, including errors from limit.sh
+ * @param array $options Array of options:
+ *    - duplicateStderr: Set this to true to duplicate stderr to stdout, 
+ *      including errors from limit.sh
+ *      
  * @return string collected stdout as a string
  */
-function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array(), $options = array() ) {
+function wfShellExec( $cmd, &$retval = null, $environ = array(),
+       $limits = array(), $options = array()
+) {
        global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime,
                $wgMaxShellWallClockTime, $wgShellCgroup;
 
@@ -2742,7 +2769,7 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array
                $retval = 1;
                return $disabled == 'safemode' ?
                        'Unable to run external programs in safe mode.' :
-                       'Unable to run external programs, passthru() is disabled.';
+                       'Unable to run external programs, proc_open() is disabled.';
        }
 
        $includeStderr = isset( $options['duplicateStderr'] ) && $options['duplicateStderr'];
@@ -2768,6 +2795,7 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array
        }
        $cmd = $envcmd . $cmd;
 
+       $useLogPipe = false;
        if ( php_uname( 's' ) == 'Linux' ) {
                $time = intval ( isset( $limits['time'] ) ? $limits['time'] : $wgMaxShellTime );
                if ( isset( $limits['walltime'] ) ) {
@@ -2789,8 +2817,10 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array
                                        'MW_CGROUP=' . escapeshellarg( $wgShellCgroup ) . '; ' .
                                        "MW_MEM_LIMIT=$mem; " .
                                        "MW_FILE_SIZE_LIMIT=$filesize; " .
-                                       "MW_WALL_CLOCK_LIMIT=$wallTime"
+                                       "MW_WALL_CLOCK_LIMIT=$wallTime; " .
+                                       "MW_USE_LOG_PIPE=yes"
                                );
+                       $useLogPipe = true;
                } elseif ( $includeStderr ) {
                        $cmd .= ' 2>&1';
                }
@@ -2799,19 +2829,135 @@ function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array
        }
        wfDebug( "wfShellExec: $cmd\n" );
 
-       // Default to an unusual value that shouldn't happen naturally,
-       // so in the unlikely event of a weird php bug, it would be
-       // more obvious what happened.
-       $retval = 200;
-       ob_start();
-       passthru( $cmd, $retval );
-       $output = ob_get_contents();
-       ob_end_clean();
+       $desc = array(
+               0 => array( 'file', 'php://stdin', 'r' ),
+               1 => array( 'pipe', 'w' ),
+               2 => array( 'file', 'php://stderr', 'w' ) );
+       if ( $useLogPipe ) {
+               $desc[3] = array( 'pipe', 'w' );
+       }
+       $pipes = null;
+       $proc = proc_open( $cmd, $desc, $pipes );
+       if ( !$proc ) {
+               wfDebugLog( 'exec', "proc_open() failed: $cmd\n" );
+               $retval = -1;
+               return '';
+       }
+       $outBuffer = $logBuffer = '';
+       $emptyArray = array();
+       $status = false;
+       $logMsg = false;
+
+       // According to the documentation, it is possible for stream_select()
+       // to fail due to EINTR. I haven't managed to induce this in testing 
+       // despite sending various signals. If it did happen, the error 
+       // message would take the form: 
+       //
+       // stream_select(): unable to select [4]: Interrupted system call (max_fd=5)
+       //
+       // where [4] is the value of the macro EINTR and "Interrupted system
+       // call" is string which according to the Linux manual is "possibly"
+       // localised according to LC_MESSAGES.
+       $eintr = defined( 'SOCKET_EINTR' ) ? SOCKET_EINTR : 4;
+       $eintrMessage = "stream_select(): unable to select [$eintr]";
+
+       // Build a table mapping resource IDs to pipe FDs to work around a
+       // PHP 5.3 issue in which stream_select() does not preserve array keys
+       // <https://bugs.php.net/bug.php?id=53427>.
+       $fds = array();
+       foreach ( $pipes as $fd => $pipe ) {
+               $fds[(int)$pipe] = $fd;
+       }
+
+       while ( true ) {
+               $status = proc_get_status( $proc );
+               if ( !$status['running'] ) {
+                       break;
+               }
+               $status = false;
 
-       if ( $retval == 127 ) {
-               wfDebugLog( 'exec', "Possibly missing executable file: $cmd\n" );
+               $readyPipes = $pipes;
+
+               // Clear last error
+               @trigger_error( '' );
+               if ( @stream_select( $readyPipes, $emptyArray, $emptyArray, null ) === false ) {
+                       $error = error_get_last();
+                       if ( strncmp( $error['message'], $eintrMessage, strlen( $eintrMessage ) ) == 0 ) {
+                               continue;
+                       } else {
+                               trigger_error( $error['message'], E_USER_WARNING );
+                               $logMsg = $error['message'];
+                               break;
+                       }
+               }
+               foreach ( $readyPipes as $pipe ) {
+                       $block = fread( $pipe, 65536 );
+                       $fd = $fds[(int)$pipe];
+                       if ( $block === '' ) {
+                               // End of file
+                               fclose( $pipes[$fd] );
+                               unset( $pipes[$fd] );
+                               if ( !$pipes ) {
+                                       break 2;
+                               }
+                       } elseif ( $block === false ) {
+                               // Read error
+                               $logMsg = "Error reading from pipe";
+                               break 2;
+                       } elseif ( $fd == 1 ) {
+                               // From stdout
+                               $outBuffer .= $block;
+                       } elseif ( $fd == 3 ) {
+                               // From log FD
+                               $logBuffer .= $block;
+                               if ( strpos( $block, "\n" ) !== false ) {
+                                       $lines = explode( "\n", $logBuffer );
+                                       $logBuffer = array_pop( $lines );
+                                       foreach ( $lines as $line ) {
+                                               wfDebugLog( 'exec', $line );
+                                       }
+                               }
+                       }
+               }
        }
-       return $output;
+
+       foreach ( $pipes as $pipe ) {
+               fclose( $pipe );
+       }
+
+       // Use the status previously collected if possible, since proc_get_status()
+       // just calls waitpid() which will not return anything useful the second time.
+       if ( $status === false ) {
+               $status = proc_get_status( $proc );
+       }
+
+       if ( $logMsg !== false ) {
+               // Read/select error
+               $retval = -1;
+               proc_close( $proc );
+       } elseif ( $status['signaled'] ) {
+               $logMsg = "Exited with signal {$status['termsig']}";
+               $retval = 128 + $status['termsig'];
+               proc_close( $proc );
+       } else {
+               if ( $status['running'] ) {
+                       $retval = proc_close( $proc );
+               } else {
+                       $retval = $status['exitcode'];
+                       proc_close( $proc );
+               }
+               if ( $retval == 127 ) {
+                       $logMsg = "Possibly missing executable file";
+               } elseif ( $retval >= 129 && $retval <= 192 ) {
+                       $logMsg = "Probably exited with signal " . ( $retval - 128 );
+               }
+       }
+
+       if ( $logMsg !== false ) {
+               wfDebugLog( 'exec', "$logMsg: $cmd\n" );
+       }
+
+       return $outBuffer;
 }
 
 /**
@@ -3065,6 +3211,14 @@ function wfUsePHP( $req_ver ) {
  * This is useful for extensions which due to their nature are not kept in sync
  * with releases
  *
+ * Note: Due to the behavior of PHP's version_compare() which is used in this
+ * fuction, if you want to allow the 'wmf' development versions add a 'c' (or
+ * any single letter other than 'a', 'b' or 'p') as a post-fix to your
+ * targeted version number. For example if you wanted to allow any variation
+ * of 1.22 use `wfUseMW( '1.22c' )`. Using an 'a' or 'b' instead of 'c' will
+ * not result in the same comparison due to the internal logic of
+ * version_compare().
+ *
  * @see perldoc -f use
  *
  * @param $req_ver Mixed: the version to check, can be a string, an integer, or
@@ -3092,9 +3246,12 @@ function wfUseMW( $req_ver ) {
  * @return String
  */
 function wfBaseName( $path, $suffix = '' ) {
-       $encSuffix = ( $suffix == '' )
-               ? ''
-               : ( '(?:' . preg_quote( $suffix, '#' ) . ')?' );
+       if ( $suffix == '' ) {
+               $encSuffix = '';
+       } else {
+               $encSuffix = '(?:' . preg_quote( $suffix, '#' ) . ')?';
+       }
+
        $matches = array();
        if ( preg_match( "#([^/\\\\]*?){$encSuffix}[/\\\\]*$#", $path, $matches ) ) {
                return $matches[1];
@@ -3175,7 +3332,9 @@ function wfDoUpdates( $commit = '' ) {
  * @param string $engine Either "gmp", "bcmath", or "php"
  * @return string|bool The output number as a string, or false on error
  */
-function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = true, $engine = 'auto' ) {
+function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1,
+       $lowercase = true, $engine = 'auto'
+) {
        $input = (string)$input;
        if (
                $sourceBase < 2 ||
@@ -3185,7 +3344,10 @@ function wfBaseConvert( $input, $sourceBase, $destBase, $pad = 1, $lowercase = t
                $sourceBase != (int)$sourceBase ||
                $destBase != (int)$destBase ||
                $pad != (int)$pad ||
-               !preg_match( "/^[" . substr( '0123456789abcdefghijklmnopqrstuvwxyz', 0, $sourceBase ) . "]+$/i", $input )
+               !preg_match(
+                       "/^[" . substr( '0123456789abcdefghijklmnopqrstuvwxyz', 0, $sourceBase ) . "]+$/i",
+                       $input
+               )
        ) {
                return false;
        }
@@ -3319,9 +3481,11 @@ function wfFixSessionID() {
        // We treat it as disabled if it doesn't have an entropy length of at least 32
        $entropyEnabled = wfCheckEntropy();
 
-       // If built-in entropy is not enabled or not sufficient override php's built in session id generation code
+       // If built-in entropy is not enabled or not sufficient override PHP's
+       // built in session id generation code
        if ( !$entropyEnabled ) {
-               wfDebug( __METHOD__ . ": PHP's built in entropy is disabled or not sufficient, overriding session id generation using our cryptrand source.\n" );
+               wfDebug( __METHOD__ . ": PHP's built in entropy is disabled or not sufficient, " .
+                       "overriding session id generation using our cryptrand source.\n" );
                session_id( MWCryptRand::generateHex( 32 ) );
        }
 }
@@ -3627,9 +3791,7 @@ function wfBoolToStr( $value ) {
  * @return string
  */
 function wfGetNull() {
-       return wfIsWindows()
-               ? 'NUL'
-               : '/dev/null';
+       return wfIsWindows() ? 'NUL' : '/dev/null';
 }
 
 /**
@@ -3639,14 +3801,17 @@ function wfGetNull() {
  * in maintenance scripts, to avoid causing too much lag.  Of course, this is
  * a no-op if there are no slaves.
  *
- * @param $maxLag Integer (deprecated)
- * @param $wiki mixed Wiki identifier accepted by wfGetLB
- * @param $cluster string cluster name accepted by LBFactory
+ * @param int|bool $maxLag (deprecated)
+ * @param mixed $wiki Wiki identifier accepted by wfGetLB
+ * @param string|bool $cluster Cluster name accepted by LBFactory. Default: false.
  */
 function wfWaitForSlaves( $maxLag = false, $wiki = false, $cluster = false ) {
-       $lb = ( $cluster !== false )
-               ? wfGetLBFactory()->getExternalLB( $cluster )
-               : wfGetLB( $wiki );
+       if( $cluster !== false ) {
+               $lb = wfGetLBFactory()->getExternalLB( $cluster );
+       } else {
+               $lb = wfGetLB( $wiki );
+       }
+
        // bug 27975 - Don't try to wait for slaves if there are none
        // Prevents permission error when getting master position
        if ( $lb->getServerCount() > 1 ) {
@@ -3701,8 +3866,10 @@ function wfCountDown( $n ) {
  *              characters before hashing.
  * @return string
  * @codeCoverageIgnore
- * @deprecated since 1.20; Please use MWCryptRand for security purposes and wfRandomString for pseudo-random strings
- * @warning This method is NOT secure. Additionally it has many callers that use it for pseudo-random purposes.
+ * @deprecated since 1.20; Please use MWCryptRand for security purposes and
+ * wfRandomString for pseudo-random strings
+ * @warning This method is NOT secure. Additionally it has many callers that
+ * use it for pseudo-random purposes.
  */
 function wfGenerateToken( $salt = '' ) {
        wfDeprecated( __METHOD__, '1.20' );
diff --git a/includes/HashRing.php b/includes/HashRing.php
deleted file mode 100644 (file)
index 930f8c0..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-<?php
-/**
- * Convenience class for weighted consistent hash rings.
- *
- * 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 Aaron Schulz
- */
-
-/**
- * Convenience class for weighted consistent hash rings
- *
- * @since 1.22
- */
-class HashRing {
-       /** @var Array (location => weight) */
-       protected $sourceMap = array();
-       /** @var Array (location => (start, end)) */
-       protected $ring = array();
-
-       const RING_SIZE = 268435456; // 2^28
-
-       /**
-        * @param array $map (location => weight)
-        */
-       public function __construct( array $map ) {
-               $map = array_filter( $map, function( $w ) { return $w > 0; } );
-               if ( !count( $map ) ) {
-                       throw new MWException( "Ring is empty or all weights are zero." );
-               }
-               $this->sourceMap = $map;
-               // Sort the locations based on the hash of their names
-               $hashes = array();
-               foreach ( $map as $location => $weight ) {
-                       $hashes[$location] = sha1( $location );
-               }
-               uksort( $map, function ( $a, $b ) use ( $hashes ) {
-                       return strcmp( $hashes[$a], $hashes[$b] );
-               } );
-               // Fit the map to weight-proportionate one with a space of size RING_SIZE
-               $sum = array_sum( $map );
-               $standardMap = array();
-               foreach ( $map as $location => $weight ) {
-                       $standardMap[$location] = (int)floor( $weight / $sum * self::RING_SIZE );
-               }
-               // Build a ring of RING_SIZE spots, with each location at a spot in location hash order
-               $index = 0;
-               foreach ( $standardMap as $location => $weight ) {
-                       // Location covers half-closed interval [$index,$index + $weight)
-                       $this->ring[$location] = array( $index, $index + $weight );
-                       $index += $weight;
-               }
-               // Make sure the last location covers what is left
-               end( $this->ring );
-               $this->ring[key( $this->ring )][1] = self::RING_SIZE;
-       }
-
-       /**
-        * Get the location of an item on the ring
-        *
-        * @param string $item
-        * @return string Location
-        */
-       public function getLocation( $item ) {
-               $locations = $this->getLocations( $item, 1 );
-               return $locations[0];
-       }
-
-       /**
-        * Get the location of an item on the ring, as well as the next clockwise locations
-        *
-        * @param string $item
-        * @param integer $limit Maximum number of locations to return
-        * @return array List of locations
-        */
-       public function getLocations( $item, $limit ) {
-               $locations = array();
-               $primaryLocation = null;
-               $spot = hexdec( substr( sha1( $item ), 0, 7 ) ); // first 28 bits
-               foreach ( $this->ring as $location => $range ) {
-                       if ( count( $locations ) >= $limit ) {
-                               break;
-                       }
-                       // The $primaryLocation is the location the item spot is in.
-                       // After that is reached, keep appending the next locations.
-                       if ( ( $range[0] <= $spot && $spot < $range[1] ) || $primaryLocation !== null ) {
-                               if ( $primaryLocation === null ) {
-                                       $primaryLocation = $location;
-                               }
-                               $locations[] = $location;
-                       }
-               }
-               // If more locations are requested, wrap-around and keep adding them
-               reset( $this->ring );
-               while ( count( $locations ) < $limit ) {
-                       list( $location, ) = each( $this->ring );
-                       if ( $location === $primaryLocation ) {
-                               break; // don't go in circles
-                       }
-                       $locations[] = $location;
-               }
-               return $locations;
-       }
-
-       /**
-        * Get the map of locations to weight (ignores 0-weight items)
-        *
-        * @return array
-        */
-       public function getLocationWeights() {
-               return $this->sourceMap;
-       }
-
-       /**
-        * Get a new hash ring with a location removed from the ring
-        *
-        * @param string $location
-        * @return HashRing|bool Returns false if no non-zero weighted spots are left
-        */
-       public function newWithoutLocation( $location ) {
-               $map = $this->sourceMap;
-               unset( $map[$location] );
-               if ( count( $map ) ) {
-                       return new self( $map );
-               }
-               return false;
-       }
-}
index 31aa0f8..9c50895 100644 (file)
@@ -290,7 +290,7 @@ class HistoryBlobStub {
  * of megabytes of data during the conversion downtime.
  *
  * Serialized HistoryBlobCurStub objects will be inserted into the text table
- * on conversion if $wgFastSchemaUpgrades is set to true.
+ * on conversion if $wgLegacySchemaConversion is set to true.
  */
 class HistoryBlobCurStub {
        var $mCurId;
diff --git a/includes/IP.php b/includes/IP.php
deleted file mode 100644 (file)
index 73834a5..0000000
+++ /dev/null
@@ -1,761 +0,0 @@
-<?php
-/**
- * Functions and constants to play with IP addresses and ranges
- *
- * 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 Antoine Musso "<hashar at free dot fr>", Aaron Schulz
- */
-
-// Some regex definition to "play" with IP address and IP address blocks
-
-// An IPv4 address is made of 4 bytes from x00 to xFF which is d0 to d255
-define( 'RE_IP_BYTE', '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])' );
-define( 'RE_IP_ADD', RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE );
-// An IPv4 block is an IP address and a prefix (d1 to d32)
-define( 'RE_IP_PREFIX', '(3[0-2]|[12]?\d)' );
-define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX );
-
-// An IPv6 address is made up of 8 words (each x0000 to xFFFF).
-// However, the "::" abbreviation can be used on consecutive x0000 words.
-define( 'RE_IPV6_WORD', '([0-9A-Fa-f]{1,4})' );
-define( 'RE_IPV6_PREFIX', '(12[0-8]|1[01][0-9]|[1-9]?\d)' );
-define( 'RE_IPV6_ADD',
-       '(?:' . // starts with "::" (including "::")
-               ':(?::|(?::' . RE_IPV6_WORD . '){1,7})' .
-       '|' . // ends with "::" (except "::")
-               RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){0,6}::' .
-       '|' . // contains one "::" in the middle (the ^ makes the test fail if none found)
-               RE_IPV6_WORD . '(?::((?(-1)|:))?' . RE_IPV6_WORD . '){1,6}(?(-2)|^)' .
-       '|' . // contains no "::"
-               RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){7}' .
-       ')'
-);
-// An IPv6 block is an IP address and a prefix (d1 to d128)
-define( 'RE_IPV6_BLOCK', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX );
-// For IPv6 canonicalization (NOT for strict validation; these are quite lax!)
-define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' );
-define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' );
-
-// This might be useful for regexps used elsewhere, matches any IPv6 or IPv6 address or network
-define( 'IP_ADDRESS_STRING',
-       '(?:' .
-               RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?' . // IPv4
-       '|' .
-               RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?' . // IPv6
-       ')'
-);
-
-/**
- * A collection of public static functions to play with IP address
- * and IP blocks.
- */
-class IP {
-       /**
-        * Determine if a string is as valid IP address or network (CIDR prefix).
-        * SIIT IPv4-translated addresses are rejected.
-        * Note: canonicalize() tries to convert translated addresses to IPv4.
-        *
-        * @param string $ip possible IP address
-        * @return Boolean
-        */
-       public static function isIPAddress( $ip ) {
-               return (bool)preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip );
-       }
-
-       /**
-        * Given a string, determine if it as valid IP in IPv6 only.
-        * Note: Unlike isValid(), this looks for networks too.
-        *
-        * @param string $ip possible IP address
-        * @return Boolean
-        */
-       public static function isIPv6( $ip ) {
-               return (bool)preg_match( '/^' . RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?$/', $ip );
-       }
-
-       /**
-        * Given a string, determine if it as valid IP in IPv4 only.
-        * Note: Unlike isValid(), this looks for networks too.
-        *
-        * @param string $ip possible IP address
-        * @return Boolean
-        */
-       public static function isIPv4( $ip ) {
-               return (bool)preg_match( '/^' . RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?$/', $ip );
-       }
-
-       /**
-        * Validate an IP address. Ranges are NOT considered valid.
-        * SIIT IPv4-translated addresses are rejected.
-        * Note: canonicalize() tries to convert translated addresses to IPv4.
-        *
-        * @param $ip String
-        * @return Boolean: True if it is valid.
-        */
-       public static function isValid( $ip ) {
-               return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip )
-                       || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip ) );
-       }
-
-       /**
-        * Validate an IP Block (valid address WITH a valid prefix).
-        * SIIT IPv4-translated addresses are rejected.
-        * Note: canonicalize() tries to convert translated addresses to IPv4.
-        *
-        * @param $ipblock String
-        * @return Boolean: True if it is valid.
-        */
-       public static function isValidBlock( $ipblock ) {
-               return ( preg_match( '/^' . RE_IPV6_BLOCK . '$/', $ipblock )
-                       || preg_match( '/^' . RE_IP_BLOCK . '$/', $ipblock ) );
-       }
-
-       /**
-        * Convert an IP into a verbose, uppercase, normalized form.
-        * IPv6 addresses in octet notation are expanded to 8 words.
-        * IPv4 addresses are just trimmed.
-        *
-        * @param string $ip IP address in quad or octet form (CIDR or not).
-        * @return String
-        */
-       public static function sanitizeIP( $ip ) {
-               $ip = trim( $ip );
-               if ( $ip === '' ) {
-                       return null;
-               }
-               if ( self::isIPv4( $ip ) || !self::isIPv6( $ip ) ) {
-                       return $ip; // nothing else to do for IPv4 addresses or invalid ones
-               }
-               // Remove any whitespaces, convert to upper case
-               $ip = strtoupper( $ip );
-               // Expand zero abbreviations
-               $abbrevPos = strpos( $ip, '::' );
-               if ( $abbrevPos !== false ) {
-                       // We know this is valid IPv6. Find the last index of the
-                       // address before any CIDR number (e.g. "a:b:c::/24").
-                       $CIDRStart = strpos( $ip, "/" );
-                       $addressEnd = ( $CIDRStart !== false )
-                               ? $CIDRStart - 1
-                               : strlen( $ip ) - 1;
-                       // If the '::' is at the beginning...
-                       if ( $abbrevPos == 0 ) {
-                               $repeat = '0:';
-                               $extra = ( $ip == '::' ) ? '0' : ''; // for the address '::'
-                               $pad = 9; // 7+2 (due to '::')
-                       // If the '::' is at the end...
-                       } elseif ( $abbrevPos == ( $addressEnd - 1 ) ) {
-                               $repeat = ':0';
-                               $extra = '';
-                               $pad = 9; // 7+2 (due to '::')
-                       // If the '::' is in the middle...
-                       } else {
-                               $repeat = ':0';
-                               $extra = ':';
-                               $pad = 8; // 6+2 (due to '::')
-                       }
-                       $ip = str_replace( '::',
-                               str_repeat( $repeat, $pad - substr_count( $ip, ':' ) ) . $extra,
-                               $ip
-                       );
-               }
-               // Remove leading zeros from each bloc as needed
-               $ip = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip );
-               return $ip;
-       }
-
-       /**
-        * Prettify an IP for display to end users.
-        * This will make it more compact and lower-case.
-        *
-        * @param $ip string
-        * @return string
-        */
-       public static function prettifyIP( $ip ) {
-               $ip = self::sanitizeIP( $ip ); // normalize (removes '::')
-               if ( self::isIPv6( $ip ) ) {
-                       // Split IP into an address and a CIDR
-                       if ( strpos( $ip, '/' ) !== false ) {
-                               list( $ip, $cidr ) = explode( '/', $ip, 2 );
-                       } else {
-                               list( $ip, $cidr ) = array( $ip, '' );
-                       }
-                       // Get the largest slice of words with multiple zeros
-                       $offset = 0;
-                       $longest = $longestPos = false;
-                       while ( preg_match(
-                               '!(?:^|:)0(?::0)+(?:$|:)!', $ip, $m, PREG_OFFSET_CAPTURE, $offset
-                       ) ) {
-                               list( $match, $pos ) = $m[0]; // full match
-                               if ( strlen( $match ) > strlen( $longest ) ) {
-                                       $longest = $match;
-                                       $longestPos = $pos;
-                               }
-                               $offset = ( $pos + strlen( $match ) ); // advance
-                       }
-                       if ( $longest !== false ) {
-                               // Replace this portion of the string with the '::' abbreviation
-                               $ip = substr_replace( $ip, '::', $longestPos, strlen( $longest ) );
-                       }
-                       // Add any CIDR back on
-                       if ( $cidr !== '' ) {
-                               $ip = "{$ip}/{$cidr}";
-                       }
-                       // Convert to lower case to make it more readable
-                       $ip = strtolower( $ip );
-               }
-               return $ip;
-       }
-
-       /**
-        * Given a host/port string, like one might find in the host part of a URL
-        * per RFC 2732, split the hostname part and the port part and return an
-        * array with an element for each. If there is no port part, the array will
-        * have false in place of the port. If the string was invalid in some way,
-        * false is returned.
-        *
-        * This was easy with IPv4 and was generally done in an ad-hoc way, but
-        * with IPv6 it's somewhat more complicated due to the need to parse the
-        * square brackets and colons.
-        *
-        * A bare IPv6 address is accepted despite the lack of square brackets.
-        *
-        * @param string $both The string with the host and port
-        * @return array
-        */
-       public static function splitHostAndPort( $both ) {
-               if ( substr( $both, 0, 1 ) === '[' ) {
-                       if ( preg_match( '/^\[(' . RE_IPV6_ADD . ')\](?::(?P<port>\d+))?$/', $both, $m ) ) {
-                               if ( isset( $m['port'] ) ) {
-                                       return array( $m[1], intval( $m['port'] ) );
-                               } else {
-                                       return array( $m[1], false );
-                               }
-                       } else {
-                               // Square bracket found but no IPv6
-                               return false;
-                       }
-               }
-               $numColons = substr_count( $both, ':' );
-               if ( $numColons >= 2 ) {
-                       // Is it a bare IPv6 address?
-                       if ( preg_match( '/^' . RE_IPV6_ADD . '$/', $both ) ) {
-                               return array( $both, false );
-                       } else {
-                               // Not valid IPv6, but too many colons for anything else
-                               return false;
-                       }
-               }
-               if ( $numColons >= 1 ) {
-                       // Host:port?
-                       $bits = explode( ':', $both );
-                       if ( preg_match( '/^\d+/', $bits[1] ) ) {
-                               return array( $bits[0], intval( $bits[1] ) );
-                       } else {
-                               // Not a valid port
-                               return false;
-                       }
-               }
-               // Plain hostname
-               return array( $both, false );
-       }
-
-       /**
-        * Given a host name and a port, combine them into host/port string like
-        * you might find in a URL. If the host contains a colon, wrap it in square
-        * brackets like in RFC 2732. If the port matches the default port, omit
-        * the port specification
-        *
-        * @param $host string
-        * @param $port int
-        * @param $defaultPort bool|int
-        * @return string
-        */
-       public static function combineHostAndPort( $host, $port, $defaultPort = false ) {
-               if ( strpos( $host, ':' ) !== false ) {
-                       $host = "[$host]";
-               }
-               if ( $defaultPort !== false && $port == $defaultPort ) {
-                       return $host;
-               } else {
-                       return "$host:$port";
-               }
-       }
-
-       /**
-        * Given an unsigned integer, returns an IPv6 address in octet notation
-        *
-        * @param $ip_int String: IP address.
-        * @return String
-        */
-       public static function toOctet( $ip_int ) {
-               return self::hexToOctet( wfBaseConvert( $ip_int, 10, 16, 32, false ) );
-       }
-
-       /**
-        * Convert an IPv4 or IPv6 hexadecimal representation back to readable format
-        *
-        * @param string $hex number, with "v6-" prefix if it is IPv6
-        * @return String: quad-dotted (IPv4) or octet notation (IPv6)
-        */
-       public static function formatHex( $hex ) {
-               if ( substr( $hex, 0, 3 ) == 'v6-' ) { // IPv6
-                       return self::hexToOctet( substr( $hex, 3 ) );
-               } else { // IPv4
-                       return self::hexToQuad( $hex );
-               }
-       }
-
-       /**
-        * Converts a hexadecimal number to an IPv6 address in octet notation
-        *
-        * @param $ip_hex String: pure hex (no v6- prefix)
-        * @return String (of format a:b:c:d:e:f:g:h)
-        */
-       public static function hexToOctet( $ip_hex ) {
-               // Pad hex to 32 chars (128 bits)
-               $ip_hex = str_pad( strtoupper( $ip_hex ), 32, '0', STR_PAD_LEFT );
-               // Separate into 8 words
-               $ip_oct = substr( $ip_hex, 0, 4 );
-               for ( $n = 1; $n < 8; $n++ ) {
-                       $ip_oct .= ':' . substr( $ip_hex, 4 * $n, 4 );
-               }
-               // NO leading zeroes
-               $ip_oct = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip_oct );
-               return $ip_oct;
-       }
-
-       /**
-        * Converts a hexadecimal number to an IPv4 address in quad-dotted notation
-        *
-        * @param $ip_hex String: pure hex
-        * @return String (of format a.b.c.d)
-        */
-       public static function hexToQuad( $ip_hex ) {
-               // Pad hex to 8 chars (32 bits)
-               $ip_hex = str_pad( strtoupper( $ip_hex ), 8, '0', STR_PAD_LEFT );
-               // Separate into four quads
-               $s = '';
-               for ( $i = 0; $i < 4; $i++ ) {
-                       if ( $s !== '' ) {
-                               $s .= '.';
-                       }
-                       $s .= base_convert( substr( $ip_hex, $i * 2, 2 ), 16, 10 );
-               }
-               return $s;
-       }
-
-       /**
-        * 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
-        */
-       public static function isPublic( $ip ) {
-               if ( self::isIPv6( $ip ) ) {
-                       return self::isPublic6( $ip );
-               }
-               $n = self::toUnsigned( $ip );
-               if ( !$n ) {
-                       return false;
-               }
-
-               // ip2long accepts incomplete addresses, as well as some addresses
-               // followed by garbage characters. Check that it's really valid.
-               if ( $ip != long2ip( $n ) ) {
-                       return false;
-               }
-
-               static $privateRanges = false;
-               if ( !$privateRanges ) {
-                       $privateRanges = array(
-                               array( '10.0.0.0', '10.255.255.255' ), # RFC 1918 (private)
-                               array( '172.16.0.0', '172.31.255.255' ), # RFC 1918 (private)
-                               array( '192.168.0.0', '192.168.255.255' ), # RFC 1918 (private)
-                               array( '0.0.0.0', '0.255.255.255' ), # this network
-                               array( '127.0.0.0', '127.255.255.255' ), # loopback
-                       );
-               }
-
-               foreach ( $privateRanges as $r ) {
-                       $start = self::toUnsigned( $r[0] );
-                       $end = self::toUnsigned( $r[1] );
-                       if ( $n >= $start && $n <= $end ) {
-                               return false;
-                       }
-               }
-               return true;
-       }
-
-       /**
-        * Determine if an IPv6 address really is an IP address, and if it is public,
-        * i.e. not RFC 4193 or similar
-        *
-        * @param $ip String
-        * @return Boolean
-        */
-       private static function isPublic6( $ip ) {
-               static $privateRanges = false;
-               if ( !$privateRanges ) {
-                       $privateRanges = array(
-                               array( 'fc00::', 'fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' ), # RFC 4193 (local)
-                               array( '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1' ), # loopback
-                       );
-               }
-               $n = self::toHex( $ip );
-               foreach ( $privateRanges as $r ) {
-                       $start = self::toHex( $r[0] );
-                       $end = self::toHex( $r[1] );
-                       if ( $n >= $start && $n <= $end ) {
-                               return false;
-                       }
-               }
-               return true;
-       }
-
-       /**
-        * Return a zero-padded upper case hexadecimal representation of an IP address.
-        *
-        * Hexadecimal addresses are used because they can easily be extended to
-        * IPv6 support. To separate the ranges, the return value from this
-        * function for an IPv6 address will be prefixed with "v6-", a non-
-        * hexadecimal string which sorts after the IPv4 addresses.
-        *
-        * @param string $ip quad dotted/octet IP address.
-        * @return String
-        */
-       public static function toHex( $ip ) {
-               if ( self::isIPv6( $ip ) ) {
-                       $n = 'v6-' . self::IPv6ToRawHex( $ip );
-               } else {
-                       $n = self::toUnsigned( $ip );
-                       if ( $n !== false ) {
-                               $n = wfBaseConvert( $n, 10, 16, 8, false );
-                       }
-               }
-               return $n;
-       }
-
-       /**
-        * Given an IPv6 address in octet notation, returns a pure hex string.
-        *
-        * @param string $ip octet ipv6 IP address.
-        * @return String: pure hex (uppercase)
-        */
-       private static function IPv6ToRawHex( $ip ) {
-               $ip = self::sanitizeIP( $ip );
-               if ( !$ip ) {
-                       return null;
-               }
-               $r_ip = '';
-               foreach ( explode( ':', $ip ) as $v ) {
-                       $r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT );
-               }
-               return $r_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
-        */
-       public static function toUnsigned( $ip ) {
-               if ( self::isIPv6( $ip ) ) {
-                       $n = self::toUnsigned6( $ip );
-               } else {
-                       $n = ip2long( $ip );
-                       if ( $n < 0 ) {
-                               $n += pow( 2, 32 );
-                               # On 32-bit platforms (and on Windows), 2^32 does not fit into an int,
-                               # so $n becomes a float. We convert it to string instead.
-                               if ( is_float( $n ) ) {
-                                       $n = (string)$n;
-                               }
-                       }
-               }
-               return $n;
-       }
-
-       /**
-        * @param $ip
-        * @return String
-        */
-       private static function toUnsigned6( $ip ) {
-               return wfBaseConvert( self::IPv6ToRawHex( $ip ), 16, 10 );
-       }
-
-       /**
-        * Convert a network specification in CIDR notation
-        * to an integer network and a number of bits
-        *
-        * @param string $range IP with CIDR prefix
-        * @return array(int or string, int)
-        */
-       public static function parseCIDR( $range ) {
-               if ( self::isIPv6( $range ) ) {
-                       return self::parseCIDR6( $range );
-               }
-               $parts = explode( '/', $range, 2 );
-               if ( count( $parts ) != 2 ) {
-                       return array( false, false );
-               }
-               list( $network, $bits ) = $parts;
-               $network = ip2long( $network );
-               if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 32 ) {
-                       if ( $bits == 0 ) {
-                               $network = 0;
-                       } else {
-                               $network &= ~( ( 1 << ( 32 - $bits ) ) - 1 );
-                       }
-                       # Convert to unsigned
-                       if ( $network < 0 ) {
-                               $network += pow( 2, 32 );
-                       }
-               } else {
-                       $network = false;
-                       $bits = false;
-               }
-               return array( $network, $bits );
-       }
-
-       /**
-        * Given a string range in a number of formats,
-        * return the start and end of the range in hexadecimal.
-        *
-        * Formats are:
-        *     1.2.3.4/24          CIDR
-        *     1.2.3.4 - 1.2.3.5   Explicit range
-        *     1.2.3.4             Single IP
-        *
-        *     2001:0db8:85a3::7344/96                                   CIDR
-        *     2001:0db8:85a3::7344 - 2001:0db8:85a3::7344   Explicit range
-        *     2001:0db8:85a3::7344                                      Single IP
-        * @param string $range IP range
-        * @return array(string, string)
-        */
-       public static function parseRange( $range ) {
-               // CIDR notation
-               if ( strpos( $range, '/' ) !== false ) {
-                       if ( self::isIPv6( $range ) ) {
-                               return self::parseRange6( $range );
-                       }
-                       list( $network, $bits ) = self::parseCIDR( $range );
-                       if ( $network === false ) {
-                               $start = $end = false;
-                       } else {
-                               $start = sprintf( '%08X', $network );
-                               $end = sprintf( '%08X', $network + pow( 2, ( 32 - $bits ) ) - 1 );
-                       }
-               // Explicit range
-               } elseif ( strpos( $range, '-' ) !== false ) {
-                       list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
-                       if ( self::isIPv6( $start ) && self::isIPv6( $end ) ) {
-                               return self::parseRange6( $range );
-                       }
-                       if ( self::isIPv4( $start ) && self::isIPv4( $end ) ) {
-                               $start = self::toUnsigned( $start );
-                               $end = self::toUnsigned( $end );
-                               if ( $start > $end ) {
-                                       $start = $end = false;
-                               } else {
-                                       $start = sprintf( '%08X', $start );
-                                       $end = sprintf( '%08X', $end );
-                               }
-                       } else {
-                               $start = $end = false;
-                       }
-               } else {
-                       # Single IP
-                       $start = $end = self::toHex( $range );
-               }
-               if ( $start === false || $end === false ) {
-                       return array( false, false );
-               } else {
-                       return array( $start, $end );
-               }
-       }
-
-       /**
-        * Convert a network specification in IPv6 CIDR notation to an
-        * integer network and a number of bits
-        *
-        * @param $range
-        *
-        * @return array(string, int)
-        */
-       private static function parseCIDR6( $range ) {
-               # Explode into <expanded IP,range>
-               $parts = explode( '/', IP::sanitizeIP( $range ), 2 );
-               if ( count( $parts ) != 2 ) {
-                       return array( false, false );
-               }
-               list( $network, $bits ) = $parts;
-               $network = self::IPv6ToRawHex( $network );
-               if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 128 ) {
-                       if ( $bits == 0 ) {
-                               $network = "0";
-                       } else {
-                               # Native 32 bit functions WONT work here!!!
-                               # Convert to a padded binary number
-                               $network = wfBaseConvert( $network, 16, 2, 128 );
-                               # Truncate the last (128-$bits) bits and replace them with zeros
-                               $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT );
-                               # Convert back to an integer
-                               $network = wfBaseConvert( $network, 2, 10 );
-                       }
-               } else {
-                       $network = false;
-                       $bits = false;
-               }
-               return array( $network, (int)$bits );
-       }
-
-       /**
-        * Given a string range in a number of formats, return the
-        * start and end of the range in hexadecimal. For IPv6.
-        *
-        * Formats are:
-        *     2001:0db8:85a3::7344/96                                   CIDR
-        *     2001:0db8:85a3::7344 - 2001:0db8:85a3::7344   Explicit range
-        *     2001:0db8:85a3::7344/96                                   Single IP
-        *
-        * @param $range
-        *
-        * @return array(string, string)
-        */
-       private static function parseRange6( $range ) {
-               # Expand any IPv6 IP
-               $range = IP::sanitizeIP( $range );
-               // CIDR notation...
-               if ( strpos( $range, '/' ) !== false ) {
-                       list( $network, $bits ) = self::parseCIDR6( $range );
-                       if ( $network === false ) {
-                               $start = $end = false;
-                       } else {
-                               $start = wfBaseConvert( $network, 10, 16, 32, false );
-                               # Turn network to binary (again)
-                               $end = wfBaseConvert( $network, 10, 2, 128 );
-                               # Truncate the last (128-$bits) bits and replace them with ones
-                               $end = str_pad( substr( $end, 0, $bits ), 128, 1, STR_PAD_RIGHT );
-                               # Convert to hex
-                               $end = wfBaseConvert( $end, 2, 16, 32, false );
-                               # see toHex() comment
-                               $start = "v6-$start";
-                               $end = "v6-$end";
-                       }
-               // Explicit range notation...
-               } elseif ( strpos( $range, '-' ) !== false ) {
-                       list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
-                       $start = self::toUnsigned6( $start );
-                       $end = self::toUnsigned6( $end );
-                       if ( $start > $end ) {
-                               $start = $end = false;
-                       } else {
-                               $start = wfBaseConvert( $start, 10, 16, 32, false );
-                               $end = wfBaseConvert( $end, 10, 16, 32, false );
-                       }
-                       # see toHex() comment
-                       $start = "v6-$start";
-                       $end = "v6-$end";
-               } else {
-                       # Single IP
-                       $start = $end = self::toHex( $range );
-               }
-               if ( $start === false || $end === false ) {
-                       return array( false, false );
-               } else {
-                       return array( $start, $end );
-               }
-       }
-
-       /**
-        * Determine if a given IPv4/IPv6 address is in a given CIDR network
-        *
-        * @param string $addr the address to check against the given range.
-        * @param string $range the range to check the given address against.
-        * @return Boolean: whether or not the given address is in the given range.
-        */
-       public static function isInRange( $addr, $range ) {
-               $hexIP = self::toHex( $addr );
-               list( $start, $end ) = self::parseRange( $range );
-               return ( strcmp( $hexIP, $start ) >= 0 &&
-                       strcmp( $hexIP, $end ) <= 0 );
-       }
-
-       /**
-        * Convert some unusual representations of IPv4 addresses to their
-        * canonical dotted quad representation.
-        *
-        * This currently only checks a few IPV4-to-IPv6 related cases.  More
-        * unusual representations may be added later.
-        *
-        * @param string $addr something that might be an IP address
-        * @return String: valid dotted quad IPv4 address or null
-        */
-       public static function canonicalize( $addr ) {
-               // remove zone info (bug 35738)
-               $addr = preg_replace( '/\%.*/', '', $addr );
-
-               if ( self::isValid( $addr ) ) {
-                       return $addr;
-               }
-               // Turn mapped addresses from ::ce:ffff:1.2.3.4 to 1.2.3.4
-               if ( strpos( $addr, ':' ) !== false && strpos( $addr, '.' ) !== false ) {
-                       $addr = substr( $addr, strrpos( $addr, ':' ) + 1 );
-                       if ( self::isIPv4( $addr ) ) {
-                               return $addr;
-                       }
-               }
-               // IPv6 loopback address
-               $m = array();
-               if ( preg_match( '/^0*' . RE_IPV6_GAP . '1$/', $addr, $m ) ) {
-                       return '127.0.0.1';
-               }
-               // IPv4-mapped and IPv4-compatible IPv6 addresses
-               if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . '(' . RE_IP_ADD . ')$/i', $addr, $m ) ) {
-                       return $m[1];
-               }
-               if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . RE_IPV6_WORD .
-                       ':' . RE_IPV6_WORD . '$/i', $addr, $m ) )
-               {
-                       return long2ip( ( hexdec( $m[1] ) << 16 ) + hexdec( $m[2] ) );
-               }
-
-               return null; // give up
-       }
-
-       /**
-        * Gets rid of unneeded numbers in quad-dotted/octet IP strings
-        * For example, 127.111.113.151/24 -> 127.111.113.0/24
-        * @param string $range IP address to normalize
-        * @return string
-        */
-       public static function sanitizeRange( $range ) {
-               list( /*...*/, $bits ) = self::parseCIDR( $range );
-               list( $start, /*...*/ ) = self::parseRange( $range );
-               $start = self::formatHex( $start );
-               if ( $bits === false ) {
-                       return $start; // wasn't actually a range
-               }
-               return "$start/$bits";
-       }
-}
diff --git a/includes/LinksUpdate.php b/includes/LinksUpdate.php
deleted file mode 100644 (file)
index fdd0e3c..0000000
+++ /dev/null
@@ -1,892 +0,0 @@
-<?php
-/**
- * Updater for link tracking tables after a page edit.
- *
- * 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
- */
-
-/**
- * See docs/deferred.txt
- *
- * @todo document (e.g. one-sentence top-level class description).
- */
-class LinksUpdate extends SqlDataUpdate {
-
-       // @todo make members protected, but make sure extensions don't break
-
-       public $mId,         //!< Page ID of the article linked from
-               $mTitle,         //!< Title object of the article linked from
-               $mParserOutput,  //!< Parser output
-               $mLinks,         //!< Map of title strings to IDs for the links in the document
-               $mImages,        //!< DB keys of the images used, in the array key only
-               $mTemplates,     //!< Map of title strings to IDs for the template references, including broken ones
-               $mExternals,     //!< URLs of external links, array key only
-               $mCategories,    //!< Map of category names to sort keys
-               $mInterlangs,    //!< Map of language codes to titles
-               $mProperties,    //!< Map of arbitrary name to value
-               $mDb,            //!< Database connection reference
-               $mOptions,       //!< SELECT options to be used (array)
-               $mRecursive;     //!< Whether to queue jobs for recursive updates
-
-       /**
-        * @var null|array Added links if calculated.
-        */
-       private $linkInsertions = null;
-
-       /**
-        * @var null|array Deleted links if calculated.
-        */
-       private $linkDeletions = null;
-
-       /**
-        * Constructor
-        *
-        * @param $title Title of the page we're updating
-        * @param $parserOutput ParserOutput: output from a full parse of this page
-        * @param $recursive Boolean: queue jobs for recursive updates?
-        * @throws MWException
-        */
-       function __construct( $title, $parserOutput, $recursive = true ) {
-               parent::__construct( false ); // no implicit transaction
-
-               if ( !( $title instanceof Title ) ) {
-                       throw new MWException( "The calling convention to LinksUpdate::LinksUpdate() has changed. " .
-                               "Please see Article::editUpdates() for an invocation example.\n" );
-               }
-
-               if ( !( $parserOutput instanceof ParserOutput ) ) {
-                       throw new MWException( "The calling convention to LinksUpdate::__construct() has changed. " .
-                               "Please see WikiPage::doEditUpdates() for an invocation example.\n" );
-               }
-
-               $this->mTitle = $title;
-               $this->mId = $title->getArticleID();
-
-               if ( !$this->mId ) {
-                       throw new MWException( "The Title object did not provide an article ID. Perhaps the page doesn't exist?" );
-               }
-
-               $this->mParserOutput = $parserOutput;
-
-               $this->mLinks = $parserOutput->getLinks();
-               $this->mImages = $parserOutput->getImages();
-               $this->mTemplates = $parserOutput->getTemplates();
-               $this->mExternals = $parserOutput->getExternalLinks();
-               $this->mCategories = $parserOutput->getCategories();
-               $this->mProperties = $parserOutput->getProperties();
-               $this->mInterwikis = $parserOutput->getInterwikiLinks();
-
-               # Convert the format of the interlanguage links
-               # I didn't want to change it in the ParserOutput, because that array is passed all
-               # the way back to the skin, so either a skin API break would be required, or an
-               # inefficient back-conversion.
-               $ill = $parserOutput->getLanguageLinks();
-               $this->mInterlangs = array();
-               foreach ( $ill as $link ) {
-                       list( $key, $title ) = explode( ':', $link, 2 );
-                       $this->mInterlangs[$key] = $title;
-               }
-
-               foreach ( $this->mCategories as &$sortkey ) {
-                       # If the sortkey is longer then 255 bytes,
-                       # it truncated by DB, and then doesn't get
-                       # matched when comparing existing vs current
-                       # categories, causing bug 25254.
-                       # Also. substr behaves weird when given "".
-                       if ( $sortkey !== '' ) {
-                               $sortkey = substr( $sortkey, 0, 255 );
-                       }
-               }
-
-               $this->mRecursive = $recursive;
-
-               wfRunHooks( 'LinksUpdateConstructed', array( &$this ) );
-       }
-
-       /**
-        * Update link tables with outgoing links from an updated article
-        */
-       public function doUpdate() {
-               wfRunHooks( 'LinksUpdate', array( &$this ) );
-               $this->doIncrementalUpdate();
-               wfRunHooks( 'LinksUpdateComplete', array( &$this ) );
-       }
-
-       protected function doIncrementalUpdate() {
-               wfProfileIn( __METHOD__ );
-
-               # Page links
-               $existing = $this->getExistingLinks();
-               $this->linkDeletions = $this->getLinkDeletions( $existing );
-               $this->linkInsertions = $this->getLinkInsertions( $existing );
-               $this->incrTableUpdate( 'pagelinks', 'pl', $this->linkDeletions, $this->linkInsertions );
-
-               # Image links
-               $existing = $this->getExistingImages();
-
-               $imageDeletes = $this->getImageDeletions( $existing );
-               $this->incrTableUpdate( 'imagelinks', 'il', $imageDeletes,
-                       $this->getImageInsertions( $existing ) );
-
-               # Invalidate all image description pages which had links added or removed
-               $imageUpdates = $imageDeletes + array_diff_key( $this->mImages, $existing );
-               $this->invalidateImageDescriptions( $imageUpdates );
-
-               # External links
-               $existing = $this->getExistingExternals();
-               $this->incrTableUpdate( 'externallinks', 'el', $this->getExternalDeletions( $existing ),
-                       $this->getExternalInsertions( $existing ) );
-
-               # Language links
-               $existing = $this->getExistingInterlangs();
-               $this->incrTableUpdate( 'langlinks', 'll', $this->getInterlangDeletions( $existing ),
-                       $this->getInterlangInsertions( $existing ) );
-
-               # Inline interwiki links
-               $existing = $this->getExistingInterwikis();
-               $this->incrTableUpdate( 'iwlinks', 'iwl', $this->getInterwikiDeletions( $existing ),
-                       $this->getInterwikiInsertions( $existing ) );
-
-               # Template links
-               $existing = $this->getExistingTemplates();
-               $this->incrTableUpdate( 'templatelinks', 'tl', $this->getTemplateDeletions( $existing ),
-                       $this->getTemplateInsertions( $existing ) );
-
-               # Category links
-               $existing = $this->getExistingCategories();
-
-               $categoryDeletes = $this->getCategoryDeletions( $existing );
-
-               $this->incrTableUpdate( 'categorylinks', 'cl', $categoryDeletes,
-                       $this->getCategoryInsertions( $existing ) );
-
-               # Invalidate all categories which were added, deleted or changed (set symmetric difference)
-               $categoryInserts = array_diff_assoc( $this->mCategories, $existing );
-               $categoryUpdates = $categoryInserts + $categoryDeletes;
-               $this->invalidateCategories( $categoryUpdates );
-               $this->updateCategoryCounts( $categoryInserts, $categoryDeletes );
-
-               # Page properties
-               $existing = $this->getExistingProperties();
-
-               $propertiesDeletes = $this->getPropertyDeletions( $existing );
-
-               $this->incrTableUpdate( 'page_props', 'pp', $propertiesDeletes,
-                       $this->getPropertyInsertions( $existing ) );
-
-               # Invalidate the necessary pages
-               $changed = $propertiesDeletes + array_diff_assoc( $this->mProperties, $existing );
-               $this->invalidateProperties( $changed );
-
-               # Refresh links of all pages including this page
-               # This will be in a separate transaction
-               if ( $this->mRecursive ) {
-                       $this->queueRecursiveJobs();
-               }
-
-               wfProfileOut( __METHOD__ );
-       }
-
-       /**
-        * Queue recursive jobs for this page
-        *
-        * Which means do LinksUpdate on all templates
-        * that include the current page, using the job queue.
-        */
-       function queueRecursiveJobs() {
-               self::queueRecursiveJobsForTable( $this->mTitle, 'templatelinks' );
-       }
-
-       /**
-        * Queue a RefreshLinks job for any table.
-        *
-        * @param Title $title Title to do job for
-        * @param String $table Table to use (e.g. 'templatelinks')
-        */
-       public static function queueRecursiveJobsForTable( Title $title, $table ) {
-               wfProfileIn( __METHOD__ );
-               if ( $title->getBacklinkCache()->hasLinks( $table ) ) {
-                       $job = new RefreshLinksJob2(
-                               $title,
-                               array(
-                                       'table' => $table,
-                               ) + Job::newRootJobParams( // "overall" refresh links job info
-                                       "refreshlinks:{$table}:{$title->getPrefixedText()}"
-                               )
-                       );
-                       JobQueueGroup::singleton()->push( $job );
-                       JobQueueGroup::singleton()->deduplicateRootJob( $job );
-               }
-               wfProfileOut( __METHOD__ );
-       }
-
-       /**
-        * @param $cats
-        */
-       function invalidateCategories( $cats ) {
-               $this->invalidatePages( NS_CATEGORY, array_keys( $cats ) );
-       }
-
-       /**
-        * Update all the appropriate counts in the category table.
-        * @param array $added associative array of category name => sort key
-        * @param array $deleted associative array of category name => sort key
-        */
-       function updateCategoryCounts( $added, $deleted ) {
-               $a = WikiPage::factory( $this->mTitle );
-               $a->updateCategoryCounts(
-                       array_keys( $added ), array_keys( $deleted )
-               );
-       }
-
-       /**
-        * @param $images
-        */
-       function invalidateImageDescriptions( $images ) {
-               $this->invalidatePages( NS_FILE, array_keys( $images ) );
-       }
-
-       /**
-        * Update a table by doing a delete query then an insert query
-        * @param $table
-        * @param $prefix
-        * @param $deletions
-        * @param $insertions
-        */
-       function incrTableUpdate( $table, $prefix, $deletions, $insertions ) {
-               if ( $table == 'page_props' ) {
-                       $fromField = 'pp_page';
-               } else {
-                       $fromField = "{$prefix}_from";
-               }
-               $where = array( $fromField => $this->mId );
-               if ( $table == 'pagelinks' || $table == 'templatelinks' || $table == 'iwlinks' ) {
-                       if ( $table == 'iwlinks' ) {
-                               $baseKey = 'iwl_prefix';
-                       } else {
-                               $baseKey = "{$prefix}_namespace";
-                       }
-                       $clause = $this->mDb->makeWhereFrom2d( $deletions, $baseKey, "{$prefix}_title" );
-                       if ( $clause ) {
-                               $where[] = $clause;
-                       } else {
-                               $where = false;
-                       }
-               } else {
-                       if ( $table == 'langlinks' ) {
-                               $toField = 'll_lang';
-                       } elseif ( $table == 'page_props' ) {
-                               $toField = 'pp_propname';
-                       } else {
-                               $toField = $prefix . '_to';
-                       }
-                       if ( count( $deletions ) ) {
-                               $where[] = "$toField IN (" . $this->mDb->makeList( array_keys( $deletions ) ) . ')';
-                       } else {
-                               $where = false;
-                       }
-               }
-               if ( $where ) {
-                       $this->mDb->delete( $table, $where, __METHOD__ );
-               }
-               if ( count( $insertions ) ) {
-                       $this->mDb->insert( $table, $insertions, __METHOD__, 'IGNORE' );
-                       wfRunHooks( 'LinksUpdateAfterInsert', array( $this, $table, $insertions ) );
-               }
-       }
-
-       /**
-        * Get an array of pagelinks insertions for passing to the DB
-        * Skips the titles specified by the 2-D array $existing
-        * @param $existing array
-        * @return array
-        */
-       private function getLinkInsertions( $existing = array() ) {
-               $arr = array();
-               foreach ( $this->mLinks as $ns => $dbkeys ) {
-                       $diffs = isset( $existing[$ns] )
-                               ? array_diff_key( $dbkeys, $existing[$ns] )
-                               : $dbkeys;
-                       foreach ( $diffs as $dbk => $id ) {
-                               $arr[] = array(
-                                       'pl_from' => $this->mId,
-                                       'pl_namespace' => $ns,
-                                       'pl_title' => $dbk
-                               );
-                       }
-               }
-               return $arr;
-       }
-
-       /**
-        * Get an array of template insertions. Like getLinkInsertions()
-        * @param $existing array
-        * @return array
-        */
-       private function getTemplateInsertions( $existing = array() ) {
-               $arr = array();
-               foreach ( $this->mTemplates as $ns => $dbkeys ) {
-                       $diffs = isset( $existing[$ns] ) ? array_diff_key( $dbkeys, $existing[$ns] ) : $dbkeys;
-                       foreach ( $diffs as $dbk => $id ) {
-                               $arr[] = array(
-                                       'tl_from' => $this->mId,
-                                       'tl_namespace' => $ns,
-                                       'tl_title' => $dbk
-                               );
-                       }
-               }
-               return $arr;
-       }
-
-       /**
-        * Get an array of image insertions
-        * Skips the names specified in $existing
-        * @param $existing array
-        * @return array
-        */
-       private function getImageInsertions( $existing = array() ) {
-               $arr = array();
-               $diffs = array_diff_key( $this->mImages, $existing );
-               foreach ( $diffs as $iname => $dummy ) {
-                       $arr[] = array(
-                               'il_from' => $this->mId,
-                               'il_to' => $iname
-                       );
-               }
-               return $arr;
-       }
-
-       /**
-        * Get an array of externallinks insertions. Skips the names specified in $existing
-        * @param $existing array
-        * @return array
-        */
-       private function getExternalInsertions( $existing = array() ) {
-               $arr = array();
-               $diffs = array_diff_key( $this->mExternals, $existing );
-               foreach ( $diffs as $url => $dummy ) {
-                       foreach ( wfMakeUrlIndexes( $url ) as $index ) {
-                               $arr[] = array(
-                                       'el_from' => $this->mId,
-                                       'el_to' => $url,
-                                       'el_index' => $index,
-                               );
-                       }
-               }
-               return $arr;
-       }
-
-       /**
-        * Get an array of category insertions
-        *
-        * @param array $existing mapping existing category names to sort keys. If both
-        * match a link in $this, the link will be omitted from the output
-        *
-        * @return array
-        */
-       private function getCategoryInsertions( $existing = array() ) {
-               global $wgContLang, $wgCategoryCollation;
-               $diffs = array_diff_assoc( $this->mCategories, $existing );
-               $arr = array();
-               foreach ( $diffs as $name => $prefix ) {
-                       $nt = Title::makeTitleSafe( NS_CATEGORY, $name );
-                       $wgContLang->findVariantLink( $name, $nt, true );
-
-                       if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
-                               $type = 'subcat';
-                       } elseif ( $this->mTitle->getNamespace() == NS_FILE ) {
-                               $type = 'file';
-                       } else {
-                               $type = 'page';
-                       }
-
-                       # Treat custom sortkeys as a prefix, so that if multiple
-                       # things are forced to sort as '*' or something, they'll
-                       # sort properly in the category rather than in page_id
-                       # order or such.
-                       $sortkey = Collation::singleton()->getSortKey(
-                               $this->mTitle->getCategorySortkey( $prefix ) );
-
-                       $arr[] = array(
-                               'cl_from' => $this->mId,
-                               'cl_to' => $name,
-                               'cl_sortkey' => $sortkey,
-                               'cl_timestamp' => $this->mDb->timestamp(),
-                               'cl_sortkey_prefix' => $prefix,
-                               'cl_collation' => $wgCategoryCollation,
-                               'cl_type' => $type,
-                       );
-               }
-               return $arr;
-       }
-
-       /**
-        * Get an array of interlanguage link insertions
-        *
-        * @param array $existing mapping existing language codes to titles
-        *
-        * @return array
-        */
-       private function getInterlangInsertions( $existing = array() ) {
-               $diffs = array_diff_assoc( $this->mInterlangs, $existing );
-               $arr = array();
-               foreach ( $diffs as $lang => $title ) {
-                       $arr[] = array(
-                               'll_from' => $this->mId,
-                               'll_lang' => $lang,
-                               'll_title' => $title
-                       );
-               }
-               return $arr;
-       }
-
-       /**
-        * Get an array of page property insertions
-        * @param $existing array
-        * @return array
-        */
-       function getPropertyInsertions( $existing = array() ) {
-               $diffs = array_diff_assoc( $this->mProperties, $existing );
-               $arr = array();
-               foreach ( $diffs as $name => $value ) {
-                       $arr[] = array(
-                               'pp_page' => $this->mId,
-                               'pp_propname' => $name,
-                               'pp_value' => $value,
-                       );
-               }
-               return $arr;
-       }
-
-       /**
-        * Get an array of interwiki insertions for passing to the DB
-        * Skips the titles specified by the 2-D array $existing
-        * @param $existing array
-        * @return array
-        */
-       private function getInterwikiInsertions( $existing = array() ) {
-               $arr = array();
-               foreach ( $this->mInterwikis as $prefix => $dbkeys ) {
-                       $diffs = isset( $existing[$prefix] ) ? array_diff_key( $dbkeys, $existing[$prefix] ) : $dbkeys;
-                       foreach ( $diffs as $dbk => $id ) {
-                               $arr[] = array(
-                                       'iwl_from' => $this->mId,
-                                       'iwl_prefix' => $prefix,
-                                       'iwl_title' => $dbk
-                               );
-                       }
-               }
-               return $arr;
-       }
-
-       /**
-        * Given an array of existing links, returns those links which are not in $this
-        * and thus should be deleted.
-        * @param $existing array
-        * @return array
-        */
-       private function getLinkDeletions( $existing ) {
-               $del = array();
-               foreach ( $existing as $ns => $dbkeys ) {
-                       if ( isset( $this->mLinks[$ns] ) ) {
-                               $del[$ns] = array_diff_key( $existing[$ns], $this->mLinks[$ns] );
-                       } else {
-                               $del[$ns] = $existing[$ns];
-                       }
-               }
-               return $del;
-       }
-
-       /**
-        * Given an array of existing templates, returns those templates which are not in $this
-        * and thus should be deleted.
-        * @param $existing array
-        * @return array
-        */
-       private function getTemplateDeletions( $existing ) {
-               $del = array();
-               foreach ( $existing as $ns => $dbkeys ) {
-                       if ( isset( $this->mTemplates[$ns] ) ) {
-                               $del[$ns] = array_diff_key( $existing[$ns], $this->mTemplates[$ns] );
-                       } else {
-                               $del[$ns] = $existing[$ns];
-                       }
-               }
-               return $del;
-       }
-
-       /**
-        * Given an array of existing images, returns those images which are not in $this
-        * and thus should be deleted.
-        * @param $existing array
-        * @return array
-        */
-       private function getImageDeletions( $existing ) {
-               return array_diff_key( $existing, $this->mImages );
-       }
-
-       /**
-        * Given an array of existing external links, returns those links which are not
-        * in $this and thus should be deleted.
-        * @param $existing array
-        * @return array
-        */
-       private function getExternalDeletions( $existing ) {
-               return array_diff_key( $existing, $this->mExternals );
-       }
-
-       /**
-        * Given an array of existing categories, returns those categories which are not in $this
-        * and thus should be deleted.
-        * @param $existing array
-        * @return array
-        */
-       private function getCategoryDeletions( $existing ) {
-               return array_diff_assoc( $existing, $this->mCategories );
-       }
-
-       /**
-        * Given an array of existing interlanguage links, returns those links which are not
-        * in $this and thus should be deleted.
-        * @param $existing array
-        * @return array
-        */
-       private function getInterlangDeletions( $existing ) {
-               return array_diff_assoc( $existing, $this->mInterlangs );
-       }
-
-       /**
-        * Get array of properties which should be deleted.
-        * @param $existing array
-        * @return array
-        */
-       function getPropertyDeletions( $existing ) {
-               return array_diff_assoc( $existing, $this->mProperties );
-       }
-
-       /**
-        * Given an array of existing interwiki links, returns those links which are not in $this
-        * and thus should be deleted.
-        * @param $existing array
-        * @return array
-        */
-       private function getInterwikiDeletions( $existing ) {
-               $del = array();
-               foreach ( $existing as $prefix => $dbkeys ) {
-                       if ( isset( $this->mInterwikis[$prefix] ) ) {
-                               $del[$prefix] = array_diff_key( $existing[$prefix], $this->mInterwikis[$prefix] );
-                       } else {
-                               $del[$prefix] = $existing[$prefix];
-                       }
-               }
-               return $del;
-       }
-
-       /**
-        * Get an array of existing links, as a 2-D array
-        *
-        * @return array
-        */
-       private function getExistingLinks() {
-               $res = $this->mDb->select( 'pagelinks', array( 'pl_namespace', 'pl_title' ),
-                       array( 'pl_from' => $this->mId ), __METHOD__, $this->mOptions );
-               $arr = array();
-               foreach ( $res as $row ) {
-                       if ( !isset( $arr[$row->pl_namespace] ) ) {
-                               $arr[$row->pl_namespace] = array();
-                       }
-                       $arr[$row->pl_namespace][$row->pl_title] = 1;
-               }
-               return $arr;
-       }
-
-       /**
-        * Get an array of existing templates, as a 2-D array
-        *
-        * @return array
-        */
-       private function getExistingTemplates() {
-               $res = $this->mDb->select( 'templatelinks', array( 'tl_namespace', 'tl_title' ),
-                       array( 'tl_from' => $this->mId ), __METHOD__, $this->mOptions );
-               $arr = array();
-               foreach ( $res as $row ) {
-                       if ( !isset( $arr[$row->tl_namespace] ) ) {
-                               $arr[$row->tl_namespace] = array();
-                       }
-                       $arr[$row->tl_namespace][$row->tl_title] = 1;
-               }
-               return $arr;
-       }
-
-       /**
-        * Get an array of existing images, image names in the keys
-        *
-        * @return array
-        */
-       private function getExistingImages() {
-               $res = $this->mDb->select( 'imagelinks', array( 'il_to' ),
-                       array( 'il_from' => $this->mId ), __METHOD__, $this->mOptions );
-               $arr = array();
-               foreach ( $res as $row ) {
-                       $arr[$row->il_to] = 1;
-               }
-               return $arr;
-       }
-
-       /**
-        * Get an array of existing external links, URLs in the keys
-        *
-        * @return array
-        */
-       private function getExistingExternals() {
-               $res = $this->mDb->select( 'externallinks', array( 'el_to' ),
-                       array( 'el_from' => $this->mId ), __METHOD__, $this->mOptions );
-               $arr = array();
-               foreach ( $res as $row ) {
-                       $arr[$row->el_to] = 1;
-               }
-               return $arr;
-       }
-
-       /**
-        * Get an array of existing categories, with the name in the key and sort key in the value.
-        *
-        * @return array
-        */
-       private function getExistingCategories() {
-               $res = $this->mDb->select( 'categorylinks', array( 'cl_to', 'cl_sortkey_prefix' ),
-                       array( 'cl_from' => $this->mId ), __METHOD__, $this->mOptions );
-               $arr = array();
-               foreach ( $res as $row ) {
-                       $arr[$row->cl_to] = $row->cl_sortkey_prefix;
-               }
-               return $arr;
-       }
-
-       /**
-        * Get an array of existing interlanguage links, with the language code in the key and the
-        * title in the value.
-        *
-        * @return array
-        */
-       private function getExistingInterlangs() {
-               $res = $this->mDb->select( 'langlinks', array( 'll_lang', 'll_title' ),
-                       array( 'll_from' => $this->mId ), __METHOD__, $this->mOptions );
-               $arr = array();
-               foreach ( $res as $row ) {
-                       $arr[$row->ll_lang] = $row->ll_title;
-               }
-               return $arr;
-       }
-
-       /**
-        * Get an array of existing inline interwiki links, as a 2-D array
-        * @return array (prefix => array(dbkey => 1))
-        */
-       protected function getExistingInterwikis() {
-               $res = $this->mDb->select( 'iwlinks', array( 'iwl_prefix', 'iwl_title' ),
-                       array( 'iwl_from' => $this->mId ), __METHOD__, $this->mOptions );
-               $arr = array();
-               foreach ( $res as $row ) {
-                       if ( !isset( $arr[$row->iwl_prefix] ) ) {
-                               $arr[$row->iwl_prefix] = array();
-                       }
-                       $arr[$row->iwl_prefix][$row->iwl_title] = 1;
-               }
-               return $arr;
-       }
-
-       /**
-        * Get an array of existing categories, with the name in the key and sort key in the value.
-        *
-        * @return array
-        */
-       private function getExistingProperties() {
-               $res = $this->mDb->select( 'page_props', array( 'pp_propname', 'pp_value' ),
-                       array( 'pp_page' => $this->mId ), __METHOD__, $this->mOptions );
-               $arr = array();
-               foreach ( $res as $row ) {
-                       $arr[$row->pp_propname] = $row->pp_value;
-               }
-               return $arr;
-       }
-
-       /**
-        * Return the title object of the page being updated
-        * @return Title
-        */
-       public function getTitle() {
-               return $this->mTitle;
-       }
-
-       /**
-        * Returns parser output
-        * @since 1.19
-        * @return ParserOutput
-        */
-       public function getParserOutput() {
-               return $this->mParserOutput;
-       }
-
-       /**
-        * Return the list of images used as generated by the parser
-        * @return array
-        */
-       public function getImages() {
-               return $this->mImages;
-       }
-
-       /**
-        * Invalidate any necessary link lists related to page property changes
-        * @param $changed
-        */
-       private function invalidateProperties( $changed ) {
-               global $wgPagePropLinkInvalidations;
-
-               foreach ( $changed as $name => $value ) {
-                       if ( isset( $wgPagePropLinkInvalidations[$name] ) ) {
-                               $inv = $wgPagePropLinkInvalidations[$name];
-                               if ( !is_array( $inv ) ) {
-                                       $inv = array( $inv );
-                               }
-                               foreach ( $inv as $table ) {
-                                       $update = new HTMLCacheUpdate( $this->mTitle, $table );
-                                       $update->doUpdate();
-                               }
-                       }
-               }
-       }
-
-       /**
-        * Fetch page links added by this LinksUpdate.  Only available after the update is complete.
-        * @since 1.22
-        * @return null|array of Titles
-        */
-       public function getAddedLinks() {
-               if ( $this->linkInsertions === null ) {
-                       return null;
-               }
-               $result = array();
-               foreach ( $this->linkInsertions as $insertion ) {
-                       $result[] = Title::makeTitle( $insertion[ 'pl_namespace' ], $insertion[ 'pl_title' ] );
-               }
-               return $result;
-       }
-
-       /**
-        * Fetch page links removed by this LinksUpdate.  Only available after the update is complete.
-        * @since 1.22
-        * @return null|array of Titles
-        */
-       public function getRemovedLinks() {
-               if ( $this->linkDeletions === null ) {
-                       return null;
-               }
-               $result = array();
-               foreach ( $this->linkDeletions as $ns => $titles ) {
-                       foreach ( $titles as $title => $unused ) {
-                               $result[] = Title::makeTitle( $ns, $title );
-                       }
-               }
-               return $result;
-       }
-}
-
-/**
- * Update object handling the cleanup of links tables after a page was deleted.
- **/
-class LinksDeletionUpdate extends SqlDataUpdate {
-
-       protected $mPage;     //!< WikiPage the wikipage that was deleted
-
-       /**
-        * Constructor
-        *
-        * @param $page WikiPage Page we are updating
-        * @throws MWException
-        */
-       function __construct( WikiPage $page ) {
-               parent::__construct( false ); // no implicit transaction
-
-               $this->mPage = $page;
-
-               if ( !$page->exists() ) {
-                       throw new MWException( "Page ID not known, perhaps the page doesn't exist?" );
-               }
-       }
-
-       /**
-        * Do some database updates after deletion
-        */
-       public function doUpdate() {
-               $title = $this->mPage->getTitle();
-               $id = $this->mPage->getId();
-
-               # Delete restrictions for it
-               $this->mDb->delete( 'page_restrictions', array( 'pr_page' => $id ), __METHOD__ );
-
-               # Fix category table counts
-               $cats = array();
-               $res = $this->mDb->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ );
-
-               foreach ( $res as $row ) {
-                       $cats[] = $row->cl_to;
-               }
-
-               $this->mPage->updateCategoryCounts( array(), $cats );
-
-               # If using cascading deletes, we can skip some explicit deletes
-               if ( !$this->mDb->cascadingDeletes() ) {
-                       # Delete outgoing links
-                       $this->mDb->delete( 'pagelinks', array( 'pl_from' => $id ), __METHOD__ );
-                       $this->mDb->delete( 'imagelinks', array( 'il_from' => $id ), __METHOD__ );
-                       $this->mDb->delete( 'categorylinks', array( 'cl_from' => $id ), __METHOD__ );
-                       $this->mDb->delete( 'templatelinks', array( 'tl_from' => $id ), __METHOD__ );
-                       $this->mDb->delete( 'externallinks', array( 'el_from' => $id ), __METHOD__ );
-                       $this->mDb->delete( 'langlinks', array( 'll_from' => $id ), __METHOD__ );
-                       $this->mDb->delete( 'iwlinks', array( 'iwl_from' => $id ), __METHOD__ );
-                       $this->mDb->delete( 'redirect', array( 'rd_from' => $id ), __METHOD__ );
-                       $this->mDb->delete( 'page_props', array( 'pp_page' => $id ), __METHOD__ );
-               }
-
-               # If using cleanup triggers, we can skip some manual deletes
-               if ( !$this->mDb->cleanupTriggers() ) {
-                       # Clean up recentchanges entries...
-                       $this->mDb->delete( 'recentchanges',
-                               array( 'rc_type != ' . RC_LOG,
-                                       'rc_namespace' => $title->getNamespace(),
-                                       'rc_title' => $title->getDBkey() ),
-                               __METHOD__ );
-                       $this->mDb->delete( 'recentchanges',
-                               array( 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ),
-                               __METHOD__ );
-               }
-       }
-
-       /**
-        * Update all the appropriate counts in the category table.
-        * @param array $added associative array of category name => sort key
-        * @param array $deleted associative array of category name => sort key
-        */
-       function updateCategoryCounts( $added, $deleted ) {
-               $a = WikiPage::factory( $this->mTitle );
-               $a->updateCategoryCounts(
-                       array_keys( $added ), array_keys( $deleted )
-               );
-       }
-}
diff --git a/includes/MWCryptRand.php b/includes/MWCryptRand.php
deleted file mode 100644 (file)
index bac018e..0000000
+++ /dev/null
@@ -1,497 +0,0 @@
-<?php
-/**
- * A cryptographic random generator class used for generating secret keys
- *
- * This is based in part on Drupal code as well as what we used in our own code
- * prior to introduction of this class.
- *
- * 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
- *
- * @author Daniel Friesen
- * @file
- */
-
-class MWCryptRand {
-
-       /**
-        * Minimum number of iterations we want to make in our drift calculations.
-        */
-       const MIN_ITERATIONS = 1000;
-
-       /**
-        * Number of milliseconds we want to spend generating each separate byte
-        * of the final generated bytes.
-        * This is used in combination with the hash length to determine the duration
-        * we should spend doing drift calculations.
-        */
-       const MSEC_PER_BYTE = 0.5;
-
-       /**
-        * Singleton instance for public use
-        */
-       protected static $singleton = null;
-
-       /**
-        * The hash algorithm being used
-        */
-       protected $algo = null;
-
-       /**
-        * The number of bytes outputted by the hash algorithm
-        */
-       protected $hashLength = null;
-
-       /**
-        * A boolean indicating whether the previous random generation was done using
-        * cryptographically strong random number generator or not.
-        */
-       protected $strong = null;
-
-       /**
-        * Initialize an initial random state based off of whatever we can find
-        */
-       protected function initialRandomState() {
-               // $_SERVER contains a variety of unstable user and system specific information
-               // It'll vary a little with each page, and vary even more with separate users
-               // It'll also vary slightly across different machines
-               $state = serialize( $_SERVER );
-
-               // To try vary the system information of the state a bit more
-               // by including the system's hostname into the state
-               $state .= wfHostname();
-
-               // Try to gather a little entropy from the different php rand sources
-               $state .= rand() . uniqid( mt_rand(), true );
-
-               // Include some information about the filesystem's current state in the random state
-               $files = array();
-
-               // We know this file is here so grab some info about ourselves
-               $files[] = __FILE__;
-
-               // We must also have a parent folder, and with the usual file structure, a grandparent
-               $files[] = __DIR__;
-               $files[] = dirname( __DIR__ );
-
-               // The config file is likely the most often edited file we know should be around
-               // so include its stat info into the state.
-               // The constant with its location will almost always be defined, as WebStart.php defines
-               // MW_CONFIG_FILE to $IP/LocalSettings.php unless being configured with MW_CONFIG_CALLBACK (eg. the installer)
-               if ( defined( 'MW_CONFIG_FILE' ) ) {
-                       $files[] = MW_CONFIG_FILE;
-               }
-
-               foreach ( $files as $file ) {
-                       wfSuppressWarnings();
-                       $stat = stat( $file );
-                       wfRestoreWarnings();
-                       if ( $stat ) {
-                               // stat() duplicates data into numeric and string keys so kill off all the numeric ones
-                               foreach ( $stat as $k => $v ) {
-                                       if ( is_numeric( $k ) ) {
-                                               unset( $k );
-                                       }
-                               }
-                               // The absolute filename itself will differ from install to install so don't leave it out
-                               if ( ( $path = realpath( $file ) ) !== false ) {
-                                       $state .= $path;
-                               } else {
-                                       $state .= $file;
-                               }
-                               $state .= implode( '', $stat );
-                       } else {
-                               // The fact that the file isn't there is worth at least a
-                               // minuscule amount of entropy.
-                               $state .= '0';
-                       }
-               }
-
-               // Try and make this a little more unstable by including the varying process
-               // id of the php process we are running inside of if we are able to access it
-               if ( function_exists( 'getmypid' ) ) {
-                       $state .= getmypid();
-               }
-
-               // If available try to increase the instability of the data by throwing in
-               // the precise amount of memory that we happen to be using at the moment.
-               if ( function_exists( 'memory_get_usage' ) ) {
-                       $state .= memory_get_usage( true );
-               }
-
-               // It's mostly worthless but throw the wiki's id into the data for a little more variance
-               $state .= wfWikiID();
-
-               // If we have a secret key or proxy key set then throw it into the state as well
-               global $wgSecretKey, $wgProxyKey;
-               if ( $wgSecretKey ) {
-                       $state .= $wgSecretKey;
-               } elseif ( $wgProxyKey ) {
-                       $state .= $wgProxyKey;
-               }
-
-               return $state;
-       }
-
-       /**
-        * Randomly hash data while mixing in clock drift data for randomness
-        *
-        * @param string $data The data to randomly hash.
-        * @return String The hashed bytes
-        * @author Tim Starling
-        */
-       protected function driftHash( $data ) {
-               // Minimum number of iterations (to avoid slow operations causing the loop to gather little entropy)
-               $minIterations = self::MIN_ITERATIONS;
-               // Duration of time to spend doing calculations (in seconds)
-               $duration = ( self::MSEC_PER_BYTE / 1000 ) * $this->hashLength();
-               // Create a buffer to use to trigger memory operations
-               $bufLength = 10000000;
-               $buffer = str_repeat( ' ', $bufLength );
-               $bufPos = 0;
-
-               // Iterate for $duration seconds or at least $minIterations number of iterations
-               $iterations = 0;
-               $startTime = microtime( true );
-               $currentTime = $startTime;
-               while ( $iterations < $minIterations || $currentTime - $startTime < $duration ) {
-                       // Trigger some memory writing to trigger some bus activity
-                       // This may create variance in the time between iterations
-                       $bufPos = ( $bufPos + 13 ) % $bufLength;
-                       $buffer[$bufPos] = ' ';
-                       // Add the drift between this iteration and the last in as entropy
-                       $nextTime = microtime( true );
-                       $delta = (int)( ( $nextTime - $currentTime ) * 1000000 );
-                       $data .= $delta;
-                       // Every 100 iterations hash the data and entropy
-                       if ( $iterations % 100 === 0 ) {
-                               $data = sha1( $data );
-                       }
-                       $currentTime = $nextTime;
-                       $iterations++;
-               }
-               $timeTaken = $currentTime - $startTime;
-               $data = $this->hash( $data );
-
-               wfDebug( __METHOD__ . ": Clock drift calculation " .
-                       "(time-taken=" . ( $timeTaken * 1000 ) . "ms, " .
-                       "iterations=$iterations, " .
-                       "time-per-iteration=" . ( $timeTaken / $iterations * 1e6 ) . "us)\n" );
-               return $data;
-       }
-
-       /**
-        * Return a rolling random state initially build using data from unstable sources
-        * @return string A new weak random state
-        */
-       protected function randomState() {
-               static $state = null;
-               if ( is_null( $state ) ) {
-                       // Initialize the state with whatever unstable data we can find
-                       // It's important that this data is hashed right afterwards to prevent
-                       // it from being leaked into the output stream
-                       $state = $this->hash( $this->initialRandomState() );
-               }
-               // Generate a new random state based on the initial random state or previous
-               // random state by combining it with clock drift
-               $state = $this->driftHash( $state );
-               return $state;
-       }
-
-       /**
-        * Decide on the best acceptable hash algorithm we have available for hash()
-        * @throws MWException
-        * @return String A hash algorithm
-        */
-       protected function hashAlgo() {
-               if ( !is_null( $this->algo ) ) {
-                       return $this->algo;
-               }
-
-               $algos = hash_algos();
-               $preference = array( 'whirlpool', 'sha256', 'sha1', 'md5' );
-
-               foreach ( $preference as $algorithm ) {
-                       if ( in_array( $algorithm, $algos ) ) {
-                               $this->algo = $algorithm;
-                               wfDebug( __METHOD__ . ": Using the {$this->algo} hash algorithm.\n" );
-                               return $this->algo;
-                       }
-               }
-
-               // We only reach here if no acceptable hash is found in the list, this should
-               // be a technical impossibility since most of php's hash list is fixed and
-               // some of the ones we list are available as their own native functions
-               // But since we already require at least 5.2 and hash() was default in
-               // 5.1.2 we don't bother falling back to methods like sha1 and md5.
-               throw new MWException( "Could not find an acceptable hashing function in hash_algos()" );
-       }
-
-       /**
-        * Return the byte-length output of the hash algorithm we are
-        * using in self::hash and self::hmac.
-        *
-        * @return int Number of bytes the hash outputs
-        */
-       protected function hashLength() {
-               if ( is_null( $this->hashLength ) ) {
-                       $this->hashLength = strlen( $this->hash( '' ) );
-               }
-               return $this->hashLength;
-       }
-
-       /**
-        * Generate an acceptably unstable one-way-hash of some text
-        * making use of the best hash algorithm that we have available.
-        *
-        * @param $data string
-        * @return String A raw hash of the data
-        */
-       protected function hash( $data ) {
-               return hash( $this->hashAlgo(), $data, true );
-       }
-
-       /**
-        * Generate an acceptably unstable one-way-hmac of some text
-        * making use of the best hash algorithm that we have available.
-        *
-        * @param $data string
-        * @param $key string
-        * @return String A raw hash of the data
-        */
-       protected function hmac( $data, $key ) {
-               return hash_hmac( $this->hashAlgo(), $data, $key, true );
-       }
-
-       /**
-        * @see self::wasStrong()
-        */
-       public function realWasStrong() {
-               if ( is_null( $this->strong ) ) {
-                       throw new MWException( __METHOD__ . ' called before generation of random data' );
-               }
-               return $this->strong;
-       }
-
-       /**
-        * @see self::generate()
-        */
-       public function realGenerate( $bytes, $forceStrong = false ) {
-               wfProfileIn( __METHOD__ );
-
-               wfDebug( __METHOD__ . ": Generating cryptographic random bytes for " . wfGetAllCallers( 5 ) . "\n" );
-
-               $bytes = floor( $bytes );
-               static $buffer = '';
-               if ( is_null( $this->strong ) ) {
-                       // Set strength to false initially until we know what source data is coming from
-                       $this->strong = true;
-               }
-
-               if ( strlen( $buffer ) < $bytes ) {
-                       // If available make use of mcrypt_create_iv URANDOM source to generate randomness
-                       // On unix-like systems this reads from /dev/urandom but does it without any buffering
-                       // and bypasses openbasedir restrictions, so it's preferable to reading directly
-                       // On Windows starting in PHP 5.3.0 Windows' native CryptGenRandom is used to generate
-                       // entropy so this is also preferable to just trying to read urandom because it may work
-                       // on Windows systems as well.
-                       if ( function_exists( 'mcrypt_create_iv' ) ) {
-                               wfProfileIn( __METHOD__ . '-mcrypt' );
-                               $rem = $bytes - strlen( $buffer );
-                               $iv = mcrypt_create_iv( $rem, MCRYPT_DEV_URANDOM );
-                               if ( $iv === false ) {
-                                       wfDebug( __METHOD__ . ": mcrypt_create_iv returned false.\n" );
-                               } else {
-                                       $buffer .= $iv;
-                                       wfDebug( __METHOD__ . ": mcrypt_create_iv generated " . strlen( $iv ) . " bytes of randomness.\n" );
-                               }
-                               wfProfileOut( __METHOD__ . '-mcrypt' );
-                       }
-               }
-
-               if ( strlen( $buffer ) < $bytes ) {
-                       // If available make use of openssl's random_pseudo_bytes method to attempt to generate randomness.
-                       // However don't do this on Windows with PHP < 5.3.4 due to a bug:
-                       // http://stackoverflow.com/questions/1940168/openssl-random-pseudo-bytes-is-slow-php
-                       // http://git.php.net/?p=php-src.git;a=commitdiff;h=cd62a70863c261b07f6dadedad9464f7e213cad5
-                       if ( function_exists( 'openssl_random_pseudo_bytes' )
-                               && ( !wfIsWindows() || version_compare( PHP_VERSION, '5.3.4', '>=' ) )
-                       ) {
-                               wfProfileIn( __METHOD__ . '-openssl' );
-                               $rem = $bytes - strlen( $buffer );
-                               $openssl_bytes = openssl_random_pseudo_bytes( $rem, $openssl_strong );
-                               if ( $openssl_bytes === false ) {
-                                       wfDebug( __METHOD__ . ": openssl_random_pseudo_bytes returned false.\n" );
-                               } else {
-                                       $buffer .= $openssl_bytes;
-                                       wfDebug( __METHOD__ . ": openssl_random_pseudo_bytes generated " . strlen( $openssl_bytes ) . " bytes of " . ( $openssl_strong ? "strong" : "weak" ) . " randomness.\n" );
-                               }
-                               if ( strlen( $buffer ) >= $bytes ) {
-                                       // openssl tells us if the random source was strong, if some of our data was generated
-                                       // using it use it's say on whether the randomness is strong
-                                       $this->strong = !!$openssl_strong;
-                               }
-                               wfProfileOut( __METHOD__ . '-openssl' );
-                       }
-               }
-
-               // Only read from urandom if we can control the buffer size or were passed forceStrong
-               if ( strlen( $buffer ) < $bytes && ( function_exists( 'stream_set_read_buffer' ) || $forceStrong ) ) {
-                       wfProfileIn( __METHOD__ . '-fopen-urandom' );
-                       $rem = $bytes - strlen( $buffer );
-                       if ( !function_exists( 'stream_set_read_buffer' ) && $forceStrong ) {
-                               wfDebug( __METHOD__ . ": Was forced to read from /dev/urandom without control over the buffer size.\n" );
-                       }
-                       // /dev/urandom is generally considered the best possible commonly
-                       // available random source, and is available on most *nix systems.
-                       wfSuppressWarnings();
-                       $urandom = fopen( "/dev/urandom", "rb" );
-                       wfRestoreWarnings();
-
-                       // Attempt to read all our random data from urandom
-                       // php's fread always does buffered reads based on the stream's chunk_size
-                       // so in reality it will usually read more than the amount of data we're
-                       // asked for and not storing that risks depleting the system's random pool.
-                       // If stream_set_read_buffer is available set the chunk_size to the amount
-                       // of data we need. Otherwise read 8k, php's default chunk_size.
-                       if ( $urandom ) {
-                               // php's default chunk_size is 8k
-                               $chunk_size = 1024 * 8;
-                               if ( function_exists( 'stream_set_read_buffer' ) ) {
-                                       // If possible set the chunk_size to the amount of data we need
-                                       stream_set_read_buffer( $urandom, $rem );
-                                       $chunk_size = $rem;
-                               }
-                               $random_bytes = fread( $urandom, max( $chunk_size, $rem ) );
-                               $buffer .= $random_bytes;
-                               fclose( $urandom );
-                               wfDebug( __METHOD__ . ": /dev/urandom generated " . strlen( $random_bytes ) . " bytes of randomness.\n" );
-                               if ( strlen( $buffer ) >= $bytes ) {
-                                       // urandom is always strong, set to true if all our data was generated using it
-                                       $this->strong = true;
-                               }
-                       } else {
-                               wfDebug( __METHOD__ . ": /dev/urandom could not be opened.\n" );
-                       }
-                       wfProfileOut( __METHOD__ . '-fopen-urandom' );
-               }
-
-               // If we cannot use or generate enough data from a secure source
-               // use this loop to generate a good set of pseudo random data.
-               // This works by initializing a random state using a pile of unstable data
-               // and continually shoving it through a hash along with a variable salt.
-               // We hash the random state with more salt to avoid the state from leaking
-               // out and being used to predict the /randomness/ that follows.
-               if ( strlen( $buffer ) < $bytes ) {
-                       wfDebug( __METHOD__ . ": Falling back to using a pseudo random state to generate randomness.\n" );
-               }
-               while ( strlen( $buffer ) < $bytes ) {
-                       wfProfileIn( __METHOD__ . '-fallback' );
-                       $buffer .= $this->hmac( $this->randomState(), mt_rand() );
-                       // This code is never really cryptographically strong, if we use it
-                       // at all, then set strong to false.
-                       $this->strong = false;
-                       wfProfileOut( __METHOD__ . '-fallback' );
-               }
-
-               // Once the buffer has been filled up with enough random data to fulfill
-               // the request shift off enough data to handle the request and leave the
-               // unused portion left inside the buffer for the next request for random data
-               $generated = substr( $buffer, 0, $bytes );
-               $buffer = substr( $buffer, $bytes );
-
-               wfDebug( __METHOD__ . ": " . strlen( $buffer ) . " bytes of randomness leftover in the buffer.\n" );
-
-               wfProfileOut( __METHOD__ );
-               return $generated;
-       }
-
-       /**
-        * @see self::generateHex()
-        */
-       public function realGenerateHex( $chars, $forceStrong = false ) {
-               // hex strings are 2x the length of raw binary so we divide the length in half
-               // odd numbers will result in a .5 that leads the generate() being 1 character
-               // short, so we use ceil() to ensure that we always have enough bytes
-               $bytes = ceil( $chars / 2 );
-               // Generate the data and then convert it to a hex string
-               $hex = bin2hex( $this->generate( $bytes, $forceStrong ) );
-               // A bit of paranoia here, the caller asked for a specific length of string
-               // here, and it's possible (eg when given an odd number) that we may actually
-               // have at least 1 char more than they asked for. Just in case they made this
-               // call intending to insert it into a database that does truncation we don't
-               // want to give them too much and end up with their database and their live
-               // code having two different values because part of what we gave them is truncated
-               // hence, we strip out any run of characters longer than what we were asked for.
-               return substr( $hex, 0, $chars );
-       }
-
-       /** Publicly exposed static methods **/
-
-       /**
-        * Return a singleton instance of MWCryptRand
-        * @return MWCryptRand
-        */
-       protected static function singleton() {
-               if ( is_null( self::$singleton ) ) {
-                       self::$singleton = new self;
-               }
-               return self::$singleton;
-       }
-
-       /**
-        * Return a boolean indicating whether or not the source used for cryptographic
-        * random bytes generation in the previously run generate* call
-        * was cryptographically strong.
-        *
-        * @return bool Returns true if the source was strong, false if not.
-        */
-       public static function wasStrong() {
-               return self::singleton()->realWasStrong();
-       }
-
-       /**
-        * Generate a run of (ideally) cryptographically random data and return
-        * it in raw binary form.
-        * You can use MWCryptRand::wasStrong() if you wish to know if the source used
-        * was cryptographically strong.
-        *
-        * @param int $bytes the number of bytes of random data to generate
-        * @param bool $forceStrong Pass true if you want generate to prefer cryptographically
-        *                          strong sources of entropy even if reading from them may steal
-        *                          more entropy from the system than optimal.
-        * @return String Raw binary random data
-        */
-       public static function generate( $bytes, $forceStrong = false ) {
-               return self::singleton()->realGenerate( $bytes, $forceStrong );
-       }
-
-       /**
-        * Generate a run of (ideally) cryptographically random data and return
-        * it in hexadecimal string format.
-        * You can use MWCryptRand::wasStrong() if you wish to know if the source used
-        * was cryptographically strong.
-        *
-        * @param int $chars the number of hex chars of random data to generate
-        * @param bool $forceStrong Pass true if you want generate to prefer cryptographically
-        *                          strong sources of entropy even if reading from them may steal
-        *                          more entropy from the system than optimal.
-        * @return String Hexadecimal random data
-        */
-       public static function generateHex( $chars, $forceStrong = false ) {
-               return self::singleton()->realGenerateHex( $chars, $forceStrong );
-       }
-
-}
diff --git a/includes/MWFunction.php b/includes/MWFunction.php
deleted file mode 100644 (file)
index 6d11d17..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-<?php
-/**
- * Helper methods to call functions and instance objects.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-class MWFunction {
-
-       /**
-        * @deprecated since 1.22; use call_user_func()
-        * @param $callback
-        * @return mixed
-        */
-       public static function call( $callback ) {
-               wfDeprecated( __METHOD__, '1.22' );
-               $args = func_get_args();
-               return call_user_func_array( 'call_user_func', $args );
-       }
-
-       /**
-        * @deprecated since 1.22; use call_user_func_array()
-        * @param $callback
-        * @param $argsarams
-        * @return mixed
-        */
-       public static function callArray( $callback, $argsarams ) {
-               wfDeprecated( __METHOD__, '1.22' );
-               return call_user_func_array( $callback, $argsarams );
-       }
-
-       /**
-        * @param $class
-        * @param $args array
-        * @return object
-        */
-       public static function newObj( $class, $args = array() ) {
-               if ( !count( $args ) ) {
-                       return new $class;
-               }
-
-               $ref = new ReflectionClass( $class );
-               return $ref->newInstanceArgs( $args );
-       }
-
-}
diff --git a/includes/MappedIterator.php b/includes/MappedIterator.php
deleted file mode 100644 (file)
index 70d2032..0000000
+++ /dev/null
@@ -1,114 +0,0 @@
-<?php
-/**
- * Convenience class for generating iterators from iterators.
- *
- * 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 Aaron Schulz
- */
-
-/**
- * Convenience class for generating iterators from iterators.
- *
- * @since 1.21
- */
-class MappedIterator extends FilterIterator {
-       /** @var callable */
-       protected $vCallback;
-       /** @var callable */
-       protected $aCallback;
-       /** @var array */
-       protected $cache = array();
-
-       protected $rewound = false; // boolean; whether rewind() has been called
-
-       /**
-        * Build an new iterator from a base iterator by having the former wrap the
-        * later, returning the result of "value" callback for each current() invocation.
-        * The callback takes the result of current() on the base iterator as an argument.
-        * The keys of the base iterator are reused verbatim.
-        *
-        * An "accept" callback can also be provided which will be called for each value in
-        * the base iterator (post-callback) and will return true if that value should be
-        * included in iteration of the MappedIterator (otherwise it will be filtered out).
-        *
-        * @param Iterator|Array $iter
-        * @param callable $vCallback Value transformation callback
-        * @param array $options Options map (includes "accept") (since 1.22)
-        * @throws MWException
-        */
-       public function __construct( $iter, $vCallback, array $options = array() ) {
-               if ( is_array( $iter ) ) {
-                       $baseIterator = new ArrayIterator( $iter );
-               } elseif ( $iter instanceof Iterator ) {
-                       $baseIterator = $iter;
-               } else {
-                       throw new MWException( "Invalid base iterator provided." );
-               }
-               parent::__construct( $baseIterator );
-               $this->vCallback = $vCallback;
-               $this->aCallback = isset( $options['accept'] ) ? $options['accept'] : null;
-       }
-
-       public function next() {
-               $this->cache = array();
-               parent::next();
-       }
-
-       public function rewind() {
-               $this->rewound = true;
-               $this->cache = array();
-               parent::rewind();
-       }
-
-       public function accept() {
-               $value = call_user_func( $this->vCallback, $this->getInnerIterator()->current() );
-               $ok = ( $this->aCallback ) ? call_user_func( $this->aCallback, $value ) : true;
-               if ( $ok ) {
-                       $this->cache['current'] = $value;
-               }
-               return $ok;
-       }
-
-       public function key() {
-               $this->init();
-               return parent::key();
-       }
-
-       public function valid() {
-               $this->init();
-               return parent::valid();
-       }
-
-       public function current() {
-               $this->init();
-               if ( parent::valid() ) {
-                       return $this->cache['current'];
-               } else {
-                       return null; // out of range
-               }
-       }
-
-       /**
-        * Obviate the usual need for rewind() before using a FilterIterator in a manual loop
-        */
-       protected function init() {
-               if ( !$this->rewound ) {
-                       $this->rewind();
-               }
-       }
-}
index 37df489..ea89f52 100644 (file)
@@ -28,10 +28,10 @@ abstract class RdfMetaData {
 
        /**
         * Constructor
-        * @param $article Article object
+        * @param Page $page
         */
-       public function __construct( Page $article ) {
-               $this->mArticle = $article;
+       public function __construct( Page $page ) {
+               $this->mArticle = $page;
        }
 
        abstract public function show();
@@ -40,7 +40,10 @@ abstract class RdfMetaData {
                global $wgOut, $wgRequest;
 
                $httpaccept = isset( $_SERVER['HTTP_ACCEPT'] ) ? $_SERVER['HTTP_ACCEPT'] : null;
-               $rdftype = wfNegotiateType( wfAcceptToPrefs( $httpaccept ), wfAcceptToPrefs( self::RDF_TYPE_PREFS ) );
+               $rdftype = wfNegotiateType(
+                       wfAcceptToPrefs( $httpaccept ),
+                       wfAcceptToPrefs( self::RDF_TYPE_PREFS )
+               );
 
                if ( !$rdftype ) {
                        throw new HttpError( 406, wfMessage( 'notacceptable' ) );
@@ -103,8 +106,8 @@ abstract class RdfMetaData {
        }
 
        /**
-        * @param $name string
-        * @param $title Title
+        * @param string $name
+        * @param Title $title
         */
        protected function page( $name, $title ) {
                $this->url( $name, $title->getFullURL() );
index 02d3546..e6d6ebf 100644 (file)
@@ -38,7 +38,7 @@
  * version are hardcoded here
  */
 function wfPHPVersionError( $type ) {
-       $mwVersion = '1.22';
+       $mwVersion = '1.23';
        $minimumVersionPHP = '5.3.2';
 
        $phpVersion = phpversion();
index 499d821..4dbc9dd 100644 (file)
@@ -1484,7 +1484,7 @@ class Sanitizer {
                }
 
                $block = array_merge( $common, array( 'align' ) );
-               $tablealign = array( 'align', 'char', 'charoff', 'valign' );
+               $tablealign = array( 'align', 'valign' );
                $tablecell = array(
                        'abbr',
                        'axis',
@@ -1504,7 +1504,7 @@ class Sanitizer {
                        # 7.5.4
                        'div'        => $block,
                        'center'     => $common, # deprecated
-                       'span'       => $block, # ??
+                       'span'       => $common,
 
                        # 7.5.5
                        'h1'         => $block,
@@ -1518,7 +1518,7 @@ class Sanitizer {
                        # address
 
                        # 8.2.4
-                       # bdo
+                       'bdo'        => $common,
 
                        # 9.2.1
                        'em'         => $common,
@@ -1534,7 +1534,7 @@ class Sanitizer {
 
                        # 9.2.2
                        'blockquote' => array_merge( $common, array( 'cite' ) ),
-                       # q
+                       'q'          => array_merge( $common, array( 'cite' ) ),
 
                        # 9.2.3
                        'sub'        => $common,
@@ -1544,10 +1544,10 @@ class Sanitizer {
                        'p'          => $block,
 
                        # 9.3.2
-                       'br'         => array( 'id', 'class', 'title', 'style', 'clear' ),
+                       'br'         => array_merge( $common, array( 'clear' ) ),
 
                        # http://www.whatwg.org/html/text-level-semantics.html#the-wbr-element
-                       'wbr'        => array( 'id', 'class', 'title', 'style' ),
+                       'wbr'        => $common,
 
                        # 9.3.4
                        'pre'        => array_merge( $common, array( 'width' ) ),
@@ -1574,16 +1574,16 @@ class Sanitizer {
                                                                ) ),
 
                        # 11.2.2
-                       'caption'    => array_merge( $common, array( 'align' ) ),
+                       'caption'    => $block,
 
                        # 11.2.3
-                       'thead'      => array_merge( $common, $tablealign ),
-                       'tfoot'      => array_merge( $common, $tablealign ),
-                       'tbody'      => array_merge( $common, $tablealign ),
+                       'thead'      => $common,
+                       'tfoot'      => $common,
+                       'tbody'      => $common,
 
                        # 11.2.4
-                       'colgroup'   => array_merge( $common, array( 'span', 'width' ), $tablealign ),
-                       'col'        => array_merge( $common, array( 'span', 'width' ), $tablealign ),
+                       'colgroup'   => array_merge( $common, array( 'span' ) ),
+                       'col'        => array_merge( $common, array( 'span' ) ),
 
                        # 11.2.5
                        'tr'         => array_merge( $common, array( 'bgcolor' ), $tablealign ),
@@ -1618,7 +1618,7 @@ class Sanitizer {
                        # basefont
 
                        # 15.3
-                       'hr'         => array_merge( $common, array( 'noshade', 'size', 'width' ) ),
+                       'hr'         => array_merge( $common, array( 'width' ) ),
 
                        # HTML Ruby annotation text module, simple ruby only.
                        # http://www.whatwg.org/html/text-level-semantics.html#the-ruby-element
diff --git a/includes/ScopedCallback.php b/includes/ScopedCallback.php
deleted file mode 100644 (file)
index ef22e0a..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-<?php
-/**
- * This file deals with RAII style scoped callbacks.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * Class for asserting that a callback happens when an dummy object leaves scope
- *
- * @since 1.21
- */
-class ScopedCallback {
-       /** @var callable */
-       protected $callback;
-
-       /**
-        * @param callable $callback
-        * @throws MWException
-        */
-       public function __construct( $callback ) {
-               if ( !is_callable( $callback ) ) {
-                       throw new MWException( "Provided callback is not valid." );
-               }
-               $this->callback = $callback;
-       }
-
-       /**
-        * Trigger a scoped callback and destroy it.
-        * This is the same is just setting it to null.
-        *
-        * @param ScopedCallback $sc
-        */
-       public static function consume( ScopedCallback &$sc = null ) {
-               $sc = null;
-       }
-
-       /**
-        * Destroy a scoped callback without triggering it
-        *
-        * @param ScopedCallback $sc
-        */
-       public static function cancel( ScopedCallback &$sc = null ) {
-               if ( $sc ) {
-                       $sc->callback = null;
-               }
-               $sc = null;
-       }
-
-       /**
-        * Trigger the callback when this leaves scope
-        */
-       function __destruct() {
-               if ( $this->callback !== null ) {
-                       call_user_func( $this->callback );
-               }
-       }
-}
diff --git a/includes/ScopedPHPTimeout.php b/includes/ScopedPHPTimeout.php
deleted file mode 100644 (file)
index d1493c3..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-<?php
-/**
- * Expansion of the PHP execution time limit feature for a function call.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-/**
- * Class to expand PHP execution time for a function call.
- * Use this when performing changes that should not be interrupted.
- *
- * On construction, set_time_limit() is called and set to $seconds.
- * If the client aborts the connection, PHP will continue to run.
- * When the object goes out of scope, the timer is restarted, with
- * the original time limit minus the time the object existed.
- */
-class ScopedPHPTimeout {
-       protected $startTime; // float; seconds
-       protected $oldTimeout; // integer; seconds
-       protected $oldIgnoreAbort; // boolean
-
-       protected static $stackDepth = 0; // integer
-       protected static $totalCalls = 0; // integer
-       protected static $totalElapsed = 0; // float; seconds
-
-       /* Prevent callers in infinite loops from running forever */
-       const MAX_TOTAL_CALLS = 1000000;
-       const MAX_TOTAL_TIME = 300; // seconds
-
-       /**
-        * @param $seconds integer
-        */
-       public function __construct( $seconds ) {
-               if ( ini_get( 'max_execution_time' ) > 0 ) { // CLI uses 0
-                       if ( self::$totalCalls >= self::MAX_TOTAL_CALLS ) {
-                               trigger_error( "Maximum invocations of " . __CLASS__ . " exceeded." );
-                       } elseif ( self::$totalElapsed >= self::MAX_TOTAL_TIME ) {
-                               trigger_error( "Time limit within invocations of " . __CLASS__ . " exceeded." );
-                       } elseif ( self::$stackDepth > 0 ) { // recursion guard
-                               trigger_error( "Resursive invocation of " . __CLASS__ . " attempted." );
-                       } else {
-                               $this->oldIgnoreAbort = ignore_user_abort( true );
-                               $this->oldTimeout = ini_set( 'max_execution_time', $seconds );
-                               $this->startTime = microtime( true );
-                               ++self::$stackDepth;
-                               ++self::$totalCalls; // proof against < 1us scopes
-                       }
-               }
-       }
-
-       /**
-        * Restore the original timeout.
-        * This does not account for the timer value on __construct().
-        */
-       public function __destruct() {
-               if ( $this->oldTimeout ) {
-                       $elapsed = microtime( true ) - $this->startTime;
-                       // Note: a limit of 0 is treated as "forever"
-                       set_time_limit( max( 1, $this->oldTimeout - (int)$elapsed ) );
-                       // If each scoped timeout is for less than one second, we end up
-                       // restoring the original timeout without any decrease in value.
-                       // Thus web scripts in an infinite loop can run forever unless we
-                       // take some measures to prevent this. Track total time and calls.
-                       self::$totalElapsed += $elapsed;
-                       --self::$stackDepth;
-                       ignore_user_abort( $this->oldIgnoreAbort );
-               }
-       }
-}
index 355993c..0df6d90 100644 (file)
@@ -251,231 +251,6 @@ class SiteStats {
        }
 }
 
-/**
- * Class for handling updates to the site_stats table
- */
-class SiteStatsUpdate implements DeferrableUpdate {
-       protected $views = 0;
-       protected $edits = 0;
-       protected $pages = 0;
-       protected $articles = 0;
-       protected $users = 0;
-       protected $images = 0;
-
-       // @todo deprecate this constructor
-       function __construct( $views, $edits, $good, $pages = 0, $users = 0 ) {
-               $this->views = $views;
-               $this->edits = $edits;
-               $this->articles = $good;
-               $this->pages = $pages;
-               $this->users = $users;
-       }
-
-       /**
-        * @param $deltas Array
-        * @return SiteStatsUpdate
-        */
-       public static function factory( array $deltas ) {
-               $update = new self( 0, 0, 0 );
-
-               $fields = array( 'views', 'edits', 'pages', 'articles', 'users', 'images' );
-               foreach ( $fields as $field ) {
-                       if ( isset( $deltas[$field] ) && $deltas[$field] ) {
-                               $update->$field = $deltas[$field];
-                       }
-               }
-
-               return $update;
-       }
-
-       public function doUpdate() {
-               global $wgSiteStatsAsyncFactor;
-
-               $rate = $wgSiteStatsAsyncFactor; // convenience
-               // If set to do so, only do actual DB updates 1 every $rate times.
-               // The other times, just update "pending delta" values in memcached.
-               if ( $rate && ( $rate < 0 || mt_rand( 0, $rate - 1 ) != 0 ) ) {
-                       $this->doUpdatePendingDeltas();
-               } else {
-                       // Need a separate transaction because this a global lock
-                       wfGetDB( DB_MASTER )->onTransactionIdle( array( $this, 'tryDBUpdateInternal' ) );
-               }
-       }
-
-       /**
-        * Do not call this outside of SiteStatsUpdate
-        *
-        * @return void
-        */
-       public function tryDBUpdateInternal() {
-               global $wgSiteStatsAsyncFactor;
-
-               $dbw = wfGetDB( DB_MASTER );
-               $lockKey = wfMemcKey( 'site_stats' ); // prepend wiki ID
-               if ( $wgSiteStatsAsyncFactor ) {
-                       // Lock the table so we don't have double DB/memcached updates
-                       if ( !$dbw->lockIsFree( $lockKey, __METHOD__ )
-                               || !$dbw->lock( $lockKey, __METHOD__, 1 ) // 1 sec timeout
-                       ) {
-                               $this->doUpdatePendingDeltas();
-                               return;
-                       }
-                       $pd = $this->getPendingDeltas();
-                       // Piggy-back the async deltas onto those of this stats update....
-                       $this->views += ( $pd['ss_total_views']['+'] - $pd['ss_total_views']['-'] );
-                       $this->edits += ( $pd['ss_total_edits']['+'] - $pd['ss_total_edits']['-'] );
-                       $this->articles += ( $pd['ss_good_articles']['+'] - $pd['ss_good_articles']['-'] );
-                       $this->pages += ( $pd['ss_total_pages']['+'] - $pd['ss_total_pages']['-'] );
-                       $this->users += ( $pd['ss_users']['+'] - $pd['ss_users']['-'] );
-                       $this->images += ( $pd['ss_images']['+'] - $pd['ss_images']['-'] );
-               }
-
-               // Build up an SQL query of deltas and apply them...
-               $updates = '';
-               $this->appendUpdate( $updates, 'ss_total_views', $this->views );
-               $this->appendUpdate( $updates, 'ss_total_edits', $this->edits );
-               $this->appendUpdate( $updates, 'ss_good_articles', $this->articles );
-               $this->appendUpdate( $updates, 'ss_total_pages', $this->pages );
-               $this->appendUpdate( $updates, 'ss_users', $this->users );
-               $this->appendUpdate( $updates, 'ss_images', $this->images );
-               if ( $updates != '' ) {
-                       $dbw->update( 'site_stats', array( $updates ), array(), __METHOD__ );
-               }
-
-               if ( $wgSiteStatsAsyncFactor ) {
-                       // Decrement the async deltas now that we applied them
-                       $this->removePendingDeltas( $pd );
-                       // Commit the updates and unlock the table
-                       $dbw->unlock( $lockKey, __METHOD__ );
-               }
-       }
-
-       /**
-        * @param $dbw DatabaseBase
-        * @return bool|mixed
-        */
-       public static function cacheUpdate( $dbw ) {
-               global $wgActiveUserDays;
-               $dbr = wfGetDB( DB_SLAVE, array( 'SpecialStatistics', 'vslow' ) );
-               # Get non-bot users than did some recent action other than making accounts.
-               # If account creation is included, the number gets inflated ~20+ fold on enwiki.
-               $activeUsers = $dbr->selectField(
-                       'recentchanges',
-                       'COUNT( DISTINCT rc_user_text )',
-                       array(
-                               'rc_user != 0',
-                               'rc_bot' => 0,
-                               'rc_log_type != ' . $dbr->addQuotes( 'newusers' ) . ' OR rc_log_type IS NULL',
-                               'rc_timestamp >= ' . $dbr->addQuotes( $dbr->timestamp( wfTimestamp( TS_UNIX ) - $wgActiveUserDays * 24 * 3600 ) ),
-                       ),
-                       __METHOD__
-               );
-               $dbw->update(
-                       'site_stats',
-                       array( 'ss_active_users' => intval( $activeUsers ) ),
-                       array( 'ss_row_id' => 1 ),
-                       __METHOD__
-               );
-               return $activeUsers;
-       }
-
-       protected function doUpdatePendingDeltas() {
-               $this->adjustPending( 'ss_total_views', $this->views );
-               $this->adjustPending( 'ss_total_edits', $this->edits );
-               $this->adjustPending( 'ss_good_articles', $this->articles );
-               $this->adjustPending( 'ss_total_pages', $this->pages );
-               $this->adjustPending( 'ss_users', $this->users );
-               $this->adjustPending( 'ss_images', $this->images );
-       }
-
-       /**
-        * @param $sql string
-        * @param $field string
-        * @param $delta integer
-        */
-       protected function appendUpdate( &$sql, $field, $delta ) {
-               if ( $delta ) {
-                       if ( $sql ) {
-                               $sql .= ',';
-                       }
-                       if ( $delta < 0 ) {
-                               $sql .= "$field=$field-" . abs( $delta );
-                       } else {
-                               $sql .= "$field=$field+" . abs( $delta );
-                       }
-               }
-       }
-
-       /**
-        * @param $type string
-        * @param string $sign ('+' or '-')
-        * @return string
-        */
-       private function getTypeCacheKey( $type, $sign ) {
-               return wfMemcKey( 'sitestatsupdate', 'pendingdelta', $type, $sign );
-       }
-
-       /**
-        * Adjust the pending deltas for a stat type.
-        * Each stat type has two pending counters, one for increments and decrements
-        * @param $type string
-        * @param $delta integer Delta (positive or negative)
-        * @return void
-        */
-       protected function adjustPending( $type, $delta ) {
-               global $wgMemc;
-
-               if ( $delta < 0 ) { // decrement
-                       $key = $this->getTypeCacheKey( $type, '-' );
-               } else { // increment
-                       $key = $this->getTypeCacheKey( $type, '+' );
-               }
-
-               $magnitude = abs( $delta );
-               if ( !$wgMemc->incr( $key, $magnitude ) ) { // not there?
-                       if ( !$wgMemc->add( $key, $magnitude ) ) { // race?
-                               $wgMemc->incr( $key, $magnitude );
-                       }
-               }
-       }
-
-       /**
-        * Get pending delta counters for each stat type
-        * @return Array Positive and negative deltas for each type
-        * @return void
-        */
-       protected function getPendingDeltas() {
-               global $wgMemc;
-
-               $pending = array();
-               foreach ( array( 'ss_total_views', 'ss_total_edits',
-                       'ss_good_articles', 'ss_total_pages', 'ss_users', 'ss_images' ) as $type )
-               {
-                       // Get pending increments and pending decrements
-                       $pending[$type]['+'] = (int)$wgMemc->get( $this->getTypeCacheKey( $type, '+' ) );
-                       $pending[$type]['-'] = (int)$wgMemc->get( $this->getTypeCacheKey( $type, '-' ) );
-               }
-
-               return $pending;
-       }
-
-       /**
-        * Reduce pending delta counters after updates have been applied
-        * @param array $pd Result of getPendingDeltas(), used for DB update
-        * @return void
-        */
-       protected function removePendingDeltas( array $pd ) {
-               global $wgMemc;
-
-               foreach ( $pd as $type => $deltas ) {
-                       foreach ( $deltas as $sign => $magnitude ) {
-                               // Lower the pending counter now that we applied these changes
-                               $wgMemc->decr( $this->getTypeCacheKey( $type, $sign ), $magnitude );
-                       }
-               }
-       }
-}
-
 /**
  * Class designed for counting of stats.
  */
index e5b8872..1e7ce13 100644 (file)
@@ -1998,9 +1998,10 @@ abstract class BaseTemplate extends QuickTemplate {
         * body and html tags.
         */
        function printTrail() { ?>
+<?php echo MWDebug::getDebugHTML( $this->getSkin()->getContext() ); ?>
 <?php $this->html( 'bottomscripts' ); /* JS call to runBodyOnloadHook */ ?>
 <?php $this->html( 'reporttime' ) ?>
-<?php echo MWDebug::getDebugHTML( $this->getSkin()->getContext() );
+<?php
        }
 
 }
diff --git a/includes/SqlDataUpdate.php b/includes/SqlDataUpdate.php
deleted file mode 100644 (file)
index 51188d8..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
-<?php
-/**
- * Base code for update jobs that put some secondary data extracted
- * from article content into the database.
- *
- * 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
- */
-
-/**
- * Abstract base class for update jobs that put some secondary data extracted
- * from article content into the database.
- *
- * @note: subclasses should NOT start or commit transactions in their doUpdate() method,
- *        a transaction will automatically be wrapped around the update. Starting another
- *        one would break the outer transaction bracket. If need be, subclasses can override
- *        the beginTransaction() and commitTransaction() methods.
- */
-abstract class SqlDataUpdate extends DataUpdate {
-
-       protected $mDb;            //!< Database connection reference
-       protected $mOptions;       //!< SELECT options to be used (array)
-
-       private   $mHasTransaction; //!< bool whether a transaction is open on this object (internal use only!)
-       protected $mUseTransaction; //!< bool whether this update should be wrapped in a transaction
-
-       /**
-        * Constructor
-        *
-        * @param bool $withTransaction whether this update should be wrapped in a transaction (default: true).
-        *             A transaction is only started if no transaction is already in progress,
-        *             see beginTransaction() for details.
-        **/
-       public function __construct( $withTransaction = true ) {
-               global $wgAntiLockFlags;
-
-               parent::__construct();
-
-               if ( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) {
-                       $this->mOptions = array();
-               } else {
-                       $this->mOptions = array( 'FOR UPDATE' );
-               }
-
-               // @todo get connection only when it's needed? make sure that doesn't break anything, especially transactions!
-               $this->mDb = wfGetDB( DB_MASTER );
-
-               $this->mWithTransaction = $withTransaction;
-               $this->mHasTransaction = false;
-       }
-
-       /**
-        * Begin a database transaction, if $withTransaction was given as true in the constructor for this SqlDataUpdate.
-        *
-        * Because nested transactions are not supported by the Database class, this implementation
-        * checks Database::trxLevel() and only opens a transaction if none is already active.
-        */
-       public function beginTransaction() {
-               if ( !$this->mWithTransaction ) {
-                       return;
-               }
-
-               // NOTE: nested transactions are not supported, only start a transaction if none is open
-               if ( $this->mDb->trxLevel() === 0 ) {
-                       $this->mDb->begin( get_class( $this ) . '::beginTransaction' );
-                       $this->mHasTransaction = true;
-               }
-       }
-
-       /**
-        * Commit the database transaction started via beginTransaction (if any).
-        */
-       public function commitTransaction() {
-               if ( $this->mHasTransaction ) {
-                       $this->mDb->commit( get_class( $this ) . '::commitTransaction' );
-                       $this->mHasTransaction = false;
-               }
-       }
-
-       /**
-        * Abort the database transaction started via beginTransaction (if any).
-        */
-       public function abortTransaction() {
-               if ( $this->mHasTransaction ) { //XXX: actually... maybe always?
-                       $this->mDb->rollback( get_class( $this ) . '::abortTransaction' );
-                       $this->mHasTransaction = false;
-               }
-       }
-
-       /**
-        * Invalidate the cache of a list of pages from a single namespace.
-        * This is intended for use by subclasses.
-        *
-        * @param $namespace Integer
-        * @param $dbkeys Array
-        */
-       protected function invalidatePages( $namespace, array $dbkeys ) {
-               if ( $dbkeys === array() ) {
-                       return;
-               }
-
-               /**
-                * Determine which pages need to be updated
-                * This is necessary to prevent the job queue from smashing the DB with
-                * large numbers of concurrent invalidations of the same page
-                */
-               $now = $this->mDb->timestamp();
-               $ids = array();
-               $res = $this->mDb->select( 'page', array( 'page_id' ),
-                       array(
-                               'page_namespace' => $namespace,
-                               'page_title' => $dbkeys,
-                               'page_touched < ' . $this->mDb->addQuotes( $now )
-                       ), __METHOD__
-               );
-
-               foreach ( $res as $row ) {
-                       $ids[] = $row->page_id;
-               }
-
-               if ( $ids === array() ) {
-                       return;
-               }
-
-               /**
-                * Do the update
-                * We still need the page_touched condition, in case the row has changed since
-                * the non-locking select above.
-                */
-               $this->mDb->update( 'page', array( 'page_touched' => $now ),
-                       array(
-                               'page_id' => $ids,
-                               'page_touched < ' . $this->mDb->addQuotes( $now )
-                       ), __METHOD__
-               );
-       }
-
-}
index 928f8eb..836c24a 100644 (file)
  * so that a lack of error-handling will be explicit.
  */
 class Status {
-       var $ok = true;
-       var $value;
+       public $ok = true;
+       public $value;
 
        /** Counters for batch operations */
-       public $successCount = 0, $failCount = 0;
+       public $successCount = 0;
+       public $failCount = 0;
+
        /** Array to indicate which items of the batch operations were successful */
        public $success = array();
 
-       /*semi-private*/ var $errors = array();
-       /*semi-private*/ var $cleanCallback = false;
+       public $errors = array();
+       public $cleanCallback = false;
 
        /**
         * Factory function for fatal errors
@@ -347,7 +349,6 @@ class Status {
        /**
         * Returns a list of status messages of the given type
         * @param $type String
-        *
         * @return Array
         */
        protected function getStatusArray( $type ) {
@@ -355,7 +356,10 @@ class Status {
                foreach ( $this->errors as $error ) {
                        if ( $error['type'] === $type ) {
                                if ( $error['message'] instanceof Message ) {
-                                       $result[] = array_merge( array( $error['message']->getKey() ), $error['message']->getParams() );
+                                       $result[] = array_merge(
+                                               array( $error['message']->getKey() ),
+                                               $error['message']->getParams()
+                                       );
                                } elseif ( $error['params'] ) {
                                        $result[] = array_merge( array( $error['message'] ), $error['params'] );
                                } else {
@@ -363,6 +367,7 @@ class Status {
                                }
                        }
                }
+
                return $result;
        }
 
diff --git a/includes/StringUtils.php b/includes/StringUtils.php
deleted file mode 100644 (file)
index c1545e6..0000000
+++ /dev/null
@@ -1,606 +0,0 @@
-<?php
-/**
- * Methods to play with strings.
- *
- * 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
- */
-
-/**
- * A collection of static methods to play with strings.
- */
-class StringUtils {
-
-       /**
-        * Test whether a string is valid UTF-8.
-        *
-        * The function check for invalid byte sequences, overlong encoding but
-        * not for different normalisations.
-        *
-        * This relies internally on the mbstring function mb_check_encoding()
-        * hardcoded to check against UTF-8. Whenever the function is not available
-        * we fallback to a pure PHP implementation. Setting $disableMbstring to
-        * true will skip the use of mb_check_encoding, this is mostly intended for
-        * unit testing our internal implementation.
-        *
-        * @since 1.21
-        * @note In MediaWiki 1.21, this function did not provide proper UTF-8 validation.
-        * In particular, the pure PHP code path did not in fact check for overlong forms.
-        * Beware of this when backporting code to that version of MediaWiki.
-        *
-        * @param string $value String to check
-        * @param boolean $disableMbstring Whether to use the pure PHP
-        * implementation instead of trying mb_check_encoding. Intended for unit
-        * testing. Default: false
-        *
-        * @return boolean Whether the given $value is a valid UTF-8 encoded string
-        */
-       static function isUtf8( $value, $disableMbstring = false ) {
-               $value = (string)$value;
-
-               // If the mbstring extension is loaded, use it. However, before PHP 5.4, values above
-               // U+10FFFF are incorrectly allowed, so we have to check for them separately.
-               if ( !$disableMbstring && function_exists( 'mb_check_encoding' ) ) {
-                       static $newPHP;
-                       if ( $newPHP === null ) {
-                               $newPHP = !mb_check_encoding( "\xf4\x90\x80\x80", 'UTF-8' );
-                       }
-
-                       return mb_check_encoding( $value, 'UTF-8' ) &&
-                               ( $newPHP || preg_match( "/\xf4[\x90-\xbf]|[\xf5-\xff]/S", $value ) === 0 );
-               }
-
-               if ( preg_match( "/[\x80-\xff]/S", $value ) === 0 ) {
-                       // String contains only ASCII characters, has to be valid
-                       return true;
-               }
-
-               // PCRE implements repetition using recursion; to avoid a stack overflow (and segfault)
-               // for large input, we check for invalid sequences (<= 5 bytes) rather than valid
-               // sequences, which can be as long as the input string is. Multiple short regexes are
-               // used rather than a single long regex for performance.
-               static $regexes;
-               if ( $regexes === null ) {
-                       $cont = "[\x80-\xbf]";
-                       $after = "(?!$cont)"; // "(?:[^\x80-\xbf]|$)" would work here
-                       $regexes = array(
-                               // Continuation byte at the start
-                               "/^$cont/",
-
-                               // ASCII byte followed by a continuation byte
-                               "/[\\x00-\x7f]$cont/S",
-
-                               // Illegal byte
-                               "/[\xc0\xc1\xf5-\xff]/S",
-
-                               // Invalid 2-byte sequence, or valid one then an extra continuation byte
-                               "/[\xc2-\xdf](?!$cont$after)/S",
-
-                               // Invalid 3-byte sequence, or valid one then an extra continuation byte
-                               "/\xe0(?![\xa0-\xbf]$cont$after)/",
-                               "/[\xe1-\xec\xee\xef](?!$cont{2}$after)/S",
-                               "/\xed(?![\x80-\x9f]$cont$after)/",
-
-                               // Invalid 4-byte sequence, or valid one then an extra continuation byte
-                               "/\xf0(?![\x90-\xbf]$cont{2}$after)/",
-                               "/[\xf1-\xf3](?!$cont{3}$after)/S",
-                               "/\xf4(?![\x80-\x8f]$cont{2}$after)/",
-                       );
-               }
-
-               foreach ( $regexes as $regex ) {
-                       if ( preg_match( $regex, $value ) !== 0 ) {
-                               return false;
-                       }
-               }
-               return true;
-       }
-
-       /**
-        * Perform an operation equivalent to
-        *
-        *     preg_replace( "!$startDelim(.*?)$endDelim!", $replace, $subject );
-        *
-        * except that it's worst-case O(N) instead of O(N^2)
-        *
-        * Compared to delimiterReplace(), this implementation is fast but memory-
-        * hungry and inflexible. The memory requirements are such that I don't
-        * recommend using it on anything but guaranteed small chunks of text.
-        *
-        * @param $startDelim
-        * @param $endDelim
-        * @param $replace
-        * @param $subject
-        *
-        * @return string
-        */
-       static function hungryDelimiterReplace( $startDelim, $endDelim, $replace, $subject ) {
-               $segments = explode( $startDelim, $subject );
-               $output = array_shift( $segments );
-               foreach ( $segments as $s ) {
-                       $endDelimPos = strpos( $s, $endDelim );
-                       if ( $endDelimPos === false ) {
-                               $output .= $startDelim . $s;
-                       } else {
-                               $output .= $replace . substr( $s, $endDelimPos + strlen( $endDelim ) );
-                       }
-               }
-               return $output;
-       }
-
-       /**
-        * Perform an operation equivalent to
-        *
-        *   preg_replace_callback( "!$startDelim(.*)$endDelim!s$flags", $callback, $subject )
-        *
-        * This implementation is slower than hungryDelimiterReplace but uses far less
-        * memory. The delimiters are literal strings, not regular expressions.
-        *
-        * If the start delimiter ends with an initial substring of the end delimiter,
-        * e.g. in the case of C-style comments, the behavior differs from the model
-        * regex. In this implementation, the end must share no characters with the
-        * start, so e.g. /*\/ is not considered to be both the start and end of a
-        * comment. /*\/xy/*\/ is considered to be a single comment with contents /xy/.
-        *
-        * @param string $startDelim start delimiter
-        * @param string $endDelim end delimiter
-        * @param $callback Callback: function to call on each match
-        * @param $subject String
-        * @param string $flags regular expression flags
-        * @throws MWException
-        * @return string
-        */
-       static function delimiterReplaceCallback( $startDelim, $endDelim, $callback, $subject, $flags = '' ) {
-               $inputPos = 0;
-               $outputPos = 0;
-               $output = '';
-               $foundStart = false;
-               $encStart = preg_quote( $startDelim, '!' );
-               $encEnd = preg_quote( $endDelim, '!' );
-               $strcmp = strpos( $flags, 'i' ) === false ? 'strcmp' : 'strcasecmp';
-               $endLength = strlen( $endDelim );
-               $m = array();
-
-               while ( $inputPos < strlen( $subject ) &&
-                       preg_match( "!($encStart)|($encEnd)!S$flags", $subject, $m, PREG_OFFSET_CAPTURE, $inputPos ) )
-               {
-                       $tokenOffset = $m[0][1];
-                       if ( $m[1][0] != '' ) {
-                               if ( $foundStart &&
-                                       $strcmp( $endDelim, substr( $subject, $tokenOffset, $endLength ) ) == 0 )
-                               {
-                                       # An end match is present at the same location
-                                       $tokenType = 'end';
-                                       $tokenLength = $endLength;
-                               } else {
-                                       $tokenType = 'start';
-                                       $tokenLength = strlen( $m[0][0] );
-                               }
-                       } elseif ( $m[2][0] != '' ) {
-                               $tokenType = 'end';
-                               $tokenLength = strlen( $m[0][0] );
-                       } else {
-                               throw new MWException( 'Invalid delimiter given to ' . __METHOD__ );
-                       }
-
-                       if ( $tokenType == 'start' ) {
-                               # Only move the start position if we haven't already found a start
-                               # This means that START START END matches outer pair
-                               if ( !$foundStart ) {
-                                       # Found start
-                                       $inputPos = $tokenOffset + $tokenLength;
-                                       # Write out the non-matching section
-                                       $output .= substr( $subject, $outputPos, $tokenOffset - $outputPos );
-                                       $outputPos = $tokenOffset;
-                                       $contentPos = $inputPos;
-                                       $foundStart = true;
-                               } else {
-                                       # Move the input position past the *first character* of START,
-                                       # to protect against missing END when it overlaps with START
-                                       $inputPos = $tokenOffset + 1;
-                               }
-                       } elseif ( $tokenType == 'end' ) {
-                               if ( $foundStart ) {
-                                       # Found match
-                                       $output .= call_user_func( $callback, array(
-                                               substr( $subject, $outputPos, $tokenOffset + $tokenLength - $outputPos ),
-                                               substr( $subject, $contentPos, $tokenOffset - $contentPos )
-                                       ));
-                                       $foundStart = false;
-                               } else {
-                                       # Non-matching end, write it out
-                                       $output .= substr( $subject, $inputPos, $tokenOffset + $tokenLength - $outputPos );
-                               }
-                               $inputPos = $outputPos = $tokenOffset + $tokenLength;
-                       } else {
-                               throw new MWException( 'Invalid delimiter given to ' . __METHOD__ );
-                       }
-               }
-               if ( $outputPos < strlen( $subject ) ) {
-                       $output .= substr( $subject, $outputPos );
-               }
-               return $output;
-       }
-
-       /**
-        * Perform an operation equivalent to
-        *
-        *   preg_replace( "!$startDelim(.*)$endDelim!$flags", $replace, $subject )
-        *
-        * @param string $startDelim start delimiter regular expression
-        * @param string $endDelim end delimiter regular expression
-        * @param string $replace replacement string. May contain $1, which will be
-        *                 replaced by the text between the delimiters
-        * @param string $subject to search
-        * @param string $flags regular expression flags
-        * @return String: The string with the matches replaced
-        */
-       static function delimiterReplace( $startDelim, $endDelim, $replace, $subject, $flags = '' ) {
-               $replacer = new RegexlikeReplacer( $replace );
-               return self::delimiterReplaceCallback( $startDelim, $endDelim,
-                       $replacer->cb(), $subject, $flags );
-       }
-
-       /**
-        * More or less "markup-safe" explode()
-        * Ignores any instances of the separator inside <...>
-        * @param string $separator
-        * @param string $text
-        * @return array
-        */
-       static function explodeMarkup( $separator, $text ) {
-               $placeholder = "\x00";
-
-               // Remove placeholder instances
-               $text = str_replace( $placeholder, '', $text );
-
-               // Replace instances of the separator inside HTML-like tags with the placeholder
-               $replacer = new DoubleReplacer( $separator, $placeholder );
-               $cleaned = StringUtils::delimiterReplaceCallback( '<', '>', $replacer->cb(), $text );
-
-               // Explode, then put the replaced separators back in
-               $items = explode( $separator, $cleaned );
-               foreach ( $items as $i => $str ) {
-                       $items[$i] = str_replace( $placeholder, $separator, $str );
-               }
-
-               return $items;
-       }
-
-       /**
-        * Escape a string to make it suitable for inclusion in a preg_replace()
-        * replacement parameter.
-        *
-        * @param string $string
-        * @return string
-        */
-       static function escapeRegexReplacement( $string ) {
-               $string = str_replace( '\\', '\\\\', $string );
-               $string = str_replace( '$', '\\$', $string );
-               return $string;
-       }
-
-       /**
-        * Workalike for explode() with limited memory usage.
-        * Returns an Iterator
-        * @param string $separator
-        * @param string $subject
-        * @return ArrayIterator|ExplodeIterator
-        */
-       static function explode( $separator, $subject ) {
-               if ( substr_count( $subject, $separator ) > 1000 ) {
-                       return new ExplodeIterator( $separator, $subject );
-               } else {
-                       return new ArrayIterator( explode( $separator, $subject ) );
-               }
-       }
-}
-
-/**
- * Base class for "replacers", objects used in preg_replace_callback() and
- * StringUtils::delimiterReplaceCallback()
- */
-class Replacer {
-
-       /**
-        * @return array
-        */
-       function cb() {
-               return array( &$this, 'replace' );
-       }
-}
-
-/**
- * Class to replace regex matches with a string similar to that used in preg_replace()
- */
-class RegexlikeReplacer extends Replacer {
-       var $r;
-
-       /**
-        * @param string $r
-        */
-       function __construct( $r ) {
-               $this->r = $r;
-       }
-
-       /**
-        * @param array $matches
-        * @return string
-        */
-       function replace( $matches ) {
-               $pairs = array();
-               foreach ( $matches as $i => $match ) {
-                       $pairs["\$$i"] = $match;
-               }
-               return strtr( $this->r, $pairs );
-       }
-
-}
-
-/**
- * Class to perform secondary replacement within each replacement string
- */
-class DoubleReplacer extends Replacer {
-
-       /**
-        * @param $from
-        * @param $to
-        * @param int $index
-        */
-       function __construct( $from, $to, $index = 0 ) {
-               $this->from = $from;
-               $this->to = $to;
-               $this->index = $index;
-       }
-
-       /**
-        * @param array $matches
-        * @return mixed
-        */
-       function replace( $matches ) {
-               return str_replace( $this->from, $this->to, $matches[$this->index] );
-       }
-}
-
-/**
- * Class to perform replacement based on a simple hashtable lookup
- */
-class HashtableReplacer extends Replacer {
-       var $table, $index;
-
-       /**
-        * @param $table
-        * @param int $index
-        */
-       function __construct( $table, $index = 0 ) {
-               $this->table = $table;
-               $this->index = $index;
-       }
-
-       /**
-        * @param array $matches
-        * @return mixed
-        */
-       function replace( $matches ) {
-               return $this->table[$matches[$this->index]];
-       }
-}
-
-/**
- * Replacement array for FSS with fallback to strtr()
- * Supports lazy initialisation of FSS resource
- */
-class ReplacementArray {
-       /*mostly private*/ var $data = false;
-       /*mostly private*/ var $fss = false;
-
-       /**
-        * Create an object with the specified replacement array
-        * The array should have the same form as the replacement array for strtr()
-        * @param array $data
-        */
-       function __construct( $data = array() ) {
-               $this->data = $data;
-       }
-
-       /**
-        * @return array
-        */
-       function __sleep() {
-               return array( 'data' );
-       }
-
-       function __wakeup() {
-               $this->fss = false;
-       }
-
-       /**
-        * Set the whole replacement array at once
-        * @param array $data
-        */
-       function setArray( $data ) {
-               $this->data = $data;
-               $this->fss = false;
-       }
-
-       /**
-        * @return array|bool
-        */
-       function getArray() {
-               return $this->data;
-       }
-
-       /**
-        * Set an element of the replacement array
-        * @param string $from
-        * @param string $to
-        */
-       function setPair( $from, $to ) {
-               $this->data[$from] = $to;
-               $this->fss = false;
-       }
-
-       /**
-        * @param array $data
-        */
-       function mergeArray( $data ) {
-               $this->data = array_merge( $this->data, $data );
-               $this->fss = false;
-       }
-
-       /**
-        * @param ReplacementArray $other
-        */
-       function merge( $other ) {
-               $this->data = array_merge( $this->data, $other->data );
-               $this->fss = false;
-       }
-
-       /**
-        * @param string $from
-        */
-       function removePair( $from ) {
-               unset( $this->data[$from] );
-               $this->fss = false;
-       }
-
-       /**
-        * @param array $data
-        */
-       function removeArray( $data ) {
-               foreach ( $data as $from => $to ) {
-                       $this->removePair( $from );
-               }
-               $this->fss = false;
-       }
-
-       /**
-        * @param string $subject
-        * @return string
-        */
-       function replace( $subject ) {
-               if ( function_exists( 'fss_prep_replace' ) ) {
-                       wfProfileIn( __METHOD__ . '-fss' );
-                       if ( $this->fss === false ) {
-                               $this->fss = fss_prep_replace( $this->data );
-                       }
-                       $result = fss_exec_replace( $this->fss, $subject );
-                       wfProfileOut( __METHOD__ . '-fss' );
-               } else {
-                       wfProfileIn( __METHOD__ . '-strtr' );
-                       $result = strtr( $subject, $this->data );
-                       wfProfileOut( __METHOD__ . '-strtr' );
-               }
-               return $result;
-       }
-}
-
-/**
- * An iterator which works exactly like:
- *
- * foreach ( explode( $delim, $s ) as $element ) {
- *    ...
- * }
- *
- * Except it doesn't use 193 byte per element
- */
-class ExplodeIterator implements Iterator {
-       // The subject string
-       var $subject, $subjectLength;
-
-       // The delimiter
-       var $delim, $delimLength;
-
-       // The position of the start of the line
-       var $curPos;
-
-       // The position after the end of the next delimiter
-       var $endPos;
-
-       // The current token
-       var $current;
-
-       /**
-        * Construct a DelimIterator
-        * @param string $delim
-        * @param string $subject
-        */
-       function __construct( $delim, $subject ) {
-               $this->subject = $subject;
-               $this->delim = $delim;
-
-               // Micro-optimisation (theoretical)
-               $this->subjectLength = strlen( $subject );
-               $this->delimLength = strlen( $delim );
-
-               $this->rewind();
-       }
-
-       function rewind() {
-               $this->curPos = 0;
-               $this->endPos = strpos( $this->subject, $this->delim );
-               $this->refreshCurrent();
-       }
-
-       function refreshCurrent() {
-               if ( $this->curPos === false ) {
-                       $this->current = false;
-               } elseif ( $this->curPos >= $this->subjectLength ) {
-                       $this->current = '';
-               } elseif ( $this->endPos === false ) {
-                       $this->current = substr( $this->subject, $this->curPos );
-               } else {
-                       $this->current = substr( $this->subject, $this->curPos, $this->endPos - $this->curPos );
-               }
-       }
-
-       function current() {
-               return $this->current;
-       }
-
-       /**
-        * @return int|bool Current position or boolean false if invalid
-        */
-       function key() {
-               return $this->curPos;
-       }
-
-       /**
-        * @return string
-        */
-       function next() {
-               if ( $this->endPos === false ) {
-                       $this->curPos = false;
-               } else {
-                       $this->curPos = $this->endPos + $this->delimLength;
-                       if ( $this->curPos >= $this->subjectLength ) {
-                               $this->endPos = false;
-                       } else {
-                               $this->endPos = strpos( $this->subject, $this->delim, $this->curPos );
-                       }
-               }
-               $this->refreshCurrent();
-               return $this->current;
-       }
-
-       /**
-        * @return bool
-        */
-       function valid() {
-               return $this->curPos !== false;
-       }
-}
diff --git a/includes/UIDGenerator.php b/includes/UIDGenerator.php
deleted file mode 100644 (file)
index 963e51a..0000000
+++ /dev/null
@@ -1,337 +0,0 @@
-<?php
-/**
- * This file deals with UID generation.
- *
- * 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 Aaron Schulz
- */
-
-/**
- * Class for getting statistically unique IDs
- *
- * @since 1.21
- */
-class UIDGenerator {
-       /** @var UIDGenerator */
-       protected static $instance = null;
-
-       protected $nodeId32; // string; node ID in binary (32 bits)
-       protected $nodeId48; // string; node ID in binary (48 bits)
-
-       protected $lockFile88; // string; local file path
-       protected $lockFile128; // string; local file path
-
-       /** @var Array */
-       protected $fileHandles = array(); // cache file handles
-
-       const QUICK_RAND = 1; // get randomness from fast and insecure sources
-
-       protected function __construct() {
-               $idFile = wfTempDir() . '/mw-' . __CLASS__ . '-UID-nodeid';
-               $nodeId = is_file( $idFile ) ? file_get_contents( $idFile ) : '';
-               // Try to get some ID that uniquely identifies this machine (RFC 4122)...
-               if ( !preg_match( '/^[0-9a-f]{12}$/i', $nodeId ) ) {
-                       wfSuppressWarnings();
-                       if ( wfIsWindows() ) {
-                               // http://technet.microsoft.com/en-us/library/bb490913.aspx
-                               $csv = trim( wfShellExec( 'getmac /NH /FO CSV' ) );
-                               $line = substr( $csv, 0, strcspn( $csv, "\n" ) );
-                               $info = str_getcsv( $line );
-                               $nodeId = isset( $info[0] ) ? str_replace( '-', '', $info[0] ) : '';
-                       } elseif ( is_executable( '/sbin/ifconfig' ) ) { // Linux/BSD/Solaris/OS X
-                               // See http://linux.die.net/man/8/ifconfig
-                               $m = array();
-                               preg_match( '/\s([0-9a-f]{2}(:[0-9a-f]{2}){5})\s/',
-                                       wfShellExec( '/sbin/ifconfig -a' ), $m );
-                               $nodeId = isset( $m[1] ) ? str_replace( ':', '', $m[1] ) : '';
-                       }
-                       wfRestoreWarnings();
-                       if ( !preg_match( '/^[0-9a-f]{12}$/i', $nodeId ) ) {
-                               $nodeId = MWCryptRand::generateHex( 12, true );
-                               $nodeId[1] = dechex( hexdec( $nodeId[1] ) | 0x1 ); // set multicast bit
-                       }
-                       file_put_contents( $idFile, $nodeId ); // cache
-               }
-               $this->nodeId32 = wfBaseConvert( substr( sha1( $nodeId ), 0, 8 ), 16, 2, 32 );
-               $this->nodeId48 = wfBaseConvert( $nodeId, 16, 2, 48 );
-               // If different processes run as different users, they may have different temp dirs.
-               // This is dealt with by initializing the clock sequence number and counters randomly.
-               $this->lockFile88 = wfTempDir() . '/mw-' . __CLASS__ . '-UID-88';
-               $this->lockFile128 = wfTempDir() . '/mw-' . __CLASS__ . '-UID-128';
-       }
-
-       /**
-        * @return UIDGenerator
-        */
-       protected static function singleton() {
-               if ( self::$instance === null ) {
-                       self::$instance = new self();
-               }
-               return self::$instance;
-       }
-
-       /**
-        * Get a statistically unique 88-bit unsigned integer ID string.
-        * The bits of the UID are prefixed with the time (down to the millisecond).
-        *
-        * These IDs are suitable as values for the shard key of distributed data.
-        * If a column uses these as values, it should be declared UNIQUE to handle collisions.
-        * New rows almost always have higher UIDs, which makes B-TREE updates on INSERT fast.
-        * They can also be stored "DECIMAL(27) UNSIGNED" or BINARY(11) in MySQL.
-        *
-        * UID generation is serialized on each server (as the node ID is for the whole machine).
-        *
-        * @param $base integer Specifies a base other than 10
-        * @return string Number
-        * @throws MWException
-        */
-       public static function newTimestampedUID88( $base = 10 ) {
-               if ( !is_integer( $base ) || $base > 36 || $base < 2 ) {
-                       throw new MWException( "Base must an integer be between 2 and 36" );
-               }
-               $gen = self::singleton();
-               $time = $gen->getTimestampAndDelay( 'lockFile88', 1, 1024 );
-               return wfBaseConvert( $gen->getTimestampedID88( $time ), 2, $base );
-       }
-
-       /**
-        * @param array $time (UIDGenerator::millitime(), clock sequence)
-        * @return string 88 bits
-        */
-       protected function getTimestampedID88( array $info ) {
-               list( $time, $counter ) = $info;
-               // Take the 46 MSBs of "milliseconds since epoch"
-               $id_bin = $this->millisecondsSinceEpochBinary( $time );
-               // Add a 10 bit counter resulting in 56 bits total
-               $id_bin .= str_pad( decbin( $counter ), 10, '0', STR_PAD_LEFT );
-               // Add the 32 bit node ID resulting in 88 bits total
-               $id_bin .= $this->nodeId32;
-               // Convert to a 1-27 digit integer string
-               if ( strlen( $id_bin ) !== 88 ) {
-                       throw new MWException( "Detected overflow for millisecond timestamp." );
-               }
-               return $id_bin;
-       }
-
-       /**
-        * Get a statistically unique 128-bit unsigned integer ID string.
-        * The bits of the UID are prefixed with the time (down to the millisecond).
-        *
-        * These IDs are suitable as globally unique IDs, without any enforced uniqueness.
-        * New rows almost always have higher UIDs, which makes B-TREE updates on INSERT fast.
-        * They can also be stored as "DECIMAL(39) UNSIGNED" or BINARY(16) in MySQL.
-        *
-        * UID generation is serialized on each server (as the node ID is for the whole machine).
-        *
-        * @param $base integer Specifies a base other than 10
-        * @return string Number
-        * @throws MWException
-        */
-       public static function newTimestampedUID128( $base = 10 ) {
-               if ( !is_integer( $base ) || $base > 36 || $base < 2 ) {
-                       throw new MWException( "Base must be an integer between 2 and 36" );
-               }
-               $gen = self::singleton();
-               $time = $gen->getTimestampAndDelay( 'lockFile128', 16384, 1048576 );
-               return wfBaseConvert( $gen->getTimestampedID128( $time ), 2, $base );
-       }
-
-       /**
-        * @param array $info (UIDGenerator::millitime(), counter, clock sequence)
-        * @return string 128 bits
-        */
-       protected function getTimestampedID128( array $info ) {
-               list( $time, $counter, $clkSeq ) = $info;
-               // Take the 46 MSBs of "milliseconds since epoch"
-               $id_bin = $this->millisecondsSinceEpochBinary( $time );
-               // Add a 20 bit counter resulting in 66 bits total
-               $id_bin .= str_pad( decbin( $counter ), 20, '0', STR_PAD_LEFT );
-               // Add a 14 bit clock sequence number resulting in 80 bits total
-               $id_bin .= str_pad( decbin( $clkSeq ), 14, '0', STR_PAD_LEFT );
-               // Add the 48 bit node ID resulting in 128 bits total
-               $id_bin .= $this->nodeId48;
-               // Convert to a 1-39 digit integer string
-               if ( strlen( $id_bin ) !== 128 ) {
-                       throw new MWException( "Detected overflow for millisecond timestamp." );
-               }
-               return $id_bin;
-       }
-
-       /**
-        * Return an RFC4122 compliant v4 UUID
-        *
-        * @param $flags integer Bitfield (supports UIDGenerator::QUICK_RAND)
-        * @return string
-        * @throws MWException
-        */
-       public static function newUUIDv4( $flags = 0 ) {
-               $hex = ( $flags & self::QUICK_RAND )
-                       ? wfRandomString( 31 )
-                       : MWCryptRand::generateHex( 31 );
-
-               return sprintf( '%s-%s-%s-%s-%s',
-                       // "time_low" (32 bits)
-                       substr( $hex, 0, 8 ),
-                       // "time_mid" (16 bits)
-                       substr( $hex, 8, 4 ),
-                       // "time_hi_and_version" (16 bits)
-                       '4' . substr( $hex, 12, 3 ),
-                       // "clk_seq_hi_res (8 bits, variant is binary 10x) and "clk_seq_low" (8 bits)
-                       dechex( 0x8 | ( hexdec( $hex[15] ) & 0x3 ) ) . $hex[16] . substr( $hex, 17, 2 ),
-                       // "node" (48 bits)
-                       substr( $hex, 19, 12 )
-               );
-       }
-
-       /**
-        * Return an RFC4122 compliant v4 UUID
-        *
-        * @param $flags integer Bitfield (supports UIDGenerator::QUICK_RAND)
-        * @return string 32 hex characters with no hyphens
-        * @throws MWException
-        */
-       public static function newRawUUIDv4( $flags = 0 ) {
-               return str_replace( '-', '', self::newUUIDv4( $flags ) );
-       }
-
-       /**
-        * Get a (time,counter,clock sequence) where (time,counter) is higher
-        * than any previous (time,counter) value for the given clock sequence.
-        * This is useful for making UIDs sequential on a per-node bases.
-        *
-        * @param string $lockFile Name of a local lock file
-        * @param $clockSeqSize integer The number of possible clock sequence values
-        * @param $counterSize integer The number of possible counter values
-        * @return Array (result of UIDGenerator::millitime(), counter, clock sequence)
-        * @throws MWException
-        */
-       protected function getTimestampAndDelay( $lockFile, $clockSeqSize, $counterSize ) {
-               // Get the UID lock file handle
-               if ( isset( $this->fileHandles[$lockFile] ) ) {
-                       $handle = $this->fileHandles[$lockFile];
-               } else {
-                       $handle = fopen( $this->$lockFile, 'cb+' );
-                       $this->fileHandles[$lockFile] = $handle ?: null; // cache
-               }
-               // Acquire the UID lock file
-               if ( $handle === false ) {
-                       throw new MWException( "Could not open '{$this->$lockFile}'." );
-               } elseif ( !flock( $handle, LOCK_EX ) ) {
-                       throw new MWException( "Could not acquire '{$this->$lockFile}'." );
-               }
-               // Get the current timestamp, clock sequence number, last time, and counter
-               rewind( $handle );
-               $data = explode( ' ', fgets( $handle ) ); // "<clk seq> <sec> <msec> <counter> <offset>"
-               $clockChanged = false; // clock set back significantly?
-               if ( count( $data ) == 5 ) { // last UID info already initialized
-                       $clkSeq = (int)$data[0] % $clockSeqSize;
-                       $prevTime = array( (int)$data[1], (int)$data[2] );
-                       $offset = (int)$data[4] % $counterSize; // random counter offset
-                       $counter = 0; // counter for UIDs with the same timestamp
-                       // Delay until the clock reaches the time of the last ID.
-                       // This detects any microtime() drift among processes.
-                       $time = $this->timeWaitUntil( $prevTime );
-                       if ( !$time ) { // too long to delay?
-                               $clockChanged = true; // bump clock sequence number
-                               $time = self::millitime();
-                       } elseif ( $time == $prevTime ) {
-                               // Bump the counter if there are timestamp collisions
-                               $counter = (int)$data[3] % $counterSize;
-                               if ( ++$counter >= $counterSize ) { // sanity (starts at 0)
-                                       flock( $handle, LOCK_UN ); // abort
-                                       throw new MWException( "Counter overflow for timestamp value." );
-                               }
-                       }
-               } else { // last UID info not initialized
-                       $clkSeq = mt_rand( 0, $clockSeqSize - 1 );
-                       $counter = 0;
-                       $offset = mt_rand( 0, $counterSize - 1 );
-                       $time = self::millitime();
-               }
-               // microtime() and gettimeofday() can drift from time() at least on Windows.
-               // The drift is immediate for processes running while the system clock changes.
-               // time() does not have this problem. See https://bugs.php.net/bug.php?id=42659.
-               if ( abs( time() - $time[0] ) >= 2 ) {
-                       // We don't want processes using too high or low timestamps to avoid duplicate
-                       // UIDs and clock sequence number churn. This process should just be restarted.
-                       flock( $handle, LOCK_UN ); // abort
-                       throw new MWException( "Process clock is outdated or drifted." );
-               }
-               // If microtime() is synced and a clock change was detected, then the clock went back
-               if ( $clockChanged ) {
-                       // Bump the clock sequence number and also randomize the counter offset,
-                       // which is useful for UIDs that do not include the clock sequence number.
-                       $clkSeq = ( $clkSeq + 1 ) % $clockSeqSize;
-                       $offset = mt_rand( 0, $counterSize - 1 );
-                       trigger_error( "Clock was set back; sequence number incremented." );
-               }
-               // Update the (clock sequence number, timestamp, counter)
-               ftruncate( $handle, 0 );
-               rewind( $handle );
-               fwrite( $handle, "{$clkSeq} {$time[0]} {$time[1]} {$counter} {$offset}" );
-               fflush( $handle );
-               // Release the UID lock file
-               flock( $handle, LOCK_UN );
-
-               return array( $time, ( $counter + $offset ) % $counterSize, $clkSeq );
-       }
-
-       /**
-        * Wait till the current timestamp reaches $time and return the current
-        * timestamp. This returns false if it would have to wait more than 10ms.
-        *
-        * @param array $time Result of UIDGenerator::millitime()
-        * @return Array|bool UIDGenerator::millitime() result or false
-        */
-       protected function timeWaitUntil( array $time ) {
-               do {
-                       $ct = self::millitime();
-                       if ( $ct >= $time ) { // http://php.net/manual/en/language.operators.comparison.php
-                               return $ct; // current timestamp is higher than $time
-                       }
-               } while ( ( ( $time[0] - $ct[0] ) * 1000 + ( $time[1] - $ct[1] ) ) <= 10 );
-
-               return false;
-       }
-
-       /**
-        * @param array $time Result of UIDGenerator::millitime()
-        * @return string 46 MSBs of "milliseconds since epoch" in binary (rolls over in 4201)
-        */
-       protected function millisecondsSinceEpochBinary( array $time ) {
-               list( $sec, $msec ) = $time;
-               $ts = 1000 * $sec + $msec;
-               if ( $ts > pow( 2, 52 ) ) {
-                       throw new MWException( __METHOD__ .
-                               ': sorry, this function doesn\'t work after the year 144680' );
-               }
-               return substr( wfBaseConvert( $ts, 10, 2, 46 ), -46 );
-       }
-
-       /**
-        * @return Array (current time in seconds, milliseconds since then)
-        */
-       protected static function millitime() {
-               list( $msec, $sec ) = explode( ' ', microtime() );
-               return array( (int)$sec, (int)( $msec * 1000 ) );
-       }
-
-       function __destruct() {
-               array_map( 'fclose', $this->fileHandles );
-       }
-}
diff --git a/includes/ViewCountUpdate.php b/includes/ViewCountUpdate.php
deleted file mode 100644 (file)
index 22a4649..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-<?php
-/**
- * Update for the 'page_counter' field
- *
- * 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
- */
-
-/**
- * Update for the 'page_counter' field, when $wgDisableCounters is false.
- *
- * Depending on $wgHitcounterUpdateFreq, this will directly increment the
- * 'page_counter' field or use the 'hitcounter' table and then collect the data
- * from that table to update the 'page_counter' field in a batch operation.
- */
-class ViewCountUpdate implements DeferrableUpdate {
-       protected $id;
-
-       /**
-        * Constructor
-        *
-        * @param $id Integer: page ID to increment the view count
-        */
-       public function __construct( $id ) {
-               $this->id = intval( $id );
-       }
-
-       /**
-        * Run the update
-        */
-       public function doUpdate() {
-               global $wgHitcounterUpdateFreq;
-
-               $dbw = wfGetDB( DB_MASTER );
-
-               if ( $wgHitcounterUpdateFreq <= 1 || $dbw->getType() == 'sqlite' ) {
-                       $dbw->update( 'page', array( 'page_counter = page_counter + 1' ), array( 'page_id' => $this->id ), __METHOD__ );
-                       return;
-               }
-
-               # Not important enough to warrant an error page in case of failure
-               try {
-                       $dbw->insert( 'hitcounter', array( 'hc_id' => $this->id ), __METHOD__ );
-                       $checkfreq = intval( $wgHitcounterUpdateFreq / 25 + 1 );
-                       if ( rand() % $checkfreq == 0 && $dbw->lastErrno() == 0 ) {
-                               $this->collect();
-                       }
-               } catch ( DBError $e ) {}
-       }
-
-       protected function collect() {
-               global $wgHitcounterUpdateFreq;
-
-               $dbw = wfGetDB( DB_MASTER );
-
-               $rown = $dbw->selectField( 'hitcounter', 'COUNT(*)', array(), __METHOD__ );
-
-               if ( $rown < $wgHitcounterUpdateFreq ) {
-                       return;
-               }
-
-               wfProfileIn( __METHOD__ . '-collect' );
-               $old_user_abort = ignore_user_abort( true );
-
-               $dbw->lockTables( array(), array( 'hitcounter' ), __METHOD__, false );
-
-               $dbType = $dbw->getType();
-               $tabletype = $dbType == 'mysql' ? "ENGINE=HEAP " : '';
-               $hitcounterTable = $dbw->tableName( 'hitcounter' );
-               $acchitsTable = $dbw->tableName( 'acchits' );
-               $pageTable = $dbw->tableName( 'page' );
-
-               $dbw->query( "CREATE TEMPORARY TABLE $acchitsTable $tabletype AS " .
-                       "SELECT hc_id,COUNT(*) AS hc_n FROM $hitcounterTable " .
-                       'GROUP BY hc_id', __METHOD__ );
-               $dbw->delete( 'hitcounter', '*', __METHOD__ );
-               $dbw->unlockTables( __METHOD__ );
-
-               if ( $dbType == 'mysql' ) {
-                       $dbw->query( "UPDATE $pageTable,$acchitsTable SET page_counter=page_counter + hc_n " .
-                               'WHERE page_id = hc_id', __METHOD__ );
-               } else {
-                       $dbw->query( "UPDATE $pageTable SET page_counter=page_counter + hc_n " .
-                               "FROM $acchitsTable WHERE page_id = hc_id", __METHOD__ );
-               }
-               $dbw->query( "DROP TABLE $acchitsTable", __METHOD__ );
-
-               ignore_user_abort( $old_user_abort );
-               wfProfileOut( __METHOD__ . '-collect' );
-       }
-}
index 403ef11..a690176 100644 (file)
@@ -302,13 +302,6 @@ class MediaWiki {
                        $article = $this->initializeArticle();
                        if ( is_object( $article ) ) {
                                $pageView = true;
-                               /**
-                                * $wgArticle is deprecated, do not use it.
-                                * @deprecated since 1.18
-                                */
-                               global $wgArticle;
-                               $wgArticle = new DeprecatedGlobal( 'wgArticle', $article, '1.18' );
-
                                $this->performAction( $article, $requestTitle );
                        } elseif ( is_string( $article ) ) {
                                $output->redirect( $article );
@@ -688,7 +681,7 @@ class MediaWiki {
                                // We don't want exceptions thrown during job execution to
                                // be reported to the user since the output is already sent.
                                // Instead we just log them.
-                               wfDebugLog( 'exception', $e->getLogMessage() );
+                               MWExceptionHandler::logException( $e );
                        }
                }
        }
diff --git a/includes/XmlTypeCheck.php b/includes/XmlTypeCheck.php
deleted file mode 100644 (file)
index 92ca7d8..0000000
+++ /dev/null
@@ -1,184 +0,0 @@
-<?php
-/**
- * XML syntax and type checker.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- */
-
-class XmlTypeCheck {
-       /**
-        * Will be set to true or false to indicate whether the file is
-        * well-formed XML. Note that this doesn't check schema validity.
-        */
-       public $wellFormed = false;
-
-       /**
-        * Will be set to true if the optional element filter returned
-        * a match at some point.
-        */
-       public $filterMatch = false;
-
-       /**
-        * Name of the document's root element, including any namespace
-        * as an expanded URL.
-        */
-       public $rootElement = '';
-
-       /**
-        * @param string $input a filename or string containing the XML element
-        * @param callable $filterCallback (optional)
-        *        Function to call to do additional custom validity checks from the
-        *        SAX element handler event. This gives you access to the element
-        *        namespace, name, and attributes, but not to text contents.
-        *        Filter should return 'true' to toggle on $this->filterMatch
-        * @param boolean $isFile (optional) indicates if the first parameter is a
-        *        filename (default, true) or if it is a string (false)
-        */
-       function __construct( $input, $filterCallback = null, $isFile = true ) {
-               $this->filterCallback = $filterCallback;
-               if ( $isFile ) {
-                       $this->validateFromFile( $input );
-               } else {
-                       $this->validateFromString( $input );
-               }
-       }
-
-       /**
-        * Alternative constructor: from filename
-        *
-        * @param string $fname the filename of an XML document
-        * @param callable $filterCallback (optional)
-        *        Function to call to do additional custom validity checks from the
-        *        SAX element handler event. This gives you access to the element
-        *        namespace, name, and attributes, but not to text contents.
-        *        Filter should return 'true' to toggle on $this->filterMatch
-        * @return XmlTypeCheck
-        */
-       public static function newFromFilename( $fname, $filterCallback = null ) {
-               return new self( $fname, $filterCallback, true );
-       }
-
-       /**
-        * Alternative constructor: from string
-        *
-        * @param string $string a string containing an XML element
-        * @param callable $filterCallback (optional)
-        *        Function to call to do additional custom validity checks from the
-        *        SAX element handler event. This gives you access to the element
-        *        namespace, name, and attributes, but not to text contents.
-        *        Filter should return 'true' to toggle on $this->filterMatch
-        * @return XmlTypeCheck
-        */
-       public static function newFromString( $string, $filterCallback = null ) {
-               return new self( $string, $filterCallback, false );
-       }
-
-       /**
-        * Get the root element. Simple accessor to $rootElement
-        *
-        * @return string
-        */
-       public function getRootElement() {
-               return $this->rootElement;
-       }
-
-       /**
-        * Get an XML parser with the root element handler.
-        * @see XmlTypeCheck::rootElementOpen()
-        * @return resource a resource handle for the XML parser
-        */
-       private function getParser() {
-               $parser = xml_parser_create_ns( 'UTF-8' );
-               // case folding violates XML standard, turn it off
-               xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
-               xml_set_element_handler( $parser, array( $this, 'rootElementOpen' ), false );
-               return $parser;
-       }
-
-       /**
-        * @param string $fname the filename
-        */
-       private function validateFromFile( $fname ) {
-               $parser = $this->getParser();
-
-               if ( file_exists( $fname ) ) {
-                       $file = fopen( $fname, "rb" );
-                       if ( $file ) {
-                               do {
-                                       $chunk = fread( $file, 32768 );
-                                       $ret = xml_parse( $parser, $chunk, feof( $file ) );
-                                       if ( $ret == 0 ) {
-                                               $this->wellFormed = false;
-                                               fclose( $file );
-                                               xml_parser_free( $parser );
-                                               return;
-                                       }
-                               } while ( !feof( $file ) );
-
-                               fclose( $file );
-                       }
-               }
-               $this->wellFormed = true;
-
-               xml_parser_free( $parser );
-       }
-
-       /**
-        *
-        * @param string $string the XML-input-string to be checked.
-        */
-       private function validateFromString( $string ) {
-               $parser = $this->getParser();
-               $ret = xml_parse( $parser, $string, true );
-               xml_parser_free( $parser );
-               if ( $ret == 0 ) {
-                       $this->wellFormed = false;
-                       return;
-               }
-               $this->wellFormed = true;
-       }
-
-       /**
-        * @param $parser
-        * @param $name
-        * @param $attribs
-        */
-       private function rootElementOpen( $parser, $name, $attribs ) {
-               $this->rootElement = $name;
-
-               if ( is_callable( $this->filterCallback ) ) {
-                       xml_set_element_handler( $parser, array( $this, 'elementOpen' ), false );
-                       $this->elementOpen( $parser, $name, $attribs );
-               } else {
-                       // We only need the first open element
-                       xml_set_element_handler( $parser, false, false );
-               }
-       }
-
-       /**
-        * @param $parser
-        * @param $name
-        * @param $attribs
-        */
-       private function elementOpen( $parser, $name, $attribs ) {
-               if ( call_user_func( $this->filterCallback, $name, $attribs ) ) {
-                       // Filter hit!
-                       $this->filterMatch = true;
-               }
-       }
-}
diff --git a/includes/ZipDirectoryReader.php b/includes/ZipDirectoryReader.php
deleted file mode 100644 (file)
index 307efce..0000000
+++ /dev/null
@@ -1,712 +0,0 @@
-<?php
-/**
- * ZIP file directories reader, for the purposes of upload verification.
- *
- * 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
- */
-
-/**
- * A class for reading ZIP file directories, for the purposes of upload
- * verification.
- *
- * Only a functional interface is provided: ZipFileReader::read(). No access is
- * given to object instances.
- *
- */
-class ZipDirectoryReader {
-       /**
-        * Read a ZIP file and call a function for each file discovered in it.
-        *
-        * Because this class is aimed at verification, an error is raised on
-        * suspicious or ambiguous input, instead of emulating some standard
-        * behavior.
-        *
-        * @param string $fileName The archive file name
-        * @param array $callback The callback function. It will be called for each file
-        *   with a single associative array each time, with members:
-        *
-        *      - name: The file name. Directories conventionally have a trailing
-        *        slash.
-        *
-        *      - mtime: The file modification time, in MediaWiki 14-char format
-        *
-        *      - size: The uncompressed file size
-        *
-        * @param array $options An associative array of read options, with the option
-        *    name in the key. This may currently contain:
-        *
-        *      - zip64: If this is set to true, then we will emulate a
-        *        library with ZIP64 support, like OpenJDK 7. If it is set to
-        *        false, then we will emulate a library with no knowledge of
-        *        ZIP64.
-        *
-        *        NOTE: The ZIP64 code is untested and probably doesn't work. It
-        *        turned out to be easier to just reject ZIP64 archive uploads,
-        *        since they are likely to be very rare. Confirming safety of a
-        *        ZIP64 file is fairly complex. What do you do with a file that is
-        *        ambiguous and broken when read with a non-ZIP64 reader, but valid
-        *        when read with a ZIP64 reader? This situation is normal for a
-        *        valid ZIP64 file, and working out what non-ZIP64 readers will make
-        *        of such a file is not trivial.
-        *
-        * @return Status object. The following fatal errors are defined:
-        *
-        *      - zip-file-open-error: The file could not be opened.
-        *
-        *      - zip-wrong-format: The file does not appear to be a ZIP file.
-        *
-        *      - zip-bad: There was something wrong or ambiguous about the file
-        *        data.
-        *
-        *      - zip-unsupported: The ZIP file uses features which
-        *        ZipDirectoryReader does not support.
-        *
-        * The default messages for those fatal errors are written in a way that
-        * makes sense for upload verification.
-        *
-        * If a fatal error is returned, more information about the error will be
-        * available in the debug log.
-        *
-        * Note that the callback function may be called any number of times before
-        * a fatal error is returned. If this occurs, the data sent to the callback
-        * function should be discarded.
-        */
-       public static function read( $fileName, $callback, $options = array() ) {
-               $zdr = new self( $fileName, $callback, $options );
-               return $zdr->execute();
-       }
-
-       /** The file name */
-       var $fileName;
-
-       /** The opened file resource */
-       var $file;
-
-       /** The cached length of the file, or null if it has not been loaded yet. */
-       var $fileLength;
-
-       /** A segmented cache of the file contents */
-       var $buffer;
-
-       /** The file data callback */
-       var $callback;
-
-       /** The ZIP64 mode */
-       var $zip64 = false;
-
-       /** Stored headers */
-       var $eocdr, $eocdr64, $eocdr64Locator;
-
-       var $data;
-
-       /** The "extra field" ID for ZIP64 central directory entries */
-       const ZIP64_EXTRA_HEADER = 0x0001;
-
-       /** The segment size for the file contents cache */
-       const SEGSIZE = 16384;
-
-       /** The index of the "general field" bit for UTF-8 file names */
-       const GENERAL_UTF8 = 11;
-
-       /** The index of the "general field" bit for central directory encryption */
-       const GENERAL_CD_ENCRYPTED = 13;
-
-       /**
-        * Private constructor
-        */
-       protected function __construct( $fileName, $callback, $options ) {
-               $this->fileName = $fileName;
-               $this->callback = $callback;
-
-               if ( isset( $options['zip64'] ) ) {
-                       $this->zip64 = $options['zip64'];
-               }
-       }
-
-       /**
-        * Read the directory according to settings in $this.
-        *
-        * @return Status
-        */
-       function execute() {
-               $this->file = fopen( $this->fileName, 'r' );
-               $this->data = array();
-               if ( !$this->file ) {
-                       return Status::newFatal( 'zip-file-open-error' );
-               }
-
-               $status = Status::newGood();
-               try {
-                       $this->readEndOfCentralDirectoryRecord();
-                       if ( $this->zip64 ) {
-                               list( $offset, $size ) = $this->findZip64CentralDirectory();
-                               $this->readCentralDirectory( $offset, $size );
-                       } else {
-                               if ( $this->eocdr['CD size'] == 0xffffffff
-                                       || $this->eocdr['CD offset'] == 0xffffffff
-                                       || $this->eocdr['CD entries total'] == 0xffff )
-                               {
-                                       $this->error( 'zip-unsupported', 'Central directory header indicates ZIP64, ' .
-                                               'but we are in legacy mode. Rejecting this upload is necessary to avoid ' .
-                                               'opening vulnerabilities on clients using OpenJDK 7 or later.' );
-                               }
-
-                               list( $offset, $size ) = $this->findOldCentralDirectory();
-                               $this->readCentralDirectory( $offset, $size );
-                       }
-               } catch ( ZipDirectoryReaderError $e ) {
-                       $status->fatal( $e->getErrorCode() );
-               }
-
-               fclose( $this->file );
-               return $status;
-       }
-
-       /**
-        * Throw an error, and log a debug message
-        */
-       function error( $code, $debugMessage ) {
-               wfDebug( __CLASS__ . ": Fatal error: $debugMessage\n" );
-               throw new ZipDirectoryReaderError( $code );
-       }
-
-       /**
-        * Read the header which is at the end of the central directory,
-        * unimaginatively called the "end of central directory record" by the ZIP
-        * spec.
-        */
-       function readEndOfCentralDirectoryRecord() {
-               $info = array(
-                       'signature' => 4,
-                       'disk' => 2,
-                       'CD start disk' => 2,
-                       'CD entries this disk' => 2,
-                       'CD entries total' => 2,
-                       'CD size' => 4,
-                       'CD offset' => 4,
-                       'file comment length' => 2,
-               );
-               $structSize = $this->getStructSize( $info );
-               $startPos = $this->getFileLength() - 65536 - $structSize;
-               if ( $startPos < 0 ) {
-                       $startPos = 0;
-               }
-
-               $block = $this->getBlock( $startPos );
-               $sigPos = strrpos( $block, "PK\x05\x06" );
-               if ( $sigPos === false ) {
-                       $this->error( 'zip-wrong-format',
-                               "zip file lacks EOCDR signature. It probably isn't a zip file." );
-               }
-
-               $this->eocdr = $this->unpack( substr( $block, $sigPos ), $info );
-               $this->eocdr['EOCDR size'] = $structSize + $this->eocdr['file comment length'];
-
-               if ( $structSize + $this->eocdr['file comment length'] != strlen( $block ) - $sigPos ) {
-                       $this->error( 'zip-bad', 'trailing bytes after the end of the file comment' );
-               }
-               if ( $this->eocdr['disk'] !== 0
-                       || $this->eocdr['CD start disk'] !== 0 )
-               {
-                       $this->error( 'zip-unsupported', 'more than one disk (in EOCDR)' );
-               }
-               $this->eocdr += $this->unpack(
-                       $block,
-                       array( 'file comment' => array( 'string', $this->eocdr['file comment length'] ) ),
-                       $sigPos + $structSize );
-               $this->eocdr['position'] = $startPos + $sigPos;
-       }
-
-       /**
-        * Read the header called the "ZIP64 end of central directory locator". An
-        * error will be raised if it does not exist.
-        */
-       function readZip64EndOfCentralDirectoryLocator() {
-               $info = array(
-                       'signature' => array( 'string', 4 ),
-                       'eocdr64 start disk' => 4,
-                       'eocdr64 offset' => 8,
-                       'number of disks' => 4,
-               );
-               $structSize = $this->getStructSize( $info );
-
-               $block = $this->getBlock( $this->getFileLength() - $this->eocdr['EOCDR size']
-                       - $structSize, $structSize );
-               $this->eocdr64Locator = $data = $this->unpack( $block, $info );
-
-               if ( $data['signature'] !== "PK\x06\x07" ) {
-                       // Note: Java will allow this and continue to read the
-                       // EOCDR64, so we have to reject the upload, we can't
-                       // just use the EOCDR header instead.
-                       $this->error( 'zip-bad', 'wrong signature on Zip64 end of central directory locator' );
-               }
-       }
-
-       /**
-        * Read the header called the "ZIP64 end of central directory record". It
-        * may replace the regular "end of central directory record" in ZIP64 files.
-        */
-       function readZip64EndOfCentralDirectoryRecord() {
-               if ( $this->eocdr64Locator['eocdr64 start disk'] != 0
-                       || $this->eocdr64Locator['number of disks'] != 0 )
-               {
-                       $this->error( 'zip-unsupported', 'more than one disk (in EOCDR64 locator)' );
-               }
-
-               $info = array(
-                       'signature' => array( 'string', 4 ),
-                       'EOCDR64 size' => 8,
-                       'version made by' => 2,
-                       'version needed' => 2,
-                       'disk' => 4,
-                       'CD start disk' => 4,
-                       'CD entries this disk' => 8,
-                       'CD entries total' => 8,
-                       'CD size' => 8,
-                       'CD offset' => 8
-               );
-               $structSize = $this->getStructSize( $info );
-               $block = $this->getBlock( $this->eocdr64Locator['eocdr64 offset'], $structSize );
-               $this->eocdr64 = $data = $this->unpack( $block, $info );
-               if ( $data['signature'] !== "PK\x06\x06" ) {
-                       $this->error( 'zip-bad', 'wrong signature on Zip64 end of central directory record' );
-               }
-               if ( $data['disk'] !== 0
-                       || $data['CD start disk'] !== 0 )
-               {
-                       $this->error( 'zip-unsupported', 'more than one disk (in EOCDR64)' );
-               }
-       }
-
-       /**
-        * Find the location of the central directory, as would be seen by a
-        * non-ZIP64 reader.
-        *
-        * @return List containing offset, size and end position.
-        */
-       function findOldCentralDirectory() {
-               $size = $this->eocdr['CD size'];
-               $offset = $this->eocdr['CD offset'];
-               $endPos = $this->eocdr['position'];
-
-               // Some readers use the EOCDR position instead of the offset field
-               // to find the directory, so to be safe, we check if they both agree.
-               if ( $offset + $size != $endPos ) {
-                       $this->error( 'zip-bad', 'the central directory does not immediately precede the end ' .
-                               'of central directory record' );
-               }
-               return array( $offset, $size );
-       }
-
-       /**
-        * Find the location of the central directory, as would be seen by a
-        * ZIP64-compliant reader.
-        *
-        * @return array List containing offset, size and end position.
-        */
-       function findZip64CentralDirectory() {
-               // The spec is ambiguous about the exact rules of precedence between the
-               // ZIP64 headers and the original headers. Here we follow zip_util.c
-               // from OpenJDK 7.
-               $size = $this->eocdr['CD size'];
-               $offset = $this->eocdr['CD offset'];
-               $numEntries = $this->eocdr['CD entries total'];
-               $endPos = $this->eocdr['position'];
-               if ( $size == 0xffffffff
-                       || $offset == 0xffffffff
-                       || $numEntries == 0xffff )
-               {
-                       $this->readZip64EndOfCentralDirectoryLocator();
-
-                       if ( isset( $this->eocdr64Locator['eocdr64 offset'] ) ) {
-                               $this->readZip64EndOfCentralDirectoryRecord();
-                               if ( isset( $this->eocdr64['CD offset'] ) ) {
-                                       $size = $this->eocdr64['CD size'];
-                                       $offset = $this->eocdr64['CD offset'];
-                                       $endPos = $this->eocdr64Locator['eocdr64 offset'];
-                               }
-                       }
-               }
-               // Some readers use the EOCDR position instead of the offset field
-               // to find the directory, so to be safe, we check if they both agree.
-               if ( $offset + $size != $endPos ) {
-                       $this->error( 'zip-bad', 'the central directory does not immediately precede the end ' .
-                               'of central directory record' );
-               }
-               return array( $offset, $size );
-       }
-
-       /**
-        * Read the central directory at the given location
-        */
-       function readCentralDirectory( $offset, $size ) {
-               $block = $this->getBlock( $offset, $size );
-
-               $fixedInfo = array(
-                       'signature' => array( 'string', 4 ),
-                       'version made by' => 2,
-                       'version needed' => 2,
-                       'general bits' => 2,
-                       'compression method' => 2,
-                       'mod time' => 2,
-                       'mod date' => 2,
-                       'crc-32' => 4,
-                       'compressed size' => 4,
-                       'uncompressed size' => 4,
-                       'name length' => 2,
-                       'extra field length' => 2,
-                       'comment length' => 2,
-                       'disk number start' => 2,
-                       'internal attrs' => 2,
-                       'external attrs' => 4,
-                       'local header offset' => 4,
-               );
-               $fixedSize = $this->getStructSize( $fixedInfo );
-
-               $pos = 0;
-               while ( $pos < $size ) {
-                       $data = $this->unpack( $block, $fixedInfo, $pos );
-                       $pos += $fixedSize;
-
-                       if ( $data['signature'] !== "PK\x01\x02" ) {
-                               $this->error( 'zip-bad', 'Invalid signature found in directory entry' );
-                       }
-
-                       $variableInfo = array(
-                               'name' => array( 'string', $data['name length'] ),
-                               'extra field' => array( 'string', $data['extra field length'] ),
-                               'comment' => array( 'string', $data['comment length'] ),
-                       );
-                       $data += $this->unpack( $block, $variableInfo, $pos );
-                       $pos += $this->getStructSize( $variableInfo );
-
-                       if ( $this->zip64 && (
-                                  $data['compressed size'] == 0xffffffff
-                               || $data['uncompressed size'] == 0xffffffff
-                               || $data['local header offset'] == 0xffffffff ) )
-                       {
-                               $zip64Data = $this->unpackZip64Extra( $data['extra field'] );
-                               if ( $zip64Data ) {
-                                       $data = $zip64Data + $data;
-                               }
-                       }
-
-                       if ( $this->testBit( $data['general bits'], self::GENERAL_CD_ENCRYPTED ) ) {
-                               $this->error( 'zip-unsupported', 'central directory encryption is not supported' );
-                       }
-
-                       // Convert the timestamp into MediaWiki format
-                       // For the format, please see the MS-DOS 2.0 Programmer's Reference,
-                       // pages 3-5 and 3-6.
-                       $time = $data['mod time'];
-                       $date = $data['mod date'];
-
-                       $year = 1980 + ( $date >> 9 );
-                       $month = ( $date >> 5 ) & 15;
-                       $day = $date & 31;
-                       $hour = ( $time >> 11 ) & 31;
-                       $minute = ( $time >> 5 ) & 63;
-                       $second = ( $time & 31 ) * 2;
-                       $timestamp = sprintf( "%04d%02d%02d%02d%02d%02d",
-                               $year, $month, $day, $hour, $minute, $second );
-
-                       // Convert the character set in the file name
-                       if ( !function_exists( 'iconv' )
-                               || $this->testBit( $data['general bits'], self::GENERAL_UTF8 ) )
-                       {
-                               $name = $data['name'];
-                       } else {
-                               $name = iconv( 'CP437', 'UTF-8', $data['name'] );
-                       }
-
-                       // Compile a data array for the user, with a sensible format
-                       $userData = array(
-                               'name' => $name,
-                               'mtime' => $timestamp,
-                               'size' => $data['uncompressed size'],
-                       );
-                       call_user_func( $this->callback, $userData );
-               }
-       }
-
-       /**
-        * Interpret ZIP64 "extra field" data and return an associative array.
-        * @return array|bool
-        */
-       function unpackZip64Extra( $extraField ) {
-               $extraHeaderInfo = array(
-                       'id' => 2,
-                       'size' => 2,
-               );
-               $extraHeaderSize = $this->getStructSize( $extraHeaderInfo );
-
-               $zip64ExtraInfo = array(
-                       'uncompressed size' => 8,
-                       'compressed size' => 8,
-                       'local header offset' => 8,
-                       'disk number start' => 4,
-               );
-
-               $extraPos = 0;
-               while ( $extraPos < strlen( $extraField ) ) {
-                       $extra = $this->unpack( $extraField, $extraHeaderInfo, $extraPos );
-                       $extraPos += $extraHeaderSize;
-                       $extra += $this->unpack( $extraField,
-                               array( 'data' => array( 'string', $extra['size'] ) ),
-                               $extraPos );
-                       $extraPos += $extra['size'];
-
-                       if ( $extra['id'] == self::ZIP64_EXTRA_HEADER ) {
-                               return $this->unpack( $extra['data'], $zip64ExtraInfo );
-                       }
-               }
-
-               return false;
-       }
-
-       /**
-        * Get the length of the file.
-        */
-       function getFileLength() {
-               if ( $this->fileLength === null ) {
-                       $stat = fstat( $this->file );
-                       $this->fileLength = $stat['size'];
-               }
-               return $this->fileLength;
-       }
-
-       /**
-        * Get the file contents from a given offset. If there are not enough bytes
-        * in the file to satisfy the request, an exception will be thrown.
-        *
-        * @param int $start The byte offset of the start of the block.
-        * @param int $length The number of bytes to return. If omitted, the remainder
-        *    of the file will be returned.
-        *
-        * @return string
-        */
-       function getBlock( $start, $length = null ) {
-               $fileLength = $this->getFileLength();
-               if ( $start >= $fileLength ) {
-                       $this->error( 'zip-bad', "getBlock() requested position $start, " .
-                               "file length is $fileLength" );
-               }
-               if ( $length === null ) {
-                       $length = $fileLength - $start;
-               }
-               $end = $start + $length;
-               if ( $end > $fileLength ) {
-                       $this->error( 'zip-bad', "getBlock() requested end position $end, " .
-                               "file length is $fileLength" );
-               }
-               $startSeg = floor( $start / self::SEGSIZE );
-               $endSeg = ceil( $end / self::SEGSIZE );
-
-               $block = '';
-               for ( $segIndex = $startSeg; $segIndex <= $endSeg; $segIndex++ ) {
-                       $block .= $this->getSegment( $segIndex );
-               }
-
-               $block = substr( $block,
-                       $start - $startSeg * self::SEGSIZE,
-                       $length );
-
-               if ( strlen( $block ) < $length ) {
-                       $this->error( 'zip-bad', 'getBlock() returned an unexpectedly small amount of data' );
-               }
-
-               return $block;
-       }
-
-       /**
-        * Get a section of the file starting at position $segIndex * self::SEGSIZE,
-        * of length self::SEGSIZE. The result is cached. This is a helper function
-        * for getBlock().
-        *
-        * If there are not enough bytes in the file to satisfy the request, the
-        * return value will be truncated. If a request is made for a segment beyond
-        * the end of the file, an empty string will be returned.
-        * @return string
-        */
-       function getSegment( $segIndex ) {
-               if ( !isset( $this->buffer[$segIndex] ) ) {
-                       $bytePos = $segIndex * self::SEGSIZE;
-                       if ( $bytePos >= $this->getFileLength() ) {
-                               $this->buffer[$segIndex] = '';
-                               return '';
-                       }
-                       if ( fseek( $this->file, $bytePos ) ) {
-                               $this->error( 'zip-bad', "seek to $bytePos failed" );
-                       }
-                       $seg = fread( $this->file, self::SEGSIZE );
-                       if ( $seg === false ) {
-                               $this->error( 'zip-bad', "read from $bytePos failed" );
-                       }
-                       $this->buffer[$segIndex] = $seg;
-               }
-               return $this->buffer[$segIndex];
-       }
-
-       /**
-        * Get the size of a structure in bytes. See unpack() for the format of $struct.
-        * @return int
-        */
-       function getStructSize( $struct ) {
-               $size = 0;
-               foreach ( $struct as $type ) {
-                       if ( is_array( $type ) ) {
-                               list( , $fieldSize ) = $type;
-                               $size += $fieldSize;
-                       } else {
-                               $size += $type;
-                       }
-               }
-               return $size;
-       }
-
-       /**
-        * Unpack a binary structure. This is like the built-in unpack() function
-        * except nicer.
-        *
-        * @param string $string The binary data input
-        *
-        * @param array $struct An associative array giving structure members and their
-        *    types. In the key is the field name. The value may be either an
-        *    integer, in which case the field is a little-endian unsigned integer
-        *    encoded in the given number of bytes, or an array, in which case the
-        *    first element of the array is the type name, and the subsequent
-        *    elements are type-dependent parameters. Only one such type is defined:
-        *       - "string": The second array element gives the length of string.
-        *          Not null terminated.
-        *
-        * @param int $offset The offset into the string at which to start unpacking.
-        *
-        * @throws MWException
-        * @return array Unpacked associative array. Note that large integers in the input
-        *    may be represented as floating point numbers in the return value, so
-        *    the use of weak comparison is advised.
-        */
-       function unpack( $string, $struct, $offset = 0 ) {
-               $size = $this->getStructSize( $struct );
-               if ( $offset + $size > strlen( $string ) ) {
-                       $this->error( 'zip-bad', 'unpack() would run past the end of the supplied string' );
-               }
-
-               $data = array();
-               $pos = $offset;
-               foreach ( $struct as $key => $type ) {
-                       if ( is_array( $type ) ) {
-                               list( $typeName, $fieldSize ) = $type;
-                               switch ( $typeName ) {
-                               case 'string':
-                                       $data[$key] = substr( $string, $pos, $fieldSize );
-                                       $pos += $fieldSize;
-                                       break;
-                               default:
-                                       throw new MWException( __METHOD__ . ": invalid type \"$typeName\"" );
-                               }
-                       } else {
-                               // Unsigned little-endian integer
-                               $length = intval( $type );
-
-                               // Calculate the value. Use an algorithm which automatically
-                               // upgrades the value to floating point if necessary.
-                               $value = 0;
-                               for ( $i = $length - 1; $i >= 0; $i-- ) {
-                                       $value *= 256;
-                                       $value += ord( $string[$pos + $i] );
-                               }
-
-                               // Throw an exception if there was loss of precision
-                               if ( $value > pow( 2, 52 ) ) {
-                                       $this->error( 'zip-unsupported', 'number too large to be stored in a double. ' .
-                                               'This could happen if we tried to unpack a 64-bit structure ' .
-                                               'at an invalid location.' );
-                               }
-                               $data[$key] = $value;
-                               $pos += $length;
-                       }
-               }
-
-               return $data;
-       }
-
-       /**
-        * Returns a bit from a given position in an integer value, converted to
-        * boolean.
-        *
-        * @param $value integer
-        * @param int $bitIndex The index of the bit, where 0 is the LSB.
-        * @return bool
-        */
-       function testBit( $value, $bitIndex ) {
-               return (bool)( ( $value >> $bitIndex ) & 1 );
-       }
-
-       /**
-        * Debugging helper function which dumps a string in hexdump -C format.
-        */
-       function hexDump( $s ) {
-               $n = strlen( $s );
-               for ( $i = 0; $i < $n; $i += 16 ) {
-                       printf( "%08X ", $i );
-                       for ( $j = 0; $j < 16; $j++ ) {
-                               print " ";
-                               if ( $j == 8 ) {
-                                       print " ";
-                               }
-                               if ( $i + $j >= $n ) {
-                                       print "  ";
-                               } else {
-                                       printf( "%02X", ord( $s[$i + $j] ) );
-                               }
-                       }
-
-                       print "  |";
-                       for ( $j = 0; $j < 16; $j++ ) {
-                               if ( $i + $j >= $n ) {
-                                       print " ";
-                               } elseif ( ctype_print( $s[$i + $j] ) ) {
-                                       print $s[$i + $j];
-                               } else {
-                                       print '.';
-                               }
-                       }
-                       print "|\n";
-               }
-       }
-}
-
-/**
- * Internal exception class. Will be caught by private code.
- */
-class ZipDirectoryReaderError extends Exception {
-       var $errorCode;
-
-       function __construct( $code ) {
-               $this->errorCode = $code;
-               parent::__construct( "ZipDirectoryReader error: $code" );
-       }
-
-       /**
-        * @return mixed
-        */
-       function getErrorCode() {
-               return $this->errorCode;
-       }
-}
index c10d85c..c11f16c 100644 (file)
@@ -383,13 +383,8 @@ class ApiMain extends ApiBase {
                        wfRunHooks( 'ApiMain::onException', array( $this, $e ) );
 
                        // Log it
-                       if ( $e instanceof MWException && !( $e instanceof UsageException ) ) {
-                               global $wgLogExceptionBacktrace;
-                               if ( $wgLogExceptionBacktrace ) {
-                                       wfDebugLog( 'exception', $e->getLogMessage() . "\n" . $e->getTraceAsString() . "\n" );
-                               } else {
-                                       wfDebugLog( 'exception', $e->getLogMessage() );
-                               }
+                       if ( !( $e instanceof UsageException ) ) {
+                               MWExceptionHandler::logException( $e );
                        }
 
                        // Handle any kind of exception by outputting properly formatted error message.
old mode 100644 (file)
new mode 100755 (executable)
index 0ea2868..81c9faf
@@ -49,6 +49,12 @@ class ApiQueryImageInfo extends ApiQueryBase {
 
                $scale = $this->getScale( $params );
 
+               $metadataOpts = array(
+                       'version' => $params['metadataversion'],
+                       'language' => $params['extmetadatalanguage'],
+                       'multilang' => $params['extmetadatamultilang'],
+               );
+
                $pageIds = $this->getPageSet()->getAllTitlesByNamespace();
                if ( !empty( $pageIds[NS_FILE] ) ) {
                        $titles = array_keys( $pageIds[NS_FILE] );
@@ -146,7 +152,9 @@ class ApiQueryImageInfo extends ApiQueryBase {
 
                                        $fit = $this->addPageSubItem( $pageId,
                                                self::getInfo( $img, $prop, $result,
-                                                       $finalThumbParams, $params['metadataversion'] ) );
+                                                       $finalThumbParams, $metadataOpts
+                                               )
+                                       );
                                        if ( !$fit ) {
                                                if ( count( $pageIds[NS_FILE] ) == 1 ) {
                                                        // See the 'the user is screwed' comment above
@@ -178,7 +186,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
                                        $fit = self::getTransformCount() < self::TRANSFORM_LIMIT &&
                                                $this->addPageSubItem( $pageId,
                                                        self::getInfo( $oldie, $prop, $result,
-                                                               $finalThumbParams, $params['metadataversion']
+                                                               $finalThumbParams, $metadataOpts
                                                        )
                                                );
                                        if ( !$fit ) {
@@ -296,10 +304,24 @@ class ApiQueryImageInfo extends ApiQueryBase {
         * @param array $prop of properties to get (in the keys)
         * @param $result ApiResult object
         * @param array $thumbParams containing 'width' and 'height' items, or null
-        * @param string $version Version of image metadata (for things like jpeg which have different versions).
+        * @param string|array $metadataOpts Options for metadata fetching.
+        *   This is an array consisting of the keys:
+        *    'version': The metadata version for the metadata option
+        *    'language': The language for extmetadata property
+        *    'multilang': Return all translations in extmetadata property
         * @return Array: result array
         */
-       static function getInfo( $file, $prop, $result, $thumbParams = null, $version = 'latest' ) {
+       static function getInfo( $file, $prop, $result, $thumbParams = null, $metadataOpts = false ) {
+               global $wgContLang;
+
+               if ( !$metadataOpts || is_string( $metadataOpts ) ) {
+                       $metadataOpts = array(
+                               'version' => $metadataOpts ?: 'latest',
+                               'language' => $wgContLang,
+                               'multilang' => false,
+                       );
+               }
+               $version = $metadataOpts['version'];
                $vals = array();
                // Timestamp is shown even if the file is revdelete'd in interface
                // so do same here.
@@ -359,6 +381,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
                $url = isset( $prop['url'] );
                $sha1 = isset( $prop['sha1'] );
                $meta = isset( $prop['metadata'] );
+               $extmetadata = isset( $prop['extmetadata'] );
                $mime = isset( $prop['mime'] );
                $mediatype = isset( $prop['mediatype'] );
                $archive = isset( $prop['archivename'] );
@@ -417,6 +440,17 @@ class ApiQueryImageInfo extends ApiQueryBase {
                        $vals['metadata'] = $metadata ? self::processMetaData( $metadata, $result ) : null;
                }
 
+               if ( $extmetadata ) {
+                       // Note, this should return an array where all the keys
+                       // start with a letter, and all the values are strings.
+                       // Thus there should be no issue with format=xml.
+                       $format = new FormatMetadata;
+                       $format->setSingleLanguage( !$metadataOpts['multilang'] );
+                       $format->getContext()->setLanguage( $metadataOpts['language'] );
+                       $extmetaArray = $format->fetchExtendedMetadata( $file );
+                       $vals['extmetadata'] = $extmetaArray;
+               }
+
                if ( $mime ) {
                        $vals['mime'] = $file->getMimeType();
                }
@@ -491,6 +525,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
        }
 
        public function getAllowedParams() {
+               global $wgContLang;
                return array(
                        'prop' => array(
                                ApiBase::PARAM_ISMULTI => true,
@@ -522,6 +557,14 @@ class ApiQueryImageInfo extends ApiQueryBase {
                                ApiBase::PARAM_TYPE => 'string',
                                ApiBase::PARAM_DFLT => '1',
                        ),
+                       'extmetadatalanguage' => array(
+                               ApiBase::PARAM_TYPE => 'string',
+                               ApiBase::PARAM_DFLT => $wgContLang->getCode(),
+                       ),
+                       'extmetadatamultilang' => array(
+                               ApiBase::PARAM_TYPE => 'boolean',
+                               ApiBase::PARAM_DFLT => false,
+                       ),
                        'urlparam' => array(
                                ApiBase::PARAM_DFLT => '',
                                ApiBase::PARAM_TYPE => 'string',
@@ -564,6 +607,7 @@ class ApiQueryImageInfo extends ApiQueryBase {
                                ' (requires url and param ' . $modulePrefix . 'urlwidth)',
                        'mediatype' =>      ' mediatype     - Adds the media type of the image',
                        'metadata' =>       ' metadata      - Lists Exif metadata for the version of the image',
+                       'extmetadata' =>    ' extmetadata   - Lists formatted metadata combined from multiple sources. Results are HTML formatted.',
                        'archivename' =>    ' archivename   - Adds the file name of the archive version for non-latest versions',
                        'bitdepth' =>       ' bitdepth      - Adds the bit depth of the version',
                        'uploadwarning' =>  ' uploadwarning - Used by the Special:Upload page to get information about an existing file. Not intended for use outside MediaWiki core',
@@ -603,6 +647,10 @@ class ApiQueryImageInfo extends ApiQueryBase {
                        'end' => 'Timestamp to stop listing at',
                        'metadataversion' => array( "Version of metadata to use. if 'latest' is specified, use latest version.",
                                                "Defaults to '1' for backwards compatibility" ),
+                       'extmetadatalanguage' => array( 'What language to fetch extmetadata in. This affects both which',
+                                               'translation to fetch, if multiple are available, as well as how things',
+                                               'like numbers and various values are formatted.' ),
+                       'extmetadatamultilang' => 'If translations for extmetadata property are available, fetch all of them.',
                        'continue' => 'If the query response includes a continue value, use it here to get another page of results',
                        'localonly' => 'Look only for files in the local repository',
                );
index a776706..ac9e85a 100644 (file)
@@ -153,8 +153,12 @@ class ApiQuerySiteinfo extends ApiQueryBase {
                }
 
                if ( $wgContLang->linkPrefixExtension() ) {
-                       $data['linkprefix'] = wfMessage( 'linkprefix' )->inContentLanguage()->text();
+                       $linkPrefixCharset = $wgContLang->linkPrefixCharset();
+                       $data['linkprefixcharset'] = $linkPrefixCharset;
+                       // For backwards compatability
+                       $data['linkprefix'] = "/^((?>.*[^$linkPrefixCharset]|))(.+)$/sDu";
                } else {
+                       $data['linkprefixcharset'] = '';
                        $data['linkprefix'] = '';
                }
 
diff --git a/includes/cache/HTMLCacheUpdate.php b/includes/cache/HTMLCacheUpdate.php
deleted file mode 100644 (file)
index 992809e..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-<?php
-/**
- * HTML cache invalidation of all pages linking to a given title.
- *
- * 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 Cache
- */
-
-/**
- * Class to invalidate the HTML cache of all the pages linking to a given title.
- *
- * @ingroup Cache
- */
-class HTMLCacheUpdate implements DeferrableUpdate {
-       /**
-        * @var Title
-        */
-       public $mTitle;
-
-       public $mTable;
-
-       /**
-        * @param $titleTo
-        * @param $table
-        * @param $start bool
-        * @param $end bool
-        */
-       function __construct( Title $titleTo, $table ) {
-               $this->mTitle = $titleTo;
-               $this->mTable = $table;
-       }
-
-       public function doUpdate() {
-               wfProfileIn( __METHOD__ );
-
-               $job = new HTMLCacheUpdateJob(
-                       $this->mTitle,
-                       array(
-                               'table' => $this->mTable,
-                       ) + Job::newRootJobParams( // "overall" refresh links job info
-                               "htmlCacheUpdate:{$this->mTable}:{$this->mTitle->getPrefixedText()}"
-                       )
-               );
-
-               $count = $this->mTitle->getBacklinkCache()->getNumLinks( $this->mTable, 200 );
-               if ( $count >= 200 ) { // many backlinks
-                       JobQueueGroup::singleton()->push( $job );
-                       JobQueueGroup::singleton()->deduplicateRootJob( $job );
-               } else { // few backlinks ($count might be off even if 0)
-                       $dbw = wfGetDB( DB_MASTER );
-                       $dbw->onTransactionIdle( function() use ( $job ) {
-                               $job->run(); // just do the purge query now
-                       } );
-               }
-
-               wfProfileOut( __METHOD__ );
-       }
-}
index 1bfd17b..c5a153a 100644 (file)
@@ -106,7 +106,7 @@ class LocalisationCache {
                'fallback', 'namespaceNames', 'bookstoreList',
                'magicWords', 'messages', 'rtl', 'capitalizeAllNouns', 'digitTransformTable',
                'separatorTransformTable', 'fallback8bitEncoding', 'linkPrefixExtension',
-               'linkTrail', 'namespaceAliases',
+               'linkTrail', 'linkPrefixCharset', 'namespaceAliases',
                'dateFormats', 'datePreferences', 'datePreferenceMigrationMap',
                'defaultDateFormat', 'extraUserToggles', 'specialPageAliases',
                'imageFiles', 'preloadedMessages', 'namespaceGenderAliases',
diff --git a/includes/cache/SquidUpdate.php b/includes/cache/SquidUpdate.php
deleted file mode 100644 (file)
index 71afeba..0000000
+++ /dev/null
@@ -1,300 +0,0 @@
-<?php
-/**
- * Squid cache purging.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- * http://www.gnu.org/copyleft/gpl.html
- *
- * @file
- * @ingroup Cache
- */
-
-/**
- * Handles purging appropriate Squid URLs given a title (or titles)
- * @ingroup Cache
- */
-class SquidUpdate {
-
-       /**
-        * Collection of URLs to purge.
-        * @var array
-        */
-       protected $urlArr;
-
-       /**
-        * @param array $urlArr Collection of URLs to purge
-        * @param bool|int $maxTitles Maximum number of unique URLs to purge
-        */
-       public function __construct( $urlArr = array(), $maxTitles = false ) {
-               global $wgMaxSquidPurgeTitles;
-               if ( $maxTitles === false ) {
-                       $maxTitles = $wgMaxSquidPurgeTitles;
-               }
-
-               // Remove duplicate URLs from list
-               $urlArr = array_unique( $urlArr );
-               if ( count( $urlArr ) > $maxTitles ) {
-                       // Truncate to desired maximum URL count
-                       $urlArr = array_slice( $urlArr, 0, $maxTitles );
-               }
-               $this->urlArr = $urlArr;
-       }
-
-       /**
-        * Create a SquidUpdate from the given Title object.
-        *
-        * The resulting SquidUpdate will purge the given Title's URLs as well as
-        * the pages that link to it. Capped at $wgMaxSquidPurgeTitles total URLs.
-        *
-        * @param Title $title
-        * @return SquidUpdate
-        */
-       public static function newFromLinksTo( Title $title ) {
-               global $wgMaxSquidPurgeTitles;
-               wfProfileIn( __METHOD__ );
-
-               # Get a list of URLs linking to this page
-               $dbr = wfGetDB( DB_SLAVE );
-               $res = $dbr->select( array( 'links', 'page' ),
-                       array( 'page_namespace', 'page_title' ),
-                       array(
-                               'pl_namespace' => $title->getNamespace(),
-                               'pl_title' => $title->getDBkey(),
-                               'pl_from=page_id' ),
-                       __METHOD__ );
-               $blurlArr = $title->getSquidURLs();
-               if ( $res->numRows() <= $wgMaxSquidPurgeTitles ) {
-                       foreach ( $res as $BL ) {
-                               $tobj = Title::makeTitle( $BL->page_namespace, $BL->page_title );
-                               $blurlArr[] = $tobj->getInternalURL();
-                       }
-               }
-
-               wfProfileOut( __METHOD__ );
-               return new SquidUpdate( $blurlArr );
-       }
-
-       /**
-        * Create a SquidUpdate from an array of Title objects, or a TitleArray object
-        *
-        * @param array $titles
-        * @param array $urlArr
-        * @return SquidUpdate
-        */
-       public static function newFromTitles( $titles, $urlArr = array() ) {
-               global $wgMaxSquidPurgeTitles;
-               $i = 0;
-               foreach ( $titles as $title ) {
-                       $urlArr[] = $title->getInternalURL();
-                       if ( $i++ > $wgMaxSquidPurgeTitles ) {
-                               break;
-                       }
-               }
-               return new SquidUpdate( $urlArr );
-       }
-
-       /**
-        * @param Title $title
-        * @return SquidUpdate
-        */
-       public static function newSimplePurge( Title $title ) {
-               $urlArr = $title->getSquidURLs();
-               return new SquidUpdate( $urlArr );
-       }
-
-       /**
-        * Purges the list of URLs passed to the constructor.
-        */
-       public function doUpdate() {
-               self::purge( $this->urlArr );
-       }
-
-       /**
-        * Purges a list of Squids defined in $wgSquidServers.
-        * $urlArr should contain the full URLs to purge as values
-        * (example: $urlArr[] = 'http://my.host/something')
-        * XXX report broken Squids per mail or log
-        *
-        * @param array $urlArr List of full URLs to purge
-        */
-       public static function purge( $urlArr ) {
-               global $wgSquidServers, $wgHTCPRouting;
-
-               if ( !$urlArr ) {
-                       return;
-               }
-
-               wfDebugLog( 'squid', __METHOD__ . ': ' . implode( ' ', $urlArr ) . "\n" );
-
-               if ( $wgHTCPRouting ) {
-                       self::HTCPPurge( $urlArr );
-               }
-
-               wfProfileIn( __METHOD__ );
-
-               // Remove duplicate URLs
-               $urlArr = array_unique( $urlArr );
-               // Maximum number of parallel connections per squid
-               $maxSocketsPerSquid = 8;
-               // Number of requests to send per socket
-               // 400 seems to be a good tradeoff, opening a socket takes a while
-               $urlsPerSocket = 400;
-               $socketsPerSquid = ceil( count( $urlArr ) / $urlsPerSocket );
-               if ( $socketsPerSquid > $maxSocketsPerSquid ) {
-                       $socketsPerSquid = $maxSocketsPerSquid;
-               }
-
-               $pool = new SquidPurgeClientPool;
-               $chunks = array_chunk( $urlArr, ceil( count( $urlArr ) / $socketsPerSquid ) );
-               foreach ( $wgSquidServers as $server ) {
-                       foreach ( $chunks as $chunk ) {
-                               $client = new SquidPurgeClient( $server );
-                               foreach ( $chunk as $url ) {
-                                       $client->queuePurge( $url );
-                               }
-                               $pool->addClient( $client );
-                       }
-               }
-               $pool->run();
-
-               wfProfileOut( __METHOD__ );
-       }
-
-       /**
-        * Send Hyper Text Caching Protocol (HTCP) CLR requests.
-        *
-        * @throws MWException
-        * @param array $urlArr Collection of URLs to purge
-        */
-       public static function HTCPPurge( $urlArr ) {
-               global $wgHTCPRouting, $wgHTCPMulticastTTL;
-               wfProfileIn( __METHOD__ );
-
-               // HTCP CLR operation
-               $htcpOpCLR = 4;
-
-               // @todo FIXME: PHP doesn't support these socket constants (include/linux/in.h)
-               if ( !defined( "IPPROTO_IP" ) ) {
-                       define( "IPPROTO_IP", 0 );
-                       define( "IP_MULTICAST_LOOP", 34 );
-                       define( "IP_MULTICAST_TTL", 33 );
-               }
-
-               // pfsockopen doesn't work because we need set_sock_opt
-               $conn = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
-               if ( ! $conn ) {
-                       $errstr = socket_strerror( socket_last_error() );
-                       wfDebugLog( 'squid', __METHOD__ .
-                               ": Error opening UDP socket: $errstr\n" );
-                       wfProfileOut( __METHOD__ );
-                       return;
-               }
-
-               // Set socket options
-               socket_set_option( $conn, IPPROTO_IP, IP_MULTICAST_LOOP, 0 );
-               if ( $wgHTCPMulticastTTL != 1 ) {
-                       // Set multicast time to live (hop count) option on socket
-                       socket_set_option( $conn, IPPROTO_IP, IP_MULTICAST_TTL,
-                               $wgHTCPMulticastTTL );
-               }
-
-               // Remove duplicate URLs from collection
-               $urlArr = array_unique( $urlArr );
-               foreach ( $urlArr as $url ) {
-                       if ( !is_string( $url ) ) {
-                               wfProfileOut( __METHOD__ );
-                               throw new MWException( 'Bad purge URL' );
-                       }
-                       $url = self::expand( $url );
-                       $conf = self::getRuleForURL( $url, $wgHTCPRouting );
-                       if ( !$conf ) {
-                               wfDebugLog( 'squid', __METHOD__ .
-                                       "No HTCP rule configured for URL {$url} , skipping\n" );
-                               continue;
-                       }
-
-                       if ( isset( $conf['host'] ) && isset( $conf['port'] ) ) {
-                               // Normalize single entries
-                               $conf = array( $conf );
-                       }
-                       foreach ( $conf as $subconf ) {
-                               if ( !isset( $subconf['host'] ) || !isset( $subconf['port'] ) ) {
-                                       wfProfileOut( __METHOD__ );
-                                       throw new MWException( "Invalid HTCP rule for URL $url\n" );
-                               }
-                       }
-
-                       // Construct a minimal HTCP request diagram
-                       // as per RFC 2756
-                       // Opcode 'CLR', no response desired, no auth
-                       $htcpTransID = rand();
-
-                       $htcpSpecifier = pack( 'na4na*na8n',
-                               4, 'HEAD', strlen( $url ), $url,
-                               8, 'HTTP/1.0', 0 );
-
-                       $htcpDataLen = 8 + 2 + strlen( $htcpSpecifier );
-                       $htcpLen = 4 + $htcpDataLen + 2;
-
-                       // Note! Squid gets the bit order of the first
-                       // word wrong, wrt the RFC. Apparently no other
-                       // implementation exists, so adapt to Squid
-                       $htcpPacket = pack( 'nxxnCxNxxa*n',
-                               $htcpLen, $htcpDataLen, $htcpOpCLR,
-                               $htcpTransID, $htcpSpecifier, 2 );
-
-                       wfDebugLog( 'squid', __METHOD__ .
-                               "Purging URL $url via HTCP\n" );
-                       foreach ( $conf as $subconf ) {
-                               socket_sendto( $conn, $htcpPacket, $htcpLen, 0,
-                                       $subconf['host'], $subconf['port'] );
-                       }
-               }
-               wfProfileOut( __METHOD__ );
-       }
-
-       /**
-        * Expand local URLs to fully-qualified URLs using the internal protocol
-        * and host defined in $wgInternalServer. Input that's already fully-
-        * qualified will be passed through unchanged.
-        *
-        * This is used to generate purge URLs that may be either local to the
-        * main wiki or include a non-native host, such as images hosted on a
-        * second internal server.
-        *
-        * Client functions should not need to call this.
-        *
-        * @param string $url
-        * @return string
-        */
-       public static function expand( $url ) {
-               return wfExpandUrl( $url, PROTO_INTERNAL );
-       }
-
-       /**
-        * Find the HTCP routing rule to use for a given URL.
-        * @param string $url URL to match
-        * @param array $rules Array of rules, see $wgHTCPRouting for format and behavior
-        * @return mixed Element of $rules that matched, or false if nothing matched
-        */
-       private static function getRuleForURL( $url, $rules ) {
-               foreach ( $rules as $regex => $routing ) {
-                       if ( $regex === '' || preg_match( $regex, $url ) ) {
-                               return $routing;
-                       }
-               }
-               return false;
-       }
-}
index 42f8482..9ffe665 100644 (file)
@@ -3910,7 +3910,7 @@ abstract class DatabaseBase implements IDatabase, DatabaseType {
 
                        }
                        $callers = implode( ', ', $callers );
-                       trigger_error( "DB transaction  callbacks still pending (from $callers)." );
+                       trigger_error( "DB transaction callbacks still pending (from $callers)." );
                }
        }
 }
index da391d7..f14a502 100644 (file)
@@ -66,8 +66,7 @@ class DBError extends MWException {
                $s = $this->getHTMLContent();
 
                if ( $wgShowDBErrorBacktrace ) {
-                       $s .= '<p>Backtrace:</p><p>' .
-                               nl2br( htmlspecialchars( $this->getTraceAsString() ) ) . '</p>';
+                       $s .= '<p>Backtrace:</p><pre>' . htmlspecialchars( $this->getTraceAsString() ) . '</pre>';
                }
 
                return $s;
@@ -134,27 +133,20 @@ class DBConnectionError extends DBError {
        }
 
        /**
-        * @return bool
+        * @return boolean
         */
-       function getLogMessage() {
-               # Don't send to the exception log
+       function isLoggable() {
+               // Don't send to the exception log, already in dberror log
                return false;
        }
 
-       /**
-        * @return string
-        */
-       function getPageTitle() {
-               return $this->msg( 'dberr-header', 'This wiki has a problem' );
-       }
-
        /**
         * @return string
         */
        function getHTML() {
                global $wgShowDBErrorBacktrace, $wgShowHostnames, $wgShowSQLErrors;
 
-               $sorry = htmlspecialchars( $this->msg( 'dberr-problems', "Sorry!\nThis site is experiencing technical difficulties." ) );
+               $sorry = htmlspecialchars( $this->msg( 'dberr-problems', 'Sorry! This site is experiencing technical difficulties.' ) );
                $again = htmlspecialchars( $this->msg( 'dberr-again', 'Try waiting a few minutes and reloading.' ) );
 
                if ( $wgShowHostnames || $wgShowSQLErrors ) {
@@ -169,17 +161,16 @@ class DBConnectionError extends DBError {
                # No database access
                MessageCache::singleton()->disable();
 
-               $text = "<h1>$sorry</h1><p>$again</p><p><small>$info</small></p>";
+               $html = "<h1>$sorry</h1><p>$again</p><p><small>$info</small></p>";
 
                if ( $wgShowDBErrorBacktrace ) {
-                       $text .= '<p>Backtrace:</p><p>' .
-                               nl2br( htmlspecialchars( $this->getTraceAsString() ) ) . '</p>';
+                       $html .= '<p>Backtrace:</p><pre>' . htmlspecialchars( $this->getTraceAsString() ) . '</pre>';
                }
 
-               $text .= '<hr />';
-               $text .= $this->searchForm();
+               $html .= '<hr />';
+               $html .= $this->searchForm();
 
-               return $text;
+               return $html;
        }
 
        protected function getTextContent() {
@@ -195,21 +186,21 @@ class DBConnectionError extends DBError {
        public function reportHTML() {
                global $wgUseFileCache;
 
-               # Check whether we can serve a file-cached copy of the page with the error underneath
+               // Check whether we can serve a file-cached copy of the page with the error underneath
                if ( $wgUseFileCache ) {
                        try {
                                $cache = $this->fileCachedPage();
-                               # Cached version on file system?
+                               // Cached version on file system?
                                if ( $cache !== null ) {
-                                       # Hack: extend the body for error messages
+                                       // Hack: extend the body for error messages
                                        $cache = str_replace( array( '</html>', '</body>' ), '', $cache );
-                                       # Add cache notice...
-                                       $cache .= '<div style="color:red;font-size:150%;font-weight:bold;">' .
+                                       // Add cache notice...
+                                       $cache .= '<div style="border:1px solid #ffd0d0;padding:1em;">' .
                                                htmlspecialchars( $this->msg( 'dberr-cachederror',
-                                                       'This is a cached copy of the requested page, and may not be up to date. ' ) ) .
+                                                       'This is a cached copy of the requested page, and may not be up to date.' ) ) .
                                                '</div>';
 
-                                       # Output cached page with notices on bottom and re-close body
+                                       // Output cached page with notices on bottom and re-close body
                                        echo "{$cache}<hr />{$this->getHTML()}</body></html>";
                                        return;
                                }
@@ -218,7 +209,7 @@ class DBConnectionError extends DBError {
                        }
                }
 
-               # We can't, cough and die in the usual fashion
+               // We can't, cough and die in the usual fashion
                parent::reportHTML();
        }
 
@@ -239,8 +230,8 @@ class DBConnectionError extends DBError {
 
                $trygoogle = <<<EOT
 <div style="margin: 1.5em">$usegoogle<br />
-<small>$outofdate</small></div>
-<!-- SiteSearch Google -->
+<small>$outofdate</small>
+</div>
 <form method="get" action="//www.google.com/search" id="googlesearch">
        <input type="hidden" name="domains" value="$server" />
        <input type="hidden" name="num" value="50" />
@@ -249,12 +240,11 @@ class DBConnectionError extends DBError {
 
        <input type="text" name="q" size="31" maxlength="255" value="$search" />
        <input type="submit" name="btnG" value="$googlesearch" />
-  <div>
-       <input type="radio" name="sitesearch" id="gwiki" value="$server" checked="checked" /><label for="gwiki">$sitename</label>
-       <input type="radio" name="sitesearch" id="gWWW" value="" /><label for="gWWW">WWW</label>
-  </div>
+       <p>
+               <label><input type="radio" name="sitesearch" value="$server" checked="checked" />$sitename</label>
+               <label><input type="radio" name="sitesearch" value="" />WWW</label>
+       </p>
 </form>
-<!-- SiteSearch Google -->
 EOT;
                return $trygoogle;
        }
@@ -266,15 +256,17 @@ EOT;
                global $wgTitle, $wgOut, $wgRequest;
 
                if ( $wgOut->isDisabled() ) {
-                       return ''; // Done already?
+                       // Done already?
+                       return '';
                }
 
-               if ( $wgTitle ) { // use $wgTitle if we managed to set it
+               if ( $wgTitle ) {
+                       // use $wgTitle if we managed to set it
                        $t = $wgTitle->getPrefixedDBkey();
                } else {
-                       # Fallback to the raw title URL param. We can't use the Title
-                       # class is it may hit the interwiki table and give a DB error.
-                       # We may get a cache miss due to not sanitizing the title though.
+                       // Fallback to the raw title URL param. We can't use the Title
+                       // class is it may hit the interwiki table and give a DB error.
+                       // We may get a cache miss due to not sanitizing the title though.
                        $t = str_replace( ' ', '_', $wgRequest->getVal( 'title' ) );
                        if ( $t == '' ) { // fallback to main page
                                $t = Title::newFromText(
@@ -318,15 +310,15 @@ class DBQueryError extends DBError {
        }
 
        /**
-        * @return bool
+        * @return boolean
         */
-       function getLogMessage() {
-               # Don't send to the exception log
+       function isLoggable() {
+               // Don't send to the exception log, already in dberror log
                return false;
        }
 
        /**
-        * @return String
+        * @return string
         */
        function getPageTitle() {
                return $this->msg( 'databaseerror', 'Database error' );
index 519e2df..f10d07f 100644 (file)
@@ -36,9 +36,9 @@ interface LoadMonitor {
 
        /**
         * Perform pre-connection load ratio adjustment.
-        * @param $loads array
-        * @param string $group the selected query group
-        * @param $wiki String
+        * @param array $loads
+        * @param string|bool $group the selected query group. Default: false
+        * @param string|bool $wiki Default: false
         */
        function scaleLoads( &$loads, $group = false, $wiki = false );
 
@@ -71,6 +71,10 @@ interface LoadMonitor {
        function getLagTimes( $serverIndexes, $wiki );
 }
 
+/**
+ * @todo FIXME: Should be  LoadMonitorNull per naming conventions.
+ * PHP CodeSniffer Squiz.Classes.ValidClassName.NotCamelCaps
+ */
 class LoadMonitor_Null implements LoadMonitor {
        function __construct( $parent ) {
        }
@@ -96,13 +100,14 @@ class LoadMonitor_Null implements LoadMonitor {
  * Uses memcached to cache the replication lag for a short time
  *
  * @ingroup Database
+ * @todo FIXME: Should be  LoadMonitorMySQL per naming conventions.
+ * PHP CodeSniffer Squiz.Classes.ValidClassName.NotCamelCaps
  */
 class LoadMonitor_MySQL implements LoadMonitor {
-
        /**
         * @var LoadBalancer
         */
-       var $parent;
+       public $parent;
 
        /**
         * @param LoadBalancer $parent
diff --git a/includes/deferred/CallableUpdate.php b/includes/deferred/CallableUpdate.php
new file mode 100644 (file)
index 0000000..6eb5541
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * Deferrable Update for closure/callback
+ */
+class MWCallableUpdate implements DeferrableUpdate {
+
+       /**
+        * @var closure/callabck
+        */
+       private $callback;
+
+       /**
+        * @param callable $callback
+        */
+       public function __construct( $callback ) {
+               if ( !is_callable( $callback ) ) {
+                       throw new MWException( 'Not a valid callback/closure!' );
+               }
+               $this->callback = $callback;
+       }
+
+       /**
+        * Run the update
+        */
+       public function doUpdate() {
+               call_user_func( $this->callback );
+       }
+
+}
diff --git a/includes/deferred/DataUpdate.php b/includes/deferred/DataUpdate.php
new file mode 100644 (file)
index 0000000..7b9ac28
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+/**
+ * Base code for update jobs that do something with some secondary
+ * data extracted from article.
+ *
+ * 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
+ */
+
+/**
+ * Abstract base class for update jobs that do something with some secondary
+ * data extracted from article.
+ *
+ * @note: subclasses should NOT start or commit transactions in their doUpdate() method,
+ *        a transaction will automatically be wrapped around the update. If need be,
+ *        subclasses can override the beginTransaction() and commitTransaction() methods.
+ */
+abstract class DataUpdate implements DeferrableUpdate {
+
+       /**
+        * Constructor
+        */
+       public function __construct() {
+               # noop
+       }
+
+       /**
+        * Begin an appropriate transaction, if any.
+        * This default implementation does nothing.
+        */
+       public function beginTransaction() {
+               //noop
+       }
+
+       /**
+        * Commit the transaction started via beginTransaction, if any.
+        * This default implementation does nothing.
+        */
+       public function commitTransaction() {
+               //noop
+       }
+
+       /**
+        * Abort / roll back the transaction started via beginTransaction, if any.
+        * This default implementation does nothing.
+        */
+       public function rollbackTransaction() {
+               //noop
+       }
+
+       /**
+        * Convenience method, calls doUpdate() on every DataUpdate in the array.
+        *
+        * This methods supports transactions logic by first calling beginTransaction()
+        * on all updates in the array, then calling doUpdate() on each, and, if all goes well,
+        * then calling commitTransaction() on each update. If an error occurs,
+        * rollbackTransaction() will be called on any update object that had beginTransaction()
+        * called but not yet commitTransaction().
+        *
+        * This allows for limited transactional logic across multiple backends for storing
+        * secondary data.
+        *
+        * @param array $updates a list of DataUpdate instances
+        * @throws Exception|null
+        */
+       public static function runUpdates( $updates ) {
+               if ( empty( $updates ) ) {
+                       return; # nothing to do
+               }
+
+               $open_transactions = array();
+               $exception = null;
+
+               /**
+                * @var $update DataUpdate
+                * @var $trans DataUpdate
+                */
+
+               try {
+                       // begin transactions
+                       foreach ( $updates as $update ) {
+                               $update->beginTransaction();
+                               $open_transactions[] = $update;
+                       }
+
+                       // do work
+                       foreach ( $updates as $update ) {
+                               $update->doUpdate();
+                       }
+
+                       // commit transactions
+                       while ( count( $open_transactions ) > 0 ) {
+                               $trans = array_pop( $open_transactions );
+                               $trans->commitTransaction();
+                       }
+               } catch ( Exception $ex ) {
+                       $exception = $ex;
+                       wfDebug( "Caught exception, will rethrow after rollback: " . $ex->getMessage() );
+               }
+
+               // rollback remaining transactions
+               while ( count( $open_transactions ) > 0 ) {
+                       $trans = array_pop( $open_transactions );
+                       $trans->rollbackTransaction();
+               }
+
+               if ( $exception ) {
+                       throw $exception; // rethrow after cleanup
+               }
+       }
+
+}
diff --git a/includes/deferred/DeferredUpdates.php b/includes/deferred/DeferredUpdates.php
new file mode 100644 (file)
index 0000000..c385f13
--- /dev/null
@@ -0,0 +1,129 @@
+<?php
+/**
+ * Interface and manager for deferred updates.
+ *
+ * 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
+ */
+
+/**
+ * Interface that deferrable updates should implement. Basically required so we
+ * can validate input on DeferredUpdates::addUpdate()
+ *
+ * @since 1.19
+ */
+interface DeferrableUpdate {
+       /**
+        * Perform the actual work
+        */
+       function doUpdate();
+}
+
+/**
+ * Class for managing the deferred updates.
+ *
+ * @since 1.19
+ */
+class DeferredUpdates {
+       /**
+        * Store of updates to be deferred until the end of the request.
+        */
+       private static $updates = array();
+
+       /**
+        * Add an update to the deferred list
+        * @param $update DeferrableUpdate Some object that implements doUpdate()
+        */
+       public static function addUpdate( DeferrableUpdate $update ) {
+               array_push( self::$updates, $update );
+       }
+
+       /**
+        * HTMLCacheUpdates are the most common deferred update people use. This
+        * is a shortcut method for that.
+        * @see HTMLCacheUpdate::__construct()
+        * @param $title
+        * @param $table
+        */
+       public static function addHTMLCacheUpdate( $title, $table ) {
+               self::addUpdate( new HTMLCacheUpdate( $title, $table ) );
+       }
+
+       /**
+        * Add a callable update.  In a lot of cases, we just need a callback/closure,
+        * defining a new DeferrableUpdate object is not necessary
+        * @see MWCallableUpdate::__construct()
+        * @param callable $callable
+        */
+       public static function addCallableUpdate( $callable ) {
+               self::addUpdate( new MWCallableUpdate( $callable ) );
+       }
+
+       /**
+        * Do any deferred updates and clear the list
+        *
+        * @param string $commit set to 'commit' to commit after every update to
+        *                prevent lock contention
+        */
+       public static function doUpdates( $commit = '' ) {
+               global $wgDeferredUpdateList;
+
+               wfProfileIn( __METHOD__ );
+
+               $updates = array_merge( $wgDeferredUpdateList, self::$updates );
+
+               // No need to get master connections in case of empty updates array
+               if ( !count( $updates ) ) {
+                       wfProfileOut( __METHOD__ );
+                       return;
+               }
+
+               $doCommit = $commit == 'commit';
+               if ( $doCommit ) {
+                       $dbw = wfGetDB( DB_MASTER );
+               }
+
+               foreach ( $updates as $update ) {
+                       try {
+                               $update->doUpdate();
+
+                               if ( $doCommit && $dbw->trxLevel() ) {
+                                       $dbw->commit( __METHOD__, 'flush' );
+                               }
+                       } catch ( MWException $e ) {
+                               // We don't want exceptions thrown during deferred updates to
+                               // be reported to the user since the output is already sent.
+                               // Instead we just log them.
+                               if ( !$e instanceof ErrorPageError ) {
+                                       MWExceptionHandler::logException( $e );
+                               }
+                       }
+               }
+
+               self::clearPendingUpdates();
+               wfProfileOut( __METHOD__ );
+       }
+
+       /**
+        * Clear all pending updates without performing them. Generally, you don't
+        * want or need to call this. Unit tests need it though.
+        */
+       public static function clearPendingUpdates() {
+               global $wgDeferredUpdateList;
+               $wgDeferredUpdateList = self::$updates = array();
+       }
+}
diff --git a/includes/deferred/HTMLCacheUpdate.php b/includes/deferred/HTMLCacheUpdate.php
new file mode 100644 (file)
index 0000000..4147424
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+/**
+ * HTML cache invalidation of all pages linking to a given title.
+ *
+ * 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 Cache
+ */
+
+/**
+ * Class to invalidate the HTML cache of all the pages linking to a given title.
+ *
+ * @ingroup Cache
+ */
+class HTMLCacheUpdate implements DeferrableUpdate {
+       /**
+        * @var Title
+        */
+       public $mTitle;
+
+       public $mTable;
+
+       /**
+        * @param $titleTo
+        * @param $table
+        */
+       function __construct( Title $titleTo, $table ) {
+               $this->mTitle = $titleTo;
+               $this->mTable = $table;
+       }
+
+       public function doUpdate() {
+               wfProfileIn( __METHOD__ );
+
+               $job = new HTMLCacheUpdateJob(
+                       $this->mTitle,
+                       array(
+                               'table' => $this->mTable,
+                       ) + Job::newRootJobParams( // "overall" refresh links job info
+                               "htmlCacheUpdate:{$this->mTable}:{$this->mTitle->getPrefixedText()}"
+                       )
+               );
+
+               $count = $this->mTitle->getBacklinkCache()->getNumLinks( $this->mTable, 200 );
+               if ( $count >= 200 ) { // many backlinks
+                       JobQueueGroup::singleton()->push( $job );
+                       JobQueueGroup::singleton()->deduplicateRootJob( $job );
+               } else { // few backlinks ($count might be off even if 0)
+                       $dbw = wfGetDB( DB_MASTER );
+                       $dbw->onTransactionIdle( function() use ( $job ) {
+                               $job->run(); // just do the purge query now
+                       } );
+               }
+
+               wfProfileOut( __METHOD__ );
+       }
+}
diff --git a/includes/deferred/LinksUpdate.php b/includes/deferred/LinksUpdate.php
new file mode 100644 (file)
index 0000000..fdd0e3c
--- /dev/null
@@ -0,0 +1,892 @@
+<?php
+/**
+ * Updater for link tracking tables after a page edit.
+ *
+ * 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
+ */
+
+/**
+ * See docs/deferred.txt
+ *
+ * @todo document (e.g. one-sentence top-level class description).
+ */
+class LinksUpdate extends SqlDataUpdate {
+
+       // @todo make members protected, but make sure extensions don't break
+
+       public $mId,         //!< Page ID of the article linked from
+               $mTitle,         //!< Title object of the article linked from
+               $mParserOutput,  //!< Parser output
+               $mLinks,         //!< Map of title strings to IDs for the links in the document
+               $mImages,        //!< DB keys of the images used, in the array key only
+               $mTemplates,     //!< Map of title strings to IDs for the template references, including broken ones
+               $mExternals,     //!< URLs of external links, array key only
+               $mCategories,    //!< Map of category names to sort keys
+               $mInterlangs,    //!< Map of language codes to titles
+               $mProperties,    //!< Map of arbitrary name to value
+               $mDb,            //!< Database connection reference
+               $mOptions,       //!< SELECT options to be used (array)
+               $mRecursive;     //!< Whether to queue jobs for recursive updates
+
+       /**
+        * @var null|array Added links if calculated.
+        */
+       private $linkInsertions = null;
+
+       /**
+        * @var null|array Deleted links if calculated.
+        */
+       private $linkDeletions = null;
+
+       /**
+        * Constructor
+        *
+        * @param $title Title of the page we're updating
+        * @param $parserOutput ParserOutput: output from a full parse of this page
+        * @param $recursive Boolean: queue jobs for recursive updates?
+        * @throws MWException
+        */
+       function __construct( $title, $parserOutput, $recursive = true ) {
+               parent::__construct( false ); // no implicit transaction
+
+               if ( !( $title instanceof Title ) ) {
+                       throw new MWException( "The calling convention to LinksUpdate::LinksUpdate() has changed. " .
+                               "Please see Article::editUpdates() for an invocation example.\n" );
+               }
+
+               if ( !( $parserOutput instanceof ParserOutput ) ) {
+                       throw new MWException( "The calling convention to LinksUpdate::__construct() has changed. " .
+                               "Please see WikiPage::doEditUpdates() for an invocation example.\n" );
+               }
+
+               $this->mTitle = $title;
+               $this->mId = $title->getArticleID();
+
+               if ( !$this->mId ) {
+                       throw new MWException( "The Title object did not provide an article ID. Perhaps the page doesn't exist?" );
+               }
+
+               $this->mParserOutput = $parserOutput;
+
+               $this->mLinks = $parserOutput->getLinks();
+               $this->mImages = $parserOutput->getImages();
+               $this->mTemplates = $parserOutput->getTemplates();
+               $this->mExternals = $parserOutput->getExternalLinks();
+               $this->mCategories = $parserOutput->getCategories();
+               $this->mProperties = $parserOutput->getProperties();
+               $this->mInterwikis = $parserOutput->getInterwikiLinks();
+
+               # Convert the format of the interlanguage links
+               # I didn't want to change it in the ParserOutput, because that array is passed all
+               # the way back to the skin, so either a skin API break would be required, or an
+               # inefficient back-conversion.
+               $ill = $parserOutput->getLanguageLinks();
+               $this->mInterlangs = array();
+               foreach ( $ill as $link ) {
+                       list( $key, $title ) = explode( ':', $link, 2 );
+                       $this->mInterlangs[$key] = $title;
+               }
+
+               foreach ( $this->mCategories as &$sortkey ) {
+                       # If the sortkey is longer then 255 bytes,
+                       # it truncated by DB, and then doesn't get
+                       # matched when comparing existing vs current
+                       # categories, causing bug 25254.
+                       # Also. substr behaves weird when given "".
+                       if ( $sortkey !== '' ) {
+                               $sortkey = substr( $sortkey, 0, 255 );
+                       }
+               }
+
+               $this->mRecursive = $recursive;
+
+               wfRunHooks( 'LinksUpdateConstructed', array( &$this ) );
+       }
+
+       /**
+        * Update link tables with outgoing links from an updated article
+        */
+       public function doUpdate() {
+               wfRunHooks( 'LinksUpdate', array( &$this ) );
+               $this->doIncrementalUpdate();
+               wfRunHooks( 'LinksUpdateComplete', array( &$this ) );
+       }
+
+       protected function doIncrementalUpdate() {
+               wfProfileIn( __METHOD__ );
+
+               # Page links
+               $existing = $this->getExistingLinks();
+               $this->linkDeletions = $this->getLinkDeletions( $existing );
+               $this->linkInsertions = $this->getLinkInsertions( $existing );
+               $this->incrTableUpdate( 'pagelinks', 'pl', $this->linkDeletions, $this->linkInsertions );
+
+               # Image links
+               $existing = $this->getExistingImages();
+
+               $imageDeletes = $this->getImageDeletions( $existing );
+               $this->incrTableUpdate( 'imagelinks', 'il', $imageDeletes,
+                       $this->getImageInsertions( $existing ) );
+
+               # Invalidate all image description pages which had links added or removed
+               $imageUpdates = $imageDeletes + array_diff_key( $this->mImages, $existing );
+               $this->invalidateImageDescriptions( $imageUpdates );
+
+               # External links
+               $existing = $this->getExistingExternals();
+               $this->incrTableUpdate( 'externallinks', 'el', $this->getExternalDeletions( $existing ),
+                       $this->getExternalInsertions( $existing ) );
+
+               # Language links
+               $existing = $this->getExistingInterlangs();
+               $this->incrTableUpdate( 'langlinks', 'll', $this->getInterlangDeletions( $existing ),
+                       $this->getInterlangInsertions( $existing ) );
+
+               # Inline interwiki links
+               $existing = $this->getExistingInterwikis();
+               $this->incrTableUpdate( 'iwlinks', 'iwl', $this->getInterwikiDeletions( $existing ),
+                       $this->getInterwikiInsertions( $existing ) );
+
+               # Template links
+               $existing = $this->getExistingTemplates();
+               $this->incrTableUpdate( 'templatelinks', 'tl', $this->getTemplateDeletions( $existing ),
+                       $this->getTemplateInsertions( $existing ) );
+
+               # Category links
+               $existing = $this->getExistingCategories();
+
+               $categoryDeletes = $this->getCategoryDeletions( $existing );
+
+               $this->incrTableUpdate( 'categorylinks', 'cl', $categoryDeletes,
+                       $this->getCategoryInsertions( $existing ) );
+
+               # Invalidate all categories which were added, deleted or changed (set symmetric difference)
+               $categoryInserts = array_diff_assoc( $this->mCategories, $existing );
+               $categoryUpdates = $categoryInserts + $categoryDeletes;
+               $this->invalidateCategories( $categoryUpdates );
+               $this->updateCategoryCounts( $categoryInserts, $categoryDeletes );
+
+               # Page properties
+               $existing = $this->getExistingProperties();
+
+               $propertiesDeletes = $this->getPropertyDeletions( $existing );
+
+               $this->incrTableUpdate( 'page_props', 'pp', $propertiesDeletes,
+                       $this->getPropertyInsertions( $existing ) );
+
+               # Invalidate the necessary pages
+               $changed = $propertiesDeletes + array_diff_assoc( $this->mProperties, $existing );
+               $this->invalidateProperties( $changed );
+
+               # Refresh links of all pages including this page
+               # This will be in a separate transaction
+               if ( $this->mRecursive ) {
+                       $this->queueRecursiveJobs();
+               }
+
+               wfProfileOut( __METHOD__ );
+       }
+
+       /**
+        * Queue recursive jobs for this page
+        *
+        * Which means do LinksUpdate on all templates
+        * that include the current page, using the job queue.
+        */
+       function queueRecursiveJobs() {
+               self::queueRecursiveJobsForTable( $this->mTitle, 'templatelinks' );
+       }
+
+       /**
+        * Queue a RefreshLinks job for any table.
+        *
+        * @param Title $title Title to do job for
+        * @param String $table Table to use (e.g. 'templatelinks')
+        */
+       public static function queueRecursiveJobsForTable( Title $title, $table ) {
+               wfProfileIn( __METHOD__ );
+               if ( $title->getBacklinkCache()->hasLinks( $table ) ) {
+                       $job = new RefreshLinksJob2(
+                               $title,
+                               array(
+                                       'table' => $table,
+                               ) + Job::newRootJobParams( // "overall" refresh links job info
+                                       "refreshlinks:{$table}:{$title->getPrefixedText()}"
+                               )
+                       );
+                       JobQueueGroup::singleton()->push( $job );
+                       JobQueueGroup::singleton()->deduplicateRootJob( $job );
+               }
+               wfProfileOut( __METHOD__ );
+       }
+
+       /**
+        * @param $cats
+        */
+       function invalidateCategories( $cats ) {
+               $this->invalidatePages( NS_CATEGORY, array_keys( $cats ) );
+       }
+
+       /**
+        * Update all the appropriate counts in the category table.
+        * @param array $added associative array of category name => sort key
+        * @param array $deleted associative array of category name => sort key
+        */
+       function updateCategoryCounts( $added, $deleted ) {
+               $a = WikiPage::factory( $this->mTitle );
+               $a->updateCategoryCounts(
+                       array_keys( $added ), array_keys( $deleted )
+               );
+       }
+
+       /**
+        * @param $images
+        */
+       function invalidateImageDescriptions( $images ) {
+               $this->invalidatePages( NS_FILE, array_keys( $images ) );
+       }
+
+       /**
+        * Update a table by doing a delete query then an insert query
+        * @param $table
+        * @param $prefix
+        * @param $deletions
+        * @param $insertions
+        */
+       function incrTableUpdate( $table, $prefix, $deletions, $insertions ) {
+               if ( $table == 'page_props' ) {
+                       $fromField = 'pp_page';
+               } else {
+                       $fromField = "{$prefix}_from";
+               }
+               $where = array( $fromField => $this->mId );
+               if ( $table == 'pagelinks' || $table == 'templatelinks' || $table == 'iwlinks' ) {
+                       if ( $table == 'iwlinks' ) {
+                               $baseKey = 'iwl_prefix';
+                       } else {
+                               $baseKey = "{$prefix}_namespace";
+                       }
+                       $clause = $this->mDb->makeWhereFrom2d( $deletions, $baseKey, "{$prefix}_title" );
+                       if ( $clause ) {
+                               $where[] = $clause;
+                       } else {
+                               $where = false;
+                       }
+               } else {
+                       if ( $table == 'langlinks' ) {
+                               $toField = 'll_lang';
+                       } elseif ( $table == 'page_props' ) {
+                               $toField = 'pp_propname';
+                       } else {
+                               $toField = $prefix . '_to';
+                       }
+                       if ( count( $deletions ) ) {
+                               $where[] = "$toField IN (" . $this->mDb->makeList( array_keys( $deletions ) ) . ')';
+                       } else {
+                               $where = false;
+                       }
+               }
+               if ( $where ) {
+                       $this->mDb->delete( $table, $where, __METHOD__ );
+               }
+               if ( count( $insertions ) ) {
+                       $this->mDb->insert( $table, $insertions, __METHOD__, 'IGNORE' );
+                       wfRunHooks( 'LinksUpdateAfterInsert', array( $this, $table, $insertions ) );
+               }
+       }
+
+       /**
+        * Get an array of pagelinks insertions for passing to the DB
+        * Skips the titles specified by the 2-D array $existing
+        * @param $existing array
+        * @return array
+        */
+       private function getLinkInsertions( $existing = array() ) {
+               $arr = array();
+               foreach ( $this->mLinks as $ns => $dbkeys ) {
+                       $diffs = isset( $existing[$ns] )
+                               ? array_diff_key( $dbkeys, $existing[$ns] )
+                               : $dbkeys;
+                       foreach ( $diffs as $dbk => $id ) {
+                               $arr[] = array(
+                                       'pl_from' => $this->mId,
+                                       'pl_namespace' => $ns,
+                                       'pl_title' => $dbk
+                               );
+                       }
+               }
+               return $arr;
+       }
+
+       /**
+        * Get an array of template insertions. Like getLinkInsertions()
+        * @param $existing array
+        * @return array
+        */
+       private function getTemplateInsertions( $existing = array() ) {
+               $arr = array();
+               foreach ( $this->mTemplates as $ns => $dbkeys ) {
+                       $diffs = isset( $existing[$ns] ) ? array_diff_key( $dbkeys, $existing[$ns] ) : $dbkeys;
+                       foreach ( $diffs as $dbk => $id ) {
+                               $arr[] = array(
+                                       'tl_from' => $this->mId,
+                                       'tl_namespace' => $ns,
+                                       'tl_title' => $dbk
+                               );
+                       }
+               }
+               return $arr;
+       }
+
+       /**
+        * Get an array of image insertions
+        * Skips the names specified in $existing
+        * @param $existing array
+        * @return array
+        */
+       private function getImageInsertions( $existing = array() ) {
+               $arr = array();
+               $diffs = array_diff_key( $this->mImages, $existing );
+               foreach ( $diffs as $iname => $dummy ) {
+                       $arr[] = array(
+                               'il_from' => $this->mId,
+                               'il_to' => $iname
+                       );
+               }
+               return $arr;
+       }
+
+       /**
+        * Get an array of externallinks insertions. Skips the names specified in $existing
+        * @param $existing array
+        * @return array
+        */
+       private function getExternalInsertions( $existing = array() ) {
+               $arr = array();
+               $diffs = array_diff_key( $this->mExternals, $existing );
+               foreach ( $diffs as $url => $dummy ) {
+                       foreach ( wfMakeUrlIndexes( $url ) as $index ) {
+                               $arr[] = array(
+                                       'el_from' => $this->mId,
+                                       'el_to' => $url,
+                                       'el_index' => $index,
+                               );
+                       }
+               }
+               return $arr;
+       }
+
+       /**
+        * Get an array of category insertions
+        *
+        * @param array $existing mapping existing category names to sort keys. If both
+        * match a link in $this, the link will be omitted from the output
+        *
+        * @return array
+        */
+       private function getCategoryInsertions( $existing = array() ) {
+               global $wgContLang, $wgCategoryCollation;
+               $diffs = array_diff_assoc( $this->mCategories, $existing );
+               $arr = array();
+               foreach ( $diffs as $name => $prefix ) {
+                       $nt = Title::makeTitleSafe( NS_CATEGORY, $name );
+                       $wgContLang->findVariantLink( $name, $nt, true );
+
+                       if ( $this->mTitle->getNamespace() == NS_CATEGORY ) {
+                               $type = 'subcat';
+                       } elseif ( $this->mTitle->getNamespace() == NS_FILE ) {
+                               $type = 'file';
+                       } else {
+                               $type = 'page';
+                       }
+
+                       # Treat custom sortkeys as a prefix, so that if multiple
+                       # things are forced to sort as '*' or something, they'll
+                       # sort properly in the category rather than in page_id
+                       # order or such.
+                       $sortkey = Collation::singleton()->getSortKey(
+                               $this->mTitle->getCategorySortkey( $prefix ) );
+
+                       $arr[] = array(
+                               'cl_from' => $this->mId,
+                               'cl_to' => $name,
+                               'cl_sortkey' => $sortkey,
+                               'cl_timestamp' => $this->mDb->timestamp(),
+                               'cl_sortkey_prefix' => $prefix,
+                               'cl_collation' => $wgCategoryCollation,
+                               'cl_type' => $type,
+                       );
+               }
+               return $arr;
+       }
+
+       /**
+        * Get an array of interlanguage link insertions
+        *
+        * @param array $existing mapping existing language codes to titles
+        *
+        * @return array
+        */
+       private function getInterlangInsertions( $existing = array() ) {
+               $diffs = array_diff_assoc( $this->mInterlangs, $existing );
+               $arr = array();
+               foreach ( $diffs as $lang => $title ) {
+                       $arr[] = array(
+                               'll_from' => $this->mId,
+                               'll_lang' => $lang,
+                               'll_title' => $title
+                       );
+               }
+               return $arr;
+       }
+
+       /**
+        * Get an array of page property insertions
+        * @param $existing array
+        * @return array
+        */
+       function getPropertyInsertions( $existing = array() ) {
+               $diffs = array_diff_assoc( $this->mProperties, $existing );
+               $arr = array();
+               foreach ( $diffs as $name => $value ) {
+                       $arr[] = array(
+                               'pp_page' => $this->mId,
+                               'pp_propname' => $name,
+                               'pp_value' => $value,
+                       );
+               }
+               return $arr;
+       }
+
+       /**
+        * Get an array of interwiki insertions for passing to the DB
+        * Skips the titles specified by the 2-D array $existing
+        * @param $existing array
+        * @return array
+        */
+       private function getInterwikiInsertions( $existing = array() ) {
+               $arr = array();
+               foreach ( $this->mInterwikis as $prefix => $dbkeys ) {
+                       $diffs = isset( $existing[$prefix] ) ? array_diff_key( $dbkeys, $existing[$prefix] ) : $dbkeys;
+                       foreach ( $diffs as $dbk => $id ) {
+                               $arr[] = array(
+                                       'iwl_from' => $this->mId,
+                                       'iwl_prefix' => $prefix,
+                                       'iwl_title' => $dbk
+                               );
+                       }
+               }
+               return $arr;
+       }
+
+       /**
+        * Given an array of existing links, returns those links which are not in $this
+        * and thus should be deleted.
+        * @param $existing array
+        * @return array
+        */
+       private function getLinkDeletions( $existing ) {
+               $del = array();
+               foreach ( $existing as $ns => $dbkeys ) {
+                       if ( isset( $this->mLinks[$ns] ) ) {
+                               $del[$ns] = array_diff_key( $existing[$ns], $this->mLinks[$ns] );
+                       } else {
+                               $del[$ns] = $existing[$ns];
+                       }
+               }
+               return $del;
+       }
+
+       /**
+        * Given an array of existing templates, returns those templates which are not in $this
+        * and thus should be deleted.
+        * @param $existing array
+        * @return array
+        */
+       private function getTemplateDeletions( $existing ) {
+               $del = array();
+               foreach ( $existing as $ns => $dbkeys ) {
+                       if ( isset( $this->mTemplates[$ns] ) ) {
+                               $del[$ns] = array_diff_key( $existing[$ns], $this->mTemplates[$ns] );
+                       } else {
+                               $del[$ns] = $existing[$ns];
+                       }
+               }
+               return $del;
+       }
+
+       /**
+        * Given an array of existing images, returns those images which are not in $this
+        * and thus should be deleted.
+        * @param $existing array
+        * @return array
+        */
+       private function getImageDeletions( $existing ) {
+               return array_diff_key( $existing, $this->mImages );
+       }
+
+       /**
+        * Given an array of existing external links, returns those links which are not
+        * in $this and thus should be deleted.
+        * @param $existing array
+        * @return array
+        */
+       private function getExternalDeletions( $existing ) {
+               return array_diff_key( $existing, $this->mExternals );
+       }
+
+       /**
+        * Given an array of existing categories, returns those categories which are not in $this
+        * and thus should be deleted.
+        * @param $existing array
+        * @return array
+        */
+       private function getCategoryDeletions( $existing ) {
+               return array_diff_assoc( $existing, $this->mCategories );
+       }
+
+       /**
+        * Given an array of existing interlanguage links, returns those links which are not
+        * in $this and thus should be deleted.
+        * @param $existing array
+        * @return array
+        */
+       private function getInterlangDeletions( $existing ) {
+               return array_diff_assoc( $existing, $this->mInterlangs );
+       }
+
+       /**
+        * Get array of properties which should be deleted.
+        * @param $existing array
+        * @return array
+        */
+       function getPropertyDeletions( $existing ) {
+               return array_diff_assoc( $existing, $this->mProperties );
+       }
+
+       /**
+        * Given an array of existing interwiki links, returns those links which are not in $this
+        * and thus should be deleted.
+        * @param $existing array
+        * @return array
+        */
+       private function getInterwikiDeletions( $existing ) {
+               $del = array();
+               foreach ( $existing as $prefix => $dbkeys ) {
+                       if ( isset( $this->mInterwikis[$prefix] ) ) {
+                               $del[$prefix] = array_diff_key( $existing[$prefix], $this->mInterwikis[$prefix] );
+                       } else {
+                               $del[$prefix] = $existing[$prefix];
+                       }
+               }
+               return $del;
+       }
+
+       /**
+        * Get an array of existing links, as a 2-D array
+        *
+        * @return array
+        */
+       private function getExistingLinks() {
+               $res = $this->mDb->select( 'pagelinks', array( 'pl_namespace', 'pl_title' ),
+                       array( 'pl_from' => $this->mId ), __METHOD__, $this->mOptions );
+               $arr = array();
+               foreach ( $res as $row ) {
+                       if ( !isset( $arr[$row->pl_namespace] ) ) {
+                               $arr[$row->pl_namespace] = array();
+                       }
+                       $arr[$row->pl_namespace][$row->pl_title] = 1;
+               }
+               return $arr;
+       }
+
+       /**
+        * Get an array of existing templates, as a 2-D array
+        *
+        * @return array
+        */
+       private function getExistingTemplates() {
+               $res = $this->mDb->select( 'templatelinks', array( 'tl_namespace', 'tl_title' ),
+                       array( 'tl_from' => $this->mId ), __METHOD__, $this->mOptions );
+               $arr = array();
+               foreach ( $res as $row ) {
+                       if ( !isset( $arr[$row->tl_namespace] ) ) {
+                               $arr[$row->tl_namespace] = array();
+                       }
+                       $arr[$row->tl_namespace][$row->tl_title] = 1;
+               }
+               return $arr;
+       }
+
+       /**
+        * Get an array of existing images, image names in the keys
+        *
+        * @return array
+        */
+       private function getExistingImages() {
+               $res = $this->mDb->select( 'imagelinks', array( 'il_to' ),
+                       array( 'il_from' => $this->mId ), __METHOD__, $this->mOptions );
+               $arr = array();
+               foreach ( $res as $row ) {
+                       $arr[$row->il_to] = 1;
+               }
+               return $arr;
+       }
+
+       /**
+        * Get an array of existing external links, URLs in the keys
+        *
+        * @return array
+        */
+       private function getExistingExternals() {
+               $res = $this->mDb->select( 'externallinks', array( 'el_to' ),
+                       array( 'el_from' => $this->mId ), __METHOD__, $this->mOptions );
+               $arr = array();
+               foreach ( $res as $row ) {
+                       $arr[$row->el_to] = 1;
+               }
+               return $arr;
+       }
+
+       /**
+        * Get an array of existing categories, with the name in the key and sort key in the value.
+        *
+        * @return array
+        */
+       private function getExistingCategories() {
+               $res = $this->mDb->select( 'categorylinks', array( 'cl_to', 'cl_sortkey_prefix' ),
+                       array( 'cl_from' => $this->mId ), __METHOD__, $this->mOptions );
+               $arr = array();
+               foreach ( $res as $row ) {
+                       $arr[$row->cl_to] = $row->cl_sortkey_prefix;
+               }
+               return $arr;
+       }
+
+       /**
+        * Get an array of existing interlanguage links, with the language code in the key and the
+        * title in the value.
+        *
+        * @return array
+        */
+       private function getExistingInterlangs() {
+               $res = $this->mDb->select( 'langlinks', array( 'll_lang', 'll_title' ),
+                       array( 'll_from' => $this->mId ), __METHOD__, $this->mOptions );
+               $arr = array();
+               foreach ( $res as $row ) {
+                       $arr[$row->ll_lang] = $row->ll_title;
+               }
+               return $arr;
+       }
+
+       /**
+        * Get an array of existing inline interwiki links, as a 2-D array
+        * @return array (prefix => array(dbkey => 1))
+        */
+       protected function getExistingInterwikis() {
+               $res = $this->mDb->select( 'iwlinks', array( 'iwl_prefix', 'iwl_title' ),
+                       array( 'iwl_from' => $this->mId ), __METHOD__, $this->mOptions );
+               $arr = array();
+               foreach ( $res as $row ) {
+                       if ( !isset( $arr[$row->iwl_prefix] ) ) {
+                               $arr[$row->iwl_prefix] = array();
+                       }
+                       $arr[$row->iwl_prefix][$row->iwl_title] = 1;
+               }
+               return $arr;
+       }
+
+       /**
+        * Get an array of existing categories, with the name in the key and sort key in the value.
+        *
+        * @return array
+        */
+       private function getExistingProperties() {
+               $res = $this->mDb->select( 'page_props', array( 'pp_propname', 'pp_value' ),
+                       array( 'pp_page' => $this->mId ), __METHOD__, $this->mOptions );
+               $arr = array();
+               foreach ( $res as $row ) {
+                       $arr[$row->pp_propname] = $row->pp_value;
+               }
+               return $arr;
+       }
+
+       /**
+        * Return the title object of the page being updated
+        * @return Title
+        */
+       public function getTitle() {
+               return $this->mTitle;
+       }
+
+       /**
+        * Returns parser output
+        * @since 1.19
+        * @return ParserOutput
+        */
+       public function getParserOutput() {
+               return $this->mParserOutput;
+       }
+
+       /**
+        * Return the list of images used as generated by the parser
+        * @return array
+        */
+       public function getImages() {
+               return $this->mImages;
+       }
+
+       /**
+        * Invalidate any necessary link lists related to page property changes
+        * @param $changed
+        */
+       private function invalidateProperties( $changed ) {
+               global $wgPagePropLinkInvalidations;
+
+               foreach ( $changed as $name => $value ) {
+                       if ( isset( $wgPagePropLinkInvalidations[$name] ) ) {
+                               $inv = $wgPagePropLinkInvalidations[$name];
+                               if ( !is_array( $inv ) ) {
+                                       $inv = array( $inv );
+                               }
+                               foreach ( $inv as $table ) {
+                                       $update = new HTMLCacheUpdate( $this->mTitle, $table );
+                                       $update->doUpdate();
+                               }
+                       }
+               }
+       }
+
+       /**
+        * Fetch page links added by this LinksUpdate.  Only available after the update is complete.
+        * @since 1.22
+        * @return null|array of Titles
+        */
+       public function getAddedLinks() {
+               if ( $this->linkInsertions === null ) {
+                       return null;
+               }
+               $result = array();
+               foreach ( $this->linkInsertions as $insertion ) {
+                       $result[] = Title::makeTitle( $insertion[ 'pl_namespace' ], $insertion[ 'pl_title' ] );
+               }
+               return $result;
+       }
+
+       /**
+        * Fetch page links removed by this LinksUpdate.  Only available after the update is complete.
+        * @since 1.22
+        * @return null|array of Titles
+        */
+       public function getRemovedLinks() {
+               if ( $this->linkDeletions === null ) {
+                       return null;
+               }
+               $result = array();
+               foreach ( $this->linkDeletions as $ns => $titles ) {
+                       foreach ( $titles as $title => $unused ) {
+                               $result[] = Title::makeTitle( $ns, $title );
+                       }
+               }
+               return $result;
+       }
+}
+
+/**
+ * Update object handling the cleanup of links tables after a page was deleted.
+ **/
+class LinksDeletionUpdate extends SqlDataUpdate {
+
+       protected $mPage;     //!< WikiPage the wikipage that was deleted
+
+       /**
+        * Constructor
+        *
+        * @param $page WikiPage Page we are updating
+        * @throws MWException
+        */
+       function __construct( WikiPage $page ) {
+               parent::__construct( false ); // no implicit transaction
+
+               $this->mPage = $page;
+
+               if ( !$page->exists() ) {
+                       throw new MWException( "Page ID not known, perhaps the page doesn't exist?" );
+               }
+       }
+
+       /**
+        * Do some database updates after deletion
+        */
+       public function doUpdate() {
+               $title = $this->mPage->getTitle();
+               $id = $this->mPage->getId();
+
+               # Delete restrictions for it
+               $this->mDb->delete( 'page_restrictions', array( 'pr_page' => $id ), __METHOD__ );
+
+               # Fix category table counts
+               $cats = array();
+               $res = $this->mDb->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ );
+
+               foreach ( $res as $row ) {
+                       $cats[] = $row->cl_to;
+               }
+
+               $this->mPage->updateCategoryCounts( array(), $cats );
+
+               # If using cascading deletes, we can skip some explicit deletes
+               if ( !$this->mDb->cascadingDeletes() ) {
+                       # Delete outgoing links
+                       $this->mDb->delete( 'pagelinks', array( 'pl_from' => $id ), __METHOD__ );
+                       $this->mDb->delete( 'imagelinks', array( 'il_from' => $id ), __METHOD__ );
+                       $this->mDb->delete( 'categorylinks', array( 'cl_from' => $id ), __METHOD__ );
+                       $this->mDb->delete( 'templatelinks', array( 'tl_from' => $id ), __METHOD__ );
+                       $this->mDb->delete( 'externallinks', array( 'el_from' => $id ), __METHOD__ );
+                       $this->mDb->delete( 'langlinks', array( 'll_from' => $id ), __METHOD__ );
+                       $this->mDb->delete( 'iwlinks', array( 'iwl_from' => $id ), __METHOD__ );
+                       $this->mDb->delete( 'redirect', array( 'rd_from' => $id ), __METHOD__ );
+                       $this->mDb->delete( 'page_props', array( 'pp_page' => $id ), __METHOD__ );
+               }
+
+               # If using cleanup triggers, we can skip some manual deletes
+               if ( !$this->mDb->cleanupTriggers() ) {
+                       # Clean up recentchanges entries...
+                       $this->mDb->delete( 'recentchanges',
+                               array( 'rc_type != ' . RC_LOG,
+                                       'rc_namespace' => $title->getNamespace(),
+                                       'rc_title' => $title->getDBkey() ),
+                               __METHOD__ );
+                       $this->mDb->delete( 'recentchanges',
+                               array( 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ),
+                               __METHOD__ );
+               }
+       }
+
+       /**
+        * Update all the appropriate counts in the category table.
+        * @param array $added associative array of category name => sort key
+        * @param array $deleted associative array of category name => sort key
+        */
+       function updateCategoryCounts( $added, $deleted ) {
+               $a = WikiPage::factory( $this->mTitle );
+               $a->updateCategoryCounts(
+                       array_keys( $added ), array_keys( $deleted )
+               );
+       }
+}
diff --git a/includes/deferred/SearchUpdate.php b/includes/deferred/SearchUpdate.php
new file mode 100644 (file)
index 0000000..82a413e
--- /dev/null
@@ -0,0 +1,185 @@
+<?php
+/**
+ * Search index updater
+ *
+ * See deferred.txt
+ *
+ * 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 Search
+ */
+
+/**
+ * Database independant search index updater
+ *
+ * @ingroup Search
+ */
+class SearchUpdate implements DeferrableUpdate {
+       /**
+        * Page id being updated
+        * @var int
+        */
+       private $id = 0;
+
+       /**
+        * Title we're updating
+        * @var Title
+        */
+       private $title;
+
+       /**
+        * Content of the page (not text)
+        * @var Content|false
+        */
+       private $content;
+
+       /**
+        * Constructor
+        *
+        * @param int $id Page id to update
+        * @param Title|string $title Title of page to update
+        * @param Content|string|false $c Content of the page to update.
+        *  If a Content object, text will be gotten from it. String is for back-compat.
+        *  Passing false tells the backend to just update the title, not the content
+        */
+       public function __construct( $id, $title, $c = false ) {
+               if ( is_string( $title ) ) {
+                       $nt = Title::newFromText( $title );
+               } else {
+                       $nt = $title;
+               }
+
+               if ( $nt ) {
+                       $this->id = $id;
+                       // is_string() check is back-compat for ApprovedRevs
+                       if ( is_string( $c ) ) {
+                               $this->content = new TextContent( $c );
+                       } else {
+                               $this->content = $c ?: false;
+                       }
+                       $this->title = $nt;
+               } else {
+                       wfDebug( "SearchUpdate object created with invalid title '$title'\n" );
+               }
+       }
+
+       /**
+        * Perform actual update for the entry
+        */
+       public function doUpdate() {
+               global $wgDisableSearchUpdate;
+
+               if ( $wgDisableSearchUpdate || !$this->id ) {
+                       return;
+               }
+
+               wfProfileIn( __METHOD__ );
+
+               $page = WikiPage::newFromId( $this->id, WikiPage::READ_LATEST );
+               $indexTitle = Title::indexTitle( $this->title->getNamespace(), $this->title->getText() );
+
+               foreach ( SearchEngine::getSearchTypes() as $type ) {
+                       $search = SearchEngine::create( $type );
+                       if ( !$search->supports( 'search-update' ) ) {
+                               continue;
+                       }
+
+                       $normalTitle = $search->normalizeText( $indexTitle );
+
+                       if ( $page === null ) {
+                               $search->delete( $this->id, $normalTitle );
+                               continue;
+                       } elseif ( $this->content === false ) {
+                               $search->updateTitle( $this->id, $normalTitle );
+                               continue;
+                       }
+
+                       $text = $search->getTextFromContent( $this->title, $this->content );
+                       if ( !$search->textAlreadyUpdatedForIndex() ) {
+                               $text = self::updateText( $text );
+                       }
+
+                       # Perform the actual update
+                       $search->update( $this->id, $normalTitle, $search->normalizeText( $text ) );
+               }
+
+               wfProfileOut( __METHOD__ );
+       }
+
+       /**
+        * Clean text for indexing. Only really suitable for indexing in databases.
+        * If you're using a real search engine, you'll probably want to override
+        * this behavior and do something nicer with the original wikitext.
+        */
+       public static function updateText( $text ) {
+               global $wgContLang;
+
+               # Language-specific strip/conversion
+               $text = $wgContLang->normalizeForSearch( $text );
+               $lc = SearchEngine::legalSearchChars() . '&#;';
+
+               wfProfileIn( __METHOD__ . '-regexps' );
+               $text = preg_replace( "/<\\/?\\s*[A-Za-z][^>]*?>/",
+                       ' ', $wgContLang->lc( " " . $text . " " ) ); # Strip HTML markup
+               $text = preg_replace( "/(^|\\n)==\\s*([^\\n]+)\\s*==(\\s)/sD",
+                       "\\1\\2 \\2 \\2\\3", $text ); # Emphasize headings
+
+               # Strip external URLs
+               $uc = "A-Za-z0-9_\\/:.,~%\\-+&;#?!=()@\\x80-\\xFF";
+               $protos = "http|https|ftp|mailto|news|gopher";
+               $pat = "/(^|[^\\[])({$protos}):[{$uc}]+([^{$uc}]|$)/";
+               $text = preg_replace( $pat, "\\1 \\3", $text );
+
+               $p1 = "/([^\\[])\\[({$protos}):[{$uc}]+]/";
+               $p2 = "/([^\\[])\\[({$protos}):[{$uc}]+\\s+([^\\]]+)]/";
+               $text = preg_replace( $p1, "\\1 ", $text );
+               $text = preg_replace( $p2, "\\1 \\3 ", $text );
+
+               # Internal image links
+               $pat2 = "/\\[\\[image:([{$uc}]+)\\.(gif|png|jpg|jpeg)([^{$uc}])/i";
+               $text = preg_replace( $pat2, " \\1 \\3", $text );
+
+               $text = preg_replace( "/([^{$lc}])([{$lc}]+)]]([a-z]+)/",
+                       "\\1\\2 \\2\\3", $text ); # Handle [[game]]s
+
+               # Strip all remaining non-search characters
+               $text = preg_replace( "/[^{$lc}]+/", " ", $text );
+
+               # Handle 's, s'
+               #
+               #   $text = preg_replace( "/([{$lc}]+)'s /", "\\1 \\1's ", $text );
+               #   $text = preg_replace( "/([{$lc}]+)s' /", "\\1s ", $text );
+               #
+               # These tail-anchored regexps are insanely slow. The worst case comes
+               # when Japanese or Chinese text (ie, no word spacing) is written on
+               # a wiki configured for Western UTF-8 mode. The Unicode characters are
+               # expanded to hex codes and the "words" are very long paragraph-length
+               # monstrosities. On a large page the above regexps may take over 20
+               # seconds *each* on a 1GHz-level processor.
+               #
+               # Following are reversed versions which are consistently fast
+               # (about 3 milliseconds on 1GHz-level processor).
+               #
+               $text = strrev( preg_replace( "/ s'([{$lc}]+)/", " s'\\1 \\1", strrev( $text ) ) );
+               $text = strrev( preg_replace( "/ 's([{$lc}]+)/", " s\\1", strrev( $text ) ) );
+
+               # Strip wiki '' and '''
+               $text = preg_replace( "/''[']*/", " ", $text );
+               wfProfileOut( __METHOD__ . '-regexps' );
+               return $text;
+       }
+}
diff --git a/includes/deferred/SiteStatsUpdate.php b/includes/deferred/SiteStatsUpdate.php
new file mode 100644 (file)
index 0000000..09ff87d
--- /dev/null
@@ -0,0 +1,245 @@
+<?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
+ */
+
+/**
+ * Class for handling updates to the site_stats table
+ */
+class SiteStatsUpdate implements DeferrableUpdate {
+       protected $views = 0;
+       protected $edits = 0;
+       protected $pages = 0;
+       protected $articles = 0;
+       protected $users = 0;
+       protected $images = 0;
+
+       // @todo deprecate this constructor
+       function __construct( $views, $edits, $good, $pages = 0, $users = 0 ) {
+               $this->views = $views;
+               $this->edits = $edits;
+               $this->articles = $good;
+               $this->pages = $pages;
+               $this->users = $users;
+       }
+
+       /**
+        * @param $deltas Array
+        * @return SiteStatsUpdate
+        */
+       public static function factory( array $deltas ) {
+               $update = new self( 0, 0, 0 );
+
+               $fields = array( 'views', 'edits', 'pages', 'articles', 'users', 'images' );
+               foreach ( $fields as $field ) {
+                       if ( isset( $deltas[$field] ) && $deltas[$field] ) {
+                               $update->$field = $deltas[$field];
+                       }
+               }
+
+               return $update;
+       }
+
+       public function doUpdate() {
+               global $wgSiteStatsAsyncFactor;
+
+               $rate = $wgSiteStatsAsyncFactor; // convenience
+               // If set to do so, only do actual DB updates 1 every $rate times.
+               // The other times, just update "pending delta" values in memcached.
+               if ( $rate && ( $rate < 0 || mt_rand( 0, $rate - 1 ) != 0 ) ) {
+                       $this->doUpdatePendingDeltas();
+               } else {
+                       // Need a separate transaction because this a global lock
+                       wfGetDB( DB_MASTER )->onTransactionIdle( array( $this, 'tryDBUpdateInternal' ) );
+               }
+       }
+
+       /**
+        * Do not call this outside of SiteStatsUpdate
+        *
+        * @return void
+        */
+       public function tryDBUpdateInternal() {
+               global $wgSiteStatsAsyncFactor;
+
+               $dbw = wfGetDB( DB_MASTER );
+               $lockKey = wfMemcKey( 'site_stats' ); // prepend wiki ID
+               if ( $wgSiteStatsAsyncFactor ) {
+                       // Lock the table so we don't have double DB/memcached updates
+                       if ( !$dbw->lockIsFree( $lockKey, __METHOD__ )
+                               || !$dbw->lock( $lockKey, __METHOD__, 1 ) // 1 sec timeout
+                       ) {
+                               $this->doUpdatePendingDeltas();
+                               return;
+                       }
+                       $pd = $this->getPendingDeltas();
+                       // Piggy-back the async deltas onto those of this stats update....
+                       $this->views += ( $pd['ss_total_views']['+'] - $pd['ss_total_views']['-'] );
+                       $this->edits += ( $pd['ss_total_edits']['+'] - $pd['ss_total_edits']['-'] );
+                       $this->articles += ( $pd['ss_good_articles']['+'] - $pd['ss_good_articles']['-'] );
+                       $this->pages += ( $pd['ss_total_pages']['+'] - $pd['ss_total_pages']['-'] );
+                       $this->users += ( $pd['ss_users']['+'] - $pd['ss_users']['-'] );
+                       $this->images += ( $pd['ss_images']['+'] - $pd['ss_images']['-'] );
+               }
+
+               // Build up an SQL query of deltas and apply them...
+               $updates = '';
+               $this->appendUpdate( $updates, 'ss_total_views', $this->views );
+               $this->appendUpdate( $updates, 'ss_total_edits', $this->edits );
+               $this->appendUpdate( $updates, 'ss_good_articles', $this->articles );
+               $this->appendUpdate( $updates, 'ss_total_pages', $this->pages );
+               $this->appendUpdate( $updates, 'ss_users', $this->users );
+               $this->appendUpdate( $updates, 'ss_images', $this->images );
+               if ( $updates != '' ) {
+                       $dbw->update( 'site_stats', array( $updates ), array(), __METHOD__ );
+               }
+
+               if ( $wgSiteStatsAsyncFactor ) {
+                       // Decrement the async deltas now that we applied them
+                       $this->removePendingDeltas( $pd );
+                       // Commit the updates and unlock the table
+                       $dbw->unlock( $lockKey, __METHOD__ );
+               }
+       }
+
+       /**
+        * @param $dbw DatabaseBase
+        * @return bool|mixed
+        */
+       public static function cacheUpdate( $dbw ) {
+               global $wgActiveUserDays;
+               $dbr = wfGetDB( DB_SLAVE, array( 'SpecialStatistics', 'vslow' ) );
+               # Get non-bot users than did some recent action other than making accounts.
+               # If account creation is included, the number gets inflated ~20+ fold on enwiki.
+               $activeUsers = $dbr->selectField(
+                       'recentchanges',
+                       'COUNT( DISTINCT rc_user_text )',
+                       array(
+                               'rc_user != 0',
+                               'rc_bot' => 0,
+                               'rc_log_type != ' . $dbr->addQuotes( 'newusers' ) . ' OR rc_log_type IS NULL',
+                               'rc_timestamp >= ' . $dbr->addQuotes( $dbr->timestamp( wfTimestamp( TS_UNIX ) - $wgActiveUserDays * 24 * 3600 ) ),
+                       ),
+                       __METHOD__
+               );
+               $dbw->update(
+                       'site_stats',
+                       array( 'ss_active_users' => intval( $activeUsers ) ),
+                       array( 'ss_row_id' => 1 ),
+                       __METHOD__
+               );
+               return $activeUsers;
+       }
+
+       protected function doUpdatePendingDeltas() {
+               $this->adjustPending( 'ss_total_views', $this->views );
+               $this->adjustPending( 'ss_total_edits', $this->edits );
+               $this->adjustPending( 'ss_good_articles', $this->articles );
+               $this->adjustPending( 'ss_total_pages', $this->pages );
+               $this->adjustPending( 'ss_users', $this->users );
+               $this->adjustPending( 'ss_images', $this->images );
+       }
+
+       /**
+        * @param $sql string
+        * @param $field string
+        * @param $delta integer
+        */
+       protected function appendUpdate( &$sql, $field, $delta ) {
+               if ( $delta ) {
+                       if ( $sql ) {
+                               $sql .= ',';
+                       }
+                       if ( $delta < 0 ) {
+                               $sql .= "$field=$field-" . abs( $delta );
+                       } else {
+                               $sql .= "$field=$field+" . abs( $delta );
+                       }
+               }
+       }
+
+       /**
+        * @param $type string
+        * @param string $sign ('+' or '-')
+        * @return string
+        */
+       private function getTypeCacheKey( $type, $sign ) {
+               return wfMemcKey( 'sitestatsupdate', 'pendingdelta', $type, $sign );
+       }
+
+       /**
+        * Adjust the pending deltas for a stat type.
+        * Each stat type has two pending counters, one for increments and decrements
+        * @param $type string
+        * @param $delta integer Delta (positive or negative)
+        * @return void
+        */
+       protected function adjustPending( $type, $delta ) {
+               global $wgMemc;
+
+               if ( $delta < 0 ) { // decrement
+                       $key = $this->getTypeCacheKey( $type, '-' );
+               } else { // increment
+                       $key = $this->getTypeCacheKey( $type, '+' );
+               }
+
+               $magnitude = abs( $delta );
+               if ( !$wgMemc->incr( $key, $magnitude ) ) { // not there?
+                       if ( !$wgMemc->add( $key, $magnitude ) ) { // race?
+                               $wgMemc->incr( $key, $magnitude );
+                       }
+               }
+       }
+
+       /**
+        * Get pending delta counters for each stat type
+        * @return Array Positive and negative deltas for each type
+        * @return void
+        */
+       protected function getPendingDeltas() {
+               global $wgMemc;
+
+               $pending = array();
+               foreach ( array( 'ss_total_views', 'ss_total_edits',
+                       'ss_good_articles', 'ss_total_pages', 'ss_users', 'ss_images' ) as $type )
+               {
+                       // Get pending increments and pending decrements
+                       $pending[$type]['+'] = (int)$wgMemc->get( $this->getTypeCacheKey( $type, '+' ) );
+                       $pending[$type]['-'] = (int)$wgMemc->get( $this->getTypeCacheKey( $type, '-' ) );
+               }
+
+               return $pending;
+       }
+
+       /**
+        * Reduce pending delta counters after updates have been applied
+        * @param array $pd Result of getPendingDeltas(), used for DB update
+        * @return void
+        */
+       protected function removePendingDeltas( array $pd ) {
+               global $wgMemc;
+
+               foreach ( $pd as $type => $deltas ) {
+                       foreach ( $deltas as $sign => $magnitude ) {
+                               // Lower the pending counter now that we applied these changes
+                               $wgMemc->decr( $this->getTypeCacheKey( $type, $sign ), $magnitude );
+                       }
+               }
+       }
+}
+
diff --git a/includes/deferred/SqlDataUpdate.php b/includes/deferred/SqlDataUpdate.php
new file mode 100644 (file)
index 0000000..51188d8
--- /dev/null
@@ -0,0 +1,152 @@
+<?php
+/**
+ * Base code for update jobs that put some secondary data extracted
+ * from article content into the database.
+ *
+ * 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
+ */
+
+/**
+ * Abstract base class for update jobs that put some secondary data extracted
+ * from article content into the database.
+ *
+ * @note: subclasses should NOT start or commit transactions in their doUpdate() method,
+ *        a transaction will automatically be wrapped around the update. Starting another
+ *        one would break the outer transaction bracket. If need be, subclasses can override
+ *        the beginTransaction() and commitTransaction() methods.
+ */
+abstract class SqlDataUpdate extends DataUpdate {
+
+       protected $mDb;            //!< Database connection reference
+       protected $mOptions;       //!< SELECT options to be used (array)
+
+       private   $mHasTransaction; //!< bool whether a transaction is open on this object (internal use only!)
+       protected $mUseTransaction; //!< bool whether this update should be wrapped in a transaction
+
+       /**
+        * Constructor
+        *
+        * @param bool $withTransaction whether this update should be wrapped in a transaction (default: true).
+        *             A transaction is only started if no transaction is already in progress,
+        *             see beginTransaction() for details.
+        **/
+       public function __construct( $withTransaction = true ) {
+               global $wgAntiLockFlags;
+
+               parent::__construct();
+
+               if ( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) {
+                       $this->mOptions = array();
+               } else {
+                       $this->mOptions = array( 'FOR UPDATE' );
+               }
+
+               // @todo get connection only when it's needed? make sure that doesn't break anything, especially transactions!
+               $this->mDb = wfGetDB( DB_MASTER );
+
+               $this->mWithTransaction = $withTransaction;
+               $this->mHasTransaction = false;
+       }
+
+       /**
+        * Begin a database transaction, if $withTransaction was given as true in the constructor for this SqlDataUpdate.
+        *
+        * Because nested transactions are not supported by the Database class, this implementation
+        * checks Database::trxLevel() and only opens a transaction if none is already active.
+        */
+       public function beginTransaction() {
+               if ( !$this->mWithTransaction ) {
+                       return;
+               }
+
+               // NOTE: nested transactions are not supported, only start a transaction if none is open
+               if ( $this->mDb->trxLevel() === 0 ) {
+                       $this->mDb->begin( get_class( $this ) . '::beginTransaction' );
+                       $this->mHasTransaction = true;
+               }
+       }
+
+       /**
+        * Commit the database transaction started via beginTransaction (if any).
+        */
+       public function commitTransaction() {
+               if ( $this->mHasTransaction ) {
+                       $this->mDb->commit( get_class( $this ) . '::commitTransaction' );
+                       $this->mHasTransaction = false;
+               }
+       }
+
+       /**
+        * Abort the database transaction started via beginTransaction (if any).
+        */
+       public function abortTransaction() {
+               if ( $this->mHasTransaction ) { //XXX: actually... maybe always?
+                       $this->mDb->rollback( get_class( $this ) . '::abortTransaction' );
+                       $this->mHasTransaction = false;
+               }
+       }
+
+       /**
+        * Invalidate the cache of a list of pages from a single namespace.
+        * This is intended for use by subclasses.
+        *
+        * @param $namespace Integer
+        * @param $dbkeys Array
+        */
+       protected function invalidatePages( $namespace, array $dbkeys ) {
+               if ( $dbkeys === array() ) {
+                       return;
+               }
+
+               /**
+                * Determine which pages need to be updated
+                * This is necessary to prevent the job queue from smashing the DB with
+                * large numbers of concurrent invalidations of the same page
+                */
+               $now = $this->mDb->timestamp();
+               $ids = array();
+               $res = $this->mDb->select( 'page', array( 'page_id' ),
+                       array(
+                               'page_namespace' => $namespace,
+                               'page_title' => $dbkeys,
+                               'page_touched < ' . $this->mDb->addQuotes( $now )
+                       ), __METHOD__
+               );
+
+               foreach ( $res as $row ) {
+                       $ids[] = $row->page_id;
+               }
+
+               if ( $ids === array() ) {
+                       return;
+               }
+
+               /**
+                * Do the update
+                * We still need the page_touched condition, in case the row has changed since
+                * the non-locking select above.
+                */
+               $this->mDb->update( 'page', array( 'page_touched' => $now ),
+                       array(
+                               'page_id' => $ids,
+                               'page_touched < ' . $this->mDb->addQuotes( $now )
+                       ), __METHOD__
+               );
+       }
+
+}
diff --git a/includes/deferred/SquidUpdate.php b/includes/deferred/SquidUpdate.php
new file mode 100644 (file)
index 0000000..71afeba
--- /dev/null
@@ -0,0 +1,300 @@
+<?php
+/**
+ * Squid cache purging.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Cache
+ */
+
+/**
+ * Handles purging appropriate Squid URLs given a title (or titles)
+ * @ingroup Cache
+ */
+class SquidUpdate {
+
+       /**
+        * Collection of URLs to purge.
+        * @var array
+        */
+       protected $urlArr;
+
+       /**
+        * @param array $urlArr Collection of URLs to purge
+        * @param bool|int $maxTitles Maximum number of unique URLs to purge
+        */
+       public function __construct( $urlArr = array(), $maxTitles = false ) {
+               global $wgMaxSquidPurgeTitles;
+               if ( $maxTitles === false ) {
+                       $maxTitles = $wgMaxSquidPurgeTitles;
+               }
+
+               // Remove duplicate URLs from list
+               $urlArr = array_unique( $urlArr );
+               if ( count( $urlArr ) > $maxTitles ) {
+                       // Truncate to desired maximum URL count
+                       $urlArr = array_slice( $urlArr, 0, $maxTitles );
+               }
+               $this->urlArr = $urlArr;
+       }
+
+       /**
+        * Create a SquidUpdate from the given Title object.
+        *
+        * The resulting SquidUpdate will purge the given Title's URLs as well as
+        * the pages that link to it. Capped at $wgMaxSquidPurgeTitles total URLs.
+        *
+        * @param Title $title
+        * @return SquidUpdate
+        */
+       public static function newFromLinksTo( Title $title ) {
+               global $wgMaxSquidPurgeTitles;
+               wfProfileIn( __METHOD__ );
+
+               # Get a list of URLs linking to this page
+               $dbr = wfGetDB( DB_SLAVE );
+               $res = $dbr->select( array( 'links', 'page' ),
+                       array( 'page_namespace', 'page_title' ),
+                       array(
+                               'pl_namespace' => $title->getNamespace(),
+                               'pl_title' => $title->getDBkey(),
+                               'pl_from=page_id' ),
+                       __METHOD__ );
+               $blurlArr = $title->getSquidURLs();
+               if ( $res->numRows() <= $wgMaxSquidPurgeTitles ) {
+                       foreach ( $res as $BL ) {
+                               $tobj = Title::makeTitle( $BL->page_namespace, $BL->page_title );
+                               $blurlArr[] = $tobj->getInternalURL();
+                       }
+               }
+
+               wfProfileOut( __METHOD__ );
+               return new SquidUpdate( $blurlArr );
+       }
+
+       /**
+        * Create a SquidUpdate from an array of Title objects, or a TitleArray object
+        *
+        * @param array $titles
+        * @param array $urlArr
+        * @return SquidUpdate
+        */
+       public static function newFromTitles( $titles, $urlArr = array() ) {
+               global $wgMaxSquidPurgeTitles;
+               $i = 0;
+               foreach ( $titles as $title ) {
+                       $urlArr[] = $title->getInternalURL();
+                       if ( $i++ > $wgMaxSquidPurgeTitles ) {
+                               break;
+                       }
+               }
+               return new SquidUpdate( $urlArr );
+       }
+
+       /**
+        * @param Title $title
+        * @return SquidUpdate
+        */
+       public static function newSimplePurge( Title $title ) {
+               $urlArr = $title->getSquidURLs();
+               return new SquidUpdate( $urlArr );
+       }
+
+       /**
+        * Purges the list of URLs passed to the constructor.
+        */
+       public function doUpdate() {
+               self::purge( $this->urlArr );
+       }
+
+       /**
+        * Purges a list of Squids defined in $wgSquidServers.
+        * $urlArr should contain the full URLs to purge as values
+        * (example: $urlArr[] = 'http://my.host/something')
+        * XXX report broken Squids per mail or log
+        *
+        * @param array $urlArr List of full URLs to purge
+        */
+       public static function purge( $urlArr ) {
+               global $wgSquidServers, $wgHTCPRouting;
+
+               if ( !$urlArr ) {
+                       return;
+               }
+
+               wfDebugLog( 'squid', __METHOD__ . ': ' . implode( ' ', $urlArr ) . "\n" );
+
+               if ( $wgHTCPRouting ) {
+                       self::HTCPPurge( $urlArr );
+               }
+
+               wfProfileIn( __METHOD__ );
+
+               // Remove duplicate URLs
+               $urlArr = array_unique( $urlArr );
+               // Maximum number of parallel connections per squid
+               $maxSocketsPerSquid = 8;
+               // Number of requests to send per socket
+               // 400 seems to be a good tradeoff, opening a socket takes a while
+               $urlsPerSocket = 400;
+               $socketsPerSquid = ceil( count( $urlArr ) / $urlsPerSocket );
+               if ( $socketsPerSquid > $maxSocketsPerSquid ) {
+                       $socketsPerSquid = $maxSocketsPerSquid;
+               }
+
+               $pool = new SquidPurgeClientPool;
+               $chunks = array_chunk( $urlArr, ceil( count( $urlArr ) / $socketsPerSquid ) );
+               foreach ( $wgSquidServers as $server ) {
+                       foreach ( $chunks as $chunk ) {
+                               $client = new SquidPurgeClient( $server );
+                               foreach ( $chunk as $url ) {
+                                       $client->queuePurge( $url );
+                               }
+                               $pool->addClient( $client );
+                       }
+               }
+               $pool->run();
+
+               wfProfileOut( __METHOD__ );
+       }
+
+       /**
+        * Send Hyper Text Caching Protocol (HTCP) CLR requests.
+        *
+        * @throws MWException
+        * @param array $urlArr Collection of URLs to purge
+        */
+       public static function HTCPPurge( $urlArr ) {
+               global $wgHTCPRouting, $wgHTCPMulticastTTL;
+               wfProfileIn( __METHOD__ );
+
+               // HTCP CLR operation
+               $htcpOpCLR = 4;
+
+               // @todo FIXME: PHP doesn't support these socket constants (include/linux/in.h)
+               if ( !defined( "IPPROTO_IP" ) ) {
+                       define( "IPPROTO_IP", 0 );
+                       define( "IP_MULTICAST_LOOP", 34 );
+                       define( "IP_MULTICAST_TTL", 33 );
+               }
+
+               // pfsockopen doesn't work because we need set_sock_opt
+               $conn = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
+               if ( ! $conn ) {
+                       $errstr = socket_strerror( socket_last_error() );
+                       wfDebugLog( 'squid', __METHOD__ .
+                               ": Error opening UDP socket: $errstr\n" );
+                       wfProfileOut( __METHOD__ );
+                       return;
+               }
+
+               // Set socket options
+               socket_set_option( $conn, IPPROTO_IP, IP_MULTICAST_LOOP, 0 );
+               if ( $wgHTCPMulticastTTL != 1 ) {
+                       // Set multicast time to live (hop count) option on socket
+                       socket_set_option( $conn, IPPROTO_IP, IP_MULTICAST_TTL,
+                               $wgHTCPMulticastTTL );
+               }
+
+               // Remove duplicate URLs from collection
+               $urlArr = array_unique( $urlArr );
+               foreach ( $urlArr as $url ) {
+                       if ( !is_string( $url ) ) {
+                               wfProfileOut( __METHOD__ );
+                               throw new MWException( 'Bad purge URL' );
+                       }
+                       $url = self::expand( $url );
+                       $conf = self::getRuleForURL( $url, $wgHTCPRouting );
+                       if ( !$conf ) {
+                               wfDebugLog( 'squid', __METHOD__ .
+                                       "No HTCP rule configured for URL {$url} , skipping\n" );
+                               continue;
+                       }
+
+                       if ( isset( $conf['host'] ) && isset( $conf['port'] ) ) {
+                               // Normalize single entries
+                               $conf = array( $conf );
+                       }
+                       foreach ( $conf as $subconf ) {
+                               if ( !isset( $subconf['host'] ) || !isset( $subconf['port'] ) ) {
+                                       wfProfileOut( __METHOD__ );
+                                       throw new MWException( "Invalid HTCP rule for URL $url\n" );
+                               }
+                       }
+
+                       // Construct a minimal HTCP request diagram
+                       // as per RFC 2756
+                       // Opcode 'CLR', no response desired, no auth
+                       $htcpTransID = rand();
+
+                       $htcpSpecifier = pack( 'na4na*na8n',
+                               4, 'HEAD', strlen( $url ), $url,
+                               8, 'HTTP/1.0', 0 );
+
+                       $htcpDataLen = 8 + 2 + strlen( $htcpSpecifier );
+                       $htcpLen = 4 + $htcpDataLen + 2;
+
+                       // Note! Squid gets the bit order of the first
+                       // word wrong, wrt the RFC. Apparently no other
+                       // implementation exists, so adapt to Squid
+                       $htcpPacket = pack( 'nxxnCxNxxa*n',
+                               $htcpLen, $htcpDataLen, $htcpOpCLR,
+                               $htcpTransID, $htcpSpecifier, 2 );
+
+                       wfDebugLog( 'squid', __METHOD__ .
+                               "Purging URL $url via HTCP\n" );
+                       foreach ( $conf as $subconf ) {
+                               socket_sendto( $conn, $htcpPacket, $htcpLen, 0,
+                                       $subconf['host'], $subconf['port'] );
+                       }
+               }
+               wfProfileOut( __METHOD__ );
+       }
+
+       /**
+        * Expand local URLs to fully-qualified URLs using the internal protocol
+        * and host defined in $wgInternalServer. Input that's already fully-
+        * qualified will be passed through unchanged.
+        *
+        * This is used to generate purge URLs that may be either local to the
+        * main wiki or include a non-native host, such as images hosted on a
+        * second internal server.
+        *
+        * Client functions should not need to call this.
+        *
+        * @param string $url
+        * @return string
+        */
+       public static function expand( $url ) {
+               return wfExpandUrl( $url, PROTO_INTERNAL );
+       }
+
+       /**
+        * Find the HTCP routing rule to use for a given URL.
+        * @param string $url URL to match
+        * @param array $rules Array of rules, see $wgHTCPRouting for format and behavior
+        * @return mixed Element of $rules that matched, or false if nothing matched
+        */
+       private static function getRuleForURL( $url, $rules ) {
+               foreach ( $rules as $regex => $routing ) {
+                       if ( $regex === '' || preg_match( $regex, $url ) ) {
+                               return $routing;
+                       }
+               }
+               return false;
+       }
+}
diff --git a/includes/deferred/ViewCountUpdate.php b/includes/deferred/ViewCountUpdate.php
new file mode 100644 (file)
index 0000000..22a4649
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+/**
+ * Update for the 'page_counter' field
+ *
+ * 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
+ */
+
+/**
+ * Update for the 'page_counter' field, when $wgDisableCounters is false.
+ *
+ * Depending on $wgHitcounterUpdateFreq, this will directly increment the
+ * 'page_counter' field or use the 'hitcounter' table and then collect the data
+ * from that table to update the 'page_counter' field in a batch operation.
+ */
+class ViewCountUpdate implements DeferrableUpdate {
+       protected $id;
+
+       /**
+        * Constructor
+        *
+        * @param $id Integer: page ID to increment the view count
+        */
+       public function __construct( $id ) {
+               $this->id = intval( $id );
+       }
+
+       /**
+        * Run the update
+        */
+       public function doUpdate() {
+               global $wgHitcounterUpdateFreq;
+
+               $dbw = wfGetDB( DB_MASTER );
+
+               if ( $wgHitcounterUpdateFreq <= 1 || $dbw->getType() == 'sqlite' ) {
+                       $dbw->update( 'page', array( 'page_counter = page_counter + 1' ), array( 'page_id' => $this->id ), __METHOD__ );
+                       return;
+               }
+
+               # Not important enough to warrant an error page in case of failure
+               try {
+                       $dbw->insert( 'hitcounter', array( 'hc_id' => $this->id ), __METHOD__ );
+                       $checkfreq = intval( $wgHitcounterUpdateFreq / 25 + 1 );
+                       if ( rand() % $checkfreq == 0 && $dbw->lastErrno() == 0 ) {
+                               $this->collect();
+                       }
+               } catch ( DBError $e ) {}
+       }
+
+       protected function collect() {
+               global $wgHitcounterUpdateFreq;
+
+               $dbw = wfGetDB( DB_MASTER );
+
+               $rown = $dbw->selectField( 'hitcounter', 'COUNT(*)', array(), __METHOD__ );
+
+               if ( $rown < $wgHitcounterUpdateFreq ) {
+                       return;
+               }
+
+               wfProfileIn( __METHOD__ . '-collect' );
+               $old_user_abort = ignore_user_abort( true );
+
+               $dbw->lockTables( array(), array( 'hitcounter' ), __METHOD__, false );
+
+               $dbType = $dbw->getType();
+               $tabletype = $dbType == 'mysql' ? "ENGINE=HEAP " : '';
+               $hitcounterTable = $dbw->tableName( 'hitcounter' );
+               $acchitsTable = $dbw->tableName( 'acchits' );
+               $pageTable = $dbw->tableName( 'page' );
+
+               $dbw->query( "CREATE TEMPORARY TABLE $acchitsTable $tabletype AS " .
+                       "SELECT hc_id,COUNT(*) AS hc_n FROM $hitcounterTable " .
+                       'GROUP BY hc_id', __METHOD__ );
+               $dbw->delete( 'hitcounter', '*', __METHOD__ );
+               $dbw->unlockTables( __METHOD__ );
+
+               if ( $dbType == 'mysql' ) {
+                       $dbw->query( "UPDATE $pageTable,$acchitsTable SET page_counter=page_counter + hc_n " .
+                               'WHERE page_id = hc_id', __METHOD__ );
+               } else {
+                       $dbw->query( "UPDATE $pageTable SET page_counter=page_counter + hc_n " .
+                               "FROM $acchitsTable WHERE page_id = hc_id", __METHOD__ );
+               }
+               $dbw->query( "DROP TABLE $acchitsTable", __METHOD__ );
+
+               ignore_user_abort( $old_user_abort );
+               wfProfileOut( __METHOD__ . '-collect' );
+       }
+}
index ea74164..39f8a47 100644 (file)
@@ -34,37 +34,40 @@ define( 'MW_DIFF_VERSION', '1.11a' );
  * @ingroup DifferenceEngine
  */
 class DifferenceEngine extends ContextSource {
-       /**#@+
-        * @private
-        */
-       var $mOldid, $mNewid;
-       var $mOldTags, $mNewTags;
+       public $mOldid;
+       public $mNewid;
+       private $mOldTags;
+       private $mNewTags;
+
        /**
         * @var Content
         */
-       var $mOldContent, $mNewContent;
+       public $mOldContent;
+       public $mNewContent;
        protected $mDiffLang;
 
        /**
         * @var Title
         */
-       var $mOldPage, $mNewPage;
+       public $mOldPage;
+       public $mNewPage;
 
        /**
         * @var Revision
         */
-       var $mOldRev, $mNewRev;
+       public $mOldRev;
+       public $mNewRev;
        private $mRevisionsIdsLoaded = false; // Have the revisions IDs been loaded
-       var $mRevisionsLoaded = false; // Have the revisions been loaded
-       var $mTextLoaded = 0; // How many text blobs have been loaded, 0, 1 or 2?
-       var $mCacheHit = false; // Was the diff fetched from cache?
+       public $mRevisionsLoaded = false; // Have the revisions been loaded
+       public $mTextLoaded = 0; // How many text blobs have been loaded, 0, 1 or 2?
+       public $mCacheHit = false; // Was the diff fetched from cache?
 
        /**
         * Set this to true to add debug info to the HTML output.
         * Warning: this may cause RSS readers to spuriously mark articles as "new"
         * (bug 20601)
         */
-       var $enableDebugComment = false;
+       public $enableDebugComment = false;
 
        // If true, line X is not displayed when X is 1, for example to increase
        // readability and conserve space with many small diffs.
@@ -80,14 +83,14 @@ class DifferenceEngine extends ContextSource {
         * Constructor
         * @param $context IContextSource context to use, anything else will be ignored
         * @param $old Integer old ID we want to show and diff with.
-        * @param $new String either 'prev' or 'next'.
+        * @param $new String|int either 'prev' or 'next'. Default: 0.
         * @param $rcid Integer Deprecated, no longer used!
         * @param $refreshCache boolean If set, refreshes the diff cache
         * @param $unhide boolean If set, allow viewing deleted revs
         */
        function __construct( $context = null, $old = 0, $new = 0, $rcid = 0,
-               $refreshCache = false, $unhide = false )
-       {
+               $refreshCache = false, $unhide = false
+       {
                if ( $context instanceof IContextSource ) {
                        $this->setContext( $context );
                }
@@ -259,8 +262,8 @@ class DifferenceEngine extends ContextSource {
                                $out->setPageTitle( $this->msg( 'difference-title', $this->mNewPage->getPrefixedText() ) );
                                $samePage = true;
                        } else {
-                               $out->setPageTitle( $this->msg( 'difference-title-multipage', $this->mOldPage->getPrefixedText(),
-                                       $this->mNewPage->getPrefixedText() ) );
+                               $out->setPageTitle( $this->msg( 'difference-title-multipage',
+                                       $this->mOldPage->getPrefixedText(), $this->mNewPage->getPrefixedText() ) );
                                $out->addSubtitle( $this->msg( 'difference-multipage' ) );
                                $samePage = false;
                        }
@@ -273,7 +276,10 @@ class DifferenceEngine extends ContextSource {
                                                $rollback = '&#160;&#160;&#160;' . $rollbackLink;
                                        }
                                }
-                               if ( !$this->mOldRev->isDeleted( Revision::DELETED_TEXT ) && !$this->mNewRev->isDeleted( Revision::DELETED_TEXT ) ) {
+
+                               if ( !$this->mOldRev->isDeleted( Revision::DELETED_TEXT ) &&
+                                       !$this->mNewRev->isDeleted( Revision::DELETED_TEXT )
+                               ) {
                                        $undoLink = Html::element( 'a', array(
                                                        'href' => $this->mNewPage->getLocalURL( array(
                                                                'action' => 'edit',
@@ -359,7 +365,8 @@ class DifferenceEngine extends ContextSource {
                foreach ( $revisionTools as $tool ) {
                        $formattedRevisionTools[] = $this->msg( 'parentheses' )->rawParams( $tool )->escaped();
                }
-               $newRevisionHeader = $this->getRevisionHeader( $this->mNewRev, 'complete' ) . ' ' . implode( ' ', $formattedRevisionTools );
+               $newRevisionHeader = $this->getRevisionHeader( $this->mNewRev, 'complete' ) .
+                       ' ' . implode( ' ', $formattedRevisionTools );
                $newChangeTags = ChangeTags::formatSummaryRow( $this->mNewTags, 'diff' );
 
                $newHeader = '<div id="mw-diff-ntitle1"><strong>' . $newRevisionHeader . '</strong></div>' .
@@ -390,9 +397,13 @@ class DifferenceEngine extends ContextSource {
                                        array( $msg ) );
                        } else {
                                # Give explanation and add a link to view the diff...
-                               $link = $this->getTitle()->getFullURL( $this->getRequest()->appendQueryValue( 'unhide', '1', true ) );
+                               $query = $this->getRequest()->appendQueryValue( 'unhide', '1', true );
+                               $link = $this->getTitle()->getFullURL( $query );
                                $msg = $suppressed ? 'rev-suppressed-unhide-diff' : 'rev-deleted-unhide-diff';
-                               $out->wrapWikiMsg( "<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n", array( $msg, $link ) );
+                               $out->wrapWikiMsg(
+                                       "<div id='mw-$msg' class='mw-warning plainlinks'>\n$1\n</div>\n",
+                                       array( $msg, $link )
+                               );
                        }
                # Otherwise, output a regular diff...
                } else {
@@ -400,7 +411,9 @@ class DifferenceEngine extends ContextSource {
                        $notice = '';
                        if ( $deleted ) {
                                $msg = $suppressed ? 'rev-suppressed-diff-view' : 'rev-deleted-diff-view';
-                               $notice = "<div id='mw-$msg' class='mw-warning plainlinks'>\n" . $this->msg( $msg )->parse() . "</div>\n";
+                               $notice = "<div id='mw-$msg' class='mw-warning plainlinks'>\n" .
+                                       $this->msg( $msg )->parse() .
+                                       "</div>\n";
                        }
                        $this->showDiff( $oldHeader, $newHeader, $notice );
                        if ( !$diffOnly ) {
@@ -505,6 +518,7 @@ class DifferenceEngine extends ContextSource {
                $out->addHTML( "<hr class='diff-hr' />
                <h2 class='diff-currentversion-title'>{$revHeader}</h2>\n" );
                # Page content may be handled by a hooked call instead...
+               # @codingStandardsIgnoreStart Ignoring long lines.
                if ( wfRunHooks( 'ArticleContentOnDiff', array( $this, $out ) ) ) {
                        $this->loadNewText();
                        $out->setRevisionId( $this->mNewid );
@@ -560,6 +574,8 @@ class DifferenceEngine extends ContextSource {
                                }
                        }
                }
+               # @codingStandardsIgnoreEnd
+
                # Add redundant patrol link on bottom...
                $out->addHTML( $this->markPatrolledLink() );
 
@@ -581,6 +597,9 @@ class DifferenceEngine extends ContextSource {
         * Get the diff text, send it to the OutputPage object
         * Returns false if the diff could not be generated, otherwise returns true
         *
+        * @param string|bool $otitle Header for old text or false
+        * @param string|bool $ntitle Header for new text or false
+        * @param string $notice
         * @return bool
         */
        function showDiff( $otitle, $ntitle, $notice = '' ) {
@@ -614,14 +633,17 @@ class DifferenceEngine extends ContextSource {
                $body = $this->getDiffBody();
                if ( $body === false ) {
                        return false;
-               } else {
-                       $multi = $this->getMultiNotice();
-                       // Display a message when the diff is empty
-                       if ( $body === '' ) {
-                               $notice .= '<div class="mw-diff-empty">' . $this->msg( 'diff-empty' )->parse() . "</div>\n";
-                       }
-                       return $this->addHeader( $body, $otitle, $ntitle, $multi, $notice );
                }
+
+               $multi = $this->getMultiNotice();
+               // Display a message when the diff is empty
+               if ( $body === '' ) {
+                       $notice .= '<div class="mw-diff-empty">' .
+                               $this->msg( 'diff-empty' )->parse() .
+                               "</div>\n";
+               }
+
+               return $this->addHeader( $body, $otitle, $ntitle, $multi, $notice );
        }
 
        /**
@@ -637,10 +659,14 @@ class DifferenceEngine extends ContextSource {
                if ( !$this->loadRevisionData() ) {
                        wfProfileOut( __METHOD__ );
                        return false;
-               } elseif ( $this->mOldRev && !$this->mOldRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
+               } elseif ( $this->mOldRev &&
+                       !$this->mOldRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
+               ) {
                        wfProfileOut( __METHOD__ );
                        return false;
-               } elseif ( $this->mNewRev && !$this->mNewRev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
+               } elseif ( $this->mNewRev &&
+                       !$this->mNewRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
+               ) {
                        wfProfileOut( __METHOD__ );
                        return false;
                }
@@ -654,8 +680,8 @@ class DifferenceEngine extends ContextSource {
                // Cacheable?
                $key = false;
                if ( $this->mOldid && $this->mNewid ) {
-                       $key = wfMemcKey( 'diff', 'version', MW_DIFF_VERSION,
-                               'oldid', $this->mOldid, 'newid', $this->mNewid );
+                       $key = $this->getDiffBodyCacheKey();
+
                        // Try cache
                        if ( !$this->mRefreshCache ) {
                                $difftext = $wgMemc->get( $key );
@@ -695,6 +721,22 @@ class DifferenceEngine extends ContextSource {
                return $difftext;
        }
 
+       /**
+        * Returns the cache key for diff body text or content.
+        *
+        * @return string
+        * @since 1.23
+        * @throws MWException
+        */
+       protected function getDiffBodyCacheKey() {
+               if ( !$this->mOldid || !$this->mNewid ) {
+                       throw new MWException( 'mOldid and mNewid must be set to get diff cache key.' );
+               }
+
+               return wfMemcKey( 'diff', 'version', MW_DIFF_VERSION,
+                       'oldid', $this->mOldid, 'newid', $this->mNewid );
+       }
+
        /**
         * Generate a diff, no caching.
         *
@@ -945,11 +987,16 @@ class DifferenceEngine extends ContextSource {
                                $editQuery['oldid'] = $rev->getID();
                        }
 
-                       $msg = $this->msg( $title->quickUserCan( 'edit', $user ) ? 'editold' : 'viewsourceold' )->escaped();
+                       $key = $title->quickUserCan( 'edit', $user ) ? 'editold' : 'viewsourceold';
+                       $msg = $this->msg( $key )->escaped();
                        $header .= ' ' . $this->msg( 'parentheses' )->rawParams(
                                Linker::linkKnown( $title, $msg, array(), $editQuery ) )->plain();
                        if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
-                               $header = Html::rawElement( 'span', array( 'class' => 'history-deleted' ), $header );
+                               $header = Html::rawElement(
+                                       'span',
+                                       array( 'class' => 'history-deleted' ),
+                                       $header
+                               );
                        }
                } else {
                        $header = Html::rawElement( 'span', array( 'class' => 'history-deleted' ), $header );
@@ -998,7 +1045,8 @@ class DifferenceEngine extends ContextSource {
                }
 
                if ( $multi != '' ) {
-                       $header .= "<tr><td colspan='{$multiColspan}' style='text-align: center;' class='diff-multi'>{$multi}</td></tr>";
+                       $header .= "<tr><td colspan='{$multiColspan}' style='text-align: center;' " .
+                               "class='diff-multi'>{$multi}</td></tr>";
                }
                if ( $notice != '' ) {
                        $header .= "<tr><td colspan='{$multiColspan}' style='text-align: center;'>{$notice}</td></tr>";
@@ -1114,9 +1162,15 @@ class DifferenceEngine extends ContextSource {
                $this->loadRevisionIds();
 
                // Load the new revision object
-               $this->mNewRev = $this->mNewid
-                       ? Revision::newFromId( $this->mNewid )
-                       : Revision::newFromTitle( $this->getTitle(), false, Revision::READ_NORMAL );
+               if ( $this->mNewid ) {
+                       $this->mNewRev = Revision::newFromId( $this->mNewid );
+               } else {
+                       $this->mNewRev = Revision::newFromTitle(
+                               $this->getTitle(),
+                               false,
+                               Revision::READ_NORMAL
+                       );
+               }
 
                if ( !$this->mNewRev instanceof Revision ) {
                        return false;
index 6f28b10..5300e5e 100644 (file)
@@ -29,8 +29,7 @@ class FileRepoStatus extends Status {
        /**
         * Factory function for fatal errors
         *
-        * @param $repo FileRepo
-        *
+        * @param FileRepo $repo
         * @return FileRepoStatus
         */
        static function newFatal( $repo /*, parameters...*/ ) {
@@ -42,7 +41,7 @@ class FileRepoStatus extends Status {
        }
 
        /**
-        * @param $repo FileRepo
+        * @param FileRepo|bool $repo Default: false
         * @param $value
         * @return FileRepoStatus
         */
old mode 100644 (file)
new mode 100755 (executable)
index 5eec9a5..07cc03b
@@ -42,6 +42,16 @@ class ForeignAPIRepo extends FileRepo {
         * Update the version every time you make breaking or significant changes. */
        const VERSION = "2.1";
 
+       /**
+        * List of iiprop values for the thumbnail fetch queries.
+        * @since 1.23
+        */
+       protected static $imageInfoProps = array(
+               'url',
+               'thumbnail',
+               'timestamp',
+       );
+
        var $fileFactory = array( 'ForeignAPIFile', 'newFromTitle' );
        /* Check back with Commons after a day */
        var $apiThumbCacheExpiry = 86400; /* 24*60*60 */
@@ -238,7 +248,7 @@ class ForeignAPIRepo extends FileRepo {
        function getThumbUrl( $name, $width = -1, $height = -1, &$result = null, $otherParams = '' ) {
                $data = $this->fetchImageQuery( array(
                        'titles' => 'File:' . $name,
-                       'iiprop' => 'url|timestamp',
+                       'iiprop' => self::getIIProps(),
                        'iiurlwidth' => $width,
                        'iiurlheight' => $height,
                        'iiurlparam' => $otherParams,
@@ -265,7 +275,7 @@ class ForeignAPIRepo extends FileRepo {
        function getThumbError( $name, $width = -1, $height = -1, $otherParams = '', $lang = null ) {
                $data = $this->fetchImageQuery( array(
                        'titles' => 'File:' . $name,
-                       'iiprop' => 'url|timestamp',
+                       'iiprop' => self::getIIProps(),
                        'iiurlwidth' => $width,
                        'iiurlheight' => $height,
                        'iiurlparam' => $otherParams,
@@ -485,6 +495,14 @@ class ForeignAPIRepo extends FileRepo {
                }
        }
 
+       /**
+        * @return string
+        * @since 1.23
+        */
+       protected static function getIIProps() {
+               return join( '|', self::$imageInfoProps );
+       }
+
        /**
         * HTTP GET request to a mediawiki API (with caching)
         * @param $target string Used in cache key creation, mostly
index ec5f927..5a5221f 100644 (file)
@@ -512,6 +512,17 @@ abstract class File {
                return false;
        }
 
+       /**
+        * Like getMetadata but returns a handler independent array of common values.
+        * @see MediaHandler::getCommonMetaArray()
+        * @return Array or false if not supported
+        * @since 1.23
+        */
+       public function getCommonMetaArray() {
+               $handler = $this->getHandler();
+               return $handler->getCommonMetaArray( $this );
+       }
+
        /**
         * get versioned metadata
         *
old mode 100644 (file)
new mode 100755 (executable)
index ed96d44..0b3d5df
@@ -57,7 +57,10 @@ class ForeignAPIFile extends File {
                        'titles' => 'File:' . $title->getDBkey(),
                        'iiprop' => self::getProps(),
                        'prop' => 'imageinfo',
-                       'iimetadataversion' => MediaHandler::getMetadataVersion()
+                       'iimetadataversion' => MediaHandler::getMetadataVersion(),
+                       // extmetadata is language-dependant, accessing the current language here
+                       // would be problematic, so we just get them all
+                       'iiextmetadatamultilang' => 1,
                ) );
 
                $info = $repo->getImageInfo( $data );
@@ -86,7 +89,7 @@ class ForeignAPIFile extends File {
         * @return string
         */
        static function getProps() {
-               return 'timestamp|user|comment|url|size|sha1|metadata|mime|mediatype';
+               return 'timestamp|user|comment|url|size|sha1|metadata|mime|mediatype|extmetadata';
        }
 
        // Dummy functions...
@@ -169,6 +172,16 @@ class ForeignAPIFile extends File {
                return null;
        }
 
+       /**
+        * @return array|null extended metadata (see imageinfo API for format) or null on error
+        */
+       public function getExtendedMetadata() {
+               if ( isset( $this->mInfo['extmetadata'] ) ) {
+                       return $this->mInfo['extmetadata'];
+               }
+               return null;
+       }
+
        /**
         * @param $metadata array
         * @return array
index 267b6c5..e56c7a9 100644 (file)
@@ -88,6 +88,11 @@ abstract class DatabaseUpdater {
         */
        protected $skipSchema = false;
 
+       /**
+        * Hold the value of $wgContentHandlerUseDB during the upgrade.
+        */
+       protected $holdContentHandlerUseDB = true;
+
        /**
         * Constructor
         *
@@ -1033,4 +1038,30 @@ abstract class DatabaseUpdater {
                $cl->execute();
                $this->output( "done.\n" );
        }
+
+       /**
+        * Turns off content handler fields during parts of the upgrade
+        * where they aren't available.
+        */
+       protected function disableContentHandlerUseDB() {
+               global $wgContentHandlerUseDB;
+
+               if( $wgContentHandlerUseDB ) {
+                       $this->output( "Turning off Content Handler DB fields for this part of upgrade.\n" );
+                       $this->holdContentHandlerUseDB = $wgContentHandlerUseDB;
+                       $wgContentHandlerUseDB = false;
+               }
+       }
+
+       /**
+        * Turns content handler fields back on.
+        */
+       protected function enableContentHandlerUseDB() {
+               global $wgContentHandlerUseDB;
+
+               if( $this->holdContentHandlerUseDB ) {
+                       $this->output( "Content Handler DB fields should be usable now.\n" );
+                       $wgContentHandlerUseDB = $this->holdContentHandlerUseDB;
+               }
+       }
 }
index edf5ff2..ac768a0 100644 (file)
@@ -374,7 +374,7 @@ Specify a different project namespace.',
        'config-ns-conflict'               => 'The specified namespace "<nowiki>$1</nowiki>" conflicts with a default MediaWiki namespace.
 Specify a different project namespace.',
        'config-admin-box'                => 'Administrator account',
-       'config-admin-name'               => 'Your name:',
+       'config-admin-name'               => 'Your username:',
        'config-admin-password'           => 'Password:',
        'config-admin-password-confirm'   => 'Password again:',
        'config-admin-help'               => 'Enter your preferred username here, for example "Joe Bloggs".
index 0594073..fb675d7 100644 (file)
@@ -31,9 +31,11 @@ class MysqlUpdater extends DatabaseUpdater {
 
        protected function getCoreUpdateList() {
                return array(
+                       array( 'disableContentHandlerUseDB' ),
+
                        // 1.2
-                       array( 'addField', 'ipblocks', 'ipb_id', 'patch-ipblocks.sql' ),
-                       array( 'addField', 'ipblocks', 'ipb_expiry', 'patch-ipb_expiry.sql' ),
+                       array( 'addField', 'ipblocks',      'ipb_id',           'patch-ipblocks.sql' ),
+                       array( 'addField', 'ipblocks',      'ipb_expiry',       'patch-ipb_expiry.sql' ),
                        array( 'doInterwikiUpdate' ),
                        array( 'doIndexUpdate' ),
                        array( 'addTable', 'hitcounter', 'patch-hitcounter.sql' ),
@@ -215,12 +217,13 @@ class MysqlUpdater extends DatabaseUpdater {
                        // 1.21
                        array( 'addField', 'revision', 'rev_content_format', 'patch-revision-rev_content_format.sql' ),
                        array( 'addField', 'revision', 'rev_content_model', 'patch-revision-rev_content_model.sql' ),
-                       array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ),
-                       array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ),
-                       array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
-                       array( 'dropField', 'site_stats', 'ss_admins', 'patch-drop-ss_admins.sql' ),
-                       array( 'dropField', 'recentchanges', 'rc_moved_to_title', 'patch-rc_moved.sql' ),
-                       array( 'addTable', 'sites', 'patch-sites.sql' ),
+                       array( 'addField',      'archive',      'ar_content_format',            'patch-archive-ar_content_format.sql' ),
+                       array( 'addField',      'archive',      'ar_content_model',                 'patch-archive-ar_content_model.sql' ),
+                       array( 'addField',      'page',     'page_content_model',               'patch-page-page_content_model.sql' ),
+                       array( 'enableContentHandlerUseDB' ),
+                       array( 'dropField', 'site_stats',   'ss_admins',        'patch-drop-ss_admins.sql' ),
+                       array( 'dropField', 'recentchanges', 'rc_moved_to_title',            'patch-rc_moved.sql' ),
+                       array( 'addTable', 'sites',                            'patch-sites.sql' ),
                        array( 'addField', 'filearchive', 'fa_sha1', 'patch-fa_sha1.sql' ),
                        array( 'addField', 'job', 'job_token', 'patch-job_token.sql' ),
                        array( 'addField', 'job', 'job_attempts', 'patch-job_attempts.sql' ),
index ec91e57..7b6f49c 100644 (file)
@@ -38,6 +38,8 @@ class OracleUpdater extends DatabaseUpdater {
 
        protected function getCoreUpdateList() {
                return array(
+                       array( 'disableContentHandlerUseDB' ),
+
                        // 1.17
                        array( 'doNamespaceDefaults' ),
                        array( 'doFKRenameDeferr' ),
@@ -79,6 +81,7 @@ class OracleUpdater extends DatabaseUpdater {
                        array( 'addField', 'archive', 'ar_id', 'patch-archive-ar_id.sql' ),
                        array( 'addField', 'externallinks', 'el_id', 'patch-externallinks-el_id.sql' ),
                        array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
+                       array( 'enableContentHandlerUseDB' ),
                        array( 'dropField', 'site_stats', 'ss_admins', 'patch-ss_admins.sql' ),
                        array( 'dropField', 'recentchanges', 'rc_moved_to_title', 'patch-rc_moved.sql' ),
                        array( 'addTable', 'sites', 'patch-sites.sql' ),
index 020993a..6fa22bc 100644 (file)
@@ -31,8 +31,10 @@ class SqliteUpdater extends DatabaseUpdater {
 
        protected function getCoreUpdateList() {
                return array(
+                       array( 'disableContentHandlerUseDB' ),
+
                        // 1.14
-                       array( 'addField', 'site_stats', 'ss_active_users', 'patch-ss_active_users.sql' ),
+                       array( 'addField', 'site_stats',    'ss_active_users',  'patch-ss_active_users.sql' ),
                        array( 'doActiveUsersInit' ),
                        array( 'addField', 'ipblocks', 'ipb_allow_usertalk', 'patch-ipb_allow_usertalk.sql' ),
                        array( 'sqliteInitialIndexes' ),
@@ -94,10 +96,12 @@ class SqliteUpdater extends DatabaseUpdater {
                        // 1.21
                        array( 'addField', 'revision', 'rev_content_format', 'patch-revision-rev_content_format.sql' ),
                        array( 'addField', 'revision', 'rev_content_model', 'patch-revision-rev_content_model.sql' ),
-                       array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ),
-                       array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ),
-                       array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
-                       array( 'dropField', 'site_stats', 'ss_admins', 'patch-drop-ss_admins.sql' ),
+                       array( 'addField', 'archive',  'ar_content_format',  'patch-archive-ar_content_format.sql' ),
+                       array( 'addField', 'archive',  'ar_content_model',   'patch-archive-ar_content_model.sql' ),
+                       array( 'addField', 'page',     'page_content_model', 'patch-page-page_content_model.sql' ),
+                       array( 'enableContentHandlerUseDB' ),
+
+                       array( 'dropField', 'site_stats',    'ss_admins',         'patch-drop-ss_admins.sql' ),
                        array( 'dropField', 'recentchanges', 'rc_moved_to_title', 'patch-rc_moved.sql' ),
                        array( 'addTable', 'sites', 'patch-sites.sql' ),
                        array( 'addField', 'filearchive', 'fa_sha1', 'patch-fa_sha1.sql' ),
index a20ea4a..fa7fee5 100644 (file)
@@ -376,7 +376,7 @@ class JobQueueGroup {
                                                        ++$count;
                                                }
                                        } catch ( JobQueueError $e ) {
-                                               wfDebugLog( 'exception', $e->getLogMessage() );
+                                               MWExceptionHandler::logException( $e );
                                        }
                                }
                        }
diff --git a/includes/libs/ScopedPHPTimeout.php b/includes/libs/ScopedPHPTimeout.php
new file mode 100644 (file)
index 0000000..d1493c3
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+/**
+ * Expansion of the PHP execution time limit feature for a function call.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Class to expand PHP execution time for a function call.
+ * Use this when performing changes that should not be interrupted.
+ *
+ * On construction, set_time_limit() is called and set to $seconds.
+ * If the client aborts the connection, PHP will continue to run.
+ * When the object goes out of scope, the timer is restarted, with
+ * the original time limit minus the time the object existed.
+ */
+class ScopedPHPTimeout {
+       protected $startTime; // float; seconds
+       protected $oldTimeout; // integer; seconds
+       protected $oldIgnoreAbort; // boolean
+
+       protected static $stackDepth = 0; // integer
+       protected static $totalCalls = 0; // integer
+       protected static $totalElapsed = 0; // float; seconds
+
+       /* Prevent callers in infinite loops from running forever */
+       const MAX_TOTAL_CALLS = 1000000;
+       const MAX_TOTAL_TIME = 300; // seconds
+
+       /**
+        * @param $seconds integer
+        */
+       public function __construct( $seconds ) {
+               if ( ini_get( 'max_execution_time' ) > 0 ) { // CLI uses 0
+                       if ( self::$totalCalls >= self::MAX_TOTAL_CALLS ) {
+                               trigger_error( "Maximum invocations of " . __CLASS__ . " exceeded." );
+                       } elseif ( self::$totalElapsed >= self::MAX_TOTAL_TIME ) {
+                               trigger_error( "Time limit within invocations of " . __CLASS__ . " exceeded." );
+                       } elseif ( self::$stackDepth > 0 ) { // recursion guard
+                               trigger_error( "Resursive invocation of " . __CLASS__ . " attempted." );
+                       } else {
+                               $this->oldIgnoreAbort = ignore_user_abort( true );
+                               $this->oldTimeout = ini_set( 'max_execution_time', $seconds );
+                               $this->startTime = microtime( true );
+                               ++self::$stackDepth;
+                               ++self::$totalCalls; // proof against < 1us scopes
+                       }
+               }
+       }
+
+       /**
+        * Restore the original timeout.
+        * This does not account for the timer value on __construct().
+        */
+       public function __destruct() {
+               if ( $this->oldTimeout ) {
+                       $elapsed = microtime( true ) - $this->startTime;
+                       // Note: a limit of 0 is treated as "forever"
+                       set_time_limit( max( 1, $this->oldTimeout - (int)$elapsed ) );
+                       // If each scoped timeout is for less than one second, we end up
+                       // restoring the original timeout without any decrease in value.
+                       // Thus web scripts in an infinite loop can run forever unless we
+                       // take some measures to prevent this. Track total time and calls.
+                       self::$totalElapsed += $elapsed;
+                       --self::$stackDepth;
+                       ignore_user_abort( $this->oldIgnoreAbort );
+               }
+       }
+}
diff --git a/includes/libs/XmlTypeCheck.php b/includes/libs/XmlTypeCheck.php
new file mode 100644 (file)
index 0000000..92ca7d8
--- /dev/null
@@ -0,0 +1,184 @@
+<?php
+/**
+ * XML syntax and type checker.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+class XmlTypeCheck {
+       /**
+        * Will be set to true or false to indicate whether the file is
+        * well-formed XML. Note that this doesn't check schema validity.
+        */
+       public $wellFormed = false;
+
+       /**
+        * Will be set to true if the optional element filter returned
+        * a match at some point.
+        */
+       public $filterMatch = false;
+
+       /**
+        * Name of the document's root element, including any namespace
+        * as an expanded URL.
+        */
+       public $rootElement = '';
+
+       /**
+        * @param string $input a filename or string containing the XML element
+        * @param callable $filterCallback (optional)
+        *        Function to call to do additional custom validity checks from the
+        *        SAX element handler event. This gives you access to the element
+        *        namespace, name, and attributes, but not to text contents.
+        *        Filter should return 'true' to toggle on $this->filterMatch
+        * @param boolean $isFile (optional) indicates if the first parameter is a
+        *        filename (default, true) or if it is a string (false)
+        */
+       function __construct( $input, $filterCallback = null, $isFile = true ) {
+               $this->filterCallback = $filterCallback;
+               if ( $isFile ) {
+                       $this->validateFromFile( $input );
+               } else {
+                       $this->validateFromString( $input );
+               }
+       }
+
+       /**
+        * Alternative constructor: from filename
+        *
+        * @param string $fname the filename of an XML document
+        * @param callable $filterCallback (optional)
+        *        Function to call to do additional custom validity checks from the
+        *        SAX element handler event. This gives you access to the element
+        *        namespace, name, and attributes, but not to text contents.
+        *        Filter should return 'true' to toggle on $this->filterMatch
+        * @return XmlTypeCheck
+        */
+       public static function newFromFilename( $fname, $filterCallback = null ) {
+               return new self( $fname, $filterCallback, true );
+       }
+
+       /**
+        * Alternative constructor: from string
+        *
+        * @param string $string a string containing an XML element
+        * @param callable $filterCallback (optional)
+        *        Function to call to do additional custom validity checks from the
+        *        SAX element handler event. This gives you access to the element
+        *        namespace, name, and attributes, but not to text contents.
+        *        Filter should return 'true' to toggle on $this->filterMatch
+        * @return XmlTypeCheck
+        */
+       public static function newFromString( $string, $filterCallback = null ) {
+               return new self( $string, $filterCallback, false );
+       }
+
+       /**
+        * Get the root element. Simple accessor to $rootElement
+        *
+        * @return string
+        */
+       public function getRootElement() {
+               return $this->rootElement;
+       }
+
+       /**
+        * Get an XML parser with the root element handler.
+        * @see XmlTypeCheck::rootElementOpen()
+        * @return resource a resource handle for the XML parser
+        */
+       private function getParser() {
+               $parser = xml_parser_create_ns( 'UTF-8' );
+               // case folding violates XML standard, turn it off
+               xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false );
+               xml_set_element_handler( $parser, array( $this, 'rootElementOpen' ), false );
+               return $parser;
+       }
+
+       /**
+        * @param string $fname the filename
+        */
+       private function validateFromFile( $fname ) {
+               $parser = $this->getParser();
+
+               if ( file_exists( $fname ) ) {
+                       $file = fopen( $fname, "rb" );
+                       if ( $file ) {
+                               do {
+                                       $chunk = fread( $file, 32768 );
+                                       $ret = xml_parse( $parser, $chunk, feof( $file ) );
+                                       if ( $ret == 0 ) {
+                                               $this->wellFormed = false;
+                                               fclose( $file );
+                                               xml_parser_free( $parser );
+                                               return;
+                                       }
+                               } while ( !feof( $file ) );
+
+                               fclose( $file );
+                       }
+               }
+               $this->wellFormed = true;
+
+               xml_parser_free( $parser );
+       }
+
+       /**
+        *
+        * @param string $string the XML-input-string to be checked.
+        */
+       private function validateFromString( $string ) {
+               $parser = $this->getParser();
+               $ret = xml_parse( $parser, $string, true );
+               xml_parser_free( $parser );
+               if ( $ret == 0 ) {
+                       $this->wellFormed = false;
+                       return;
+               }
+               $this->wellFormed = true;
+       }
+
+       /**
+        * @param $parser
+        * @param $name
+        * @param $attribs
+        */
+       private function rootElementOpen( $parser, $name, $attribs ) {
+               $this->rootElement = $name;
+
+               if ( is_callable( $this->filterCallback ) ) {
+                       xml_set_element_handler( $parser, array( $this, 'elementOpen' ), false );
+                       $this->elementOpen( $parser, $name, $attribs );
+               } else {
+                       // We only need the first open element
+                       xml_set_element_handler( $parser, false, false );
+               }
+       }
+
+       /**
+        * @param $parser
+        * @param $name
+        * @param $attribs
+        */
+       private function elementOpen( $parser, $name, $attribs ) {
+               if ( call_user_func( $this->filterCallback, $name, $attribs ) ) {
+                       // Filter hit!
+                       $this->filterMatch = true;
+               }
+       }
+}
index 2a1545b..82197b5 100644 (file)
@@ -6,7 +6,40 @@
 # and is available on most Linux systems. If Perl was distributed with
 # BSD::Resource included, we would happily use that instead, but it isn't.
 
+# Clean up cgroup
+cleanup() {
+       # First we have to move the current task into a "garbage" group, otherwise
+       # the cgroup will not be empty, and attempting to remove it will fail with
+       # "Device or resource busy"
+       if [ -w "$MW_CGROUP"/tasks ]; then
+               GARBAGE="$MW_CGROUP"
+       else
+               GARBAGE="$MW_CGROUP"/garbage-`id -un`
+               if [ ! -e "$GARBAGE" ]; then
+                       mkdir -m 0700 "$GARBAGE"
+               fi
+       fi
+       echo $BASHPID > "$GARBAGE"/tasks
+
+       # Suppress errors in case the cgroup has disappeared due to a release script
+       rmdir "$MW_CGROUP"/$$ 2>/dev/null
+}
+
+updateTaskCount() {
+       # There are lots of ways to count lines in a file in shell script, but this
+       # is one of the few that doesn't create another process, which would
+       # increase the returned number of tasks.
+       readarray < "$MW_CGROUP"/$$/tasks
+       NUM_TASKS=${#MAPFILE[*]}
+}
+
+log() {
+       echo limit.sh: "$*" >&3
+       echo limit.sh: "$*" >&2
+}
+
 MW_INCLUDE_STDERR=
+MW_USE_LOG_PIPE=
 MW_CPU_LIMIT=0
 MW_CGROUP=
 MW_MEM_LIMIT=0
@@ -19,6 +52,10 @@ eval "$2"
 if [ -n "$MW_INCLUDE_STDERR" ]; then
        exec 2>&1
 fi
+if [ -z "$MW_USE_LOG_PIPE" ]; then
+       # Open a dummy log FD
+       exec 3>/dev/null
+fi
 
 if [ "$MW_CPU_LIMIT" -gt 0 ]; then
        ulimit -t "$MW_CPU_LIMIT"
@@ -27,9 +64,11 @@ if [ "$MW_MEM_LIMIT" -gt 0 ]; then
        if [ -n "$MW_CGROUP" ]; then
                # Create cgroup
                if ! mkdir -m 0700 "$MW_CGROUP"/$$; then
-                       echo "limit.sh: failed to create the cgroup." 1>&2
-                       exit 1
+                       log "failed to create the cgroup."
+                       MW_CGROUP=""
                fi
+       fi
+       if [ -n "$MW_CGROUP" ]; then
                echo $$ > "$MW_CGROUP"/$$/tasks
                if [ -n "$MW_CGROUP_NOTIFY" ]; then
                        echo "1" > "$MW_CGROUP"/$$/notify_on_release
@@ -48,43 +87,16 @@ if [ "$MW_FILE_SIZE_LIMIT" -gt 0 ]; then
        ulimit -f "$MW_FILE_SIZE_LIMIT"
 fi
 if [ "$MW_WALL_CLOCK_LIMIT" -gt 0 -a -x "/usr/bin/timeout" ]; then
-       /usr/bin/timeout $MW_WALL_CLOCK_LIMIT /bin/bash -c "$1"
+       /usr/bin/timeout $MW_WALL_CLOCK_LIMIT /bin/bash -c "$1" 3>&-
        STATUS="$?"
        if [ "$STATUS" == 124 ]; then
-               echo "limit.sh: timed out." 1>&2
+               log "timed out executing command \"$1\""
        fi
 else
-       eval "$1"
+       eval "$1" 3>&-
        STATUS="$?"
 fi
 
-# Clean up cgroup
-cleanup() {
-       # First we have to move the current task into a "garbage" group, otherwise
-       # the cgroup will not be empty, and attempting to remove it will fail with
-       # "Device or resource busy"
-       if [ -w "$MW_CGROUP"/tasks ]; then
-               GARBAGE="$MW_CGROUP"
-       else
-               GARBAGE="$MW_CGROUP"/garbage-"$USER"
-               if [ ! -e "$GARBAGE" ]; then
-                       mkdir -m 0700 "$GARBAGE"
-               fi
-       fi
-       echo $BASHPID > "$GARBAGE"/tasks
-
-       # Suppress errors in case the cgroup has disappeared due to a release script
-       rmdir "$MW_CGROUP"/$$ 2>/dev/null
-}
-
-updateTaskCount() {
-       # There are lots of ways to count lines in a file in shell script, but this
-       # is one of the few that doesn't create another process, which would
-       # increase the returned number of tasks.
-       readarray < "$MW_CGROUP"/$$/tasks
-       NUM_TASKS=${#MAPFILE[*]}
-}
-
 if [ -n "$MW_CGROUP" ]; then
        updateTaskCount
 
@@ -97,7 +109,7 @@ if [ -n "$MW_CGROUP" ]; then
                                updateTaskCount
                        done
                        cleanup
-               ) >&/dev/null < /dev/null &
+               ) >&/dev/null < /dev/null 3>&- &
                disown -a
        else
                cleanup
index 09ae3b8..a11dcb7 100644 (file)
  * @ingroup Pager
  */
 class LogPager extends ReverseChronologicalPager {
-       private $types = array(), $performer = '', $title = '', $pattern = '';
+       private $types = array();
+       private $performer = '';
+       private $title = '';
+       private $pattern = '';
        private $typeCGI = '';
        public $mLogEventsList;
 
@@ -35,13 +38,13 @@ class LogPager extends ReverseChronologicalPager {
         * Constructor
         *
         * @param LogEventsList $list
-        * @param string $types or Array: log types to show
+        * @param string|array $types Log types to show
         * @param string $performer the user who made the log entries
         * @param string|Title $title the page title the log entries are for
         * @param string $pattern do a prefix search rather than an exact title match
         * @param array $conds extra conditions for the query
-        * @param int $year The year to start from
-        * @param int $month The month to start from
+        * @param int|bool $year The year to start from. Default: false
+        * @param int|bool $month The month to start from. Default: false
         * @param string $tagFilter tag
         */
        public function __construct( $list, $types = array(), $performer = '', $title = '', $pattern = '',
index d8d0bed..d3fa36d 100644 (file)
@@ -61,16 +61,18 @@ class ExifBitmapHandler extends BitmapHandler {
                                . $metadata['Software'][0][1] . ')';
                }
 
+               $formatter = new FormatMetadata;
+
                // ContactInfo also has to be dealt with specially
                if ( isset( $metadata['Contact'] ) ) {
                        $metadata['Contact'] =
-                               FormatMetadata::collapseContactInfo(
+                               $formatter->collapseContactInfo(
                                        $metadata['Contact'] );
                }
 
                foreach ( $metadata as &$val ) {
                        if ( is_array( $val ) ) {
-                               $val = FormatMetadata::flattenArray( $val, 'ul', $avoidHtml );
+                               $val = $formatter->flattenArrayReal( $val, 'ul', $avoidHtml );
                        }
                }
                $metadata['MEDIAWIKI_EXIF_VERSION'] = 1;
@@ -117,25 +119,32 @@ class ExifBitmapHandler extends BitmapHandler {
         * @return array|bool
         */
        function formatMetadata( $image ) {
-               $metadata = $image->getMetadata();
+               $meta = $this->getCommonMetaArray( $image );
+               if ( count( $meta ) === 0 ) {
+                       return false;
+               }
+
+               return $this->formatMetadataHelper( $meta );
+       }
+
+       public function getCommonMetaArray( File $file ) {
+               $metadata = $file->getMetadata();
                if ( $metadata === self::OLD_BROKEN_FILE ||
                        $metadata === self::BROKEN_FILE ||
-                       $this->isMetadataValid( $image, $metadata ) === self::METADATA_BAD )
+                       $this->isMetadataValid( $file, $metadata ) === self::METADATA_BAD )
                {
                        // So we don't try and display metadata from PagedTiffHandler
                        // for example when using InstantCommons.
-                       return false;
+                       return array();
                }
 
                $exif = unserialize( $metadata );
                if ( !$exif ) {
-                       return false;
+                       return array();
                }
                unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
-               if ( count( $exif ) == 0 ) {
-                       return false;
-               }
-               return $this->formatMetadataHelper( $exif );
+
+               return $exif;
        }
 
        function getMetadataType( $image ) {
old mode 100644 (file)
new mode 100755 (executable)
index 1c5136f..b34ad65
  * is already a large number of messages using the 'exif' prefix.
  *
  * @ingroup Media
+ * @since 1.23 the class extends ContextSource and various formerly-public internal methods are private
  */
-class FormatMetadata {
+class FormatMetadata extends ContextSource {
+
+       /**
+        * Only output a single language for multi-language fields
+        * @var boolean
+        * @since 1.23
+        */
+       protected $singleLang = false;
+
+       /**
+        * Trigger only outputting single language for multilanguage fields
+        *
+        * @param Boolean $val
+        * @since 1.23
+        */
+       public function setSingleLanguage( $val ) {
+               $this->singleLang = $val;
+       }
 
        /**
         * Numbers given by Exif user agents are often magical, that is they
@@ -52,13 +70,33 @@ class FormatMetadata {
         * value which most of the time are plain integers. This function
         * formats Exif (and other metadata) values into human readable form.
         *
+        * This is the usual entry point for this class.
+        *
         * @param array $tags the Exif data to format ( as returned by
         *                    Exif::getFilteredData() or BitmapMetadataHandler )
+        * @param IContextSource $context Context to use (optional)
         * @return array
         */
-       public static function getFormattedData( $tags ) {
-               global $wgLang;
+       public static function getFormattedData( $tags, $context = false ) {
+               $obj = new FormatMetadata;
+               if ( $context ) {
+                       $obj->setContext( $context );
+               }
+               return $obj->makeFormattedData( $tags );
+       }
 
+       /**
+        * Numbers given by Exif user agents are often magical, that is they
+        * should be replaced by a detailed explanation depending on their
+        * value which most of the time are plain integers. This function
+        * formats Exif (and other metadata) values into human readable form.
+        *
+        * @param array $tags the Exif data to format ( as returned by
+        *                    Exif::getFilteredData() or BitmapMetadataHandler )
+        * @return array
+        * @since 1.23
+        */
+       public function makeFormattedData( $tags ) {
                $resolutionunit = !isset( $tags['ResolutionUnit'] ) || $tags['ResolutionUnit'] == 2 ? 2 : 3;
                unset( $tags['ResolutionUnit'] );
 
@@ -107,7 +145,7 @@ class FormatMetadata {
                                        $time = wfTimestamp( TS_MW, '1971:01:01 ' . $tags[$tag] );
                                        // the 1971:01:01 is just a placeholder, and not shown to user.
                                        if ( $time && intval( $time ) > 0 ) {
-                                               $tags[$tag] = $wgLang->time( $time );
+                                               $tags[$tag] = $this->getLanguage()->time( $time );
                                        }
                                } catch ( TimestampException $e ) {
                                        // This shouldn't happen, but we've seen bad formats
@@ -121,7 +159,7 @@ class FormatMetadata {
                        // instead of the other props which are single
                        // valued (mostly) so handle as a special case.
                        if ( $tag === 'Contact' ) {
-                               $vals = self::collapseContactInfo( $vals );
+                               $vals = $this->collapseContactInfo( $vals );
                                continue;
                        }
 
@@ -133,7 +171,7 @@ class FormatMetadata {
                                        case 1: case 2: case 3: case 4:
                                        case 5: case 6: case 7: case 8:
                                        case 32773: case 32946: case 34712:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -144,7 +182,7 @@ class FormatMetadata {
                                case 'PhotometricInterpretation':
                                        switch ( $val ) {
                                        case 2: case 6:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -155,7 +193,7 @@ class FormatMetadata {
                                case 'Orientation':
                                        switch ( $val ) {
                                        case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -166,7 +204,7 @@ class FormatMetadata {
                                case 'PlanarConfiguration':
                                        switch ( $val ) {
                                        case 1: case 2:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -179,7 +217,7 @@ class FormatMetadata {
                                        switch ( $val ) {
                                        case 1:
                                        case 2:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -191,10 +229,10 @@ class FormatMetadata {
                                case 'YResolution':
                                        switch ( $resolutionunit ) {
                                                case 2:
-                                                       $val = self::msg( 'XYResolution', 'i', self::formatNum( $val ) );
+                                                       $val = $this->exifMsg( 'XYResolution', 'i', $this->formatNum( $val ) );
                                                        break;
                                                case 3:
-                                                       $val = self::msg( 'XYResolution', 'c', self::formatNum( $val ) );
+                                                       $val = $this->exifMsg( 'XYResolution', 'c', $this->formatNum( $val ) );
                                                        break;
                                                default:
                                                        /* If not recognized, display as is. */
@@ -210,7 +248,7 @@ class FormatMetadata {
                                case 'ColorSpace':
                                        switch ( $val ) {
                                        case 1: case 65535:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -221,7 +259,7 @@ class FormatMetadata {
                                case 'ComponentsConfiguration':
                                        switch ( $val ) {
                                        case 0: case 1: case 2: case 3: case 4: case 5: case 6:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -238,12 +276,12 @@ class FormatMetadata {
                                case 'dc-date':
                                case 'DateTimeMetadata':
                                        if ( $val == '0000:00:00 00:00:00' || $val == '    :  :     :  :  ' ) {
-                                               $val = wfMessage( 'exif-unknowndate' )->text();
+                                               $val = $this->msg( 'exif-unknowndate' )->text();
                                        } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/D', $val ) ) {
                                                // Full date.
                                                $time = wfTimestamp( TS_MW, $val );
                                                if ( $time && intval( $time ) > 0 ) {
-                                                       $val = $wgLang->timeanddate( $time );
+                                                       $val = $this->getLanguage()->timeanddate( $time );
                                                }
                                        } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d)$/D', $val ) ) {
                                                // No second field. Still format the same
@@ -251,7 +289,7 @@ class FormatMetadata {
                                                // but second still available in api
                                                $time = wfTimestamp( TS_MW, $val . ':00' );
                                                if ( $time && intval( $time ) > 0 ) {
-                                                       $val = $wgLang->timeanddate( $time );
+                                                       $val = $this->getLanguage()->timeanddate( $time );
                                                }
                                        } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d)$/D', $val ) ) {
                                                // If only the date but not the time is filled in.
@@ -260,7 +298,7 @@ class FormatMetadata {
                                                        . substr( $val, 8, 2 )
                                                        . '000000' );
                                                if ( $time && intval( $time ) > 0 ) {
-                                                       $val = $wgLang->date( $time );
+                                                       $val = $this->getLanguage()->date( $time );
                                                }
                                        }
                                        // else it will just output $val without formatting it.
@@ -269,7 +307,7 @@ class FormatMetadata {
                                case 'ExposureProgram':
                                        switch ( $val ) {
                                        case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -278,13 +316,13 @@ class FormatMetadata {
                                        break;
 
                                case 'SubjectDistance':
-                                       $val = self::msg( $tag, '', self::formatNum( $val ) );
+                                       $val = $this->exifMsg( $tag, '', $this->formatNum( $val ) );
                                        break;
 
                                case 'MeteringMode':
                                        switch ( $val ) {
                                        case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 255:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -297,7 +335,7 @@ class FormatMetadata {
                                        case 0: case 1: case 2: case 3: case 4: case 9: case 10: case 11:
                                        case 12: case 13: case 14: case 15: case 17: case 18: case 19: case 20:
                                        case 21: case 22: case 23: case 24: case 255:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -322,15 +360,15 @@ class FormatMetadata {
                                                        continue;
                                                }
                                                $fullTag = $tag . '-' . $subTag;
-                                               $flashMsgs[] = self::msg( $fullTag, $subValue );
+                                               $flashMsgs[] = $this->exifMsg( $fullTag, $subValue );
                                        }
-                                       $val = $wgLang->commaList( $flashMsgs );
+                                       $val = $this->getLanguage()->commaList( $flashMsgs );
                                        break;
 
                                case 'FocalPlaneResolutionUnit':
                                        switch ( $val ) {
                                        case 2:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -341,7 +379,7 @@ class FormatMetadata {
                                case 'SensingMethod':
                                        switch ( $val ) {
                                        case 1: case 2: case 3: case 4: case 5: case 7: case 8:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -352,7 +390,7 @@ class FormatMetadata {
                                case 'FileSource':
                                        switch ( $val ) {
                                        case 3:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -363,7 +401,7 @@ class FormatMetadata {
                                case 'SceneType':
                                        switch ( $val ) {
                                        case 1:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -374,7 +412,7 @@ class FormatMetadata {
                                case 'CustomRendered':
                                        switch ( $val ) {
                                        case 0: case 1:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -385,7 +423,7 @@ class FormatMetadata {
                                case 'ExposureMode':
                                        switch ( $val ) {
                                        case 0: case 1: case 2:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -396,7 +434,7 @@ class FormatMetadata {
                                case 'WhiteBalance':
                                        switch ( $val ) {
                                        case 0: case 1:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -407,7 +445,7 @@ class FormatMetadata {
                                case 'SceneCaptureType':
                                        switch ( $val ) {
                                        case 0: case 1: case 2: case 3:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -418,7 +456,7 @@ class FormatMetadata {
                                case 'GainControl':
                                        switch ( $val ) {
                                        case 0: case 1: case 2: case 3: case 4:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -429,7 +467,7 @@ class FormatMetadata {
                                case 'Contrast':
                                        switch ( $val ) {
                                        case 0: case 1: case 2:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -440,7 +478,7 @@ class FormatMetadata {
                                case 'Saturation':
                                        switch ( $val ) {
                                        case 0: case 1: case 2:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -451,7 +489,7 @@ class FormatMetadata {
                                case 'Sharpness':
                                        switch ( $val ) {
                                        case 0: case 1: case 2:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -462,7 +500,7 @@ class FormatMetadata {
                                case 'SubjectDistanceRange':
                                        switch ( $val ) {
                                        case 0: case 1: case 2: case 3:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -475,7 +513,7 @@ class FormatMetadata {
                                case 'GPSDestLatitudeRef':
                                        switch ( $val ) {
                                        case 'N': case 'S':
-                                               $val = self::msg( 'GPSLatitude', $val );
+                                               $val = $this->exifMsg( 'GPSLatitude', $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -487,7 +525,7 @@ class FormatMetadata {
                                case 'GPSDestLongitudeRef':
                                        switch ( $val ) {
                                        case 'E': case 'W':
-                                               $val = self::msg( 'GPSLongitude', $val );
+                                               $val = $this->exifMsg( 'GPSLongitude', $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -497,16 +535,16 @@ class FormatMetadata {
 
                                case 'GPSAltitude':
                                        if ( $val < 0 ) {
-                                               $val = self::msg( 'GPSAltitude', 'below-sealevel', self::formatNum( -$val, 3 ) );
+                                               $val = $this->exifMsg( 'GPSAltitude', 'below-sealevel', $this->formatNum( -$val, 3 ) );
                                        } else {
-                                               $val = self::msg( 'GPSAltitude', 'above-sealevel', self::formatNum( $val, 3 ) );
+                                               $val = $this->exifMsg( 'GPSAltitude', 'above-sealevel', $this->formatNum( $val, 3 ) );
                                        }
                                        break;
 
                                case 'GPSStatus':
                                        switch ( $val ) {
                                        case 'A': case 'V':
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -517,7 +555,7 @@ class FormatMetadata {
                                case 'GPSMeasureMode':
                                        switch ( $val ) {
                                        case 2: case 3:
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -530,7 +568,7 @@ class FormatMetadata {
                                case 'GPSDestBearingRef':
                                        switch ( $val ) {
                                        case 'T': case 'M':
-                                               $val = self::msg( 'GPSDirection', $val );
+                                               $val = $this->exifMsg( 'GPSDirection', $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -540,17 +578,17 @@ class FormatMetadata {
 
                                case 'GPSLatitude':
                                case 'GPSDestLatitude':
-                                       $val = self::formatCoords( $val, 'latitude' );
+                                       $val = $this->formatCoords( $val, 'latitude' );
                                        break;
                                case 'GPSLongitude':
                                case 'GPSDestLongitude':
-                                       $val = self::formatCoords( $val, 'longitude' );
+                                       $val = $this->formatCoords( $val, 'longitude' );
                                        break;
 
                                case 'GPSSpeedRef':
                                        switch ( $val ) {
                                        case 'K': case 'M': case 'N':
-                                               $val = self::msg( 'GPSSpeed', $val );
+                                               $val = $this->exifMsg( 'GPSSpeed', $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -561,7 +599,7 @@ class FormatMetadata {
                                case 'GPSDestDistanceRef':
                                        switch ( $val ) {
                                        case 'K': case 'M': case 'N':
-                                               $val = self::msg( 'GPSDestDistance', $val );
+                                               $val = $this->exifMsg( 'GPSDestDistance', $val );
                                                break;
                                        default:
                                                /* If not recognized, display as is. */
@@ -572,15 +610,15 @@ class FormatMetadata {
                                case 'GPSDOP':
                                        // See http://en.wikipedia.org/wiki/Dilution_of_precision_(GPS)
                                        if ( $val <= 2 ) {
-                                               $val = self::msg( $tag, 'excellent', self::formatNum( $val ) );
+                                               $val = $this->exifMsg( $tag, 'excellent', $this->formatNum( $val ) );
                                        } elseif ( $val <= 5 ) {
-                                               $val = self::msg( $tag, 'good', self::formatNum( $val ) );
+                                               $val = $this->exifMsg( $tag, 'good', $this->formatNum( $val ) );
                                        } elseif ( $val <= 10 ) {
-                                               $val = self::msg( $tag, 'moderate', self::formatNum( $val ) );
+                                               $val = $this->exifMsg( $tag, 'moderate', $this->formatNum( $val ) );
                                        } elseif ( $val <= 20 ) {
-                                               $val = self::msg( $tag, 'fair', self::formatNum( $val ) );
+                                               $val = $this->exifMsg( $tag, 'fair', $this->formatNum( $val ) );
                                        } else {
-                                               $val = self::msg( $tag, 'poor', self::formatNum( $val ) );
+                                               $val = $this->exifMsg( $tag, 'poor', $this->formatNum( $val ) );
                                        }
                                        break;
 
@@ -589,41 +627,41 @@ class FormatMetadata {
                                // the make, model and software name to link to their articles.
                                case 'Make':
                                case 'Model':
-                                       $val = self::msg( $tag, '', $val );
+                                       $val = $this->exifMsg( $tag, '', $val );
                                        break;
 
                                case 'Software':
                                        if ( is_array( $val ) ) {
                                                //if its a software, version array.
-                                               $val = wfMessage( 'exif-software-version-value', $val[0], $val[1] )->text();
+                                               $val = $this->msg( 'exif-software-version-value', $val[0], $val[1] )->text();
                                        } else {
-                                               $val = self::msg( $tag, '', $val );
+                                               $val = $this->exifMsg( $tag, '', $val );
                                        }
                                        break;
 
                                case 'ExposureTime':
                                        // Show the pretty fraction as well as decimal version
-                                       $val = wfMessage( 'exif-exposuretime-format',
-                                               self::formatFraction( $val ), self::formatNum( $val ) )->text();
+                                       $val = $this->msg( 'exif-exposuretime-format',
+                                               $this->formatFraction( $val ), $this->formatNum( $val ) )->text();
                                        break;
                                case 'ISOSpeedRatings':
                                        // If its = 65535 that means its at the
                                        // limit of the size of Exif::short and
                                        // is really higher.
                                        if ( $val == '65535' ) {
-                                               $val = self::msg( $tag, 'overflow' );
+                                               $val = $this->exifMsg( $tag, 'overflow' );
                                        } else {
-                                               $val = self::formatNum( $val );
+                                               $val = $this->formatNum( $val );
                                        }
                                        break;
                                case 'FNumber':
-                                       $val = wfMessage( 'exif-fnumber-format',
-                                               self::formatNum( $val ) )->text();
+                                       $val = $this->msg( 'exif-fnumber-format',
+                                               $this->formatNum( $val ) )->text();
                                        break;
 
                                case 'FocalLength': case 'FocalLengthIn35mmFilm':
-                                       $val = wfMessage( 'exif-focallength-format',
-                                               self::formatNum( $val ) )->text();
+                                       $val = $this->msg( 'exif-focallength-format',
+                                               $this->formatNum( $val ) )->text();
                                        break;
 
                                case 'MaxApertureValue':
@@ -637,9 +675,9 @@ class FormatMetadata {
                                        if ( is_numeric( $val ) ) {
                                                $fNumber = pow( 2, $val / 2 );
                                                if ( $fNumber !== false ) {
-                                                       $val = wfMessage( 'exif-maxaperturevalue-value',
-                                                               self::formatNum( $val ),
-                                                               self::formatNum( $fNumber, 2 )
+                                                       $val = $this->msg( 'exif-maxaperturevalue-value',
+                                                               $this->formatNum( $val ),
+                                                               $this->formatNum( $fNumber, 2 )
                                                        )->text();
                                                }
                                        }
@@ -658,7 +696,7 @@ class FormatMetadata {
                                                case 'sci': case 'soi':
                                                case 'spo': case 'war':
                                                case 'wea':
-                                                       $val = self::msg(
+                                                       $val = $this->exifMsg(
                                                                'iimcategory',
                                                                $val
                                                        );
@@ -670,7 +708,7 @@ class FormatMetadata {
                                        // classification. We decode the
                                        // first 2 digits, which provide
                                        // a broad category.
-                                       $val = self::convertNewsCode( $val );
+                                       $val = $this->convertNewsCode( $val );
                                        break;
                                case 'Urgency':
                                        // 1-8 with 1 being highest, 5 normal
@@ -687,7 +725,7 @@ class FormatMetadata {
                                        }
 
                                        if ( $urgency !== '' ) {
-                                               $val = self::msg( 'urgency',
+                                               $val = $this->exifMsg( 'urgency',
                                                        $urgency, $val
                                                );
                                        }
@@ -700,7 +738,7 @@ class FormatMetadata {
                                case 'PixelYDimension':
                                case 'ImageWidth':
                                case 'ImageLength':
-                                       $val = self::formatNum( $val ) . ' ' . wfMessage( 'unit-pixel' )->text();
+                                       $val = $this->formatNum( $val ) . ' ' . $this->msg( 'unit-pixel' )->text();
                                        break;
 
                                // Do not transform fields with pure text.
@@ -783,7 +821,7 @@ class FormatMetadata {
                                case 'ObjectCycle':
                                        switch ( $val ) {
                                        case 'a': case 'p': case 'b':
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        default:
                                                $val = htmlspecialchars( $val );
@@ -793,20 +831,20 @@ class FormatMetadata {
                                case 'Copyrighted':
                                        switch ( $val ) {
                                        case 'True': case 'False':
-                                               $val = self::msg( $tag, $val );
+                                               $val = $this->exifMsg( $tag, $val );
                                                break;
                                        }
                                        break;
                                case 'Rating':
                                        if ( $val == '-1' ) {
-                                               $val = self::msg( $tag, 'rejected' );
+                                               $val = $this->exifMsg( $tag, 'rejected' );
                                        } else {
-                                               $val = self::formatNum( $val );
+                                               $val = $this->formatNum( $val );
                                        }
                                        break;
 
                                case 'LanguageCode':
-                                       $lang = Language::fetchLanguageName( strtolower( $val ), $wgLang->getCode() );
+                                       $lang = Language::fetchLanguageName( strtolower( $val ), $this->getLanguage()->getCode() );
                                        if ( $lang ) {
                                                $val = htmlspecialchars( $lang );
                                        } else {
@@ -815,17 +853,64 @@ class FormatMetadata {
                                        break;
 
                                default:
-                                       $val = self::formatNum( $val );
+                                       $val = $this->formatNum( $val );
                                        break;
                                }
                        }
                        // End formatting values, start flattening arrays.
-                       $vals = self::flattenArray( $vals, $type );
+                       $vals = $this->flattenArrayReal( $vals, $type );
 
                }
                return $tags;
        }
 
+       /**
+        * Flatten an array, using the content language for any messages.
+        *
+        * @param array $vals array of values
+        * @param string $type Type of array (either lang, ul, ol).
+        *     lang = language assoc array with keys being the lang code
+        *     ul = unordered list, ol = ordered list
+        *     type can also come from the '_type' member of $vals.
+        * @param $noHtml Boolean If to avoid returning anything resembling
+        *     html. (Ugly hack for backwards compatibility with old mediawiki).
+        * @param IContextSource $context
+        * @return String single value (in wiki-syntax).
+        * @since 1.23
+        */
+       public static function flattenArrayContentLang( $vals, $type = 'ul', $noHtml = false, $context = false ) {
+               global $wgContLang;
+               $obj = new FormatMetadata;
+               if ( $context ) {
+                       $obj->setContext( $context );
+               }
+               $context = new DerivativeContext( $obj->getContext() );
+               $context->setLanguage( $wgContLang );
+               $obj->setContext( $context );
+               return $obj->flattenArrayReal( $vals, $type, $noHtml );
+       }
+
+       /**
+        * Flatten an array, using the user language for any messages.
+        *
+        * @param array $vals array of values
+        * @param string $type Type of array (either lang, ul, ol).
+        *     lang = language assoc array with keys being the lang code
+        *     ul = unordered list, ol = ordered list
+        *     type can also come from the '_type' member of $vals.
+        * @param $noHtml Boolean If to avoid returning anything resembling
+        *     html. (Ugly hack for backwards compatibility with old mediawiki).
+        * @param IContextSource $context
+        * @return String single value (in wiki-syntax).
+        */
+       public static function flattenArray( $vals, $type = 'ul', $noHtml = false, $context = false ) {
+               $obj = new FormatMetadata;
+               if ( $context ) {
+                       $obj->setContext( $context );
+               }
+               return $obj->flattenArrayReal( $vals, $type, $noHtml );
+       }
+
        /**
         * A function to collapse multivalued tags into a single value.
         * This turns an array of (for example) authors into a bulleted list.
@@ -834,14 +919,19 @@ class FormatMetadata {
         *
         * @param array $vals array of values
         * @param string $type Type of array (either lang, ul, ol).
-        * lang = language assoc array with keys being the lang code
-        * ul = unordered list, ol = ordered list
-        * type can also come from the '_type' member of $vals.
+        *     lang = language assoc array with keys being the lang code
+        *     ul = unordered list, ol = ordered list
+        *     type can also come from the '_type' member of $vals.
         * @param $noHtml Boolean If to avoid returning anything resembling
-        * html. (Ugly hack for backwards compatibility with old mediawiki).
+        *     html. (Ugly hack for backwards compatibility with old mediawiki).
         * @return String single value (in wiki-syntax).
+        * @since 1.23
         */
-       public static function flattenArray( $vals, $type = 'ul', $noHtml = false ) {
+       public function flattenArrayReal( $vals, $type = 'ul', $noHtml = false ) {
+               if ( !is_array( $vals ) ) {
+                       return $vals; // do nothing if not an array;
+               }
+
                if ( isset( $vals['_type'] ) ) {
                        $type = $vals['_type'];
                        unset( $vals['_type'] );
@@ -862,7 +952,6 @@ class FormatMetadata {
                 * languages, we don't want to show them all by default
                 */
                else {
-                       global $wgContLang;
                        switch ( $type ) {
                        case 'lang':
                                // Display default, followed by ContLang,
@@ -873,7 +962,7 @@ class FormatMetadata {
 
                                $content = '';
 
-                               $cLang = $wgContLang->getCode();
+                               $priorityLanguages = $this->getPriorityLanguages();
                                $defaultItem = false;
                                $defaultLang = false;
 
@@ -888,18 +977,24 @@ class FormatMetadata {
                                        $defaultItem = $vals['x-default'];
                                        unset( $vals['x-default'] );
                                }
-                               // Do contentLanguage.
-                               if ( isset( $vals[$cLang] ) ) {
-                                       $isDefault = false;
-                                       if ( $vals[$cLang] === $defaultItem ) {
-                                               $defaultItem = false;
-                                               $isDefault = true;
-                                       }
-                                       $content .= self::langItem(
-                                               $vals[$cLang], $cLang,
-                                               $isDefault, $noHtml );
+                               foreach( $priorityLanguages as $pLang ) {
+                                       if ( isset( $vals[$pLang] ) ) {
+                                               $isDefault = false;
+                                               if ( $vals[$pLang] === $defaultItem ) {
+                                                       $defaultItem = false;
+                                                       $isDefault = true;
+                                               }
+                                               $content .= $this->langItem(
+                                                       $vals[$pLang], $pLang,
+                                                       $isDefault, $noHtml );
+
+                                               unset( $vals[$pLang] );
 
-                                       unset( $vals[$cLang] );
+                                               if ( $this->singleLang ) {
+                                                       return Html::rawElement( 'span',
+                                                               array( 'lang' => $pLang ), $vals[$pLang] );
+                                               }
+                                       }
                                }
 
                                // Now do the rest.
@@ -908,13 +1003,20 @@ class FormatMetadata {
                                                $defaultLang = $lang;
                                                continue;
                                        }
-                                       $content .= self::langItem( $item,
+                                       $content .= $this->langItem( $item,
                                                $lang, false, $noHtml );
+                                       if ( $this->singleLang ) {
+                                               return Html::rawElement( 'span',
+                                                       array( 'lang' => $lang ), $item );
+                                       }
                                }
                                if ( $defaultItem !== false ) {
-                                       $content = self::langItem( $defaultItem,
+                                       $content = $this->langItem( $defaultItem,
                                                $defaultLang, true, $noHtml ) .
                                                $content;
+                                       if ( $this->singleLang ) {
+                                               return $defaultItem;
+                                       }
                                }
                                if ( $noHtml ) {
                                        return $content;
@@ -947,7 +1049,7 @@ class FormatMetadata {
         * @return string language item (Note: despite how this looks,
         * this is treated as wikitext not html).
         */
-       private static function langItem( $value, $lang, $default = false, $noHtml = false ) {
+       private function langItem( $value, $lang, $default = false, $noHtml = false ) {
                if ( $lang === false && $default === false ) {
                        throw new MWException( '$lang and $default cannot both '
                                . 'be false.' );
@@ -961,13 +1063,12 @@ class FormatMetadata {
                }
 
                if ( $lang === false ) {
+                       $msg = $this->msg( 'metadata-langitem-default', $wrappedValue );
                        if ( $noHtml ) {
-                               return wfMessage( 'metadata-langitem-default',
-                                       $wrappedValue )->text() . "\n\n";
+                               return $msg->text() . "\n\n";
                        } /* else */
                        return '<li class="mw-metadata-lang-default">'
-                               . wfMessage( 'metadata-langitem-default',
-                                       $wrappedValue )->text()
+                               . $msg->text()
                                . "</li>\n";
                }
 
@@ -984,9 +1085,9 @@ class FormatMetadata {
                }
                // else we have a language specified
 
+               $msg = $this->msg( 'metadata-langitem', $wrappedValue, $langName, $lang );
                if ( $noHtml ) {
-                       return '*' . wfMessage( 'metadata-langitem',
-                               $wrappedValue, $langName, $lang )->text();
+                       return '*' . $msg->text();
                } /* else: */
 
                $item = '<li class="mw-metadata-lang-code-'
@@ -995,8 +1096,7 @@ class FormatMetadata {
                        $item .= ' mw-metadata-lang-default';
                }
                $item .= '" lang="' . $lang . '">';
-               $item .= wfMessage( 'metadata-langitem',
-                       $wrappedValue, $langName, $lang )->text();
+               $item .= $msg->text();
                $item .= "</li>\n";
                return $item;
        }
@@ -1010,15 +1110,15 @@ class FormatMetadata {
         * @param string $val the value of the tag
         * @param string $arg an argument to pass ($1)
         * @param string $arg2 a 2nd argument to pass ($2)
-        * @return string A wfMessage of "exif-$tag-$val" in lower case
+        * @return string The text content of "exif-$tag-$val" message in lower case
         */
-       static function msg( $tag, $val, $arg = null, $arg2 = null ) {
+       private function exifMsg( $tag, $val, $arg = null, $arg2 = null ) {
                global $wgContLang;
 
                if ( $val === '' ) {
                        $val = 'value';
                }
-               return wfMessage( $wgContLang->lc( "exif-$tag-$val" ), $arg, $arg2 )->text();
+               return $this->msg( $wgContLang->lc( "exif-$tag-$val" ), $arg, $arg2 )->text();
        }
 
        /**
@@ -1029,15 +1129,14 @@ class FormatMetadata {
         * @param $round float|int|bool digits to round to or false.
         * @return mixed A floating point number or whatever we were fed
         */
-       static function formatNum( $num, $round = false ) {
-               global $wgLang;
+       private function formatNum( $num, $round = false ) {
                $m = array();
                if ( is_array( $num ) ) {
                        $out = array();
                        foreach ( $num as $number ) {
-                               $out[] = self::formatNum( $number );
+                               $out[] = $this->formatNum( $number );
                        }
-                       return $wgLang->commaList( $out );
+                       return $this->getLanguage()->commaList( $out );
                }
                if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
                        if ( $m[2] != 0 ) {
@@ -1049,12 +1148,12 @@ class FormatMetadata {
                                $newNum = $num;
                        }
 
-                       return $wgLang->formatNum( $newNum );
+                       return $this->getLanguage()->formatNum( $newNum );
                } else {
                        if ( is_numeric( $num ) && $round !== false ) {
                                $num = round( $num, $round );
                        }
-                       return $wgLang->formatNum( $num );
+                       return $this->getLanguage()->formatNum( $num );
                }
        }
 
@@ -1066,18 +1165,18 @@ class FormatMetadata {
         * @param $num Mixed: the value to format
         * @return mixed A floating point number or whatever we were fed
         */
-       static function formatFraction( $num ) {
+       private function formatFraction( $num ) {
                $m = array();
                if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
                        $numerator = intval( $m[1] );
                        $denominator = intval( $m[2] );
-                       $gcd = self::gcd( abs( $numerator ), $denominator );
+                       $gcd = $this->gcd( abs( $numerator ), $denominator );
                        if ( $gcd != 0 ) {
                                // 0 shouldn't happen! ;)
-                               return self::formatNum( $numerator / $gcd ) . '/' . self::formatNum( $denominator / $gcd );
+                               return $this->formatNum( $numerator / $gcd ) . '/' . $this->formatNum( $denominator / $gcd );
                        }
                }
-               return self::formatNum( $num );
+               return $this->formatNum( $num );
        }
 
        /**
@@ -1088,7 +1187,7 @@ class FormatMetadata {
         * @return int
         * @private
         */
-       static function gcd( $a, $b ) {
+       private function gcd( $a, $b ) {
                /*
                        // http://en.wikipedia.org/wiki/Euclidean_algorithm
                        // Recursive form would be:
@@ -1119,7 +1218,7 @@ class FormatMetadata {
         * @param string $val The 8 digit news code.
         * @return string The human readable form
         */
-       private static function convertNewsCode( $val ) {
+       private function convertNewsCode( $val ) {
                if ( !preg_match( '/^\d{8}$/D', $val ) ) {
                        // Not a valid news code.
                        return $val;
@@ -1179,8 +1278,8 @@ class FormatMetadata {
                                break;
                }
                if ( $cat !== '' ) {
-                       $catMsg = self::msg( 'iimcategory', $cat );
-                       $val = self::msg( 'subjectnewscode', '', $val, $catMsg );
+                       $catMsg = $this->exifMsg( 'iimcategory', $cat );
+                       $val = $this->exifMsg( 'subjectnewscode', '', $val, $catMsg );
                }
                return $val;
        }
@@ -1193,7 +1292,7 @@ class FormatMetadata {
         * @param string $type latitude or longitude (for if its a NWS or E)
         * @return mixed A floating point number or whatever we were fed
         */
-       static function formatCoords( $coord, $type ) {
+       private function formatCoords( $coord, $type ) {
                $ref = '';
                if ( $coord < 0 ) {
                        $nCoord = -$coord;
@@ -1215,11 +1314,11 @@ class FormatMetadata {
                $min = floor( ( $nCoord - $deg ) * 60.0 );
                $sec = round( ( ( $nCoord - $deg ) - $min / 60 ) * 3600, 2 );
 
-               $deg = self::formatNum( $deg );
-               $min = self::formatNum( $min );
-               $sec = self::formatNum( $sec );
+               $deg = $this->formatNum( $deg );
+               $min = $this->formatNum( $min );
+               $sec = $this->formatNum( $sec );
 
-               return wfMessage( 'exif-coordinate-format', $deg, $min, $sec, $ref, $coord )->text();
+               return $this->msg( 'exif-coordinate-format', $deg, $min, $sec, $ref, $coord )->text();
        }
 
        /**
@@ -1235,8 +1334,9 @@ class FormatMetadata {
         * public.
         *
         * @return String of html-ish looking wikitext
+        * @since 1.23 no longer static
         */
-       public static function collapseContactInfo( $vals ) {
+       public function collapseContactInfo( $vals ) {
                if ( !( isset( $vals['CiAdrExtadr'] )
                        || isset( $vals['CiAdrCity'] )
                        || isset( $vals['CiAdrCtry'] )
@@ -1258,7 +1358,7 @@ class FormatMetadata {
                        foreach ( $vals as &$val ) {
                                $val = htmlspecialchars( $val );
                        }
-                       return self::flattenArray( $vals );
+                       return $this->flattenArrayReal( $vals );
                } else {
                        // We have a real ContactInfo field.
                        // Its unclear if all these fields have to be
@@ -1340,11 +1440,301 @@ class FormatMetadata {
                                        . htmlspecialchars( $vals['CiUrlWork'] )
                                        . '</span>';
                        }
-                       return wfMessage( 'exif-contact-value', $email, $url,
+                       return $this->msg( 'exif-contact-value', $email, $url,
                                $street, $city, $region, $postal, $country,
                                $tel )->text();
                }
        }
+
+       /**
+        * Get a list of fields that are visible by default.
+        *
+        * @return array
+        * @since 1.23
+        */
+       public static function getVisibleFields() {
+               $fields = array();
+               $lines = explode( "\n", wfMessage( 'metadata-fields' )->inContentLanguage()->text() );
+               foreach ( $lines as $line ) {
+                       $matches = array();
+                       if ( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
+                               $fields[] = $matches[1];
+                       }
+               }
+               $fields = array_map( 'strtolower', $fields );
+               return $fields;
+       }
+
+       /**
+        * Get an array of extended metadata. (See the imageinfo API for format.)
+        *
+        * @param File $file File to use
+        * @return array [<property name> => ['value' => <value>]], or [] on error
+        * @since 1.23
+        */
+       public function fetchExtendedMetadata( File $file ) {
+               global $wgMemc;
+
+               wfProfileIn( __METHOD__ );
+
+               // If revision deleted, exit immediately
+               if ( $file->isDeleted( File::DELETED_FILE ) ) {
+                       return array();
+               }
+
+               $cacheKey = wfMemcKey(
+                       'getExtendedMetadata',
+                       $this->getLanguage()->getCode(),
+                       (int) $this->singleLang,
+                       $file->getSha1()
+               );
+
+               $cachedValue = $wgMemc->get( $cacheKey );
+               if (
+                       $cachedValue
+                       && wfRunHooks( 'ValidateExtendedMetadataCache', array( $cachedValue['timestamp'], $file ) )
+               ) {
+                       $extendedMetadata = $cachedValue['data'];
+               } else {
+                       $maxCacheTime = ( $file instanceof ForeignAPIFile ) ? 60 * 60 * 12 : 60 * 60 * 24 * 30;
+                       $fileMetadata = $this->getExtendedMetadataFromFile( $file );
+                       $extendedMetadata = $this->getExtendedMetadataFromHook( $file, $fileMetadata, $maxCacheTime );
+                       if ( $this->singleLang ) {
+                               $this->resolveMultilangMetadata( $extendedMetadata );
+                       }
+                       // Make sure the metadata won't break the API when an XML format is used.
+                       // This is an API-specific function so it would be cleaner to call it from
+                       // outside fetchExtendedMetadata, but this way we don't need to redo the
+                       // computation on a cache hit.
+                       $this->sanitizeArrayForXml($extendedMetadata);
+                       $valueToCache = array( 'data' => $extendedMetadata, 'timestamp' => wfTimestampNow() );
+                       $wgMemc->set( $cacheKey, $valueToCache, $maxCacheTime );
+               }
+
+               wfProfileOut( __METHOD__ );
+               return $extendedMetadata;
+       }
+
+       /**
+        * Get file-based metadata in standardized format.
+        *
+        * Note that for a remote file, this might return metadata supplied by extensions.
+        *
+        * @param File $file File to use
+        * @return array [<property name> => ['value' => <value>]], or [] on error
+        * @since 1.23
+        */
+       protected function getExtendedMetadataFromFile( File $file ) {
+               // If this is a remote file accessed via an API request, we already
+               // have remote metadata so we just ignore any local one
+               if ( $file instanceof ForeignAPIFile ) {
+                       // in case of error we pretend no metadata - this will get cached. Might or might not be a good idea.
+                       return $file->getExtendedMetadata() ?: array();
+               }
+
+               wfProfileIn( __METHOD__ );
+
+               $uploadDate = wfTimestamp( TS_ISO_8601, $file->getTimestamp() );
+
+               $fileMetadata = array(
+                       // This is modification time, which is close to "upload" time.
+                       'DateTime' => array(
+                               'value' => $uploadDate,
+                               'source' => 'mediawiki-metadata',
+                       ),
+               );
+
+               $title = $file->getTitle();
+               if ( $title ) {
+                       $text = $title->getText();
+                       $pos = strrpos( $text, '.' );
+
+                       if ( $pos ) {
+                               $name = substr( $text, 0, $pos );
+                       } else {
+                               $name = $text;
+                       }
+
+                       $fileMetadata[ 'ObjectName' ] = array(
+                               'value' => $name,
+                               'source' => 'mediawiki-metadata',
+                       );
+               }
+
+               $common = $file->getCommonMetaArray();
+
+               foreach ( $common as $key => $value ) {
+                       $fileMetadata[$key] = array(
+                               'value' => $value,
+                               'source' => 'file-metadata',
+                       );
+               }
+
+               wfProfileOut( __METHOD__ );
+               return $fileMetadata;
+       }
+
+       /**
+        * Get additional metadata from hooks in standardized format.
+        *
+        * @param File $file File to use
+        * @param array $extendedMetadata
+        * @param int $maxCacheTime hook handlers might use this parameter to override cache time
+        *
+        * @return array [<property name> => ['value' => <value>]], or [] on error
+        * @since 1.23
+        */
+       protected function getExtendedMetadataFromHook( File $file, array $extendedMetadata, &$maxCacheTime ) {
+               wfProfileIn( __METHOD__ );
+
+               wfRunHooks( 'GetExtendedMetadata', array(
+                       &$extendedMetadata,
+                       $file,
+                       $this->getContext(),
+                       $this->singleLang,
+                       &$maxCacheTime
+               ) );
+
+               $visible = array_flip( self::getVisibleFields() );
+               foreach ( $extendedMetadata as $key => $value ) {
+                       if ( !isset( $visible[ strtolower( $key ) ] ) ) {
+                               $extendedMetadata[$key]['hidden'] = '';
+                       }
+               }
+
+               wfProfileOut( __METHOD__ );
+               return $extendedMetadata;
+       }
+
+       /**
+        * Turns an XMP-style multilang array into a single value.
+        * If the value is not a multilang array, it is returned unchanged.
+        * See mediawiki.org/wiki/Manual:File_metadata_handling#Multi-language_array_format
+        * @param mixed $value
+        * @return mixed value in best language, null if there were no languages at all
+        * @since 1.23
+        */
+       protected function resolveMultilangValue( $value )
+       {
+               if (
+                       !is_array( $value )
+                       || !isset( $value['_type'] )
+                       || $value['_type'] != 'lang'
+               ) {
+                       return $value; // do nothing if not a multilang array
+               }
+
+               // choose the language best matching user or site settings
+               $priorityLanguages = $this->getPriorityLanguages();
+               foreach( $priorityLanguages as $lang ) {
+                       if ( isset( $value[$lang] ) ) {
+                               return $value[$lang];
+                       }
+               }
+
+               // otherwise go with the default language, if set
+               if ( isset( $value['x-default'] ) ) {
+                       return $value['x-default'];
+               }
+
+               // otherwise just return any one language
+               unset($value['_type']);
+               if (!empty($value)) {
+                       return reset($value);
+               }
+
+               // this should not happen; signal error
+               return null;
+       }
+
+       /**
+        * Takes an array returned by the getExtendedMetadata* functions,
+        * and resolves multi-language values in it.
+        * @param array $metadata
+        * @since 1.23
+        */
+       protected function resolveMultilangMetadata( &$metadata ) {
+               if ( !is_array( $metadata ) ) {
+                       return;
+               }
+               foreach ( $metadata as &$field ) {
+                       if ( isset( $field['value'] ) ) {
+                               $field['value'] = $this->resolveMultilangValue( $field['value'] );
+                       }
+               }
+       }
+
+       /**
+        * Makes sure the given array is a valid API response fragment
+        * (can be transformed into XML)
+        * @param array $arr
+        */
+       protected function sanitizeArrayForXml( &$arr ) {
+               if ( !is_array( $arr ) ) {
+                       return;
+               }
+
+               $counter = 1;
+               foreach ( $arr as $key => &$value ) {
+                       $sanitizedKey = $this->sanitizeKeyForXml( $key );
+                       if ( $sanitizedKey !== $key ) {
+                               if ( isset( $arr[$sanitizedKey] ) ) {
+                                       // Make the sanitized keys hopefully unique.
+                                       // To make it definitely unique would be too much effort, given that
+                                       // sanitizing is only needed for misformatted metadata anyway, but
+                                       // this at least covers the case when $arr is numeric.
+                                       $sanitizedKey .= $counter;
+                                       ++$counter;
+                               }
+                               $arr[$sanitizedKey] = $arr[$key];
+                               unset( $arr[$key] );
+                       }
+                       if ( is_array( $value ) ) {
+                               $this->sanitizeArrayForXml( $value );
+                       }
+               }
+       }
+
+       /**
+        * Turns a string into a valid XML identifier.
+        * Used to ensure that keys of an associative array in the
+        * API response do not break the XML formatter.
+        * @param string $key
+        * @return string
+        * @since 1.23
+        */
+       protected function sanitizeKeyForXml( $key ) {
+               // drop all characters which are not valid in an XML tag name
+               // a bunch of non-ASCII letters would be valid but probably won't
+               // be used so we take the easy way
+               $key = preg_replace( '/[^a-zA-z0-9_:.-]/', '', $key );
+               // drop characters which are invalid at the first position
+               $key = preg_replace( '/^[\d-.]+/', '', $key );
+
+               if ( $key == '' ) {
+                       $key = '_';
+               }
+
+               // special case for an internal keyword
+               if ( $key == '_element' ) {
+                       $key = 'element';
+               }
+
+               return $key;
+       }
+
+       /**
+        * Returns a list of languages (first is best) to use when formatting multilang fields,
+        * based on user and site preferences.
+        * @return array
+        * @since 1.23
+        */
+       protected function getPriorityLanguages()
+       {
+               $priorityLanguages = Language::getFallbacksIncludingSiteLanguage( $this->getLanguage()->getCode() );
+               $priorityLanguages = array_merge( (array) $this->getLanguage()->getCode(), $priorityLanguages[0], $priorityLanguages[1] );
+               return $priorityLanguages;
+       }
 }
 
 /** For compatability with old FormatExif class
index 608fb25..19635da 100644 (file)
@@ -47,20 +47,31 @@ class GIFHandler extends BitmapHandler {
         * @return array|bool
         */
        function formatMetadata( $image ) {
+               $meta = $this->getCommonMetaArray( $image );
+               if ( count( $meta ) === 0 ) {
+                       return false;
+               }
+
+               return $this->formatMetadataHelper( $meta );
+       }
+
+       /**
+        * Return the standard metadata elements for #filemetadata parser func.
+        * @param File $image
+        * @return array|bool
+        */
+       public function getCommonMetaArray( File $image ) {
                $meta = $image->getMetadata();
 
                if ( !$meta ) {
-                       return false;
+                       return array();
                }
                $meta = unserialize( $meta );
-               if ( !isset( $meta['metadata'] ) || count( $meta['metadata'] ) <= 1 ) {
-                       return false;
-               }
-
-               if ( isset( $meta['metadata']['_MW_GIF_VERSION'] ) ) {
-                       unset( $meta['metadata']['_MW_GIF_VERSION'] );
+               if ( !isset( $meta['metadata'] ) ) {
+                       return array();
                }
-               return $this->formatMetadataHelper( $meta['metadata'] );
+               unset( $meta['metadata']['_MW_GIF_VERSION'] );
+               return $meta['metadata'];
        }
 
        /**
old mode 100644 (file)
new mode 100755 (executable)
index 779e23c..ddb8efd
@@ -187,6 +187,43 @@ abstract class MediaHandler {
                return self::METADATA_GOOD;
        }
 
+       /**
+        * Get an array of standard (FormatMetadata type) metadata values.
+        *
+        * The returned data is largely the same as that from getMetadata(),
+        * but formatted in a standard, stable, handler-independent way.
+        * The idea being that some values like ImageDescription or Artist
+        * are universal and should be retrievable in a handler generic way.
+        *
+        * The specific properties are the type of properties that can be
+        * handled by the FormatMetadata class. These values are exposed to the
+        * user via the filemetadata parser function.
+        *
+        * Details of the response format of this function can be found at
+        * https://www.mediawiki.org/wiki/Manual:File_metadata_handling
+        * tl/dr: the response is an associative array of
+        * properties keyed by name, but the value can be complex. You probably
+        * want to call one of the FormatMetadata::flatten* functions on the
+        * property values before using them, or call
+        * FormatMetadata::getFormattedData() on the full response array, which
+        * transforms all values into prettified, human-readable text.
+        *
+        * Subclasses overriding this function must return a value which is a
+        * valid API response fragment (all associative array keys are valid
+        * XML tagnames).
+        *
+        * Note, if the file simply has no metadata, but the handler supports
+        * this interface, it should return an empty array, not false.
+        *
+        * @param File $file
+        *
+        * @return Array or false if interface not supported
+        * @since 1.23
+        */
+       public function getCommonMetaArray( File $file ) {
+               return false;
+       }
+
        /**
         * Get a MediaTransformOutput object representing an alternate of the transformed
         * output which will call an intermediary thumbnail assist script.
@@ -436,16 +473,7 @@ abstract class MediaHandler {
         * @access protected
         */
        function visibleMetadataFields() {
-               $fields = array();
-               $lines = explode( "\n", wfMessage( 'metadata-fields' )->inContentLanguage()->text() );
-               foreach ( $lines as $line ) {
-                       $matches = array();
-                       if ( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
-                               $fields[] = $matches[1];
-                       }
-               }
-               $fields = array_map( 'strtolower', $fields );
-               return $fields;
+               return FormatMetadata::getVisibleFields();
        }
 
        /**
index 98f1386..d2c17ef 100644 (file)
@@ -52,20 +52,32 @@ class PNGHandler extends BitmapHandler {
         * @return array|bool
         */
        function formatMetadata( $image ) {
+               $meta = $this->getCommonMetaArray( $image );
+               if ( count( $meta ) === 0 ) {
+                       return false;
+               }
+
+               return $this->formatMetadataHelper( $meta );
+       }
+
+       /**
+        * Get a file type independent array of metadata.
+        *
+        * @param $image File
+        * @return array The metadata array
+        */
+       public function getCommonMetaArray( File $image ) {
                $meta = $image->getMetadata();
 
                if ( !$meta ) {
-                       return false;
+                       return array();
                }
                $meta = unserialize( $meta );
-               if ( !isset( $meta['metadata'] ) || count( $meta['metadata'] ) <= 1 ) {
-                       return false;
-               }
-
-               if ( isset( $meta['metadata']['_MW_PNG_VERSION'] ) ) {
-                       unset( $meta['metadata']['_MW_PNG_VERSION'] );
+               if ( !isset( $meta['metadata'] ) ) {
+                       return array();
                }
-               return $this->formatMetadataHelper( $meta['metadata'] );
+               unset( $meta['metadata']['_MW_PNG_VERSION'] );
+               return $meta['metadata'];
        }
 
        /**
index 72a9696..d6f8483 100644 (file)
 class SvgHandler extends ImageHandler {
        const SVG_METADATA_VERSION = 2;
 
+       /**
+        * A list of metadata tags that can be converted
+        * to the commonly used exif tags. This allows messages
+        * to be reused, and consistent tag names for {{#formatmetadata:..}}
+        */
+       private static $metaConversion = array(
+               'originalwidth' => 'ImageWidth',
+               'originalheight' => 'ImageLength',
+               'description' => 'ImageDescription',
+               'title' => 'ObjectName',
+       );
+
        function isEnabled() {
                global $wgSVGConverters, $wgSVGConverter;
                if ( !isset( $wgSVGConverters[$wgSVGConverter] ) ) {
@@ -340,19 +352,11 @@ class SvgHandler extends ImageHandler {
                // Sort fields into visible and collapsed
                $visibleFields = $this->visibleMetadataFields();
 
-               // Rename fields to be compatible with exif, so that
-               // the labels for these fields work and reuse existing messages.
-               $conversion = array(
-                       'originalwidth' => 'imagewidth',
-                       'originalheight' => 'imagelength',
-                       'description' => 'imagedescription',
-                       'title' => 'objectname',
-               );
                $showMeta = false;
                foreach ( $metadata as $name => $value ) {
                        $tag = strtolower( $name );
-                       if ( isset( $conversion[$tag] ) ) {
-                               $tag = $conversion[$tag];
+                       if ( isset( self::$metaConversion[$tag] ) ) {
+                               $tag = strtolower( self::$metaConversion[$tag] );
                        } else {
                                // Do not output other metadata not in list
                                continue;
@@ -368,7 +372,6 @@ class SvgHandler extends ImageHandler {
                return $showMeta ? $result : false;
        }
 
-
        /**
         * @param string $name Parameter name
         * @param $string $value Parameter value
@@ -431,4 +434,29 @@ class SvgHandler extends ImageHandler {
                        'lang' => $params['lang'],
                );
        }
+
+       public function getCommonMetaArray( File $file ) {
+               $metadata = $file->getMetadata();
+               if ( !$metadata ) {
+                       return array();
+               }
+               $metadata = $this->unpackMetadata( $metadata );
+               if ( !$metadata || isset( $metadata['error'] ) ) {
+                       return array();
+               }
+               $stdMetadata = array();
+               foreach ( $metadata as $name => $value ) {
+                       $tag = strtolower( $name );
+                       if ( $tag === 'originalwidth' || $tag === 'originalheight' ) {
+                               // Skip these. In the exif metadata stuff, it is assumed these
+                               // are measured in px, which is not the case here.
+                               continue;
+                       }
+                       if ( isset( self::$metaConversion[$tag] ) ) {
+                               $tag = self::$metaConversion[$tag];
+                               $stdMetadata[$tag] = $value;
+                       }
+               }
+               return $stdMetadata;
+       }
 }
index 5118366..ad913b1 100644 (file)
@@ -39,6 +39,7 @@ if( defined( 'PRETTY_UTF8' ) ) {
 } else {
        /**
         * @ignore
+        * @param string $string
         * @return string
         */
        function pretty( $string ) {
index 4600402..b629776 100644 (file)
@@ -251,9 +251,8 @@ class LinkHolderArray {
        }
 
        /**
-        * @todo FIXME: Update documentation. makeLinkObj() is deprecated.
         * Replace <!--LINK--> link placeholders with actual links, in the buffer
-        * Placeholders created in Skin::makeLinkObj()
+        *
         * @return array of link CSS classes, indexed by PDBK.
         */
        function replace( &$text ) {
index 1f14223..6e9e06e 100644 (file)
@@ -1863,7 +1863,9 @@ class Parser {
                if ( $useLinkPrefixExtension ) {
                        # Match the end of a line for a word that's not followed by whitespace,
                        # e.g. in the case of 'The Arab al[[Razi]]', 'al' will be matched
-                       $e2 = wfMessage( 'linkprefix' )->inContentLanguage()->text();
+                       global $wgContLang;
+                       $charset = $wgContLang->linkPrefixCharset();
+                       $e2 = "/^((?>.*[^$charset]|))(.+)$/sDu";
                }
 
                if ( is_null( $this->mTitle ) ) {
index 20f6e0b..b38f448 100644 (file)
@@ -41,7 +41,8 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                        $wgVariantArticlePath, $wgActionPaths, $wgVersion,
                        $wgEnableAPI, $wgEnableWriteAPI, $wgDBname,
                        $wgSitename, $wgFileExtensions, $wgExtensionAssetsPath,
-                       $wgCookiePrefix, $wgResourceLoaderMaxQueryLength;
+                       $wgCookiePrefix, $wgResourceLoaderMaxQueryLength,
+                       $wgResourceLoaderStorageEnabled, $wgResourceLoaderStorageVersion;
 
                $mainPage = Title::newMainPage();
 
@@ -96,6 +97,8 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                        'wgResourceLoaderMaxQueryLength' => $wgResourceLoaderMaxQueryLength,
                        'wgCaseSensitiveNamespaces' => $caseSensitiveNamespaces,
                        'wgLegalTitleChars' => Title::convertByteClassToUnicodeClass( Title::legalChars() ),
+                       'wgResourceLoaderStorageVersion' => $wgResourceLoaderStorageVersion,
+                       'wgResourceLoaderStorageEnabled' => $wgResourceLoaderStorageEnabled,
                );
 
                wfRunHooks( 'ResourceLoaderGetConfigVars', array( &$vars ) );
diff --git a/includes/search/SearchUpdate.php b/includes/search/SearchUpdate.php
deleted file mode 100644 (file)
index 82a413e..0000000
+++ /dev/null
@@ -1,185 +0,0 @@
-<?php
-/**
- * Search index updater
- *
- * See deferred.txt
- *
- * 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 Search
- */
-
-/**
- * Database independant search index updater
- *
- * @ingroup Search
- */
-class SearchUpdate implements DeferrableUpdate {
-       /**
-        * Page id being updated
-        * @var int
-        */
-       private $id = 0;
-
-       /**
-        * Title we're updating
-        * @var Title
-        */
-       private $title;
-
-       /**
-        * Content of the page (not text)
-        * @var Content|false
-        */
-       private $content;
-
-       /**
-        * Constructor
-        *
-        * @param int $id Page id to update
-        * @param Title|string $title Title of page to update
-        * @param Content|string|false $c Content of the page to update.
-        *  If a Content object, text will be gotten from it. String is for back-compat.
-        *  Passing false tells the backend to just update the title, not the content
-        */
-       public function __construct( $id, $title, $c = false ) {
-               if ( is_string( $title ) ) {
-                       $nt = Title::newFromText( $title );
-               } else {
-                       $nt = $title;
-               }
-
-               if ( $nt ) {
-                       $this->id = $id;
-                       // is_string() check is back-compat for ApprovedRevs
-                       if ( is_string( $c ) ) {
-                               $this->content = new TextContent( $c );
-                       } else {
-                               $this->content = $c ?: false;
-                       }
-                       $this->title = $nt;
-               } else {
-                       wfDebug( "SearchUpdate object created with invalid title '$title'\n" );
-               }
-       }
-
-       /**
-        * Perform actual update for the entry
-        */
-       public function doUpdate() {
-               global $wgDisableSearchUpdate;
-
-               if ( $wgDisableSearchUpdate || !$this->id ) {
-                       return;
-               }
-
-               wfProfileIn( __METHOD__ );
-
-               $page = WikiPage::newFromId( $this->id, WikiPage::READ_LATEST );
-               $indexTitle = Title::indexTitle( $this->title->getNamespace(), $this->title->getText() );
-
-               foreach ( SearchEngine::getSearchTypes() as $type ) {
-                       $search = SearchEngine::create( $type );
-                       if ( !$search->supports( 'search-update' ) ) {
-                               continue;
-                       }
-
-                       $normalTitle = $search->normalizeText( $indexTitle );
-
-                       if ( $page === null ) {
-                               $search->delete( $this->id, $normalTitle );
-                               continue;
-                       } elseif ( $this->content === false ) {
-                               $search->updateTitle( $this->id, $normalTitle );
-                               continue;
-                       }
-
-                       $text = $search->getTextFromContent( $this->title, $this->content );
-                       if ( !$search->textAlreadyUpdatedForIndex() ) {
-                               $text = self::updateText( $text );
-                       }
-
-                       # Perform the actual update
-                       $search->update( $this->id, $normalTitle, $search->normalizeText( $text ) );
-               }
-
-               wfProfileOut( __METHOD__ );
-       }
-
-       /**
-        * Clean text for indexing. Only really suitable for indexing in databases.
-        * If you're using a real search engine, you'll probably want to override
-        * this behavior and do something nicer with the original wikitext.
-        */
-       public static function updateText( $text ) {
-               global $wgContLang;
-
-               # Language-specific strip/conversion
-               $text = $wgContLang->normalizeForSearch( $text );
-               $lc = SearchEngine::legalSearchChars() . '&#;';
-
-               wfProfileIn( __METHOD__ . '-regexps' );
-               $text = preg_replace( "/<\\/?\\s*[A-Za-z][^>]*?>/",
-                       ' ', $wgContLang->lc( " " . $text . " " ) ); # Strip HTML markup
-               $text = preg_replace( "/(^|\\n)==\\s*([^\\n]+)\\s*==(\\s)/sD",
-                       "\\1\\2 \\2 \\2\\3", $text ); # Emphasize headings
-
-               # Strip external URLs
-               $uc = "A-Za-z0-9_\\/:.,~%\\-+&;#?!=()@\\x80-\\xFF";
-               $protos = "http|https|ftp|mailto|news|gopher";
-               $pat = "/(^|[^\\[])({$protos}):[{$uc}]+([^{$uc}]|$)/";
-               $text = preg_replace( $pat, "\\1 \\3", $text );
-
-               $p1 = "/([^\\[])\\[({$protos}):[{$uc}]+]/";
-               $p2 = "/([^\\[])\\[({$protos}):[{$uc}]+\\s+([^\\]]+)]/";
-               $text = preg_replace( $p1, "\\1 ", $text );
-               $text = preg_replace( $p2, "\\1 \\3 ", $text );
-
-               # Internal image links
-               $pat2 = "/\\[\\[image:([{$uc}]+)\\.(gif|png|jpg|jpeg)([^{$uc}])/i";
-               $text = preg_replace( $pat2, " \\1 \\3", $text );
-
-               $text = preg_replace( "/([^{$lc}])([{$lc}]+)]]([a-z]+)/",
-                       "\\1\\2 \\2\\3", $text ); # Handle [[game]]s
-
-               # Strip all remaining non-search characters
-               $text = preg_replace( "/[^{$lc}]+/", " ", $text );
-
-               # Handle 's, s'
-               #
-               #   $text = preg_replace( "/([{$lc}]+)'s /", "\\1 \\1's ", $text );
-               #   $text = preg_replace( "/([{$lc}]+)s' /", "\\1s ", $text );
-               #
-               # These tail-anchored regexps are insanely slow. The worst case comes
-               # when Japanese or Chinese text (ie, no word spacing) is written on
-               # a wiki configured for Western UTF-8 mode. The Unicode characters are
-               # expanded to hex codes and the "words" are very long paragraph-length
-               # monstrosities. On a large page the above regexps may take over 20
-               # seconds *each* on a 1GHz-level processor.
-               #
-               # Following are reversed versions which are consistently fast
-               # (about 3 milliseconds on 1GHz-level processor).
-               #
-               $text = strrev( preg_replace( "/ s'([{$lc}]+)/", " s'\\1 \\1", strrev( $text ) ) );
-               $text = strrev( preg_replace( "/ 's([{$lc}]+)/", " s\\1", strrev( $text ) ) );
-
-               # Strip wiki '' and '''
-               $text = preg_replace( "/''[']*/", " ", $text );
-               wfProfileOut( __METHOD__ . '-regexps' );
-               return $text;
-       }
-}
index 129e919..7fcab19 100644 (file)
@@ -85,7 +85,7 @@ class SpecialChangePassword extends UnlistedSpecialPage {
 
                                if ( $user->isLoggedIn() ) {
                                        $this->getOutput()->wrapWikiMsg(
-                                                       "<div class=\"successbox\"><strong>\n$1\n</strong></div>",
+                                                       "<div class=\"successbox\">\n$1\n</div>",
                                                        'changepassword-success'
                                        );
                                        $this->getOutput()->returnToMain();
index ce7a45b..ecee0bb 100644 (file)
@@ -57,7 +57,7 @@ class SpecialPreferences extends SpecialPage {
 
                if ( $this->getRequest()->getCheck( 'success' ) ) {
                        $out->wrapWikiMsg(
-                               "<div class=\"successbox mw-sp-pref-successbox\">\n$1\n</div>",
+                               "<div class=\"successbox\">\n$1\n</div>",
                                'savedprefs'
                        );
                }
index a42a217..51e7450 100644 (file)
@@ -517,8 +517,8 @@ class SpecialRecentChanges extends IncludableSpecialPage {
                }
 
                if ( $rows->numRows() === 0 ) {
-                       $this->getOutput()->wrapWikiMsg(
-                               "<div class='mw-changeslist-empty'>\n$1\n</div>", 'recentchanges-noresult'
+                       $this->getOutput()->addHtml(
+                               '<div class="mw-changeslist-empty">' . $this->msg( 'recentchanges-noresult' )->parse() . '</div>'
                        );
                } else {
                        $this->getOutput()->addHTML( $rclistOutput );
index a844704..f37ea20 100644 (file)
@@ -72,7 +72,7 @@ class SpecialRecentchangeslinked extends SpecialRecentChanges {
                $outputPage = $this->getOutput();
                $title = Title::newFromURL( $target );
                if ( !$title || $title->getInterwiki() != '' ) {
-                       $outputPage->wrapWikiMsg( "<div class=\"errorbox\">\n$1\n</div>", 'allpagesbadtitle' );
+                       $outputPage->addHtml( '<div class="errorbox">' . $this->msg( 'allpagesbadtitle' )->parse() . '</div>' );
                        return false;
                }
 
index 8609c74..d5a6b29 100644 (file)
@@ -738,7 +738,7 @@ class SpecialSearch extends SpecialPage {
         *
         * @return string
         */
-       protected function showInterwiki( &$matches, $query ) {
+       protected function showInterwiki( $matches, $query ) {
                global $wgContLang;
                wfProfileIn( __METHOD__ );
                $terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
index 2e0b944..091bd78 100644 (file)
  * @author Michael Dale
  */
 class UploadFromChunks extends UploadFromFile {
-       protected $mOffset, $mChunkIndex, $mFileKey, $mVirtualTempPath;
+       protected $mOffset;
+       protected $mChunkIndex;
+       protected $mFileKey;
+       protected $mVirtualTempPath;
 
        /**
         * Setup local pointers to stash, repo and user (similar to UploadFromStash)
         *
-        * @param $user User
-        * @param $stash UploadStash
-        * @param $repo FileRepo
+        * @param $user User|null Default: null
+        * @param $stash UploadStash|bool Default: false
+        * @param $repo FileRepo|bool Default: false
         */
        public function __construct( $user = null, $stash = false, $repo = false ) {
                // user object. sometimes this won't exist, as when running from cron.
@@ -108,13 +111,14 @@ class UploadFromChunks extends UploadFromFile {
         * @return FileRepoStatus
         */
        public function concatenateChunks() {
+               $chunkIndex = $this->getChunkIndex();
                wfDebug( __METHOD__ . " concatenate {$this->mChunkIndex} chunks:" .
-                       $this->getOffset() . ' inx:' . $this->getChunkIndex() . "\n" );
+                       $this->getOffset() . ' inx:' . $chunkIndex . "\n" );
 
                // Concatenate all the chunks to mVirtualTempPath
-               $fileList = Array();
+               $fileList = array();
                // The first chunk is stored at the mVirtualTempPath path so we start on "chunk 1"
-               for ( $i = 0; $i <= $this->getChunkIndex(); $i++ ) {
+               for ( $i = 0; $i <= $chunkIndex; $i++ ) {
                        $fileList[] = $this->getVirtualChunkLocation( $i );
                }
 
@@ -122,9 +126,12 @@ class UploadFromChunks extends UploadFromFile {
                $ext = FileBackend::extensionFromPath( $this->mVirtualTempPath );
                // Get a 0-byte temp file to perform the concatenation at
                $tmpFile = TempFSFile::factory( 'chunkedupload_', $ext );
-               $tmpPath = $tmpFile
-                       ? $tmpFile->bind( $this )->getPath() // keep alive with $this
-                       : false; // fail in concatenate()
+               $tmpPath = false; // fail in concatenate()
+               if( $tmpFile ) {
+                       // keep alive with $this
+                       $tmpPath = $tmpFile->bind( $this )->getPath();
+               }
+
                // Concatenate the chunks at the temp file
                $tStart = microtime( true );
                $status = $this->repo->concatenate( $fileList, $tmpPath, FileRepo::DELETE_SOURCE );
@@ -134,8 +141,10 @@ class UploadFromChunks extends UploadFromFile {
                }
                wfDebugLog( 'fileconcatenate', "Combined $i chunks in $tAmount seconds.\n" );
 
-               $this->mTempPath = $tmpPath; // file system path
-               $this->mFileSize = filesize( $this->mTempPath ); //Since this was set for the last chunk previously
+               // File system path
+               $this->mTempPath = $tmpPath;
+               // Since this was set for the last chunk previously
+               $this->mFileSize = filesize( $this->mTempPath );
                $ret = $this->verifyUpload();
                if ( $ret['status'] !== UploadBase::OK ) {
                        wfDebugLog( 'fileconcatenate', "Verification failed for chunked upload" );
@@ -321,7 +330,8 @@ class UploadFromChunks extends UploadFromFile {
                                        $error = array( 'unknown', 'no error recorded' );
                                }
                        }
-                       throw new UploadChunkFileException( "error storing file in '$chunkPath': " . implode( '; ', $error ) );
+                       throw new UploadChunkFileException( "Error storing file in '$chunkPath': " .
+                               implode( '; ', $error ) );
                }
                return $storeStatus;
        }
@@ -352,6 +362,11 @@ class UploadFromChunks extends UploadFromFile {
        }
 }
 
-class UploadChunkZeroLengthFileException extends MWException {};
-class UploadChunkFileException extends MWException {};
-class UploadChunkVerificationException extends MWException {};
+class UploadChunkZeroLengthFileException extends MWException {
+}
+
+class UploadChunkFileException extends MWException {
+}
+
+class UploadChunkVerificationException extends MWException {
+}
index cb85fc6..a67fc57 100644 (file)
  * @author Bryan Tong Minh
  */
 class UploadFromStash extends UploadBase {
-       protected $mFileKey, $mVirtualTempPath, $mFileProps, $mSourceType;
+       protected $mFileKey;
+       protected $mVirtualTempPath;
+       protected $mFileProps;
+       protected $mSourceType;
 
        // an instance of UploadStash
        private $stash;
@@ -37,9 +40,9 @@ class UploadFromStash extends UploadBase {
        private $repo;
 
        /**
-        * @param $user User
-        * @param $stash UploadStash
-        * @param $repo FileRepo
+        * @param User|bool $user Default: false
+        * @param UploadStash|bool $stash Default: false
+        * @param FileRepo|bool $repo Default: false
         */
        public function __construct( $user = false, $stash = false, $repo = false ) {
                // user object. sometimes this won't exist, as when running from cron.
@@ -65,7 +68,7 @@ class UploadFromStash extends UploadBase {
        }
 
        /**
-        * @param $key string
+        * @param string $key
         * @return bool
         */
        public static function isValidKey( $key ) {
@@ -74,9 +77,8 @@ class UploadFromStash extends UploadBase {
        }
 
        /**
-        * @param $request WebRequest
-        *
-        * @return Boolean
+        * @param WebRequest $request
+        * @return bool
         */
        public static function isValidRequest( $request ) {
                // this passes wpSessionKey to getText() as a default when wpFileKey isn't set.
@@ -86,8 +88,9 @@ class UploadFromStash extends UploadBase {
        }
 
        /**
-        * @param $key string
-        * @param $name string
+        * @param string $key
+        * @param string $name
+        * @param bool $initTempFile
         */
        public function initialize( $key, $name = 'upload_file', $initTempFile = true ) {
                /**
@@ -110,14 +113,17 @@ class UploadFromStash extends UploadBase {
        }
 
        /**
-        * @param $request WebRequest
+        * @param WebRequest $request
         */
        public function initializeFromRequest( &$request ) {
                // sends wpSessionKey as a default when wpFileKey is missing
                $fileKey = $request->getText( 'wpFileKey', $request->getText( 'wpSessionKey' ) );
 
                // chooses one of wpDestFile, wpUploadFile, filename in that order.
-               $desiredDestName = $request->getText( 'wpDestFile', $request->getText( 'wpUploadFile', $request->getText( 'filename' ) ) );
+               $desiredDestName = $request->getText(
+                       'wpDestFile',
+                       $request->getText( 'wpUploadFile', $request->getText( 'filename' ) )
+               );
 
                $this->initialize( $fileKey, $desiredDestName );
        }
@@ -144,7 +150,7 @@ class UploadFromStash extends UploadBase {
        /**
         * Stash the file.
         *
-        * @param $user User
+        * @param User $user
         * @return UploadStashFile
         */
        public function stashFile( User $user = null ) {
@@ -156,7 +162,7 @@ class UploadFromStash extends UploadBase {
 
        /**
         * This should return the key instead of the UploadStashFile instance, for backward compatibility.
-        * @return String
+        * @return string
         */
        public function stashSession() {
                return $this->stashFile()->getFileKey();
@@ -164,7 +170,7 @@ class UploadFromStash extends UploadBase {
 
        /**
         * Remove a temporarily kept file stashed by saveTempUploadedFile().
-        * @return bool success
+        * @return bool Success
         */
        public function unsaveUploadedFile() {
                return $this->stash->removeFile( $this->mFileKey );
@@ -172,10 +178,10 @@ class UploadFromStash extends UploadBase {
 
        /**
         * Perform the upload, then remove the database record afterward.
-        * @param $comment string
-        * @param $pageText string
-        * @param $watch bool
-        * @param $user User
+        * @param string $comment
+        * @param string $pageText
+        * @param bool $watch
+        * @param User $user
         * @return Status
         */
        public function performUpload( $comment, $pageText, $watch, $user ) {
index ebeb9c1..7db6c64 100644 (file)
@@ -358,7 +358,7 @@ class UploadStash {
                wfDebug( __METHOD__ . " clearing row $key\n" );
 
                // Ensure we have the UploadStashFile loaded for this key
-               $this->getFile( $key );
+               $this->getFile( $key, true );
 
                $dbw = $this->repo->getMasterDb();
 
diff --git a/includes/utils/ArrayUtils.php b/includes/utils/ArrayUtils.php
new file mode 100644 (file)
index 0000000..97a56e1
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+
+class ArrayUtils {
+       /**
+        * Sort the given array in a pseudo-random order which depends only on the
+        * given key and each element value. This is typically used for load
+        * balancing between servers each with a local cache.
+        *
+        * Keys are preserved. The input array is modified in place.
+        *
+        * Note: Benchmarking on PHP 5.3 and 5.4 indicates that for small
+        * strings, md5() is only 10% slower than hash('joaat',...) etc.,
+        * since the function call overhead dominates. So there's not much
+        * justification for breaking compatibility with installations
+        * compiled with ./configure --disable-hash.
+        *
+        * @param array $array Array to sort
+        * @param string $key
+        * @param string $separator A separator used to delimit the array elements and the
+        *     key. This can be chosen to provide backwards compatibility with
+        *     various consistent hash implementations that existed before this
+        *     function was introduced.
+        */
+       public static function consistentHashSort( &$array, $key, $separator = "\000" ) {
+               $hashes = array();
+               foreach ( $array as $elt ) {
+                       $hashes[$elt] = md5( $elt . $separator . $key );
+               }
+               uasort( $array, function ( $a, $b ) use ( $hashes ) {
+                       return strcmp( $hashes[$a], $hashes[$b] );
+               } );
+       }
+
+       /**
+        * Given an array of non-normalised probabilities, this function will select
+        * an element and return the appropriate key
+        *
+        * @param array $weights
+        * @return bool|int|string
+        */
+       public static function pickRandom( $weights ) {
+               if ( !is_array( $weights ) || count( $weights ) == 0 ) {
+                       return false;
+               }
+
+               $sum = array_sum( $weights );
+               if ( $sum == 0 ) {
+                       # No loads on any of them
+                       # In previous versions, this triggered an unweighted random selection,
+                       # but this feature has been removed as of April 2006 to allow for strict
+                       # separation of query groups.
+                       return false;
+               }
+               $max = mt_getrandmax();
+               $rand = mt_rand( 0, $max ) / $max * $sum;
+
+               $sum = 0;
+               foreach ( $weights as $i => $w ) {
+                       $sum += $w;
+                       # Do not return keys if they have 0 weight.
+                       # Note that the "all 0 weight" case is handed above
+                       if ( $w > 0 && $sum >= $rand ) {
+                               break;
+                       }
+               }
+               return $i;
+       }
+}
diff --git a/includes/utils/Cdb.php b/includes/utils/Cdb.php
new file mode 100644 (file)
index 0000000..81c0afe
--- /dev/null
@@ -0,0 +1,184 @@
+<?php
+/**
+ * Native CDB file reader and writer.
+ *
+ * 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
+ */
+
+/**
+ * Read from a CDB file.
+ * Native and pure PHP implementations are provided.
+ * http://cr.yp.to/cdb.html
+ */
+abstract class CdbReader {
+       /**
+        * Open a file and return a subclass instance
+        *
+        * @param $fileName string
+        *
+        * @return CdbReader
+        */
+       public static function open( $fileName ) {
+               if ( self::haveExtension() ) {
+                       return new CdbReader_DBA( $fileName );
+               } else {
+                       wfDebug( "Warning: no dba extension found, using emulation.\n" );
+                       return new CdbReader_PHP( $fileName );
+               }
+       }
+
+       /**
+        * Returns true if the native extension is available
+        *
+        * @return bool
+        */
+       public static function haveExtension() {
+               if ( !function_exists( 'dba_handlers' ) ) {
+                       return false;
+               }
+               $handlers = dba_handlers();
+               if ( !in_array( 'cdb', $handlers ) || !in_array( 'cdb_make', $handlers ) ) {
+                       return false;
+               }
+               return true;
+       }
+
+       /**
+        * Construct the object and open the file
+        */
+       abstract function __construct( $fileName );
+
+       /**
+        * Close the file. Optional, you can just let the variable go out of scope.
+        */
+       abstract function close();
+
+       /**
+        * Get a value with a given key. Only string values are supported.
+        *
+        * @param $key string
+        */
+       abstract public function get( $key );
+}
+
+/**
+ * Write to a CDB file.
+ * Native and pure PHP implementations are provided.
+ */
+abstract class CdbWriter {
+       /**
+        * Open a writer and return a subclass instance.
+        * The user must have write access to the directory, for temporary file creation.
+        *
+        * @param $fileName string
+        *
+        * @return CdbWriter_DBA|CdbWriter_PHP
+        */
+       public static function open( $fileName ) {
+               if ( CdbReader::haveExtension() ) {
+                       return new CdbWriter_DBA( $fileName );
+               } else {
+                       wfDebug( "Warning: no dba extension found, using emulation.\n" );
+                       return new CdbWriter_PHP( $fileName );
+               }
+       }
+
+       /**
+        * Create the object and open the file
+        *
+        * @param $fileName string
+        */
+       abstract function __construct( $fileName );
+
+       /**
+        * Set a key to a given value. The value will be converted to string.
+        * @param $key string
+        * @param $value string
+        */
+       abstract public function set( $key, $value );
+
+       /**
+        * Close the writer object. You should call this function before the object
+        * goes out of scope, to write out the final hashtables.
+        */
+       abstract public function close();
+}
+
+/**
+ * Reader class which uses the DBA extension
+ */
+class CdbReader_DBA {
+       var $handle;
+
+       function __construct( $fileName ) {
+               $this->handle = dba_open( $fileName, 'r-', 'cdb' );
+               if ( !$this->handle ) {
+                       throw new MWException( 'Unable to open CDB file "' . $fileName . '"' );
+               }
+       }
+
+       function close() {
+               if ( isset( $this->handle ) ) {
+                       dba_close( $this->handle );
+               }
+               unset( $this->handle );
+       }
+
+       function get( $key ) {
+               return dba_fetch( $key, $this->handle );
+       }
+}
+
+/**
+ * Writer class which uses the DBA extension
+ */
+class CdbWriter_DBA {
+       var $handle, $realFileName, $tmpFileName;
+
+       function __construct( $fileName ) {
+               $this->realFileName = $fileName;
+               $this->tmpFileName = $fileName . '.tmp.' . mt_rand( 0, 0x7fffffff );
+               $this->handle = dba_open( $this->tmpFileName, 'n', 'cdb_make' );
+               if ( !$this->handle ) {
+                       throw new MWException( 'Unable to open CDB file for write "' . $fileName . '"' );
+               }
+       }
+
+       function set( $key, $value ) {
+               return dba_insert( $key, $value, $this->handle );
+       }
+
+       function close() {
+               if ( isset( $this->handle ) ) {
+                       dba_close( $this->handle );
+               }
+               if ( wfIsWindows() ) {
+                       unlink( $this->realFileName );
+               }
+               if ( !rename( $this->tmpFileName, $this->realFileName ) ) {
+                       throw new MWException( 'Unable to move the new CDB file into place.' );
+               }
+               unset( $this->handle );
+       }
+
+       function __destruct() {
+               if ( isset( $this->handle ) ) {
+                       $this->close();
+               }
+       }
+}
diff --git a/includes/utils/Cdb_PHP.php b/includes/utils/Cdb_PHP.php
new file mode 100644 (file)
index 0000000..a38b9a8
--- /dev/null
@@ -0,0 +1,493 @@
+<?php
+/**
+ * This is a port of D.J. Bernstein's CDB to PHP. It's based on the copy that
+ * appears in PHP 5.3. Changes are:
+ *    * Error returns replaced with exceptions
+ *    * Exception thrown if sizes or offsets are between 2GB and 4GB
+ *    * Some variables renamed
+ *
+ * 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
+ */
+
+/**
+ * Common functions for readers and writers
+ */
+class CdbFunctions {
+       /**
+        * Take a modulo of a signed integer as if it were an unsigned integer.
+        * $b must be less than 0x40000000 and greater than 0
+        *
+        * @param $a
+        * @param $b
+        *
+        * @return int
+        */
+       public static function unsignedMod( $a, $b ) {
+               if ( $a & 0x80000000 ) {
+                       $m = ( $a & 0x7fffffff ) % $b + 2 * ( 0x40000000 % $b );
+                       return $m % $b;
+               } else {
+                       return $a % $b;
+               }
+       }
+
+       /**
+        * Shift a signed integer right as if it were unsigned
+        * @param $a
+        * @param $b
+        * @return int
+        */
+       public static function unsignedShiftRight( $a, $b ) {
+               if ( $b == 0 ) {
+                       return $a;
+               }
+               if ( $a & 0x80000000 ) {
+                       return ( ( $a & 0x7fffffff ) >> $b ) | ( 0x40000000 >> ( $b - 1 ) );
+               } else {
+                       return $a >> $b;
+               }
+       }
+
+       /**
+        * The CDB hash function.
+        *
+        * @param $s string
+        *
+        * @return
+        */
+       public static function hash( $s ) {
+               $h = 5381;
+               for ( $i = 0; $i < strlen( $s ); $i++ ) {
+                       $h5 = ( $h << 5 ) & 0xffffffff;
+                       // Do a 32-bit sum
+                       // Inlined here for speed
+                       $sum = ( $h & 0x3fffffff ) + ( $h5 & 0x3fffffff );
+                       $h =
+                               (
+                                       ( $sum & 0x40000000 ? 1 : 0 )
+                                       + ( $h & 0x80000000 ? 2 : 0 )
+                                       + ( $h & 0x40000000 ? 1 : 0 )
+                                       + ( $h5 & 0x80000000 ? 2 : 0 )
+                                       + ( $h5 & 0x40000000 ? 1 : 0 )
+                               ) << 30
+                               | ( $sum & 0x3fffffff );
+                       $h ^= ord( $s[$i] );
+                       $h &= 0xffffffff;
+               }
+               return $h;
+       }
+}
+
+/**
+ * CDB reader class
+ */
+class CdbReader_PHP extends CdbReader {
+       /** The filename */
+       var $fileName;
+
+       /** The file handle */
+       var $handle;
+
+       /* number of hash slots searched under this key */
+       var $loop;
+
+       /* initialized if loop is nonzero */
+       var $khash;
+
+       /* initialized if loop is nonzero */
+       var $kpos;
+
+       /* initialized if loop is nonzero */
+       var $hpos;
+
+       /* initialized if loop is nonzero */
+       var $hslots;
+
+       /* initialized if findNext() returns true */
+       var $dpos;
+
+       /* initialized if cdb_findnext() returns 1 */
+       var $dlen;
+
+       /**
+        * @param $fileName string
+        * @throws MWException
+        */
+       function __construct( $fileName ) {
+               $this->fileName = $fileName;
+               $this->handle = fopen( $fileName, 'rb' );
+               if ( !$this->handle ) {
+                       throw new MWException( 'Unable to open CDB file "' . $this->fileName . '".' );
+               }
+               $this->findStart();
+       }
+
+       function close() {
+               if ( isset( $this->handle ) ) {
+                       fclose( $this->handle );
+               }
+               unset( $this->handle );
+       }
+
+       /**
+        * @param $key
+        * @return bool|string
+        */
+       public function get( $key ) {
+               // strval is required
+               if ( $this->find( strval( $key ) ) ) {
+                       return $this->read( $this->dlen, $this->dpos );
+               } else {
+                       return false;
+               }
+       }
+
+       /**
+        * @param $key
+        * @param $pos
+        * @return bool
+        */
+       protected function match( $key, $pos ) {
+               $buf = $this->read( strlen( $key ), $pos );
+               return $buf === $key;
+       }
+
+       protected function findStart() {
+               $this->loop = 0;
+       }
+
+       /**
+        * @throws MWException
+        * @param $length
+        * @param $pos
+        * @return string
+        */
+       protected function read( $length, $pos ) {
+               if ( fseek( $this->handle, $pos ) == -1 ) {
+                       // This can easily happen if the internal pointers are incorrect
+                       throw new MWException(
+                               'Seek failed, file "' . $this->fileName . '" may be corrupted.' );
+               }
+
+               if ( $length == 0 ) {
+                       return '';
+               }
+
+               $buf = fread( $this->handle, $length );
+               if ( $buf === false || strlen( $buf ) !== $length ) {
+                       throw new MWException(
+                               'Read from CDB file failed, file "' . $this->fileName . '" may be corrupted.' );
+               }
+               return $buf;
+       }
+
+       /**
+        * Unpack an unsigned integer and throw an exception if it needs more than 31 bits
+        * @param $s
+        * @throws MWException
+        * @return mixed
+        */
+       protected function unpack31( $s ) {
+               $data = unpack( 'V', $s );
+               if ( $data[1] > 0x7fffffff ) {
+                       throw new MWException(
+                               'Error in CDB file "' . $this->fileName . '", integer too big.' );
+               }
+               return $data[1];
+       }
+
+       /**
+        * Unpack a 32-bit signed integer
+        * @param $s
+        * @return int
+        */
+       protected function unpackSigned( $s ) {
+               $data = unpack( 'va/vb', $s );
+               return $data['a'] | ( $data['b'] << 16 );
+       }
+
+       /**
+        * @param $key
+        * @return bool
+        */
+       protected function findNext( $key ) {
+               if ( !$this->loop ) {
+                       $u = CdbFunctions::hash( $key );
+                       $buf = $this->read( 8, ( $u << 3 ) & 2047 );
+                       $this->hslots = $this->unpack31( substr( $buf, 4 ) );
+                       if ( !$this->hslots ) {
+                               return false;
+                       }
+                       $this->hpos = $this->unpack31( substr( $buf, 0, 4 ) );
+                       $this->khash = $u;
+                       $u = CdbFunctions::unsignedShiftRight( $u, 8 );
+                       $u = CdbFunctions::unsignedMod( $u, $this->hslots );
+                       $u <<= 3;
+                       $this->kpos = $this->hpos + $u;
+               }
+
+               while ( $this->loop < $this->hslots ) {
+                       $buf = $this->read( 8, $this->kpos );
+                       $pos = $this->unpack31( substr( $buf, 4 ) );
+                       if ( !$pos ) {
+                               return false;
+                       }
+                       $this->loop += 1;
+                       $this->kpos += 8;
+                       if ( $this->kpos == $this->hpos + ( $this->hslots << 3 ) ) {
+                               $this->kpos = $this->hpos;
+                       }
+                       $u = $this->unpackSigned( substr( $buf, 0, 4 ) );
+                       if ( $u === $this->khash ) {
+                               $buf = $this->read( 8, $pos );
+                               $keyLen = $this->unpack31( substr( $buf, 0, 4 ) );
+                               if ( $keyLen == strlen( $key ) && $this->match( $key, $pos + 8 ) ) {
+                                       // Found
+                                       $this->dlen = $this->unpack31( substr( $buf, 4 ) );
+                                       $this->dpos = $pos + 8 + $keyLen;
+                                       return true;
+                               }
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * @param $key
+        * @return bool
+        */
+       protected function find( $key ) {
+               $this->findStart();
+               return $this->findNext( $key );
+       }
+}
+
+/**
+ * CDB writer class
+ */
+class CdbWriter_PHP extends CdbWriter {
+       var $handle, $realFileName, $tmpFileName;
+
+       var $hplist;
+       var $numentries, $pos;
+
+       /**
+        * @param $fileName string
+        */
+       function __construct( $fileName ) {
+               $this->realFileName = $fileName;
+               $this->tmpFileName = $fileName . '.tmp.' . mt_rand( 0, 0x7fffffff );
+               $this->handle = fopen( $this->tmpFileName, 'wb' );
+               if ( !$this->handle ) {
+                       $this->throwException(
+                               'Unable to open CDB file "' . $this->tmpFileName . '" for write.' );
+               }
+               $this->hplist = array();
+               $this->numentries = 0;
+               $this->pos = 2048; // leaving space for the pointer array, 256 * 8
+               if ( fseek( $this->handle, $this->pos ) == -1 ) {
+                       $this->throwException( 'fseek failed in file "' . $this->tmpFileName . '".' );
+               }
+       }
+
+       function __destruct() {
+               if ( isset( $this->handle ) ) {
+                       $this->close();
+               }
+       }
+
+       /**
+        * @param $key
+        * @param $value
+        * @return
+        */
+       public function set( $key, $value ) {
+               if ( strval( $key ) === '' ) {
+                       // DBA cross-check hack
+                       return;
+               }
+               $this->addbegin( strlen( $key ), strlen( $value ) );
+               $this->write( $key );
+               $this->write( $value );
+               $this->addend( strlen( $key ), strlen( $value ), CdbFunctions::hash( $key ) );
+       }
+
+       /**
+        * @throws MWException
+        */
+       public function close() {
+               $this->finish();
+               if ( isset( $this->handle ) ) {
+                       fclose( $this->handle );
+               }
+               if ( wfIsWindows() && file_exists( $this->realFileName ) ) {
+                       unlink( $this->realFileName );
+               }
+               if ( !rename( $this->tmpFileName, $this->realFileName ) ) {
+                       $this->throwException( 'Unable to move the new CDB file into place.' );
+               }
+               unset( $this->handle );
+       }
+
+       /**
+        * @throws MWException
+        * @param $buf
+        */
+       protected function write( $buf ) {
+               $len = fwrite( $this->handle, $buf );
+               if ( $len !== strlen( $buf ) ) {
+                       $this->throwException( 'Error writing to CDB file "' . $this->tmpFileName . '".' );
+               }
+       }
+
+       /**
+        * @throws MWException
+        * @param $len
+        */
+       protected function posplus( $len ) {
+               $newpos = $this->pos + $len;
+               if ( $newpos > 0x7fffffff ) {
+                       $this->throwException(
+                               'A value in the CDB file "' . $this->tmpFileName . '" is too large.' );
+               }
+               $this->pos = $newpos;
+       }
+
+       /**
+        * @param $keylen
+        * @param $datalen
+        * @param $h
+        */
+       protected function addend( $keylen, $datalen, $h ) {
+               $this->hplist[] = array(
+                       'h' => $h,
+                       'p' => $this->pos
+               );
+
+               $this->numentries++;
+               $this->posplus( 8 );
+               $this->posplus( $keylen );
+               $this->posplus( $datalen );
+       }
+
+       /**
+        * @throws MWException
+        * @param $keylen
+        * @param $datalen
+        */
+       protected function addbegin( $keylen, $datalen ) {
+               if ( $keylen > 0x7fffffff ) {
+                       $this->throwException( 'Key length too long in file "' . $this->tmpFileName . '".' );
+               }
+               if ( $datalen > 0x7fffffff ) {
+                       $this->throwException( 'Data length too long in file "' . $this->tmpFileName . '".' );
+               }
+               $buf = pack( 'VV', $keylen, $datalen );
+               $this->write( $buf );
+       }
+
+       /**
+        * @throws MWException
+        */
+       protected function finish() {
+               // Hack for DBA cross-check
+               $this->hplist = array_reverse( $this->hplist );
+
+               // Calculate the number of items that will be in each hashtable
+               $counts = array_fill( 0, 256, 0 );
+               foreach ( $this->hplist as $item ) {
+                       ++ $counts[255 & $item['h']];
+               }
+
+               // Fill in $starts with the *end* indexes
+               $starts = array();
+               $pos = 0;
+               for ( $i = 0; $i < 256; ++$i ) {
+                       $pos += $counts[$i];
+                       $starts[$i] = $pos;
+               }
+
+               // Excessively clever and indulgent code to simultaneously fill $packedTables
+               // with the packed hashtables, and adjust the elements of $starts
+               // to actually point to the starts instead of the ends.
+               $packedTables = array_fill( 0, $this->numentries, false );
+               foreach ( $this->hplist as $item ) {
+                       $packedTables[--$starts[255 & $item['h']]] = $item;
+               }
+
+               $final = '';
+               for ( $i = 0; $i < 256; ++$i ) {
+                       $count = $counts[$i];
+
+                       // The size of the hashtable will be double the item count.
+                       // The rest of the slots will be empty.
+                       $len = $count + $count;
+                       $final .= pack( 'VV', $this->pos, $len );
+
+                       $hashtable = array();
+                       for ( $u = 0; $u < $len; ++$u ) {
+                               $hashtable[$u] = array( 'h' => 0, 'p' => 0 );
+                       }
+
+                       // Fill the hashtable, using the next empty slot if the hashed slot
+                       // is taken.
+                       for ( $u = 0; $u < $count; ++$u ) {
+                               $hp = $packedTables[$starts[$i] + $u];
+                               $where = CdbFunctions::unsignedMod(
+                                       CdbFunctions::unsignedShiftRight( $hp['h'], 8 ), $len );
+                               while ( $hashtable[$where]['p'] ) {
+                                       if ( ++$where == $len ) {
+                                               $where = 0;
+                                       }
+                               }
+                               $hashtable[$where] = $hp;
+                       }
+
+                       // Write the hashtable
+                       for ( $u = 0; $u < $len; ++$u ) {
+                               $buf = pack( 'vvV',
+                                       $hashtable[$u]['h'] & 0xffff,
+                                       CdbFunctions::unsignedShiftRight( $hashtable[$u]['h'], 16 ),
+                                       $hashtable[$u]['p'] );
+                               $this->write( $buf );
+                               $this->posplus( 8 );
+                       }
+               }
+
+               // Write the pointer array at the start of the file
+               rewind( $this->handle );
+               if ( ftell( $this->handle ) != 0 ) {
+                       $this->throwException( 'Error rewinding to start of file "' . $this->tmpFileName . '".' );
+               }
+               $this->write( $final );
+       }
+
+       /**
+        * Clean up the temp file and throw an exception
+        *
+        * @param $msg string
+        * @throws MWException
+        */
+       protected function throwException( $msg ) {
+               if ( $this->handle ) {
+                       fclose( $this->handle );
+                       unlink( $this->tmpFileName );
+               }
+               throw new MWException( $msg );
+       }
+}
diff --git a/includes/utils/ConfEditor.php b/includes/utils/ConfEditor.php
new file mode 100644 (file)
index 0000000..67cb87d
--- /dev/null
@@ -0,0 +1,1109 @@
+<?php
+/**
+ * Configuration file editor.
+ *
+ * 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
+ */
+
+/**
+ * This is a state machine style parser with two internal stacks:
+ *   * A next state stack, which determines the state the machine will progress to next
+ *   * A path stack, which keeps track of the logical location in the file.
+ *
+ * Reference grammar:
+ *
+ * file = T_OPEN_TAG *statement
+ * statement = T_VARIABLE "=" expression ";"
+ * expression = array / scalar / T_VARIABLE
+ * array = T_ARRAY "(" [ element *( "," element ) [ "," ] ] ")"
+ * element = assoc-element / expression
+ * assoc-element = scalar T_DOUBLE_ARROW expression
+ * scalar = T_LNUMBER / T_DNUMBER / T_STRING / T_CONSTANT_ENCAPSED_STRING
+ */
+class ConfEditor {
+       /** The text to parse */
+       var $text;
+
+       /** The token array from token_get_all() */
+       var $tokens;
+
+       /** The current position in the token array */
+       var $pos;
+
+       /** The current 1-based line number */
+       var $lineNum;
+
+       /** The current 1-based column number */
+       var $colNum;
+
+       /** The current 0-based byte number */
+       var $byteNum;
+
+       /** The current ConfEditorToken object */
+       var $currentToken;
+
+       /** The previous ConfEditorToken object */
+       var $prevToken;
+
+       /**
+        * The state machine stack. This is an array of strings where the topmost
+        * element will be popped off and become the next parser state.
+        */
+       var $stateStack;
+
+       /**
+        * The path stack is a stack of associative arrays with the following elements:
+        *    name              The name of top level of the path
+        *    level             The level (number of elements) of the path
+        *    startByte         The byte offset of the start of the path
+        *    startToken        The token offset of the start
+        *    endByte           The byte offset of thee
+        *    endToken          The token offset of the end, plus one
+        *    valueStartToken   The start token offset of the value part
+        *    valueStartByte    The start byte offset of the value part
+        *    valueEndToken     The end token offset of the value part, plus one
+        *    valueEndByte      The end byte offset of the value part, plus one
+        *    nextArrayIndex    The next numeric array index at this level
+        *    hasComma          True if the array element ends with a comma
+        *    arrowByte         The byte offset of the "=>", or false if there isn't one
+        */
+       var $pathStack;
+
+       /**
+        * The elements of the top of the pathStack for every path encountered, indexed
+        * by slash-separated path.
+        */
+       var $pathInfo;
+
+       /**
+        * Next serial number for whitespace placeholder paths (\@extra-N)
+        */
+       var $serial;
+
+       /**
+        * Editor state. This consists of the internal copy/insert operations which
+        * are applied to the source string to obtain the destination string.
+        */
+       var $edits;
+
+       /**
+        * Simple entry point for command-line testing
+        *
+        * @param $text string
+        *
+        * @return string
+        */
+       static function test( $text ) {
+               try {
+                       $ce = new self( $text );
+                       $ce->parse();
+               } catch ( ConfEditorParseError $e ) {
+                       return $e->getMessage() . "\n" . $e->highlight( $text );
+               }
+               return "OK";
+       }
+
+       /**
+        * Construct a new parser
+        */
+       public function __construct( $text ) {
+               $this->text = $text;
+       }
+
+       /**
+        * Edit the text. Returns the edited text.
+        * @param array $ops of operations.
+        *
+        * Operations are given as an associative array, with members:
+        *    type:     One of delete, set, append or insert (required)
+        *    path:     The path to operate on (required)
+        *    key:      The array key to insert/append, with PHP quotes
+        *    value:    The value, with PHP quotes
+        *
+        * delete
+        *    Deletes an array element or statement with the specified path.
+        *    e.g.
+        *        array('type' => 'delete', 'path' => '$foo/bar/baz' )
+        *    is equivalent to the runtime PHP code:
+        *        unset( $foo['bar']['baz'] );
+        *
+        * set
+        *    Sets the value of an array element. If the element doesn't exist, it
+        *    is appended to the array. If it does exist, the value is set, with
+        *    comments and indenting preserved.
+        *
+        * append
+        *    Appends a new element to the end of the array. Adds a trailing comma.
+        *    e.g.
+        *        array( 'type' => 'append', 'path', '$foo/bar',
+        *            'key' => 'baz', 'value' => "'x'" )
+        *    is like the PHP code:
+        *        $foo['bar']['baz'] = 'x';
+        *
+        * insert
+        *    Insert a new element at the start of the array.
+        *
+        * @throws MWException
+        * @return string
+        */
+       public function edit( $ops ) {
+               $this->parse();
+
+               $this->edits = array(
+                       array( 'copy', 0, strlen( $this->text ) )
+               );
+               foreach ( $ops as $op ) {
+                       $type = $op['type'];
+                       $path = $op['path'];
+                       $value = isset( $op['value'] ) ? $op['value'] : null;
+                       $key = isset( $op['key'] ) ? $op['key'] : null;
+
+                       switch ( $type ) {
+                       case 'delete':
+                               list( $start, $end ) = $this->findDeletionRegion( $path );
+                               $this->replaceSourceRegion( $start, $end, false );
+                               break;
+                       case 'set':
+                               if ( isset( $this->pathInfo[$path] ) ) {
+                                       list( $start, $end ) = $this->findValueRegion( $path );
+                                       $encValue = $value; // var_export( $value, true );
+                                       $this->replaceSourceRegion( $start, $end, $encValue );
+                                       break;
+                               }
+                               // No existing path, fall through to append
+                               $slashPos = strrpos( $path, '/' );
+                               $key = var_export( substr( $path, $slashPos + 1 ), true );
+                               $path = substr( $path, 0, $slashPos );
+                               // Fall through
+                       case 'append':
+                               // Find the last array element
+                               $lastEltPath = $this->findLastArrayElement( $path );
+                               if ( $lastEltPath === false ) {
+                                       throw new MWException( "Can't find any element of array \"$path\"" );
+                               }
+                               $lastEltInfo = $this->pathInfo[$lastEltPath];
+
+                               // Has it got a comma already?
+                               if ( strpos( $lastEltPath, '@extra' ) === false && !$lastEltInfo['hasComma'] ) {
+                                       // No comma, insert one after the value region
+                                       list( , $end ) = $this->findValueRegion( $lastEltPath );
+                                       $this->replaceSourceRegion( $end - 1, $end - 1, ',' );
+                               }
+
+                               // Make the text to insert
+                               list( $start, $end ) = $this->findDeletionRegion( $lastEltPath );
+
+                               if ( $key === null ) {
+                                       list( $indent, ) = $this->getIndent( $start );
+                                       $textToInsert = "$indent$value,";
+                               } else {
+                                       list( $indent, $arrowIndent ) =
+                                               $this->getIndent( $start, $key, $lastEltInfo['arrowByte'] );
+                                       $textToInsert = "$indent$key$arrowIndent=> $value,";
+                               }
+                               $textToInsert .= ( $indent === false ? ' ' : "\n" );
+
+                               // Insert the item
+                               $this->replaceSourceRegion( $end, $end, $textToInsert );
+                               break;
+                       case 'insert':
+                               // Find first array element
+                               $firstEltPath = $this->findFirstArrayElement( $path );
+                               if ( $firstEltPath === false ) {
+                                       throw new MWException( "Can't find array element of \"$path\"" );
+                               }
+                               list( $start, ) = $this->findDeletionRegion( $firstEltPath );
+                               $info = $this->pathInfo[$firstEltPath];
+
+                               // Make the text to insert
+                               if ( $key === null ) {
+                                       list( $indent, ) = $this->getIndent( $start );
+                                       $textToInsert = "$indent$value,";
+                               } else {
+                                       list( $indent, $arrowIndent ) =
+                                               $this->getIndent( $start, $key, $info['arrowByte'] );
+                                       $textToInsert = "$indent$key$arrowIndent=> $value,";
+                               }
+                               $textToInsert .= ( $indent === false ? ' ' : "\n" );
+
+                               // Insert the item
+                               $this->replaceSourceRegion( $start, $start, $textToInsert );
+                               break;
+                       default:
+                               throw new MWException( "Unrecognised operation: \"$type\"" );
+                       }
+               }
+
+               // Do the edits
+               $out = '';
+               foreach ( $this->edits as $edit ) {
+                       if ( $edit[0] == 'copy' ) {
+                               $out .= substr( $this->text, $edit[1], $edit[2] - $edit[1] );
+                       } else { // if ( $edit[0] == 'insert' )
+                               $out .= $edit[1];
+                       }
+               }
+
+               // Do a second parse as a sanity check
+               $this->text = $out;
+               try {
+                       $this->parse();
+               } catch ( ConfEditorParseError $e ) {
+                       throw new MWException(
+                               "Sorry, ConfEditor broke the file during editing and it won't parse anymore: " .
+                               $e->getMessage() );
+               }
+               return $out;
+       }
+
+       /**
+        * Get the variables defined in the text
+        * @return array( varname => value )
+        */
+       function getVars() {
+               $vars = array();
+               $this->parse();
+               foreach ( $this->pathInfo as $path => $data ) {
+                       if ( $path[0] != '$' ) {
+                               continue;
+                       }
+                       $trimmedPath = substr( $path, 1 );
+                       $name = $data['name'];
+                       if ( $name[0] == '@' ) {
+                               continue;
+                       }
+                       if ( $name[0] == '$' ) {
+                               $name = substr( $name, 1 );
+                       }
+                       $parentPath = substr( $trimmedPath, 0,
+                               strlen( $trimmedPath ) - strlen( $name ) );
+                       if ( substr( $parentPath, -1 ) == '/' ) {
+                               $parentPath = substr( $parentPath, 0, -1 );
+                       }
+
+                       $value = substr( $this->text, $data['valueStartByte'],
+                               $data['valueEndByte'] - $data['valueStartByte']
+                       );
+                       $this->setVar( $vars, $parentPath, $name,
+                               $this->parseScalar( $value ) );
+               }
+               return $vars;
+       }
+
+       /**
+        * Set a value in an array, unless it's set already. For instance,
+        * setVar( $arr, 'foo/bar', 'baz', 3 ); will set
+        * $arr['foo']['bar']['baz'] = 3;
+        * @param $array array
+        * @param string $path slash-delimited path
+        * @param $key mixed Key
+        * @param $value mixed Value
+        */
+       function setVar( &$array, $path, $key, $value ) {
+               $pathArr = explode( '/', $path );
+               $target =& $array;
+               if ( $path !== '' ) {
+                       foreach ( $pathArr as $p ) {
+                               if ( !isset( $target[$p] ) ) {
+                                       $target[$p] = array();
+                               }
+                               $target =& $target[$p];
+                       }
+               }
+               if ( !isset( $target[$key] ) ) {
+                       $target[$key] = $value;
+               }
+       }
+
+       /**
+        * Parse a scalar value in PHP
+        * @return mixed Parsed value
+        */
+       function parseScalar( $str ) {
+               if ( $str !== '' && $str[0] == '\'' ) {
+                       // Single-quoted string
+                       // @todo FIXME: trim() call is due to mystery bug where whitespace gets
+                       // appended to the token; without it we ended up reading in the
+                       // extra quote on the end!
+                       return strtr( substr( trim( $str ), 1, -1 ),
+                               array( '\\\'' => '\'', '\\\\' => '\\' ) );
+               }
+               if ( $str !== '' && $str[0] == '"' ) {
+                       // Double-quoted string
+                       // @todo FIXME: trim() call is due to mystery bug where whitespace gets
+                       // appended to the token; without it we ended up reading in the
+                       // extra quote on the end!
+                       return stripcslashes( substr( trim( $str ), 1, -1 ) );
+               }
+               if ( substr( $str, 0, 4 ) == 'true' ) {
+                       return true;
+               }
+               if ( substr( $str, 0, 5 ) == 'false' ) {
+                       return false;
+               }
+               if ( substr( $str, 0, 4 ) == 'null' ) {
+                       return null;
+               }
+               // Must be some kind of numeric value, so let PHP's weak typing
+               // be useful for a change
+               return $str;
+       }
+
+       /**
+        * Replace the byte offset region of the source with $newText.
+        * Works by adding elements to the $this->edits array.
+        */
+       function replaceSourceRegion( $start, $end, $newText = false ) {
+               // Split all copy operations with a source corresponding to the region
+               // in question.
+               $newEdits = array();
+               foreach ( $this->edits as $edit ) {
+                       if ( $edit[0] !== 'copy' ) {
+                               $newEdits[] = $edit;
+                               continue;
+                       }
+                       $copyStart = $edit[1];
+                       $copyEnd = $edit[2];
+                       if ( $start >= $copyEnd || $end <= $copyStart ) {
+                               // Outside this region
+                               $newEdits[] = $edit;
+                               continue;
+                       }
+                       if ( ( $start < $copyStart && $end > $copyStart )
+                               || ( $start < $copyEnd && $end > $copyEnd )
+                       ) {
+                               throw new MWException( "Overlapping regions found, can't do the edit" );
+                       }
+                       // Split the copy
+                       $newEdits[] = array( 'copy', $copyStart, $start );
+                       if ( $newText !== false ) {
+                               $newEdits[] = array( 'insert', $newText );
+                       }
+                       $newEdits[] = array( 'copy', $end, $copyEnd );
+               }
+               $this->edits = $newEdits;
+       }
+
+       /**
+        * Finds the source byte region which you would want to delete, if $pathName
+        * was to be deleted. Includes the leading spaces and tabs, the trailing line
+        * break, and any comments in between.
+        * @param $pathName
+        * @throws MWException
+        * @return array
+        */
+       function findDeletionRegion( $pathName ) {
+               if ( !isset( $this->pathInfo[$pathName] ) ) {
+                       throw new MWException( "Can't find path \"$pathName\"" );
+               }
+               $path = $this->pathInfo[$pathName];
+               // Find the start
+               $this->firstToken();
+               while ( $this->pos != $path['startToken'] ) {
+                       $this->nextToken();
+               }
+               $regionStart = $path['startByte'];
+               for ( $offset = -1; $offset >= -$this->pos; $offset-- ) {
+                       $token = $this->getTokenAhead( $offset );
+                       if ( !$token->isSkip() ) {
+                               // If there is other content on the same line, don't move the start point
+                               // back, because that will cause the regions to overlap.
+                               $regionStart = $path['startByte'];
+                               break;
+                       }
+                       $lfPos = strrpos( $token->text, "\n" );
+                       if ( $lfPos === false ) {
+                               $regionStart -= strlen( $token->text );
+                       } else {
+                               // The line start does not include the LF
+                               $regionStart -= strlen( $token->text ) - $lfPos - 1;
+                               break;
+                       }
+               }
+               // Find the end
+               while ( $this->pos != $path['endToken'] ) {
+                       $this->nextToken();
+               }
+               $regionEnd = $path['endByte']; // past the end
+               for ( $offset = 0; $offset < count( $this->tokens ) - $this->pos; $offset++ ) {
+                       $token = $this->getTokenAhead( $offset );
+                       if ( !$token->isSkip() ) {
+                               break;
+                       }
+                       $lfPos = strpos( $token->text, "\n" );
+                       if ( $lfPos === false ) {
+                               $regionEnd += strlen( $token->text );
+                       } else {
+                               // This should point past the LF
+                               $regionEnd += $lfPos + 1;
+                               break;
+                       }
+               }
+               return array( $regionStart, $regionEnd );
+       }
+
+       /**
+        * Find the byte region in the source corresponding to the value part.
+        * This includes the quotes, but does not include the trailing comma
+        * or semicolon.
+        *
+        * The end position is the past-the-end (end + 1) value as per convention.
+        * @param $pathName
+        * @throws MWException
+        * @return array
+        */
+       function findValueRegion( $pathName ) {
+               if ( !isset( $this->pathInfo[$pathName] ) ) {
+                       throw new MWException( "Can't find path \"$pathName\"" );
+               }
+               $path = $this->pathInfo[$pathName];
+               if ( $path['valueStartByte'] === false || $path['valueEndByte'] === false ) {
+                       throw new MWException( "Can't find value region for path \"$pathName\"" );
+               }
+               return array( $path['valueStartByte'], $path['valueEndByte'] );
+       }
+
+       /**
+        * Find the path name of the last element in the array.
+        * If the array is empty, this will return the \@extra interstitial element.
+        * If the specified path is not found or is not an array, it will return false.
+        * @return bool|int|string
+        */
+       function findLastArrayElement( $path ) {
+               // Try for a real element
+               $lastEltPath = false;
+               foreach ( $this->pathInfo as $candidatePath => $info ) {
+                       $part1 = substr( $candidatePath, 0, strlen( $path ) + 1 );
+                       $part2 = substr( $candidatePath, strlen( $path ) + 1, 1 );
+                       if ( $part2 == '@' ) {
+                               // Do nothing
+                       } elseif ( $part1 == "$path/" ) {
+                               $lastEltPath = $candidatePath;
+                       } elseif ( $lastEltPath !== false ) {
+                               break;
+                       }
+               }
+               if ( $lastEltPath !== false ) {
+                       return $lastEltPath;
+               }
+
+               // Try for an interstitial element
+               $extraPath = false;
+               foreach ( $this->pathInfo as $candidatePath => $info ) {
+                       $part1 = substr( $candidatePath, 0, strlen( $path ) + 1 );
+                       if ( $part1 == "$path/" ) {
+                               $extraPath = $candidatePath;
+                       } elseif ( $extraPath !== false ) {
+                               break;
+                       }
+               }
+               return $extraPath;
+       }
+
+       /**
+        * Find the path name of first element in the array.
+        * If the array is empty, this will return the \@extra interstitial element.
+        * If the specified path is not found or is not an array, it will return false.
+        * @return bool|int|string
+        */
+       function findFirstArrayElement( $path ) {
+               // Try for an ordinary element
+               foreach ( $this->pathInfo as $candidatePath => $info ) {
+                       $part1 = substr( $candidatePath, 0, strlen( $path ) + 1 );
+                       $part2 = substr( $candidatePath, strlen( $path ) + 1, 1 );
+                       if ( $part1 == "$path/" && $part2 != '@' ) {
+                               return $candidatePath;
+                       }
+               }
+
+               // Try for an interstitial element
+               foreach ( $this->pathInfo as $candidatePath => $info ) {
+                       $part1 = substr( $candidatePath, 0, strlen( $path ) + 1 );
+                       if ( $part1 == "$path/" ) {
+                               return $candidatePath;
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Get the indent string which sits after a given start position.
+        * Returns false if the position is not at the start of the line.
+        * @return array
+        */
+       function getIndent( $pos, $key = false, $arrowPos = false ) {
+               $arrowIndent = ' ';
+               if ( $pos == 0 || $this->text[$pos - 1] == "\n" ) {
+                       $indentLength = strspn( $this->text, " \t", $pos );
+                       $indent = substr( $this->text, $pos, $indentLength );
+               } else {
+                       $indent = false;
+               }
+               if ( $indent !== false && $arrowPos !== false ) {
+                       $arrowIndentLength = $arrowPos - $pos - $indentLength - strlen( $key );
+                       if ( $arrowIndentLength > 0 ) {
+                               $arrowIndent = str_repeat( ' ', $arrowIndentLength );
+                       }
+               }
+               return array( $indent, $arrowIndent );
+       }
+
+       /**
+        * Run the parser on the text. Throws an exception if the string does not
+        * match our defined subset of PHP syntax.
+        */
+       public function parse() {
+               $this->initParse();
+               $this->pushState( 'file' );
+               $this->pushPath( '@extra-' . ( $this->serial++ ) );
+               $token = $this->firstToken();
+
+               while ( !$token->isEnd() ) {
+                       $state = $this->popState();
+                       if ( !$state ) {
+                               $this->error( 'internal error: empty state stack' );
+                       }
+
+                       switch ( $state ) {
+                       case 'file':
+                               $this->expect( T_OPEN_TAG );
+                               $token = $this->skipSpace();
+                               if ( $token->isEnd() ) {
+                                       break 2;
+                               }
+                               $this->pushState( 'statement', 'file 2' );
+                               break;
+                       case 'file 2':
+                               $token = $this->skipSpace();
+                               if ( $token->isEnd() ) {
+                                       break 2;
+                               }
+                               $this->pushState( 'statement', 'file 2' );
+                               break;
+                       case 'statement':
+                               $token = $this->skipSpace();
+                               if ( !$this->validatePath( $token->text ) ) {
+                                       $this->error( "Invalid variable name \"{$token->text}\"" );
+                               }
+                               $this->nextPath( $token->text );
+                               $this->expect( T_VARIABLE );
+                               $this->skipSpace();
+                               $arrayAssign = false;
+                               if ( $this->currentToken()->type == '[' ) {
+                                       $this->nextToken();
+                                       $token = $this->skipSpace();
+                                       if ( !$token->isScalar() ) {
+                                               $this->error( "expected a string or number for the array key" );
+                                       }
+                                       if ( $token->type == T_CONSTANT_ENCAPSED_STRING ) {
+                                               $text = $this->parseScalar( $token->text );
+                                       } else {
+                                               $text = $token->text;
+                                       }
+                                       if ( !$this->validatePath( $text ) ) {
+                                               $this->error( "Invalid associative array name \"$text\"" );
+                                       }
+                                       $this->pushPath( $text );
+                                       $this->nextToken();
+                                       $this->skipSpace();
+                                       $this->expect( ']' );
+                                       $this->skipSpace();
+                                       $arrayAssign = true;
+                               }
+                               $this->expect( '=' );
+                               $this->skipSpace();
+                               $this->startPathValue();
+                               if ( $arrayAssign ) {
+                                       $this->pushState( 'expression', 'array assign end' );
+                               } else {
+                                       $this->pushState( 'expression', 'statement end' );
+                               }
+                               break;
+                       case 'array assign end':
+                       case 'statement end':
+                               $this->endPathValue();
+                               if ( $state == 'array assign end' ) {
+                                       $this->popPath();
+                               }
+                               $this->skipSpace();
+                               $this->expect( ';' );
+                               $this->nextPath( '@extra-' . ( $this->serial++ ) );
+                               break;
+                       case 'expression':
+                               $token = $this->skipSpace();
+                               if ( $token->type == T_ARRAY ) {
+                                       $this->pushState( 'array' );
+                               } elseif ( $token->isScalar() ) {
+                                       $this->nextToken();
+                               } elseif ( $token->type == T_VARIABLE ) {
+                                       $this->nextToken();
+                               } else {
+                                       $this->error( "expected simple expression" );
+                               }
+                               break;
+                       case 'array':
+                               $this->skipSpace();
+                               $this->expect( T_ARRAY );
+                               $this->skipSpace();
+                               $this->expect( '(' );
+                               $this->skipSpace();
+                               $this->pushPath( '@extra-' . ( $this->serial++ ) );
+                               if ( $this->isAhead( ')' ) ) {
+                                       // Empty array
+                                       $this->pushState( 'array end' );
+                               } else {
+                                       $this->pushState( 'element', 'array end' );
+                               }
+                               break;
+                       case 'array end':
+                               $this->skipSpace();
+                               $this->popPath();
+                               $this->expect( ')' );
+                               break;
+                       case 'element':
+                               $token = $this->skipSpace();
+                               // Look ahead to find the double arrow
+                               if ( $token->isScalar() && $this->isAhead( T_DOUBLE_ARROW, 1 ) ) {
+                                       // Found associative element
+                                       $this->pushState( 'assoc-element', 'element end' );
+                               } else {
+                                       // Not associative
+                                       $this->nextPath( '@next' );
+                                       $this->startPathValue();
+                                       $this->pushState( 'expression', 'element end' );
+                               }
+                               break;
+                       case 'element end':
+                               $token = $this->skipSpace();
+                               if ( $token->type == ',' ) {
+                                       $this->endPathValue();
+                                       $this->markComma();
+                                       $this->nextToken();
+                                       $this->nextPath( '@extra-' . ( $this->serial++ ) );
+                                       // Look ahead to find ending bracket
+                                       if ( $this->isAhead( ")" ) ) {
+                                               // Found ending bracket, no continuation
+                                               $this->skipSpace();
+                                       } else {
+                                               // No ending bracket, continue to next element
+                                               $this->pushState( 'element' );
+                                       }
+                               } elseif ( $token->type == ')' ) {
+                                       // End array
+                                       $this->endPathValue();
+                               } else {
+                                       $this->error( "expected the next array element or the end of the array" );
+                               }
+                               break;
+                       case 'assoc-element':
+                               $token = $this->skipSpace();
+                               if ( !$token->isScalar() ) {
+                                       $this->error( "expected a string or number for the array key" );
+                               }
+                               if ( $token->type == T_CONSTANT_ENCAPSED_STRING ) {
+                                       $text = $this->parseScalar( $token->text );
+                               } else {
+                                       $text = $token->text;
+                               }
+                               if ( !$this->validatePath( $text ) ) {
+                                       $this->error( "Invalid associative array name \"$text\"" );
+                               }
+                               $this->nextPath( $text );
+                               $this->nextToken();
+                               $this->skipSpace();
+                               $this->markArrow();
+                               $this->expect( T_DOUBLE_ARROW );
+                               $this->skipSpace();
+                               $this->startPathValue();
+                               $this->pushState( 'expression' );
+                               break;
+                       }
+               }
+               if ( count( $this->stateStack ) ) {
+                       $this->error( 'unexpected end of file' );
+               }
+               $this->popPath();
+       }
+
+       /**
+        * Initialise a parse.
+        */
+       protected function initParse() {
+               $this->tokens = token_get_all( $this->text );
+               $this->stateStack = array();
+               $this->pathStack = array();
+               $this->firstToken();
+               $this->pathInfo = array();
+               $this->serial = 1;
+       }
+
+       /**
+        * Set the parse position. Do not call this except from firstToken() and
+        * nextToken(), there is more to update than just the position.
+        */
+       protected function setPos( $pos ) {
+               $this->pos = $pos;
+               if ( $this->pos >= count( $this->tokens ) ) {
+                       $this->currentToken = ConfEditorToken::newEnd();
+               } else {
+                       $this->currentToken = $this->newTokenObj( $this->tokens[$this->pos] );
+               }
+               return $this->currentToken;
+       }
+
+       /**
+        * Create a ConfEditorToken from an element of token_get_all()
+        * @return ConfEditorToken
+        */
+       function newTokenObj( $internalToken ) {
+               if ( is_array( $internalToken ) ) {
+                       return new ConfEditorToken( $internalToken[0], $internalToken[1] );
+               } else {
+                       return new ConfEditorToken( $internalToken, $internalToken );
+               }
+       }
+
+       /**
+        * Reset the parse position
+        */
+       function firstToken() {
+               $this->setPos( 0 );
+               $this->prevToken = ConfEditorToken::newEnd();
+               $this->lineNum = 1;
+               $this->colNum = 1;
+               $this->byteNum = 0;
+               return $this->currentToken;
+       }
+
+       /**
+        * Get the current token
+        */
+       function currentToken() {
+               return $this->currentToken;
+       }
+
+       /**
+        * Advance the current position and return the resulting next token
+        */
+       function nextToken() {
+               if ( $this->currentToken ) {
+                       $text = $this->currentToken->text;
+                       $lfCount = substr_count( $text, "\n" );
+                       if ( $lfCount ) {
+                               $this->lineNum += $lfCount;
+                               $this->colNum = strlen( $text ) - strrpos( $text, "\n" );
+                       } else {
+                               $this->colNum += strlen( $text );
+                       }
+                       $this->byteNum += strlen( $text );
+               }
+               $this->prevToken = $this->currentToken;
+               $this->setPos( $this->pos + 1 );
+               return $this->currentToken;
+       }
+
+       /**
+        * Get the token $offset steps ahead of the current position.
+        * $offset may be negative, to get tokens behind the current position.
+        * @return ConfEditorToken
+        */
+       function getTokenAhead( $offset ) {
+               $pos = $this->pos + $offset;
+               if ( $pos >= count( $this->tokens ) || $pos < 0 ) {
+                       return ConfEditorToken::newEnd();
+               } else {
+                       return $this->newTokenObj( $this->tokens[$pos] );
+               }
+       }
+
+       /**
+        * Advances the current position past any whitespace or comments
+        */
+       function skipSpace() {
+               while ( $this->currentToken && $this->currentToken->isSkip() ) {
+                       $this->nextToken();
+               }
+               return $this->currentToken;
+       }
+
+       /**
+        * Throws an error if the current token is not of the given type, and
+        * then advances to the next position.
+        */
+       function expect( $type ) {
+               if ( $this->currentToken && $this->currentToken->type == $type ) {
+                       return $this->nextToken();
+               } else {
+                       $this->error( "expected " . $this->getTypeName( $type ) .
+                               ", got " . $this->getTypeName( $this->currentToken->type ) );
+               }
+       }
+
+       /**
+        * Push a state or two on to the state stack.
+        */
+       function pushState( $nextState, $stateAfterThat = null ) {
+               if ( $stateAfterThat !== null ) {
+                       $this->stateStack[] = $stateAfterThat;
+               }
+               $this->stateStack[] = $nextState;
+       }
+
+       /**
+        * Pop a state from the state stack.
+        * @return mixed
+        */
+       function popState() {
+               return array_pop( $this->stateStack );
+       }
+
+       /**
+        * Returns true if the user input path is valid.
+        * This exists to allow "/" and "@" to be reserved for string path keys
+        * @return bool
+        */
+       function validatePath( $path ) {
+               return strpos( $path, '/' ) === false && substr( $path, 0, 1 ) != '@';
+       }
+
+       /**
+        * Internal function to update some things at the end of a path region. Do
+        * not call except from popPath() or nextPath().
+        */
+       function endPath() {
+               $key = '';
+               foreach ( $this->pathStack as $pathInfo ) {
+                       if ( $key !== '' ) {
+                               $key .= '/';
+                       }
+                       $key .= $pathInfo['name'];
+               }
+               $pathInfo['endByte'] = $this->byteNum;
+               $pathInfo['endToken'] = $this->pos;
+               $this->pathInfo[$key] = $pathInfo;
+       }
+
+       /**
+        * Go up to a new path level, for example at the start of an array.
+        */
+       function pushPath( $path ) {
+               $this->pathStack[] = array(
+                       'name' => $path,
+                       'level' => count( $this->pathStack ) + 1,
+                       'startByte' => $this->byteNum,
+                       'startToken' => $this->pos,
+                       'valueStartToken' => false,
+                       'valueStartByte' => false,
+                       'valueEndToken' => false,
+                       'valueEndByte' => false,
+                       'nextArrayIndex' => 0,
+                       'hasComma' => false,
+                       'arrowByte' => false
+               );
+       }
+
+       /**
+        * Go down a path level, for example at the end of an array.
+        */
+       function popPath() {
+               $this->endPath();
+               array_pop( $this->pathStack );
+       }
+
+       /**
+        * Go to the next path on the same level. This ends the current path and
+        * starts a new one. If $path is \@next, the new path is set to the next
+        * numeric array element.
+        */
+       function nextPath( $path ) {
+               $this->endPath();
+               $i = count( $this->pathStack ) - 1;
+               if ( $path == '@next' ) {
+                       $nextArrayIndex =& $this->pathStack[$i]['nextArrayIndex'];
+                       $this->pathStack[$i]['name'] = $nextArrayIndex;
+                       $nextArrayIndex++;
+               } else {
+                       $this->pathStack[$i]['name'] = $path;
+               }
+               $this->pathStack[$i] =
+                       array(
+                               'startByte' => $this->byteNum,
+                               'startToken' => $this->pos,
+                               'valueStartToken' => false,
+                               'valueStartByte' => false,
+                               'valueEndToken' => false,
+                               'valueEndByte' => false,
+                               'hasComma' => false,
+                               'arrowByte' => false,
+                       ) + $this->pathStack[$i];
+       }
+
+       /**
+        * Mark the start of the value part of a path.
+        */
+       function startPathValue() {
+               $path =& $this->pathStack[count( $this->pathStack ) - 1];
+               $path['valueStartToken'] = $this->pos;
+               $path['valueStartByte'] = $this->byteNum;
+       }
+
+       /**
+        * Mark the end of the value part of a path.
+        */
+       function endPathValue() {
+               $path =& $this->pathStack[count( $this->pathStack ) - 1];
+               $path['valueEndToken'] = $this->pos;
+               $path['valueEndByte'] = $this->byteNum;
+       }
+
+       /**
+        * Mark the comma separator in an array element
+        */
+       function markComma() {
+               $path =& $this->pathStack[count( $this->pathStack ) - 1];
+               $path['hasComma'] = true;
+       }
+
+       /**
+        * Mark the arrow separator in an associative array element
+        */
+       function markArrow() {
+               $path =& $this->pathStack[count( $this->pathStack ) - 1];
+               $path['arrowByte'] = $this->byteNum;
+       }
+
+       /**
+        * Generate a parse error
+        */
+       function error( $msg ) {
+               throw new ConfEditorParseError( $this, $msg );
+       }
+
+       /**
+        * Get a readable name for the given token type.
+        * @return string
+        */
+       function getTypeName( $type ) {
+               if ( is_int( $type ) ) {
+                       return token_name( $type );
+               } else {
+                       return "\"$type\"";
+               }
+       }
+
+       /**
+        * Looks ahead to see if the given type is the next token type, starting
+        * from the current position plus the given offset. Skips any intervening
+        * whitespace.
+        * @return bool
+        */
+       function isAhead( $type, $offset = 0 ) {
+               $ahead = $offset;
+               $token = $this->getTokenAhead( $offset );
+               while ( !$token->isEnd() ) {
+                       if ( $token->isSkip() ) {
+                               $ahead++;
+                               $token = $this->getTokenAhead( $ahead );
+                               continue;
+                       } elseif ( $token->type == $type ) {
+                               // Found the type
+                               return true;
+                       } else {
+                               // Not found
+                               return false;
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Get the previous token object
+        */
+       function prevToken() {
+               return $this->prevToken;
+       }
+
+       /**
+        * Echo a reasonably readable representation of the tokenizer array.
+        */
+       function dumpTokens() {
+               $out = '';
+               foreach ( $this->tokens as $token ) {
+                       $obj = $this->newTokenObj( $token );
+                       $out .= sprintf( "%-28s %s\n",
+                               $this->getTypeName( $obj->type ),
+                               addcslashes( $obj->text, "\0..\37" ) );
+               }
+               echo "<pre>" . htmlspecialchars( $out ) . "</pre>";
+       }
+}
+
+/**
+ * Exception class for parse errors
+ */
+class ConfEditorParseError extends MWException {
+       var $lineNum, $colNum;
+       function __construct( $editor, $msg ) {
+               $this->lineNum = $editor->lineNum;
+               $this->colNum = $editor->colNum;
+               parent::__construct( "Parse error on line {$editor->lineNum} " .
+                       "col {$editor->colNum}: $msg" );
+       }
+
+       function highlight( $text ) {
+               $lines = StringUtils::explode( "\n", $text );
+               foreach ( $lines as $lineNum => $line ) {
+                       if ( $lineNum == $this->lineNum - 1 ) {
+                               return "$line\n" . str_repeat( ' ', $this->colNum - 1 ) . "^\n";
+                       }
+               }
+               return '';
+       }
+
+}
+
+/**
+ * Class to wrap a token from the tokenizer.
+ */
+class ConfEditorToken {
+       var $type, $text;
+
+       static $scalarTypes = array( T_LNUMBER, T_DNUMBER, T_STRING, T_CONSTANT_ENCAPSED_STRING );
+       static $skipTypes = array( T_WHITESPACE, T_COMMENT, T_DOC_COMMENT );
+
+       static function newEnd() {
+               return new self( 'END', '' );
+       }
+
+       function __construct( $type, $text ) {
+               $this->type = $type;
+               $this->text = $text;
+       }
+
+       function isSkip() {
+               return in_array( $this->type, self::$skipTypes );
+       }
+
+       function isScalar() {
+               return in_array( $this->type, self::$scalarTypes );
+       }
+
+       function isEnd() {
+               return $this->type == 'END';
+       }
+}
diff --git a/includes/utils/HashRing.php b/includes/utils/HashRing.php
new file mode 100644 (file)
index 0000000..930f8c0
--- /dev/null
@@ -0,0 +1,142 @@
+<?php
+/**
+ * Convenience class for weighted consistent hash rings.
+ *
+ * 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 Aaron Schulz
+ */
+
+/**
+ * Convenience class for weighted consistent hash rings
+ *
+ * @since 1.22
+ */
+class HashRing {
+       /** @var Array (location => weight) */
+       protected $sourceMap = array();
+       /** @var Array (location => (start, end)) */
+       protected $ring = array();
+
+       const RING_SIZE = 268435456; // 2^28
+
+       /**
+        * @param array $map (location => weight)
+        */
+       public function __construct( array $map ) {
+               $map = array_filter( $map, function( $w ) { return $w > 0; } );
+               if ( !count( $map ) ) {
+                       throw new MWException( "Ring is empty or all weights are zero." );
+               }
+               $this->sourceMap = $map;
+               // Sort the locations based on the hash of their names
+               $hashes = array();
+               foreach ( $map as $location => $weight ) {
+                       $hashes[$location] = sha1( $location );
+               }
+               uksort( $map, function ( $a, $b ) use ( $hashes ) {
+                       return strcmp( $hashes[$a], $hashes[$b] );
+               } );
+               // Fit the map to weight-proportionate one with a space of size RING_SIZE
+               $sum = array_sum( $map );
+               $standardMap = array();
+               foreach ( $map as $location => $weight ) {
+                       $standardMap[$location] = (int)floor( $weight / $sum * self::RING_SIZE );
+               }
+               // Build a ring of RING_SIZE spots, with each location at a spot in location hash order
+               $index = 0;
+               foreach ( $standardMap as $location => $weight ) {
+                       // Location covers half-closed interval [$index,$index + $weight)
+                       $this->ring[$location] = array( $index, $index + $weight );
+                       $index += $weight;
+               }
+               // Make sure the last location covers what is left
+               end( $this->ring );
+               $this->ring[key( $this->ring )][1] = self::RING_SIZE;
+       }
+
+       /**
+        * Get the location of an item on the ring
+        *
+        * @param string $item
+        * @return string Location
+        */
+       public function getLocation( $item ) {
+               $locations = $this->getLocations( $item, 1 );
+               return $locations[0];
+       }
+
+       /**
+        * Get the location of an item on the ring, as well as the next clockwise locations
+        *
+        * @param string $item
+        * @param integer $limit Maximum number of locations to return
+        * @return array List of locations
+        */
+       public function getLocations( $item, $limit ) {
+               $locations = array();
+               $primaryLocation = null;
+               $spot = hexdec( substr( sha1( $item ), 0, 7 ) ); // first 28 bits
+               foreach ( $this->ring as $location => $range ) {
+                       if ( count( $locations ) >= $limit ) {
+                               break;
+                       }
+                       // The $primaryLocation is the location the item spot is in.
+                       // After that is reached, keep appending the next locations.
+                       if ( ( $range[0] <= $spot && $spot < $range[1] ) || $primaryLocation !== null ) {
+                               if ( $primaryLocation === null ) {
+                                       $primaryLocation = $location;
+                               }
+                               $locations[] = $location;
+                       }
+               }
+               // If more locations are requested, wrap-around and keep adding them
+               reset( $this->ring );
+               while ( count( $locations ) < $limit ) {
+                       list( $location, ) = each( $this->ring );
+                       if ( $location === $primaryLocation ) {
+                               break; // don't go in circles
+                       }
+                       $locations[] = $location;
+               }
+               return $locations;
+       }
+
+       /**
+        * Get the map of locations to weight (ignores 0-weight items)
+        *
+        * @return array
+        */
+       public function getLocationWeights() {
+               return $this->sourceMap;
+       }
+
+       /**
+        * Get a new hash ring with a location removed from the ring
+        *
+        * @param string $location
+        * @return HashRing|bool Returns false if no non-zero weighted spots are left
+        */
+       public function newWithoutLocation( $location ) {
+               $map = $this->sourceMap;
+               unset( $map[$location] );
+               if ( count( $map ) ) {
+                       return new self( $map );
+               }
+               return false;
+       }
+}
diff --git a/includes/utils/IP.php b/includes/utils/IP.php
new file mode 100644 (file)
index 0000000..73834a5
--- /dev/null
@@ -0,0 +1,761 @@
+<?php
+/**
+ * Functions and constants to play with IP addresses and ranges
+ *
+ * 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 Antoine Musso "<hashar at free dot fr>", Aaron Schulz
+ */
+
+// Some regex definition to "play" with IP address and IP address blocks
+
+// An IPv4 address is made of 4 bytes from x00 to xFF which is d0 to d255
+define( 'RE_IP_BYTE', '(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[0-9]?[0-9])' );
+define( 'RE_IP_ADD', RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE );
+// An IPv4 block is an IP address and a prefix (d1 to d32)
+define( 'RE_IP_PREFIX', '(3[0-2]|[12]?\d)' );
+define( 'RE_IP_BLOCK', RE_IP_ADD . '\/' . RE_IP_PREFIX );
+
+// An IPv6 address is made up of 8 words (each x0000 to xFFFF).
+// However, the "::" abbreviation can be used on consecutive x0000 words.
+define( 'RE_IPV6_WORD', '([0-9A-Fa-f]{1,4})' );
+define( 'RE_IPV6_PREFIX', '(12[0-8]|1[01][0-9]|[1-9]?\d)' );
+define( 'RE_IPV6_ADD',
+       '(?:' . // starts with "::" (including "::")
+               ':(?::|(?::' . RE_IPV6_WORD . '){1,7})' .
+       '|' . // ends with "::" (except "::")
+               RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){0,6}::' .
+       '|' . // contains one "::" in the middle (the ^ makes the test fail if none found)
+               RE_IPV6_WORD . '(?::((?(-1)|:))?' . RE_IPV6_WORD . '){1,6}(?(-2)|^)' .
+       '|' . // contains no "::"
+               RE_IPV6_WORD . '(?::' . RE_IPV6_WORD . '){7}' .
+       ')'
+);
+// An IPv6 block is an IP address and a prefix (d1 to d128)
+define( 'RE_IPV6_BLOCK', RE_IPV6_ADD . '\/' . RE_IPV6_PREFIX );
+// For IPv6 canonicalization (NOT for strict validation; these are quite lax!)
+define( 'RE_IPV6_GAP', ':(?:0+:)*(?::(?:0+:)*)?' );
+define( 'RE_IPV6_V4_PREFIX', '0*' . RE_IPV6_GAP . '(?:ffff:)?' );
+
+// This might be useful for regexps used elsewhere, matches any IPv6 or IPv6 address or network
+define( 'IP_ADDRESS_STRING',
+       '(?:' .
+               RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?' . // IPv4
+       '|' .
+               RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?' . // IPv6
+       ')'
+);
+
+/**
+ * A collection of public static functions to play with IP address
+ * and IP blocks.
+ */
+class IP {
+       /**
+        * Determine if a string is as valid IP address or network (CIDR prefix).
+        * SIIT IPv4-translated addresses are rejected.
+        * Note: canonicalize() tries to convert translated addresses to IPv4.
+        *
+        * @param string $ip possible IP address
+        * @return Boolean
+        */
+       public static function isIPAddress( $ip ) {
+               return (bool)preg_match( '/^' . IP_ADDRESS_STRING . '$/', $ip );
+       }
+
+       /**
+        * Given a string, determine if it as valid IP in IPv6 only.
+        * Note: Unlike isValid(), this looks for networks too.
+        *
+        * @param string $ip possible IP address
+        * @return Boolean
+        */
+       public static function isIPv6( $ip ) {
+               return (bool)preg_match( '/^' . RE_IPV6_ADD . '(?:\/' . RE_IPV6_PREFIX . ')?$/', $ip );
+       }
+
+       /**
+        * Given a string, determine if it as valid IP in IPv4 only.
+        * Note: Unlike isValid(), this looks for networks too.
+        *
+        * @param string $ip possible IP address
+        * @return Boolean
+        */
+       public static function isIPv4( $ip ) {
+               return (bool)preg_match( '/^' . RE_IP_ADD . '(?:\/' . RE_IP_PREFIX . ')?$/', $ip );
+       }
+
+       /**
+        * Validate an IP address. Ranges are NOT considered valid.
+        * SIIT IPv4-translated addresses are rejected.
+        * Note: canonicalize() tries to convert translated addresses to IPv4.
+        *
+        * @param $ip String
+        * @return Boolean: True if it is valid.
+        */
+       public static function isValid( $ip ) {
+               return ( preg_match( '/^' . RE_IP_ADD . '$/', $ip )
+                       || preg_match( '/^' . RE_IPV6_ADD . '$/', $ip ) );
+       }
+
+       /**
+        * Validate an IP Block (valid address WITH a valid prefix).
+        * SIIT IPv4-translated addresses are rejected.
+        * Note: canonicalize() tries to convert translated addresses to IPv4.
+        *
+        * @param $ipblock String
+        * @return Boolean: True if it is valid.
+        */
+       public static function isValidBlock( $ipblock ) {
+               return ( preg_match( '/^' . RE_IPV6_BLOCK . '$/', $ipblock )
+                       || preg_match( '/^' . RE_IP_BLOCK . '$/', $ipblock ) );
+       }
+
+       /**
+        * Convert an IP into a verbose, uppercase, normalized form.
+        * IPv6 addresses in octet notation are expanded to 8 words.
+        * IPv4 addresses are just trimmed.
+        *
+        * @param string $ip IP address in quad or octet form (CIDR or not).
+        * @return String
+        */
+       public static function sanitizeIP( $ip ) {
+               $ip = trim( $ip );
+               if ( $ip === '' ) {
+                       return null;
+               }
+               if ( self::isIPv4( $ip ) || !self::isIPv6( $ip ) ) {
+                       return $ip; // nothing else to do for IPv4 addresses or invalid ones
+               }
+               // Remove any whitespaces, convert to upper case
+               $ip = strtoupper( $ip );
+               // Expand zero abbreviations
+               $abbrevPos = strpos( $ip, '::' );
+               if ( $abbrevPos !== false ) {
+                       // We know this is valid IPv6. Find the last index of the
+                       // address before any CIDR number (e.g. "a:b:c::/24").
+                       $CIDRStart = strpos( $ip, "/" );
+                       $addressEnd = ( $CIDRStart !== false )
+                               ? $CIDRStart - 1
+                               : strlen( $ip ) - 1;
+                       // If the '::' is at the beginning...
+                       if ( $abbrevPos == 0 ) {
+                               $repeat = '0:';
+                               $extra = ( $ip == '::' ) ? '0' : ''; // for the address '::'
+                               $pad = 9; // 7+2 (due to '::')
+                       // If the '::' is at the end...
+                       } elseif ( $abbrevPos == ( $addressEnd - 1 ) ) {
+                               $repeat = ':0';
+                               $extra = '';
+                               $pad = 9; // 7+2 (due to '::')
+                       // If the '::' is in the middle...
+                       } else {
+                               $repeat = ':0';
+                               $extra = ':';
+                               $pad = 8; // 6+2 (due to '::')
+                       }
+                       $ip = str_replace( '::',
+                               str_repeat( $repeat, $pad - substr_count( $ip, ':' ) ) . $extra,
+                               $ip
+                       );
+               }
+               // Remove leading zeros from each bloc as needed
+               $ip = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip );
+               return $ip;
+       }
+
+       /**
+        * Prettify an IP for display to end users.
+        * This will make it more compact and lower-case.
+        *
+        * @param $ip string
+        * @return string
+        */
+       public static function prettifyIP( $ip ) {
+               $ip = self::sanitizeIP( $ip ); // normalize (removes '::')
+               if ( self::isIPv6( $ip ) ) {
+                       // Split IP into an address and a CIDR
+                       if ( strpos( $ip, '/' ) !== false ) {
+                               list( $ip, $cidr ) = explode( '/', $ip, 2 );
+                       } else {
+                               list( $ip, $cidr ) = array( $ip, '' );
+                       }
+                       // Get the largest slice of words with multiple zeros
+                       $offset = 0;
+                       $longest = $longestPos = false;
+                       while ( preg_match(
+                               '!(?:^|:)0(?::0)+(?:$|:)!', $ip, $m, PREG_OFFSET_CAPTURE, $offset
+                       ) ) {
+                               list( $match, $pos ) = $m[0]; // full match
+                               if ( strlen( $match ) > strlen( $longest ) ) {
+                                       $longest = $match;
+                                       $longestPos = $pos;
+                               }
+                               $offset = ( $pos + strlen( $match ) ); // advance
+                       }
+                       if ( $longest !== false ) {
+                               // Replace this portion of the string with the '::' abbreviation
+                               $ip = substr_replace( $ip, '::', $longestPos, strlen( $longest ) );
+                       }
+                       // Add any CIDR back on
+                       if ( $cidr !== '' ) {
+                               $ip = "{$ip}/{$cidr}";
+                       }
+                       // Convert to lower case to make it more readable
+                       $ip = strtolower( $ip );
+               }
+               return $ip;
+       }
+
+       /**
+        * Given a host/port string, like one might find in the host part of a URL
+        * per RFC 2732, split the hostname part and the port part and return an
+        * array with an element for each. If there is no port part, the array will
+        * have false in place of the port. If the string was invalid in some way,
+        * false is returned.
+        *
+        * This was easy with IPv4 and was generally done in an ad-hoc way, but
+        * with IPv6 it's somewhat more complicated due to the need to parse the
+        * square brackets and colons.
+        *
+        * A bare IPv6 address is accepted despite the lack of square brackets.
+        *
+        * @param string $both The string with the host and port
+        * @return array
+        */
+       public static function splitHostAndPort( $both ) {
+               if ( substr( $both, 0, 1 ) === '[' ) {
+                       if ( preg_match( '/^\[(' . RE_IPV6_ADD . ')\](?::(?P<port>\d+))?$/', $both, $m ) ) {
+                               if ( isset( $m['port'] ) ) {
+                                       return array( $m[1], intval( $m['port'] ) );
+                               } else {
+                                       return array( $m[1], false );
+                               }
+                       } else {
+                               // Square bracket found but no IPv6
+                               return false;
+                       }
+               }
+               $numColons = substr_count( $both, ':' );
+               if ( $numColons >= 2 ) {
+                       // Is it a bare IPv6 address?
+                       if ( preg_match( '/^' . RE_IPV6_ADD . '$/', $both ) ) {
+                               return array( $both, false );
+                       } else {
+                               // Not valid IPv6, but too many colons for anything else
+                               return false;
+                       }
+               }
+               if ( $numColons >= 1 ) {
+                       // Host:port?
+                       $bits = explode( ':', $both );
+                       if ( preg_match( '/^\d+/', $bits[1] ) ) {
+                               return array( $bits[0], intval( $bits[1] ) );
+                       } else {
+                               // Not a valid port
+                               return false;
+                       }
+               }
+               // Plain hostname
+               return array( $both, false );
+       }
+
+       /**
+        * Given a host name and a port, combine them into host/port string like
+        * you might find in a URL. If the host contains a colon, wrap it in square
+        * brackets like in RFC 2732. If the port matches the default port, omit
+        * the port specification
+        *
+        * @param $host string
+        * @param $port int
+        * @param $defaultPort bool|int
+        * @return string
+        */
+       public static function combineHostAndPort( $host, $port, $defaultPort = false ) {
+               if ( strpos( $host, ':' ) !== false ) {
+                       $host = "[$host]";
+               }
+               if ( $defaultPort !== false && $port == $defaultPort ) {
+                       return $host;
+               } else {
+                       return "$host:$port";
+               }
+       }
+
+       /**
+        * Given an unsigned integer, returns an IPv6 address in octet notation
+        *
+        * @param $ip_int String: IP address.
+        * @return String
+        */
+       public static function toOctet( $ip_int ) {
+               return self::hexToOctet( wfBaseConvert( $ip_int, 10, 16, 32, false ) );
+       }
+
+       /**
+        * Convert an IPv4 or IPv6 hexadecimal representation back to readable format
+        *
+        * @param string $hex number, with "v6-" prefix if it is IPv6
+        * @return String: quad-dotted (IPv4) or octet notation (IPv6)
+        */
+       public static function formatHex( $hex ) {
+               if ( substr( $hex, 0, 3 ) == 'v6-' ) { // IPv6
+                       return self::hexToOctet( substr( $hex, 3 ) );
+               } else { // IPv4
+                       return self::hexToQuad( $hex );
+               }
+       }
+
+       /**
+        * Converts a hexadecimal number to an IPv6 address in octet notation
+        *
+        * @param $ip_hex String: pure hex (no v6- prefix)
+        * @return String (of format a:b:c:d:e:f:g:h)
+        */
+       public static function hexToOctet( $ip_hex ) {
+               // Pad hex to 32 chars (128 bits)
+               $ip_hex = str_pad( strtoupper( $ip_hex ), 32, '0', STR_PAD_LEFT );
+               // Separate into 8 words
+               $ip_oct = substr( $ip_hex, 0, 4 );
+               for ( $n = 1; $n < 8; $n++ ) {
+                       $ip_oct .= ':' . substr( $ip_hex, 4 * $n, 4 );
+               }
+               // NO leading zeroes
+               $ip_oct = preg_replace( '/(^|:)0+(' . RE_IPV6_WORD . ')/', '$1$2', $ip_oct );
+               return $ip_oct;
+       }
+
+       /**
+        * Converts a hexadecimal number to an IPv4 address in quad-dotted notation
+        *
+        * @param $ip_hex String: pure hex
+        * @return String (of format a.b.c.d)
+        */
+       public static function hexToQuad( $ip_hex ) {
+               // Pad hex to 8 chars (32 bits)
+               $ip_hex = str_pad( strtoupper( $ip_hex ), 8, '0', STR_PAD_LEFT );
+               // Separate into four quads
+               $s = '';
+               for ( $i = 0; $i < 4; $i++ ) {
+                       if ( $s !== '' ) {
+                               $s .= '.';
+                       }
+                       $s .= base_convert( substr( $ip_hex, $i * 2, 2 ), 16, 10 );
+               }
+               return $s;
+       }
+
+       /**
+        * 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
+        */
+       public static function isPublic( $ip ) {
+               if ( self::isIPv6( $ip ) ) {
+                       return self::isPublic6( $ip );
+               }
+               $n = self::toUnsigned( $ip );
+               if ( !$n ) {
+                       return false;
+               }
+
+               // ip2long accepts incomplete addresses, as well as some addresses
+               // followed by garbage characters. Check that it's really valid.
+               if ( $ip != long2ip( $n ) ) {
+                       return false;
+               }
+
+               static $privateRanges = false;
+               if ( !$privateRanges ) {
+                       $privateRanges = array(
+                               array( '10.0.0.0', '10.255.255.255' ), # RFC 1918 (private)
+                               array( '172.16.0.0', '172.31.255.255' ), # RFC 1918 (private)
+                               array( '192.168.0.0', '192.168.255.255' ), # RFC 1918 (private)
+                               array( '0.0.0.0', '0.255.255.255' ), # this network
+                               array( '127.0.0.0', '127.255.255.255' ), # loopback
+                       );
+               }
+
+               foreach ( $privateRanges as $r ) {
+                       $start = self::toUnsigned( $r[0] );
+                       $end = self::toUnsigned( $r[1] );
+                       if ( $n >= $start && $n <= $end ) {
+                               return false;
+                       }
+               }
+               return true;
+       }
+
+       /**
+        * Determine if an IPv6 address really is an IP address, and if it is public,
+        * i.e. not RFC 4193 or similar
+        *
+        * @param $ip String
+        * @return Boolean
+        */
+       private static function isPublic6( $ip ) {
+               static $privateRanges = false;
+               if ( !$privateRanges ) {
+                       $privateRanges = array(
+                               array( 'fc00::', 'fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' ), # RFC 4193 (local)
+                               array( '0:0:0:0:0:0:0:1', '0:0:0:0:0:0:0:1' ), # loopback
+                       );
+               }
+               $n = self::toHex( $ip );
+               foreach ( $privateRanges as $r ) {
+                       $start = self::toHex( $r[0] );
+                       $end = self::toHex( $r[1] );
+                       if ( $n >= $start && $n <= $end ) {
+                               return false;
+                       }
+               }
+               return true;
+       }
+
+       /**
+        * Return a zero-padded upper case hexadecimal representation of an IP address.
+        *
+        * Hexadecimal addresses are used because they can easily be extended to
+        * IPv6 support. To separate the ranges, the return value from this
+        * function for an IPv6 address will be prefixed with "v6-", a non-
+        * hexadecimal string which sorts after the IPv4 addresses.
+        *
+        * @param string $ip quad dotted/octet IP address.
+        * @return String
+        */
+       public static function toHex( $ip ) {
+               if ( self::isIPv6( $ip ) ) {
+                       $n = 'v6-' . self::IPv6ToRawHex( $ip );
+               } else {
+                       $n = self::toUnsigned( $ip );
+                       if ( $n !== false ) {
+                               $n = wfBaseConvert( $n, 10, 16, 8, false );
+                       }
+               }
+               return $n;
+       }
+
+       /**
+        * Given an IPv6 address in octet notation, returns a pure hex string.
+        *
+        * @param string $ip octet ipv6 IP address.
+        * @return String: pure hex (uppercase)
+        */
+       private static function IPv6ToRawHex( $ip ) {
+               $ip = self::sanitizeIP( $ip );
+               if ( !$ip ) {
+                       return null;
+               }
+               $r_ip = '';
+               foreach ( explode( ':', $ip ) as $v ) {
+                       $r_ip .= str_pad( $v, 4, 0, STR_PAD_LEFT );
+               }
+               return $r_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
+        */
+       public static function toUnsigned( $ip ) {
+               if ( self::isIPv6( $ip ) ) {
+                       $n = self::toUnsigned6( $ip );
+               } else {
+                       $n = ip2long( $ip );
+                       if ( $n < 0 ) {
+                               $n += pow( 2, 32 );
+                               # On 32-bit platforms (and on Windows), 2^32 does not fit into an int,
+                               # so $n becomes a float. We convert it to string instead.
+                               if ( is_float( $n ) ) {
+                                       $n = (string)$n;
+                               }
+                       }
+               }
+               return $n;
+       }
+
+       /**
+        * @param $ip
+        * @return String
+        */
+       private static function toUnsigned6( $ip ) {
+               return wfBaseConvert( self::IPv6ToRawHex( $ip ), 16, 10 );
+       }
+
+       /**
+        * Convert a network specification in CIDR notation
+        * to an integer network and a number of bits
+        *
+        * @param string $range IP with CIDR prefix
+        * @return array(int or string, int)
+        */
+       public static function parseCIDR( $range ) {
+               if ( self::isIPv6( $range ) ) {
+                       return self::parseCIDR6( $range );
+               }
+               $parts = explode( '/', $range, 2 );
+               if ( count( $parts ) != 2 ) {
+                       return array( false, false );
+               }
+               list( $network, $bits ) = $parts;
+               $network = ip2long( $network );
+               if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 32 ) {
+                       if ( $bits == 0 ) {
+                               $network = 0;
+                       } else {
+                               $network &= ~( ( 1 << ( 32 - $bits ) ) - 1 );
+                       }
+                       # Convert to unsigned
+                       if ( $network < 0 ) {
+                               $network += pow( 2, 32 );
+                       }
+               } else {
+                       $network = false;
+                       $bits = false;
+               }
+               return array( $network, $bits );
+       }
+
+       /**
+        * Given a string range in a number of formats,
+        * return the start and end of the range in hexadecimal.
+        *
+        * Formats are:
+        *     1.2.3.4/24          CIDR
+        *     1.2.3.4 - 1.2.3.5   Explicit range
+        *     1.2.3.4             Single IP
+        *
+        *     2001:0db8:85a3::7344/96                                   CIDR
+        *     2001:0db8:85a3::7344 - 2001:0db8:85a3::7344   Explicit range
+        *     2001:0db8:85a3::7344                                      Single IP
+        * @param string $range IP range
+        * @return array(string, string)
+        */
+       public static function parseRange( $range ) {
+               // CIDR notation
+               if ( strpos( $range, '/' ) !== false ) {
+                       if ( self::isIPv6( $range ) ) {
+                               return self::parseRange6( $range );
+                       }
+                       list( $network, $bits ) = self::parseCIDR( $range );
+                       if ( $network === false ) {
+                               $start = $end = false;
+                       } else {
+                               $start = sprintf( '%08X', $network );
+                               $end = sprintf( '%08X', $network + pow( 2, ( 32 - $bits ) ) - 1 );
+                       }
+               // Explicit range
+               } elseif ( strpos( $range, '-' ) !== false ) {
+                       list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
+                       if ( self::isIPv6( $start ) && self::isIPv6( $end ) ) {
+                               return self::parseRange6( $range );
+                       }
+                       if ( self::isIPv4( $start ) && self::isIPv4( $end ) ) {
+                               $start = self::toUnsigned( $start );
+                               $end = self::toUnsigned( $end );
+                               if ( $start > $end ) {
+                                       $start = $end = false;
+                               } else {
+                                       $start = sprintf( '%08X', $start );
+                                       $end = sprintf( '%08X', $end );
+                               }
+                       } else {
+                               $start = $end = false;
+                       }
+               } else {
+                       # Single IP
+                       $start = $end = self::toHex( $range );
+               }
+               if ( $start === false || $end === false ) {
+                       return array( false, false );
+               } else {
+                       return array( $start, $end );
+               }
+       }
+
+       /**
+        * Convert a network specification in IPv6 CIDR notation to an
+        * integer network and a number of bits
+        *
+        * @param $range
+        *
+        * @return array(string, int)
+        */
+       private static function parseCIDR6( $range ) {
+               # Explode into <expanded IP,range>
+               $parts = explode( '/', IP::sanitizeIP( $range ), 2 );
+               if ( count( $parts ) != 2 ) {
+                       return array( false, false );
+               }
+               list( $network, $bits ) = $parts;
+               $network = self::IPv6ToRawHex( $network );
+               if ( $network !== false && is_numeric( $bits ) && $bits >= 0 && $bits <= 128 ) {
+                       if ( $bits == 0 ) {
+                               $network = "0";
+                       } else {
+                               # Native 32 bit functions WONT work here!!!
+                               # Convert to a padded binary number
+                               $network = wfBaseConvert( $network, 16, 2, 128 );
+                               # Truncate the last (128-$bits) bits and replace them with zeros
+                               $network = str_pad( substr( $network, 0, $bits ), 128, 0, STR_PAD_RIGHT );
+                               # Convert back to an integer
+                               $network = wfBaseConvert( $network, 2, 10 );
+                       }
+               } else {
+                       $network = false;
+                       $bits = false;
+               }
+               return array( $network, (int)$bits );
+       }
+
+       /**
+        * Given a string range in a number of formats, return the
+        * start and end of the range in hexadecimal. For IPv6.
+        *
+        * Formats are:
+        *     2001:0db8:85a3::7344/96                                   CIDR
+        *     2001:0db8:85a3::7344 - 2001:0db8:85a3::7344   Explicit range
+        *     2001:0db8:85a3::7344/96                                   Single IP
+        *
+        * @param $range
+        *
+        * @return array(string, string)
+        */
+       private static function parseRange6( $range ) {
+               # Expand any IPv6 IP
+               $range = IP::sanitizeIP( $range );
+               // CIDR notation...
+               if ( strpos( $range, '/' ) !== false ) {
+                       list( $network, $bits ) = self::parseCIDR6( $range );
+                       if ( $network === false ) {
+                               $start = $end = false;
+                       } else {
+                               $start = wfBaseConvert( $network, 10, 16, 32, false );
+                               # Turn network to binary (again)
+                               $end = wfBaseConvert( $network, 10, 2, 128 );
+                               # Truncate the last (128-$bits) bits and replace them with ones
+                               $end = str_pad( substr( $end, 0, $bits ), 128, 1, STR_PAD_RIGHT );
+                               # Convert to hex
+                               $end = wfBaseConvert( $end, 2, 16, 32, false );
+                               # see toHex() comment
+                               $start = "v6-$start";
+                               $end = "v6-$end";
+                       }
+               // Explicit range notation...
+               } elseif ( strpos( $range, '-' ) !== false ) {
+                       list( $start, $end ) = array_map( 'trim', explode( '-', $range, 2 ) );
+                       $start = self::toUnsigned6( $start );
+                       $end = self::toUnsigned6( $end );
+                       if ( $start > $end ) {
+                               $start = $end = false;
+                       } else {
+                               $start = wfBaseConvert( $start, 10, 16, 32, false );
+                               $end = wfBaseConvert( $end, 10, 16, 32, false );
+                       }
+                       # see toHex() comment
+                       $start = "v6-$start";
+                       $end = "v6-$end";
+               } else {
+                       # Single IP
+                       $start = $end = self::toHex( $range );
+               }
+               if ( $start === false || $end === false ) {
+                       return array( false, false );
+               } else {
+                       return array( $start, $end );
+               }
+       }
+
+       /**
+        * Determine if a given IPv4/IPv6 address is in a given CIDR network
+        *
+        * @param string $addr the address to check against the given range.
+        * @param string $range the range to check the given address against.
+        * @return Boolean: whether or not the given address is in the given range.
+        */
+       public static function isInRange( $addr, $range ) {
+               $hexIP = self::toHex( $addr );
+               list( $start, $end ) = self::parseRange( $range );
+               return ( strcmp( $hexIP, $start ) >= 0 &&
+                       strcmp( $hexIP, $end ) <= 0 );
+       }
+
+       /**
+        * Convert some unusual representations of IPv4 addresses to their
+        * canonical dotted quad representation.
+        *
+        * This currently only checks a few IPV4-to-IPv6 related cases.  More
+        * unusual representations may be added later.
+        *
+        * @param string $addr something that might be an IP address
+        * @return String: valid dotted quad IPv4 address or null
+        */
+       public static function canonicalize( $addr ) {
+               // remove zone info (bug 35738)
+               $addr = preg_replace( '/\%.*/', '', $addr );
+
+               if ( self::isValid( $addr ) ) {
+                       return $addr;
+               }
+               // Turn mapped addresses from ::ce:ffff:1.2.3.4 to 1.2.3.4
+               if ( strpos( $addr, ':' ) !== false && strpos( $addr, '.' ) !== false ) {
+                       $addr = substr( $addr, strrpos( $addr, ':' ) + 1 );
+                       if ( self::isIPv4( $addr ) ) {
+                               return $addr;
+                       }
+               }
+               // IPv6 loopback address
+               $m = array();
+               if ( preg_match( '/^0*' . RE_IPV6_GAP . '1$/', $addr, $m ) ) {
+                       return '127.0.0.1';
+               }
+               // IPv4-mapped and IPv4-compatible IPv6 addresses
+               if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . '(' . RE_IP_ADD . ')$/i', $addr, $m ) ) {
+                       return $m[1];
+               }
+               if ( preg_match( '/^' . RE_IPV6_V4_PREFIX . RE_IPV6_WORD .
+                       ':' . RE_IPV6_WORD . '$/i', $addr, $m ) )
+               {
+                       return long2ip( ( hexdec( $m[1] ) << 16 ) + hexdec( $m[2] ) );
+               }
+
+               return null; // give up
+       }
+
+       /**
+        * Gets rid of unneeded numbers in quad-dotted/octet IP strings
+        * For example, 127.111.113.151/24 -> 127.111.113.0/24
+        * @param string $range IP address to normalize
+        * @return string
+        */
+       public static function sanitizeRange( $range ) {
+               list( /*...*/, $bits ) = self::parseCIDR( $range );
+               list( $start, /*...*/ ) = self::parseRange( $range );
+               $start = self::formatHex( $start );
+               if ( $bits === false ) {
+                       return $start; // wasn't actually a range
+               }
+               return "$start/$bits";
+       }
+}
diff --git a/includes/utils/MWCryptRand.php b/includes/utils/MWCryptRand.php
new file mode 100644 (file)
index 0000000..bac018e
--- /dev/null
@@ -0,0 +1,497 @@
+<?php
+/**
+ * A cryptographic random generator class used for generating secret keys
+ *
+ * This is based in part on Drupal code as well as what we used in our own code
+ * prior to introduction of this class.
+ *
+ * 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
+ *
+ * @author Daniel Friesen
+ * @file
+ */
+
+class MWCryptRand {
+
+       /**
+        * Minimum number of iterations we want to make in our drift calculations.
+        */
+       const MIN_ITERATIONS = 1000;
+
+       /**
+        * Number of milliseconds we want to spend generating each separate byte
+        * of the final generated bytes.
+        * This is used in combination with the hash length to determine the duration
+        * we should spend doing drift calculations.
+        */
+       const MSEC_PER_BYTE = 0.5;
+
+       /**
+        * Singleton instance for public use
+        */
+       protected static $singleton = null;
+
+       /**
+        * The hash algorithm being used
+        */
+       protected $algo = null;
+
+       /**
+        * The number of bytes outputted by the hash algorithm
+        */
+       protected $hashLength = null;
+
+       /**
+        * A boolean indicating whether the previous random generation was done using
+        * cryptographically strong random number generator or not.
+        */
+       protected $strong = null;
+
+       /**
+        * Initialize an initial random state based off of whatever we can find
+        */
+       protected function initialRandomState() {
+               // $_SERVER contains a variety of unstable user and system specific information
+               // It'll vary a little with each page, and vary even more with separate users
+               // It'll also vary slightly across different machines
+               $state = serialize( $_SERVER );
+
+               // To try vary the system information of the state a bit more
+               // by including the system's hostname into the state
+               $state .= wfHostname();
+
+               // Try to gather a little entropy from the different php rand sources
+               $state .= rand() . uniqid( mt_rand(), true );
+
+               // Include some information about the filesystem's current state in the random state
+               $files = array();
+
+               // We know this file is here so grab some info about ourselves
+               $files[] = __FILE__;
+
+               // We must also have a parent folder, and with the usual file structure, a grandparent
+               $files[] = __DIR__;
+               $files[] = dirname( __DIR__ );
+
+               // The config file is likely the most often edited file we know should be around
+               // so include its stat info into the state.
+               // The constant with its location will almost always be defined, as WebStart.php defines
+               // MW_CONFIG_FILE to $IP/LocalSettings.php unless being configured with MW_CONFIG_CALLBACK (eg. the installer)
+               if ( defined( 'MW_CONFIG_FILE' ) ) {
+                       $files[] = MW_CONFIG_FILE;
+               }
+
+               foreach ( $files as $file ) {
+                       wfSuppressWarnings();
+                       $stat = stat( $file );
+                       wfRestoreWarnings();
+                       if ( $stat ) {
+                               // stat() duplicates data into numeric and string keys so kill off all the numeric ones
+                               foreach ( $stat as $k => $v ) {
+                                       if ( is_numeric( $k ) ) {
+                                               unset( $k );
+                                       }
+                               }
+                               // The absolute filename itself will differ from install to install so don't leave it out
+                               if ( ( $path = realpath( $file ) ) !== false ) {
+                                       $state .= $path;
+                               } else {
+                                       $state .= $file;
+                               }
+                               $state .= implode( '', $stat );
+                       } else {
+                               // The fact that the file isn't there is worth at least a
+                               // minuscule amount of entropy.
+                               $state .= '0';
+                       }
+               }
+
+               // Try and make this a little more unstable by including the varying process
+               // id of the php process we are running inside of if we are able to access it
+               if ( function_exists( 'getmypid' ) ) {
+                       $state .= getmypid();
+               }
+
+               // If available try to increase the instability of the data by throwing in
+               // the precise amount of memory that we happen to be using at the moment.
+               if ( function_exists( 'memory_get_usage' ) ) {
+                       $state .= memory_get_usage( true );
+               }
+
+               // It's mostly worthless but throw the wiki's id into the data for a little more variance
+               $state .= wfWikiID();
+
+               // If we have a secret key or proxy key set then throw it into the state as well
+               global $wgSecretKey, $wgProxyKey;
+               if ( $wgSecretKey ) {
+                       $state .= $wgSecretKey;
+               } elseif ( $wgProxyKey ) {
+                       $state .= $wgProxyKey;
+               }
+
+               return $state;
+       }
+
+       /**
+        * Randomly hash data while mixing in clock drift data for randomness
+        *
+        * @param string $data The data to randomly hash.
+        * @return String The hashed bytes
+        * @author Tim Starling
+        */
+       protected function driftHash( $data ) {
+               // Minimum number of iterations (to avoid slow operations causing the loop to gather little entropy)
+               $minIterations = self::MIN_ITERATIONS;
+               // Duration of time to spend doing calculations (in seconds)
+               $duration = ( self::MSEC_PER_BYTE / 1000 ) * $this->hashLength();
+               // Create a buffer to use to trigger memory operations
+               $bufLength = 10000000;
+               $buffer = str_repeat( ' ', $bufLength );
+               $bufPos = 0;
+
+               // Iterate for $duration seconds or at least $minIterations number of iterations
+               $iterations = 0;
+               $startTime = microtime( true );
+               $currentTime = $startTime;
+               while ( $iterations < $minIterations || $currentTime - $startTime < $duration ) {
+                       // Trigger some memory writing to trigger some bus activity
+                       // This may create variance in the time between iterations
+                       $bufPos = ( $bufPos + 13 ) % $bufLength;
+                       $buffer[$bufPos] = ' ';
+                       // Add the drift between this iteration and the last in as entropy
+                       $nextTime = microtime( true );
+                       $delta = (int)( ( $nextTime - $currentTime ) * 1000000 );
+                       $data .= $delta;
+                       // Every 100 iterations hash the data and entropy
+                       if ( $iterations % 100 === 0 ) {
+                               $data = sha1( $data );
+                       }
+                       $currentTime = $nextTime;
+                       $iterations++;
+               }
+               $timeTaken = $currentTime - $startTime;
+               $data = $this->hash( $data );
+
+               wfDebug( __METHOD__ . ": Clock drift calculation " .
+                       "(time-taken=" . ( $timeTaken * 1000 ) . "ms, " .
+                       "iterations=$iterations, " .
+                       "time-per-iteration=" . ( $timeTaken / $iterations * 1e6 ) . "us)\n" );
+               return $data;
+       }
+
+       /**
+        * Return a rolling random state initially build using data from unstable sources
+        * @return string A new weak random state
+        */
+       protected function randomState() {
+               static $state = null;
+               if ( is_null( $state ) ) {
+                       // Initialize the state with whatever unstable data we can find
+                       // It's important that this data is hashed right afterwards to prevent
+                       // it from being leaked into the output stream
+                       $state = $this->hash( $this->initialRandomState() );
+               }
+               // Generate a new random state based on the initial random state or previous
+               // random state by combining it with clock drift
+               $state = $this->driftHash( $state );
+               return $state;
+       }
+
+       /**
+        * Decide on the best acceptable hash algorithm we have available for hash()
+        * @throws MWException
+        * @return String A hash algorithm
+        */
+       protected function hashAlgo() {
+               if ( !is_null( $this->algo ) ) {
+                       return $this->algo;
+               }
+
+               $algos = hash_algos();
+               $preference = array( 'whirlpool', 'sha256', 'sha1', 'md5' );
+
+               foreach ( $preference as $algorithm ) {
+                       if ( in_array( $algorithm, $algos ) ) {
+                               $this->algo = $algorithm;
+                               wfDebug( __METHOD__ . ": Using the {$this->algo} hash algorithm.\n" );
+                               return $this->algo;
+                       }
+               }
+
+               // We only reach here if no acceptable hash is found in the list, this should
+               // be a technical impossibility since most of php's hash list is fixed and
+               // some of the ones we list are available as their own native functions
+               // But since we already require at least 5.2 and hash() was default in
+               // 5.1.2 we don't bother falling back to methods like sha1 and md5.
+               throw new MWException( "Could not find an acceptable hashing function in hash_algos()" );
+       }
+
+       /**
+        * Return the byte-length output of the hash algorithm we are
+        * using in self::hash and self::hmac.
+        *
+        * @return int Number of bytes the hash outputs
+        */
+       protected function hashLength() {
+               if ( is_null( $this->hashLength ) ) {
+                       $this->hashLength = strlen( $this->hash( '' ) );
+               }
+               return $this->hashLength;
+       }
+
+       /**
+        * Generate an acceptably unstable one-way-hash of some text
+        * making use of the best hash algorithm that we have available.
+        *
+        * @param $data string
+        * @return String A raw hash of the data
+        */
+       protected function hash( $data ) {
+               return hash( $this->hashAlgo(), $data, true );
+       }
+
+       /**
+        * Generate an acceptably unstable one-way-hmac of some text
+        * making use of the best hash algorithm that we have available.
+        *
+        * @param $data string
+        * @param $key string
+        * @return String A raw hash of the data
+        */
+       protected function hmac( $data, $key ) {
+               return hash_hmac( $this->hashAlgo(), $data, $key, true );
+       }
+
+       /**
+        * @see self::wasStrong()
+        */
+       public function realWasStrong() {
+               if ( is_null( $this->strong ) ) {
+                       throw new MWException( __METHOD__ . ' called before generation of random data' );
+               }
+               return $this->strong;
+       }
+
+       /**
+        * @see self::generate()
+        */
+       public function realGenerate( $bytes, $forceStrong = false ) {
+               wfProfileIn( __METHOD__ );
+
+               wfDebug( __METHOD__ . ": Generating cryptographic random bytes for " . wfGetAllCallers( 5 ) . "\n" );
+
+               $bytes = floor( $bytes );
+               static $buffer = '';
+               if ( is_null( $this->strong ) ) {
+                       // Set strength to false initially until we know what source data is coming from
+                       $this->strong = true;
+               }
+
+               if ( strlen( $buffer ) < $bytes ) {
+                       // If available make use of mcrypt_create_iv URANDOM source to generate randomness
+                       // On unix-like systems this reads from /dev/urandom but does it without any buffering
+                       // and bypasses openbasedir restrictions, so it's preferable to reading directly
+                       // On Windows starting in PHP 5.3.0 Windows' native CryptGenRandom is used to generate
+                       // entropy so this is also preferable to just trying to read urandom because it may work
+                       // on Windows systems as well.
+                       if ( function_exists( 'mcrypt_create_iv' ) ) {
+                               wfProfileIn( __METHOD__ . '-mcrypt' );
+                               $rem = $bytes - strlen( $buffer );
+                               $iv = mcrypt_create_iv( $rem, MCRYPT_DEV_URANDOM );
+                               if ( $iv === false ) {
+                                       wfDebug( __METHOD__ . ": mcrypt_create_iv returned false.\n" );
+                               } else {
+                                       $buffer .= $iv;
+                                       wfDebug( __METHOD__ . ": mcrypt_create_iv generated " . strlen( $iv ) . " bytes of randomness.\n" );
+                               }
+                               wfProfileOut( __METHOD__ . '-mcrypt' );
+                       }
+               }
+
+               if ( strlen( $buffer ) < $bytes ) {
+                       // If available make use of openssl's random_pseudo_bytes method to attempt to generate randomness.
+                       // However don't do this on Windows with PHP < 5.3.4 due to a bug:
+                       // http://stackoverflow.com/questions/1940168/openssl-random-pseudo-bytes-is-slow-php
+                       // http://git.php.net/?p=php-src.git;a=commitdiff;h=cd62a70863c261b07f6dadedad9464f7e213cad5
+                       if ( function_exists( 'openssl_random_pseudo_bytes' )
+                               && ( !wfIsWindows() || version_compare( PHP_VERSION, '5.3.4', '>=' ) )
+                       ) {
+                               wfProfileIn( __METHOD__ . '-openssl' );
+                               $rem = $bytes - strlen( $buffer );
+                               $openssl_bytes = openssl_random_pseudo_bytes( $rem, $openssl_strong );
+                               if ( $openssl_bytes === false ) {
+                                       wfDebug( __METHOD__ . ": openssl_random_pseudo_bytes returned false.\n" );
+                               } else {
+                                       $buffer .= $openssl_bytes;
+                                       wfDebug( __METHOD__ . ": openssl_random_pseudo_bytes generated " . strlen( $openssl_bytes ) . " bytes of " . ( $openssl_strong ? "strong" : "weak" ) . " randomness.\n" );
+                               }
+                               if ( strlen( $buffer ) >= $bytes ) {
+                                       // openssl tells us if the random source was strong, if some of our data was generated
+                                       // using it use it's say on whether the randomness is strong
+                                       $this->strong = !!$openssl_strong;
+                               }
+                               wfProfileOut( __METHOD__ . '-openssl' );
+                       }
+               }
+
+               // Only read from urandom if we can control the buffer size or were passed forceStrong
+               if ( strlen( $buffer ) < $bytes && ( function_exists( 'stream_set_read_buffer' ) || $forceStrong ) ) {
+                       wfProfileIn( __METHOD__ . '-fopen-urandom' );
+                       $rem = $bytes - strlen( $buffer );
+                       if ( !function_exists( 'stream_set_read_buffer' ) && $forceStrong ) {
+                               wfDebug( __METHOD__ . ": Was forced to read from /dev/urandom without control over the buffer size.\n" );
+                       }
+                       // /dev/urandom is generally considered the best possible commonly
+                       // available random source, and is available on most *nix systems.
+                       wfSuppressWarnings();
+                       $urandom = fopen( "/dev/urandom", "rb" );
+                       wfRestoreWarnings();
+
+                       // Attempt to read all our random data from urandom
+                       // php's fread always does buffered reads based on the stream's chunk_size
+                       // so in reality it will usually read more than the amount of data we're
+                       // asked for and not storing that risks depleting the system's random pool.
+                       // If stream_set_read_buffer is available set the chunk_size to the amount
+                       // of data we need. Otherwise read 8k, php's default chunk_size.
+                       if ( $urandom ) {
+                               // php's default chunk_size is 8k
+                               $chunk_size = 1024 * 8;
+                               if ( function_exists( 'stream_set_read_buffer' ) ) {
+                                       // If possible set the chunk_size to the amount of data we need
+                                       stream_set_read_buffer( $urandom, $rem );
+                                       $chunk_size = $rem;
+                               }
+                               $random_bytes = fread( $urandom, max( $chunk_size, $rem ) );
+                               $buffer .= $random_bytes;
+                               fclose( $urandom );
+                               wfDebug( __METHOD__ . ": /dev/urandom generated " . strlen( $random_bytes ) . " bytes of randomness.\n" );
+                               if ( strlen( $buffer ) >= $bytes ) {
+                                       // urandom is always strong, set to true if all our data was generated using it
+                                       $this->strong = true;
+                               }
+                       } else {
+                               wfDebug( __METHOD__ . ": /dev/urandom could not be opened.\n" );
+                       }
+                       wfProfileOut( __METHOD__ . '-fopen-urandom' );
+               }
+
+               // If we cannot use or generate enough data from a secure source
+               // use this loop to generate a good set of pseudo random data.
+               // This works by initializing a random state using a pile of unstable data
+               // and continually shoving it through a hash along with a variable salt.
+               // We hash the random state with more salt to avoid the state from leaking
+               // out and being used to predict the /randomness/ that follows.
+               if ( strlen( $buffer ) < $bytes ) {
+                       wfDebug( __METHOD__ . ": Falling back to using a pseudo random state to generate randomness.\n" );
+               }
+               while ( strlen( $buffer ) < $bytes ) {
+                       wfProfileIn( __METHOD__ . '-fallback' );
+                       $buffer .= $this->hmac( $this->randomState(), mt_rand() );
+                       // This code is never really cryptographically strong, if we use it
+                       // at all, then set strong to false.
+                       $this->strong = false;
+                       wfProfileOut( __METHOD__ . '-fallback' );
+               }
+
+               // Once the buffer has been filled up with enough random data to fulfill
+               // the request shift off enough data to handle the request and leave the
+               // unused portion left inside the buffer for the next request for random data
+               $generated = substr( $buffer, 0, $bytes );
+               $buffer = substr( $buffer, $bytes );
+
+               wfDebug( __METHOD__ . ": " . strlen( $buffer ) . " bytes of randomness leftover in the buffer.\n" );
+
+               wfProfileOut( __METHOD__ );
+               return $generated;
+       }
+
+       /**
+        * @see self::generateHex()
+        */
+       public function realGenerateHex( $chars, $forceStrong = false ) {
+               // hex strings are 2x the length of raw binary so we divide the length in half
+               // odd numbers will result in a .5 that leads the generate() being 1 character
+               // short, so we use ceil() to ensure that we always have enough bytes
+               $bytes = ceil( $chars / 2 );
+               // Generate the data and then convert it to a hex string
+               $hex = bin2hex( $this->generate( $bytes, $forceStrong ) );
+               // A bit of paranoia here, the caller asked for a specific length of string
+               // here, and it's possible (eg when given an odd number) that we may actually
+               // have at least 1 char more than they asked for. Just in case they made this
+               // call intending to insert it into a database that does truncation we don't
+               // want to give them too much and end up with their database and their live
+               // code having two different values because part of what we gave them is truncated
+               // hence, we strip out any run of characters longer than what we were asked for.
+               return substr( $hex, 0, $chars );
+       }
+
+       /** Publicly exposed static methods **/
+
+       /**
+        * Return a singleton instance of MWCryptRand
+        * @return MWCryptRand
+        */
+       protected static function singleton() {
+               if ( is_null( self::$singleton ) ) {
+                       self::$singleton = new self;
+               }
+               return self::$singleton;
+       }
+
+       /**
+        * Return a boolean indicating whether or not the source used for cryptographic
+        * random bytes generation in the previously run generate* call
+        * was cryptographically strong.
+        *
+        * @return bool Returns true if the source was strong, false if not.
+        */
+       public static function wasStrong() {
+               return self::singleton()->realWasStrong();
+       }
+
+       /**
+        * Generate a run of (ideally) cryptographically random data and return
+        * it in raw binary form.
+        * You can use MWCryptRand::wasStrong() if you wish to know if the source used
+        * was cryptographically strong.
+        *
+        * @param int $bytes the number of bytes of random data to generate
+        * @param bool $forceStrong Pass true if you want generate to prefer cryptographically
+        *                          strong sources of entropy even if reading from them may steal
+        *                          more entropy from the system than optimal.
+        * @return String Raw binary random data
+        */
+       public static function generate( $bytes, $forceStrong = false ) {
+               return self::singleton()->realGenerate( $bytes, $forceStrong );
+       }
+
+       /**
+        * Generate a run of (ideally) cryptographically random data and return
+        * it in hexadecimal string format.
+        * You can use MWCryptRand::wasStrong() if you wish to know if the source used
+        * was cryptographically strong.
+        *
+        * @param int $chars the number of hex chars of random data to generate
+        * @param bool $forceStrong Pass true if you want generate to prefer cryptographically
+        *                          strong sources of entropy even if reading from them may steal
+        *                          more entropy from the system than optimal.
+        * @return String Hexadecimal random data
+        */
+       public static function generateHex( $chars, $forceStrong = false ) {
+               return self::singleton()->realGenerateHex( $chars, $forceStrong );
+       }
+
+}
diff --git a/includes/utils/MWFunction.php b/includes/utils/MWFunction.php
new file mode 100644 (file)
index 0000000..6d11d17
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+/**
+ * Helper methods to call functions and instance objects.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+class MWFunction {
+
+       /**
+        * @deprecated since 1.22; use call_user_func()
+        * @param $callback
+        * @return mixed
+        */
+       public static function call( $callback ) {
+               wfDeprecated( __METHOD__, '1.22' );
+               $args = func_get_args();
+               return call_user_func_array( 'call_user_func', $args );
+       }
+
+       /**
+        * @deprecated since 1.22; use call_user_func_array()
+        * @param $callback
+        * @param $argsarams
+        * @return mixed
+        */
+       public static function callArray( $callback, $argsarams ) {
+               wfDeprecated( __METHOD__, '1.22' );
+               return call_user_func_array( $callback, $argsarams );
+       }
+
+       /**
+        * @param $class
+        * @param $args array
+        * @return object
+        */
+       public static function newObj( $class, $args = array() ) {
+               if ( !count( $args ) ) {
+                       return new $class;
+               }
+
+               $ref = new ReflectionClass( $class );
+               return $ref->newInstanceArgs( $args );
+       }
+
+}
diff --git a/includes/utils/MappedIterator.php b/includes/utils/MappedIterator.php
new file mode 100644 (file)
index 0000000..70d2032
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+/**
+ * Convenience class for generating iterators from iterators.
+ *
+ * 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 Aaron Schulz
+ */
+
+/**
+ * Convenience class for generating iterators from iterators.
+ *
+ * @since 1.21
+ */
+class MappedIterator extends FilterIterator {
+       /** @var callable */
+       protected $vCallback;
+       /** @var callable */
+       protected $aCallback;
+       /** @var array */
+       protected $cache = array();
+
+       protected $rewound = false; // boolean; whether rewind() has been called
+
+       /**
+        * Build an new iterator from a base iterator by having the former wrap the
+        * later, returning the result of "value" callback for each current() invocation.
+        * The callback takes the result of current() on the base iterator as an argument.
+        * The keys of the base iterator are reused verbatim.
+        *
+        * An "accept" callback can also be provided which will be called for each value in
+        * the base iterator (post-callback) and will return true if that value should be
+        * included in iteration of the MappedIterator (otherwise it will be filtered out).
+        *
+        * @param Iterator|Array $iter
+        * @param callable $vCallback Value transformation callback
+        * @param array $options Options map (includes "accept") (since 1.22)
+        * @throws MWException
+        */
+       public function __construct( $iter, $vCallback, array $options = array() ) {
+               if ( is_array( $iter ) ) {
+                       $baseIterator = new ArrayIterator( $iter );
+               } elseif ( $iter instanceof Iterator ) {
+                       $baseIterator = $iter;
+               } else {
+                       throw new MWException( "Invalid base iterator provided." );
+               }
+               parent::__construct( $baseIterator );
+               $this->vCallback = $vCallback;
+               $this->aCallback = isset( $options['accept'] ) ? $options['accept'] : null;
+       }
+
+       public function next() {
+               $this->cache = array();
+               parent::next();
+       }
+
+       public function rewind() {
+               $this->rewound = true;
+               $this->cache = array();
+               parent::rewind();
+       }
+
+       public function accept() {
+               $value = call_user_func( $this->vCallback, $this->getInnerIterator()->current() );
+               $ok = ( $this->aCallback ) ? call_user_func( $this->aCallback, $value ) : true;
+               if ( $ok ) {
+                       $this->cache['current'] = $value;
+               }
+               return $ok;
+       }
+
+       public function key() {
+               $this->init();
+               return parent::key();
+       }
+
+       public function valid() {
+               $this->init();
+               return parent::valid();
+       }
+
+       public function current() {
+               $this->init();
+               if ( parent::valid() ) {
+                       return $this->cache['current'];
+               } else {
+                       return null; // out of range
+               }
+       }
+
+       /**
+        * Obviate the usual need for rewind() before using a FilterIterator in a manual loop
+        */
+       protected function init() {
+               if ( !$this->rewound ) {
+                       $this->rewind();
+               }
+       }
+}
diff --git a/includes/utils/README b/includes/utils/README
new file mode 100644 (file)
index 0000000..b5b8ec8
--- /dev/null
@@ -0,0 +1,9 @@
+The classes in this directory are general utilities for use by any part of
+MediaWiki. They do not favour any particular user interface and are not
+constrained to serve any particular feature. This is similar to includes/libs,
+except that some dependency on the MediaWiki framework (such as the use of
+MWException, Status or wfDebug()) disqualifies them from use outside of
+MediaWiki without modification.
+
+Utilities should not use global configuration variables, rather they should rely
+on the caller to configure their behaviour.
diff --git a/includes/utils/ScopedCallback.php b/includes/utils/ScopedCallback.php
new file mode 100644 (file)
index 0000000..ef22e0a
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+/**
+ * This file deals with RAII style scoped callbacks.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Class for asserting that a callback happens when an dummy object leaves scope
+ *
+ * @since 1.21
+ */
+class ScopedCallback {
+       /** @var callable */
+       protected $callback;
+
+       /**
+        * @param callable $callback
+        * @throws MWException
+        */
+       public function __construct( $callback ) {
+               if ( !is_callable( $callback ) ) {
+                       throw new MWException( "Provided callback is not valid." );
+               }
+               $this->callback = $callback;
+       }
+
+       /**
+        * Trigger a scoped callback and destroy it.
+        * This is the same is just setting it to null.
+        *
+        * @param ScopedCallback $sc
+        */
+       public static function consume( ScopedCallback &$sc = null ) {
+               $sc = null;
+       }
+
+       /**
+        * Destroy a scoped callback without triggering it
+        *
+        * @param ScopedCallback $sc
+        */
+       public static function cancel( ScopedCallback &$sc = null ) {
+               if ( $sc ) {
+                       $sc->callback = null;
+               }
+               $sc = null;
+       }
+
+       /**
+        * Trigger the callback when this leaves scope
+        */
+       function __destruct() {
+               if ( $this->callback !== null ) {
+                       call_user_func( $this->callback );
+               }
+       }
+}
diff --git a/includes/utils/StringUtils.php b/includes/utils/StringUtils.php
new file mode 100644 (file)
index 0000000..c1545e6
--- /dev/null
@@ -0,0 +1,606 @@
+<?php
+/**
+ * Methods to play with strings.
+ *
+ * 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
+ */
+
+/**
+ * A collection of static methods to play with strings.
+ */
+class StringUtils {
+
+       /**
+        * Test whether a string is valid UTF-8.
+        *
+        * The function check for invalid byte sequences, overlong encoding but
+        * not for different normalisations.
+        *
+        * This relies internally on the mbstring function mb_check_encoding()
+        * hardcoded to check against UTF-8. Whenever the function is not available
+        * we fallback to a pure PHP implementation. Setting $disableMbstring to
+        * true will skip the use of mb_check_encoding, this is mostly intended for
+        * unit testing our internal implementation.
+        *
+        * @since 1.21
+        * @note In MediaWiki 1.21, this function did not provide proper UTF-8 validation.
+        * In particular, the pure PHP code path did not in fact check for overlong forms.
+        * Beware of this when backporting code to that version of MediaWiki.
+        *
+        * @param string $value String to check
+        * @param boolean $disableMbstring Whether to use the pure PHP
+        * implementation instead of trying mb_check_encoding. Intended for unit
+        * testing. Default: false
+        *
+        * @return boolean Whether the given $value is a valid UTF-8 encoded string
+        */
+       static function isUtf8( $value, $disableMbstring = false ) {
+               $value = (string)$value;
+
+               // If the mbstring extension is loaded, use it. However, before PHP 5.4, values above
+               // U+10FFFF are incorrectly allowed, so we have to check for them separately.
+               if ( !$disableMbstring && function_exists( 'mb_check_encoding' ) ) {
+                       static $newPHP;
+                       if ( $newPHP === null ) {
+                               $newPHP = !mb_check_encoding( "\xf4\x90\x80\x80", 'UTF-8' );
+                       }
+
+                       return mb_check_encoding( $value, 'UTF-8' ) &&
+                               ( $newPHP || preg_match( "/\xf4[\x90-\xbf]|[\xf5-\xff]/S", $value ) === 0 );
+               }
+
+               if ( preg_match( "/[\x80-\xff]/S", $value ) === 0 ) {
+                       // String contains only ASCII characters, has to be valid
+                       return true;
+               }
+
+               // PCRE implements repetition using recursion; to avoid a stack overflow (and segfault)
+               // for large input, we check for invalid sequences (<= 5 bytes) rather than valid
+               // sequences, which can be as long as the input string is. Multiple short regexes are
+               // used rather than a single long regex for performance.
+               static $regexes;
+               if ( $regexes === null ) {
+                       $cont = "[\x80-\xbf]";
+                       $after = "(?!$cont)"; // "(?:[^\x80-\xbf]|$)" would work here
+                       $regexes = array(
+                               // Continuation byte at the start
+                               "/^$cont/",
+
+                               // ASCII byte followed by a continuation byte
+                               "/[\\x00-\x7f]$cont/S",
+
+                               // Illegal byte
+                               "/[\xc0\xc1\xf5-\xff]/S",
+
+                               // Invalid 2-byte sequence, or valid one then an extra continuation byte
+                               "/[\xc2-\xdf](?!$cont$after)/S",
+
+                               // Invalid 3-byte sequence, or valid one then an extra continuation byte
+                               "/\xe0(?![\xa0-\xbf]$cont$after)/",
+                               "/[\xe1-\xec\xee\xef](?!$cont{2}$after)/S",
+                               "/\xed(?![\x80-\x9f]$cont$after)/",
+
+                               // Invalid 4-byte sequence, or valid one then an extra continuation byte
+                               "/\xf0(?![\x90-\xbf]$cont{2}$after)/",
+                               "/[\xf1-\xf3](?!$cont{3}$after)/S",
+                               "/\xf4(?![\x80-\x8f]$cont{2}$after)/",
+                       );
+               }
+
+               foreach ( $regexes as $regex ) {
+                       if ( preg_match( $regex, $value ) !== 0 ) {
+                               return false;
+                       }
+               }
+               return true;
+       }
+
+       /**
+        * Perform an operation equivalent to
+        *
+        *     preg_replace( "!$startDelim(.*?)$endDelim!", $replace, $subject );
+        *
+        * except that it's worst-case O(N) instead of O(N^2)
+        *
+        * Compared to delimiterReplace(), this implementation is fast but memory-
+        * hungry and inflexible. The memory requirements are such that I don't
+        * recommend using it on anything but guaranteed small chunks of text.
+        *
+        * @param $startDelim
+        * @param $endDelim
+        * @param $replace
+        * @param $subject
+        *
+        * @return string
+        */
+       static function hungryDelimiterReplace( $startDelim, $endDelim, $replace, $subject ) {
+               $segments = explode( $startDelim, $subject );
+               $output = array_shift( $segments );
+               foreach ( $segments as $s ) {
+                       $endDelimPos = strpos( $s, $endDelim );
+                       if ( $endDelimPos === false ) {
+                               $output .= $startDelim . $s;
+                       } else {
+                               $output .= $replace . substr( $s, $endDelimPos + strlen( $endDelim ) );
+                       }
+               }
+               return $output;
+       }
+
+       /**
+        * Perform an operation equivalent to
+        *
+        *   preg_replace_callback( "!$startDelim(.*)$endDelim!s$flags", $callback, $subject )
+        *
+        * This implementation is slower than hungryDelimiterReplace but uses far less
+        * memory. The delimiters are literal strings, not regular expressions.
+        *
+        * If the start delimiter ends with an initial substring of the end delimiter,
+        * e.g. in the case of C-style comments, the behavior differs from the model
+        * regex. In this implementation, the end must share no characters with the
+        * start, so e.g. /*\/ is not considered to be both the start and end of a
+        * comment. /*\/xy/*\/ is considered to be a single comment with contents /xy/.
+        *
+        * @param string $startDelim start delimiter
+        * @param string $endDelim end delimiter
+        * @param $callback Callback: function to call on each match
+        * @param $subject String
+        * @param string $flags regular expression flags
+        * @throws MWException
+        * @return string
+        */
+       static function delimiterReplaceCallback( $startDelim, $endDelim, $callback, $subject, $flags = '' ) {
+               $inputPos = 0;
+               $outputPos = 0;
+               $output = '';
+               $foundStart = false;
+               $encStart = preg_quote( $startDelim, '!' );
+               $encEnd = preg_quote( $endDelim, '!' );
+               $strcmp = strpos( $flags, 'i' ) === false ? 'strcmp' : 'strcasecmp';
+               $endLength = strlen( $endDelim );
+               $m = array();
+
+               while ( $inputPos < strlen( $subject ) &&
+                       preg_match( "!($encStart)|($encEnd)!S$flags", $subject, $m, PREG_OFFSET_CAPTURE, $inputPos ) )
+               {
+                       $tokenOffset = $m[0][1];
+                       if ( $m[1][0] != '' ) {
+                               if ( $foundStart &&
+                                       $strcmp( $endDelim, substr( $subject, $tokenOffset, $endLength ) ) == 0 )
+                               {
+                                       # An end match is present at the same location
+                                       $tokenType = 'end';
+                                       $tokenLength = $endLength;
+                               } else {
+                                       $tokenType = 'start';
+                                       $tokenLength = strlen( $m[0][0] );
+                               }
+                       } elseif ( $m[2][0] != '' ) {
+                               $tokenType = 'end';
+                               $tokenLength = strlen( $m[0][0] );
+                       } else {
+                               throw new MWException( 'Invalid delimiter given to ' . __METHOD__ );
+                       }
+
+                       if ( $tokenType == 'start' ) {
+                               # Only move the start position if we haven't already found a start
+                               # This means that START START END matches outer pair
+                               if ( !$foundStart ) {
+                                       # Found start
+                                       $inputPos = $tokenOffset + $tokenLength;
+                                       # Write out the non-matching section
+                                       $output .= substr( $subject, $outputPos, $tokenOffset - $outputPos );
+                                       $outputPos = $tokenOffset;
+                                       $contentPos = $inputPos;
+                                       $foundStart = true;
+                               } else {
+                                       # Move the input position past the *first character* of START,
+                                       # to protect against missing END when it overlaps with START
+                                       $inputPos = $tokenOffset + 1;
+                               }
+                       } elseif ( $tokenType == 'end' ) {
+                               if ( $foundStart ) {
+                                       # Found match
+                                       $output .= call_user_func( $callback, array(
+                                               substr( $subject, $outputPos, $tokenOffset + $tokenLength - $outputPos ),
+                                               substr( $subject, $contentPos, $tokenOffset - $contentPos )
+                                       ));
+                                       $foundStart = false;
+                               } else {
+                                       # Non-matching end, write it out
+                                       $output .= substr( $subject, $inputPos, $tokenOffset + $tokenLength - $outputPos );
+                               }
+                               $inputPos = $outputPos = $tokenOffset + $tokenLength;
+                       } else {
+                               throw new MWException( 'Invalid delimiter given to ' . __METHOD__ );
+                       }
+               }
+               if ( $outputPos < strlen( $subject ) ) {
+                       $output .= substr( $subject, $outputPos );
+               }
+               return $output;
+       }
+
+       /**
+        * Perform an operation equivalent to
+        *
+        *   preg_replace( "!$startDelim(.*)$endDelim!$flags", $replace, $subject )
+        *
+        * @param string $startDelim start delimiter regular expression
+        * @param string $endDelim end delimiter regular expression
+        * @param string $replace replacement string. May contain $1, which will be
+        *                 replaced by the text between the delimiters
+        * @param string $subject to search
+        * @param string $flags regular expression flags
+        * @return String: The string with the matches replaced
+        */
+       static function delimiterReplace( $startDelim, $endDelim, $replace, $subject, $flags = '' ) {
+               $replacer = new RegexlikeReplacer( $replace );
+               return self::delimiterReplaceCallback( $startDelim, $endDelim,
+                       $replacer->cb(), $subject, $flags );
+       }
+
+       /**
+        * More or less "markup-safe" explode()
+        * Ignores any instances of the separator inside <...>
+        * @param string $separator
+        * @param string $text
+        * @return array
+        */
+       static function explodeMarkup( $separator, $text ) {
+               $placeholder = "\x00";
+
+               // Remove placeholder instances
+               $text = str_replace( $placeholder, '', $text );
+
+               // Replace instances of the separator inside HTML-like tags with the placeholder
+               $replacer = new DoubleReplacer( $separator, $placeholder );
+               $cleaned = StringUtils::delimiterReplaceCallback( '<', '>', $replacer->cb(), $text );
+
+               // Explode, then put the replaced separators back in
+               $items = explode( $separator, $cleaned );
+               foreach ( $items as $i => $str ) {
+                       $items[$i] = str_replace( $placeholder, $separator, $str );
+               }
+
+               return $items;
+       }
+
+       /**
+        * Escape a string to make it suitable for inclusion in a preg_replace()
+        * replacement parameter.
+        *
+        * @param string $string
+        * @return string
+        */
+       static function escapeRegexReplacement( $string ) {
+               $string = str_replace( '\\', '\\\\', $string );
+               $string = str_replace( '$', '\\$', $string );
+               return $string;
+       }
+
+       /**
+        * Workalike for explode() with limited memory usage.
+        * Returns an Iterator
+        * @param string $separator
+        * @param string $subject
+        * @return ArrayIterator|ExplodeIterator
+        */
+       static function explode( $separator, $subject ) {
+               if ( substr_count( $subject, $separator ) > 1000 ) {
+                       return new ExplodeIterator( $separator, $subject );
+               } else {
+                       return new ArrayIterator( explode( $separator, $subject ) );
+               }
+       }
+}
+
+/**
+ * Base class for "replacers", objects used in preg_replace_callback() and
+ * StringUtils::delimiterReplaceCallback()
+ */
+class Replacer {
+
+       /**
+        * @return array
+        */
+       function cb() {
+               return array( &$this, 'replace' );
+       }
+}
+
+/**
+ * Class to replace regex matches with a string similar to that used in preg_replace()
+ */
+class RegexlikeReplacer extends Replacer {
+       var $r;
+
+       /**
+        * @param string $r
+        */
+       function __construct( $r ) {
+               $this->r = $r;
+       }
+
+       /**
+        * @param array $matches
+        * @return string
+        */
+       function replace( $matches ) {
+               $pairs = array();
+               foreach ( $matches as $i => $match ) {
+                       $pairs["\$$i"] = $match;
+               }
+               return strtr( $this->r, $pairs );
+       }
+
+}
+
+/**
+ * Class to perform secondary replacement within each replacement string
+ */
+class DoubleReplacer extends Replacer {
+
+       /**
+        * @param $from
+        * @param $to
+        * @param int $index
+        */
+       function __construct( $from, $to, $index = 0 ) {
+               $this->from = $from;
+               $this->to = $to;
+               $this->index = $index;
+       }
+
+       /**
+        * @param array $matches
+        * @return mixed
+        */
+       function replace( $matches ) {
+               return str_replace( $this->from, $this->to, $matches[$this->index] );
+       }
+}
+
+/**
+ * Class to perform replacement based on a simple hashtable lookup
+ */
+class HashtableReplacer extends Replacer {
+       var $table, $index;
+
+       /**
+        * @param $table
+        * @param int $index
+        */
+       function __construct( $table, $index = 0 ) {
+               $this->table = $table;
+               $this->index = $index;
+       }
+
+       /**
+        * @param array $matches
+        * @return mixed
+        */
+       function replace( $matches ) {
+               return $this->table[$matches[$this->index]];
+       }
+}
+
+/**
+ * Replacement array for FSS with fallback to strtr()
+ * Supports lazy initialisation of FSS resource
+ */
+class ReplacementArray {
+       /*mostly private*/ var $data = false;
+       /*mostly private*/ var $fss = false;
+
+       /**
+        * Create an object with the specified replacement array
+        * The array should have the same form as the replacement array for strtr()
+        * @param array $data
+        */
+       function __construct( $data = array() ) {
+               $this->data = $data;
+       }
+
+       /**
+        * @return array
+        */
+       function __sleep() {
+               return array( 'data' );
+       }
+
+       function __wakeup() {
+               $this->fss = false;
+       }
+
+       /**
+        * Set the whole replacement array at once
+        * @param array $data
+        */
+       function setArray( $data ) {
+               $this->data = $data;
+               $this->fss = false;
+       }
+
+       /**
+        * @return array|bool
+        */
+       function getArray() {
+               return $this->data;
+       }
+
+       /**
+        * Set an element of the replacement array
+        * @param string $from
+        * @param string $to
+        */
+       function setPair( $from, $to ) {
+               $this->data[$from] = $to;
+               $this->fss = false;
+       }
+
+       /**
+        * @param array $data
+        */
+       function mergeArray( $data ) {
+               $this->data = array_merge( $this->data, $data );
+               $this->fss = false;
+       }
+
+       /**
+        * @param ReplacementArray $other
+        */
+       function merge( $other ) {
+               $this->data = array_merge( $this->data, $other->data );
+               $this->fss = false;
+       }
+
+       /**
+        * @param string $from
+        */
+       function removePair( $from ) {
+               unset( $this->data[$from] );
+               $this->fss = false;
+       }
+
+       /**
+        * @param array $data
+        */
+       function removeArray( $data ) {
+               foreach ( $data as $from => $to ) {
+                       $this->removePair( $from );
+               }
+               $this->fss = false;
+       }
+
+       /**
+        * @param string $subject
+        * @return string
+        */
+       function replace( $subject ) {
+               if ( function_exists( 'fss_prep_replace' ) ) {
+                       wfProfileIn( __METHOD__ . '-fss' );
+                       if ( $this->fss === false ) {
+                               $this->fss = fss_prep_replace( $this->data );
+                       }
+                       $result = fss_exec_replace( $this->fss, $subject );
+                       wfProfileOut( __METHOD__ . '-fss' );
+               } else {
+                       wfProfileIn( __METHOD__ . '-strtr' );
+                       $result = strtr( $subject, $this->data );
+                       wfProfileOut( __METHOD__ . '-strtr' );
+               }
+               return $result;
+       }
+}
+
+/**
+ * An iterator which works exactly like:
+ *
+ * foreach ( explode( $delim, $s ) as $element ) {
+ *    ...
+ * }
+ *
+ * Except it doesn't use 193 byte per element
+ */
+class ExplodeIterator implements Iterator {
+       // The subject string
+       var $subject, $subjectLength;
+
+       // The delimiter
+       var $delim, $delimLength;
+
+       // The position of the start of the line
+       var $curPos;
+
+       // The position after the end of the next delimiter
+       var $endPos;
+
+       // The current token
+       var $current;
+
+       /**
+        * Construct a DelimIterator
+        * @param string $delim
+        * @param string $subject
+        */
+       function __construct( $delim, $subject ) {
+               $this->subject = $subject;
+               $this->delim = $delim;
+
+               // Micro-optimisation (theoretical)
+               $this->subjectLength = strlen( $subject );
+               $this->delimLength = strlen( $delim );
+
+               $this->rewind();
+       }
+
+       function rewind() {
+               $this->curPos = 0;
+               $this->endPos = strpos( $this->subject, $this->delim );
+               $this->refreshCurrent();
+       }
+
+       function refreshCurrent() {
+               if ( $this->curPos === false ) {
+                       $this->current = false;
+               } elseif ( $this->curPos >= $this->subjectLength ) {
+                       $this->current = '';
+               } elseif ( $this->endPos === false ) {
+                       $this->current = substr( $this->subject, $this->curPos );
+               } else {
+                       $this->current = substr( $this->subject, $this->curPos, $this->endPos - $this->curPos );
+               }
+       }
+
+       function current() {
+               return $this->current;
+       }
+
+       /**
+        * @return int|bool Current position or boolean false if invalid
+        */
+       function key() {
+               return $this->curPos;
+       }
+
+       /**
+        * @return string
+        */
+       function next() {
+               if ( $this->endPos === false ) {
+                       $this->curPos = false;
+               } else {
+                       $this->curPos = $this->endPos + $this->delimLength;
+                       if ( $this->curPos >= $this->subjectLength ) {
+                               $this->endPos = false;
+                       } else {
+                               $this->endPos = strpos( $this->subject, $this->delim, $this->curPos );
+                       }
+               }
+               $this->refreshCurrent();
+               return $this->current;
+       }
+
+       /**
+        * @return bool
+        */
+       function valid() {
+               return $this->curPos !== false;
+       }
+}
diff --git a/includes/utils/UIDGenerator.php b/includes/utils/UIDGenerator.php
new file mode 100644 (file)
index 0000000..963e51a
--- /dev/null
@@ -0,0 +1,337 @@
+<?php
+/**
+ * This file deals with UID generation.
+ *
+ * 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 Aaron Schulz
+ */
+
+/**
+ * Class for getting statistically unique IDs
+ *
+ * @since 1.21
+ */
+class UIDGenerator {
+       /** @var UIDGenerator */
+       protected static $instance = null;
+
+       protected $nodeId32; // string; node ID in binary (32 bits)
+       protected $nodeId48; // string; node ID in binary (48 bits)
+
+       protected $lockFile88; // string; local file path
+       protected $lockFile128; // string; local file path
+
+       /** @var Array */
+       protected $fileHandles = array(); // cache file handles
+
+       const QUICK_RAND = 1; // get randomness from fast and insecure sources
+
+       protected function __construct() {
+               $idFile = wfTempDir() . '/mw-' . __CLASS__ . '-UID-nodeid';
+               $nodeId = is_file( $idFile ) ? file_get_contents( $idFile ) : '';
+               // Try to get some ID that uniquely identifies this machine (RFC 4122)...
+               if ( !preg_match( '/^[0-9a-f]{12}$/i', $nodeId ) ) {
+                       wfSuppressWarnings();
+                       if ( wfIsWindows() ) {
+                               // http://technet.microsoft.com/en-us/library/bb490913.aspx
+                               $csv = trim( wfShellExec( 'getmac /NH /FO CSV' ) );
+                               $line = substr( $csv, 0, strcspn( $csv, "\n" ) );
+                               $info = str_getcsv( $line );
+                               $nodeId = isset( $info[0] ) ? str_replace( '-', '', $info[0] ) : '';
+                       } elseif ( is_executable( '/sbin/ifconfig' ) ) { // Linux/BSD/Solaris/OS X
+                               // See http://linux.die.net/man/8/ifconfig
+                               $m = array();
+                               preg_match( '/\s([0-9a-f]{2}(:[0-9a-f]{2}){5})\s/',
+                                       wfShellExec( '/sbin/ifconfig -a' ), $m );
+                               $nodeId = isset( $m[1] ) ? str_replace( ':', '', $m[1] ) : '';
+                       }
+                       wfRestoreWarnings();
+                       if ( !preg_match( '/^[0-9a-f]{12}$/i', $nodeId ) ) {
+                               $nodeId = MWCryptRand::generateHex( 12, true );
+                               $nodeId[1] = dechex( hexdec( $nodeId[1] ) | 0x1 ); // set multicast bit
+                       }
+                       file_put_contents( $idFile, $nodeId ); // cache
+               }
+               $this->nodeId32 = wfBaseConvert( substr( sha1( $nodeId ), 0, 8 ), 16, 2, 32 );
+               $this->nodeId48 = wfBaseConvert( $nodeId, 16, 2, 48 );
+               // If different processes run as different users, they may have different temp dirs.
+               // This is dealt with by initializing the clock sequence number and counters randomly.
+               $this->lockFile88 = wfTempDir() . '/mw-' . __CLASS__ . '-UID-88';
+               $this->lockFile128 = wfTempDir() . '/mw-' . __CLASS__ . '-UID-128';
+       }
+
+       /**
+        * @return UIDGenerator
+        */
+       protected static function singleton() {
+               if ( self::$instance === null ) {
+                       self::$instance = new self();
+               }
+               return self::$instance;
+       }
+
+       /**
+        * Get a statistically unique 88-bit unsigned integer ID string.
+        * The bits of the UID are prefixed with the time (down to the millisecond).
+        *
+        * These IDs are suitable as values for the shard key of distributed data.
+        * If a column uses these as values, it should be declared UNIQUE to handle collisions.
+        * New rows almost always have higher UIDs, which makes B-TREE updates on INSERT fast.
+        * They can also be stored "DECIMAL(27) UNSIGNED" or BINARY(11) in MySQL.
+        *
+        * UID generation is serialized on each server (as the node ID is for the whole machine).
+        *
+        * @param $base integer Specifies a base other than 10
+        * @return string Number
+        * @throws MWException
+        */
+       public static function newTimestampedUID88( $base = 10 ) {
+               if ( !is_integer( $base ) || $base > 36 || $base < 2 ) {
+                       throw new MWException( "Base must an integer be between 2 and 36" );
+               }
+               $gen = self::singleton();
+               $time = $gen->getTimestampAndDelay( 'lockFile88', 1, 1024 );
+               return wfBaseConvert( $gen->getTimestampedID88( $time ), 2, $base );
+       }
+
+       /**
+        * @param array $time (UIDGenerator::millitime(), clock sequence)
+        * @return string 88 bits
+        */
+       protected function getTimestampedID88( array $info ) {
+               list( $time, $counter ) = $info;
+               // Take the 46 MSBs of "milliseconds since epoch"
+               $id_bin = $this->millisecondsSinceEpochBinary( $time );
+               // Add a 10 bit counter resulting in 56 bits total
+               $id_bin .= str_pad( decbin( $counter ), 10, '0', STR_PAD_LEFT );
+               // Add the 32 bit node ID resulting in 88 bits total
+               $id_bin .= $this->nodeId32;
+               // Convert to a 1-27 digit integer string
+               if ( strlen( $id_bin ) !== 88 ) {
+                       throw new MWException( "Detected overflow for millisecond timestamp." );
+               }
+               return $id_bin;
+       }
+
+       /**
+        * Get a statistically unique 128-bit unsigned integer ID string.
+        * The bits of the UID are prefixed with the time (down to the millisecond).
+        *
+        * These IDs are suitable as globally unique IDs, without any enforced uniqueness.
+        * New rows almost always have higher UIDs, which makes B-TREE updates on INSERT fast.
+        * They can also be stored as "DECIMAL(39) UNSIGNED" or BINARY(16) in MySQL.
+        *
+        * UID generation is serialized on each server (as the node ID is for the whole machine).
+        *
+        * @param $base integer Specifies a base other than 10
+        * @return string Number
+        * @throws MWException
+        */
+       public static function newTimestampedUID128( $base = 10 ) {
+               if ( !is_integer( $base ) || $base > 36 || $base < 2 ) {
+                       throw new MWException( "Base must be an integer between 2 and 36" );
+               }
+               $gen = self::singleton();
+               $time = $gen->getTimestampAndDelay( 'lockFile128', 16384, 1048576 );
+               return wfBaseConvert( $gen->getTimestampedID128( $time ), 2, $base );
+       }
+
+       /**
+        * @param array $info (UIDGenerator::millitime(), counter, clock sequence)
+        * @return string 128 bits
+        */
+       protected function getTimestampedID128( array $info ) {
+               list( $time, $counter, $clkSeq ) = $info;
+               // Take the 46 MSBs of "milliseconds since epoch"
+               $id_bin = $this->millisecondsSinceEpochBinary( $time );
+               // Add a 20 bit counter resulting in 66 bits total
+               $id_bin .= str_pad( decbin( $counter ), 20, '0', STR_PAD_LEFT );
+               // Add a 14 bit clock sequence number resulting in 80 bits total
+               $id_bin .= str_pad( decbin( $clkSeq ), 14, '0', STR_PAD_LEFT );
+               // Add the 48 bit node ID resulting in 128 bits total
+               $id_bin .= $this->nodeId48;
+               // Convert to a 1-39 digit integer string
+               if ( strlen( $id_bin ) !== 128 ) {
+                       throw new MWException( "Detected overflow for millisecond timestamp." );
+               }
+               return $id_bin;
+       }
+
+       /**
+        * Return an RFC4122 compliant v4 UUID
+        *
+        * @param $flags integer Bitfield (supports UIDGenerator::QUICK_RAND)
+        * @return string
+        * @throws MWException
+        */
+       public static function newUUIDv4( $flags = 0 ) {
+               $hex = ( $flags & self::QUICK_RAND )
+                       ? wfRandomString( 31 )
+                       : MWCryptRand::generateHex( 31 );
+
+               return sprintf( '%s-%s-%s-%s-%s',
+                       // "time_low" (32 bits)
+                       substr( $hex, 0, 8 ),
+                       // "time_mid" (16 bits)
+                       substr( $hex, 8, 4 ),
+                       // "time_hi_and_version" (16 bits)
+                       '4' . substr( $hex, 12, 3 ),
+                       // "clk_seq_hi_res (8 bits, variant is binary 10x) and "clk_seq_low" (8 bits)
+                       dechex( 0x8 | ( hexdec( $hex[15] ) & 0x3 ) ) . $hex[16] . substr( $hex, 17, 2 ),
+                       // "node" (48 bits)
+                       substr( $hex, 19, 12 )
+               );
+       }
+
+       /**
+        * Return an RFC4122 compliant v4 UUID
+        *
+        * @param $flags integer Bitfield (supports UIDGenerator::QUICK_RAND)
+        * @return string 32 hex characters with no hyphens
+        * @throws MWException
+        */
+       public static function newRawUUIDv4( $flags = 0 ) {
+               return str_replace( '-', '', self::newUUIDv4( $flags ) );
+       }
+
+       /**
+        * Get a (time,counter,clock sequence) where (time,counter) is higher
+        * than any previous (time,counter) value for the given clock sequence.
+        * This is useful for making UIDs sequential on a per-node bases.
+        *
+        * @param string $lockFile Name of a local lock file
+        * @param $clockSeqSize integer The number of possible clock sequence values
+        * @param $counterSize integer The number of possible counter values
+        * @return Array (result of UIDGenerator::millitime(), counter, clock sequence)
+        * @throws MWException
+        */
+       protected function getTimestampAndDelay( $lockFile, $clockSeqSize, $counterSize ) {
+               // Get the UID lock file handle
+               if ( isset( $this->fileHandles[$lockFile] ) ) {
+                       $handle = $this->fileHandles[$lockFile];
+               } else {
+                       $handle = fopen( $this->$lockFile, 'cb+' );
+                       $this->fileHandles[$lockFile] = $handle ?: null; // cache
+               }
+               // Acquire the UID lock file
+               if ( $handle === false ) {
+                       throw new MWException( "Could not open '{$this->$lockFile}'." );
+               } elseif ( !flock( $handle, LOCK_EX ) ) {
+                       throw new MWException( "Could not acquire '{$this->$lockFile}'." );
+               }
+               // Get the current timestamp, clock sequence number, last time, and counter
+               rewind( $handle );
+               $data = explode( ' ', fgets( $handle ) ); // "<clk seq> <sec> <msec> <counter> <offset>"
+               $clockChanged = false; // clock set back significantly?
+               if ( count( $data ) == 5 ) { // last UID info already initialized
+                       $clkSeq = (int)$data[0] % $clockSeqSize;
+                       $prevTime = array( (int)$data[1], (int)$data[2] );
+                       $offset = (int)$data[4] % $counterSize; // random counter offset
+                       $counter = 0; // counter for UIDs with the same timestamp
+                       // Delay until the clock reaches the time of the last ID.
+                       // This detects any microtime() drift among processes.
+                       $time = $this->timeWaitUntil( $prevTime );
+                       if ( !$time ) { // too long to delay?
+                               $clockChanged = true; // bump clock sequence number
+                               $time = self::millitime();
+                       } elseif ( $time == $prevTime ) {
+                               // Bump the counter if there are timestamp collisions
+                               $counter = (int)$data[3] % $counterSize;
+                               if ( ++$counter >= $counterSize ) { // sanity (starts at 0)
+                                       flock( $handle, LOCK_UN ); // abort
+                                       throw new MWException( "Counter overflow for timestamp value." );
+                               }
+                       }
+               } else { // last UID info not initialized
+                       $clkSeq = mt_rand( 0, $clockSeqSize - 1 );
+                       $counter = 0;
+                       $offset = mt_rand( 0, $counterSize - 1 );
+                       $time = self::millitime();
+               }
+               // microtime() and gettimeofday() can drift from time() at least on Windows.
+               // The drift is immediate for processes running while the system clock changes.
+               // time() does not have this problem. See https://bugs.php.net/bug.php?id=42659.
+               if ( abs( time() - $time[0] ) >= 2 ) {
+                       // We don't want processes using too high or low timestamps to avoid duplicate
+                       // UIDs and clock sequence number churn. This process should just be restarted.
+                       flock( $handle, LOCK_UN ); // abort
+                       throw new MWException( "Process clock is outdated or drifted." );
+               }
+               // If microtime() is synced and a clock change was detected, then the clock went back
+               if ( $clockChanged ) {
+                       // Bump the clock sequence number and also randomize the counter offset,
+                       // which is useful for UIDs that do not include the clock sequence number.
+                       $clkSeq = ( $clkSeq + 1 ) % $clockSeqSize;
+                       $offset = mt_rand( 0, $counterSize - 1 );
+                       trigger_error( "Clock was set back; sequence number incremented." );
+               }
+               // Update the (clock sequence number, timestamp, counter)
+               ftruncate( $handle, 0 );
+               rewind( $handle );
+               fwrite( $handle, "{$clkSeq} {$time[0]} {$time[1]} {$counter} {$offset}" );
+               fflush( $handle );
+               // Release the UID lock file
+               flock( $handle, LOCK_UN );
+
+               return array( $time, ( $counter + $offset ) % $counterSize, $clkSeq );
+       }
+
+       /**
+        * Wait till the current timestamp reaches $time and return the current
+        * timestamp. This returns false if it would have to wait more than 10ms.
+        *
+        * @param array $time Result of UIDGenerator::millitime()
+        * @return Array|bool UIDGenerator::millitime() result or false
+        */
+       protected function timeWaitUntil( array $time ) {
+               do {
+                       $ct = self::millitime();
+                       if ( $ct >= $time ) { // http://php.net/manual/en/language.operators.comparison.php
+                               return $ct; // current timestamp is higher than $time
+                       }
+               } while ( ( ( $time[0] - $ct[0] ) * 1000 + ( $time[1] - $ct[1] ) ) <= 10 );
+
+               return false;
+       }
+
+       /**
+        * @param array $time Result of UIDGenerator::millitime()
+        * @return string 46 MSBs of "milliseconds since epoch" in binary (rolls over in 4201)
+        */
+       protected function millisecondsSinceEpochBinary( array $time ) {
+               list( $sec, $msec ) = $time;
+               $ts = 1000 * $sec + $msec;
+               if ( $ts > pow( 2, 52 ) ) {
+                       throw new MWException( __METHOD__ .
+                               ': sorry, this function doesn\'t work after the year 144680' );
+               }
+               return substr( wfBaseConvert( $ts, 10, 2, 46 ), -46 );
+       }
+
+       /**
+        * @return Array (current time in seconds, milliseconds since then)
+        */
+       protected static function millitime() {
+               list( $msec, $sec ) = explode( ' ', microtime() );
+               return array( (int)$sec, (int)( $msec * 1000 ) );
+       }
+
+       function __destruct() {
+               array_map( 'fclose', $this->fileHandles );
+       }
+}
diff --git a/includes/utils/ZipDirectoryReader.php b/includes/utils/ZipDirectoryReader.php
new file mode 100644 (file)
index 0000000..307efce
--- /dev/null
@@ -0,0 +1,712 @@
+<?php
+/**
+ * ZIP file directories reader, for the purposes of upload verification.
+ *
+ * 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
+ */
+
+/**
+ * A class for reading ZIP file directories, for the purposes of upload
+ * verification.
+ *
+ * Only a functional interface is provided: ZipFileReader::read(). No access is
+ * given to object instances.
+ *
+ */
+class ZipDirectoryReader {
+       /**
+        * Read a ZIP file and call a function for each file discovered in it.
+        *
+        * Because this class is aimed at verification, an error is raised on
+        * suspicious or ambiguous input, instead of emulating some standard
+        * behavior.
+        *
+        * @param string $fileName The archive file name
+        * @param array $callback The callback function. It will be called for each file
+        *   with a single associative array each time, with members:
+        *
+        *      - name: The file name. Directories conventionally have a trailing
+        *        slash.
+        *
+        *      - mtime: The file modification time, in MediaWiki 14-char format
+        *
+        *      - size: The uncompressed file size
+        *
+        * @param array $options An associative array of read options, with the option
+        *    name in the key. This may currently contain:
+        *
+        *      - zip64: If this is set to true, then we will emulate a
+        *        library with ZIP64 support, like OpenJDK 7. If it is set to
+        *        false, then we will emulate a library with no knowledge of
+        *        ZIP64.
+        *
+        *        NOTE: The ZIP64 code is untested and probably doesn't work. It
+        *        turned out to be easier to just reject ZIP64 archive uploads,
+        *        since they are likely to be very rare. Confirming safety of a
+        *        ZIP64 file is fairly complex. What do you do with a file that is
+        *        ambiguous and broken when read with a non-ZIP64 reader, but valid
+        *        when read with a ZIP64 reader? This situation is normal for a
+        *        valid ZIP64 file, and working out what non-ZIP64 readers will make
+        *        of such a file is not trivial.
+        *
+        * @return Status object. The following fatal errors are defined:
+        *
+        *      - zip-file-open-error: The file could not be opened.
+        *
+        *      - zip-wrong-format: The file does not appear to be a ZIP file.
+        *
+        *      - zip-bad: There was something wrong or ambiguous about the file
+        *        data.
+        *
+        *      - zip-unsupported: The ZIP file uses features which
+        *        ZipDirectoryReader does not support.
+        *
+        * The default messages for those fatal errors are written in a way that
+        * makes sense for upload verification.
+        *
+        * If a fatal error is returned, more information about the error will be
+        * available in the debug log.
+        *
+        * Note that the callback function may be called any number of times before
+        * a fatal error is returned. If this occurs, the data sent to the callback
+        * function should be discarded.
+        */
+       public static function read( $fileName, $callback, $options = array() ) {
+               $zdr = new self( $fileName, $callback, $options );
+               return $zdr->execute();
+       }
+
+       /** The file name */
+       var $fileName;
+
+       /** The opened file resource */
+       var $file;
+
+       /** The cached length of the file, or null if it has not been loaded yet. */
+       var $fileLength;
+
+       /** A segmented cache of the file contents */
+       var $buffer;
+
+       /** The file data callback */
+       var $callback;
+
+       /** The ZIP64 mode */
+       var $zip64 = false;
+
+       /** Stored headers */
+       var $eocdr, $eocdr64, $eocdr64Locator;
+
+       var $data;
+
+       /** The "extra field" ID for ZIP64 central directory entries */
+       const ZIP64_EXTRA_HEADER = 0x0001;
+
+       /** The segment size for the file contents cache */
+       const SEGSIZE = 16384;
+
+       /** The index of the "general field" bit for UTF-8 file names */
+       const GENERAL_UTF8 = 11;
+
+       /** The index of the "general field" bit for central directory encryption */
+       const GENERAL_CD_ENCRYPTED = 13;
+
+       /**
+        * Private constructor
+        */
+       protected function __construct( $fileName, $callback, $options ) {
+               $this->fileName = $fileName;
+               $this->callback = $callback;
+
+               if ( isset( $options['zip64'] ) ) {
+                       $this->zip64 = $options['zip64'];
+               }
+       }
+
+       /**
+        * Read the directory according to settings in $this.
+        *
+        * @return Status
+        */
+       function execute() {
+               $this->file = fopen( $this->fileName, 'r' );
+               $this->data = array();
+               if ( !$this->file ) {
+                       return Status::newFatal( 'zip-file-open-error' );
+               }
+
+               $status = Status::newGood();
+               try {
+                       $this->readEndOfCentralDirectoryRecord();
+                       if ( $this->zip64 ) {
+                               list( $offset, $size ) = $this->findZip64CentralDirectory();
+                               $this->readCentralDirectory( $offset, $size );
+                       } else {
+                               if ( $this->eocdr['CD size'] == 0xffffffff
+                                       || $this->eocdr['CD offset'] == 0xffffffff
+                                       || $this->eocdr['CD entries total'] == 0xffff )
+                               {
+                                       $this->error( 'zip-unsupported', 'Central directory header indicates ZIP64, ' .
+                                               'but we are in legacy mode. Rejecting this upload is necessary to avoid ' .
+                                               'opening vulnerabilities on clients using OpenJDK 7 or later.' );
+                               }
+
+                               list( $offset, $size ) = $this->findOldCentralDirectory();
+                               $this->readCentralDirectory( $offset, $size );
+                       }
+               } catch ( ZipDirectoryReaderError $e ) {
+                       $status->fatal( $e->getErrorCode() );
+               }
+
+               fclose( $this->file );
+               return $status;
+       }
+
+       /**
+        * Throw an error, and log a debug message
+        */
+       function error( $code, $debugMessage ) {
+               wfDebug( __CLASS__ . ": Fatal error: $debugMessage\n" );
+               throw new ZipDirectoryReaderError( $code );
+       }
+
+       /**
+        * Read the header which is at the end of the central directory,
+        * unimaginatively called the "end of central directory record" by the ZIP
+        * spec.
+        */
+       function readEndOfCentralDirectoryRecord() {
+               $info = array(
+                       'signature' => 4,
+                       'disk' => 2,
+                       'CD start disk' => 2,
+                       'CD entries this disk' => 2,
+                       'CD entries total' => 2,
+                       'CD size' => 4,
+                       'CD offset' => 4,
+                       'file comment length' => 2,
+               );
+               $structSize = $this->getStructSize( $info );
+               $startPos = $this->getFileLength() - 65536 - $structSize;
+               if ( $startPos < 0 ) {
+                       $startPos = 0;
+               }
+
+               $block = $this->getBlock( $startPos );
+               $sigPos = strrpos( $block, "PK\x05\x06" );
+               if ( $sigPos === false ) {
+                       $this->error( 'zip-wrong-format',
+                               "zip file lacks EOCDR signature. It probably isn't a zip file." );
+               }
+
+               $this->eocdr = $this->unpack( substr( $block, $sigPos ), $info );
+               $this->eocdr['EOCDR size'] = $structSize + $this->eocdr['file comment length'];
+
+               if ( $structSize + $this->eocdr['file comment length'] != strlen( $block ) - $sigPos ) {
+                       $this->error( 'zip-bad', 'trailing bytes after the end of the file comment' );
+               }
+               if ( $this->eocdr['disk'] !== 0
+                       || $this->eocdr['CD start disk'] !== 0 )
+               {
+                       $this->error( 'zip-unsupported', 'more than one disk (in EOCDR)' );
+               }
+               $this->eocdr += $this->unpack(
+                       $block,
+                       array( 'file comment' => array( 'string', $this->eocdr['file comment length'] ) ),
+                       $sigPos + $structSize );
+               $this->eocdr['position'] = $startPos + $sigPos;
+       }
+
+       /**
+        * Read the header called the "ZIP64 end of central directory locator". An
+        * error will be raised if it does not exist.
+        */
+       function readZip64EndOfCentralDirectoryLocator() {
+               $info = array(
+                       'signature' => array( 'string', 4 ),
+                       'eocdr64 start disk' => 4,
+                       'eocdr64 offset' => 8,
+                       'number of disks' => 4,
+               );
+               $structSize = $this->getStructSize( $info );
+
+               $block = $this->getBlock( $this->getFileLength() - $this->eocdr['EOCDR size']
+                       - $structSize, $structSize );
+               $this->eocdr64Locator = $data = $this->unpack( $block, $info );
+
+               if ( $data['signature'] !== "PK\x06\x07" ) {
+                       // Note: Java will allow this and continue to read the
+                       // EOCDR64, so we have to reject the upload, we can't
+                       // just use the EOCDR header instead.
+                       $this->error( 'zip-bad', 'wrong signature on Zip64 end of central directory locator' );
+               }
+       }
+
+       /**
+        * Read the header called the "ZIP64 end of central directory record". It
+        * may replace the regular "end of central directory record" in ZIP64 files.
+        */
+       function readZip64EndOfCentralDirectoryRecord() {
+               if ( $this->eocdr64Locator['eocdr64 start disk'] != 0
+                       || $this->eocdr64Locator['number of disks'] != 0 )
+               {
+                       $this->error( 'zip-unsupported', 'more than one disk (in EOCDR64 locator)' );
+               }
+
+               $info = array(
+                       'signature' => array( 'string', 4 ),
+                       'EOCDR64 size' => 8,
+                       'version made by' => 2,
+                       'version needed' => 2,
+                       'disk' => 4,
+                       'CD start disk' => 4,
+                       'CD entries this disk' => 8,
+                       'CD entries total' => 8,
+                       'CD size' => 8,
+                       'CD offset' => 8
+               );
+               $structSize = $this->getStructSize( $info );
+               $block = $this->getBlock( $this->eocdr64Locator['eocdr64 offset'], $structSize );
+               $this->eocdr64 = $data = $this->unpack( $block, $info );
+               if ( $data['signature'] !== "PK\x06\x06" ) {
+                       $this->error( 'zip-bad', 'wrong signature on Zip64 end of central directory record' );
+               }
+               if ( $data['disk'] !== 0
+                       || $data['CD start disk'] !== 0 )
+               {
+                       $this->error( 'zip-unsupported', 'more than one disk (in EOCDR64)' );
+               }
+       }
+
+       /**
+        * Find the location of the central directory, as would be seen by a
+        * non-ZIP64 reader.
+        *
+        * @return List containing offset, size and end position.
+        */
+       function findOldCentralDirectory() {
+               $size = $this->eocdr['CD size'];
+               $offset = $this->eocdr['CD offset'];
+               $endPos = $this->eocdr['position'];
+
+               // Some readers use the EOCDR position instead of the offset field
+               // to find the directory, so to be safe, we check if they both agree.
+               if ( $offset + $size != $endPos ) {
+                       $this->error( 'zip-bad', 'the central directory does not immediately precede the end ' .
+                               'of central directory record' );
+               }
+               return array( $offset, $size );
+       }
+
+       /**
+        * Find the location of the central directory, as would be seen by a
+        * ZIP64-compliant reader.
+        *
+        * @return array List containing offset, size and end position.
+        */
+       function findZip64CentralDirectory() {
+               // The spec is ambiguous about the exact rules of precedence between the
+               // ZIP64 headers and the original headers. Here we follow zip_util.c
+               // from OpenJDK 7.
+               $size = $this->eocdr['CD size'];
+               $offset = $this->eocdr['CD offset'];
+               $numEntries = $this->eocdr['CD entries total'];
+               $endPos = $this->eocdr['position'];
+               if ( $size == 0xffffffff
+                       || $offset == 0xffffffff
+                       || $numEntries == 0xffff )
+               {
+                       $this->readZip64EndOfCentralDirectoryLocator();
+
+                       if ( isset( $this->eocdr64Locator['eocdr64 offset'] ) ) {
+                               $this->readZip64EndOfCentralDirectoryRecord();
+                               if ( isset( $this->eocdr64['CD offset'] ) ) {
+                                       $size = $this->eocdr64['CD size'];
+                                       $offset = $this->eocdr64['CD offset'];
+                                       $endPos = $this->eocdr64Locator['eocdr64 offset'];
+                               }
+                       }
+               }
+               // Some readers use the EOCDR position instead of the offset field
+               // to find the directory, so to be safe, we check if they both agree.
+               if ( $offset + $size != $endPos ) {
+                       $this->error( 'zip-bad', 'the central directory does not immediately precede the end ' .
+                               'of central directory record' );
+               }
+               return array( $offset, $size );
+       }
+
+       /**
+        * Read the central directory at the given location
+        */
+       function readCentralDirectory( $offset, $size ) {
+               $block = $this->getBlock( $offset, $size );
+
+               $fixedInfo = array(
+                       'signature' => array( 'string', 4 ),
+                       'version made by' => 2,
+                       'version needed' => 2,
+                       'general bits' => 2,
+                       'compression method' => 2,
+                       'mod time' => 2,
+                       'mod date' => 2,
+                       'crc-32' => 4,
+                       'compressed size' => 4,
+                       'uncompressed size' => 4,
+                       'name length' => 2,
+                       'extra field length' => 2,
+                       'comment length' => 2,
+                       'disk number start' => 2,
+                       'internal attrs' => 2,
+                       'external attrs' => 4,
+                       'local header offset' => 4,
+               );
+               $fixedSize = $this->getStructSize( $fixedInfo );
+
+               $pos = 0;
+               while ( $pos < $size ) {
+                       $data = $this->unpack( $block, $fixedInfo, $pos );
+                       $pos += $fixedSize;
+
+                       if ( $data['signature'] !== "PK\x01\x02" ) {
+                               $this->error( 'zip-bad', 'Invalid signature found in directory entry' );
+                       }
+
+                       $variableInfo = array(
+                               'name' => array( 'string', $data['name length'] ),
+                               'extra field' => array( 'string', $data['extra field length'] ),
+                               'comment' => array( 'string', $data['comment length'] ),
+                       );
+                       $data += $this->unpack( $block, $variableInfo, $pos );
+                       $pos += $this->getStructSize( $variableInfo );
+
+                       if ( $this->zip64 && (
+                                  $data['compressed size'] == 0xffffffff
+                               || $data['uncompressed size'] == 0xffffffff
+                               || $data['local header offset'] == 0xffffffff ) )
+                       {
+                               $zip64Data = $this->unpackZip64Extra( $data['extra field'] );
+                               if ( $zip64Data ) {
+                                       $data = $zip64Data + $data;
+                               }
+                       }
+
+                       if ( $this->testBit( $data['general bits'], self::GENERAL_CD_ENCRYPTED ) ) {
+                               $this->error( 'zip-unsupported', 'central directory encryption is not supported' );
+                       }
+
+                       // Convert the timestamp into MediaWiki format
+                       // For the format, please see the MS-DOS 2.0 Programmer's Reference,
+                       // pages 3-5 and 3-6.
+                       $time = $data['mod time'];
+                       $date = $data['mod date'];
+
+                       $year = 1980 + ( $date >> 9 );
+                       $month = ( $date >> 5 ) & 15;
+                       $day = $date & 31;
+                       $hour = ( $time >> 11 ) & 31;
+                       $minute = ( $time >> 5 ) & 63;
+                       $second = ( $time & 31 ) * 2;
+                       $timestamp = sprintf( "%04d%02d%02d%02d%02d%02d",
+                               $year, $month, $day, $hour, $minute, $second );
+
+                       // Convert the character set in the file name
+                       if ( !function_exists( 'iconv' )
+                               || $this->testBit( $data['general bits'], self::GENERAL_UTF8 ) )
+                       {
+                               $name = $data['name'];
+                       } else {
+                               $name = iconv( 'CP437', 'UTF-8', $data['name'] );
+                       }
+
+                       // Compile a data array for the user, with a sensible format
+                       $userData = array(
+                               'name' => $name,
+                               'mtime' => $timestamp,
+                               'size' => $data['uncompressed size'],
+                       );
+                       call_user_func( $this->callback, $userData );
+               }
+       }
+
+       /**
+        * Interpret ZIP64 "extra field" data and return an associative array.
+        * @return array|bool
+        */
+       function unpackZip64Extra( $extraField ) {
+               $extraHeaderInfo = array(
+                       'id' => 2,
+                       'size' => 2,
+               );
+               $extraHeaderSize = $this->getStructSize( $extraHeaderInfo );
+
+               $zip64ExtraInfo = array(
+                       'uncompressed size' => 8,
+                       'compressed size' => 8,
+                       'local header offset' => 8,
+                       'disk number start' => 4,
+               );
+
+               $extraPos = 0;
+               while ( $extraPos < strlen( $extraField ) ) {
+                       $extra = $this->unpack( $extraField, $extraHeaderInfo, $extraPos );
+                       $extraPos += $extraHeaderSize;
+                       $extra += $this->unpack( $extraField,
+                               array( 'data' => array( 'string', $extra['size'] ) ),
+                               $extraPos );
+                       $extraPos += $extra['size'];
+
+                       if ( $extra['id'] == self::ZIP64_EXTRA_HEADER ) {
+                               return $this->unpack( $extra['data'], $zip64ExtraInfo );
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * Get the length of the file.
+        */
+       function getFileLength() {
+               if ( $this->fileLength === null ) {
+                       $stat = fstat( $this->file );
+                       $this->fileLength = $stat['size'];
+               }
+               return $this->fileLength;
+       }
+
+       /**
+        * Get the file contents from a given offset. If there are not enough bytes
+        * in the file to satisfy the request, an exception will be thrown.
+        *
+        * @param int $start The byte offset of the start of the block.
+        * @param int $length The number of bytes to return. If omitted, the remainder
+        *    of the file will be returned.
+        *
+        * @return string
+        */
+       function getBlock( $start, $length = null ) {
+               $fileLength = $this->getFileLength();
+               if ( $start >= $fileLength ) {
+                       $this->error( 'zip-bad', "getBlock() requested position $start, " .
+                               "file length is $fileLength" );
+               }
+               if ( $length === null ) {
+                       $length = $fileLength - $start;
+               }
+               $end = $start + $length;
+               if ( $end > $fileLength ) {
+                       $this->error( 'zip-bad', "getBlock() requested end position $end, " .
+                               "file length is $fileLength" );
+               }
+               $startSeg = floor( $start / self::SEGSIZE );
+               $endSeg = ceil( $end / self::SEGSIZE );
+
+               $block = '';
+               for ( $segIndex = $startSeg; $segIndex <= $endSeg; $segIndex++ ) {
+                       $block .= $this->getSegment( $segIndex );
+               }
+
+               $block = substr( $block,
+                       $start - $startSeg * self::SEGSIZE,
+                       $length );
+
+               if ( strlen( $block ) < $length ) {
+                       $this->error( 'zip-bad', 'getBlock() returned an unexpectedly small amount of data' );
+               }
+
+               return $block;
+       }
+
+       /**
+        * Get a section of the file starting at position $segIndex * self::SEGSIZE,
+        * of length self::SEGSIZE. The result is cached. This is a helper function
+        * for getBlock().
+        *
+        * If there are not enough bytes in the file to satisfy the request, the
+        * return value will be truncated. If a request is made for a segment beyond
+        * the end of the file, an empty string will be returned.
+        * @return string
+        */
+       function getSegment( $segIndex ) {
+               if ( !isset( $this->buffer[$segIndex] ) ) {
+                       $bytePos = $segIndex * self::SEGSIZE;
+                       if ( $bytePos >= $this->getFileLength() ) {
+                               $this->buffer[$segIndex] = '';
+                               return '';
+                       }
+                       if ( fseek( $this->file, $bytePos ) ) {
+                               $this->error( 'zip-bad', "seek to $bytePos failed" );
+                       }
+                       $seg = fread( $this->file, self::SEGSIZE );
+                       if ( $seg === false ) {
+                               $this->error( 'zip-bad', "read from $bytePos failed" );
+                       }
+                       $this->buffer[$segIndex] = $seg;
+               }
+               return $this->buffer[$segIndex];
+       }
+
+       /**
+        * Get the size of a structure in bytes. See unpack() for the format of $struct.
+        * @return int
+        */
+       function getStructSize( $struct ) {
+               $size = 0;
+               foreach ( $struct as $type ) {
+                       if ( is_array( $type ) ) {
+                               list( , $fieldSize ) = $type;
+                               $size += $fieldSize;
+                       } else {
+                               $size += $type;
+                       }
+               }
+               return $size;
+       }
+
+       /**
+        * Unpack a binary structure. This is like the built-in unpack() function
+        * except nicer.
+        *
+        * @param string $string The binary data input
+        *
+        * @param array $struct An associative array giving structure members and their
+        *    types. In the key is the field name. The value may be either an
+        *    integer, in which case the field is a little-endian unsigned integer
+        *    encoded in the given number of bytes, or an array, in which case the
+        *    first element of the array is the type name, and the subsequent
+        *    elements are type-dependent parameters. Only one such type is defined:
+        *       - "string": The second array element gives the length of string.
+        *          Not null terminated.
+        *
+        * @param int $offset The offset into the string at which to start unpacking.
+        *
+        * @throws MWException
+        * @return array Unpacked associative array. Note that large integers in the input
+        *    may be represented as floating point numbers in the return value, so
+        *    the use of weak comparison is advised.
+        */
+       function unpack( $string, $struct, $offset = 0 ) {
+               $size = $this->getStructSize( $struct );
+               if ( $offset + $size > strlen( $string ) ) {
+                       $this->error( 'zip-bad', 'unpack() would run past the end of the supplied string' );
+               }
+
+               $data = array();
+               $pos = $offset;
+               foreach ( $struct as $key => $type ) {
+                       if ( is_array( $type ) ) {
+                               list( $typeName, $fieldSize ) = $type;
+                               switch ( $typeName ) {
+                               case 'string':
+                                       $data[$key] = substr( $string, $pos, $fieldSize );
+                                       $pos += $fieldSize;
+                                       break;
+                               default:
+                                       throw new MWException( __METHOD__ . ": invalid type \"$typeName\"" );
+                               }
+                       } else {
+                               // Unsigned little-endian integer
+                               $length = intval( $type );
+
+                               // Calculate the value. Use an algorithm which automatically
+                               // upgrades the value to floating point if necessary.
+                               $value = 0;
+                               for ( $i = $length - 1; $i >= 0; $i-- ) {
+                                       $value *= 256;
+                                       $value += ord( $string[$pos + $i] );
+                               }
+
+                               // Throw an exception if there was loss of precision
+                               if ( $value > pow( 2, 52 ) ) {
+                                       $this->error( 'zip-unsupported', 'number too large to be stored in a double. ' .
+                                               'This could happen if we tried to unpack a 64-bit structure ' .
+                                               'at an invalid location.' );
+                               }
+                               $data[$key] = $value;
+                               $pos += $length;
+                       }
+               }
+
+               return $data;
+       }
+
+       /**
+        * Returns a bit from a given position in an integer value, converted to
+        * boolean.
+        *
+        * @param $value integer
+        * @param int $bitIndex The index of the bit, where 0 is the LSB.
+        * @return bool
+        */
+       function testBit( $value, $bitIndex ) {
+               return (bool)( ( $value >> $bitIndex ) & 1 );
+       }
+
+       /**
+        * Debugging helper function which dumps a string in hexdump -C format.
+        */
+       function hexDump( $s ) {
+               $n = strlen( $s );
+               for ( $i = 0; $i < $n; $i += 16 ) {
+                       printf( "%08X ", $i );
+                       for ( $j = 0; $j < 16; $j++ ) {
+                               print " ";
+                               if ( $j == 8 ) {
+                                       print " ";
+                               }
+                               if ( $i + $j >= $n ) {
+                                       print "  ";
+                               } else {
+                                       printf( "%02X", ord( $s[$i + $j] ) );
+                               }
+                       }
+
+                       print "  |";
+                       for ( $j = 0; $j < 16; $j++ ) {
+                               if ( $i + $j >= $n ) {
+                                       print " ";
+                               } elseif ( ctype_print( $s[$i + $j] ) ) {
+                                       print $s[$i + $j];
+                               } else {
+                                       print '.';
+                               }
+                       }
+                       print "|\n";
+               }
+       }
+}
+
+/**
+ * Internal exception class. Will be caught by private code.
+ */
+class ZipDirectoryReaderError extends Exception {
+       var $errorCode;
+
+       function __construct( $code ) {
+               $this->errorCode = $code;
+               parent::__construct( "ZipDirectoryReader error: $code" );
+       }
+
+       /**
+        * @return mixed
+        */
+       function getErrorCode() {
+               return $this->errorCode;
+       }
+}
index 527f382..dc87bc8 100644 (file)
@@ -3949,6 +3949,16 @@ class Language {
                return self::$dataCache->getItem( $this->mCode, 'linkTrail' );
        }
 
+       /**
+        * A regular expression character set to match legal word-prefixing
+        * characters which should be merged onto a link of the form foo[[bar]].
+        *
+        * @return string
+        */
+       public function linkPrefixCharset() {
+               return self::$dataCache->getItem( $this->mCode, 'linkPrefixCharset' );
+       }
+
        /**
         * @return Language
         */
index 929c513..3848a0b 100644 (file)
        'crh' => 'qırımtatarca',   # Crimean Tatar (multiple scripts - defaults to Latin)
        'crh-latn' => "qırımtatarca (Latin)\xE2\x80\x8E",       # Crimean Tatar (Latin)
        'crh-cyrl' => "къырымтатарджа (Кирилл)\xE2\x80\x8E",       # Crimean Tatar (Cyrillic)
-       'cs' => 'česky',       # Czech
+       'cs' => 'čeština',    # Czech
        'csb' => 'kaszëbsczi', # Cassubian
        'cu' => 'словѣ́ньскъ / ⰔⰎⰑⰂⰡⰐⰠⰔⰍⰟ',      # Old Church Slavonic (ancient language)
        'cv' => 'Чӑвашла',       # Chuvash
index c087891..0d2ec3d 100644 (file)
@@ -282,7 +282,7 @@ $messages = array(
 'category_header' => 'Teunuléh lam kawan "$1"',
 'subcategories' => 'Aneuk kawan',
 'category-media-header' => 'Peukakaih lam kawan "$1"',
-'category-empty' => "''Kawan nyoë jinoë hat hana teunuléh atawa media.''",
+'category-empty' => "''Kawan nyoë jinoë hat hana halaman atawa media.''",
 'hidden-categories' => '{{PLURAL:$1|Kawan teusom|Kawan teusom}}',
 'hidden-category-category' => 'Kawan teusom',
 'category-subcat-count' => '{{PLURAL:$2|Kawan nyoë  cit na saboh yupkawan nyoë.|Kawan nyoë na {{PLURAL:$1|yupkawan|$1 yupkawan}} nyoë, dari ban dum $2.}}',
@@ -348,7 +348,7 @@ $messages = array(
 'history_short' => 'Riwayat',
 'updatedmarker' => 'geuubah yoh seunaweue keuneulheueh lon phon kon',
 'printableversion' => 'Seunalén rakam',
-'permalink' => 'Hubông teutap',
+'permalink' => 'Seuneumat teutap',
 'print' => 'Rakam',
 'view' => 'Beuet',
 'edit' => 'Andam',
@@ -374,7 +374,7 @@ $messages = array(
 'articlepage' => 'Eu ôn asoë',
 'talk' => 'Marit',
 'views' => 'Ôn',
-'toolbox' => 'Plôk alat',
+'toolbox' => 'Alat',
 'userpage' => 'Eu on ureueng nguy',
 'projectpage' => 'Eu ôn buët',
 'imagepage' => 'Eu on beureukaih',
@@ -389,7 +389,7 @@ $messages = array(
 'lastmodifiedat' => 'Ôn nyoë seuneulheuëh geuubah bak $2, $1.',
 'viewcount' => 'On nyoe ka geusaweue {{PLURAL:$1|sigo|$sigo}}.<br />',
 'protectedpage' => 'Ôn teupeulindông',
-'jumpto' => 'Lansông u:',
+'jumpto' => 'Grôp u:',
 'jumptonavigation' => 'navigasi',
 'jumptosearch' => 'mita',
 'view-pool-error' => "Meu'ah, server teungoh sibuk jinoe
@@ -639,7 +639,7 @@ Droëneuh jeuët [[Special:Search/{{PAGENAME}}|neumita keu nan ôn nyoë]] bak 
 atawa <span class="plainlinks">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} neumita log nyang na meuhubông]</span>, tapi Droëneuh hana idin keu neupeugöt ôn nyoë',
 'updated' => '(Seubarô)',
 'note' => "'''Ceunatat:'''",
-'previewnote' => "'''Beu neuingat meunyo ôn nyoë goh lom neukeubah!'''",
+'previewnote' => "'''Beu neuingat meunyo laman nyoë goh lom neukeubah!'''",
 'editing' => 'Andam $1',
 'editingsection' => 'Andam $1 (bideuëng)',
 'copyrightwarning' => "Beu neuingat bahwa ban mandum nyang Droëneuh   tuléh keu {{SITENAME}} geukira geupeuteubiët di yup $2 (ngiëng $1 keu leubèh jeulah). Meunyoë Droëneuh h‘an neutém teunuléh Droëneuh  ji’andam ngön jiba ho ho la’én, bèk neupasoë teunuléh Droëneuh  keunoë.<br />Droëneuh  neumeujanji chit meunyoë teunuléh nyoë nakeuh atra neutuléh keudroë, atawa neucok nibak nè nè atra umôm atawa nè bibeuëh la’én.
@@ -775,22 +775,22 @@ Surat-e droeneuh h'an geupeugah keu ureueng nyan.",
 'rightslog' => 'Log neuubah hak peuhah',
 
 # Associated actions - in the sentence "You do not have permission to X"
-'action-edit' => 'andam ôn nyoë',
+'action-edit' => 'andam laman nyoë',
 
 # Recent changes
-'nchanges' => '$1 {{PLURAL:$1|neu’ubah|neu’ubah}}',
+'nchanges' => '$1 {{PLURAL:$1|neuubah}}',
 'recentchanges' => 'Neuubah barô',
 'recentchanges-legend' => 'Peuniléh neuubah barô',
 'recentchanges-summary' => "Di yup nyoë nakeuh neuubah barô nyang na bak Wikipèdia nyoë.
 Ceunatat: (bida) = neuubah, (riwayat) = riwayat teumuléh, '''B''' = ôn barô, '''u''' = neuandam ubeut, '''b''' = neuandam bot, (± ''bit'') = jumeulah asoë meutamah/meukureuëng, → = neuandam beunagi, ← = mohtasa otomatis.
 ----",
-'recentchanges-feed-description' => 'Peuteumèë neu’ubah barô lam wiki bak umpeuën nyoë.',
+'recentchanges-feed-description' => 'Seutot neuubah barô lam wiki bak umpeuën nyoë.',
 'recentchanges-label-newpage' => 'Neuandam nyoe jipeugot on baro',
 'recentchanges-label-minor' => 'Nyoe neuandam ubeut',
 'recentchanges-label-bot' => 'Neuandam nyoe geupubuet le bot',
 'recentchanges-label-unpatrolled' => 'Neuandam nyoe goh lom geukalon',
 'rcnote' => "Di yup nyoë nakeuh {{PLURAL:$1|nakeuh '''1''' neu’ubah barô |nakeuh '''$1''' neu’ubah barô}} lam {{PLURAL:$2|'''1''' uroë|'''$2''' uroë}} nyoë, trôk ‘an $5, $4.",
-'rcnotefrom' => 'Di yup nyoë nakeuh neu’ubah yôh <strong>$2</strong> (geupeuleumah trôh ‘an <strong>$1</strong> neu’ubah).',
+'rcnotefrom' => 'Di yup nyoë nakeuh neuubah yôh <strong>$2</strong> (geupeudeuh trôh ‘an <strong>$1</strong> neuubah).',
 'rclistfrom' => 'Peuleumah neuubah paléng barô yôh $1 kön',
 'rcshowhideminor' => '$1 andam bacut',
 'rcshowhidebots' => '$1 bot',
@@ -958,7 +958,7 @@ Teuneurang bak [$2 on teuneurangjih] geupeuleumah di yup nyoe.",
 'watchthispage' => 'Kalön ôn nyoë',
 'unwatch' => 'Bateuë kalön',
 'watchlist-details' => '{{PLURAL:$1|$1 ôn|$1 ôn}} geukalön, hana kira ôn peugah haba.',
-'wlshowlast' => 'Peuleumah $1 jeum $2 uroë $3 keuneulheuëh',
+'wlshowlast' => 'Peudeuh $1 jeum $2 uroë $3 seuneulheuëh',
 'watchlist-options' => 'Peuniléh dapeuta kalön',
 
 # Displayed when you click the "watch" button and it is in the process of watching
@@ -1026,7 +1026,7 @@ Droëneuh jeuët neugantoë tingkat lindông keu ôn nyoë, tapi nyan hana peung
 'month' => 'Yôh buleuën (ngön yôh goh lom nyan)',
 'year' => 'Yôh thôn (ngön yôh goh lom nyan)',
 
-'sp-contributions-newbies' => 'Keu ureuëng ban dapeuta mantöng',
+'sp-contributions-newbies' => 'Peudeuh beuneuri atra ureuëng ban dapeuta mantöng',
 'sp-contributions-newbies-sub' => 'Keu ureuëng nguy barô',
 'sp-contributions-blocklog' => 'Log peutheun',
 'sp-contributions-uploads' => 'peunasoe',
@@ -1042,7 +1042,7 @@ Droëneuh jeuët neugantoë tingkat lindông keu ôn nyoë, tapi nyan hana peung
 'whatlinkshere-title' => 'Ôn nyang na neuhubông u $1',
 'whatlinkshere-page' => 'Ôn:',
 'linkshere' => "Ôn-ôn nyoë meuhubông u '''[[:$1]]''':",
-'nolinkshere' => "Hana ôn nyang teuhubông u '''[[:$1]]'''.",
+'nolinkshere' => "Hana halaman nyang teukaw'et u '''[[:$1]]'''.",
 'isredirect' => 'ôn peupinah',
 'istemplate' => 'ngön seunaleuëk',
 'isimage' => 'hubông beureukaih',
@@ -1052,7 +1052,7 @@ Droëneuh jeuët neugantoë tingkat lindông keu ôn nyoë, tapi nyan hana peung
 'whatlinkshere-hideredirs' => '$1 peuninah',
 'whatlinkshere-hidetrans' => '$1 transklusi',
 'whatlinkshere-hidelinks' => '$1 hubông',
-'whatlinkshere-hideimages' => '$1 hubông beureukaih',
+'whatlinkshere-hideimages' => '$1 seuneumat beureukaih',
 'whatlinkshere-filters' => 'Saréng',
 
 # Block/unblock
@@ -1135,21 +1135,21 @@ Droëneuh jeuët neu’eu nèjih.',
 'tooltip-p-logo' => 'Saweuë ôn keuë',
 'tooltip-n-mainpage' => 'Jak u ôn keuë',
 'tooltip-n-mainpage-description' => 'Saweuë ôn keuë',
-'tooltip-n-portal' => 'Bhaih buët, peuë nyang jeuët neupeulaku, pat tamita sipeuë hay',
+'tooltip-n-portal' => 'Bhaih buët, peuë nyang jeuët neupubuët, pat keu mita sipeuë hay',
 'tooltip-n-currentevents' => 'Mita haba barô',
 'tooltip-n-recentchanges' => 'Dapeuta neuubah baro lam wiki.',
 'tooltip-n-randompage' => 'Peuleumah ôn beurangkari',
 'tooltip-n-help' => 'Bak mita bantu.',
-'tooltip-t-whatlinkshere' => 'Dapeuta ban dum ôn wiki nyang na neuhubông u ôn nyoë',
+'tooltip-t-whatlinkshere' => 'Dapeuta ban dum ôn wiki nyang meuhubông keunoë',
 'tooltip-t-recentchangeslinked' => 'Neuubah barô ôn nyang na seuneumat u ôn nyoë',
 'tooltip-feed-rss' => 'Umpeuën RSS keu ôn nyoë',
 'tooltip-feed-atom' => 'Umpeuën Atom keu ôn nyoë',
 'tooltip-t-contributions' => 'Eu dapeuta nyang ka geutuléh lé ureuëng nguy nyoë',
-'tooltip-t-emailuser' => "Peu'ét surat-e u ureuëng nguy nyoë",
+'tooltip-t-emailuser' => "Peu'ét surat-e keu ureuëng nguy nyoë",
 'tooltip-t-upload' => 'Peutamong beureukaih',
 'tooltip-t-specialpages' => 'Dapeuta ban dum ôn kusuih',
 'tooltip-t-print' => 'Seunalén rakam ôn nyoë',
-'tooltip-t-permalink' => 'Hubông teutap keu geunantoë ôn nyoë',
+'tooltip-t-permalink' => 'Seuneumat teutap keu geunantoë ôn nyoë',
 'tooltip-ca-nstab-main' => 'Eu ôn asoë',
 'tooltip-ca-nstab-user' => 'Eu ôn ureuëng nguy',
 'tooltip-ca-nstab-special' => 'Nyoë nakeuh ôn kusuih nyang h’an jeuët geu’andam.',
@@ -1219,8 +1219,8 @@ Data nyang la'én eunteuk teupeusom keudroë.
 'monthsall' => 'ban dum',
 
 # Watchlist editing tools
-'watchlisttools-view' => 'Peuleumah neuubah meuhubông',
-'watchlisttools-edit' => 'Peuleumah ngön andam dapeuta kaeunalön',
+'watchlisttools-view' => "Peudeuh neuubah meukaw'èt",
+'watchlisttools-edit' => 'Peudeuh ngön andam dapeuta keunalön',
 'watchlisttools-raw' => 'Andam dapeuta keunalön meuntah',
 
 # Core parser functions
index 4505ae6..6679715 100644 (file)
@@ -23,6 +23,7 @@
  * @author Bassem JARKAS
  * @author Chaos
  * @author Ciphers
+ * @author Claw eg
  * @author DRIHEM
  * @author DrFO.Tn
  * @author Elmondo21st
@@ -624,7 +625,7 @@ $messages = array(
 'mypage' => 'صفحة',
 'mytalk' => 'نقاش',
 'anontalk' => 'نقاش عنوان الآي بي',
-'navigation' => 'إبحار',
+'navigation' => 'تصÙ\81Ø­',
 'and' => '&#32;و',
 
 # Cologne Blue skin
@@ -1005,7 +1006,7 @@ $2',
 من فضلك حاول تسجيل الدخول مرة ثانية بعد استلامها.',
 'blocked-mailpassword' => 'تم منع عنوان الأيبي الخاص بك من التحرير، ولمنع التخريب لا يمكنك أن تستخدم خاصية استرجاع كلمة السر.',
 'eauthentsent' => 'تم إرسال رسالة تأكيد إلكترونية إلى العنوان المسمى.
-حتى ترسل أي رسالة أخرى لذلك الحساب عليك أن تتبع التعليمات الواردة في الرسالة لتأكيد أن هذا الحساب هو لك بالفعل.',
+قبل إرسال أي رسالة أخرى لذلك الحساب، عليك أن تتبع التعليمات الواردة في الرسالة، لتأكيد أن هذا الحساب هو لك بالفعل.',
 'throttled-mailpassword' => 'تم بالفعل إرسال تذكير بكلمة السر، في ال{{PLURAL:$1||ساعة الماضية|ساعتين الماضيتين|$1 ساعات الماضية|$1 ساعة الماضية}}.
 لمنع التخريب، سيتم إرسال تذكير واحد كل {{PLURAL:$1||ساعة|ساعتين|$1 ساعات|$1 ساعة}}.',
 'mailerror' => 'خطأ أثناء إرسال البريد: $1',
index fe0312a..51884d4 100644 (file)
@@ -21,8 +21,8 @@ $messages = array(
 # User preference toggles
 'tog-underline' => 'Miñcewirilpe lasun',
 'tog-justify' => 'Xvrvmpe cijkantvkun',
-'tog-showtoolbar' => 'Pengelün kümeelün ñi chemkün (JavaScript duamyengey)',
-'tog-editondblclick' => 'Wirin pakina epu klik mew (JavaScript)',
+'tog-showtoolbar' => 'Pengelün kümeelün ñi chemkün',
+'tog-editondblclick' => 'Wirin pakina epu klik mew',
 'tog-rememberpassword' => 'Amulen tañi nülküwküleael tüfa mew (alürumechi $1 {{PLURAL:$1 antü}})',
 
 'underline-always' => 'Rumel',
@@ -430,7 +430,7 @@ Rulpakünuy feychi kangelkülelu dungu.",
 'powersearch-field' => 'Kintun',
 
 # Preferences page
-'mypreferences' => 'Tami dullin',
+'mypreferences' => 'Dullin',
 'prefs-edits' => 'Rakin Wirin:',
 'prefsnologin' => 'Mülelay Konün',
 'skin-preview' => 'Pen chum müley',
index 9f1b336..3c31b1f 100644 (file)
@@ -142,8 +142,6 @@ $messages = array(
 'noindex-category' => 'shat mamfhtsach',
 'broken-file-category' => 'ṣfaḫi fiha wṣlat milffaṫ mhrrsa',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'ala',
 'article' => 'sfht mohtawa',
 'newwindow' => '(kayṫḫell fe ċerjem weḫdaĥor)',
index 442d64e..671c716 100644 (file)
@@ -596,7 +596,7 @@ usando la contraseña antigua.",
 Por favor vuelvi a aniciar sesión depués de recibila.',
 'blocked-mailpassword' => 'Ta bloquiada la edición dende la to direición IP, polo que pa evitar abusos nun se pue usar la función de recuperación de contraseña.',
 'eauthentsent' => "Unvióse un corréu electrónicu de confirmación a la direición indicada.
-Enantes de que s'unvie nengún otru corréu a la cuenta, has de siguir les instrucciones del corréu electrónicu pa confirmar que la cuenta ye de to.",
+Enantes de que s'unvie nengún otru corréu a la cuenta, has de siguir les instrucciones d'esi corréu pa confirmar que la cuenta ye daveres de to.",
 'throttled-mailpassword' => "Yá s'unvió un corréu de reaniciu la clave {{PLURAL:$1|na postrer hora|nes postreres $1 hores}}.
 Pa evitar abusos, namái s'unviará un corréu de reaniciu cada {{PLURAL:$1|hora|$1 hores}}.",
 'mailerror' => 'Fallu al unviar el corréu: $1',
@@ -2981,7 +2981,7 @@ Probablemente tea causao por un enllaz a un sitiu esternu de la llista prieta.',
 'spam_reverting' => 'Revirtiendo a la cabera versión que nun contién enllaces a $1',
 'spam_blanking' => 'Toles revisiones teníen enllaces a $1; dexando en blanco',
 'spam_deleting' => 'Toles revisiones teníen enllaces a $1, desaniciando',
-'simpleantispam-label' => "Control anti-spam.
+'simpleantispam-label' => "Comprobación anti-spam.
 ¡'''NUN''' rellenes esto!",
 
 # Info page
index 7b345b3..a39894c 100644 (file)
@@ -206,8 +206,6 @@ $messages = array(
 'noindex-category' => 'İndeksləşdirilməyən səhifələr',
 'broken-file-category' => 'İşləməyən fayl keçidləri olan səhifələr',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'Haqqında',
 'article' => 'Mündəricat',
 'newwindow' => '(yeni pəncərədə açılır)',
index dd61d09..61c065a 100644 (file)
@@ -666,7 +666,7 @@ $2',
 
 Зинһар, серһүҙҙе алғас, системаға яңынан керегеҙ.',
 'blocked-mailpassword' => 'Һеҙҙең IP-адресығыҙҙан мөхәррирләү тыйылған, шул сәбәпле серһүҙ тергеҙеү ғәмәле лә блокланған.',
-'eauthentsent' => 'Күрһәтелгән электрон почта адресына адресты үҙгәртеүҙе раҫлауығыҙ өсөн хат ебәрелде. Хатта, был адрес һеҙҙеке булғанын раҫлау өсөн ниндәй ғәмәлдәрҙе үтәү кәрәкле икәне тураһында мәғлүмәт бар.',
+'eauthentsent' => 'Күрһәтелгән электрон почта адресына адресты үҙгәртеүҙе раҫлауығыҙ өсөн хат ебәрелде. Хатта был адрес һеҙҙеке булғанын раҫлау өсөн ниндәй ғәмәлдәрҙе үтәү кәрәклеге тураһында мәғлүмәт бар.',
 'throttled-mailpassword' => 'Серһүҙҙе иҫләтеү ғәмәле {{PLURAL:$1|һуңғы $1 сәғәт}} эсенде ҡулланылды инде.
 Насар ниәтле ҡулланыуҙарға ҡаршы, Серһүҙ иҫләтеү ғәмәлен {{PLURAL:$1|сәғәт|$1 сәғәт}} эсендә бер тапҡыр ғына ҡулланырға була.',
 'mailerror' => 'Хат ебәреү хатаһы: $1',
@@ -3903,7 +3903,7 @@ MediaWiki файҙалы булыр, тигән өмөттә, ләкин БЕР
 'logentry-delete-event-legacy' => '$1  $3 журналы яҙмаларының күренеүсәнлеген {{GENDER:$2|үҙгәртте}}',
 'logentry-delete-revision-legacy' => '$1  $3 битендә версияларҙың күренеүсәнлеген {{GENDER:$2|үҙгәртте}}',
 'logentry-suppress-delete' => '$1 $3 битен {{GENDER:$2|баҫырылдырҙы}}',
-'logentry-suppress-event' => '$1 журналдағы {{PLURAL:$5|$5 яҙманың}} күренеүсәнлеген $3 битендә йәшерен үҙгәртте: $4',
+'logentry-suppress-event' => '$1 журналдағы {{PLURAL:$5|$5 яҙманың}} күренеүсәнлеген $3 битендә йәшерен {{GENDER:$2|}}үҙгәртте: $4',
 'logentry-suppress-revision' => '$1 {{PLURAL:$5|$5 версияның}} күренеүсәнлеген $3 битендә йәшерен үҙгәртте: $4',
 'logentry-suppress-event-legacy' => '$1  журнал яҙмаларының күренеүсәнлеген йәшерен  {{GENDER:$2|үҙгәртте}}$3',
 'logentry-suppress-revision-legacy' => '$1 $3 битендә версияларҙың күренеүсәнлеген йәшерен {{GENDER:$2|}}',
index 1575605..cfca8e6 100644 (file)
@@ -314,7 +314,7 @@ $messages = array(
 'articlepage' => 'Tanawon an laog kan pahina',
 'talk' => 'Orolayan',
 'views' => 'Mga Tanawon',
-'toolbox' => 'Kagamitang kahon',
+'toolbox' => 'Mga gagamiton:',
 'userpage' => 'Tanawon an pahina kan parágamit',
 'projectpage' => 'Tanawon an pahina kan proyekto',
 'imagepage' => 'Hilngón an pahina nin sagunson (file)',
@@ -344,7 +344,7 @@ $1',
 # All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).
 'aboutsite' => 'Dapít sa {{SITENAME}}',
 'aboutpage' => 'Project:Mapanonongód',
-'copyright' => 'An kalamnan manunumpungan sa laog kan $1.',
+'copyright' => 'An kalamnan manunumpungan sa laog kan $1 o baya notado na ining laen.',
 'copyrightpage' => '{{ns:project}}:Mga Katanosang pansurat',
 'currentevents' => 'Sa ngunyan na mga pangyayari',
 'currentevents-url' => 'Project:Mga pangyayari sa ngunyán',
@@ -558,6 +558,9 @@ Dae malingaw na liwaton an saimong [[Special:Preferences|{{SITENAME}} mga kamuya
 'userlogin-resetpassword-link' => 'Pakibaguha an saimong sekretong panlaog',
 'helplogin-url' => 'Help:Paglalaog',
 'userlogin-helplink' => '[[{{MediaWiki:helplogin-url}}|Tabang sa paglalaog]]',
+'userlogin-loggedin' => 'Ika nakalaog na tabi bilang si {{GENDER:$1|$1}}.
+Gamita an porma sa ibaba sa paglaog bilang ibang paragamit.',
+'userlogin-createanother' => 'Magmukna nin ibang panindog',
 'createacct-join' => 'Pakikaag an saimong impormasyon sa ibaba.',
 'createacct-another-join' => 'Ikaag an impormasyon kan baguhong panindog sa ibaba.',
 'createacct-emailrequired' => 'Estada kan e-surat',
@@ -620,8 +623,8 @@ Kun ibang tawo an naghimo kaining kahagadan, o kun saimo nang nagiromdoman an sa
 'passwordsent' => 'Sarong baguhon na sekretong panlaog an ipinadara sa e-koreong address na nakarehistro para ki "$1".
 Tabi maglaog giraray matapos mong maresibe ini.',
 'blocked-mailpassword' => 'An saimong IP address pinagkubkob na magliwat, asin kaya dae tinutugutan na gumamit kan pambawi nin sekretong panlaog na punksyon tanganing makalikay sa abuso.',
-'eauthentsent' => 'Sarong e-koreong pankumpirmasyon an ipinadara sa nominadong e-koreong address.
-Bago an ibang e-koreo ipinadara sa panindog, ika igwang pagsunudong na mga instruksyon na yaon sa e-koreo, tanganing kumpirmaron na an panindog tunay talagang saimo.',
+'eauthentsent' => 'Sarong pankumpirmasyon na e-surat an ipinadara sa isinambit na estada nin e-surat.
+Bago an ibang e-surat ipinapadara sa panindog, ika igwang susunudon na mga instruksyon na yaon sa e-surat, tanganing kumpirmaron na an panindog tunay talagang saimo.',
 'throttled-mailpassword' => 'Sarong e-surat sa pagliliwat kan sekretong panlaog an ipinadara na, sa laog nin {{PLURAL:$1|hour|$1 hours}}.
 Tangarig malikayan an abuso, saro sanang e-surat sa pagliliwat kan sekretong panlaog an ipinapadara sa lambang {{PLURAL:$1|hour|$1 hours}}.',
 'mailerror' => 'Salâ an pagpadará kan koreo: $1',
@@ -1500,8 +1503,8 @@ An saimong e-surat na adres dae ipagbuyagyag kunsoarin na an ibang paragamit mak
 'action-block' => 'kubkubon ining paragamit gikan sa pagliliwat',
 'action-protect' => 'ribayan an kurit nin proteksyon para sa pahinang ini',
 'action-rollback' => 'hidaling ipagbalik an mga pagliwat kan huring paragamit na pinagliwat an sarong partikular na pahina',
-'action-import' => 'importaron ining pahina gikan sa ibang wiki',
-'action-importupload' => 'importaron ining pahina gikan sa sarong ikinargang sagunson',
+'action-import' => 'importaron an mga pahina gikan sa ibang wiki',
+'action-importupload' => 'importaron an mga pahina gikan sa sarong ikinargang sagunson',
 'action-patrol' => 'markahan an pagliwat kan iba bilang patrolyado',
 'action-autopatrol' => 'Giboha na an saimong pagliwat markado bilang patrolyado',
 'action-unwatchedpages' => 'tanawon an listahan kan mayong bantay na mga pahina',
@@ -2025,6 +2028,7 @@ Ini ngunyan minatukdo-liwat pasiring sa [[$2]].',
 'listusers' => 'Lista nin paragamit',
 'listusers-editsonly' => 'Ipahiling sana an mga paragamit na igwang mga pinagliwat',
 'listusers-creationsort' => 'Salansanon sa paagi kan petsa nin pagmukna',
+'listusers-desc' => 'Salansanon sa paibabang pasurunod',
 'usereditcount' => '$1 {{PLURAL:$1|pigliwat|mga pigliwat}}',
 'usercreated' => '{{GENDER:$3|Minukna}} kan $1 sa $2',
 'newpages' => 'Mga bàguhong pahina',
@@ -2289,10 +2293,12 @@ Hilingón tabì an $2 para mahiling an lista nin mga kaaagi pa sanang pagparà.'
 'deletecomment' => 'Rason:',
 'deleteotherreason' => 'Iba/dugang na rason:',
 'deletereasonotherlist' => 'Ibang rason',
-'deletereason-dropdown' => '*Pirmehang rason nin pagpupura
-** Kahagadan nin Awtor/Parasurat
-** Kalapasan sa Copyright
-** Bandalismo',
+'deletereason-dropdown' => '*Kumon na mga rason nin pagpura
+**Ispam
+**Bandalismo
+**Kalapasan sa Katanosan nin pagsadiri
+**Kahagadan nin Awtor
+**Parasa na panlikwat',
 'delete-edit-reasonlist' => 'Pagliwat kan mga rason nin pagpupura',
 'delete-toobig' => 'Ining pahina igwa nin dakulaong historiya sa pagliwat, minasobrang $1 {{PLURAL:$1|rebisyon|mga rebisyon}}.
 An pagpupura kan nasambit na mga pahina dae pinagtutugot tanganing maiwasan an aksidenteng pagka-antala kan {{SITENAME}}.',
@@ -2313,7 +2319,7 @@ may iba na tabing nagliwat o nagbalik kan pahina.
 An huring pagliwat sa pahina ginibo ni [[User:$3|$3]] ([[User talk:$3|olay]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).',
 'editcomment' => "An sumaryo kan pagliwat: \"''\$1''\".",
 'revertpage' => 'Ibinalik na mga pagliwat ni [[Special:Contributions/$2|$2]] ([[User talk:$2|talk]]) sagkod sa huring rebisyon ni [[User:$1|$1]]',
-'revertpage-nouser' => 'Binalikwat na mga pagliliwat kan sarong nakatagong paragamit sa huring rebisyon ni [[User:$1|$1]]',
+'revertpage-nouser' => 'Binalikwat na mga pagliliwat kan sarong nakatagong paragamit sa huring rebisyon ni {{GENDER:$1|[[User:$1|$1]]}}',
 'rollback-success' => 'Binawî na mga paghirá ni $1; pigbalik sa dating bersyón ni $2.',
 
 # Edit tokens
@@ -2453,7 +2459,7 @@ $1",
 'contributions' => '{{GENDER:$1|Paragamit}} na mga kaambagan',
 'contributions-title' => 'Mga kontribusyon kan paragamit para sa $1',
 'mycontris' => 'Mga Kaarambagan',
-'contribsub2' => 'Para ki $1($2)',
+'contribsub2' => 'Para ki {{GENDER:$3|$1}} ($2)',
 'nocontribs' => 'Mayong mga pagbabago na nahanap na kapadis sa ining mga criteria.',
 'uctop' => '(sa ngunyan)',
 'month' => 'Poon bulan (asin mas amay):',
@@ -2960,8 +2966,8 @@ Ini hurot na pinagkausa nin sarong sugpunan pasiring sa sarong pinagbawal na pan
 'spam_reverting' => 'Mabalik sa huring bersion na mayong takod sa $1',
 'spam_blanking' => 'An gabos na mga pahirá na may takod sa $1, pigblablanko',
 'spam_deleting' => 'An gabos na mga rebisyon na igwang mga kasugpunan sa $1, pinupura',
-'simpleantispam-label' => 'Narikisa kan anti-espam.
-"Dae" ka magkaag nin laman digde!',
+'simpleantispam-label' => "Rikisa nin Kontra-Ispam.
+Giboha na '''DAE''' paglaogan digde!",
 
 # Info page
 'pageinfo-title' => 'Impormasyon para sa "$1"',
@@ -3730,7 +3736,7 @@ Ika dapat na nakapagresibe na kan [{{SERVER}}{{SCRIPTPATH}}/COPYING sarong kopya
 # Special:Redirect
 'redirect' => 'Palikwatong sa paagi nin sagunson, paragamit, or rebisyon kan ID',
 'redirect-legend' => 'Palikwatong pasiring sa sarong sagunson o pahina',
-'redirect-summary' => 'Ining espesyal na pahina minalikwat pasiring sa sarong sagunson (ipinagtao an ngaran kan sagunson), sarong pahina (ipinagtao an sarong rebisyon kan ID), o sarong pahina nin paragamit (ipinagtao an numerikong ID nin paragamit).',
+'redirect-summary' => 'Ining espesyal na pahina minalikwat pasiring sa sarong sagunson (ipinagtao an ngaran kan sagunson), sarong pahina (ipinagtao an sarong rebisyon kan ID), o sarong pahina nin paragamit (ipinagtao an numerikong ID nin paragamit). Pinaggamitan: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], or [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit' => 'Dumani',
 'redirect-lookup' => 'Hanapon mo',
 'redirect-value' => 'Halaga:',
@@ -3792,7 +3798,10 @@ Ika dapat na nakapagresibe na kan [{{SERVER}}{{SCRIPTPATH}}/COPYING sarong kopya
 'tags-tag' => 'Ngarang panmarka',
 'tags-display-header' => 'Kinaluwasan sa listahan nin kaliwatan',
 'tags-description-header' => 'Bilog na deskripsyon nin kahulugan',
+'tags-active-header' => 'Aktibo?',
 'tags-hitcount-header' => 'Pinagmarkahan na mga kaliwatan',
+'tags-active-yes' => 'Iyo',
+'tags-active-no' => 'Dae',
 'tags-edit' => 'liwatón',
 'tags-hitcount' => '$1 {{PLURAL:$1|kaliwatan|mga kaliwatan}}',
 
@@ -3958,9 +3967,9 @@ Kun bako man, ika makakagamit nin sayon na porma sa ibaba. An saimong komento id
 'limitreport-ppvisitednodes' => 'Bilang kan tagapagsumpay sa pangenot na tagapagprosesong pinagbisita',
 'limitreport-ppgeneratednodes' => 'Bilang kan tagapagsumpay sa pangenot na tagapagprosesong pinagpuyos',
 'limitreport-postexpandincludesize' => 'Bigak kan paskil kabali an sukol',
-'limitreport-postexpandincludesize-value' => '$1/$2 mga bayta',
+'limitreport-postexpandincludesize-value' => '$1/$2 {{PLURAL:$2|bayta|mga bayta}}',
 'limitreport-templateargumentsize' => 'Sukol kan argumentong panguyog',
-'limitreport-templateargumentsize-value' => '$1/$2 mga bayta',
+'limitreport-templateargumentsize-value' => '$1/$2{{PLURAL:$2|bayta|mga bayta}}',
 'limitreport-expansiondepth' => 'Kinatugmadan kan pinakahalangkaw na kahiwasan',
 'limitreport-expensivefunctioncount' => 'Bilang kan hiro nin mamahalon na parabangay',
 
index 00fb4de..54097af 100644 (file)
@@ -783,7 +783,7 @@ $2',
 Калі ласка, увайдзіце ў сыстэму пасьля яго атрыманьня.',
 'blocked-mailpassword' => 'З Вашага IP-адрасу забароненыя рэдагаваньні, а таму таксама для прадухіленьня шкоды недаступная функцыя аднаўленьня паролю.',
 'eauthentsent' => 'Пацьверджаньне было дасланае на пазначаны адрас электроннай пошты.
-У лісьце ўтрымліваюцца інструкцыі, па выкананьні якіх, Вы зможаце пацьвердзіць, што адрас сапраўды належыць Вам, і на гэты адрас будзе дасылацца пошта адсюль.',
+У лісьце ўтрымліваюцца інструкцыі, па выкананьні якіх Вы зможаце пацьвердзіць, што адрас сапраўды належыць Вам, і на гэты адрас будзе дасылацца пошта адсюль.',
 'throttled-mailpassword' => 'Ліст пра скіданьне паролю ўжо было даслана за {{PLURAL:$1|$1 апошнюю гадзіну|$1 апошнія гадзіны|$1 апошніх гадзінаў}}.
 Для прадухіленьня злоўжываньняў напамін будзе дасылацца не часьцей як аднойчы ў $1 {{PLURAL:$1|гадзіну|гадзіны|гадзінаў}}.',
 'mailerror' => 'Памылка пры адпраўцы электроннай пошты: $1',
@@ -3907,7 +3907,7 @@ MediaWiki распаўсюджваецца з надзеяй, што будзе
 # Special:Redirect
 'redirect' => 'Перанакіраваньне да файла, удзельніка або вэрсіі старонкі',
 'redirect-legend' => 'Перанакіраваньне да файла або старонкі',
-'redirect-summary' => 'Гэтая спэцыяльная старонка перанакіруе да файла (паводле імя файла), старонкі (паводле нумара вэрсіі) або старонкі ўдзельніка (паводле нумара ўдзельніка).',
+'redirect-summary' => 'Гэтая спэцыяльная старонка перанакіруе да файла (паводле імя файла), старонкі (паводле нумара вэрсіі) або старонкі ўдзельніка (паводле нумара ўдзельніка). Ужываньне: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]] або [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit' => 'Перайсьці',
 'redirect-lookup' => 'Шукаць паводле:',
 'redirect-value' => 'Значэньне:',
index f95bbe6..8046029 100644 (file)
@@ -716,7 +716,7 @@ la vostra antiga contrasenya.",
 'passwordsent' => "S'ha enviat una nova contrasenya a l'adreça electrònica registrada per «$1».
 Inicieu una sessió després que la rebeu.",
 'blocked-mailpassword' => 'La vostra adreça IP ha estat blocada. Se us ha desactivat la funció de recuperació de contrasenya per a prevenir abusos.',
-'eauthentsent' => "S'ha enviat un correu electrònic a la direcció especificada. Abans no s'envïi cap altre correu electrònic a aquesta adreça, cal verificar que és realment vostra. Per tant, cal que seguiu les instruccions presents en el correu electrònic que se us ha enviat.",
+'eauthentsent' => "S'ha enviat un correu electrònic a la direcció especificada. Abans no s'env cap altre correu electrònic a aquesta adreça, cal verificar que és realment vostra. Per tant, cal que seguiu les instruccions presents en el correu electrònic que se us ha enviat.",
 'throttled-mailpassword' => "Ja se us ha enviat un correu electrònic de reinicialització de contrasenya en {{PLURAL:$1|l'última hora|les últimes $1 hores}}.
 Per a prevenir abusos, només s'envia un correu electrònic de reinicialització de contrasenya cada {{PLURAL:$1|hora|$1 hores}}.",
 'mailerror' => "S'ha produït un error en enviar el missatge: $1",
@@ -1155,15 +1155,15 @@ Els altres administradors de {{SITENAME}} encara podran accedir al contingut ama
 * Informació personal inapropiada
 *: ''adreces personals, números de telèfon, números de la seguretat social, etc.''",
 'revdelete-legend' => 'Defineix restriccions en la visibilitat',
-'revdelete-hide-text' => 'Amaga el text de revisió',
+'revdelete-hide-text' => 'Text de la revisió',
 'revdelete-hide-image' => 'Amaga el contingut del fitxer',
 'revdelete-hide-name' => "Acció d'amagar i objectiu",
-'revdelete-hide-comment' => "Amaga el comentari de l'edició",
-'revdelete-hide-user' => "Amaga el nom d'usuari o la IP de l'editor",
+'revdelete-hide-comment' => 'Modifica el resum',
+'revdelete-hide-user' => "Nom d'usuari / adreça IP de l'editor",
 'revdelete-hide-restricted' => 'Suprimir les dades als administradors així com a la resta.',
 'revdelete-radio-same' => '(no modificar)',
-'revdelete-radio-set' => 'Si',
-'revdelete-radio-unset' => 'No',
+'revdelete-radio-set' => 'Visible',
+'revdelete-radio-unset' => 'Oculta',
 'revdelete-suppress' => 'Suprimeix també les dades dels administradors',
 'revdelete-unsuppress' => 'Suprimir les restriccions de les revisions restaurades',
 'revdelete-log' => 'Motiu:',
@@ -2633,12 +2633,12 @@ l'accés a l'escriptura a una adreça IP o un usuari prèviament bloquejat.",
 'blocklist-userblocks' => 'Amaga bloquejos de compte',
 'blocklist-tempblocks' => 'Amaga bloquejos temporals',
 'blocklist-addressblocks' => "Amaga bloquejos d'una sola IP",
-'blocklist-rangeblocks' => 'Amaga els blocatges de rang',
+'blocklist-rangeblocks' => 'Amaga els bloquejos de rang',
 'blocklist-timestamp' => 'Marca horària',
 'blocklist-target' => 'Usuari blocat',
 'blocklist-expiry' => 'Caduca',
-'blocklist-by' => 'Administrador del blocatge',
-'blocklist-params' => 'Paràmetres del blocatge',
+'blocklist-by' => 'Administrador que ha blocat',
+'blocklist-params' => 'Paràmetres del bloqueig',
 'blocklist-reason' => 'Motiu',
 'ipblocklist-submit' => 'Cerca',
 'ipblocklist-localblock' => 'Bloqueig local',
@@ -3036,7 +3036,7 @@ Això deu ser degut per un enllaç a un lloc extern inclòs a la llista negra.',
 'spam_reverting' => 'Es reverteix a la darrera versió que no conté enllaços a $1',
 'spam_blanking' => "Totes les revisions contenien enllaços $1, s'està deixant en blanc",
 'spam_deleting' => "S'estan suprimint totes les revisions que contenien enllaços a $1",
-'simpleantispam-label' => "Comprovació anti-spam.
+'simpleantispam-label' => "Comprovació antispam.
 '''NO''' ho ompliu!",
 
 # Info page
@@ -3810,7 +3810,7 @@ Amb aquest programa heu d'haver rebut [{{SERVER}}{{SCRIPTPATH}}/COPYING una còp
 # Special:Redirect
 'redirect' => 'Redirigeix per fitxer, usuari o ID de la revisió',
 'redirect-legend' => 'Redirigeix a un fitxer o a una pàgina',
-'redirect-summary' => "Aquesta pàgina especial redirigeix a un fitxer (donat el nom del fitxer), una pàgina (donada un ID de la revisió), o a una pàgina d'usuari (donat un ID numèric d'usuari).",
+'redirect-summary' => "Aquesta pàgina especial redirigeix a un fitxer (donat el nom del fitxer), una pàgina (donada un ID de la revisió), o a una pàgina d'usuari (donat un ID numèric d'usuari). Ús: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], or [[{{#Special:Redirect}}/user/101]].",
 'redirect-submit' => 'Vés-hi',
 'redirect-lookup' => 'Consulta:',
 'redirect-value' => 'Valor:',
index 2c1d585..7850b31 100644 (file)
@@ -459,8 +459,6 @@ $messages = array(
 'broken-file-category' => '{{#switch:{{NAMESPACE}}
  |{{ns:0}}=Болх цабеш файлийн хьажорагаш йолу агӀонаш}}',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'Цуьнах лаьцна',
 'article' => 'Яззам',
 'newwindow' => '(керлачу корехь)',
@@ -749,6 +747,9 @@ $1',
 'userlogin-resetpassword-link' => 'Пароль кхоссар',
 'helplogin-url' => 'Help:Системин довзийтар',
 'userlogin-helplink' => '[[{{MediaWiki:helplogin-url}}|Системин чудаха гӀодар]]',
+'userlogin-loggedin' => 'Хьо {{GENDER:$1|$1}} цӀарца чохь ву/ю.
+Лахара форманца кхин цӀарца чугӀо.',
+'userlogin-createanother' => 'Кхолла декъашхочун кхин дӀаяздар',
 'createacct-join' => 'ДӀаязбе лахахь хай хаам.',
 'createacct-emailrequired' => 'Электронни почтан адрес',
 'createacct-emailoptional' => 'Электронни почтан адрес (ца яздича мега)',
@@ -946,7 +947,7 @@ $1',
 'template-semiprotected' => '(дуьззина доцуш гlаролла)',
 'hiddencategories' => 'ХӀара агӀо чуйогӀуш ю оцу $1 {{PLURAL:$1|къайлаха категори чу|къайлаха категореш чу}}:',
 'edittools' => '<!-- Кхузе буха диллина йоза гуш хир ду редоккхуче бухахь а хlума чуйоккхуче бухахь. -->',
-'permissionserrors' => 'ТӀекхачарехь гӀалат',
+'permissionserrors' => 'ТӀекхачаре бакъона гӀалат',
 'permissionserrorstext' => 'Хьан бакъо яц кхочуш хилийта хийцам оцу {{PLURAL:$1|шолгlа бахьанца|шолгlа бахьанашца}}:',
 'permissionserrorstext-withaction' => "Хьан бакъо яц хlумда «'''$2'''» оцу {{PLURAL:$1|шолгlа бахьанца|шолгlа бахьанашца}}:",
 'recreate-moveddeleted-warn' => "'''Тидам бе. Ахьа кхуллуш ю, хьалхо дӀаяккхина йолу агӀо.'''
@@ -1052,6 +1053,7 @@ $1',
 'compareselectedversions' => 'Хаьржина версеш муха ю хьажа',
 'showhideselectedversions' => 'Гайта/къайлайаха хаьржина башхонаш',
 'editundo' => 'цаоьшу',
+'diff-empty' => '(башхалла яц)',
 'diff-multi' => '({{PLURAL:$1|гайтина яц $1 юккъера верси|гайтина яц $1 юккъера версеш}} {{PLURAL:$2|$2 декъашхочун|$2 декъашхой}})',
 
 # Search results
@@ -1538,10 +1540,13 @@ PICT # тайп тайпан
 'pageswithprop-text' => 'Кхузахь гойтуш ю агӀонаш цхьадолу къастамаш куьйга юху билгал даьхнарш.',
 'pageswithprop-prop' => 'Къастаман цӀе:',
 
-'doubleredirects' => 'ШалгIа дIасахьажийнарш',
+'doubleredirects' => 'Шалха дIасахьажийнарш',
+'doubleredirectstext' => 'ХӀокху агӀонехь ю дӀасахьажорашан тӀе хьажийна йолу дӀасахьажораш.
+<del>ТӀехула сиз хаькхна </del>нисйина чарна.',
 'double-redirect-fixed-move' => 'Агlон [[$1]] цlе хийцна, хlинца иза дlахьажийна оцу [[$2]]',
 
 'brokenredirects' => 'ДIахаьдна долу дIасахьажораш',
+'brokenredirectstext' => 'Лахара дӀасахьажийнарш ю йоцучу агӀонийн тӀе хьажийна:',
 'brokenredirects-edit' => 'нисйé',
 'brokenredirects-delete' => 'дӀаяккха',
 
@@ -1561,6 +1566,7 @@ PICT # тайп тайпан
 'ntransclusions' => 'лелош ю $1 {{PLURAL:$1|агӀоначохь|агӀонашкахь|агӀонашкахь}}',
 'specialpage-empty' => 'Дехаро хӀумма ца елла.',
 'lonelypages' => 'Байлахь йисина агIонаш',
+'lonelypagestext' => 'Кхузахь ю {{grammar:genitive|{{SITENAME}}}} кхечу агӀонашкахь тӀе хьажийна хьажорагаш йоцу агӀонаш.',
 'uncategorizedpages' => 'Категореш йоцу агIонаш',
 'uncategorizedcategories' => 'Категореш йоцу категореш',
 'uncategorizedimages' => 'Категореш йоцу файлаш',
@@ -2201,7 +2207,7 @@ PICT # тайп тайпан
 'show-big-image-size' => '$1 × $2 пикселш',
 
 # Special:NewFiles
-'newimages' => 'Ð\9aеÑ\80лаÑ\87Ñ\83 Ñ\84айлийн Ð³Ð°Ð»ÐµÑ\80ий',
+'newimages' => 'Ð\9aеÑ\80лаÑ\87Ñ\83 Ñ\84айлийн Ð³Ð°Ð»ÐµÑ\80ей',
 'newimages-summary' => 'ХӀокху белхан агӀона чохь гойтуш ю дукха хан йоццуш чуйаьхна файлаш.',
 'newimages-legend' => 'Литтар',
 'showhidebots' => '$1 шабелхалой',
index a582b18..c07de24 100644 (file)
@@ -103,6 +103,7 @@ $dateFormats = array(
 
 $separatorTransformTable = array( ','  => '.', '.' => ',' );
 $linkTrail = '/^([a-zâçğıñöşüа-яё“»]+)(.*)$/sDu';
+$linkPrefixCharset = 'a-zâçğıñöşüA-ZÂÇĞİÑÖŞÜa-яёА-ЯЁ«„';
 
 $messages = array(
 # User preference toggles
@@ -230,8 +231,6 @@ $messages = array(
 'noindex-category' => 'Индекссиз саифелер',
 'broken-file-category' => 'Ичинде бозукъ файл багълантылары олгъан саифелер',
 
-'linkprefix' => '/^(.*?)([a-zâçğıñöşüA-ZÂÇĞİÑÖŞÜa-яёА-ЯЁ«„]+)$/sDu',
-
 'about' => 'Акъкъында',
 'article' => 'Саифе',
 'newwindow' => '(янъы бир пенджереде ачылыр)',
index bd6306f..6bb6a4c 100644 (file)
@@ -97,6 +97,7 @@ $dateFormats = array(
 
 $separatorTransformTable = array( ',' => '.', '.' => ',' );
 $linkTrail = '/^([a-zâçğıñöşüа-яё“»]+)(.*)$/sDu';
+$linkPrefixCharset = 'a-zâçğıñöşüA-ZÂÇĞİÑÖŞÜa-яёА-ЯЁ«„';
 
 $messages = array(
 # User preference toggles
@@ -224,8 +225,6 @@ $messages = array(
 'noindex-category' => 'İndekssiz saifeler',
 'broken-file-category' => 'İçinde bozuq fayl bağlantıları olğan saifeler',
 
-'linkprefix' => '/^(.*?)([a-zâçğıñöşüA-ZÂÇĞİÑÖŞÜa-яёА-ЯЁ«„]+)$/sDu',
-
 'about' => 'Aqqında',
 'article' => 'Saife',
 'newwindow' => '(yañı bir pencerede açılır)',
index 06378b9..8d3b367 100644 (file)
@@ -1,5 +1,5 @@
 <?php
-/** Czech (česky)
+/** Czech (čeština)
  *
  * See MessagesQqq.php for message documentation incl. usage of parameters
  * To improve a translation please visit http://translatewiki.net
@@ -4044,7 +4044,7 @@ MediaWiki je distribuována v naději, že bude užitečná, avšak BEZ JAKÉKOL
 # Special:Redirect
 'redirect' => 'Přesměrování podle souboru, uživatele nebo ID revize',
 'redirect-legend' => 'Přesměrování na soubor či stránku',
-'redirect-summary' => 'Tato speciální stránka přesměrovává na soubor (podle názvu), stránku (podle ID revize) nebo uživatele (podle číselného uživatelského ID).',
+'redirect-summary' => 'Tato speciální stránka přesměrovává na soubor (podle názvu), stránku (podle ID revize) nebo uživatele (podle číselného uživatelského ID). Použití: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]] nebo [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit' => 'Přejít',
 'redirect-lookup' => 'Najít:',
 'redirect-value' => 'Hodnota:',
index e9c7e05..706051c 100644 (file)
@@ -97,6 +97,7 @@ $dateFormats = array(
 );
 
 $linkTrail = '/^([a-zабвгдеєжѕзїіıићклмнопсстѹфхѡѿцчшщъыьѣюѥѧѩѫѭѯѱѳѷѵґѓђёјйљњќуўџэ҄я“»]+)(.*)$/sDu';
+$linkPrefixCharset = '„«';
 
 $messages = array(
 # User preference toggles
@@ -180,8 +181,6 @@ $messages = array(
 'category-subcat-count' => '{{PLURAL:$2|Сѥи катигорїи тъкъмо сꙗ подъкатигорїꙗ ѥстъ|Сѥи катигорїи {{PLURAL:$1|ѥдина подъкатигорїꙗ ѥстъ|2 подъкатигорїи ѥстє|$1 подъкатигорїѩ сѫтъ}} · а вьсѩ жє подъкатигорїѩ число $2 ѥстъ}}',
 'listingcontinuesabbrev' => '· вѧщє',
 
-'linkprefix' => '/^(.*?)(„|«)$/sD',
-
 'about' => 'опьсаниѥ',
 'article' => 'члѣнъ',
 'newwindow' => '(иномь окънѣ)',
index ef5f4f1..7a93e72 100644 (file)
@@ -42,6 +42,7 @@ $namespaceGenderAliases = array();
 
 $linkPrefixExtension = true;
 $linkTrail = '/^([a-zа-яĕçăӳ"»]+)(.*)$/sDu';
+$linkPrefixCharset = 'a-zA-Z"\\x{80}-\\x{10ffff}';
 
 $messages = array(
 # User preference toggles
@@ -153,8 +154,6 @@ $messages = array(
 'category-file-count-limited' => 'Ку категоринче $1 файл.',
 'listingcontinuesabbrev' => '(малалли)',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff«"]+)$/sD',
-
 'about' => 'Ăнлантаркăч',
 'article' => 'Статья',
 'newwindow' => '(çĕнĕ чӳречере)',
index 194a62c..5d564fb 100644 (file)
@@ -159,7 +159,7 @@ $messages = array(
 'tog-diffonly' => "Peidio â dangos cynnwys y dudalen islaw'r gymhariaeth ar dudalennau cymharu",
 'tog-showhiddencats' => 'Dangos categorïau cuddiedig',
 'tog-norollbackdiff' => 'Hepgor dangos cymhariaeth ar ôl gwrthdroi golygiad',
-'tog-useeditwarning' => "Tynnwch fy sylw pan wyf ar fin gadael tudalen olygu heb roi'r newidiadau ar gadw",
+'tog-useeditwarning' => "Tynnu fy sylw pan wyf ar fin gadael tudalen olygu heb roi'r newidiadau ar gadw",
 'tog-prefershttps' => 'Defnyddio cysylltiad diogel bob amser tra fy mod wedi mewngofnodi',
 
 'underline-always' => 'Bob amser',
@@ -629,7 +629,7 @@ Os mai rhywun arall a holodd am y cyfrinair, ynteu eich bod wedi cofio\'r hen gy
 'passwordsent' => 'Mae cyfrinair newydd wedi\'i ddanfon at gyfeiriad e-bost cofrestredig "$1". Mewngofnodwch eto ar ôl i chi dderbyn y cyfrinair, os gwelwch yn dda.',
 'blocked-mailpassword' => 'Gan fod eich cyfeiriad IP wedi ei atal rhag golygu, ni ellir adfer y cyfrinair.',
 'eauthentsent' => 'Anfonwyd e-bost o gadarnhâd at y cyfeiriad a benwyd.
-Cyn y gellir anfon unrhywbeth arall at y cyfeiriad hwnnw rhaid i chi ddilyn y cyfarwyddiadau yn yr e-bost hwnnw er mwyn cadarnhau bod y cyfeiriad yn un dilys.',
+Cyn y gellir anfon unrhywbeth arall at y cyfeiriad hwnnw rhaid i chi ddilyn y cyfarwyddiadau yn yr e-bost er mwyn cadarnhau mai chi sydd berchen y cyfeiriad hwnnw.',
 'throttled-mailpassword' => "Anfonwyd e-bost atoch eisoes i'ch atgoffa o'ch cyfrinair, a hynny yn ystod y $1 {{PLURAL:$1|awr}} diwethaf.
 Er mwyn rhwystro camddefnydd, dim ond un e-bost i'ch atgoffa o'ch cyfrinair gaiff ei anfon bob yn $1 {{PLURAL:$1|awr}}.",
 'mailerror' => 'Gwall wrth ddanfon yr e-bost: $1',
@@ -1062,15 +1062,15 @@ Fe fydd gweinyddwyr eraill {{SITENAME}} o hyd yn gallu gweld yr hyn a guddiwyd.
 * Gwybodaeth bersonol anaddas
 *: ''cyfeiriad cartref, rhif ffôn, rhif yswiriant cenedlaethol, ayb.''",
 'revdelete-legend' => 'Gosod cyfyngiadau ar y gallu i weld',
-'revdelete-hide-text' => 'Cuddio testun y diwygiad',
+'revdelete-hide-text' => 'Testun y diwygiad',
 'revdelete-hide-image' => 'Cuddio cynnwys y ffeil',
 'revdelete-hide-name' => "Cuddio'r weithred a'r targed",
-'revdelete-hide-comment' => 'Cuddio sylwad golygu',
-'revdelete-hide-user' => 'Cuddio enw defnyddiwr/IP y golygydd',
+'revdelete-hide-comment' => 'Sylw golygu',
+'revdelete-hide-user' => 'Enw defnyddiwr/IP y golygydd',
 'revdelete-hide-restricted' => 'Gosod y cyfyngiadau gweld data ar weinyddwyr yn ogystal ag eraill',
 'revdelete-radio-same' => '(peidier â newid)',
-'revdelete-radio-set' => 'Cuddier',
-'revdelete-radio-unset' => 'Na chuddier',
+'revdelete-radio-set' => 'Gweladwy',
+'revdelete-radio-unset' => 'Cudd',
 'revdelete-suppress' => 'Atal data oddi wrth Weinyddwyr yn ogystal ag eraill',
 'revdelete-unsuppress' => "Tynnu'r cyfyngiadau ar y golygiadau a adferwyd",
 'revdelete-log' => 'Rheswm:',
@@ -3716,7 +3716,9 @@ Dylech fod wedi derbyn [{{SERVER}}{{SCRIPTPATH}}/COPYING gopi o GNU General Publ
 # Special:Redirect
 'redirect' => 'Ailgyfeirio yn ôl enw ffeil, ID defnyddiwr neu ID diwygiad tudalen',
 'redirect-legend' => 'Ailgyfeirio i ffeil neu dudalen',
-'redirect-summary' => "Mae'r dudalen arbennig hon yn arwain at ffeil (o roi enw'r ffeil), at dudalen (o roi ID rhyw ddidwygiad o'r dudalen), neu at dudalen defnyddiwr (o roi rhif y defnyddiwr).",
+'redirect-summary' => "Mae'r dudalen arbennig hon yn ailgyfeirio at ffeil (o roi enw'r ffeil), at dudalen (o roi ID rhyw ddiwygiad o'r dudalen), neu at dudalen defnyddiwr (o roi rhif ID y defnyddiwr).
+Defnydd: 
+[[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], neu [[{{#Special:Redirect}}/user/101]].",
 'redirect-submit' => 'Ati',
 'redirect-lookup' => 'Chwilio drwy:',
 'redirect-value' => 'Chwilio am:',
index 29caffc..162fee0 100644 (file)
@@ -720,17 +720,18 @@ Før en e-mail kan modtages af andre brugere af {{SITENAME}}-mailfunktionen, ska
 'acct_creation_throttle_hit' => 'Besøgende med samme IP-adresse som dig har oprettet {{PLURAL:$1|en konto|$1 kontoer}} det sidste døgn, og det er ikke tilladt at oprette flere.
 Derfor kan besøgende ikke oprette flere kontoer fra denne IP-adresse i øjeblikket.',
 'emailauthenticated' => 'Din e-mailadresse blev bekræftet $2 $3.',
-'emailnotauthenticated' => 'Din e-mail-adresse er endnu ikke bekræftet og de avancerede e-mail-funktioner er slået fra indtil bekræftelse har fundet sted (d.u.a.). Log ind med den midlertidige adgangskode, der er blevet sendt til dig, for at bekræfte, eller bestil et nyt på loginsiden.',
+'emailnotauthenticated' => 'Din e-mailadresse er endnu ikke bekræftet.
+Ingen e-mail vil blive sendt for de følgende funktioner.',
 'noemailprefs' => 'Angiv en e-mailadresse, så følgende funktioner er til rådighed.',
 'emailconfirmlink' => 'Bekræft din e-mailadresse',
 'invalidemailaddress' => 'E-mailadressen kan ikke accepteres da den tilsyneladende har et ugyldigt format. Skriv venligst en e-mailadresse med et korrekt format eller tøm feltet.',
 'cannotchangeemail' => 'De e-mailadresser, der er tilknyttet brugerkontoer, kan ikke ændres på denne wiki.',
-'emaildisabled' => 'Denne hjemmeside kan ikke sende emails.',
+'emaildisabled' => 'Denne hjemmeside kan ikke sende e-mails.',
 'accountcreated' => 'Brugerkonto oprettet',
 'accountcreatedtext' => 'Brugerkontoen for [[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|diskussion]]) er oprettet.',
 'createaccount-title' => 'Opret brugerkonto på {{SITENAME}}',
-'createaccount-text' => 'Nogen har oprettet en konto for din e-post-adresse på {{SITENAME}} ($4) med navnet "$2". Adgangskoden er "$3".
-Du opfordres til at logge ind og ændre adgangskoden med det samme.
+'createaccount-text' => 'Nogen har oprettet en konto for din e-mailadresse på {{SITENAME}} ($4) med navnet "$2". Adgangskoden er "$3".
+Du opfordres til at logge  og ændre adgangskoden med det samme.
 
 Du kan ignorere denne besked hvis kontoen blev oprettet ved en fejl.',
 'usernamehasherror' => 'Brugernavn må ikke indeholde #',
@@ -738,12 +739,12 @@ Du kan ignorere denne besked hvis kontoen blev oprettet ved en fejl.',
 Vent venligst $1, før du prøver igen.',
 'login-abort-generic' => 'Det lykkedes dig ikke at logge på - afbrudt',
 'loginlanguagelabel' => 'Sprog: $1',
-'suspicious-userlogout' => 'Din anmodning om at logge ud blev nægtet, fordi det ser ud som den blev sendt af en ødelagt browser eller caching proxy.',
+'suspicious-userlogout' => 'Din anmodning om at logge af blev nægtet, fordi det ser ud som den blev sendt af en ødelagt browser eller caching proxy.',
 'createacct-another-realname-tip' => 'Angivelse af rigtigt navn er valgfrit.
 Hvis du vælger at oplyse dit navn, vil det blive brugt til at tilskrive dig dit arbejde.',
 
 # Email sending
-'php-mail-error-unknown' => "Ukendt fejl i PHP's mail()-funtion",
+'php-mail-error-unknown' => 'Ukendt fejl i PHP funktionen mail()',
 'user-mail-no-addy' => 'Forsøgte at sende e-mail uden en e-mailadresse',
 'user-mail-no-body' => 'Forsøgte at sende en e-mail med tomt eller urimeligt kort indhold.',
 
@@ -769,7 +770,7 @@ Du har muligvis allerede skiftet din adgangskode eller anmodet om en ny midlerti
 # Special:PasswordReset
 'passwordreset' => 'Nulstil adgangskode',
 'passwordreset-text-one' => 'Udfyld denne formular for at nulstille din adgangskode.',
-'passwordreset-text-many' => '{{PLURAL:$1|Udfyld en af felterne nedenfor for at nulstille din adgangskode.}}',
+'passwordreset-text-many' => '{{PLURAL:$1|Udfyld et af felterne nedenfor for at nulstille din adgangskode.}}',
 'passwordreset-legend' => 'Nulstil adgangskode',
 'passwordreset-disabled' => 'Nulstilling af adgangskode er slået fra på denne wiki.',
 'passwordreset-emaildisabled' => 'E-mailfunktioner er slået fra på denne wiki.',
@@ -817,7 +818,7 @@ Du bør gøre det, hvis du ved et uheld deler dem med nogen, eller hvis din kont
 'resettokens-no-tokens' => 'Der er ingen nøgler at nulstille.',
 'resettokens-legend' => 'Nulstil nøgler',
 'resettokens-tokens' => 'Nøgler:',
-'resettokens-token-label' => '$1(aktuel værdi: $2)',
+'resettokens-token-label' => '$1 (aktuel værdi: $2)',
 'resettokens-watchlist-token' => 'Nøgle for web-feed (Atom/RSS) af [[Special:Watchlist|ændringer af sider på din overvågningsliste]]',
 'resettokens-done' => 'Nøgler er nulstillet.',
 'resettokens-resetbutton' => 'Nulstil valgte nøgler',
@@ -829,16 +830,16 @@ Du bør gøre det, hvis du ved et uheld deler dem med nogen, eller hvis din kont
 'italic_tip' => 'Kursiv tekst',
 'link_sample' => 'Henvisning',
 'link_tip' => 'Intern henvisning',
-'extlink_sample' => 'http://www.example.com Titel på henvisning',
+'extlink_sample' => 'http://www.example.com titel på henvisning',
 'extlink_tip' => 'Ekstern henvisning (husk http:// præfiks)',
 'headline_sample' => 'Tekst til overskrift',
 'headline_tip' => 'Type 2-overskrift',
 'nowiki_sample' => 'Indsæt tekst her som ikke skal wikiformateres',
 'nowiki_tip' => 'Ignorer wikiformatering',
 'image_sample' => 'Eksempel.jpg',
-'image_tip' => 'Indlejret billede',
+'image_tip' => 'Indlejret fil',
 'media_sample' => 'Eksempel.ogg',
-'media_tip' => 'Henvisning til multimediefil',
+'media_tip' => 'Henvisning til fil',
 'sig_tip' => 'Din signatur med tidsstempel',
 'hr_tip' => 'Horisontal linje (brug den sparsomt)',
 
@@ -852,11 +853,11 @@ Du bør gøre det, hvis du ved et uheld deler dem med nogen, eller hvis din kont
 'showpreview' => 'Forhåndsvisning',
 'showlivepreview' => 'Live-forhåndsvisning',
 'showdiff' => 'Vis ændringer',
-'anoneditwarning' => 'Du arbejder uden at være logget på. Istedet for brugernavn vises din IP-adresse i versionshistorikken.',
-'anonpreviewwarning' => "''Du er ikke logget ind. Hvis du gemmer, registreres din IP-adresse i versionshistorikken.''",
-'missingsummary' => "'''Påmindelse:''' Du har ikke angivet en redigeringsbeskrivelse. Hvis du igen trykker på \"Gem\", gemmes ændringerne uden en beskrivelse.",
-'missingcommenttext' => 'Indtast venligst et resume.',
-'missingcommentheader' => "'''Bemærk:''' Du har ikke angivet en overskrift i feltet „Emne:“ for denne kommentar. Hvis du trykker „Gem side“ én gang til, gemmes dine ændringer uden overskrift.",
+'anoneditwarning' => "'''Advarsel:''' Du er ikke logget på. I stedet for brugernavn vises din IP-adresse i versionshistorikken.",
+'anonpreviewwarning' => "''Du er ikke logget . Hvis du gemmer, registreres din IP-adresse i versionshistorikken.''",
+'missingsummary' => "'''Bemærk:''' Du har ikke angivet en redigeringsbeskrivelse. Hvis du igen trykker på \"{{int:savearticle}}\", gemmes ændringerne uden en beskrivelse.",
+'missingcommenttext' => 'Skriv venligst en kommentar nedenfor.',
+'missingcommentheader' => "'''Bemærk:''' Du har ikke angivet en overskrift/emne for denne kommentar. Hvis du trykker \"{{int:savearticle}}\" én gang til, gemmes dine ændringer uden overskrift/emne.",
 'summary-preview' => 'Forhåndsvisning af beskrivelsen:',
 'subject-preview' => 'Forhåndsvisning af emnet:',
 'blockedtitle' => 'Brugeren er blokeret',
@@ -894,7 +895,7 @@ Angiv venligst alle de ovenstående detaljer ved eventuelle henvendelser.',
 'whitelistedittext' => 'Du skal $1 for at kunne redigere sider.',
 'confirmedittext' => 'Du skal først bekræfte e-mailadressen, før du kan lave ændringer. Udfyld og bekræft din e-mailadresse i dine [[Special:Preferences|indstillinger]].',
 'nosuchsectiontitle' => 'Kan ikke finde afsnittet',
-'nosuchsectiontext' => 'Du forsøgte at ændre et ikke-eksisterende afsnit. Det kan være flyttet eller slettet, siden du hentede siden.',
+'nosuchsectiontext' => 'Du forsøgte at ændre et afsnit der ikke findes. Det kan være flyttet eller slettet, siden du hentede siden.',
 'loginreqtitle' => 'Log på nødvendigt',
 'loginreqlink' => 'logge på',
 'loginreqpagetext' => 'Du skal $1 for at se andre sider.',
@@ -1548,7 +1549,7 @@ Vær venlig at gennemse og bekræft dine ændringer.',
 'right-siteadmin' => 'Låse og frigive databasen',
 'right-override-export-depth' => 'Eksportere sider inkl. henviste sider op til en dybde på 5',
 'right-sendemail' => 'Sende e-mail til andre brugere',
-'right-passwordreset' => 'Se emails til nulstilling af adgangskoder',
+'right-passwordreset' => 'Se e-mails til nulstilling af adgangskoder',
 
 # Special:Log/newusers
 'newuserlogpage' => 'Brugeroprettelseslog',
index 210078a..e373c86 100644 (file)
@@ -1377,15 +1377,15 @@ Andere Administratoren auf {{SITENAME}} haben Zugriff auf den versteckten Inhalt
 * Unangebrachte persönliche Informationen
 *: ''Adressen, Telefonnummern, Sozialversicherungsnummern etc.''",
 'revdelete-legend' => 'Setzen der Sichtbarkeitseinschränkungen',
-'revdelete-hide-text' => 'Text der Version verstecken',
+'revdelete-hide-text' => 'Text der Version',
 'revdelete-hide-image' => 'Dateiinhalt verstecken',
 'revdelete-hide-name' => 'Logbuchaktion und Ziel verstecken',
-'revdelete-hide-comment' => 'Bearbeitungszusammenfassung verstecken',
-'revdelete-hide-user' => 'Benutzername/IP-Adresse des Bearbeiters verstecken',
+'revdelete-hide-comment' => 'Bearbeitungszusammenfassung',
+'revdelete-hide-user' => 'Benutzername/IP-Adresse des Bearbeiters',
 'revdelete-hide-restricted' => 'Daten sowohl vor Administratoren als auch anderen Benutzern unterdrücken',
 'revdelete-radio-same' => '(nicht ändern)',
-'revdelete-radio-set' => 'Ja',
-'revdelete-radio-unset' => 'Nein',
+'revdelete-radio-set' => 'Sichtbar',
+'revdelete-radio-unset' => 'Versteckt',
 'revdelete-suppress' => 'Grund der Löschung auch vor Administratoren verstecken',
 'revdelete-unsuppress' => 'Einschränkungen für wiederhergestellte Versionen aufheben',
 'revdelete-log' => 'Grund:',
@@ -4074,7 +4074,7 @@ Eine [{{SERVER}}{{SCRIPTPATH}}/COPYING Kopie der ''GNU General Public License'']
 # Special:Redirect
 'redirect' => 'Weiterleitung auf Benutzerseite, Seitenversion oder Datei',
 'redirect-legend' => 'Weiterleitung auf eine Benutzerseite, Seitenversion oder Datei',
-'redirect-summary' => 'Diese Spezialseite leitet auf eine Benutzerseite (numerische Benutzerkennung angegeben), Seitenversion (Versionskennung angegeben) oder Datei (Dateiname angegeben) weiter.',
+'redirect-summary' => 'Diese Spezialseite leitet auf eine Benutzerseite (numerische Benutzerkennung angegeben), Seitenversion (Versionskennung angegeben) oder Datei (Dateiname angegeben) weiter. Benutzung: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]] oder [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit' => 'Los',
 'redirect-lookup' => 'Suchen:',
 'redirect-value' => 'Kennung oder Dateiname:',
index aa5ce1f..ab87bab 100644 (file)
@@ -458,8 +458,6 @@ $messages = array(
 'broken-file-category' => 'Peleye ke gıreyê dosyeyanê ğeletan muhtewa kenê',
 'categoryviewer-pagedlinks' => '($1) ($2)',
 
-'linkprefix' => "'''MediaWiki niya ro.'''",
-
 'about' => 'Heqa cı de',
 'article' => 'Wesiqe',
 'newwindow' => '(pençereyê newey de beno a)',
@@ -778,6 +776,7 @@ Wexta ke verhafızayê cıgerayoxê şıma pak beno no benate de taye peli de he
 'userlogin-resetpassword-link' => 'Parolaya xo reset ke',
 'helplogin-url' => 'Help:Qeydbiyayış',
 'userlogin-helplink' => '[[{{MediaWiki:helplogin-url}}|Desteg be qeydbiyayış ra]]',
+'userlogin-createanother' => 'Zewbi hesab vıraz',
 'createacct-join' => 'Cêr melumatê xo cı ke',
 'createacct-emailrequired' => 'Adresa e-postey',
 'createacct-emailoptional' => 'Adresa e-postey (mecburi niya)',
@@ -835,8 +834,8 @@ Eke vurnayişê parolayi, şıma nêwaşt ya zi parolayê şıma ameyo şıma vi
 'noemailcreate' => 'Şıma gani yew parolayo meqbul peda bıkeri',
 'passwordsent' => '"$1" No name de yew e-posta erşawiya (ruşya). hesabê xo, şıma wext mesaj gırewt u çax akere.',
 'blocked-mailpassword' => 'Cıkewetışê na keyepel de şıma qedexe biye, ey ra newe yew şifre nêerşawyeno.',
-'eauthentsent' => 'Adreso ke şıma dayo ma, ma yew e-posta rışt uca, o e-posta de kodê araşt kerdış esto.
-Heta ke şıma o e-postaaraşt nêkeri ma yewna e-posta şıma ri nêrışêno.',
+'eauthentsent' => 'Adresok şıma qeyd kerdo wıcayré e-posta rışiyé.
+Hetana şıma ne e-posta néwweyniyé, şımaé zewbi e-posta do nérışiyo.',
 'throttled-mailpassword' => 'Eyarkerdışê parola xora zerreyê {{PLURAL:$1|yew saete|$1 saetan}} erşawiya.
 Seba xırabgurenayışê xızmete ra, her {{PLURAL:$1|yew saete|$1 saetan}} de rey tenya yew eyarkerdışê parola erşawiyeno.',
 'mailerror' => 'Erşawıtışe xetayê e-posta: $1',
@@ -892,6 +891,7 @@ Bıne vındere u newe ra dest pê bıkere.',
 'passwordreset-text-many' => '{{PLURAL:$1|Qande parola reset kerdışi cayanra taynın pırkeri}}',
 'passwordreset-legend' => 'Parola reset ke',
 'passwordreset-disabled' => 'Parola reset kerdış ena viki sera qefılneyayo.',
+'passwordreset-emaildisabled' => 'Na wikid hısusiyeté e-posta dewera vıcyayé',
 'passwordreset-username' => 'Nameyê karberi:',
 'passwordreset-domain' => 'Domain:',
 'passwordreset-capture' => 'neticey e-postay bımocne?',
@@ -1638,6 +1638,7 @@ Kaberê bini ke şıma de kewti irtıbat, adresa e-postey şıma eşkera nêbena
 'right-editusercss' => 'Dosyanê CSSiê karberanê binan sero bıgureye',
 'right-edituserjs' => 'Dosyanê JSiê karberanê binan sero bıgureye',
 'right-viewmywatchlist' => 'Lista seyr de xo bıvin',
+'right-editmyoptions' => 'Tercihané ğo bıvırn',
 'right-rollback' => 'Lez/herbi vurnayışanê karberê peyêni tekrar bıke, oyo ke yew be yew pelê sero gureyao',
 'right-markbotedits' => 'Vurnayışanê peyd ameyan, vurnayışê boti deye nışan kerê',
 'right-noratelimit' => 'Sinoranê xızi (rate limit) ra tesir nêbi',
@@ -1711,6 +1712,7 @@ Kaberê bini ke şıma de kewti irtıbat, adresa e-postey şıma eşkera nêbena
 'recentchanges' => 'Vurnayışê peyêni',
 'recentchanges-legend' => 'Tercihê vurnayışanê peyênan',
 'recentchanges-summary' => 'Ena pele de wiki sero vurnayışanê peyênan teqib ke.',
+'recentchanges-noresult' => 'Zey kiterandé şıma vırnayış névineya',
 'recentchanges-feed-description' => 'Ena feed dı vurnayişanê tewr peniyan teqip bık.',
 'recentchanges-label-newpage' => 'Enê vurnayışi pelaya newi vıraşt',
 'recentchanges-label-minor' => 'Eno yew vurnayışo qıckeko',
@@ -2012,6 +2014,7 @@ keyepel nıka zaf meşğulo yew dema herayi de newe ra tesel bıkerê.',
 'listfiles_size' => 'Gırdiye',
 'listfiles_description' => 'Sılasnayış',
 'listfiles_count' => 'Versiyoni',
+'listfiles-show-all' => 'Asayışa versiyonandé verénan',
 'listfiles-latestversion' => 'Versiyono verin',
 'listfiles-latestversion-yes' => 'E',
 'listfiles-latestversion-no' => 'Nê',
@@ -2201,6 +2204,7 @@ gıreyê her satıri de gıreyi; raş motışê yewın u dıyıni esto.
 'mostrevisions' => 'Pelan ke tewr zaf revizyonî biyê.',
 'prefixindex' => 'Veroleya peley pêro',
 'prefixindex-namespace' => 'Peleyê Veroleyıni ($1 cay nami)',
+'prefixindex-strip' => 'Listeya réz bıyayışi',
 'shortpages' => 'Pelê kılmeki',
 'longpages' => 'Peleyê dergeki',
 'deadendpages' => 'Pelê nêgıredayey',
@@ -2481,10 +2485,12 @@ Qe qeydê wedarnayışi, $2 bevinin.',
 'deletecomment' => 'Sebeb:',
 'deleteotherreason' => 'Sebebo bin:',
 'deletereasonotherlist' => 'Sebebo bin',
-'deletereason-dropdown' => '*sebebê hewnakerdışê pêroyî
-** talebê nuştekari
-** ihlalê heqê telifi
-** Vandalizm',
+'deletereason-dropdown' => '*Sebebé esterıti
+** Spam
+** Vandalizm
+** İhlala heqdé telifi
+** Waştışé nustoği
+** Xırab hetenayış',
 'delete-edit-reasonlist' => 'Sebebê vurnayışan bıvurne',
 'delete-toobig' => 'no pel, pê $1 {{PLURAL:$1|tene vuriyayiş|tene vuriyayiş}}i wayirê yew tarixo kehen o.
 qey hewna nêşiyayişi wina pelani u {{SITENAME}}nêxerebnayişê keyepeli yew hed niyaya ro.',
@@ -3152,8 +3158,8 @@ Tı eşkeno yew sebeb bınus.',
 'spam_reverting' => 'agêriyeno revizyon o ke tawayê $1 ıney piya çiniyo',
 'spam_blanking' => 'Revizyonê gredê $1 vineyay, wa weng kero',
 'spam_deleting' => 'Revizyonê gredê $1 vineyay, wa besterneyê',
-'simpleantispam-label' => "tehqiqatê Anti-spami.
-no '''de mekerê'''!",
+'simpleantispam-label' => "Cerbnayışa anti-spami.
+Ney '''Mefiyé de'''!",
 
 # Info page
 'pageinfo-title' => 'Heq tê "$1"\'i',
@@ -4209,6 +4215,7 @@ satır ê ke pê ney # # destpêkenê zey mışore/mıjore muamele vineno.
 'dberr-problems' => 'Mayê muxulêm! Ena sita dı newke xırabiya teknik esta.',
 'dberr-again' => 'Yew di dekika vinder u hin bar bike.',
 'dberr-info' => '(Erzmelumati ra xızmetkari nêreseno: $1)',
+'dberr-info-hidden' => '(Ardendé erz malumatiya gredayışo nébeno)',
 'dberr-usegoogle' => 'Ti eşkeno hem zi ser Google de bigêre.',
 'dberr-outofdate' => 'Ekê raten da ma deyê belki zi newen niyo qandê coy diqet kerê.',
 'dberr-cachederror' => 'Pel ke ti wazeno yew kopyayê cacheyî ay esto, ay belki rocaniyeyo.',
@@ -4349,6 +4356,9 @@ satır ê ke pê ney # # destpêkenê zey mışore/mıjore muamele vineno.
 'limitreport-cputime-value' => '$1 {{PLURAL:$1|saniye|saniyeyan}}',
 'limitreport-walltime' => 'Raştay demdı bıkarn',
 'limitreport-walltime-value' => '$1 {{PLURAL:$1|saniye|saniyeyan}}',
+'limitreport-ppvisitednodes' => 'Amariya ziyaretda gozgıreya verkarkerdoği',
+'limitreport-ppgeneratednodes' => 'Amariya vıraştışda gozgırandé vekarkerdoği',
+'limitreport-postexpandincludesize' => 'Ebata herayina rışteri dahil a.',
 'limitreport-postexpandincludesize-value' => '$1/$2 {{PLURAL:$2|bayt|bayti}}',
 'limitreport-templateargumentsize' => 'Ebata hacetandi şablonan',
 'limitreport-templateargumentsize-value' => '$1/$2 {{PLURAL:$2|bayt|bayti}}',
index 6d68d36..7b62d29 100644 (file)
@@ -494,6 +494,12 @@ $specialPageAliases = array(
  */
 $linkTrail = '/^([a-z]+)(.*)$/sD';
 
+/**
+ * Regular expression charset matching the "link prefix", e.g. "foo" in
+ * foo[[bar]]. UTF-8 characters may be used.
+ */
+$linkPrefixCharset = 'a-zA-Z\\x{80}-\\x{10ffff}';
+
 /**
  * List of filenames for some ui images that can be overridden per language
  * basis if needed.
@@ -790,8 +796,6 @@ future releases. Also note that since each list value is wrapped in a unique
 'broken-file-category'           => 'Pages with broken file links',
 'categoryviewer-pagedlinks'      => '($1) ($2)', # only translate this message to other languages if you have to change it
 
-'linkprefix' => '/^((?>.*(?<![a-zA-Z\\x80-\\xff])))(.+)$/sD', # only translate this message to other languages if you have to change it
-
 'about'         => 'About',
 'article'       => 'Content page',
 'newwindow'     => '(opens in new window)',
@@ -1706,15 +1710,15 @@ Other administrators on {{SITENAME}} will still be able to access the hidden con
 * Inappropriate personal information
 *: ''home addresses and telephone numbers, social security numbers, etc.''",
 'revdelete-legend'            => 'Set visibility restrictions',
-'revdelete-hide-text'         => 'Hide revision text',
+'revdelete-hide-text'         => 'Revision text',
 'revdelete-hide-image'        => 'Hide file content',
 'revdelete-hide-name'         => 'Hide action and target',
-'revdelete-hide-comment'      => 'Hide edit summary',
-'revdelete-hide-user'         => "Hide editor's username/IP address",
+'revdelete-hide-comment'      => 'Edit summary',
+'revdelete-hide-user'         => "Editor's username/IP address",
 'revdelete-hide-restricted'   => 'Suppress data from administrators as well as others',
 'revdelete-radio-same'        => '(do not change)',
-'revdelete-radio-set'         => 'Yes',
-'revdelete-radio-unset'       => 'No',
+'revdelete-radio-set'         => 'Visible',
+'revdelete-radio-unset'       => 'Hidden',
 'revdelete-suppress'          => 'Suppress data from administrators as well as others',
 'revdelete-unsuppress'        => 'Remove restrictions on restored revisions',
 'revdelete-log'               => 'Reason:',
@@ -4870,7 +4874,7 @@ You should have received [{{SERVER}}{{SCRIPTPATH}}/COPYING a copy of the GNU Gen
 'redirect'            => 'Redirect by file, user, 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).',
+'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-submit'     => 'Go',
 'redirect-lookup'     => 'Lookup:',
 'redirect-value'      => 'Value:',
@@ -4956,8 +4960,7 @@ You should have received [{{SERVER}}{{SCRIPTPATH}}/COPYING a copy of the GNU Gen
 
 # Database error messages
 'dberr-header'      => 'This wiki has a problem',
-'dberr-problems'    => 'Sorry!
-This site is experiencing technical difficulties.',
+'dberr-problems'    => 'Sorry! This site is experiencing technical difficulties.',
 'dberr-again'       => 'Try waiting a few minutes and reloading.',
 'dberr-info'        => '(Cannot contact the database server: $1)',
 'dberr-info-hidden' => '(Cannot contact the database server)',
index b8ec1e5..48979e9 100644 (file)
@@ -38,6 +38,7 @@
  * @author Smeira
  * @author ThomasPusch
  * @author Tlustulimu
+ * @author Umbert'
  * @author Urhixidur
  * @author Yekrats
  * @author Александр Сигачёв
@@ -570,7 +571,7 @@ $1',
 # All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).
 'aboutsite' => 'Pri {{SITENAME}}',
 'aboutpage' => 'Project:Enkonduko',
-'copyright' => 'La enhavo estas disponebla laŭ $1.',
+'copyright' => 'La enhavo estas disponebla laŭ $1, se ne estas alia indiko.',
 'copyrightpage' => '{{ns:project}}:Aŭtorrajto',
 'currentevents' => 'Aktualaĵoj',
 'currentevents-url' => 'Project:Aktualaĵoj',
@@ -653,6 +654,7 @@ Listo de validaj specialaj paĝoj estas trovebla ĉe [[Special:SpecialPages|{{in
 # General errors
 'error' => 'Eraro',
 'databaseerror' => 'Datumbaza eraro',
+'databaseerror-error' => 'Eraro: $1',
 'laggedslavemode' => 'Avertu: la paĝo eble ne enhavas lastatempajn ĝisdatigojn.',
 'readonly' => 'Datumaro ŝlosita, nurlega',
 'enterlockreason' => 'Bonvolu klarigi, kial oni ŝlosas la datumaron, kaj
@@ -778,7 +780,7 @@ Ne forgesu ŝanĝi viajn [[Special:Preferences|{{SITENAME}}-preferojn]]',
 'createacct-emailrequired' => 'Retpoŝta adreso',
 'createacct-emailoptional' => 'Retpoŝta adreso (nedeviga)',
 'createacct-email-ph' => 'Enigu vian retpoŝtan adreson',
-'createaccountmail' => 'Uzi provizoran hazardsignan pasvorton kaj sendi ĝin al la retpoŝto suben',
+'createaccountmail' => 'Uzi provizoran hazardsignan pasvorton kaj sendi ĝin al la retpoŝta adreso ĉi-suba',
 'createacct-realname' => 'Vera nomo (nedeviga)',
 'createaccountreason' => 'Kialo:',
 'createacct-reason' => 'Kialo',
@@ -795,6 +797,7 @@ Ne forgesu ŝanĝi viajn [[Special:Preferences|{{SITENAME}}-preferojn]]',
 'userexists' => 'Salutnomo enigita jam estas uzata.
 Bonvolu elekti alian nomon.',
 'loginerror' => 'Ensaluta eraro',
+'createacct-error' => 'Eraro pri kreado de konto',
 'createaccounterror' => 'Ne eblis krei konton: $1',
 'nocookiesnew' => 'La uzantokonto estis kreita sed vi ne estas ensalutinta. *** E-igo lcfirst {{SITENAME}} uzas kuketojn por akcepti uzantojn. Kuketoj esta malaktivigitaj ĉe vi. Bonvolu aktivigi ilin kaj ensalutu per viaj novaj salutnomo kaj pasvorto.',
 'nocookieslogin' => '{{SITENAME}} uzas kuketojn por akcepti uzantojn. Kuketoj esta malaktivigitaj ĉe vi. Bonvolu aktivigi ilin kaj provu denove.',
@@ -830,7 +833,7 @@ registrita por "$1".
 Bonvolu saluti denove ricevinte ĝin.',
 'blocked-mailpassword' => 'Via IP adreso estas forbarita de redaktado, kaj tial
 ne rajtas uzi la pasvorto-rekovran funkcion por malebligi misuzon.',
-'eauthentsent' => 'Konfirma retmesaĝo estas sendita al la nomita retadreso. Antaŭ ol iu ajn alia mesaĝo estos sendita al la konto, vi devos sekvi la instrukciojn en la mesaĝo por konfirmi ke la konto ja estas la via.',
+'eauthentsent' => 'Konfirma retmesaĝo estis sendita al la nomita retadreso. Antaŭ ol iu ajn alia mesaĝo estos sendita al la konto, vi devos sekvi la instrukciojn en la mesaĝo por konfirmi ke la konto ja estas via.',
 'throttled-mailpassword' => 'Retpoŝto kun reŝargita pasvorto estis jam sendita ene de la {{PLURAL:$1|lasta horo|lastaj $1 horoj}}.
 Por preventi misuzon, nur unu reŝargita pasvorto estos sendita dum {{PLURAL:$1|horo|$1 horoj}}.',
 'mailerror' => 'Okazis eraro sendante retpoŝtaĵon: $1',
@@ -852,11 +855,13 @@ Enigi bone formatita adreso aŭ malplenigi tiun kampon.',
 
 Vi povas ignori ĉi mesaĝon, se ĉi konto estis kreita erare.',
 'usernamehasherror' => 'Salutnomo ne povas enhavi kriphaketaĵajn signojn',
-'login-throttled' => 'Vi tro ofte provis ensaluti.
-Bonvolu ĝisatendi antaŭ retrovi.',
+'login-throttled' => 'Vi ĵus tro ofte provis ensaluti.
+Bonvolu ĝisatendi $1 antaŭ reprovi.',
 'login-abort-generic' => 'Via ensaluto malsukcesis - Ĉesigita',
 'loginlanguagelabel' => 'Lingvo: $1',
 'suspicious-userlogout' => 'Via peto por elsaluti estis malpermesita ĉar verŝajne ĝi estis sendita de trompita retumilo aŭ kaŝiganta proksima servilo.',
+'createacct-another-realname-tip' => 'La vera nomo estas nenecesa.
+Se vi decidas indiki ĝin, ĝi estos uzata por montri atribuadon de viaj kontribuoj.',
 
 # Email sending
 'php-mail-error-unknown' => 'Nekonata eraro en la funkcio mail() de PHP',
@@ -872,7 +877,7 @@ Bonvolu ĝisatendi antaŭ retrovi.',
 'newpassword' => 'Nova pasvorto',
 'retypenew' => 'Retajpi novan pasvorton',
 'resetpass_submit' => 'Fari pasvorton kaj ensaluti',
-'changepassword-success' => 'Via pasvorto estis sukcese ŝanĝita! Nun ensalutanta vin...',
+'changepassword-success' => 'Via pasvorto estis sukcese ŝanĝita!',
 'resetpass_forbidden' => 'Pasvortoj ne estas ŝanĝeblaj',
 'resetpass-no-info' => 'Vi devas ensaluti por atingi ĉi tiun paĝon rekte.',
 'resetpass-submit-loggedin' => 'Ŝanĝi pasvorton',
@@ -884,6 +889,7 @@ Vi eble jam ŝanĝis vian pasvorton aŭ petis novan provizoran pasvorton.',
 
 # Special:PasswordReset
 'passwordreset' => 'Restarigo de pasvorto',
+'passwordreset-text-one' => 'Plenigu ĉi tiun formularon por renovigi vian pasvorton.',
 'passwordreset-legend' => 'Refari pasvorton',
 'passwordreset-disabled' => 'Pasvortaj restarigoj estis malŝaltitaj en ĉi tiu vikio.',
 'passwordreset-emaildisabled' => 'Retpoŝtaj funkcioj estas malfunkciigitaj en tiu ĉi vikio.',
@@ -915,7 +921,7 @@ aŭ se vi memoris vian originalan pasvorton, kaj vi ne plu volas ŝanĝi
 Provizora pasvorto: $2',
 'passwordreset-emailsent' => 'Renovigita pasvorto estis retpoŝte sendita.',
 'passwordreset-emailsent-capture' => 'Retpoŝto kun renovigita pasvorto estis sendita, kiu estas montrata sube.',
-'passwordreset-emailerror-capture' => 'Retpoŝto kun renovigita pasvorto estis generita, montrata sube, sed sendado al uzanto malsukcesis: $1',
+'passwordreset-emailerror-capture' => 'Retpoŝto kun renovigita pasvorto estis generita, montrata sube, sed sendado al la {{GENDER:$2|uzanto}} malsukcesis: $1',
 
 # Special:ChangeEmail
 'changeemail' => 'Ŝanĝi retpoŝtadreson',
@@ -929,6 +935,14 @@ Provizora pasvorto: $2',
 'changeemail-submit' => 'Ŝanĝi retpoŝtadreson',
 'changeemail-cancel' => 'Nuligi',
 
+# Special:ResetTokens
+'resettokens-no-tokens' => 'Ne estas ŝlosiloj renovigeblaj.',
+'resettokens-legend' => 'Renovigi ŝlosilojn',
+'resettokens-tokens' => 'Ŝlosiloj:',
+'resettokens-token-label' => '$1 (nuna valoro: $2)',
+'resettokens-done' => 'Ŝlosiloj renovigitaj.',
+'resettokens-resetbutton' => 'Renovigi elektitajn ŝlosilojn',
+
 # Edit page toolbar
 'bold_sample' => 'Grasa teksto',
 'bold_tip' => 'Grasa teksto',
@@ -1135,7 +1149,7 @@ Verŝajne ĝi estis forigita.',
 'content-failed-to-parse' => 'Oni malsukcesis analizi $2-entenon laŭ la $1-modelo: $3',
 'invalid-content-data' => 'Enhavo estas malvalida',
 'content-not-allowed-here' => 'Enhavo de $1 ne estas permesita en paĝo [[$2]]',
-'editwarning-warning' => 'Forlasante ĉi tiun paĝon kaŭzos al vi perdi iun ajn ŝanĝojn kiujn vi faris.
+'editwarning-warning' => 'Forlaso de ĉi tiu paĝo kaŭzos al vi perdi iun ajn ŝanĝojn kiujn vi faris.
 Se vi ensalutas, vi povas malŝalti ĉi tiun averton en la sekcio "{{int:prefs-editing}}" de viaj preferoj.',
 
 # Content models
@@ -1172,6 +1186,7 @@ Bonvolu konfirmi la jenan komparaĵon por verigi ĉi tiel vi volas, kaj konservi
 'undo-failure' => 'Ne povis nuligi redakton pro konfliktaj intermezaj redaktoj.',
 'undo-norev' => 'La redakto ne eblis esti malfarita ĉar ĝi aŭ ne ekzistas aŭ estis forigita.',
 'undo-summary' => 'Nuligis version $1 de [[Special:Contributions/$2|$2]] ([[User talk:$2|Diskuto]] | [[Special:Contributions/$2|{{MediaWiki:Contribslink}}]])',
+'undo-summary-username-hidden' => 'Malfari ŝanĝon $1 de kaŝita uzulo',
 
 # Account creation failure
 'cantcreateaccounttitle' => 'Ne povas krei konton',
@@ -1269,8 +1284,8 @@ Aliaj administrantoj ĉe {{SITENAME}} plu povos aliri la kaŝitan entenon kaj re
 'revdelete-hide-user' => 'Kaŝi nomon aŭ IP-adreson de redaktinto',
 'revdelete-hide-restricted' => 'Subpremi ĉi tiujn datenojn de administrantoj kaj ankaŭ aliaj',
 'revdelete-radio-same' => '(ne ŝanĝi)',
-'revdelete-radio-set' => 'Jes',
-'revdelete-radio-unset' => 'Ne',
+'revdelete-radio-set' => 'Videbla',
+'revdelete-radio-unset' => 'Kaŝita',
 'revdelete-suppress' => 'Subpremi datenojn de kaj administrantoj kaj aliaj',
 'revdelete-unsuppress' => 'Forigi limigojn al restarigitaj versioj',
 'revdelete-log' => 'Kialo:',
@@ -1535,6 +1550,7 @@ indekso pro troŝarĝita servilo. Intertempe, vi povas serĉi per <i>guglo</i> a
 'prefs-displaywatchlist' => 'Montraj opcioj',
 'prefs-tokenwatchlist' => 'Ĵetono',
 'prefs-diffs' => 'Diferencoj',
+'prefs-help-prefershttps' => 'Ĉi tiu agordo ekefikos je via sekva ensaluto.',
 
 # User preference: email validation using jQuery
 'email-address-validity-valid' => 'Ŝajnas ke la retpoŝtadreso estas valida',
@@ -1561,6 +1577,7 @@ indekso pro troŝarĝita servilo. Intertempe, vi povas serĉi per <i>guglo</i> a
 'userrights-notallowed' => 'Via konto ne rajtas doni aŭ forigi uzanto-rajtojn.',
 'userrights-changeable-col' => 'Grupoj kiujn vi povas ŝanĝi',
 'userrights-unchangeable-col' => 'Grupoj kiujn vi ne povas ŝanĝi',
+'userrights-removed-self' => 'Vi sukcese nuligis viajn proprajn rajtojn. Do vi ne plu rajtas aliri ĉi tiun paĝon.',
 
 # Groups
 'group' => 'Grupo:',
@@ -1634,6 +1651,8 @@ indekso pro troŝarĝita servilo. Intertempe, vi povas serĉi per <i>guglo</i> a
 'right-editmyusercss' => 'Redakti viajn proprajn CSS-dosierojn',
 'right-editmyuserjs' => 'Redakti viajn proprajn JavaScript-dosierojn',
 'right-viewmywatchlist' => 'Rigardi vian atentaron',
+'right-viewmyprivateinfo' => 'Vidi viajn proprajn privatajn informojn (ekz. retpoŝtan adreson, veran nomon)',
+'right-editmyprivateinfo' => 'Redakti viajn proprajn privatajn informojn (ekz. retpoŝtan adreson, veran nomon)',
 'right-rollback' => 'Tuj malfari la redaktojn de la lasta uzanto kiu redaktis specifan paĝon',
 'right-markbotedits' => 'Marki restarigitajn redaktojn kiel robotajn redaktojn',
 'right-noratelimit' => 'Ne influita de po-limoj',
@@ -1685,7 +1704,7 @@ indekso pro troŝarĝita servilo. Intertempe, vi povas serĉi per <i>guglo</i> a
 'action-block' => 'forari ĉi tiun uzanton de redaktado',
 'action-protect' => 'ŝanĝi la protektan nivelon por ĉi tiu paĝo',
 'action-rollback' => 'tuj malfari la redaktojn de la lasta uzanto kiu redaktis specifan paĝon',
-'action-import' => 'importi ĉi tiun paĝon de alia vikio',
+'action-import' => 'enporti paĝojn de alia vikio',
 'action-importupload' => 'importi ĉi tiun paĝon de dosiera alŝuto',
 'action-patrol' => 'marki redakton de alia persono kiel patrolitan',
 'action-autopatrol' => 'fari vian redakton markitan kiel patrolitan',
@@ -1695,12 +1714,18 @@ indekso pro troŝarĝita servilo. Intertempe, vi povas serĉi per <i>guglo</i> a
 'action-userrights-interwiki' => 'redakti la rajtojn de uzantoj en aliaj vikioj',
 'action-siteadmin' => 'ŝlosi aŭ malŝlosi la datumbazon',
 'action-sendemail' => 'sendi retpoŝtojn',
+'action-editmywatchlist' => 'modifi vian atento-liston',
+'action-viewmywatchlist' => 'vidi vian atento-liston',
+'action-viewmyprivateinfo' => 'vidi viajn privatajn informojn',
+'action-editmyprivateinfo' => 'redakti viajn privatajn informojn',
 
 # Recent changes
 'nchanges' => '$1 {{PLURAL:$1|ŝanĝo|ŝanĝoj}}',
+'enhancedrc-history' => 'historio',
 'recentchanges' => 'Lastaj ŝanĝoj',
 'recentchanges-legend' => 'Opcioj pri lastaj ŝanĝoj',
 'recentchanges-summary' => 'Per ĉi tiu paĝo vi povas sekvi la plej lastajn ŝanĝojn en la {{SITENAME}}.',
+'recentchanges-noresult' => 'En la donita tempo ne estis ŝanĝoj, kiuj konformas al la kriterioj.',
 'recentchanges-feed-description' => 'Sekvi la plej lastatempajn ŝanĝojn al la vikio en ĉi tiu fonto.',
 'recentchanges-label-newpage' => 'Ĉi tiu redakto kreis novan paĝon',
 'recentchanges-label-minor' => 'Ĉi tiu estas eta redakto',
index df55273..34455a1 100644 (file)
@@ -4035,7 +4035,7 @@ Has recibido [{{SERVER}}{{SCRIPTPATH}}/COPYING una copia de la Licencia Pública
 # Special:Redirect
 'redirect' => 'Redirigir por archivo, usuario o ID de revisión',
 'redirect-legend' => 'Redirigir a un archivo o página',
-'redirect-summary' => 'Esta página especial redirige a un fichero (dado un nombre de fichero), a una página (dado un identificador de revisión) o a una página de usuario (dado en identificador numérico de usuario).',
+'redirect-summary' => 'Esta página especial redirige a un fichero (dado un nombre de fichero), a una página (dado un identificador de revisión) o a una página de usuario (dado en identificador numérico de usuario). Uso: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], o [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit' => 'Ir',
 'redirect-lookup' => 'Buscar:',
 'redirect-value' => 'Valor:',
index 739314d..1848261 100644 (file)
@@ -1117,7 +1117,7 @@ Kontura zaitez nabigazio loturek, zutabea ezabatu dezakela.',
 
 # Diffs
 'history-title' => '"$1" orrialdearen historia berrikuspena',
-'difference-title' => '"$1"-en berrikuspenen arteko aldaketa',
+'difference-title' => '«$1»: berrikuspenen arteko aldeak',
 'difference-title-multipage' => '"$1" eta "$2" orrialdeen arteko ezberditasunak',
 'difference-multipage' => '(Orrialdeen arteko ezberdintasunak)',
 'lineno' => '$1. lerroa:',
index 9744b0e..c96c615 100644 (file)
@@ -2267,7 +2267,7 @@ https://www.mediawiki.org/wiki/Manual:Image_Authorization را ببینید.',
 'mimesearch-summary' => 'با کمک این صفحه شما می‌توانید پرونده‌هایی که یک نوع MIME به خصوص دارند را پیدا کنید.
 ورودی: به صورت contenttype/subtype ، نظیر <code>image/jpeg</code>.',
 'mimetype' => 'نوع MIME:',
-'download' => 'بارگÛ\8cرÛ\8c',
+'download' => 'درÛ\8cاÙ\81ت',
 
 # Unwatched pages
 'unwatchedpages' => 'صفحه‌های پی‌گیری‌نشده',
@@ -4183,7 +4183,7 @@ $5
 # Special:Redirect
 'redirect' => 'تغییرمسیر توسط پرونده، کاربر یا شناسهٔ نسخه',
 'redirect-legend' => 'تغییرمسیر به یک پرونده یا صفحه',
-'redirect-summary' => 'این صفحهٔ ویژه به پرونده (نام پرونده داده‌شده)، صفحه (شماره شناسهٔ صفحه داده‌شده) یا صفحهٔ کاربری (شناسهٔ عددی کاربری داده‌شده) تغییرمسیر می‌یابد',
+'redirect-summary' => 'این صفحهٔ ویژه به پرونده (نام پرونده داده‌شده)، صفحه (شماره شناسهٔ صفحه داده‌شده) یا صفحهٔ کاربری (شناسهٔ عددی کاربری داده‌شده) تغییرمسیر می‌یابد. طرز استفاده: [[{{#Special:Redirect}}/file/Example.jpg]]، [[{{#Special:Redirect}}/revision/328429]] یا [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit' => 'برو',
 'redirect-lookup' => 'جستجو:',
 'redirect-value' => 'مقدار:',
@@ -4296,7 +4296,7 @@ $5
 # New logging system
 'logentry-delete-delete' => '$1 صفحهٔ $3 را {{GENDER:$2|حذف کرد}}',
 'logentry-delete-restore' => '$1 صفحهٔ $3 را {{GENDER:$2|احیا کرد}}',
-'logentry-delete-event' => '$1 پیدایی {{PLURAL:$5|یک مورد سیاهه|$5 مورد سیاهه}} را در $3 {{GENDER:$2|تغییر داد}} : $4',
+'logentry-delete-event' => '$1 پیدایی {{PLURAL:$5|یک مورد سیاهه|$5 مورد سیاهه}} را در $3 {{GENDER:$2|تغییر داد}}: $4',
 'logentry-delete-revision' => '$1 پیدایی {{PLURAL:$5|یک نسخه|$5 نسخه}} صفحه $3 را {{GENDER:$2|تغییر داد}}: $4',
 'logentry-delete-event-legacy' => '$1 پیدایی موارد سیاهه را در $3 {{GENDER:$2|تغییر داد}}',
 'logentry-delete-revision-legacy' => '$1 پیدایی نسخه‌های $3 را {{GENDER:$2|تغییر داد}}',
index 350c00c..8a62105 100644 (file)
@@ -829,7 +829,8 @@ Jos joku muu on tehnyt tämän pyynnön, tai jos olet muistanut salasanasi ja et
 'passwordsent' => 'Uusi salasana on lähetetty käyttäjän <b>$1</b> sähköpostiosoitteeseen.
 Ole hyvä ja kirjaudu sisään kun olet saanut sen.',
 'blocked-mailpassword' => 'Osoitteellesi on asetettu muokkausesto, joka estää käyttämästä salasanamuistutustoimintoa.',
-'eauthentsent' => 'Varmennussähköposti on lähetetty annettuun sähköpostiosoitteeseen. Muita viestejä ei lähetetä, ennen kuin olet toiminut viestin ohjeiden mukaan ja varmistanut, että sähköpostiosoite kuuluu sinulle.',
+'eauthentsent' => 'Varmennussähköposti on lähetetty annettuun sähköpostiosoitteeseen.
+Muita viestejä ei lähetetä, ennen kuin olet toiminut viestin ohjeiden mukaan ja varmistanut, että sähköpostiosoite kuuluu sinulle.',
 'throttled-mailpassword' => 'Salasananpalautusviesti on lähetetty {{PLURAL:$1|kuluvan|kuluvien $1}} tunnin aikana. Salasananpalautusviestejä lähetetään enintään {{PLURAL:$1|tunnin|$1 tunnin}} välein.',
 'mailerror' => 'Virhe lähetettäessä sähköpostia: $1',
 'acct_creation_throttle_hit' => 'IP-osoitteestasi on luotu tähän wikiin jo {{PLURAL:$1|yksi tunnus|$1 tunnusta}} päivän aikana, joka suurin sallittu määrä tälle ajalle.
@@ -1281,11 +1282,12 @@ $1",
 'revdel-restore-visible' => 'näkyvät versiot',
 'pagehist' => 'Sivun muutoshistoria',
 'deletedhist' => 'Poistettujen versioiden historia',
-'revdelete-hide-current' => 'Virhe tapahtui $2, $1 päivätyn kohteen piilottamisessa: tämä on nykyinen versio. Sitä ei voi piilottaa.',
+'revdelete-hide-current' => 'Virhe piilotettaessa $1 kello $2 päivättyä kohdetta: Tämä on uusin versio.
+Sitä ei voi piilottaa.',
 'revdelete-show-no-access' => 'Virhe näyttäessä kohtaa $2 kello $1: kohta on merkitty ”rajoitetuksi”.
 Sinulla ei ole oikeutta siihen.',
 'revdelete-modify-no-access' => 'Virhe tapahtui $2, $1 kohteen muokkauksessa: tämä kohde on merkitty "rajoitetuksi". Sinulla ei ole oikeuksia sen muokkaukseen.',
-'revdelete-modify-missing' => 'Virhe muuttaessa kohdetta, jonka tunnus on $1: Se puuttuu tietokannasta.',
+'revdelete-modify-missing' => 'Virhe muuttaessa kohdetta, jonka tunniste on $1: Se puuttuu tietokannasta.',
 'revdelete-no-change' => "'''Varoitus:''' kohteessa $2 kello $1 on jo valmiiksi haluamasi näkyvyysasetukset.",
 'revdelete-concurrent-change' => 'Virhe $2, $1 päivätyn kohteen muokkauksessa: sen tilan on näköjään muuttanut joku sillä aikaa kun yritit muokata sitä. Ole hyvä ja tarkista lokit.',
 'revdelete-only-restricted' => 'Virhe piilotettaessa $1 kello $2 päivättyä kohdetta: Et voi poistaa kohteita ylläpitäjien näkyviltä valitsematta myös jotain muuta näkyvyysasetusta.',
@@ -3473,7 +3475,7 @@ Kaikki muut linkit ovat poikkeuksia eli toisin sanoen sivuja, joissa tiedostoa s
 'exif-copyrightowner' => 'Tekijänoikeuden haltija',
 'exif-usageterms' => 'Käyttöehdot',
 'exif-webstatement' => 'Verkossa oleva tekijänoikeustieto',
-'exif-originaldocumentid' => 'Alkuperäisen asiakirjan tunnusnumero',
+'exif-originaldocumentid' => 'Alkuperäisen asiakirjan tunniste',
 'exif-licenseurl' => 'Tekijänoikeuslisenssin URL',
 'exif-morepermissionsurl' => 'Vaihtoehtoiset lisenssitiedot',
 'exif-attributionurl' => 'Kun kuvaa käytetään, linkitä tähän osoitteeseen',
@@ -3906,7 +3908,7 @@ Sinun olisi pitänyt saada [{{SERVER}}{{SCRIPTPATH}}/COPYING kopio GNU General P
 # Special:Redirect
 'redirect' => 'Ohjaus tiedostonimen, käyttäjätunnisteen tai versiotunnisteen mukaan',
 'redirect-legend' => 'Ohjaus tiedostoon tai sivulle',
-'redirect-summary' => 'Tämä toimintosivu ohjaa tiedostoon (tiedostonimen mukaan), sivulle (versiotunnisteen mukaan) tai käyttäjäsivulle (käyttäjätunnisteen numeron mukaan).',
+'redirect-summary' => 'Tämä toimintosivu ohjaa tiedostoon (tiedostonimen mukaan), sivulle (versiotunnisteen mukaan) tai käyttäjäsivulle (käyttäjätunnisteen mukaan). Käyttö: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]] tai [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit' => 'Siirry',
 'redirect-lookup' => 'Hae:',
 'redirect-value' => 'Arvo:',
index f733321..06f49ba 100644 (file)
@@ -909,8 +909,8 @@ pouvez ignorer ce message et continuer à utiliser votre ancien mot de passe.",
 'noemailcreate' => 'Vous devez fournir une adresse de courriel valide',
 'passwordsent' => "Un nouveau mot de passe a été envoyé à l'adresse de courriel de l'utilisateur « $1 ». Veuillez vous reconnecter après l'avoir reçu.",
 'blocked-mailpassword' => 'Votre adresse IP est bloquée en écriture, la fonction de rappel du mot de passe est donc désactivée pour éviter les abus.',
-'eauthentsent' => "Un courriel de confirmation a été envoyé à l'adresse indiquée.
-Avant qu'un autre courriel ne soit envoyé à ce compte, vous devrez suivre les instructions du courriel et confirmer que le compte est bien le vôtre.",
+'eauthentsent' => 'Un courriel de confirmation a été envoyé à l’adresse indiquée.
+Avant qu’un autre courriel ne soit envoyé à ce compte, vous devrez suivre les instructions du courriel et confirmer que le compte est bien le vôtre.',
 'throttled-mailpassword' => "Un courriel de réinitialisation de votre mot de passe a déjà été envoyé durant {{PLURAL:$1|la dernière heure|les $1 dernières heures}}. Afin d'éviter les abus, un seul courriel de réinitialisation de votre mot de passe sera envoyé par {{PLURAL:$1|heure|intervalle de $1 heures}}.",
 'mailerror' => "Erreur lors de l'envoi du courriel : $1",
 'acct_creation_throttle_hit' => "Quelqu'un utilisant votre adresse IP a créé {{PLURAL:$1|un compte|$1 comptes}} au cours des dernières 24 heures, ce qui constitue la limite autorisée dans cet intervalle de temps.
@@ -3270,8 +3270,8 @@ Vous pouvez toutefois en visualiser la source.',
 'spam_reverting' => 'Rétablissement de la dernière version ne contenant pas de lien vers $1',
 'spam_blanking' => 'Toutes les versions contenant des liens vers $1 sont blanchies',
 'spam_deleting' => 'Toutes les versions contenaient des liens vers $1, suppression',
-'simpleantispam-label' => "Vérification anti-spam.
-Ne '''RIEN''' inscrire ici !",
+'simpleantispam-label' => "Vérification anti-pourriel.
+Ne '''RIEN''' inscrire ici!",
 
 # Info page
 'pageinfo-title' => 'Informations pour « $1 »',
@@ -4126,7 +4126,7 @@ Vous devriez avoir reçu [{{SERVER}}{{SCRIPTPATH}}/COPYING une copie de la Licen
 # Special:Redirect
 'redirect' => 'Redirigé par fichier, utilisateur, ou ID de révision',
 'redirect-legend' => 'Rediriger vers une page ou un fichier',
-'redirect-summary' => "Cette page spéciale redirige vers un fichier (nom donné au fichier), une page (ID attribuée à la révision) ou une page d'utilisateur (identifiant numérique attribué à l'utilisateur).",
+'redirect-summary' => 'Cette page spéciale redirige vers un fichier (nom de fichier fourni), une page (ID de révision fourni) ou une page d’utilisateur (identifiant numérique de l’utilisateur fourni). Utilisation : [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], ou [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit' => 'Valider',
 'redirect-lookup' => 'Recherche :',
 'redirect-value' => 'Valeur :',
index 0087677..af7f653 100644 (file)
@@ -3580,7 +3580,8 @@ You should have received [{{SERVER}}{{SCRIPTPATH}}/COPYING a copy of the GNU Gen
 # Special:Redirect
 'redirect' => 'Widjerfeerang üüb en brükersidj, sidjenwerjuun of datei.',
 'redirect-legend' => 'Widjerfeerang üüb en sidjenwerjuun of datei.',
-'redirect-summary' => 'Det spezial-sidj feert widjer üüb en brükersidj, sidjenwerjuun of datei.',
+'redirect-summary' => 'Detdiar spezial-sidj feert widjer üüb en brükersidj, sidjenwerjuun of datei.
+An det woort so brükt: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], of [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit' => 'Widjer',
 'redirect-lookup' => 'Schük:',
 'redirect-value' => 'Käänang of dateinööm:',
index d7b37f5..c8172dc 100644 (file)
@@ -218,8 +218,6 @@ $messages = array(
 'listingcontinuesabbrev' => 'ar lean.',
 'index-category' => 'Leathanaigh innéacsaithe',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'Maidir leis',
 'article' => 'Leathanach ábhair',
 'newwindow' => '(a osclófar i bhfuinneog nua)',
index a65a09d..a9a2da9 100644 (file)
@@ -42,12 +42,12 @@ $messages = array(
 'tog-hidepatrolled' => 'Falaich mùthaidhean fo fhaire ann an liosta nam mùthaidhean ùra',
 'tog-newpageshidepatrolled' => 'Falaich duilleagan fo fhaire ann an liosta nan duilleagan ùra',
 'tog-extendwatchlist' => "Leudaich an clàr-faire gus an seall e gach mùthadh 's chan ann an fheadhainn as ùire a-mhàin",
-'tog-usenewrc' => "Buidhnich na h-atharraichean a-rèir duilleige sna mùthaidhean ùra agus air a' chlàr-fhaire (feumaidh seo JavaScript)",
+'tog-usenewrc' => "Buidhnich na h-atharraichean a-rèir duilleige sna mùthaidhean ùra agus air a' chlàr-fhaire",
 'tog-numberheadings' => 'Cuir àireamhan ri ceann-sgrìobhaidhean leis fhèin',
-'tog-showtoolbar' => 'Seall am bàr-inneil deasachaidh (feumaidh seo JavaScript)',
-'tog-editondblclick' => 'Tòisich air deasachadh duilleige le briogadh dùbailt (feumaidh seo JavaScript)',
+'tog-showtoolbar' => 'Seall am bàr-inneal deasachaidh',
+'tog-editondblclick' => 'Tòisich air deasachadh duilleige le briogadh dùbailte',
 'tog-editsection' => 'Cuir am comas deasachadh earainn le ceanglaichean [deasaich]',
-'tog-editsectiononrightclick' => "Cuir an comas deasachadh earainn le briogadh deas air tiotal de dh'earrainn (feumaidh seo JavaScript)",
+'tog-editsectiononrightclick' => "Cuir an comas deasachadh earrainn le briogadh deas air tiotal de dh'earrainn",
 'tog-showtoc' => 'Seall an clàr-innse (air duilleagan air a bheil barrachd air 3 ceann-sgrìobhaidhean)',
 'tog-rememberpassword' => "Cuimhnich gu bheil mi air logadh a-steach air a' choimpiutair seo (suas gu $1 {{PLURAL:$1|latha|latha|làithean|latha}})",
 'tog-watchcreations' => "Cuir duilleagan a chruthaicheas mi air a' chlàr-fhaire agam",
@@ -65,7 +65,7 @@ $messages = array(
 'tog-shownumberswatching' => "Nochd àireamh nan cleachdaichean a tha a' cumail sùil air",
 'tog-oldsig' => 'An t-earr-sgrìobhadh làithreach:',
 'tog-fancysig' => 'Làimhsich an t-earr-sgrìobhadh mar wikitext (gun cheangal leis fhèin)',
-'tog-uselivepreview' => 'Cleachd an ro-shealladh beò (feumaidh seo JavaScript) (deuchainneach)',
+'tog-uselivepreview' => 'Cleachd an ro-shealladh beò (deuchainneach)',
 'tog-forceeditsummary' => "Cuir ceist nuair a dh'fhàgas mi gearr-chunntas an deasachaidh bàn",
 'tog-watchlisthideown' => 'Falaich mo mhùthaidhean fhèin air mo chlàr-faire',
 'tog-watchlisthidebots' => 'Falaich mùthaidhean nam bot air mo chlàr-faire',
@@ -78,6 +78,7 @@ $messages = array(
 'tog-showhiddencats' => 'Seall na roinnean falaichte',
 'tog-norollbackdiff' => 'Na dèan diof às dèidh roiligeadh air ais',
 'tog-useeditwarning' => 'Thoir rabhadh dhomh ma bhios mi an impis duilleag deasachaidh fhàgail mus do shàbhail mi na mùthaidhean agam',
+'tog-prefershttps' => 'Cleachd ceangal tèarainte an-còmhnaidh nuair a bhios mi clàraichte a-staigh',
 
 'underline-always' => 'An-còmhnaidh',
 'underline-never' => 'Na dèan seo idir',
@@ -141,6 +142,18 @@ $messages = array(
 'oct' => 'Dàmh',
 'nov' => 'Samh',
 'dec' => 'Dùbh',
+'january-date' => '$1 dhen Fhaoilleach',
+'february-date' => '$1 dhen Ghearran',
+'march-date' => '$1 dhen Mhàrt',
+'april-date' => '$1 dhen Ghiblean',
+'may-date' => '$1 dhen Chèitean',
+'june-date' => '$1 dhen Ògmhios',
+'july-date' => '$1 dhen Iuchar',
+'august-date' => '$1 dhen Lùnastal',
+'september-date' => '$1 dhen t-Sultain',
+'october-date' => '$1 dhen Dàmhair',
+'november-date' => '$1 dhen t-Samhain',
+'december-date' => '$1 dhen Dùbhlachd',
 
 # Categories related messages
 'pagecategories' => '{{PLURAL:$1|Roinn-seòrsa|Roinn-seòrsa|Roinnean-seòrsa|Roinn-seòrsa}}',
@@ -166,7 +179,7 @@ $messages = array(
 'newwindow' => "(a' fosgladh ann an uinneag ùr)",
 'cancel' => 'Sguir dheth',
 'moredotdotdot' => 'Barrachd...',
-'morenotlisted' => 'Barrachd nach eil air an liosta...',
+'morenotlisted' => 'Chan eil an liosta seo iomlan.',
 'mypage' => 'Duilleag',
 'mytalk' => 'Deasbaireachd',
 'anontalk' => 'Conaltradh airson an IP seo',
@@ -222,6 +235,7 @@ $messages = array(
 'create-this-page' => 'Cruthaich an duilleag seo',
 'delete' => 'Sguab às',
 'deletethispage' => 'Sguab às an duilleag seo',
+'undeletethispage' => 'Neo-dhèan sguabadh às na duilleige seo',
 'undelete_short' => "Neo-dhèan sguabadh às de {{PLURAL:$1|dh'aon deasachadh|$1 dheasachadh|$1 deasachaidhean|$1 deasachadh}}",
 'viewdeleted_short' => 'Seall {{PLURAL:$1|aon deasachadh|$1 dheasachadh|$1 deasachaidhean|$1 deasachadh}} a chaidh a sguabadh às',
 'protect' => 'Dìon',
@@ -238,7 +252,7 @@ $messages = array(
 'articlepage' => 'Seall duilleag na susbainte',
 'talk' => 'Deasbaireachd',
 'views' => 'Tadhalan',
-'toolbox' => 'Bogsa-innealan',
+'toolbox' => 'Innealan',
 'userpage' => "Seall duilleag a' chleachdaiche",
 'projectpage' => "Seall duilleag a' phròiseict",
 'imagepage' => 'Seall duilleag an fhaidhle',
@@ -268,7 +282,7 @@ $1",
 # All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).
 'aboutsite' => 'Mu dhèidhinn {{SITENAME}}',
 'aboutpage' => 'Project:Mu dhèidhinn',
-'copyright' => 'Tha susbaint ri làimh fo $1.',
+'copyright' => "Tha susbaint ri làimh fo $1 mur eil an caochladh 'ga innse.",
 'copyrightpage' => '{{ns:project}}:Còraichean lethbhric',
 'currentevents' => 'Cùisean an latha',
 'currentevents-url' => 'Project:Cùisean an latha',
@@ -352,6 +366,12 @@ Gheibh thu liosta nan duilleagan sònraichte 's dligheach aig [[Special:SpecialP
 # General errors
 'error' => 'Mearachd',
 'databaseerror' => 'Mearachd an stòir-dhàta',
+'databaseerror-text' => "Thachair mearachd leis a' cheist a chuireadh dhan stòr-dàta.
+Dh'fhaoidte gu bheil buga sa bhathar-bhog.",
+'databaseerror-textcl' => "Thachair mearachd leis a' cheist a chuireadh dhan stòr-dàta.",
+'databaseerror-query' => 'Ceist: $1',
+'databaseerror-function' => 'Foincsean: $1',
+'databaseerror-error' => 'Mearachd: $1',
 'laggedslavemode' => "'''Rabhadh:''' Faodaidh nach eil ùrachaidhean a rinneadh o chionn ghoirid a' nochdadh san duilleag.",
 'readonly' => 'Stòr-dàta glaiste',
 'enterlockreason' => "Cuir a-steach adhbhar a' ghlais, a' gabhail a-steach tuairmeas air fuasgladh a' ghlais.",
@@ -385,6 +405,7 @@ Faodaidh gun deach a sguabadh às le cuideigin eile mu thràth.',
 'cannotdelete-title' => 'Cha ghabh an duilleag "$1" a sguabadh às',
 'delete-hook-aborted' => 'Sguireadh dhen sguabadh às ri linn dubhain.
 Cha deach adhbhar a thoirt seachad.',
+'no-null-revision' => 'Cha b\' urrainn dhuinn lèirmheas neoinitheach ùr a chruthachadh dhan duilleag "$1"',
 'badtitle' => 'Droch thiotal',
 'badtitletext' => "Bha an duilleag a dh'iarr thu mì-dhligheach, falamh no le tiotal eadar-chànanach no eadar-uici air a dhroch cheangal.
 Faodaidh gu bheil aon no barrachd charactairean ann nach urrainn dhut a chleachdadh ann an tiotalan.",
@@ -428,9 +449,9 @@ Thug an rianaire a ghlais e seachad an t-adhbhar a leanas: "$3".',
 'virus-unknownscanner' => 'sganair bhìorasan neo-aithnichte:',
 
 # Login and logout pages
-'logouttext' => "'''Chaidh do logadh a-mach.'''
-'S urrainn dhut leantainn air adhart a' cleachdadh {{SITENAME}} a chleachdadh gun urra no 's urrainn dhut <span class='plainlinks'>[$1 logadh a-steach a-rithist]</span> mar an dearbh-chleachdaiche no mar chleachdaiche eile.
-Thoir an aire gum bi coltas air cuide dhe na duilleagan mar gum biodh tu air logadh a-steach gus am falamhaich thu tasgadan a' bhrabhsair agad.",
+'logouttext' => "'''Chaidh do chlàradh a-mach.'''
+
+Thoir an aire gum bi coltas air cuid dhe na duilleagan mar gum biodh tu air clàradh a-steach gus am falamhaich thu tasgadan a' bhrabhsair agad.",
 'welcomeuser' => 'Fàilte ort, $1',
 'welcomecreation-msg' => 'Chaidh an cunntas agad a chruthachadh.
 Na dìochuimhnich na [[Special:Preferences|roghainnean agad air {{SITENAME}}]] a ghleusadh dhut fhèin.',
@@ -503,7 +524,7 @@ agus leantainn ort leis an t-seann fhacal-faire.',
 Clàraich a-steach a-rithist nuair a gheibh thu e.',
 'blocked-mailpassword' => "Chaidh bacadh a chur air an t-seòladh IP agad 's chan eil cead deasachaidh agad agus chan urrainn dhut an gleus a chum aiseag an fhacail-fhaire a chleachdadh gus casg a chur air mì-ghnàthachadh.",
 'eauthentsent' => 'Chaidh post-d dearbhaidh a chur dhan phost-d a chaidh ainmeachadh.
-Mus dèid post-d sam bith eile a chur dhan chunntas, feumaidh tu leantainn ris an treòrachadh sa phost-d mar dhearbhadh gur ann agadsa a tha an cunntas.',
+Mus dèid post-d sam bith eile a chur dhan chunntas, feumaidh tu leantainn ris an stiùireadh sa phost-d mar dhearbhadh gur ann agadsa a tha an cunntas.',
 'throttled-mailpassword' => 'Chaidh post-d a chur airson ath-shuidheachadh facail-fhaire mu thràth {{PLURAL:$1|uair|$1 uair|$1 uairean|$1 uair}} a thìde air ais.
 Gus casg a chur air mì-ghnàthachadh, cha chuir sinn ach aon chuimhneachan facail-fhaire gach {{PLURAL:$1|uair|$1 uair|$1 uairean|$1 uair}} a thìde.',
 'mailerror' => "Mearachd a' cur post: $1",
@@ -518,7 +539,7 @@ Cuir a-steach seòladh san fhòrmat cheart no falamhaich an raon sin.",
 'cannotchangeemail' => 'Cha ghabh na puist-d a tha co-cheangailte ri cunntas atharrachadh air an uicipeid seo.',
 'emaildisabled' => 'Chan urrainn dhut puist-d a chur air an làrach seo.',
 'accountcreated' => 'Cunntas cruthaichte',
-'accountcreatedtext' => 'Chaidh an cunntas cleachdaiche airson $1 a chruthachadh.',
+'accountcreatedtext' => 'Chaidh an cunntas cleachdaiche airson [[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|talk]]) a chruthachadh.',
 'createaccount-title' => 'Cruthachadh cunntais airson {{SITENAME}}',
 'createaccount-text' => 'Chruthaich cuideigin cunntas airson a\' phost-d agad air {{SITENAME}} ($4) air a bheil "$2", leis an fhacal-fhaire "$3".
 Bu chòir dhut clàradh a-steach agus am facal-faire agad atharrachadh gu h-ìosal an-dràsta.
@@ -526,7 +547,7 @@ Bu chòir dhut clàradh a-steach agus am facal-faire agad atharrachadh gu h-ìos
 \'S urrainn dhut an teachdaireachd seo a leigeil seachad ma chaidh an cunntas a chruthachadh air mhearachd.',
 'usernamehasherror' => 'Chan fhaod hais a bhith ann an ainm cleachdaiche',
 'login-throttled' => "Dh'fheuch thu ri clàradh a-steach ro thric o chionn ghoirid.
-Fuirich ort mus feuch thu ris a-rithist.",
+Fuirich ort $1 mus feuch thu ris a-rithist.",
 'login-abort-generic' => "Cha do shoirbhich leat leis a' chlàradh a-steach - Chaidh sgur dheth",
 'loginlanguagelabel' => 'Cànan: $1',
 'suspicious-userlogout' => "Chaidh d' iarrtas airson clàradh a-mach a dhiùltadh a chionn 's gu bheil coltas gun deach a chur le brabhsair briste no le progsaidh tasglannaidh.",
@@ -545,8 +566,7 @@ Gus an clàradh a-steach a choileadh, tha agad ri facal-faire ùr a shuidheachad
 'newpassword' => 'Facal-faire ùr',
 'retypenew' => 'Ath-sgrìobh am facal-faire ùr',
 'resetpass_submit' => "Suidhich am facal-faire 's clàraich a-steach",
-'changepassword-success' => "Chaidh am facal-faire agad atharrachadh!
-'Gad chlàradh a-steach an-dràsta...",
+'changepassword-success' => 'Chaidh am facal-faire agad atharrachadh!',
 'resetpass_forbidden' => 'Cha ghabh na faclan-faire atharrachadh',
 'resetpass-no-info' => 'Feumaidh tu clàradh a-steach mus dèan thu inntrigeadh dìreach dhan duilleag seo.',
 'resetpass-submit-loggedin' => 'Atharraich am facal-faire',
@@ -581,7 +601,7 @@ Bu chòir dhut clàradh a-steach agus facal-faire ùr a thaghadh an-dràsta. Ma
 Facal-faire sealach: $2',
 'passwordreset-emailsent' => 'Chaidh post-d airson ath-shuidheachadh an fhacail-fhaire a chur.',
 'passwordreset-emailsent-capture' => 'Chaidh post-d a chum ath-shuidheachadh an fhacail-fhaire a chur agus chì thu sin gu h-ìosal.',
-'passwordreset-emailerror-capture' => "Chaidh post-d a chum ath-shuidheachadh an fhacail-fhaire a ghintinn agus chì thu sin gu h-ìosal ach cha b' urrainn dhuinn a chur dhan chleachdaiche: $1",
+'passwordreset-emailerror-capture' => "Chaidh post-d a chum ath-shuidheachadh an fhacail-fhaire a ghintinn agus chì thu sin gu h-ìosal ach cha b' urrainn dhuinn a chur dhan chleachdaiche {{GENDER:$2|user}}: $1",
 
 # Special:ChangeEmail
 'changeemail' => 'Atharraich am post-d',
@@ -677,7 +697,7 @@ Dh'fhaoidte gun deach a ghluasad no a sguabadh às fhad 's a bha thu a' coimhead
 'accmailtitle' => 'Facal-faire air a chur.',
 'accmailtext' => "Chaidh facal-faire a chruthachadh air thuaiream airson [[User talk:$1|$1]] 's a chur gu $2.
 
-Gabhaidh am facal-faire airson a' chunntais ùir seo atharrachadh air an fo ''[[Special:ChangePassword|atharraich facal-faire]]'' as dèidh do chleachdaiche logadh a-steach.",
+Gabhaidh am facal-faire airson a' chunntais ùir seo atharrachadh air an fo ''[[Special:ChangePassword|atharraich facal-faire]]'' as dèidh dhan chleachdaiche clàradh a-steach.",
 'newarticle' => '(Ùr)',
 'newarticletext' => "Lean thu ri ceangal gu duilleag nach eil ann fhathast.
 Cuir teacs sa bhogsa gu h-ìosal gus an duilleag seo a chruthachadh (seall air [[{{MediaWiki:Helppage}}|duilleag na cobharach]] airson barrachd fiosrachaidh).
@@ -786,7 +806,7 @@ Seo an rud mu dheireadh san loga mar fhiosrachadh dhut:",
 'nocreate-loggedin' => 'Chan eil cead agad duilleagan ùra a chruthachadh.',
 'sectioneditnotsupported-title' => 'Chan eil taic ri deasachadh earrannan',
 'sectioneditnotsupported-text' => 'Chan eil taic ri deasachadh earrannan air an duilleag seo.',
-'permissionserrors' => "Meareachd leis a' chead",
+'permissionserrors' => "Mearachd leis a' chead",
 'permissionserrorstext' => 'Chan eil cead agad sin a dhèanamh air sgàth {{PLURAL:$1|an adhbhair|an $1 adhbhar|nan $1 adhbharan|nan $1 adhbhar}} a leanas:',
 'permissionserrorstext-withaction' => 'Chan eil cead agad airson "$2" air sgàth {{PLURAL:$1|an $1 adhbhair|an $1 adhbhar|nan $1 adhbharan|nan $1 adhbhar}} a leanas:',
 'recreate-moveddeleted-warn' => "'''Rabhadh: Tha thu gu bhith ath-chruthachadh duilleag a chaidh a sguabadh às roimhe.'''
@@ -874,10 +894,10 @@ Feuch is [[Special:Search|lorg duilleagan ùra iomachaidh air an uici]]",
 'rev-showdeleted' => 'seall',
 'revdelete-selected' => "'''{{PLURAL:$2|Lèirmheas|Lèirmheasan}} de [[:$1]] a thagh thu:'''",
 'logdelete-selected' => "'''{{PLURAL:$1|An tachartas loga|Na tachartasan loga}} a thagh thu:'''",
-'revdelete-hide-user' => 'Falaich ainm-cleachdaiche/seòladh IP an deasaiche',
+'revdelete-hide-user' => 'Ainm-cleachdaiche/seòladh IP an deasaiche',
 'revdelete-radio-same' => '(na atharraich)',
-'revdelete-radio-set' => 'Dèan seo',
-'revdelete-radio-unset' => 'Na dèan seo',
+'revdelete-radio-set' => 'Ri fhaicinn',
+'revdelete-radio-unset' => 'Falaichte',
 'revdelete-log' => 'Adhbhar:',
 'revdelete-submit' => 'Cuir air {{PLURAL:$1|an lèirmheas|na lèirmheasan}} a thagh thu',
 'revdel-restore' => 'mùth follaiseachd',
@@ -995,7 +1015,7 @@ Faodaidh gum bi inneacsan susbaint {{SITENAME}} tuilleadh 's sean ge-tà.",
 'prefs-rendering' => 'Coltas',
 'saveprefs' => 'Sàbhail',
 'resetprefs' => 'Falamhaich atharrachaidhean nach deach a shàbhaladh fhathast',
-'restoreprefs' => 'Aisig na roghainnean bunaiteach uile',
+'restoreprefs' => 'Aisig na roghainnean bunaiteach uile (anns gach earrann)',
 'prefs-editing' => "A' deasachadh",
 'rows' => 'Sreathan',
 'columns' => 'Colbhan',
@@ -1038,10 +1058,10 @@ Faodaidh gum bi inneacsan susbaint {{SITENAME}} tuilleadh 's sean ge-tà.",
 Thoir sùil air na tagaichean HTML.',
 'badsiglength' => 'Tha an t-earr-sgrìobhadh agad ro fhada.
 Chan fhaod e a bhith nas fhaide na $1 {{PLURAL:$1|charactar|charactar|caractaran|caractar}}.',
-'yourgender' => 'Gnè:',
-'gender-unknown' => 'Gun innse',
-'gender-male' => 'Fireann',
-'gender-female' => 'Boireann',
+'yourgender' => "Dè a' ghnè a tha annad:",
+'gender-unknown' => "B' fhearr leam gun a bhith 'ga leigeil ris",
+'gender-male' => 'Deasaichidh e duilleagan na h-Uicipeid',
+'gender-female' => 'Deasaichidh i duilleagan na h-Uicipeid',
 'email' => 'Post-d:',
 'prefs-help-email' => "Chan leig thu leas post-dealain a chur ann ach bidh feum air ma dhìochuimhnicheas tu am facal-faire agad 's ma dh'iarras tu fear ùr.",
 'prefs-help-email-others' => "'S urrainn dhut leigeil le daoine eile post-dealain a chur thugad tro cheangal air an duilleag agad.
@@ -1052,7 +1072,7 @@ Chan fhaicear an seòladh fhèin nuair a chuireas cuideigin post-dealain thugad.
 'prefs-signature' => 'Earr-sgrìobhadh',
 'prefs-dateformat' => "Fòrmat a' chinn-là",
 'prefs-timeoffset' => 'Diofar ama',
-'prefs-advancedediting' => 'Roghainnean adhartach',
+'prefs-advancedediting' => 'Roghainnean coitcheann',
 'prefs-advancedrc' => 'Roghainnean adhartach',
 'prefs-advancedrendering' => 'Roghainnean adhartach',
 'prefs-advancedsearchoptions' => 'Roghainnean adhartach',
@@ -1151,7 +1171,7 @@ Chan fhaicear an seòladh fhèin nuair a chuireas cuideigin post-dealain thugad.
 'minoreditletter' => 'b',
 'newpageletter' => 'Ù',
 'boteditletter' => 'bt',
-'rc-enhanced-expand' => 'Seall am mion-fhiosrachadh (feumaidh seo JavaScript)',
+'rc-enhanced-expand' => 'Seall am mion-fhiosrachadh',
 'rc-enhanced-hide' => 'Cuir am mion-fhiosrachadh am falach',
 
 # Recent changes linked
@@ -1335,9 +1355,11 @@ Seall air $2 airson clàr de dhuilleagan a chaidh a sguabadh às o chionn ghoiri
 'deleteotherreason' => 'Adhbhar eile/a bharrachd:',
 'deletereasonotherlist' => 'Adhbhar eile',
 'deletereason-dropdown' => "*Adhbharan cumanta airson sguabadh às
-** Dh'iarr an t-ùghdar e
+** Spama
+** Milleadh
 ** Tha e a' briseadh na còrach-lethbhreac
-** Milleadh",
+** Dh'iarr an t-ùghdar e
+** Ath-threòrachadh briste",
 'delete-edit-reasonlist' => 'Deasaich adhbharan sguabadh às',
 
 # Rollback
@@ -1395,7 +1417,7 @@ Seo roghainnean làithreach na duilleige '''$1''':",
 'contributions' => "Mùthaidhean a' {{GENDER:$1|chleachdaiche}}",
 'contributions-title' => 'Mùthaidhean a rinn $1',
 'mycontris' => 'Mùthaidhean',
-'contribsub2' => 'Do $1 ($2)',
+'contribsub2' => 'Airson {{GENDER:$3|$1}} ($2)',
 'uctop' => '(làithreach)',
 'month' => 'On mhìos (agus na bu tràithe):',
 'year' => 'On bhliadhna (agus na bu tràithe):',
index df89c18..40c829b 100644 (file)
@@ -784,7 +784,7 @@ continuar a utilizar o seu contrasinal vello.',
 'passwordsent' => 'Enviouse un contrasinal novo ao enderezo de correo electrónico rexistrado de "$1".
 Por favor, acceda ao sistema de novo tras recibilo.',
 'blocked-mailpassword' => 'O seu enderezo IP está bloqueado e ten restrinxida a edición de artigos. Tampouco se lle permite usar a función de recuperación do contrasinal para evitar abusos do sistema.',
-'eauthentsent' => 'Envióuselle un correo electrónico de confirmación ao enderezo mencionado.
+'eauthentsent' => 'Envióuselle un correo electrónico de confirmación ao enderezo especificado.
 Antes de que se lle envíe calquera outro correo a esta conta terá que seguir as instrucións que aparecen nesa mensaxe para confirmar que a conta é realmente súa.',
 'throttled-mailpassword' => 'Enviouse un correo electrónico de restablecemento do contrasinal {{PLURAL:$1|na última hora|nas últimas $1 horas}}.
 Para evitar o abuso do sistema só se enviará unha mensaxe de restablecemento cada {{PLURAL:$1|hora|$1 horas}}.',
@@ -3958,7 +3958,7 @@ Debería recibir [{{SERVER}}{{SCRIPTPATH}}/COPYING unha copia da licenza públic
 # Special:Redirect
 'redirect' => 'Redirixir por nome de ficheiro, ID de usuario ou ID de revisión',
 'redirect-legend' => 'Redirixir a un ficheiro ou unha páxina',
-'redirect-summary' => 'Esta páxina especial redirixe cara a un ficheiro (dado o nome), unha páxina (dado o ID dunha revisión) ou unha páxina de usuario (dado o ID dun usuario).',
+'redirect-summary' => 'Esta páxina especial redirixe cara a un ficheiro (dado o nome), unha páxina (dado o ID dunha revisión) ou unha páxina de usuario (dado o ID dun usuario). Utilización: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], or [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit' => 'Continuar',
 'redirect-lookup' => 'Procurar:',
 'redirect-value' => 'Valor:',
index 1e34d5b..6716266 100644 (file)
@@ -335,7 +335,7 @@ $messages = array(
 'articlepage' => 'Syte',
 'talk' => 'Diskussion',
 'views' => 'Wievylmol agluegt',
-'toolbox' => 'Wärkzügkäschtli',
+'toolbox' => 'Wärchzyyg',
 'userpage' => 'Benutzersyte',
 'projectpage' => 'Projektsyte azeige',
 'imagepage' => 'Dateisyte',
@@ -937,8 +937,8 @@ Erklärig: (aktuell) = Underschid zu jetz,
 (vorane) = Underschid zur alte Version, <strong>K</strong> = chlyni Änderig',
 'history-fieldset-title' => 'Suech in dr Versionsgschicht',
 'history-show-deleted' => 'nume gleschti Versione',
-'histfirst' => 'Eltischti',
-'histlast' => 'ischti',
+'histfirst' => 'eltischti',
+'histlast' => 'neischti',
 'historysize' => '({{PLURAL:$1|1 Byte|$1 Bytes}})',
 'historyempty' => '(läär)',
 
@@ -1452,7 +1452,7 @@ Des cha nimmi ruckgängig gmacht wäre.',
 'rc_categories_any' => 'Alli',
 'rc-change-size-new' => '$1 {{PLURAL:$1|Byte|Byte}} no dr Änderig',
 'newsectionsummary' => 'Neje Abschnitt /* $1 */',
-'rc-enhanced-expand' => 'Detail aazeige (brucht JavaScript)',
+'rc-enhanced-expand' => 'Detail aazeige',
 'rc-enhanced-hide' => 'Detail verstecke',
 'rc-old-title' => 'urspringlig aaglait as „$1“',
 
@@ -2309,7 +2309,7 @@ $1',
 'contributions' => '{{GENDER:$1|Benutzer-Byträg}}',
 'contributions-title' => 'Benutzerbyytreg vu „$1“',
 'mycontris' => 'Myyni Byyträg',
-'contribsub2' => 'Für $1 ($2)',
+'contribsub2' => 'Vu {{GENDER:$3|$1}} ($2)',
 'nocontribs' => 'S sin keini Benutzerbyytreg mit däne Kriterie gfunde wore.',
 'uctop' => '(aktuell)',
 'month' => 'u Monet:',
index 69692e8..864ffa5 100644 (file)
@@ -291,8 +291,6 @@ $messages = array(
 'noindex-category' => 'અનુક્રમણિકા નહી બનાવેલા પાનાં',
 'broken-file-category' => 'ફાઇલોની ત્રૂટક કડીઓવાળાં પાનાં',
 
-'linkprefix' => '/^(.*?)((?:[a-zA-Z\\x80-\\xff]|ક્|ખ્|ગ્|ઘ્|ચ્|છ્|જ્|ઝ્|ટ્|ઠ્|ડ્|ઢ્|ણ્|ત્|થ્|દ્|ધ્|ન્|પ્|ફ્|બ્|ભ્|મ્|ય્|ર્|લ્|વ્|સ્|શ્|ષ્|હ્|ળ્|ક્ષ્|જ્ઞ્|અ|આ|ઇ|ઈ|ઉ|ઊ|એ|ઐ|ઓ|ઔ|અં|અઃ|અઁ|ઍ|ઑ|ઋ|ઁ|઼|।|્|ા|િ|ી|ુ|ૂ|ે|ૈ|ો|ૌ|ં|ઃ|ૅ|ૉ|ૃ)+)$/sD',
-
 'about' => 'વિષે',
 'article' => 'લેખનું પાનું',
 'newwindow' => '(નવા પાનામાં ખુલશે)',
index ffa2ae2..aee4c07 100644 (file)
@@ -890,7 +890,7 @@ $2',
 'passwordsent' => 'סיסמה חדשה נשלחה לכתובת הדואר האלקטרוני הרשומה עבור "$1".
 אנא היכנסו חזרה לאתר אחרי שתקבלו אותה.',
 'blocked-mailpassword' => 'כתובת ה־IP שלכם חסומה מעריכה, ולפיכך אינכם מורשים להשתמש באפשרות שחזור הסיסמה כדי למנוע ניצול לרעה של התכונה.',
-'eauthentsent' => '×\93×\95×\90\9c ×\90×\99×\9e×\95ת × ×©×\9c×\97 ×\9c×\9bת×\95×\91ת ×\94×\93×\95×\90\9c ×©×§×\91עת.
+'eauthentsent' => '×\93×\95×\90\9c ×\90×\99×\9e×\95ת × ×©×\9c×\97 ×\9c×\9bת×\95×\91ת ×\94×\93×\95×\90\9c ×©×¦×\95×\99× ×\94.
 לפני שדברי דוא"ל אחרים יישלחו לחשבון הזה, יהיה עליך לפעול לפי ההוראות בדוא"ל, כדי לאשר שהחשבון אכן שייך לך.',
 'throttled-mailpassword' => 'כבר נשלח דוא"ל לאיפוס הסיסמה ב{{PLURAL:$1|שעה האחרונה|שעתיים האחרונות|־$1 השעות האחרונות}}.
 כדי למנוע ניצול לרעה, יכול להישלח רק דוא"ל אחד כזה בכל {{PLURAL:$1|שעה|שעתיים|$1 שעות}}.',
@@ -4107,7 +4107,7 @@ $5
 # Special:Redirect
 'redirect' => 'הפניה לפי שם קובץ, מספר משתמש או מספר גרסה',
 'redirect-legend' => 'הפניה לקובץ או לדף',
-'redirect-summary' => 'דף מיוחד זה מפנה לקובץ (בהינתן שם הקובץ), לדף (בהינתן מספר גרסה), או לדף משתמש (בהינתן מספר משתמש).',
+'redirect-summary' => 'דף מיוחד זה מפנה לקובץ (בהינתן שם הקובץ), לדף (בהינתן מספר גרסה), או לדף משתמש (בהינתן מספר משתמש). דוגמאות לשימוש: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], או [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit' => 'מעבר',
 'redirect-lookup' => 'סוג:',
 'redirect-value' => 'ערך:',
index aa48902..54d9372 100644 (file)
@@ -674,8 +674,8 @@ e continuar a usar le contrasigno original.',
 'passwordsent' => 'Un nove contrasigno ha essite inviate al adresse de e-mail registrate pro "$1".
 Per favor aperi session de novo post reciper lo.',
 'blocked-mailpassword' => 'Tu adresse IP es blocate de facer modificationes, e pro impedir le abuso, le uso del function pro recuperar contrasignos es equalmente blocate.',
-'eauthentsent' => 'Un e-mail de confirmation ha essite inviate al adresse de e-mail specificate.
-Pro poter reciper altere e-mail a iste conto, tu debe sequer le instructiones in iste e-mail pro confirmar que le conto es realmente tue.',
+'eauthentsent' => 'Un message de confirmation ha essite inviate al adresse de e-mail specificate.
+Pro permitter que le systema invia altere messages a iste adresse, tu debe sequer le instructiones in iste message pro confirmar que le adresse es realmente tue.',
 'throttled-mailpassword' => 'Un message pro le reinitialisation del contrasigno ha jam essite inviate intra le ultime {{PLURAL:$1|hora|$1 horas}}.
 Pro prevenir le abuso, solmente un message pro le reinitialisation del contrasigno essera inviate per {{PLURAL:$1|hora|$1 horas}}.',
 'mailerror' => 'Error de inviar e-mail: $1',
@@ -1139,15 +1139,15 @@ Altere administratores in {{SITENAME}} continuara a poter acceder al contento ce
 * Informationes personal inappropriate
 *: ''adresses de domicilio e numeros de telephono, numeros de securitate social, etc.''",
 'revdelete-legend' => 'Definir restrictiones de visibilitate',
-'revdelete-hide-text' => 'Celar le texto del version',
+'revdelete-hide-text' => 'Texto del version',
 'revdelete-hide-image' => 'Celar le contento del file',
 'revdelete-hide-name' => 'Celar action e objectivo',
-'revdelete-hide-comment' => 'Celar le summario del modification',
-'revdelete-hide-user' => 'Celar le nomine de usator o adresse IP del modificator',
+'revdelete-hide-comment' => 'Summario del modification',
+'revdelete-hide-user' => 'Nomine de usator o adresse IP del modificator',
 'revdelete-hide-restricted' => 'Supprimer le datos a administratores assi como a alteres',
 'revdelete-radio-same' => '(non cambiar)',
-'revdelete-radio-set' => 'Si',
-'revdelete-radio-unset' => 'No',
+'revdelete-radio-set' => 'Visibile',
+'revdelete-radio-unset' => 'Celate',
 'revdelete-suppress' => 'Supprimer le datos a administratores assi como a alteres',
 'revdelete-unsuppress' => 'Eliminar restrictiones super versiones restaurate',
 'revdelete-log' => 'Motivo:',
@@ -3868,7 +3868,7 @@ Vos deberea haber recipite [{{SERVER}}{{SCRIPTPATH}}/COPYING un exemplar del Lic
 # Special:Redirect
 'redirect' => 'Rediriger per nomine de file, ID de usator o ID de version',
 'redirect-legend' => 'Rediriger a un file o pagina',
-'redirect-summary' => 'Iste pagina special redirige a un file (si es date le nomine de un file), a un pagina (si es date un ID de version) o a un pagina de usator (si es date un ID de usator numeric).',
+'redirect-summary' => 'Iste pagina special redirige a un file (si es date le nomine de un file), a un pagina (si es date un ID de version) o a un pagina de usator (si es date un ID de usator numeric). Usage: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]] o [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit' => 'Va',
 'redirect-lookup' => 'Cercar:',
 'redirect-value' => 'Valor:',
index cee748d..dd1a209 100644 (file)
@@ -481,8 +481,6 @@ $messages = array(
 'broken-file-category' => 'Halaman dengan gambar rusak',
 'categoryviewer-pagedlinks' => '($1) ($2)',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'Tentang',
 'article' => 'Halaman isi',
 'newwindow' => '(buka di jendela baru)',
index 2e25bdd..8b76026 100644 (file)
@@ -231,6 +231,7 @@ $specialPageAliases = array(
 $separatorTransformTable = array( ',' => '.', '.' => ',' );
 $linkPrefixExtension = true;
 $linkTrail = '/^([áðéíóúýþæöa-z-–]+)(.*)$/sDu';
+$linkPrefixCharset = 'áÁðÐéÉíÍóÓúÚýÝþÞæÆöÖA-Za-z–-';
 
 $messages = array(
 # User preference toggles
@@ -371,8 +372,6 @@ $messages = array(
 'noindex-category' => 'Óraðaðar skrár',
 'broken-file-category' => 'Síður með brotna myndatengla',
 
-'linkprefix' => '/^(.*?)([áÁðÐéÉíÍóÓúÚýÝþÞæÆöÖA-Za-z-–]+)$/sDu',
-
 'about' => 'Um',
 'article' => 'Efnissíða',
 'newwindow' => '(opnast í nýjum glugga)',
index 705b376..3bcb31e 100644 (file)
@@ -311,7 +311,7 @@ $messages = array(
 'tog-watchdeletion' => 'Aggiungi le pagine e i file cancellati agli osservati speciali',
 'tog-minordefault' => 'Indica ogni modifica come minore (solo come predefinito)',
 'tog-previewontop' => "Mostra l'anteprima sopra la casella di modifica e non sotto",
-'tog-previewonfirst' => "Mostra l'anteprima per la prima modifica",
+'tog-previewonfirst' => "Mostra l'anteprima almeno una volta prima di salvare",
 'tog-nocache' => 'Disabilita la cache delle pagine del browser',
 'tog-enotifwatchlistpages' => 'Inviami una email quando viene modificata una pagina o un file presente tra gli osservati speciali',
 'tog-enotifusertalkpages' => 'Segnalami via e-mail le modifiche alla mia pagina di discussione',
@@ -805,8 +805,8 @@ Se non sei stato tu a fare la richiesta, oppure hai ritrovato la password e non
 'passwordsent' => 'Una nuova password è stata inviata all\'indirizzo e-mail registrato per l\'utente "$1".
 Per favore, effettua un accesso non appena la ricevi.',
 'blocked-mailpassword' => 'Per prevenire abusi, non è consentito usare la funzione "Invia nuova password" da un indirizzo IP bloccato.',
-'eauthentsent' => "Un messaggio e-mail di conferma è stato spedito all'indirizzo indicato.
-Per abilitare l'invio di messaggi e-mail per questo accesso è necessario seguire le istruzioni che vi sono indicate, in modo da confermare che si è i legittimi proprietari dell'indirizzo",
+'eauthentsent' => "Un messaggio email di conferma è stato spedito all'indirizzo indicato.
+Per abilitare l'invio di messaggi email per questo utente è necessario seguire le istruzioni che vi sono indicate, in modo da confermare che si è i legittimi proprietari dell'indirizzo.",
 'throttled-mailpassword' => 'Una email di reimpostazione della password è già stata inviata da meno di {{PLURAL:$1|1 ora|$1 ore}}.
 Per prevenire abusi, la funzione di reimpostazione della password può essere usata solo una volta ogni {{PLURAL:$1|ora|$1 ore}}.',
 'mailerror' => "Errore nell'invio del messaggio: $1",
@@ -3900,7 +3900,8 @@ Questo programma deve essere distribuito assieme ad [{{SERVER}}{{SCRIPTPATH}}/CO
 # Special:Redirect
 'redirect' => 'Reindirizzamento da file, utente o ID versione',
 'redirect-legend' => 'Reindirizza a un file o una pagina',
-'redirect-summary' => 'Questa pagina speciale reindirizza a un file (specificando il nome del file), a una pagina (specificando un ID di versione) o a un utente (specificando un ID utente numerico).',
+'redirect-summary' => 'Questa pagina speciale reindirizza a un file (specificando il nome del file), a una pagina (specificando un ID di versione) o a un utente (specificando un ID utente numerico).
+Esempi: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], or [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit' => 'Vai',
 'redirect-lookup' => 'Ricerca:',
 'redirect-value' => 'Valore:',
index 69e841b..7e49604 100644 (file)
@@ -4262,7 +4262,7 @@ MediaWikiは、有用であることを期待して配布されていますが
 # Special:Redirect
 'redirect' => 'ファイル名、利用者ID、版IDでの転送',
 'redirect-legend' => 'ファイルまたはページヘの転送',
-'redirect-summary' => 'この特別ページは、ファイル (ファイル名を指定)、ページ (版 ID を指定)、利用者ページ (利用者 ID を整数で指定) に転送されます。',
+'redirect-summary' => 'この特別ページは、ファイル (ファイル名を指定)、ページ (版 ID を指定)、利用者ページ (利用者 ID を整数で指定) に転送されます。使用例: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], or [[{{#Special:Redirect}}/user/101]]',
 'redirect-submit' => '実行',
 'redirect-lookup' => '検索の種類:',
 'redirect-value' => '値:',
index a4cd57a..dfcdb94 100644 (file)
@@ -310,8 +310,6 @@ $messages = array(
 'broken-file-category' => 'გვერდები ფაილების არასწორი ბმულებით',
 'categoryviewer-pagedlinks' => '($1) ($2)',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'შესახებ',
 'article' => 'სტატია',
 'newwindow' => '(ახალ ფანჯარაში)',
index 147e19a..b1314d1 100644 (file)
@@ -151,6 +151,7 @@ $dateFormats = array(
 );
 
 $linkTrail = "/^((?:[a-zıʼ’“»]|'(?!'))+)(.*)$/sDu";
+$linkPrefixCharset = 'a-zıA-Zİ\\x80-\\xff';
 
 $messages = array(
 # User preference toggles
@@ -264,8 +265,6 @@ $messages = array(
 'category-file-count-limited' => "Usı kategoriyada to'mendegi {{PLURAL:$1|fayl|$1 fayl}} bar.",
 'listingcontinuesabbrev' => 'dawamı',
 
-'linkprefix' => '/^(.*?)([a-zıA-Zİ\\x80-\\xff]+)$/sDu',
-
 'about' => 'Haqqında',
 'article' => "Mag'lıwmat beti",
 'newwindow' => "(jan'a aynada)",
index 8f18ce2..36c0437 100644 (file)
@@ -177,8 +177,6 @@ $messages = array(
 'noindex-category' => 'Pelê bêendeksıni',
 'broken-file-category' => 'Peli be gıreunê dosyeunê sıkıtau',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'Heqa',
 'article' => 'Pela tedeesteyu',
 'newwindow' => '(zerrê pençerê dê newey de beno ra)',
index c4e951b..41360e7 100644 (file)
@@ -308,7 +308,7 @@ $messages = array(
 'tog-prefershttps' => 'ប្រើប្រាស់ការតភ្ជាប់មានសុវត្ថិភាពជានិច្ចពេលកត់ឈ្មោះចូល',
 
 'underline-always' => 'ជានិច្ច',
-'underline-never' => 'á\9e\80á\9e»á\9f\86á\9e¢á\9f\84យសោះ',
+'underline-never' => 'á\9e\80á\9e»á\9f\86á\9e²á\9f\92យសោះ',
 'underline-default' => 'តាមលំនាំដើមនៃ​កម្មវិធី​រុករក​',
 
 # Font style option in Special:Preferences
@@ -402,8 +402,6 @@ $messages = array(
 'broken-file-category' => 'ទំព័រទាំងឡាយដែលដាច់តំណភ្ជាប់',
 'categoryviewer-pagedlinks' => '($1) ($2)',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'អំពី',
 'article' => 'មាតិកាអត្ថបទ',
 'newwindow' => '(បើក​លើ​បង្អួច​ថ្មី)',
@@ -482,7 +480,7 @@ $messages = array(
 'articlepage' => 'មើលខ្លឹមសារទំព័រ​',
 'talk' => 'ការពិភាក្សា',
 'views' => 'គំហើញ',
-'toolbox' => 'ប្រអប់​ឧបករណ៍',
+'toolbox' => '​ឧបករណ៍',
 'userpage' => 'មើលទំព័រអ្នកប្រើប្រាស់',
 'projectpage' => 'មើល​ទំព័រគម្រោង',
 'imagepage' => 'មើល​ទំព័រ​ឯកសារ',
@@ -602,6 +600,7 @@ $1',
 # General errors
 'error' => 'មានបញ្ហា',
 'databaseerror' => 'មូលដ្ឋានទិន្នន័យមានបញ្ហា',
+'databaseerror-error' => 'កំហុស៖ $1',
 'laggedslavemode' => "'''ប្រយ័ត្ន៖''' ទំព័រនេះ​ប្រហែលជាគ្មានព័ត៌មានទាន់សម័យទេ។",
 'readonly' => 'មូលដ្ឋានទិន្នន័យត្រូវបានចាក់សោ',
 'enterlockreason' => 'សូមផ្ដល់ហេតុផលសម្រាប់ការជាប់សោ ព្រមទាំងកាលបរិច្ឆេទដោះសោវិញ',
@@ -724,6 +723,7 @@ $2',
 'userlogin-resetpassword-link' => 'ស្ដារពាក្យសម្ងាត់របស់អ្នក',
 'helplogin-url' => 'Help:ការកត់ឈ្មោះចូល',
 'userlogin-helplink' => '[[{{MediaWiki:helplogin-url}}|ជំនួយក្នុងការកត់ឈ្មោះចូល]]',
+'userlogin-createanother' => 'បង្កើតគណនីមួយទៀត',
 'createacct-join' => 'បំពេញព័ត៌មានរបស់អ្នកខាងក្រោម។',
 'createacct-another-join' => 'បញ្ចូលព័ត៌មានគណនីថ្មីខាងក្រោម។',
 'createacct-emailrequired' => 'អាសយដ្ឋានអ៊ីមែល',
index 7098643..c3022f4 100644 (file)
@@ -881,7 +881,7 @@ $2',
 비밀번호를 받고 다시 로그인해 주세요.',
 'blocked-mailpassword' => '당신의 IP 주소는 편집을 할 수 없게 차단되어 있어서 악용하지 못하도록 비밀번호 되살리기 기능 사용이 금지됩니다.',
 'eauthentsent' => '입력한 이메일로 확인 이메일을 보냈습니다.
-ê²\8cì \95ì\97\90ì\84\9c ë\8b¤ë¥¸ ì\9d´ë©\94ì\9d¼ë¡\9c ë³´ë\82´ê¸° ì \84ì\97\90 ì\9d´ë©\94ì\9d¼ ë\82´ì\9a©ì\9d\98 ì§\80ì\8b\9cë\8c\80ë¡\9c ê³\84ì \95 í\99\95ì\9d¸ ì \88차를 ì\8b¤í\96\89í\95´ ì£¼ì\8b­ì\8b\9cì\98¤.',
+ë\8b¤ë¥¸ ëª¨ë\93  í\98\95í\83\9cì\9d\98 ì\9d´ë©\94ì\9d¼ì\9d\84 ë\8b¹ì\8b ì\9d\98 ê³\84ì \95ì\9c¼ë¡\9c ë³´ë\82´ê¸° ì \84ì\97\90, ê³\84ì \95ì\9d´ ì \95ë§\90 ë\8b¹ì\8b ì\9d\98 ê²\83ì\9d¸ì§\80 í\99\95ì\9d¸í\95\98기 ì\9c\84í\95´ ì\9d´ë©\94ì\9d¼ ë\82´ì\9a©ì\9d\98 ì§\80ì\8b\9cë\8c\80ë¡\9c ê³\84ì \95 í\99\95ì\9d¸ ì \88차를 ì\8b¤í\96\89í\95´ ì£¼ì\85\94ì\95¼ í\95©ë\8b\88ë\8b¤.',
 'throttled-mailpassword' => '비밀번호 재설정 이메일을 이미 최근 {{PLURAL:$1|$1시간}} 안에 보냈습니다.
 악용을 방지하기 위해 비밀번호 재설정 메일은 {{PLURAL:$1|$1시간}}마다 오직 하나씩만 보낼 수 있습니다.',
 'mailerror' => '메일 보내기 오류: $1',
@@ -4094,7 +4094,7 @@ $5
 # Special:Redirect
 'redirect' => '파일, 사용자나 판 ID별 넘겨주기',
 'redirect-legend' => '파일이나 문서로 넘겨주기',
-'redirect-summary' => '이 특수 문서는 파일(파일 이름을 지정), 문서(판 ID를 지정)나 사용자 문서(사용자 ID를 정수로 지정)로 넘겨줍니다.',
+'redirect-summary' => '이 특수 문서는 파일(파일 이름을 지정), 문서(판 ID를 지정)나 사용자 문서(사용자 ID를 정수로 지정)로 넘겨줍니다. 사용법: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], 혹은 [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit' => '찾기',
 'redirect-lookup' => '찾을 종류:',
 'redirect-value' => '값:',
index a9e2266..8d41d92 100644 (file)
@@ -431,7 +431,7 @@ $messages = array(
 'articlepage' => 'Aanluure wat op dä Sigg drop steiht',
 'talk' => 'Klaafe',
 'views' => 'Aansichte',
-'toolbox' => 'Werkzüch',
+'toolbox' => 'Wärkzüsch',
 'userpage' => 'Däm Metmaacher sing Sigg aanluure',
 'projectpage' => 'De Projeksigg aanluure',
 'imagepage' => 'De Sigg övver die Dattei aanluure',
@@ -461,7 +461,7 @@ $1',
 # All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).
 'aboutsite' => 'Övver {{GRAMMAR:Akkusativ|{{ucfirst:{{SITENAME}}}}}}',
 'aboutpage' => 'Project:Övver {{GRAMMAR:Akkusativ|{{ucfirst:{{SITENAME}}}}}}',
-'copyright' => 'Dä Enhald steiht unger de $1.',
+'copyright' => 'Dä Enhald steiht unger dä Lezänz $1, ußer wann ußdröklesch jäd anders jesaad es.',
 'copyrightpage' => '{{ns:project}}:Lizenz',
 'currentevents' => 'Et Neuste',
 'currentevents-url' => 'Project:Et Neuste',
@@ -685,7 +685,9 @@ Wann De wells, künnts De Ding [[Special:Preferences|Enschtällonge aanpaße]].'
 'userlogin-resetpassword-link' => 'Paßwoot verjäße?',
 'helplogin-url' => 'Help:Övver et Enlogge',
 'userlogin-helplink' => '[[{{MediaWiki:helplogin-url}}|Hölp bem Enlogge]]',
+'userlogin-createanother' => 'Donn ene zohsäzlejje Zohjang aanlääje',
 'createacct-join' => 'Jiv Ding Daate en:',
+'createacct-another-join' => 'Maach de nüüdeje Aanjaabe för dä neue Zohjaang.',
 'createacct-emailrequired' => 'Ding Addräß för de <i lang="en">e-mail</i>',
 'createacct-emailoptional' => 'Ding Addräß för de <i lang="en">e-mail</i>, kann fott bliive',
 'createacct-email-ph' => 'Jiv Ding Addräß för de <i lang="en">e-mail</i> en!',
@@ -772,7 +774,7 @@ Ene schöne Jroß vun {{GRAMMAR:Dat|{{SITENAME}}}}.
 'noemailcreate' => 'Do moß en jöltijje Adräß för Ding <i lang="en">e-mail</i> aanjävve',
 'passwordsent' => 'E neu Passwood es aan de E-Mail Adress vun däm Metmaacher „$1“ ungerwähs. Meld dich domet aan, wann De et häs. Dat ahle Passwood bliev erhalde un kann och noch jebruch wääde, bes dat De Dich et eetste Mol met däm Neue enjelogg häs.',
 'blocked-mailpassword' => 'Ding IP Adress es blockeet.',
-'eauthentsent' => 'En <i lang="en">e-mail</i> es jäz ungerwähs aan di Adräß, di en de Enschtällonge schteiht. Ih dat <i lang="en">e-mails</i> övver {{GRAMMAR:Genitiv iere male|{{ucfirst:{{SITENAME}}}}}} <i lang="en">e-mail</i>-Knopp verscheck wääde künne, moß de <i lang="en">e-mail</i>-Adräß eets ens beschtäätesch woode sin. Wat mer doför maache moß, schteiht en dä <i lang="en">e-mail</i> dren, di jrad avjescheck woode es.',
+'eauthentsent' => 'En <i lang="en">e-mail</i> es jäz ungerwähs aan di Adräß en de Enschtällonge. Ih dat mieh <i lang="en">e-mails</i> verscheck wääde künne, moß mer maache, wat en dä <i lang="en">e-mail</i> dren schteiht, öm ze beschtääteje, dat di Adräß schtemmp.',
 'throttled-mailpassword' => 'En Erennerung för di Passwood es alld ongerwähs, un mieh wi eimol en {{PLURAL:$1|der Schtond|$1 Schtonde|nidd ens ener Schtond}} dommer kein schecke.',
 'mailerror' => 'Fähler beim E-Mail Verschecke: $1.',
 'acct_creation_throttle_hit' => '<b>Schad.</b>
@@ -806,6 +808,9 @@ Waad e Wielsche ävver $1, ih dat De et wider versöhks.',
 'loginlanguagelabel' => 'Sproch: $1',
 'suspicious-userlogout' => "Do bes '''nit''' ußjelogg.
 Et süht us, wi wann ene kappodde Brauser udder <i lang=\"en\">proxy</i>ẞööver met Zwescheschpeischer noh däm Ußlogge jefrooch hät.",
+'createacct-another-realname-tip' => 'Dä reschteje Nahme kam_mer fott lohße.
+
+Wann dä aanjejovve es, weet_e jebruch, öm öffentlesch de Schriiver för Beidrääsch ze nänne.',
 
 # Email sending
 'php-mail-error-unknown' => 'Nit bekannte Fähler met dä Funxjohn <code lang="en">mail()</code> vum PHP',
@@ -821,7 +826,7 @@ Et süht us, wi wann ene kappodde Brauser udder <i lang=\"en\">proxy</i>ẞööv
 'newpassword' => 'Et neue Passwood:',
 'retypenew' => 'Noch ens dat neue Passwood:',
 'resetpass_submit' => 'E neu Zweschepasswood övvermeddele un aanmellde',
-'changepassword-success' => 'Passwood jeändert. Jetz küdd_et Enlogge&nbsp;…',
+'changepassword-success' => 'Et Paßwood es jeändert.',
 'resetpass_forbidden' => 'E Passwoot kann nit jeändert wääde.',
 'resetpass-no-info' => 'Do mööts ad enjelogg sin, öm tiräk op di Sigg jonn ze dörve',
 'resetpass-submit-loggedin' => 'Passwood tuusche',
@@ -834,6 +839,8 @@ Do häs Der enzwesche e neu Zweschepaßwood jehollt.',
 
 # Special:PasswordReset
 'passwordreset' => 'Et Paßwoot zeröck säze',
+'passwordreset-text-one' => 'Föll dat Fommolaa uß, öm Ding Paßwoot ze ändere.',
+'passwordreset-text-many' => '{{PLURAL:$1|Föll ei Fäld en däm Fommolaa uß, öm Ding Paßwoot ze ändere.}}',
 'passwordreset-legend' => 'Et Paßwoot zeröck säze',
 'passwordreset-disabled' => 'Et Paßwoot zeröck ze säze es heh em Wiki afjeschalldt.',
 'passwordreset-emaildisabled' => 'Heh dat Wiki määt nix met <i lang="en">e-mail</i>!',
@@ -886,6 +893,9 @@ Do moß Ding Paßwoot enjävve, öm Ding Änderong ze bschtäätejje.',
 'changeemail-submit' => 'Lohß jonn!',
 'changeemail-cancel' => 'Ophüre',
 
+# Special:ResetTokens
+'resettokens-token-label' => '$1 (Em Momang es et: $2)',
+
 # Edit page toolbar
 'bold_sample' => 'Fätte Schreff',
 'bold_tip' => 'Fätte Schreff',
@@ -1129,13 +1139,14 @@ Ene Jrond weße mer nit.',
 'edit-gone-missing' => 'Kunnt di Sigg nit änndere. Se schingk verschwunde un weed fottjeschemeße woode sin.',
 'edit-conflict' => 'Dubbelt beärbeit.',
 'edit-no-change' => 'Do häs ja nix aan dä Sigg jeändert, do dom_mer och nix domet.',
+'postedit-confirmation' => 'Ding Änderunge sin nit faßjehallde.',
 'edit-already-exists' => 'Kunnt kei neu Sigg aanlääje. Di Sigg jidd_et ald.',
 'defaultmessagetext' => 'Dä standaadmäßije Tex',
 'content-failed-to-parse' => 'Et wohr nit müjjelesch, dä Enhalld met däm <i lang="en">MIME-Typ</i> $2 för en Dattei met $1 dren ze verwooschte: $3.',
 'invalid-content-data' => 'Di Daate en dä Sigg sen onjöltesch.',
 'content-not-allowed-here' => 'Ene Enhalld vun dä Zoot „$1“ es op dä Sigg „[[$2]]“ nit zohjelohße.',
-'editwarning-warning' => 'Wann de vun hee dä Sigg fott jeihß, doh künnte all Ding Änderunge aan dä Sigg verschött jonn.
-Do kanns heh di Warnung affschallde, wann de aanjemelldt un enjelogg bes, dann kriß de se nieh mieh wider. Jangk doför en dä Affschnett „{{int:prefs-editing}}“ en Dinge Enshtellunge.',
+'editwarning-warning' => 'Wann de vun hee dä Sigg fott jeihß, doh künnte all Ding Änderonge aan dä Sigg verschött jonn.
+Do kanns heh di Warnung affschallde, wann de aanjemelldt un enjelogg bes, dann kriß de se nieh mieh wider. Jangk doför en dä Afschnett „{{int:prefs-editing}}“ en Dinge Enschtellonge.',
 
 # Content models
 'content-model-wikitext' => 'Wikitäx',
@@ -1155,7 +1166,7 @@ Do kanns heh di Warnung affschallde, wann de aanjemelldt un enjelogg bes, dann k
 'parser-template-loop-warning' => 'Schablon roofe sesch em Kringel op: [[$1]]',
 'parser-template-recursion-depth-warning' => 'Schablone refe sesch zo öff sellver op ($1)',
 'language-converter-depth-warning' => 'Zoh vill Verschachtelunge (övver $1) beim Täx-Ömwandelle vun ein Shprooch en andere.',
-'node-count-exceeded-category' => 'Sigge, woh dä  node-count övverschredde es',
+'node-count-exceeded-category' => 'Sigge, woh dä <i lang="en" xml:lang="en">node-count</i> övverschredde es',
 'node-count-exceeded-warning' => 'Heh di Sigg hät dä <i lang="en" xml:lang="en">node-count</i> övverschredde',
 'expansion-depth-exceeded-category' => 'Sigge, woh de <i lang="en" xml:lang="en">expansion depth</i> övverschredde es',
 'expansion-depth-exceeded-warning' => 'Heh di Sigg hät de <i lang="en" xml:lang="en">expansion depth</i> övverschredde',
@@ -1348,6 +1359,7 @@ Donn de Version makeere bes wohen (inklusive) dat övverdraare wäde sull. Donn
 'compareselectedversions' => 'Dun de markeete Version verjliche',
 'showhideselectedversions' => 'De ußjewählte Versione aanzeije udder vershteiche',
 'editundo' => 'De letzte Änderung zeröck nämme',
+'diff-empty' => '(Keine Ongerscheid)',
 'diff-multi' => '(Mer don hee {{PLURAL:$1|eij Version|$1 Versione|keij Version}} dozwesche beim Verjliesche översprenge. Di sin vun jesamp {{PLURAL:$2|einem Metmaacher|$2 Metmaachere|keinem Metmaacher}} jemaat woode)',
 'diff-multi-manyusers' => '({{PLURAL:$1|Ein Version|$1 Versione|kei Version}} dozwesche vun mieh wi {{PLURAL:$2|einem Metmaacher|$2 Metmaachere|keinem Metmaacher}} wääde nit jezeish)',
 'difference-missing-revision' => '{{PLURAL:$2|Ein Version|$2 Versione}} vun heh däm Verjlisch zwesche Versione ($1) {{PLURAL:$2|hammer}} nit jefonge.
@@ -1637,6 +1649,10 @@ dat dänne ehr Daate topaktoell sin,
 'right-editusercssjs' => 'Anderlücks CSS- un JS-Dateie ändere',
 'right-editusercss' => 'Anderlücks CSS-Dateie ändere',
 'right-edituserjs' => 'Anderlücks JS-Dateie ändere',
+'right-editmyusercss' => 'De eije <i lang="en" xml:lang="en">CSS</i> Datteije aanlääje un ändere',
+'right-editmyuserjs' => 'Eije JaavaSkrepp-Datteije aanlääje un ändere',
+'right-viewmywatchlist' => 'De eije Oppaßleß beloore',
+'right-editmyoptions' => 'De eije Enschtällonge ändere',
 'right-rollback' => 'All de letzte Änderunge fom letzte Metmaacher aan ene Sigg retur maache',
 'right-markbotedits' => 'Retur jemaate Änderonge als Bot-Änderung makeere',
 'right-noratelimit' => 'Kein Beschränkunge dorch Jrenze (<i lang="en">[http://www.mediawiki.org/wiki/Manual:%24wgRateLimits $wgRateLimits]</i>)',
@@ -1688,8 +1704,8 @@ dat dänne ehr Daate topaktoell sin,
 'action-block' => 'hee dämm Metmaacher et Sigge Ändere ze verbeede',
 'action-protect' => 'hee dä Sigg iere Sigge-Schotz ze ändere',
 'action-rollback' => 'all de letzte Änderunge fom letzte Metmaacher aan ene beshtemmpte Sigg flöck retur ze maache',
-'action-import' => 'hee di Sigg uss enem andere Wiki ze empotteere',
-'action-importupload' => 'hee di Sigg uss ene huhjelaade Datei ze impotteere',
+'action-import' => 'Sigge uss_enem andere Wiki ze empotteere',
+'action-importupload' => 'Sigge uss_ene huhjelaade Dattei ze empotteere',
 'action-patrol' => 'anderlüx Änderunge als „nohjeloort“ ze makeere',
 'action-autopatrol' => 'Ding eije Änderunge sälver als „nohjeloort“ ze makeere',
 'action-unwatchedpages' => 'de Leß met de Sigg en kei Oppassleß aanzeloore',
@@ -1698,14 +1714,19 @@ dat dänne ehr Daate topaktoell sin,
 'action-userrights-interwiki' => 'dä Metmaacher fun ander Wikis ier Rääschte ze ändere',
 'action-siteadmin' => 'de Datebank ze sperre udder widder freizejävve',
 'action-sendemail' => '<i lang="en">e-mails</i> ze verschecke',
+'action-editmywatchlist' => 'de eije Oppaßleß ze ändere',
+'action-viewmywatchlist' => 'de eije Oppaßleß ze belooere',
+'action-viewmyprivateinfo' => 'de eije päsöönlesche Aanjaabe ze belooere',
 'action-editmyprivateinfo' => 'Ding päsöönlesche Aanjaabe ze ändere',
 
 # Recent changes
 'nchanges' => '{{PLURAL:$1|Ein Änderong|$1 Änderonge|Kein Änderong}}',
+'enhancedrc-since-last-visit' => '{{PLURAL:$1|Ein|$1|Kein}} zigg_em läzde Aanloore',
 'enhancedrc-history' => 'Väsjohne',
 'recentchanges' => 'Neuste Änderonge',
 'recentchanges-legend' => 'Enstellunge',
 'recentchanges-summary' => 'Op dä Sigg hee sin de neuste Änderunge am Wiki opjeliss.',
+'recentchanges-noresult' => 'Nit verändert en dä Zigg met de aanjejovve Beschrängkonge.',
 'recentchanges-feed-description' => 'Op dämm Abonnomang-Kannal (<i lang="en">Feed</i>) kannze de {{int:recentchanges}} aam Wiki en Laif un en Färve metloore.',
 'recentchanges-label-newpage' => 'Heh di Sigg es neu dobei jekumme met dä Änderung',
 'recentchanges-label-minor' => 'Heh dat es en Mini-Änderung',
@@ -2538,10 +2559,12 @@ Do kanns hee noh Hölp luure:
 'deletecomment' => 'Aanlaß odder Jrund:',
 'deleteotherreason' => 'Ander Jrund oder Zosätzlich:',
 'deletereasonotherlist' => 'Ander Jrund',
-'deletereason-dropdown' => '* Alljemein Jrönde
-** dä Schriever wollt et esu
+'deletereason-dropdown' => '* Alljemein Jrönde för et Fottschmiiße
+** SPAM
+** et wohd jät kapott jemaat
 ** wohr jäje et Urhävverrääsch
-** et wohd jet kapott jemaat',
+** dä Schriever wolld et esu
+** kappodde Ömleidong',
 'delete-edit-reasonlist' => 'De Jrönde för et Fottschmieße beärbeide',
 'delete-toobig' => 'Di Sigg hät {{PLURAL:$1|ein Version|$1 Versione|jaa kein Version}}. Dat sinn_er ärsch fill. Domet unsere ẞööver do nit draan en de Kneen jeit, dom_mer esu en Sigg nit fottschmieße.',
 'delete-warning-toobig' => 'Di Sigg hät {{PLURAL:$1|ein Version|$1 Versione|jakein Version}}. Dat sinn_er ärsch fill. Wann De die all fottschmieße wells, dat kann dem Wiki sing Datenbangk schwer ußbremse.',
index fea0593..51ffbd1 100644 (file)
@@ -517,8 +517,8 @@ All Spezialsäiten déi et gëtt, sinn op der [[Special:SpecialPages|{{int:speci
 # General errors
 'error' => 'Feeler',
 'databaseerror' => 'Datebank Feeler',
-'databaseerror-text' => 'Et ass ee Feeler bäi enger Ufro un Datebank geschitt. Dat deit op e Feeler an der Software.',
-'databaseerror-textcl' => "Et ass e Feeler bäi enger Ufro un d'Datebank geschitt.",
+'databaseerror-text' => 'Et ass ee Feeler bei enger Ufro un Datebank geschitt. Dat deit op e Feeler an der Software.',
+'databaseerror-textcl' => "Et ass e Feeler bei enger Ufro un d'Datebank geschitt.",
 'databaseerror-query' => 'Ufro: $1',
 'databaseerror-function' => 'Funktioun: $1',
 'databaseerror-error' => 'Feeler: $1',
@@ -540,7 +540,7 @@ Mellt dëst w.e.g. bei engem [[Special:ListUsers/sysop|Administrateur]] a vergie
 'internalerror' => 'Interne Feeler',
 'internalerror_info' => 'Interne Feeler: $1',
 'fileappenderrorread' => '"$1" konnt während dem Derbäisetze net gelies ginn.',
-'fileappenderror' => '"$1" konnt net bäi "$2" derbäigesat ginn.',
+'fileappenderror' => '"$1" konnt net bei "$2" derbäigesat ginn.',
 'filecopyerror' => 'De Fichier "$1" konnt net op "$2" kopéiert ginn.',
 'filerenameerror' => 'De Fichier "$1" konnt net op "$2" ëmbenannt ginn.',
 'filedeleteerror' => 'De Fichier "$1" konnt net geläscht ginn.',
@@ -597,7 +597,7 @@ Den Administrateur den d\'Schreiwe gespaart huet, huet dës Erklärung uginn: "$
 'exception-nologin-text' => 'Dës Säit oder Aktioun erfuerdert datt Dir op dëser Wiki ageloggt sidd.',
 
 # Virus scanner
-'virus-badscanner' => "Schlecht Configuratioun: onbekannte  Virescanner: ''$1''",
+'virus-badscanner' => "Schlecht Konfiguratioun: onbekannte Virescanner: ''$1''",
 'virus-scanfailed' => 'De Scan huet net funktionéiert (Code $1)',
 'virus-unknownscanner' => 'onbekannten Antivirus:',
 
@@ -634,7 +634,7 @@ Vergiesst net fir Är [[Special:Preferences|{{SITENAME}} Astellungen]] z'ännere
 'userlogout' => 'Ausloggen',
 'notloggedin' => 'Net ageloggt',
 'userlogin-noaccount' => 'Hutt Dir kee Benotzerkont?',
-'userlogin-joinproject' => 'Maacht mat bäi {{SITENAME}}',
+'userlogin-joinproject' => 'Maacht mat bei {{SITENAME}}',
 'nologin' => 'Hutt Dir kee Benotzerkont? $1.',
 'nologinlink' => 'Neie Benotzerkont maachen',
 'createaccount' => 'Neie Kont opmaachen',
@@ -707,7 +707,8 @@ Wann een aneren dës Ufro sollt gemaach hunn oder wann Dir Iech an der Zwëschen
 'passwordsent' => 'Een neit Passwuert gouf un déi fir de Benotzer "$1" gespäichert E-Mailadress geschéckt.
 Mellt Iech w.e.g. domat un, soubal Dir et kritt hutt.',
 'blocked-mailpassword' => "Déi vun Iech benotzten IP-Adress ass fir d'Ännere vu Säite gespaart. Fir Mëssbrauch ze verhënneren, gouf d'Méiglechkeet fir een neit Passwuert unzefroen och gespaart.",
-'eauthentsent' => "Eng Confirmatiouns-E-Mail gouf un déi Adress geschéckt déi Dir uginn hutt.<br />
+'eauthentsent' => "Eng Confirmatiouns-E-Mail gouf un déi Adress geschéckt déi Dir uginn hutt.
+
 Ier iergendeng E-Mail vun anere Benotzer op dee Kont geschéckt ka ginn, musst Dir als éischt d'Instructiounen an der Confirmatiouns-E-Mail befollegen, fir ze bestätegen datt de Kont wierklech Ären eegenen ass.",
 'throttled-mailpassword' => "An {{PLURAL:$1|der leschter Stonn|de leschte(n) $1 Stonnen}} eng E-Mail verschéckt fir d'Passwuert zréckzesetzen.
 Fir de Mëssbrauch vun dëser Funktioun ze verhënneren kann nëmmen all {{PLURAL:$1|Stonn|$1 Stonnen}} sou eng Mail verschéckt ginn.",
@@ -718,7 +719,7 @@ Dofir kënne Visiteure déi dës IP-Adress benotzen den Ament keng Benotzerkonte
 'emailnotauthenticated' => 'Är E-Mail Adress gouf <strong>nach net confirméiert</strong>.<br />
 Dowéinst ass et bis ewell net méiglech, fir déi folgend Funktiounen E-Mailen ze schécken oder ze kréien.',
 'noemailprefs' => 'Gitt eng E-Mailadress bei Ären Astellungen un, fir datt déi Funktioune funktionéieren.',
-'emailconfirmlink' => 'Confirméiert Ã¤r E-Mailadress w.e.g..',
+'emailconfirmlink' => 'Confirméiert Ã\84r E-Mailadress w.e.g.',
 'invalidemailaddress' => 'Dës E-Mail-Adress gëtt net akzeptéiert well se en ongëltegt Format (z. B. ongëlteg Zeechen) ze hu schéngt.
 Gitt eng valabel E-Mail-Adress an oder loosst dëst Feld eidel.',
 'cannotchangeemail' => 'Mailadresse vu Benotzerkonte kënnen op dëser Wiki net geännert ginn.',
@@ -996,7 +997,7 @@ Den Administrateur den d'Datebank gespaart huet, huet dës Erklärung ginn: $1",
 'template-protected' => '(gespaart)',
 'template-semiprotected' => '(gespaart fir net-ugemellten an nei Benotzer)',
 'hiddencategories' => 'Dës Säit gehéiert zu {{PLURAL:$1|1 verstoppter Kategorie|$1 verstoppte Kategorien}}:',
-'edittools' => '<!-- Dësen Text gëtt ënner dem "Ännere"Formulaire esouwéi dem "Eropluede"-Formulaire ugewisen. -->',
+'edittools' => '<!-- Dësen Text gëtt ënner dem "Ännere"-Formulaire souwéi dem "Eropluede"-Formulaire ugewisen. -->',
 'nocreatetext' => "Op {{SITENAME}} gouf d'Schafe vun neie Säite limitéiert. Dir kënnt Säiten déi scho bestinn änneren oder Iech [[Special:UserLogin|umellen]].",
 'nocreate-loggedin' => 'Dir hutt keng Berechtigung fir nei Säiten unzeleeën.',
 'sectioneditnotsupported-title' => 'Ännere vum Abschnitt gëtt net ënnerstëtzt',
@@ -1144,15 +1145,15 @@ Aner {{SITENAME}}-Administrateure kënnen de geläschten Inhalt oder aner geläs
 * Net ubruechte perséinlechen Informatiounen
 *: ''Adressen, Telefonsnummeren, Sozialversécherungsnummeren asw.''",
 'revdelete-legend' => "Limitatioune fir d'Sichtbarkeet festleeën",
-'revdelete-hide-text' => 'Text vun der Versioun verstoppen',
+'revdelete-hide-text' => 'Text vun der Versioun',
 'revdelete-hide-image' => 'Bildinhalt verstoppen',
 'revdelete-hide-name' => 'Logbuch-Aktioun verstoppen',
-'revdelete-hide-comment' => 'Bemierkung verstoppen',
-'revdelete-hide-user' => 'Dem Auteur säi Benotzernumm/IP verstoppen',
+'revdelete-hide-comment' => 'Resumé vun der Ännerung',
+'revdelete-hide-user' => 'Dem Auteur säi Benotzernumm/IP-Adress',
 'revdelete-hide-restricted' => 'Donnéeën och fir Administrateuren suppriméieren geneesou wéi fir déi Aner',
 'revdelete-radio-same' => '(net änneren)',
-'revdelete-radio-set' => 'Jo',
-'revdelete-radio-unset' => 'Neen',
+'revdelete-radio-set' => 'Visibel',
+'revdelete-radio-unset' => 'Verstoppt',
 'revdelete-suppress' => 'Grond vum Läschen och fir Administrateure verstoppt',
 'revdelete-unsuppress' => 'Limitatiounen fir restauréiert Versiounen ophiewen',
 'revdelete-log' => 'Grond:',
@@ -1449,7 +1450,7 @@ Dës Informatioun ass ëffentlech.",
 'userrights-notallowed' => 'Dir hutt net déi néideg Rechter fir Rechter vun anere Benotzer derbäizesetzen oder ewechzehuelen.',
 'userrights-changeable-col' => 'Gruppen déi Dir ännere kënnt',
 'userrights-unchangeable-col' => 'Gruppen déi Dir net ännere kënnt',
-'userrights-conflict' => 'Konflikt bäi de Benotzerrechter! Kuckt Är Ännerunge w.e.g. no a maacht se w.e.g. nach eng Kéier.',
+'userrights-conflict' => 'Konflikt bei de Benotzerrechter! Kuckt Är Ännerunge w.e.g. no a maacht se w.e.g. nach eng Kéier.',
 'userrights-removed-self' => 'Dir hutt Är eege Rechter ewechgeholl. Dofir kënnt Dir net méi op dës Säit zougräifen.',
 
 # Groups
@@ -1850,7 +1851,7 @@ Kuckt  https://www.mediawiki.org/wiki/Manual:Image_Authorization',
 Nèemmen Datenofruff ass erlaabt.',
 'img-auth-streaming' => '"$1" lueden.',
 'img-auth-public' => "D'Funktioun img_auth.php erlaabt et fir Fichieren vun enger privater Wiki erauszeginn.
-Dës Wiki ass als ëffentlech Wiki configuréiert.
+Dës Wiki ass als ëffentlech Wiki konfiguréiert.
 Fir eng optimal Sécherheet ass img_auth.php ausgeschalt.",
 'img-auth-noread' => 'De Benotzer hut keen Zougang fir "$1" ze liesen',
 'img-auth-bad-query-string' => "D'URL huet eng net valabel Rei vun Zeechen.",
@@ -1862,7 +1863,7 @@ Fir eng optimal Sécherheet ass img_auth.php ausgeschalt.",
 'http-read-error' => 'HTTP-Feeler beim Liesen.',
 'http-timed-out' => 'HTTP-Ufro huet ze laang gebraucht (time out).',
 'http-curl-error' => 'Feeler beim Ofruff vun der URL: $1',
-'http-bad-status' => 'Et gouf e Problem bäi der HTTP-Ufro: $1 $2',
+'http-bad-status' => 'Et gouf e Problem bei der HTTP-Ufro: $1 $2',
 
 # Some likely curl errors. More could be added from <http://curl.haxx.se/libcurl/c/libcurl-errors.html>
 'upload-curl-error6' => "URL ass net z'erreechen",
@@ -2214,9 +2215,9 @@ Et ginn [[{{MediaWiki:Listgrouprights-helppage}}|zousätzlech Informatiounen]] i
 'listgrouprights-removegroup' => 'Kann {{PLURAL:$2|dëse Gruppe|dës Gruppen}} ewechhuelen: $1',
 'listgrouprights-addgroup-all' => 'Kann all Gruppen derbäisetzen',
 'listgrouprights-removegroup-all' => 'Ka Benotzer aus alle Gruppen eraushuelen',
-'listgrouprights-addgroup-self' => "Däerf {{PLURAL:$2|de Grupp|d'Gruppe}} bäi säin eegene Benotzerkont derbäisetzen: $1",
+'listgrouprights-addgroup-self' => "Däerf {{PLURAL:$2|de Grupp|d'Gruppe}} bei säin eegene Benotzerkont derbäisetzen: $1",
 'listgrouprights-removegroup-self' => "Däerf {{PLURAL:$2|de Grupp|d'Gruppe}} vu sengem eegene Benotzerkont ewechhuelen: $1",
-'listgrouprights-addgroup-self-all' => 'däerf all Gruppe bäi säin eegene Benotzerkont derbäisetzen',
+'listgrouprights-addgroup-self-all' => 'däerf all Gruppe bei säin eegene Benotzerkont derbäisetzen',
 'listgrouprights-removegroup-self-all' => 'Däerf all Gruppe vu sengem eegene Benotzerkont ewechhuelen',
 
 # Email user
@@ -2281,7 +2282,7 @@ All weider Ännerungen op dëser Säit an der associéierter Diskussiounssäit g
 'watchmethod-recent' => 'Rezent Ännerunge ginn op iwwerwaacht Säiten iwwerpréift',
 'watchmethod-list' => 'Iwwerwaachte Säite ginn op rezent Ännerungen iwwerpréift',
 'watchlistcontains' => 'Op ärer Iwwerwaachungslëscht $1 {{PLURAL:$1|steet $1 Säit|stinn $1 Säiten}}.',
-'iteminvalidname' => "Problem mat dem Objet '$1', ongëltegen Numm ...",
+'iteminvalidname' => "Problem mam Element '$1', ongëltegen Numm ...",
 'wlnote' => "Hei {{PLURAL:$1|ass déi lescht Ännerung|sinn déi lescht '''$1''' Ännerunge}} vun {{PLURAL:$2|der leschter Stonn|de leschte(n) '''$2''' Stonnen}}, Stand: $3 ëm $4 Auer.",
 'wlshowlast' => "D'Ännerunge vun de leschte(n) $1 Stonnen, $2 Deeg oder $3 (an de leschten 30 Deeg) weisen.",
 'watchlist-options' => 'Optioune vun der Iwwerwaachungslëscht',
@@ -2396,9 +2397,9 @@ Déi lescht Ännerung vun der Säit ass vum [[User:$3|$3]] ([[User talk:$3|Disku
 
 # Edit tokens
 'sessionfailure-title' => 'Setzungsfeeler',
-'sessionfailure' => 'Et schéngt e Problem mat Ã¤rer Loginséance ze ginn;
-Dës Aktioun gouf aus Sécherheetsgrënn ofgebrach, fir ze verhënneren datt Ã¤r Séance piratéiert ka ginn.
-Klickt w.e.g. op "Zréck" a lued déi Säit vun där Dir komm sidd nei, a versicht et dann nach eng Kéier.',
+'sessionfailure' => 'Et schéngt e Problem mat Ã\84rer Loginseance ze ginn;
+Dës Aktioun gouf aus Sécherheetsgrënn ofgebrach, fir ze verhënneren datt Ã\84r Seance piratéiert ka ginn.
+Klickt w.e.g. op "Zréck" a luet déi Säit vun där Dir komm sidd nei, a versicht et dann nach eng Kéier.',
 
 # Protect
 'protectlogpage' => 'Protektiounslogbuch',
@@ -2962,7 +2963,7 @@ Späichert en op Ärem Computer of a luet en hei nees erop.',
 'tooltip-n-mainpage' => "Besicht d'Haaptsäit",
 'tooltip-n-mainpage-description' => "Besicht d'Haaptsäit",
 'tooltip-n-portal' => 'Iwwer de Portal, wat Dir maache kënnt, wou wat ze fannen ass',
-'tooltip-n-currentevents' => "D'Aktualitéit a wat derhanner ass",
+'tooltip-n-currentevents' => "D'Aktualitéit a wat dohanner ass",
 'tooltip-n-recentchanges' => 'Lëscht vun de rezenten Ännerungen op {{SITENAME}}.',
 'tooltip-n-randompage' => 'Zoufälleg Säit',
 'tooltip-n-help' => 'Hëllefsäiten weisen.',
@@ -3318,7 +3319,7 @@ Déi aner sinn am Standard verstoppt.
 'exif-gpsdestlongitude' => 'Längt',
 'exif-gpsdestbearingref' => "Referenz fir d'Motivrichtung",
 'exif-gpsdestbearing' => 'Richtung vum Motiv',
-'exif-gpsdestdistanceref' => "Referenz fir d'Distanz bis bäi den Objet (vun der Foto)",
+'exif-gpsdestdistanceref' => "Referenz fir d'Distanz bis bei den Objet (vun der Foto)",
 'exif-gpsdestdistance' => 'Motivdistanz',
 'exif-gpsprocessingmethod' => 'Numm vun der GPS-Prozedur-Method',
 'exif-gpsareainformation' => 'Numm vun der GPS-Géigend',
@@ -3786,7 +3787,8 @@ Dir misst eng [{{SERVER}}{{SCRIPTPATH}}/COPYING Kopie vun der GNU General Public
 # Special:Redirect
 'redirect' => 'Viruleedung duerch e Fichier, Benotzer oder Versiouns-ID',
 'redirect-legend' => 'Viruleedung op ee Fichier oder eng Säit',
-'redirect-summary' => 'Dës Spezialsäit ass eng Viruleedung op e Fichier (Fichiersnumm uginn), eng Säit (Versiounsnummer uginn) oder eng Benotzersäit (numeresch Benotzeridentifikatioun uginn).',
+'redirect-summary' => 'Dës Spezialsäit ass eng Viruleedung op e Fichier (Fichiersnumm uginn), eng Säit (Versiounsnummer uginn) oder eng Benotzersäit (numeresch Benotzeridentifikatioun uginn).
+Gebrauch: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], oder [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit' => 'Lass',
 'redirect-lookup' => 'Nosichen:',
 'redirect-value' => 'Wäert:',
@@ -3940,7 +3942,7 @@ Soss kënnt Dir den einfache Formulär hei drënner benotzen. Är Bemierkung gë
 'feedback-message' => 'Message:',
 'feedback-cancel' => 'Ofbriechen',
 'feedback-submit' => 'Feedback schécken',
-'feedback-adding' => "Feedback gëtt bäi d'Säit derbäigesat...",
+'feedback-adding' => "Feedback gëtt bei d'Säit derbäigesat...",
 'feedback-error1' => 'Feeler: Resultat vum API gouf net erkannt',
 'feedback-error2' => "Feeler: D'Ännerung gouf net gespäichert",
 'feedback-error3' => 'Feeler: Keng Äntwert vum API',
index 1b4b7f5..558d6c0 100644 (file)
@@ -105,8 +105,6 @@ $messages = array(
 'category-article-count' => '{{PLURAL:$2|Itymā kategorejā ir vīn dūtuo puslopa.|{{PLURAL:$1|Paruodeita $1 puslopa|Paruodeitys $1 puslopys}} nu $2.}}',
 'listingcontinuesabbrev' => '(tuoļuojums)',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'Aproksts',
 'article' => 'Rakstīņs',
 'newwindow' => '(atdareišona jaunuo puslopā)',
index 9580b70..f636b64 100644 (file)
@@ -3200,7 +3200,7 @@ Var arī lietot [[Special:EditWatchlist|standarta izmainīšanas lapu]].',
 'logentry-newusers-newusers' => 'Lietotāja konts $1 tika {{GENDER:$2|izveidots}}',
 'logentry-newusers-create' => 'Lietotāja konts $1 tika {{GENDER:$2|izveidots}}',
 'logentry-newusers-create2' => '$1 {{GENDER:$2|izveidoja}} lietotāja kontu $3',
-'logentry-newusers-autocreate' => 'Konts $1 tika {GENDER:$2|izveidots}} automātiski',
+'logentry-newusers-autocreate' => 'Lietotaja konts $1 tika {{GENDER:$2|izveidots}} automātiski',
 'rightsnone' => '(nav)',
 
 # Feedback
index 982427e..fa50c49 100644 (file)
@@ -489,8 +489,6 @@ $messages = array(
 'noindex-category' => 'Неиндексирани страници',
 'broken-file-category' => 'Страници со прекинати врски до податотеки',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'За {{SITENAME}}',
 'article' => 'Статија',
 'newwindow' => '(се отвора во нов прозорец)',
@@ -4228,7 +4226,7 @@ $5
 # Special:Redirect
 'redirect' => 'Пренасочување по податотека, корисник или назнака на ревизија',
 'redirect-legend' => 'Пренасочување кон податотека или страница',
-'redirect-summary' => 'Оваа специјална страница пренасочува кон податотека (се задава името), страница (се задава назнаката на ревизијата) или корисничка странца (се задава бројчената назнака на корисникот).',
+'redirect-summary' => 'Оваа специјална страница пренасочува кон податотека (се задава името), страница (се задава назнаката на ревизијата) или корисничка странца (се задава бројчената назнака на корисникот). Употреба: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]] или [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit' => 'Оди',
 'redirect-lookup' => 'Пребарај:',
 'redirect-value' => 'Вредност:',
index d4e81af..89621fd 100644 (file)
@@ -855,7 +855,7 @@ $2',
 'noemailcreate' => 'താങ്കൾ സാധുവായ ഇമെയിൽ വിലാസം നൽകേണ്ടതാണ്',
 'passwordsent' => '‘$1” എന്ന അംഗത്വത്തിനായി രജിസ്റ്റർ ചെയ്യപ്പെട്ടിട്ടുള്ള ഇമെയിൽ വിലാസത്തിലേക്ക് ഒരു പുതിയ രഹസ്യവാക്ക് അയച്ചിട്ടുണ്ട്. അത് ലഭിച്ചശേഷം ദയവായി ലോഗിൻ ചെയ്യുക.',
 'blocked-mailpassword' => 'താങ്കളുടെ ഐ.പി. വിലാസത്തെ ഈ വിക്കി തിരുത്തുന്നതിൽ നിന്നു തടഞ്ഞിട്ടുള്ളതാണ്‌. അതിനാൽ രഹസ്യവാക്ക് വീണ്ടെടുക്കുവാനുള്ള സജ്ജീകരണം ഉപയോഗിക്കുന്നതിനു താങ്കൾക്ക് അവകാശമില്ല.',
-'eauthentsent' => 'താà´\99àµ\8dà´\95ൾ à´µà´¿à´\95àµ\8dà´\95ിയിൽ à´\95àµ\8dà´°à´®àµ\80à´\95à´°à´¿à´\9aàµ\8dà´\9aà´¿à´\9fàµ\8dà´\9fàµ\81à´³àµ\8dà´³ à´\87à´®àµ\86യിൽ à´µà´¿à´²à´¾à´¸à´¤àµ\8dതിലàµ\87à´\95àµ\8dà´\95àµ\8d à´¸àµ\8dഥിരàµ\80à´\95രണതàµ\8dതിനായി à´\92à´°àµ\81 à´®àµ\86യിൽ à´\85à´¯à´\9aàµ\8dà´\9aà´¿à´\9fàµ\8dà´\9fàµ\81à´£àµ\8dà´\9fàµ\8d. à´\87വിà´\9fàµ\86 à´¨à´¿à´¨àµ\8dà´¨àµ\8d à´\86 à´\87à´®àµ\86യിൽ à´µà´¿à´²à´¾à´¸à´¤àµ\8dതിലàµ\87à´\95àµ\8dà´\95àµ\8d à´®à´±àµ\8dà´±àµ\8aà´°àµ\81 à´®àµ\86യിൽ à´\95àµ\82à´\9fà´¿ à´\85à´¯à´\95àµ\8dà´\95àµ\81à´¨àµ\8dനതിനàµ\81 à´®àµ\81ൻപായി, à´\85à´\82à´\97à´¤àµ\8dà´µà´\82 à´¤à´¾à´\99àµ\8dà´\95à´³àµ\81à´\9fàµ\87à´¤àµ\81 à´¤à´¨àµ\8dà´¨àµ\86 à´\8eà´¨àµ\8dà´¨àµ\81 à´\89റപàµ\8dà´ªàµ\81 à´µà´°àµ\81à´¤àµ\8dà´¤àµ\81à´¨àµ\8dനതിനായി, à´\87à´ªàµ\8dà´ªàµ\8bൾ à´\85à´¯à´\9aàµ\8dà´\9aà´¿à´\9fàµ\8dà´\9fàµ\81à´³àµ\8dà´³ മെയിലിലെ നിർദ്ദേശങ്ങൾ താങ്കൾ പാലിക്കേണ്ടതാണ്.',
+'eauthentsent' => 'താà´\99àµ\8dà´\95ൾ à´¨àµ½à´\95ിയിà´\9fàµ\8dà´\9fàµ\81à´³àµ\8dà´³ à´\87à´®àµ\86യിൽ à´µà´¿à´²à´¾à´¸à´¤àµ\8dതിലàµ\87à´\95àµ\8dà´\95àµ\8d à´¸àµ\8dഥിരàµ\80à´\95രണതàµ\8dതിനായി à´\92à´°àµ\81 à´\87à´®àµ\86യിൽ à´\85à´¯à´\9aàµ\8dà´\9aà´¿à´\9fàµ\8dà´\9fàµ\81à´£àµ\8dà´\9fàµ\8d. à´\86 à´µà´¿à´²à´¾à´¸à´¤àµ\8dതിലàµ\87à´\95àµ\8dà´\95àµ\8d à´®à´±àµ\8dà´±àµ\8aà´°àµ\81 à´\87à´®àµ\86യിൽ à´\85à´¯à´\95àµ\8dà´\95àµ\81à´¨àµ\8dനതിനàµ\81 à´®àµ\81ൻപായി, à´\85à´\82à´\97à´¤àµ\8dà´µà´\82 à´¤à´¾à´\99àµ\8dà´\95à´³àµ\81à´\9fàµ\87à´¤àµ\81 à´¤à´¨àµ\8dà´¨àµ\86 à´\8eà´¨àµ\8dà´¨àµ\81 à´\89റപàµ\8dà´ªàµ\81 à´µà´°àµ\81à´¤àµ\8dà´¤àµ\81à´¨àµ\8dനതിനàµ\8d, à´\87à´ªàµ\8dà´ªàµ\8bൾ à´\85à´¯à´\9aàµ\8dà´\9aà´¿à´\9fàµ\8dà´\9fàµ\81à´³àµ\8dà´³ à´\87മെയിലിലെ നിർദ്ദേശങ്ങൾ താങ്കൾ പാലിക്കേണ്ടതാണ്.',
 'throttled-mailpassword' => 'കഴിഞ്ഞ {{PLURAL:$1|മണിക്കൂറിനുള്ളിൽ |$1 മണിക്കൂറുകൾക്കുള്ളിൽ}} രഹസ്യവാക്ക് പുനർസജ്ജീകരിക്കാനുള്ള ഒരു ഇമെയിൽ അയച്ചിട്ടുണ്ട്. ദുരുപയോഗം ഒഴിവാക്കാൻ {{PLURAL:$1|ഒരു മണിക്കൂറിനുള്ളിൽ |$1 മണിക്കൂറുകൾക്കുള്ളിൽ}} രഹസ്യവാക്ക് പുനർസജ്ജീകരിക്കാനുള്ള ഒരു ഇമെയിൽ മാത്രമേ അയയ്ക്കുകയുള്ളു.',
 'mailerror' => 'മെയിൽ അയയ്ക്കുന്നതിൽ പിഴവ്: $1',
 'acct_creation_throttle_hit' => 'കഴിഞ്ഞ ഒരു ദിവസത്തിനുള്ളിൽ താങ്കളുടെ ഐ.പി. വിലാസത്തിൽ നിന്നുമുള്ള സന്ദർശകർ {{PLURAL:$1|1 അംഗത്വം|$1 അംഗത്വങ്ങൾ}} എടുത്തിട്ടുണ്ട്, പ്രസ്താവിത സമയത്തിനുള്ളിൽ എടുക്കാവുന്ന ഏറ്റവും കൂടിയ പരിധിയാണിത്.
index 2f07107..dfef63f 100644 (file)
@@ -338,8 +338,6 @@ $messages = array(
 'noindex-category' => 'Laman tak diindeks',
 'broken-file-category' => 'Laman yang ada pautan fail yang terputus',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'Perihal',
 'article' => 'Laman kandungan',
 'newwindow' => '(dibuka di tetingkap baru)',
index 6bec1c6..a1bea2d 100644 (file)
@@ -271,6 +271,8 @@ $magicWords = array(
        'formatdate'                => array( '0', 'formatdata', 'dataformat', 'formatdate', 'dateformat' ),
 );
 
+$linkPrefixCharset = 'A-\\x{10ffff}';
+
 $messages = array(
 # User preference toggles
 'tog-underline' => 'Ħoloq sottolinjati:',
@@ -411,8 +413,6 @@ $messages = array(
 'noindex-category' => 'Paġni mhux indiċizzati',
 'broken-file-category' => "Paġni b'ħoloq lejn fajls miksura",
 
-'linkprefix' => '/^(.*?)([a-żA-Ż\\x80-\\xff]+)$/sD',
-
 'about' => 'Dwar',
 'article' => 'artiklu',
 'newwindow' => "(tinfetaħ f'tieqa ġdida)",
index 6abbfda..99a16bf 100644 (file)
@@ -193,8 +193,6 @@ $messages = array(
 'noindex-category' => 'क्रमांकन नगरिएका पृष्ठहरु',
 'broken-file-category' => 'टुटेको फाइल लिंकसितको पृष्ठ',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'बारेमा',
 'article' => 'सामाग्री पृष्ठ',
 'newwindow' => '(नयाँ विन्डोमा खुल्छ)',
index c4fb71d..4ec34de 100644 (file)
@@ -16,6 +16,7 @@
  * @author Erwin
  * @author Erwin85
  * @author Extended by Hendrik Maryns <hendrik.maryns@uni-tuebingen.de>, March 2007.
+ * @author Flightmare
  * @author Fryed-peach
  * @author Galwaygirl
  * @author Geitost
index fe944b1..47bc87c 100644 (file)
@@ -457,8 +457,6 @@ $messages = array(
 'noindex-category' => 'Ikkje-indekserte sider',
 'broken-file-category' => 'Sider med brotne fillenkjer',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'Om',
 'article' => 'Innhaldsside',
 'newwindow' => '(vert opna i eit nytt vindauge)',
index bd206aa..ea8cf0b 100644 (file)
@@ -811,7 +811,7 @@ podètz ignorar aqueste messatge e contunhar d'utilizar vòstre senhal ancian.",
 Identificatz-vos tre que l'aurètz recebut.",
 'blocked-mailpassword' => 'Vòstra adreça IP es blocada en edicion, la foncion de rapèl del senhal es doncas desactivada per evitar los abuses.',
 'eauthentsent' => 'Un corrièr de confirmacion es estat mandat a l’adreça indicada.
-Abans qu’un autre corrièr sià mandat a aqueste compte, vos caldrà seguir las instruccions donadas dins lo messatge per confirmar que sètz plan lo titular.',
+Abans qu’un autre corrièr sià mandat a aqueste compte, vos caldrà seguir las instruccions donadas dins lo messatge per confirmar que lo compte es plan vòstre.',
 'throttled-mailpassword' => 'Un corrièr electronic de reïnicializacion de vòstre senhal es ja estat mandat durant {{PLURAL:$1|la darrièra ora|las $1 darrièras oras}}. Per evitar los abuses, un sol corrièr de reïnicializacion de vòstre senhal serà pas mandat per {{PLURAL:$1|ora|interval de $1 oras}}.',
 'mailerror' => 'Error en mandant lo corrièr electronic : $1',
 'acct_creation_throttle_hit' => "De visitors d'aqueste wiki qu'utilizan vòstra adreça IP an creat $1 {{PLURAL:$1|compte|comptes}} lo jorn darrièr, aquò es lo limit maximum autorizat pendent aqueste periòde.
index 92d6598..171475b 100644 (file)
@@ -941,7 +941,7 @@ $1 ਲੁਕਵੀਆਂ ਸ਼੍ਰੇਣੀਆਂ}} ਦਾ ਮੈਂਬਰ 
 'post-expand-template-inclusion-category' => 'ਉਹ ਸਫ਼ੇ ਜਿੱਥੇ ਫਰਮੇ ਸ਼ਾਮਲ ਕਰਨ ਦਾ ਅਕਾਰ ਹੱਦੋਂ ਵੱਧ ਗਿਆ ਹੈ',
 'post-expand-template-argument-warning' => "'''ਚੇਤਾਵਨੀ:'''
 ਇਸ ਪੰਨੇ ਤੇ ਘੱਟੋ ਘੱਟ ਇੱਕ ਐਸੀ ਸਾਂਚਾ ਬਹਿਸ ਹੈ ਜਿਸ ਦਾ ਅਕਾਰ ਬਹੁਤ ਵੱਡਾ ਹੈ। ਅਜਿਹੀਆਂ ਬਹਿਸਾਂ ਨੂੰ ਛੱਡ ਦਿੱਤਾ ਗਿਆ ਹੈ।",
-'post-expand-template-argument-category' => 'à¨\90ਸà©\87 à¨ªà©°à¨¨à©\87 à¨\9cਿਨà©\8dਹਾà¨\82 à¨µà¨¿à©±à¨\9a à¨¸à¨¾à¨\82à¨\9aà©\87 à¨¦à©\87 à¨¸à¨\81à¨\98à¨\9fà¨\95 à¨\9bà©\81ੱà¨\9f à¨\97à¨\8f à¨¹à¨¨ ।',
+'post-expand-template-argument-category' => 'à¨\90ਸà©\87 à¨¸à¨«à¨¼à©\87 à¨\9cਿਨà©\8dਹਾà¨\82 à¨µà¨¿à©±à¨\9a à¨«à¨°à¨®à©\87 à¨¦à©\87 à¨\86ਰà¨\97à©\82ਮà©\88à¨\82à¨\9f à¨\9bà©\81ੱà¨\9f à¨\97à¨\8f à¨¹à¨¨।',
 'parser-template-loop-warning' => 'ਫਰਮੇ ਦਾ ਲੂਪ ਲੱਭਿਆ: [[$1]]',
 
 # "Undo" feature
index 5614ffc..54fe1db 100644 (file)
@@ -4043,7 +4043,7 @@ Powinieneś otrzymać [{{SERVER}}{{SCRIPTPATH}}/COPYING kopię licencji GNU Gene
 # Special:Redirect
 'redirect' => 'Przekierowanie według pliku, użytkownika albo identyfikatora wersji',
 'redirect-legend' => 'Przekieruj do pliku lub strony',
-'redirect-summary' => 'Ta strona specjalna przekierowuje do pliku (wg nazwy pliku), do strony (wg numeru wersji) albo do strony użytkownika (wg liczbowego identyfikatora użytkownika)',
+'redirect-summary' => 'Ta strona specjalna przekierowuje do: pliku (o podanej nazwie), do strony (o podanym numerze wersji) albo do strony użytkownika (o podanym identyfikatorze numerycznym). Sposób użycia: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]] albo [[{{#Special:Redirect}}/user/103]].',
 'redirect-submit' => 'Przejdź',
 'redirect-lookup' => 'Wyszukaj:',
 'redirect-value' => 'Wartość:',
index c709598..b90b565 100644 (file)
@@ -869,14 +869,14 @@ A smija che a sia stàita scancelà.',
 'edit-conflict' => 'Conflit ëd modìfiche.',
 'edit-no-change' => "Soa modìfica a l'é stàita ignorà, përchè gnun cambiament a l'é stàit fàit al test.",
 'postedit-confirmation' => "Soa modìfica a l'é stàita salvà.",
-'edit-already-exists' => 'As peul nen creesse la pàgina.
-A esist già.',
+'edit-already-exists' => "La neuva pàgina a l'ha nen podù creesse.
+A esist già.",
 'defaultmessagetext' => "Test che a-i sarìa se a-i fusso pa 'd modìfiche",
 'content-failed-to-parse' => "Faliment ëd l'anàlisi dël contnù ëd $2 për ël model $1: $3",
 'invalid-content-data' => 'Dat dël contnù pa bon',
 'content-not-allowed-here' => "Ël contnù «$1» a l'é nen autorisà an sla pàgina [[$2]]",
 'editwarning-warning' => "Chité sta pàgina-sì a peul feje perde tute le modìfiche ch'a l'ha fàit.
-S'a l'é rintrà ant ël sistema, a peul disabilité st'avis ant la session «Modìfiche» dij sò gust.",
+S'a l'é rintrà ant ël sistema, a peul disabilité st'avis ant la session «Modìfica» dij sò gust.",
 
 # Content models
 'content-model-wikitext' => 'test wiki',
@@ -885,21 +885,21 @@ S'a l'é rintrà ant ël sistema, a peul disabilité st'avis ant la session «Mo
 'content-model-css' => 'CSS',
 
 # Parser/template warnings
-'expensive-parserfunction-warning' => "'''Atension:''' Costa pàgina a l'ha tròpe ciamà costose a le fonsions ëd parser.
+'expensive-parserfunction-warning' => "'''Atension:''' Costa pàgina a l'ha tròpe ciamà costose a le fonsions d'anàlisi sintàtica.
 
-A dovrìa essnie men che {{PLURAL:$2|$2|$2}}, adess a-i na j'é {{PLURAL:$1|$1|$1}}.",
-'expensive-parserfunction-category' => 'Pàgine con tròpe ciamà costose a le fonsion parser',
-'post-expand-template-inclusion-warning' => "'''Atension:''' La dimension djë stamp anserì a l'é tròp gròssa.
+A dovrìa essnie men che {{PLURAL:$2|$2}}, adess a-i na j'é {{PLURAL:$1|$1}}.",
+'expensive-parserfunction-category' => "Pàgine con tròpe ciamà costose ëd fonsion ëd l'analisator sintàtich",
+'post-expand-template-inclusion-warning' => "'''Atension:''' La dimension dj'anseriment dë stamp a l'é tròp gròssa.
 Chèich stamp a saran nen anserì.",
-'post-expand-template-inclusion-category' => "Pàgine andoa la dimension djë stamp anserì a l'é tròpa",
+'post-expand-template-inclusion-category' => 'Pàgine con tròpe anclusion dë stamp',
 'post-expand-template-argument-warning' => "'''Atension:''' Costa pàgina a conten almanch un paràmeter dë stamp che a l'ha n'espansion tròp gròssa.
-Costi paràmeter a son stàit lassà fòra.",
-'post-expand-template-argument-category' => 'Pàgine contenente stamp con paràmeter mancant',
+Costi paràmeter a son stàit ignorà.",
+'post-expand-template-argument-category' => 'Pàgine contenente djë stamp con paràmeter mancant',
 'parser-template-loop-warning' => 'Trovà na liassa dlë stamp: [[$1]]',
 'parser-template-recursion-depth-warning' => 'Passà ël lìmit ëd ricorsion dlë stamp ($1)',
-'language-converter-depth-warning' => 'Passà lìmit ëd profondità dël convertidor ëd lenghe ($1)',
-'node-count-exceeded-category' => "Pàgine anté che ël nùmer ëd grop a l'é sorpassà",
-'node-count-exceeded-warning' => "La pàgina a l'ha sorpassà ël nùmer ëd grop",
+'language-converter-depth-warning' => 'Lìmit ëd profondità dël convertidor ëd lenga sorpassà ($1)',
+'node-count-exceeded-category' => "Pàgine anté che ël nùmer ëd neu a l'é sorpassà",
+'node-count-exceeded-warning' => "La pàgina a l'ha sorpassà ël nùmer ëd neu",
 'expansion-depth-exceeded-category' => "Pàgine anté che la profondeur d'espansion a l'é sorpassà",
 'expansion-depth-exceeded-warning' => "La pàgina a l'ha sorpassà la profondità d'espansion",
 'parser-unstrip-loop-warning' => 'Trovà un sicl nen dësmontàbil',
@@ -3717,7 +3717,7 @@ A dovrìa avèj arseivù [{{SERVER}}{{SCRIPTPATH}}/COPYING na còpia dla Licensa
 # Special:Redirect
 'redirect' => 'Ridirigiù da archivi, utent o ID ëd revision',
 'redirect-legend' => "Ridirige a n'archivi o na pàgina",
-'redirect-summary' => "Costa pàgina special a ponta a n'archivi (dàit un nòm d'archivi), na pàgina (dàita n'ID a la revision) o na pàgina d'utent (dàit n'identificativ numérich a l'utent).",
+'redirect-summary' => "Costa pàgina special a ponta a n'archivi (dàit ël nòm dl'archivi), na pàgina (dàita n'ID a la revision) o na pàgina d'utent (dàit n'identificativ numérich a l'utent). Usagi: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], or [[{{#Special:Redirect}}/user/101]].",
 'redirect-submit' => 'Andé',
 'redirect-lookup' => 'Arserca:',
 'redirect-value' => 'Valor:',
index 60bef83..e55b989 100644 (file)
@@ -1483,6 +1483,9 @@ $1',
 'randompage' => 'ناټاکلی مخ',
 'randompage-nopages' => 'په لانديني {{PLURAL:$2|نوم-تشيال|نوم-تشيالونو}} کې هېڅ کوم مخ نشته: $1.',
 
+# Random page in category
+'randomincategory-selectcategory' => 'يو ناټاکلی مخ له وېشنيزې موندل: $1 $2.',
+
 # Random redirect
 'randomredirect' => 'ناټاکلی ورگرځېدنه',
 
@@ -1684,7 +1687,7 @@ $1',
 'emailsubject' => 'سکالو:',
 'emailmessage' => 'پيغام:',
 'emailsend' => 'لېږل',
-'emailccme' => 'زÙ\85ا Ø¯ Ù¾Ù\8aغاÙ\85 Ù\8aÙ\88Ù\87 Ø¨Û\90Ù\84Ú«ه دې ماته هم برېښليک شي.',
+'emailccme' => 'زÙ\85ا Ø¯ Ù¾Ù\8aغاÙ\85 Ù\8aÙ\88Ù\87 Ø¨Û\90Ù\84Ú¯ه دې ماته هم برېښليک شي.',
 'emailccsubject' => '$1 ته ستاسو د پيغام لمېسه: $2',
 'emailsent' => 'برېښليک مو ولېږل شو',
 'emailsenttext' => 'ستاسو برېښليکي پيغام ولېږل شو.',
@@ -2330,7 +2333,7 @@ $1',
 'exif-pixelxdimension' => 'د انځور جګوالی',
 'exif-usercomment' => 'د کارن تبصرې',
 'exif-relatedsoundfile' => 'اړونده غږيزه دوتنه',
-'exif-datetimedigitized' => 'د Ú«ڼياليز کېدنې وخت او نېټه',
+'exif-datetimedigitized' => 'د Ú¯ڼياليز کېدنې وخت او نېټه',
 'exif-fnumber' => 'F شمېره',
 'exif-lightsource' => 'د رڼا سرچينه',
 'exif-flash' => 'فلش',
@@ -2562,7 +2565,7 @@ $5
 # Size units
 'size-bytes' => '$1 بايټ',
 'size-kilobytes' => '$1 کيلوبايټ',
-'size-megabytes' => '$1 Ù\85Û\90Ú«ابايټ',
+'size-megabytes' => '$1 Ù\85Û\90Ú¯ابايټ',
 'size-gigabytes' => '$1 ګېګابايټ',
 'size-terabytes' => '$1 ټېرابايټ',
 'size-petabytes' => '$1 پېبي بايټ',
index a8b66b2..9d58538 100644 (file)
@@ -426,8 +426,6 @@ The pagination links in category viewer. Parameters:
 * $1 - the previous link, uses {{msg-mw|Prevn}}
 * $2 - the next link, uses {{msg-mw|Nextn}}',
 
-'linkprefix' => '{{optional}}',
-
 'about' => '{{Identical|About}}',
 'article' => "A 'content page' is a page that forms part of the purpose of the wiki. It includes the main page and pages in the main namespace and any other namespaces that are included when the wiki is customised. For example on Wikimedia Commons 'content pages' include pages in the file and category namespaces. On Wikinews 'content pages' include pages in the Portal namespace. For technical definition of 'content namespaces' see [[mw:Manual:Using_custom_namespaces#Content_namespaces|MediaWiki]].
 
index ad80824..28e992f 100644 (file)
@@ -438,8 +438,6 @@ $messages = array(
 'noindex-category' => 'Pagini neindexate',
 'broken-file-category' => 'Pagini cu legături invalide către fișiere',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'Despre',
 'article' => 'Articol',
 'newwindow' => '(se deschide într-o fereastră nouă)',
@@ -822,7 +820,8 @@ să folosiți vechea parolă.',
 'noemailcreate' => 'Trebuie oferită o adresă e e-mail validă.',
 'passwordsent' => 'O nouă parolă a fost trimisă la adresa de e-mail a utilizatorului "$1". Te rugăm să te autentifici pe {{SITENAME}} după ce o primești.',
 'blocked-mailpassword' => 'Această adresă IP este blocată la editare, și deci nu este permisă utilizarea funcției de recuperare a parolei pentru a preveni abuzul.',
-'eauthentsent' => 'Un email de confirmare a fost trimis adresei nominalizate. Înainte de a fi trimis orice alt email acestui cont, trebuie să urmați intrucțiunile din email, pentru a confirma că acest cont este într-adevăr al dvs.',
+'eauthentsent' => 'Un e-mail de confirmare a fost trimis către adresa specificată.
+Înainte ca orice alt e-mail să mai fie trimis către acel cont, trebuie să urmați instrucțiunile prezente în e-mail pentru a confirma că acest cont este într-adevăr al dumneavoastră.',
 'throttled-mailpassword' => 'Un e-mail pentru resetarea parolei a fost deja trimis în {{PLURAL:$1|ultima oră|ultimele $1 ore|ultimele $1 de ore}}. Pentru a preveni abuzul, se va trimite doar un e-mail de resetare a parolei la un interval de o {{PLURAL:$1|o oră|$1 ore|$1 de ore}}.',
 'mailerror' => 'Eroare la trimitere e-mail: $1',
 'acct_creation_throttle_hit' => 'De la această adresă IP, vizitatorii sitului au creat {{PLURAL:$1|1 cont|$1 conturi|$1 de conturi}} de utilizator în ultimele zile, acest număr de noi conturi fiind maximul admis în această perioadă de timp.
@@ -3949,7 +3948,7 @@ MediaWiki este distribuit în speranța că va fi folositor, dar FĂRĂ VREO GAR
 # Special:Redirect
 'redirect' => 'Redirecționare după fișier, utilizator sau ID-ul versiunii',
 'redirect-legend' => 'Redirecționare către un fișier sau o pagină',
-'redirect-summary' => 'Această pagină specială vă redirecționează către un fișier (dat fiind un nume de fișier), o pagină (dat fiind ID-ul unei versiuni) sau o pagină de utilizator (dat fiind un ID numeric al utilizatorului).',
+'redirect-summary' => 'Această pagină specială vă redirecționează către un fișier (dat fiind un nume de fișier), o pagină (dat fiind ID-ul unei versiuni) sau o pagină de utilizator (dat fiind un ID numeric al utilizatorului). Utilizare: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]] sau [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit' => 'Du-te',
 'redirect-lookup' => 'Căutare:',
 'redirect-value' => 'Valoare:',
index bc1f773..0203dd1 100644 (file)
@@ -165,8 +165,6 @@ $messages = array(
 'broken-file-category' => 'Pàggene cu collegaminde a le file scuasciate',
 'categoryviewer-pagedlinks' => '($1) ($2)',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'Sus a',
 'article' => 'Pàgene de le condenute',
 'newwindow' => "(iapre jndr'à 'na fenestra nova)",
index 8eb25d1..fb422e1 100644 (file)
@@ -962,7 +962,7 @@ $2',
 Пожалуйста, представьтесь системе заново после получения пароля.',
 'blocked-mailpassword' => 'Редактирование с вашего IP-адреса запрещено, поэтому заблокирована и функция восстановления пароля.',
 'eauthentsent' => 'На указанный адрес электронной почты отправлено письмо. 
¡ледуйте изложенным там инструкциям для подтверждения того, что этот адрес действительно принадлежит вам.',
§Ñ\82об Ð¿Ð¾Ð»Ñ\83Ñ\87аÑ\82Ñ\8c Ð¿Ð¸Ñ\81Ñ\8cма Ð² Ð´Ð°Ð»Ñ\8cнейÑ\88ем, Ñ\81ледуйте изложенным там инструкциям для подтверждения того, что этот адрес действительно принадлежит вам.',
 'throttled-mailpassword' => 'Функция напоминания пароля уже использовалась в течение {{PLURAL:$1|последнего часа|последних $1 часов}}.
 Для предотвращения злоупотреблений, разрешено запрашивать не более одного напоминания за $1 {{PLURAL:$1|час|часа|часов}}.',
 'mailerror' => 'Ошибка при отправке почты: $1',
@@ -1415,15 +1415,15 @@ $3 {{GENDER:$3|указал|указала}} следующую причину:
 * Неуместная личная информация
 *: ''домашний адрес, номера телефонов, номер паспорта и т. д.''",
 'revdelete-legend' => 'Установить ограничения:',
-'revdelete-hide-text' => 'СкÑ\80Ñ\8bÑ\82Ñ\8c Ñ\82екÑ\81Ñ\82 Ñ\8dÑ\82ой Ð²ÐµÑ\80Ñ\81ии Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\8b',
+'revdelete-hide-text' => 'ТекÑ\81Ñ\82 Ð¿Ñ\80авки',
 'revdelete-hide-image' => 'Скрыть содержимое файла',
 'revdelete-hide-name' => 'Скрыть действие и его объект',
-'revdelete-hide-comment' => 'СкÑ\80Ñ\8bÑ\82Ñ\8c Ð¾писание изменений',
-'revdelete-hide-user' => 'СкÑ\80Ñ\8bÑ\82Ñ\8c Ð¸Ð¼Ñ\8f Ð°Ð²Ñ\82оÑ\80а',
+'revdelete-hide-comment' => 'Ð\9eписание изменений',
+'revdelete-hide-user' => 'Ð\98мÑ\8f Ñ\83Ñ\87аÑ\81Ñ\82ника/IP-адÑ\80еÑ\81',
 'revdelete-hide-restricted' => 'Скрыть данные также и от администраторов',
 'revdelete-radio-same' => '(не изменять)',
-'revdelete-radio-set' => 'Ð\94а',
-'revdelete-radio-unset' => 'Ð\9dеÑ\82',
+'revdelete-radio-set' => 'Ð\92идимаÑ\8f',
+'revdelete-radio-unset' => 'СкÑ\80Ñ\8bÑ\82аÑ\8f',
 'revdelete-suppress' => 'Скрывать данные также и от администраторов',
 'revdelete-unsuppress' => 'Снять ограничения с восстановленных версий',
 'revdelete-log' => 'Причина:',
@@ -4200,7 +4200,8 @@ MediaWiki распространяется в надежде, что она бу
 # Special:Redirect
 'redirect' => 'Перенаправление с файла, участника или идентификатора версии',
 'redirect-legend' => 'Перенаправление на файл или страницу',
-'redirect-summary' => 'Эта специальная страница перенаправляет на файл (с имени файла), страницу (с идентификатора версии) или страницу участника (с числового идентификатора участника).',
+'redirect-summary' => 'Эта специальная страница перенаправляет на файл (с имени файла), страницу (с идентификатора версии) или страницу участника (с числового идентификатора участника).
+Использование: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]] или [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit' => 'Перейти',
 'redirect-lookup' => 'Поиск:',
 'redirect-value' => 'Значение:',
index 2990ac8..6c74bcd 100644 (file)
@@ -261,7 +261,7 @@ $messages = array(
 'articlepage' => 'Ыстатыйаны көр',
 'talk' => 'Ырытыы',
 'views' => 'Көрүүлэр',
-'toolbox' => 'Үнүстүрүмүөннэр',
+'toolbox' => 'Сэп-сэбиргэл',
 'userpage' => 'Кыттааччы туһунан сирэй',
 'projectpage' => 'Бырайыак сирэйэ',
 'imagepage' => 'Билэ сирэйин көрүү',
@@ -291,7 +291,7 @@ $1',
 # All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).
 'aboutsite' => '{{SITENAME}} туһунан',
 'aboutpage' => 'Project:туһунан',
-'copyright' => 'Ð\9cанÑ\8b Ñ\82Ñ\83һанаÑ\80Ñ\8b $1 ÐºÓ©Ò¥Ò¯Ð»Ð»Ò¯Ò¯Ñ\80.',
+'copyright' => 'Ð\9cанÑ\8b Ñ\82Ñ\83һанаÑ\80Ñ\8b $1 Ð»Ð¸Ñ\81Ñ\81иÑ\8dнÑ\81ийÑ\8d ÐºÓ©Ò¥Ò¯Ð»Ð»Ò¯Ò¯Ñ\80 (аÑ\82Ñ\8bн Ñ\8bйÑ\8bллÑ\8bбÑ\8bÑ\82аÑ\85 Ð±Ñ\83оллаÒ\95Ñ\8bна).',
 'copyrightpage' => '{{ns:project}}:бас билиитэ',
 'currentevents' => 'Туох буола турара',
 'currentevents-url' => 'Project:Сонуннар',
@@ -409,6 +409,7 @@ $1',
 'cannotdelete-title' => '«$1» сирэйи сотор сатаммат',
 'delete-hook-aborted' => 'Көннөрүү төттөрү көннөрүллүбүт.
 Эбии туох да быһаарыллыбатах.',
+'no-null-revision' => '«$1» сирэйгэ кураанах көннөрүүнү оҥорор табыллыбата',
 'badtitle' => 'Табыллыбат аат',
 'badtitletext' => 'Ыйытыллыбыт сирэй аата сыыһа, иччитэх, эбэтэр сыыһа ыйынньыктаах тыллар ыккардыларынааҕы дуу, биикилэр ыккардыларынааҕы дуу аат.',
 'perfcached' => 'Бу кээстэн ылыллыбыт онон бүтэһик уларыйыылары аахсымыан сөп. Кээскэ {{PLURAL:$1|соҕотох суруктан|$1 суруктан}} ордук хараллыбат.',
@@ -497,6 +498,9 @@ $2',
 'userlogin-resetpassword-link' => 'Киирии тылы уларытыы',
 'helplogin-url' => 'Help:Бэлиэ-ааты киллэрии',
 'userlogin-helplink' => '[[{{MediaWiki:helplogin-url}}|Бэлиэтэниигэ көмө]]',
+'userlogin-loggedin' => 'Маннык аатынан киирбиккин {{GENDER:$1|$1}}.
+Атын аатынан киирэргэ аллара көстөр форманы туһан.',
+'userlogin-createanother' => 'Атын аатынан бэлиэтэн',
 'createacct-join' => 'Аллара суруй.',
 'createacct-another-join' => 'Саҥа бэлиэ-аат туһунан аллара суруй.',
 'createacct-emailrequired' => 'Email аадырыс',
@@ -557,7 +561,7 @@ $2',
 Системаҕа саҥа киирии тылы туһанан киир.',
 'blocked-mailpassword' => 'Эн IP аадырыскыттан манна тугу эмэ уларытар бобуллубут,
 онон киирии тылы өйдөтөр кыах эмиэ суох.',
-'eauthentsent' => 'Ð\91Ñ\8bÑ\81Ñ\82аÑ\85 ÐºÑ\8dмҥÑ\8d Ñ\82Ñ\83Ñ\82Ñ\82Ñ\83ллаÑ\80 ÐºÐ¸Ð¸Ñ\80ии Ñ\82Ñ\8bл Ñ\81аҥа ÐºÑ\8bÑ\82Ñ\82ааÑ\87Ñ\87Ñ\8b Ñ\8dл. Ð¿Ð¾Ñ\87Ñ\82аÑ\82Ñ\8bгаÑ\80 ыытылынна.
+'eauthentsent' => 'Эл. Ð¿Ð¾Ñ\87Ñ\82аÒ\95аÑ\80 Ñ\81Ñ\83Ñ\80Ñ\83к ыытылынна.
 Бу аадырыс эйиэнэ буоларын бигэргэтэргэ өссө тугу гыныахтааҕыҥ туһунан сурукка кэпсэниллэр.',
 'throttled-mailpassword' => 'Киирии тылы өйдөтөр тэрил бүтэһик {{PLURAL:$1|чаас|$1 чаас}} иһигэр туттулла сылдьыбыт.
 Көмүскэнэр соруктан сылтаан киирии тылы {{PLURAL:$1|чааска|$1 чааска}} биирдэ эрэ ыйытыахха сөп.',
index bfc2b2b..d97bb14 100644 (file)
@@ -177,8 +177,6 @@ $messages = array(
 'index-category' => 'Pàginas indicizadas',
 'noindex-category' => 'Pàginas no indicitzadas',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'A propòsitu de',
 'article' => 'Artìculu',
 'newwindow' => '(aberit in una bentana noa)',
index 4a30265..a020dd0 100644 (file)
@@ -331,8 +331,6 @@ $messages = array(
 'noindex-category' => 'සූචිගත නොකළ පිටු',
 'broken-file-category' => 'භින්න වූ ගොනු සබැඳි සහිත පිටු',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'පිළිබඳ',
 'article' => 'පටුන',
 'newwindow' => '(නව කවුළුවක විවෘතවේ)',
index b28c453..a7b487d 100644 (file)
@@ -536,8 +536,6 @@ $messages = array(
 'noindex-category' => 'Непописане странице',
 'broken-file-category' => 'Странице с неисправним везама до датотека',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'О нама',
 'article' => 'Страница са садржајем',
 'newwindow' => '(отвара се у новом прозору)',
@@ -646,7 +644,7 @@ $1',
 # All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).
 'aboutsite' => 'О пројекту {{SITENAME}}',
 'aboutpage' => 'Project:О нама',
-'copyright' => 'Садржај је доступан под лиценцом $1.',
+'copyright' => 'Садржај је доступан под лиценцом $1 осим ако је другачије наведено.',
 'copyrightpage' => '{{ns:project}}:Ауторска права',
 'currentevents' => 'Актуелности',
 'currentevents-url' => 'Project:Новости',
@@ -2130,6 +2128,7 @@ $1',
 'listfiles_size' => 'Величина',
 'listfiles_description' => 'Опис',
 'listfiles_count' => 'Верзије',
+'listfiles-show-all' => 'Обухвати старе верзије слика',
 'listfiles-latestversion' => 'Тренутна верзија',
 'listfiles-latestversion-yes' => 'Да',
 'listfiles-latestversion-no' => 'Не',
@@ -2608,6 +2607,7 @@ $UNWATCHURL
 'deleteotherreason' => 'Други/додатни разлог:',
 'deletereasonotherlist' => 'Други разлог',
 'deletereason-dropdown' => '*Најчешћи разлози за брисање
+** Спам
 ** Захтев аутора
 ** Кршење ауторских права
 ** Вандализам',
@@ -2631,7 +2631,7 @@ $UNWATCHURL
 Последњу измену је {{GENDER:$3|направио|направила|направио}} [[User:$3|$3]] ([[User talk:$3|разговор]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).',
 'editcomment' => "Опис измене: \"''\$1''\".",
 'revertpage' => 'Враћене измене [[Special:Contributions/$2|$2]] ([[User talk:$2|разговор]]) на последњу измену корисника [[User:$1|$1]]',
-'revertpage-nouser' => 'Враћене су измене скривеног корисника на последњу измену члана [[User:$1|$1]]',
+'revertpage-nouser' => 'Враћене су измене скривеног корисника на последњу измену члана {{GENDER:$1|[[User:$1|$1]]}}',
 'rollback-success' => 'Враћене су измене {{GENDER:$1|корисника|кориснице|корисника}} $1
 на последњу измену {{GENDER:$2|корисника|кориснице|корисника}} $2.',
 
@@ -4274,8 +4274,7 @@ $5
 'specialpages' => 'Посебне странице',
 'specialpages-note' => '----
 * обичне посебне странице
-* <span class="mw-specialpagerestricted">ограничене посебне странице</span>
-* <span class="mw-specialpagecached">привремено меморисане посебне странице</span>',
+* <span class="mw-specialpagerestricted">ограничене посебне странице</span>',
 'specialpages-group-maintenance' => 'Извештаји одржавања',
 'specialpages-group-other' => 'Остале посебне странице',
 'specialpages-group-login' => 'Пријава/регистрација',
@@ -4358,17 +4357,17 @@ $5
 'sqlite-no-fts' => '$1 без подршке претраге целог текста',
 
 # New logging system
-'logentry-delete-delete' => '$1 је {{GENDER:|обрисао|обрисала}} $3',
+'logentry-delete-delete' => '$1 је {{GENDER:|обрисао|обрисала}} страницу $3',
 'logentry-delete-restore' => '$1 је {{GENDER:$2|вратио|вратила}} страницу $3',
 'logentry-delete-event' => '$1 је {{GENDER:$2|променио|променила}} видљивост {{PLURAL:$5|догађаја|$5 догађаја}} у дневнику на $3: $4',
 'logentry-delete-revision' => '$1 је {{GENDER:$2|променио|променила}} видљивост {{PLURAL:$5|измене|$5 измена}} на страници $3: $4',
 'logentry-delete-event-legacy' => '$1 је {{GENDER:$2|променио|променила}} видљивост догађаја у дневнику на $3',
-'logentry-delete-revision-legacy' => '$1 је {{GENDER:$2|променио|променила}} видљивост изменâ на страници $3',
+'logentry-delete-revision-legacy' => '$1 је {{GENDER:$2|променио|променила}} видљивост измена на страници $3',
 'logentry-suppress-delete' => '$1 је {{GENDER:$2|потиснуо|потиснула}} страницу $3',
-'logentry-suppress-event' => '$1 је потајно {{GENDER:$2|променио|променила}} видљивост {{PLURAL:$5|догађаја|$5 догађаја}} у дневнику на $3: $4',
-'logentry-suppress-revision' => '$1 је потајно {{GENDER:$2|променио|променила}} видљивост {{PLURAL:$5|измене|$5 измена}} на страници $3: $4',
+'logentry-suppress-event' => '$1 је тајно {{GENDER:$2|променио|променила}} видљивост {{PLURAL:$5|догађаја|$5 догађаја}} у дневнику на $3: $4',
+'logentry-suppress-revision' => '$1 је тајно {{GENDER:$2|променио|променила}} видљивост {{PLURAL:$5|измене|$5 измена}} на страници $3: $4',
 'logentry-suppress-event-legacy' => '$1 је потајно {{GENDER:$2|променио|променила}} видљивост догађаја у дневнику на $3',
-'logentry-suppress-revision-legacy' => '$1 је потајно {{GENDER:$2|променио|променила}} видљивост измена на страници $3',
+'logentry-suppress-revision-legacy' => '$1 је тајно {{GENDER:$2|променио|променила}} видљивост измена на страници $3',
 'revdelete-content-hid' => 'садржај је сакривен',
 'revdelete-summary-hid' => 'опис измене је сакривен',
 'revdelete-uname-hid' => 'корисничко име је сакривено',
index bb7f381..592ec21 100644 (file)
@@ -349,6 +349,7 @@ $messages = array(
 'tog-noconvertlink' => 'Onemogući pretvaranje naslova veza',
 'tog-norollbackdiff' => 'Izostavi razliku nakon izvršenog vraćanja',
 'tog-useeditwarning' => 'Upozori me kada napuštam stranicu sa nesačuvanim promenama',
+'tog-prefershttps' => 'Uvek koristi sigurnu konekciju kada sam prijavljen.',
 
 'underline-always' => 'uvek podvlači',
 'underline-never' => 'nikad ne podvlači',
@@ -444,8 +445,6 @@ $messages = array(
 'noindex-category' => 'Nepopisane stranice',
 'broken-file-category' => 'Stranice s neispravnim vezama do datoteka',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'O nama',
 'article' => 'Stranica sa sadržajem',
 'newwindow' => '(otvara u novom prozoru)',
@@ -480,7 +479,7 @@ $messages = array(
 'vector-view-edit' => 'Uredi',
 'vector-view-history' => 'Istorija',
 'vector-view-view' => 'Čitaj',
-'vector-view-viewsource' => 'Izvornik',
+'vector-view-viewsource' => 'Izvorni kod',
 'actions' => 'Radnje',
 'namespaces' => 'Imenski prostori',
 'variants' => 'Varijante',
@@ -552,7 +551,7 @@ $1',
 # All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).
 'aboutsite' => 'O projektu {{SITENAME}}',
 'aboutpage' => 'Project:O nama',
-'copyright' => 'Sadržaj je dostupan pod licencom $1.',
+'copyright' => 'Sadržaj je dostupan pod licencom $1 osim ako je drugačije navedeno.',
 'copyrightpage' => '{{ns:project}}:Autorska prava',
 'currentevents' => 'Aktuelnosti',
 'currentevents-url' => 'Project:Novosti',
@@ -591,7 +590,7 @@ Pogledajte stranicu za [[Special:Version|izdanje]].',
 'youhavenewmessagesmulti' => 'Imate novih poruka na $1',
 'editsection' => 'uredi',
 'editold' => 'uredi',
-'viewsourceold' => 'izvornik',
+'viewsourceold' => 'izvorni kod',
 'editlink' => 'uredi',
 'viewsourcelink' => 'Izvor',
 'editsectionhint' => 'Uredite odeljak „$1“',
@@ -684,11 +683,11 @@ Podaci koji se ovde nalaze mogu biti zastareli.',
 'wrong_wfQuery_params' => 'Neispravni parametri za wfQuery()<br />
 Funkcija: $1<br />
 Upit: $2',
-'viewsource' => 'Izvornik',
+'viewsource' => 'Izvorni kod',
 'viewsource-title' => 'Prikaz izvora stranice $1',
 'actionthrottled' => 'Radnja je usporena',
 'actionthrottledtext' => 'U cilju borbe protiv nepoželjnih poruka, ograničene su vam izmene u određenom vremenu, a upravo ste prešli to ograničenje. Pokušajte ponovo za nekoliko minuta.',
-'protectedpagetext' => 'Ova stranica je zaključana za uređivanja.',
+'protectedpagetext' => 'Ova stranica je zaključana za izmene i druge radnje.',
 'viewsourcetext' => 'Možete da pogledate i umnožite izvorni tekst ove stranice:',
 'viewyourtext' => "Možete da pogledate i umnožite izvor '''vaših izmena''' na ovoj stranici:",
 'protectedinterface' => 'Ova stranica sadrži tekst korisničkog okruženja za softver na ovom vikiju i zaštićena je radi sprečavanja zloupotrebe.
@@ -1336,6 +1335,7 @@ Korišćenje navigacionih veza će poništiti ovu kolonu.',
 'compareselectedversions' => 'Uporedi izabrane izmene',
 'showhideselectedversions' => 'Prikaži/sakrij izabrane izmene',
 'editundo' => 'poništi',
+'diff-empty' => '(Nema razlike)',
 'diff-multi' => '({{PLURAL:$1|nije prikazana međuizmena|nisu prikazane $1 međuizmene|nije prikazano $1 međuizmena}} {{PLURAL:$2|jednog|$2|$2}} korisnika)',
 'diff-multi-manyusers' => '({{PLURAL:$1|Nije prikazana međuizmena|Nisu prikazane $1 međuizmene|Nije prikazano $1 međuizmena}} od više od $2 korisnika)',
 'difference-missing-revision' => 'Ne mogu da pronađem {{PLURAL:$2|jednu izmenu|$2 izmene|$2 izmena}} od ove razlike ($1).
@@ -1522,6 +1522,7 @@ Ako izaberete da ga unesete, ono će biti korišćeno za pripisivanje vašeg rad
 'prefs-displaysearchoptions' => 'Postavke prikaza',
 'prefs-displaywatchlist' => 'Postavke prikaza',
 'prefs-diffs' => 'Razlike',
+'prefs-help-prefershttps' => 'Ova podešavanja će stupiti na snagu pri sledećoj prijavi.',
 
 # User preference: email validation using jQuery
 'email-address-validity-valid' => 'E-adresa je ispravna',
@@ -2003,6 +2004,10 @@ Probajte kasnije kada bude manje opterećenje.',
 'listfiles_size' => 'Veličina',
 'listfiles_description' => 'Opis',
 'listfiles_count' => 'Verzije',
+'listfiles-show-all' => 'Obuhvati stare verzije slika',
+'listfiles-latestversion' => 'Trenutna verzija',
+'listfiles-latestversion-yes' => 'Da',
+'listfiles-latestversion-no' => 'Ne',
 
 # File description page
 'file-anchor-link' => 'Datoteka',
@@ -2461,6 +2466,7 @@ Pogledajte ''$2'' za više detalja.",
 'deleteotherreason' => 'Drugi/dodatni razlog:',
 'deletereasonotherlist' => 'Drugi razlog',
 'deletereason-dropdown' => '*Najčešći razlozi za brisanje
+** Spam
 ** Zahtev autora
 ** Kršenje autorskih prava
 ** Vandalizam',
@@ -2484,7 +2490,7 @@ Poslednji autor je ujedno i jedini.',
 Poslednju izmenu je {{GENDER:$3|napravio|napravila|napravio}} [[User:$3|$3]] ([[User talk:$3|razgovor]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).',
 'editcomment' => "Opis izmene: \"''\$1''\".",
 'revertpage' => 'Vraćene izmene [[Special:Contributions/$2|$2]] ([[User talk:$2|razgovor]]) na poslednju  izmenu korisnika [[User:$1|$1]]',
-'revertpage-nouser' => 'Vraćene su izmene skrivengo korisnika na poslednju izmenu člana [[User:$1|$1]]',
+'revertpage-nouser' => 'Vraćene su izmene skrivenog korisnika na poslednju izmenu člana {{GENDER:$1|[[User:$1|$1]]}}',
 'rollback-success' => 'Vraćene su izmene {{GENDER:$1|korisnika|korisnice|korisnika}} $1
 na poslednju izmenu {{GENDER:$2|korisnika|korisnice|korisnika}} $2.',
 
@@ -2524,9 +2530,9 @@ Ovo su trenutne postavke stranice '''$1''':",
 'protect-cascadeon' => 'Ova stranica je trenutno zaštićena jer se nalazi na {{PLURAL:$1|stranici koja ima|stranicama koje imaju}} prenosivu zaštitu.
 Možete da promenite stepen zaštite, ali to neće uticati na prenosivu zaštitu.',
 'protect-default' => 'Dozvoli svim korisnicima',
-'protect-fallback' => 'Potrebno je imati ovlašćenja „$1“',
-'protect-level-autoconfirmed' => 'Blokiraj nove i anonimne korisnike',
-'protect-level-sysop' => 'Samo administratori',
+'protect-fallback' => 'Dozvoljeno samo korisnicima sa dozvolom „$1“',
+'protect-level-autoconfirmed' => 'Dopušteno samo automatski potvrđenim korisnicima',
+'protect-level-sysop' => 'Dopušteno samo administratorima',
 'protect-summary-cascade' => 'prenosiva zaštita',
 'protect-expiring' => 'ističe $1 (UTC)',
 'protect-expiring-local' => 'ističe $1',
@@ -4107,8 +4113,7 @@ Trebalo bi da ste primili [{{SERVER}}{{SCRIPTPATH}}/COPYING primerak GNU-ove op
 'specialpages' => 'Posebne stranice',
 'specialpages-note' => '----
 * obične posebne stranice
-* <span class="mw-specialpagerestricted">ograničene posebne stranice</span>
-* <span class="mw-specialpagecached">privremeno memorisane posebne stranice</span>',
+* <span class="mw-specialpagerestricted">ograničene posebne stranice</span>',
 'specialpages-group-maintenance' => 'Izveštaji održavanja',
 'specialpages-group-other' => 'Ostale posebne stranice',
 'specialpages-group-login' => 'Prijava/registracija',
@@ -4140,6 +4145,7 @@ Trebalo bi da ste primili [{{SERVER}}{{SCRIPTPATH}}/COPYING primerak GNU-ove op
 'tags' => 'Važeće oznake izmena',
 'tag-filter' => 'Filter za [[Special:Tags|oznake]]:',
 'tag-filter-submit' => 'Filtriraj',
+'tag-list-wrapper' => '([[Special:Tags|{{PLURAL:$1|Oznaka|Oznake}}]]: $2)',
 'tags-title' => 'Oznake',
 'tags-intro' => 'Na ovoj stranici je naveden spisak oznaka s kojima program može da označi izmene i njegovo značenje.',
 'tags-tag' => 'Naziv oznake',
@@ -4190,17 +4196,17 @@ Trebalo bi da ste primili [{{SERVER}}{{SCRIPTPATH}}/COPYING primerak GNU-ove op
 'sqlite-no-fts' => '$1 bez podrške pretrage celog teksta',
 
 # New logging system
-'logentry-delete-delete' => '$1 {{GENDER:|je obrisao|je obrisala|je obrisao}} $3',
-'logentry-delete-restore' => '$1 {{GENDER:|je vratio|je vratila|je vratio}} stranicu $3',
-'logentry-delete-event' => '$1 {{GENDER:$2|je promenio|je promenila|je promenio}} vidljivost {{PLURAL:$5|događaja|$5 događaja|$5 događaja}} u dnevniku na $3: $4',
-'logentry-delete-revision' => '$1 {{GENDER:|je promenio|je promenila|je promenio}} vidljivost {{PLURAL:$5|izmene|$5 izmene|$5 izmena}} na stranici $3: $4',
+'logentry-delete-delete' => '$1 je {{GENDER:$2|obrisao|obrisala}} stranicu $3',
+'logentry-delete-restore' => '$1 je {{GENDER:$2|vratio|vratila}} stranicu $3',
+'logentry-delete-event' => '$1 je {{GENDER:$2|promenio|promenila}} vidljivost {{PLURAL:$5|događaja|$5 daogađaja}} u dnevniku na $3: $4',
+'logentry-delete-revision' => '$1 je {{GENDER:$2|promenio|promenila}} vidljivost {{PLURAL:$5|izmene|$5 izmjena}} na stranici $3: $4',
 'logentry-delete-event-legacy' => '$1 je {{GENDER:$2|promenio|promenila}} vidljivost događaja u dnevniku na $3',
-'logentry-delete-revision-legacy' => '$1 {{GENDER:|je promenio|je promenila|je promenio}} vidljivost izmenâ na stranici $3',
-'logentry-suppress-delete' => '$1 {{GENDER:|je potisnuo|je potisnula|je potisnuo}} stranicu $3',
-'logentry-suppress-event' => '$1 je potajno {{GENDER:|promenio|promenila|promenio}} vidljivost {{PLURAL:$5|događaja|$5 događaja|$5 događaja}} u dnevniku na $3: $4',
-'logentry-suppress-revision' => '$1 je potajno {{GENDER:|promenio|promenila|promenio}} vidljivost {{PLURAL:$5|izmene|$5 izmene|$5 izmena}} na stranici $3: $4',
-'logentry-suppress-event-legacy' => '$1 je potajno {{GENDER:|promenio|promenila|promenio}} vidljivost događajâ u dnevniku na $3',
-'logentry-suppress-revision-legacy' => '$1 je potajno {{GENDER:|promenio|promenila}} vidljivost izmena na stranici $3',
+'logentry-delete-revision-legacy' => '$1 je {{GENDER:$2|promenio|promenila}} vidljivost izmena na stranici $3',
+'logentry-suppress-delete' => '$1 je {{GENDER:$2|potisnuo|potisnula}} stranicu $3',
+'logentry-suppress-event' => '$1 je tajno {{GENDER:$2|promenio|promenila}} vidljivost {{PLURAL:$5|događaja|$5 događaja}} u dnevniku na $3: $4',
+'logentry-suppress-revision' => '$1 je tajno {{GENDER:$2|promenio|promenila}} vidljivost {{PLURAL:$5|izmene|$5 izmena}} na stranici $3: $4',
+'logentry-suppress-event-legacy' => '$1 је tajno {{GENDER:$2|promenio|promenila}} vidljivost događaj u dnevniku na $3',
+'logentry-suppress-revision-legacy' => '$1 je tajno {{GENDER:$2|promenio|promenila}} vidljivost izmena na stranici $3',
 'revdelete-content-hid' => 'sadržaj je sakriven',
 'revdelete-summary-hid' => 'opis izmene je sakriven',
 'revdelete-uname-hid' => 'korisničko ime je sakriveno',
@@ -4213,15 +4219,15 @@ Trebalo bi da ste primili [{{SERVER}}{{SCRIPTPATH}}/COPYING primerak GNU-ove op
 'logentry-move-move-noredirect' => '$1 je {{GENDER:$2|premestio|premestila}} stranicu $3 na $4 bez ostavljanja preusmerenja',
 'logentry-move-move_redir' => '$1 je {{GENDER:$2|premestio|premestila}} stranicu $3 na $4 preko preusmerenja',
 'logentry-move-move_redir-noredirect' => '$1 je {{GENDER:|premestio|premestila}} stranicu $3 na $4 preko preusmerenja bez ostavljanja preusmerenja',
-'logentry-patrol-patrol' => '$1 {{GENDER:|je označio|je označila|je označio}} izmenu $4 stranice $3 kao patroliranu',
+'logentry-patrol-patrol' => '$1 je {{GENDER:$2|osznačio|označila}} izenu $4 stranice $3 kao patroliranu',
 'logentry-patrol-patrol-auto' => '$1 je automatski {{GENDER:$2|označio|označila}} izmenu $4 stranice $3 kao pregledanu',
 'logentry-newusers-newusers' => '$1 je {{GENDER:$2|otvorio|otvorila}} korisnički nalog',
 'logentry-newusers-create' => '$1 je {{GENDER:$2|otvorio|otvorila}} korisnički nalog',
 'logentry-newusers-create2' => '$1 je {{GENDER:$2|otvorio|otvorila}} korisnički nalog $3',
 'logentry-newusers-autocreate' => 'Korisnički nalog $1 je automatski {{GENDER:$2|otvoren}}',
 'logentry-rights-rights' => '$1 je {{GENDER:$1|promenio|promenila}} članstvo grupe za $3 iz $4 u $5',
-'logentry-rights-rights-legacy' => '$1 {{GENDER:$1|je promenio|je promenila|je promenio}} članstvo grupe za $3',
-'logentry-rights-autopromote' => '$1 je automatski {{GENDER:$1|unapređen|unapređena|unapređen}} iz $4 u $5',
+'logentry-rights-rights-legacy' => '$1 je {{GENDER:$2|promenio|promenila}} čalnstvo grupe za $3',
+'logentry-rights-autopromote' => '$1 je automatski {{GENDER:$1|unapređen|unapređena}} iz $4 u $5',
 'rightsnone' => '(ništa)',
 
 # Feedback
index 16d4439..cd7ffa8 100644 (file)
@@ -844,8 +844,8 @@ fortsätta använda ditt gamla lösenord.',
 'noemailcreate' => 'Du måste ange en giltig e-postadress',
 'passwordsent' => 'Ett nytt lösenord har skickats till den e-postadress som användaren "$1" har registrerat. När du får meddelandet, var god logga in igen.',
 'blocked-mailpassword' => 'Din IP-adress är blockerad, därför kan den inte användas för att få ett nytt lösenord.',
-'eauthentsent' => 'Ett e-brev för bekräftelse har skickats till den e-postadress som angivits.
-Innan någon annan e-post kan skickas härifrån till kontot, måste du följa instruktionerna i e-brevet för att bekräfta att kontot verkligen är ditt.',
+'eauthentsent' => 'Ett e-postmeddelande för bekräftelse har skickats till den angivna e-postadressen.
+Innan någon annan e-post kan skickas till kontot, måste du följa instruktionerna i e-postmeddelandet för att bekräfta att kontot verkligen är ditt.',
 'throttled-mailpassword' => 'En lösenordsåterställning har redan skickats för mindre än {{PLURAL:$1|en timme|$1 timmar}} sedan.
 För att förhindra missbruk skickas bara en lösenordsåterställning per {{PLURAL:$1|timme|$1-timmarsperiod}}.',
 'mailerror' => 'Fel vid skickande av e-post: $1',
@@ -3985,7 +3985,7 @@ Du bör ha fått [{{SERVER}}{{SCRIPTPATH}}/COPYING en kopia av GNU General Publi
 # Special:Redirect
 'redirect' => 'Omdirigering efter filnamn, användar-ID eller versions-ID',
 'redirect-legend' => 'Omdirigera till en fil eller sida',
-'redirect-summary' => 'Den här specialsidan omdirigerar till en fil (efter filnamn), en sida (efter versions-id) eller en användarsida (efter användar-id).',
+'redirect-summary' => 'Den här specialsidan omdirigerar till en fil (efter filnamn), en sida (efter versions-id) eller en användarsida (efter användar-id). Användning: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], eller [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit' => 'Kör',
 'redirect-lookup' => 'Slå upp:',
 'redirect-value' => 'Värde:',
index b311126..1a0cca4 100644 (file)
@@ -70,15 +70,15 @@ $messages = array(
 'tog-justify' => 'Wyrůwnowej tekst we akapitach (justowańy)',
 'tog-hideminor' => 'Schow drobne pomjyńańa we ńydowno pomjyńanych',
 'tog-hidepatrolled' => 'Schow sprowdzůne sprowjyńa we ńydowno pomjyńanych',
-'tog-newpageshidepatrolled' => 'Schow sprowdzůne zajty na wykoźe nowych zajtůw',
-'tog-extendwatchlist' => 'Pokoż na mojij pozůrliśće wszyjske, a ńyjyno uostatńe sprowjyńa',
+'tog-newpageshidepatrolled' => 'Schow sprawdzůne zajty na wykoźe nowych zajtůw',
+'tog-extendwatchlist' => 'Pokoż na mojij pozůrliśće wszyjske, a ńy jyno uostatńe sprowjyńa',
 'tog-usenewrc' => 'Używej poszyrzyńo ńydowno pomjyńanych (JavaScript)',
 'tog-numberheadings' => 'Automatyczno numeracyjo titlůw',
 'tog-showtoolbar' => 'Pokoż gurt werkcojgůw (JavaScript)',
 'tog-editondblclick' => 'Edycyjo napoczynajům dwa klikńyńća (JavaScript)',
 'tog-editsection' => 'Kożdo tajla zajty sprowjano uosobno',
 'tog-editsectiononrightclick' => 'Klikńyńće prawym kneflym myszy na titlu tajli<br />napoczyno jigo sprowjańy(JavaScript)',
-'tog-showtoc' => 'Pokoż spis treśći (na zajtach, kere majům wjyncy jak trzi tajle)',
+'tog-showtoc' => 'Pokoż spis treśći (na zajtach, kere majům wjyncyj kej trzi tajle)',
 'tog-rememberpassword' => 'Pamjyntej můj ausdruk na tym kůmputrze (nojdalij bez $1 {{PLURAL:$1|dźyń|dńůw}})',
 'tog-watchcreations' => 'Dowům pozůr na zajty, kere żech naszkryfloł',
 'tog-watchdefault' => 'Dowům pozůr na zajty, kere żech sprowjoł',
@@ -89,12 +89,12 @@ $messages = array(
 'tog-previewonfirst' => 'Obźyrej zajta przi pjyrszym sprowjańu',
 'tog-nocache' => 'Wypńij podrynczno pamjyńć',
 'tog-enotifwatchlistpages' => 'Wyślij e-brifa, kej ftoś zmjyńi zajta, na kero dowom pozůr',
-'tog-enotifusertalkpages' => 'Wyślij e-brifa, kej zajta mojij godki bydźe pomjyńono',
+'tog-enotifusertalkpages' => 'Wyślij e-brifa, kej zajta mojij godki bydźe půmjyńono',
 'tog-enotifminoredits' => 'Wyślij e-brifa tyż, kej by szło uo drobne pomjyńańa',
 'tog-enotifrevealaddr' => 'Ńy chow mojigo e-brifa we powjadomjyńach',
 'tog-shownumberswatching' => 'Pokoż, wjela sprowjorzy dowo pozůr',
 'tog-oldsig' => 'Teroźni wyglůnd Twojygo szrajbowańo',
-'tog-fancysig' => 'Szrajbńij s kodůma wiki (bez autůmatycznygo linka)',
+'tog-fancysig' => 'Szrajbńij ze kodůma wiki (bez autůmatycznygo linka)',
 'tog-uselivepreview' => 'Używej dynamiczne uobźyrańy (JavaScript) (ekszperymentalny)',
 'tog-forceeditsummary' => 'Pedź, kejbych ńic ńy naszkryfloł we uopiśe pomjyńań',
 'tog-watchlisthideown' => 'Schow moje pomjyńańa we artiklach, na kere dowom pozůr',
@@ -104,7 +104,7 @@ $messages = array(
 'tog-watchlisthideanons' => 'Schow sprowjyńa anůńimowych sprowjoczy na liśće artikli, na kere dowom pozůr',
 'tog-watchlisthidepatrolled' => 'Schowej sprowdzůne sprowjyńa na pozorliśće',
 'tog-ccmeonemails' => 'Przesyłej mi kopje e-brifůw co żech je posłoł inkszym sprowjaczom',
-'tog-diffonly' => 'Å\83y pokozuj treÅ\9bÄ\87i zajtůw půnižyj porůwnańo pomjyńań',
+'tog-diffonly' => 'Å\83y pokozuj treÅ\9bÄ\87i zajtůw půniżyj porůwnańo pomjyńań',
 'tog-showhiddencats' => 'Pokoż schowane kategoryje',
 'tog-norollbackdiff' => 'Uomiń pokozywańy pomjyńań po użyću funkcyje „cofej”',
 'tog-useeditwarning' => 'Uostrzegej mje, kej uopuszczom zajta edycyji bez spamjyntańo půmjań',
@@ -138,11 +138,11 @@ $messages = array(
 'sat' => 'Sob',
 'january' => 'styczyń',
 'february' => 'luty',
-'march' => 'merc',
+'march' => 'marzec',
 'april' => 'kwjećyń',
 'may_long' => 'moj',
-'june' => 'czyrwjyń',
-'july' => 'lipjyń',
+'june' => 'czyrwjyc',
+'july' => 'lipjyc',
 'august' => 'śyrpjyń',
 'september' => 'wrześyń',
 'october' => 'paźdźerńik',
@@ -150,19 +150,19 @@ $messages = array(
 'december' => 'grudźyń',
 'january-gen' => 'styczńa',
 'february-gen' => 'lutygo',
-'march-gen' => 'merca',
+'march-gen' => 'marca',
 'april-gen' => 'kwjetńa',
-'may-gen' => 'maja',
-'june-gen' => 'czyrwńa',
-'july-gen' => 'lipńa',
+'may-gen' => 'moja',
+'june-gen' => 'czyrwca',
+'july-gen' => 'lipca',
 'august-gen' => 'śyrpńa',
-'september-gen' => 'wrześńa',
+'september-gen' => 'wrzyśńa',
 'october-gen' => 'paźdźerńika',
 'november-gen' => 'listopada',
 'december-gen' => 'grudńa',
 'jan' => 'sty',
 'feb' => 'lut',
-'mar' => 'mer',
+'mar' => 'mar',
 'apr' => 'kwj',
 'may' => 'moj',
 'jun' => 'czy',
@@ -183,10 +183,10 @@ $messages = array(
 'december-date' => '$1 grudńa',
 
 # Categories related messages
-'pagecategories' => '{{PLURAL:$1|Kategoryjo|Kategoryje|Kategoryj}}',
+'pagecategories' => '{{PLURAL:$1|Kategoryjo|Kategoryje|Kategoryji}}',
 'category_header' => 'Zajty we katygoryji "$1"',
 'subcategories' => 'Podkatygoryje',
-'category-media-header' => 'Pliki w katygoryji "$1"',
+'category-media-header' => 'Pliki we katygoryji "$1"',
 'category-empty' => "''Terozki w tyj katygoryji sům żodne artikle a pliki''",
 'hidden-categories' => '{{PLURAL:$1|Schowano katygoryjo|Schowane katygoryje|Schowanych katygoryj}}',
 'hidden-category-category' => 'Schowane katygoryje',
@@ -194,7 +194,7 @@ $messages = array(
 'category-subcat-count-limited' => 'Ta katygoryjo mo {{PLURAL:$1|tako podkatygoryjo|$1 podkatygoryje|$1 podkatygoryji}}.',
 'category-article-count' => '{{PLURAL:$2|W tyj katygoryji je jyno jydno zajta.|W katygoryji {{PLURAL:$1|je ukozano $1 zajta|sům ukozane $1 zajty|je ukozanych $1 zajtůw}} ze cołkij wjelośći $2 zajtůw.}}',
 'category-article-count-limited' => 'W katygoryji {{PLURAL:$1|je pokozano $1 zajta|sům pokozane $1 zajty|je pokazanych $1 zajtůw}}.',
-'category-file-count' => '{{PLURAL:$2|W katygoryji snojduje śe jydyn plik.|W katygoryji {{PLURAL:$1|je pokozany $1 plik|sům pokozane $1 pliki|je pokozanych $1 plikůw}} s cołkyj liczby $2 plikůw.}}',
+'category-file-count' => '{{PLURAL:$2|W katygoryji znojduje śe jydyn plik.|W katygoryji {{PLURAL:$1|je pokozany $1 plik|sům pokozane $1 pliki|je pokozanych $1 plikůw}} ze cołkyj liczby $2 plikůw.}}',
 'category-file-count-limited' => 'W katygoryji {{PLURAL:$1|je pokozany $1 plik|sům pokozane $1 pliki|je pokozanych $1 plikůw}}.',
 'listingcontinuesabbrev' => 'ć.d.',
 'index-category' => 'Indeksowane zajty',
@@ -205,7 +205,7 @@ $messages = array(
 'article' => 'zajta',
 'newwindow' => '(uodwjyro śe we nowym uokńe)',
 'cancel' => 'Uodćepej',
-'moredotdotdot' => 'Wjyncy...',
+'moredotdotdot' => 'Wjyncyj...',
 'morenotlisted' => 'Ńy je to kůmplytno lista',
 'mypage' => 'Zajta',
 'mytalk' => 'Dyskusyjo',
@@ -219,7 +219,7 @@ $messages = array(
 'qbedit' => 'Sprowjej',
 'qbpageoptions' => 'Ta zajta',
 'qbmyoptions' => 'Moje zajty',
-'qbspecialpages' => 'Szpecyjalne zajty',
+'qbspecialpages' => 'Szpecyjolne zajty',
 'faq' => 'FAQ',
 'faqpage' => 'Project:FAQ',
 
@@ -252,9 +252,9 @@ $messages = array(
 'history' => 'Gyszichta zajty',
 'history_short' => 'Gyszichta',
 'updatedmarker' => 'pomjyńane uod uostatńij wizyty',
-'printableversion' => 'Wersyjo do durku',
+'printableversion' => 'Wersyjo do druku',
 'permalink' => 'Link do tyj wersyje zajty',
-'print' => 'Durkuj',
+'print' => 'Drukuj',
 'view' => 'Podglůnd',
 'edit' => 'Sprowjej',
 'create' => 'Stwůrz',
@@ -267,13 +267,13 @@ $messages = array(
 'viewdeleted_short' => '{{PLURAL:$1|jedna wyćepano wersyjo|$1 wyćepane wersyje|$1 wyćepanych wersyjůw}}',
 'protect' => 'Zawrzij',
 'protect_change' => 'půmjyń',
-'protectthispage' => 'Zawřij ta zajta',
+'protectthispage' => 'Zawrzij ta zajta',
 'unprotect' => 'Uodymkńij',
 'unprotectthispage' => 'Uodymkńij ta zajta',
 'newpage' => 'Nowy artikel',
 'talkpage' => 'Godej uo tym artiklu',
 'talkpagelinktext' => 'dyskusyjo',
-'specialpage' => 'Špecyjalno zajta',
+'specialpage' => 'Szpecyjolno zajta',
 'personaltools' => 'Perzůnolne',
 'postcomment' => 'Skůmyntuj',
 'articlepage' => 'Zajta artikla',
@@ -282,14 +282,14 @@ $messages = array(
 'toolbox' => 'Werkcojg',
 'userpage' => 'Zajta sprowjorza',
 'projectpage' => 'Zajta projekta',
-'imagepage' => 'Zobejrz zajte pliku',
+'imagepage' => 'Uobejrz zajta pliku',
 'mediawikipage' => 'Zajta komuńikata',
-'templatepage' => 'Zajta šablůna',
-'viewhelppage' => 'Zajta pomocy',
+'templatepage' => 'Zajta mustra',
+'viewhelppage' => 'Zajta půmocy',
 'categorypage' => 'Zajta katygoryji',
 'viewtalkpage' => 'Zajta godki',
 'otherlanguages' => 'We inkszych godkach',
-'redirectedfrom' => '(Punkńyńto s $1)',
+'redirectedfrom' => '(Punkńyńto ze $1)',
 'redirectpagesub' => 'Zajta przekerowujůnco',
 'lastmodifiedat' => 'Ta zajta bůła uostatńo sprowjano $2, $1.',
 'viewcount' => 'W ta zajta filowano {{PLURAL:$1|tylko roz|$1 rozůw}}.',
@@ -297,12 +297,12 @@ $messages = array(
 'jumpto' => 'Przyńdź do:',
 'jumptonavigation' => 'nawigacyje',
 'jumptosearch' => 'sznupańo',
-'view-pool-error' => 'Felerńe, syrwyry sům przecionżone.
+'view-pool-error' => 'Felerńe, syrwyry sům przećůnżone.
 
 $1',
-'pool-timeout' => 'Zbyt długi czas oczekiwania na blokadę',
-'pool-queuefull' => 'Kolejka zadań jest pełna',
-'pool-errorunknown' => 'Feler ńyznany',
+'pool-timeout' => 'Za dugi czas uoczekiwańo na blokada',
+'pool-queuefull' => 'Kolyjność zadań je pełno',
+'pool-errorunknown' => 'Feler ńyznony',
 
 # All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).
 'aboutsite' => 'Uo {{GRAMMAR:MS.lp|{{SITENAME}}}}',
@@ -317,18 +317,18 @@ $1',
 'helppage' => 'Help:Treść',
 'mainpage' => 'Przodńo zajta',
 'mainpage-description' => 'Przodńo zajta',
-'policy-url' => 'Project:Prawidua',
+'policy-url' => 'Project:Prawidła',
 'portal' => 'Portal używoczůw',
 'portal-url' => 'Project:Portal używoczůw',
 'privacy' => 'Prawidła chrůńyńo prywotnośći',
 'privacypage' => 'Project:Prawidła chrůńyńo prywotnośći',
 
 'badaccess' => 'Felerne uprawńyńo',
-'badaccess-group0' => 'Ńy moš uprawńyń coby wykůnać ta uoperacyjo.',
-'badaccess-groups' => 'Ta uoperacyjo mogům wykůnaÄ\87 ino užytkownicy s keryjś z grup {{PLURAL:$2|grupa|grupy}}:$1.',
+'badaccess-group0' => 'Ńy mosz uprawńyń coby wykůnać ta uoperacyjo.',
+'badaccess-groups' => 'Ta uoperacyjo mogům wykůnaÄ\87 ino użytkownicy ze keryjś z grup {{PLURAL:$2|grupa|grupy}}:$1.',
 
 'versionrequired' => 'Wymagano MediaWiki we wersyji $1',
-'versionrequiredtext' => 'Wymagano jest MediaWiki we wersji $1 coby skořistać s tyj zajty. Uoboč [[Special:Version]]',
+'versionrequiredtext' => 'Wymagano jest MediaWiki we wersji $1 coby skorzistać zr tyj zajty. Uobocz [[Special:Version]]',
 
 'ok' => 'OK',
 'retrievedfrom' => 'Zdrzůdło "$1"',
@@ -347,28 +347,28 @@ $1',
 'viewsourcelink' => 'zdrzůdłowy tekst',
 'editsectionhint' => 'Sprowjej tajla: $1',
 'toc' => 'Treść',
-'showtoc' => 'pokož',
+'showtoc' => 'uobejrzij',
 'hidetoc' => 'schrůń',
 'collapsible-collapse' => 'Zwjyń',
 'collapsible-expand' => 'Rozwjyń',
-'thisisdeleted' => 'Pokož/wćepej nazod $1',
-'viewdeleted' => 'Uobejřij $1',
+'thisisdeleted' => 'Pokoż/wćepej nazod $1',
+'viewdeleted' => 'Uobejrzij $1',
 'restorelink' => '{{PLURAL:$1|jedna wyćepano wersyjo|$1 wyćepane wersyje|$1 wyćepanych wersyjůw}}',
-'feedlinks' => 'Kanauy:',
-'feed-invalid' => 'Ńywuaściwy typ kanauů informacyjnygo.',
-'feed-unavailable' => 'Kanouy informacyjne ńy sům dostympne',
-'site-rss-feed' => 'Kanau RSS {{GRAMMAR:D.lp|$1}}',
+'feedlinks' => 'Kanały:',
+'feed-invalid' => 'Ńywłaściwy typ kanałů informacyjnygo.',
+'feed-unavailable' => 'Kanoły informacyjne ńy sům dostympne',
+'site-rss-feed' => 'Kan RSS {{GRAMMAR:D.lp|$1}}',
 'site-atom-feed' => 'Kanoł Atom {{GRAMMAR:D.lp|$1}}',
-'page-rss-feed' => 'Kanau RSS "$1"',
+'page-rss-feed' => 'Kan RSS "$1"',
 'page-atom-feed' => 'Kanoł Atom "$1"',
 'red-link-title' => '$1 (ńy mo zajty)',
-'sort-descending' => 'Sortuj malejąco',
-'sort-ascending' => 'Sortuj rosnąco',
+'sort-descending' => 'Sortuj pomńijszajůnco',
+'sort-ascending' => 'Sortuj rosnůnco',
 
 # Short words for each namespace, by default used in the namespace tab in monobook
 'nstab-main' => 'Zajta',
 'nstab-user' => '{{GENDER:{{BASEPAGENAME}}|Zajta używocza|Zajta używoczki}}',
-'nstab-media' => 'Medja',
+'nstab-media' => 'Pliki',
 'nstab-special' => 'Ekstra zajta',
 'nstab-project' => 'Zajta projektu',
 'nstab-image' => 'Plik',
@@ -380,10 +380,10 @@ $1',
 # Main script and global functions
 'nosuchaction' => 'Ńy mo takij uoperacyji',
 'nosuchactiontext' => 'Uoprogramowańy ńy rozpoznowo uoperacyji takij kej podano w URL.',
-'nosuchspecialpage' => 'Ńy mo takij špecyjalnyj zajty',
-'nospecialpagetext' => '<strong>Uoprogramowańy ńy rozpoznowo takij špecyjalnyj zajty.</strong>
+'nosuchspecialpage' => 'Ńy mo takij szpecyjolnyj zajty',
+'nospecialpagetext' => '<strong>Uoprogramowańy ńy rozpoznowo takij szpecyjalnyj zajty.</strong>
 
-Lista špecyjalnych zajtůw znejdźeš na [[Special:SpecialPages|{{int:specialpages}}]].',
+Lista szpecyjalnych zajtůw znojdźesz na [[Special:SpecialPages|{{int:specialpages}}]].',
 
 # General errors
 'error' => 'Feler',
@@ -393,13 +393,13 @@ Lista špecyjalnych zajtůw znejdźeš na [[Special:SpecialPages|{{int:specialpa
 'databaseerror-query' => 'Zapytańe: $1',
 'databaseerror-function' => 'Funkcyjo: $1',
 'databaseerror-error' => 'Feler: $1',
-'laggedslavemode' => 'Dej pozůr: Ta zajta može Å\84y mjeÄ\87 nojnowÅ¡ych aktualizacyjůw.',
+'laggedslavemode' => 'Dej pozůr: Ta zajta może Å\84y mjeÄ\87 nojnowszych aktualizacyjůw.',
 'readonly' => 'Baza danych je zawarto',
-'enterlockreason' => 'Naškryflej sam powůd zawarća bazy danych a za wjela (myńi-wjyncyj) ja uodymkńeš',
+'enterlockreason' => 'Naszkryflej sam powůd zawarćo bazy danych a za wjela (myńi-wjyncyj) ja uodymkńesz',
 'readonlytext' => 'Baza danych jest terozki zawarto
-- Å\84y do Å\9be wÄ\87epywaÄ\87 nowych artikli Å\84i sprowjaÄ\87 juž wćepanych. Powodym
-sům prawdopodańy čynnośći admińistracyjne. Po jejich zakůńčeńu pouno funkcjonalność bazy bydźe přywrůcono.
-Administrator, kery zablokowou baza, podou takie wyjaśńyńy:<br /> $1',
+- Å\84y do Å\9be wÄ\87epywaÄ\87 nowych artikli Å\84i sprowjaÄ\87 już wćepanych. Powodym
+sům prawdopodańy czynnośći admińistracyjne. Po jejich zakůńczyńu cołko funkcjonalność bazy bydźe prziwrůcono.
+Administrator, kery zablokowoł baza, podoł takie wyjaśńyńy:<br /> $1',
 'missing-article' => 'W databaźe ńy idzie nolyźć treść zajty „$1” $2.
 
 Uobwykle je to spůsobiůne tym, że sznupoł żeś po ńyaktualnym linku na zmjyny mjyndzy půmjyńańami, abo do wyćepanyj wersyje z gyszichty sprowjyń zajty.
@@ -407,46 +407,48 @@ Uobwykle je to spůsobiůne tym, że sznupoł żeś po ńyaktualnym linku na zmj
 Eli tak ńy je, możno śe trefił feler we softwaru MediaWiki. Kej ja, pedz uo tym [[Special:ListUsers/sysop|admińistratorowi]] a podej mu adres URL.',
 'missingarticle-rev' => '(wersyjo#: $1)',
 'missingarticle-diff' => '(dyferencyjo: $1, $2)',
-'readonly_lag' => 'Baza danych zostoua automatyčńy zawarto na čas potřebny na synchrońizacyjo zmjan mjyndzy serwerym guůwnym a serwerami postředńičůncymi.',
-'internalerror' => 'Wewnyntřny feler',
-'internalerror_info' => 'Wewnytřny feler: $1',
+'readonly_lag' => 'Baza danych zostoła automatyczńy zawarto na czas potrzebny na synchrońizacyjo zmjan mjyndzy serwerym głůwnym a serwerami postrzedńiczůncymi.',
+'internalerror' => 'Wewnyntrzny feler',
+'internalerror_info' => 'Wewnytrzny feler: $1',
 'fileappenderrorread' => 'Feler uodczytu "$1".',
 'fileappenderror' => 'Ńy idźe skopjować plika "$1" do "$2".',
 'filecopyerror' => 'Ńy idźe skopjować plika "$1" do "$2".',
 'filerenameerror' => 'Ńy idźe zmjyńić mjana plika "$1" na "$2".',
 'filedeleteerror' => 'Ńy idźe wyćepać plika "$1".',
-'directorycreateerror' => 'Ńy idźe utwořić katalogu "$1".',
+'directorycreateerror' => 'Ńy idźe utworzić katalogu "$1".',
 'filenotfound' => 'Ńy idźe znejść plika "$1".',
 'fileexistserror' => 'Ńy idźe sprowjać we pliku "$1": plik istńeje',
 'unexpected' => 'Ńyspodźewano wartość: "$1"="$2".',
-'formerror' => 'Feler: ńy idźe wysuać formulařa',
+'formerror' => 'Feler: ńy idźe wysłać formulazra',
 'badarticleerror' => 'Tyj uoperacyje ńy idźe zrobić lo tyj zajty.',
 'cannotdelete' => 'Ńy idźe wyćepać podanyj zajty abo grafiki $1.',
 'cannotdelete-title' => 'Ńy idźie wyćepać zajty "$1".',
 'delete-hook-aborted' => 'Wyćepywańe sztopńynte bez hak. Przyczyna ńyuokreślůno.',
-'badtitle' => 'Felerno tytůua',
-'badtitletext' => 'Podano felerny titel zajty. PrawdopodaÅ\84y sům w Å\84im znoki, kerych Å\84y wolno užywać we titlach abo je pusty.',
-'perfcached' => 'To co sam je naszkryflane, to ino kopja s pamjyńći podryncznyj a może ńy być aktualne. Nojwjyncyj {{PLURAL:$1|jydyn wynik je|$1 wyniki sům}} w tyj pamjyńći.',
+'badtitle' => 'Felerny titel',
+'badtitletext' => 'Podano felerny titel zajty. PrawdopodaÅ\84y sům w Å\84im znoki, kerych Å\84y wolno używać we titlach abo je pusty.',
+'perfcached' => 'To co sam je naszkryflane, to ino kopja ze pamjyńći podryncznyj a może ńy być aktualne. Nojwjyncyj {{PLURAL:$1|jydyn wynik je|$1 wyniki sům}} we tyj pamjyńći.',
 'perfcachedts' => 'To co sam je naszkryflane, to ino kopja s pamjyńći podryncznyj a bůło uaktualńůne $1. Nojwjyncyj {{PLURAL:$4|jeden wynik je|$4 wyniki sům}} dostympne.',
-'querypage-no-updates' => 'UaktualÅ\84\84o lo tyj zajty sům terozki zawarte. Dane, kere sam sům, Å\84y zostouy uodÅ\9bwjyžůne.',
-'wrong_wfQuery_params' => 'Felerne parametry překozane do wfQuery()<br />
+'querypage-no-updates' => 'UaktualÅ\84\84o lo tyj zajty sům terozki zawarte. Dane, kere sam sům, Å\84y zostouy uodÅ\9bwjyżůne.',
+'wrong_wfQuery_params' => 'Felerne parametry przekozane do wfQuery()<br />
 Funkcyjo: $1<br />
 Zapytańy: $2',
 'viewsource' => 'Zdrzůdłowy tekst',
 'viewsource-title' => 'Uobocz zdrzůdło lo $1',
-'actionthrottled' => 'Akcyjo wstřimano',
-'actionthrottledtext' => 'Mechańizm uobrůny před spamym uograńičo ličba wykonań tyj čynnośći we jednostce času. Průbowoužeś go uocygańić. Proša, sprůbuj na nowo za pora minut.',
+'actionthrottled' => 'Akcyjo wstrzimano',
+'actionthrottledtext' => 'Mechańizm uobrůny przed spamym uograńiczo liczba wykonań tyj czynnośći we jednostce czasu. Průbowołżeś go uocygańić. Prosza, sprůbuj na nowo za pora minut.',
 'protectedpagetext' => 'Ta zajta je zawarto przed sprowjańym.',
 'viewsourcetext' => 'We tekst zdrzůduowy tyj zajty możno dali filować, idźe go tyż kopjyrować.',
 'viewyourtext' => 'We tekst zdrzůduowy tyj zajty możno dali filować, idźe go tyż kopjować.',
-'protectedinterface' => 'Na tyj zajće znojduje śe tekst interfejsu uoprogramowańo, bestož uůna je zawarto uod sprowjańo.',
-'editinginterface' => "''''Dej pozůr:''' Sprowjosz zajta, na keryj je tekst interfejsu uoprogramowańo. Pomjyńyńa na tyj zajće zmjyńům wyglůnd interfejsu lo inkšych užytkowńikůw.",
-'cascadeprotected' => 'Ta zajta je zawarto uod sprowjańo, po takymu, co uůna je zauončono na {{PLURAL:$1|nastympujůncyj zajće, kero zostaua zawarto|nastympujůncych zajtach, kere zostauy zawarte}} ze zauončonům opcyjům dźedźičyńo:
+'protectedinterface' => 'Na tyj zajće znojduje śe tekst interfejsu uoprogramowańo, bestůż uůna je zawarto uod sprowjańo. Coby doćepnůńć abo sprowjić tůmaczyńa wszyskich serwerůw, użyj [//translatewiki.net/ translatewiki.net], průjyktu lokalizacyji MediaWiki.',
+'editinginterface' => "''''Dej pozůr:''' Sprowjosz zajta, na keryj je tekst interfejsu uoprogramowańo. Pomjyńyńa na tyj zajće zmjyńům wyglůnd interfejsu lo inkszych użytkowńikůw. Coby doćepnůńć abo sprowjić tůmaczyńa, użyj [//translatewiki.net/wiki/Main_Page?setlang=szl translatewiki.net].",
+'cascadeprotected' => 'Ta zajta je zawarto uod sprowjańo, po takymu, co uůna je załůnczůno na {{PLURAL:$1|nastympujůncyj zajće, kero zostoła zawarto|nastympujůncych zajtach, kere zostoły zawarte}} ze załůnczůnům uopcyjům dźedźiczyńo:
 $2',
-'namespaceprotected' => "Ńy moš uprowńyń, coby sprowjać zajty we přestřeńi mjan '''$1'''.",
+'namespaceprotected' => "Ńy mosz uprowńyń, coby sprowjać zajty we raumje mjan '''$1'''.",
 'customcssprotected' => 'Ńy mosz uprawńyń do sprowjańo tyj zajty, bo na ńij sům uosobiste sztalowańo inkszego użytkowńika.',
 'customjsprotected' => 'Ńy mosz uprawńyń do sprowjańo tyj zajty, bo na ńij sům uosobiste sztalowańo inkszego użytkowńika.',
-'ns-specialprotected' => 'Ńy idźe sprowjać zajtůw we přestřyni mjan {{ns:special}}.',
+'mycustomcssprotected' => 'Ńy mosz uprawńyń do sprowjańo tyj zajty CSS.',
+'mycustomjsprotected' => 'Ńy mosz uprawńyń do sprowjańo tyj zajty JavaScript.',
+'ns-specialprotected' => 'Ńy idźe sprowjać zajtůw we przestrzyni mjan {{ns:special}}.',
 'titleprotected' => "Wćepańy sam zajty uo takim mjańe zawar [[User:$1|$1]].
 Powůd zawarćo: ''$2''.",
 'filereadonlyerror' => 'Ńy idźe pomjyńać plika "$1" abo repozytorjum "$2" terozki je zawarte.
@@ -464,7 +466,6 @@ Administrator kery zawarł wćepał kůmyntorz: "$3".',
 # Login and logout pages
 'logouttext' => "'''Terozki jeżeś wylůgowany'''.
 
-Możesz dali sam sprowjać zajty we {{SITENAME}} kej ńyzalůgowany sprowjorz, abo <span class='plainlinks'>[$1 zalůgować śe nazod]</span> kej tyn som abo inkszy używocz.
 Dej pozůr, co na ńykerych zajtach przeglůndarka może dali pokozywać co jeżeś zalůgowany, a bydźe tak aże uodśwjyżysz jeij cache.",
 'welcomeuser' => 'Witej, $1',
 'welcomecreation-msg' => 'Uotwarli my sam lo Ćebje kůnto.
@@ -484,15 +485,15 @@ Pamjyntej coby posztalować [[Special:Preferences|preferencyji]]',
 'userlogin-signwithsecure' => 'Użyj bezpjecznygo połůnczyńa',
 'yourdomainname' => 'Twoja domyna',
 'password-change-forbidden' => 'Ńy można půmjyńać haseł na tyj wiki.',
-'externaldberror' => 'Je jaki feler we zewnyntřnyj baźe autentyfikacyjnyj, abo ńy moš uprawńyń potřebnych do aktualizacyji zewnyntřnego kůnta.',
+'externaldberror' => 'Je jaki feler we zewnyntrznyj baźe autentyfikacyjnyj, abo ńy mosz uprawńyń potrzebnych do aktualizacyji zewnyntrznego kůnta.',
 'login' => 'Zaloguj śe',
-'nav-login-createaccount' => 'Logowańy / tworzińy kůnta',
-'loginprompt' => 'Muśisz mjeć zołůnczůne cookies coby můc śe sam zalůgować.',
-'userlogin' => 'Lůgowańy / Tworzyńy kůnta',
-'userloginnocreate' => 'Zalůguj śe',
+'nav-login-createaccount' => 'Logowańy / Tworzyńy kůnta',
+'loginprompt' => 'Muśisz mjeć załůnczůne cookies coby můc śe sam zalogować.',
+'userlogin' => 'Logowańy / Tworzyńy kůnta',
+'userloginnocreate' => 'Zaloguj śe',
 'logout' => 'Wyloguj',
 'userlogout' => 'Uodloguj śe',
-'notloggedin' => 'Å\83y ježeÅ\9b zalůgowany',
+'notloggedin' => 'Å\83y jeżeÅ\9b zalogowany',
 'userlogin-noaccount' => 'Ńy mosz kůnta?',
 'userlogin-joinproject' => 'Doćep śe do {{SITENAME}}',
 'nologin' => "Ńy mosz kůnta? '''$1'''.",
@@ -501,82 +502,105 @@ Pamjyntej coby posztalować [[Special:Preferences|preferencyji]]',
 'gotaccount' => "Mosz już kůnto? '''$1'''.",
 'gotaccountlink' => 'Naloguj śe',
 'userlogin-resetlink' => 'Zapomńoł żeś dane lo nalogowańo?',
+'userlogin-resetpassword-link' => 'Ńy pamjyntosz hasła?',
+'helplogin-url' => 'Help:Logowańy',
+'userlogin-helplink' => '[[{{MediaWiki:helplogin-url}}|Hilfa ze logowańym]]',
+'userlogin-loggedin' => 'Zalogowano kej {{GENDER:$1|$1}}. Użyj formulara půńiżyj, coby zalogować śe kej inkszy używocz.',
+'userlogin-createanother' => 'Twůrz inksze kůnto',
+'createacct-join' => 'Wszkryflej půńiżyj swoje dane.',
+'createacct-another-join' => 'Wszkryflej půńiżyj szczegůły nowygo kůnta.',
+'createacct-emailrequired' => 'E-brif',
+'createacct-emailoptional' => 'E-brif (uopcjůnalne)',
+'createacct-email-ph' => 'Wszkryflej swůj adres do e-brifa',
+'createacct-another-email-ph' => 'Nastow e-brif',
 'createaccountmail' => 'Użyj chwilowygo hasła losowo genyrowanygo a wyślij je na wrychtowany adres e-brifa.',
 'createacct-realname' => 'Prawdźiwe imje a nazwisko (uopcjůnalńe)',
 'createaccountreason' => 'Kůmyntorz:',
 'createacct-reason' => 'Powůd:',
-'badretype' => 'Hasua kere žeś naškryflou ńy zgodzajům śe jydne s drugim.',
+'createacct-reason-ph' => 'Pojakymu tworzisz nowe kůnta',
+'createacct-captcha' => 'Zicherkontrola',
+'createacct-imgcaptcha-ph' => 'Wszkryflej tekst, kery widoć powyżyj',
+'createacct-submit' => 'Twůrz kůnto',
+'createacct-another-submit' => 'Twůrz inksze kůnto',
+'createacct-benefit-heading' => '{{grammar:B.lp|{{SITENAME}}}} tworzům perzůny take kej Ty.',
+'createacct-benefit-body1' => '{{PLURAL:$1|edycyjo|edycyje|edycyji}}',
+'createacct-benefit-body2' => '{{PLURAL:$1|zajta|zajty|zajt}}',
+'createacct-benefit-body3' => '{{PLURAL:$1|używocz|używoczůw}} we uostatńim czaśe',
+'badretype' => 'Hasła kere żeś naszkryfloł ńy zgodzajům śe jydne ze drugim.',
 'userexists' => 'Mjano użytkowńika, kere żeś wybroł, je zajynte. Wybjer, prosza, inksze mjano.',
-'loginerror' => 'Feler při logůwańu',
+'loginerror' => 'Feler przi logowańu',
+'createacct-error' => 'Feler tworzyńo kůnta',
 'createaccounterror' => 'Ńy możno stworzić konta $1',
 'nocookiesnew' => 'Kůnto użytkowńika zostoło utworzůne, nale ńy jeżeś zalůgowany. {{SITENAME}} używo ćosteczek do logůwańo. Mosz wyłůnczone ćosteczka. Coby śe zalůgować, uodymknij ćosteczka a podej mjano a hasło swojigo kůnta.',
-'nocookieslogin' => '{{SITENAME}} užywo Ä\87osteÄ\8dek do lůgowaÅ\84o užytkowÅ\84ikůw. MoÅ¡ zablokowano jejich uobsuůga. Sprůbuj zaÅ\9b jak zauůnÄ\8dyÅ¡ uobsuůga Ä\87osteÄ\8dek.',
-'nocookiesfornew' => 'Konto sprowjorza ńy uostoło stworzone. Sprawdź, cze mosz uodymkńynto obsługe cookies.',
-'noname' => 'To ńy je půprowne mjano użytkowńika.',
-'loginsuccesstitle' => 'Lůgowańy udane',
-'loginsuccess' => "'''Terozki ježeÅ\9b zalůgowany do {{SITENAME}} jako \"\$1\".'''",
+'nocookieslogin' => '{{SITENAME}} używo Ä\87osteczek do logowaÅ\84o użytkowÅ\84ikůw. Mosz zablokowano jejich uobsÅ\82ůga. Sprůbuj zaÅ\9b kej zaÅ\82ůnczysz uobsÅ\82ůga Ä\87osteczek.',
+'nocookiesfornew' => 'Konto sprowjorza ńy uostoło stworzone. Sprawdź, co mosz uodymkńynto uobsługa cookies.',
+'noname' => 'To ńy je poprowne mjano użytkowńika.',
+'loginsuccesstitle' => 'Logowańy udane',
+'loginsuccess' => "'''Terozki jeżeÅ\9b zalogowany do {{SITENAME}} kej \"\$1\".'''",
 'nosuchuser' => 'Ńy ma sam użytkowńika uo mjańe "$1".
 Sprowdź szrajbůng, abo [[Special:UserLogin/signup|utwůrz nowe kůnto]].',
-'nosuchusershort' => 'Å\83y mo sam užytkowńika uo mjańe "$1".',
+'nosuchusershort' => 'Å\83y mo sam użytkowńika uo mjańe "$1".',
 'nouserspecified' => 'Podej mjano użytkowńika.',
-'login-userblocked' => 'Tyn sprowjorz ma zawrzite sprowjyńa. Ńy możno sie zalgować.',
+'login-userblocked' => 'Tyn sprowjorz ma zawrzite sprowjyńa. Ńy możno sie zalogować.',
 'wrongpassword' => 'Hasło kere żeś naszkryfloł je felerne. Poprůbůj naszkryflać je jeszcze roz.',
-'wrongpasswordempty' => 'Hasuo kere žeś podou je puste. Naškryflej je ješče roz.',
+'wrongpasswordempty' => 'Hasło kere żeś podou je uostawjůne blank. Naszkryflej je jeszcze roz.',
 'passwordtooshort' => 'Hasło kere żeś podoł je felerne abo za krůtke.
-Hasło muśi mjeć przinojmńij {{PLURAL:$1|1 buchsztaba|$1 buchsztabůw}} a być inksze od mjana użytkowńika.',
-'password-name-match' => 'Hasło musi być inne niż nazwa użytkownika.',
-'password-login-forbidden' => 'Ńy wolno mjyć takij nazwy a hasua.',
+Hasło muśi mjeć przinojmńij {{PLURAL:$1|1 buchsztaba|$1 buchsztabůw}} a być inksze uod mjana użytkowńika.',
+'password-name-match' => 'Hasło mo być inksze atoli mjano używocza.',
+'password-login-forbidden' => 'Mogebność wyboru tygo mjana używocza abo hasła je zawarte.',
 'mailmypassword' => 'Wyślij mi nowe hasło bez e-brif',
-'passwordremindertitle' => 'Nowe tymčasowe hasuo dla {{SITENAME}}',
-'passwordremindertext' => 'Ftůś (cheba Ty, s IP $1)
+'passwordremindertitle' => 'Nowe tymczasowe hasło lo {{SITENAME}}',
+'passwordremindertext' => 'Ftoś (cheba Ty, ze IP $1)
 pado, aże chce nowe hasło do {{SITENAME}} ($4).
 Lo użytkowńika "$2" wygenyrowano nowe hasło a je ńim "$3".
 Jak chćołżeś gynał to zrobjyć, to zalůgůj śe terozki a podej swoje hasło.
 Hasło wygaśnie za {{PLURAL:$5|1 dzień|$5 dni}}.
 
-Jak ktůś inkszy chćoł nowe hasło abo jak Ci śe przipůmńouo stare a ńy chcesz nowygo, to zignoruj to a używej starygo hasła.',
+Jak ftoś inkszy chćoł nowe hasło abo jak Ci śe przipůmńouo stare a ńy chcesz nowygo, to zignoruj to a używej starygo hasła.',
 'noemail' => 'Ńy mo u nos adresu e-brifa do "$1".',
-'noemailcreate' => 'Podaj dobry e-mail ausdruk',
-'passwordsent' => 'Nowe hasuo pošuo na e-brifa uod užytkowńika "$1".
-Zalůguj śe zaś jak dostańyš tygo brifa.',
-'blocked-mailpassword' => 'Twůj adres IP zostou zawarty a ńy možeš užywać funkcyje odzyskiwańo hasua skuli možliwośći jeji nadužywańo.',
+'noemailcreate' => 'Podej dobry e-mail ausdruk',
+'passwordsent' => 'Nowe hasło polozło na e-brifa uod użytkowńika "$1".
+Zaloguj śe zaś jak dostańysz tygo brifa.',
+'blocked-mailpassword' => 'Twůj adres IP zostoł zawarty a ńy moższ używać funkcyje uodzyskiwańo hasła skuli mogebnośći jeji nadużywańo.',
 'eauthentsent' => 'Potwjerdzeńy zostoło posłane na e-brifa.
-Jak bydźesz chćoł, coby wysyłouo Ći e-brify, pjyrwyj go przeczytej. Bydźesz tam mjoł instrukcyjo co mosz zrobić, coby pokozać, aże tyn ausdruk je Twůj.',
+Jak bydźesz chćoł, coby wysyłoło Ći e-brify, pjyrwyj go przeczytej. Bydźesz tam mjoł instrukcyjo co mosz zrobić, coby pokozać, aże tyn ausdruk je Twůj.',
 'throttled-mailpassword' => 'Przipůmńyńy hasła bůło już wysłane bez {{PLURAL:$1|uostatńo godźina|uostatńe $1 godźin}}.
 Coby powstrzimać nadużyća, mogebność wysyłańo przipůmńyń nasztalowano na jydne bez {{PLURAL:$1|godźina|$1 godźiny}}.',
-'mailerror' => 'Při wysyuańu e-brifa zdořiu śe feler: $1',
-'acct_creation_throttle_hit' => 'Przikro nom, założył(a)żeś już {{PLURAL:$1|1 kůnto|$1 kůnta}}. Ńy możesz założyć kolejnygo.',
-'emailauthenticated' => 'Twůj adres e-brifa zostou uwjeřitelńůny $2 uo $3.',
-'emailnotauthenticated' => 'Twůj adres e-brifa ńy je uwjeřitelńůny. Půnižše funkcyje počty ńy bydům dźauać.',
-'noemailprefs' => 'Muśiš podać adres e-brifa, coby te funkcyje dźouauy.',
-'emailconfirmlink' => 'Potwjerdź swůj adres e-brifa',
-'invalidemailaddress' => 'E-brif Å\84y bydźe zaakceptůwany skiž tygo co jego format Å\84y speuÅ\84o formalnych wymagaÅ\84. ProÅ¡a naÅ¡kryflaÄ\87 poprowny adres e-brifa abo wyÄ\8dyśćić pole.',
+'mailerror' => 'Przi wysyłańu e-brifa zdorził śe feler: $1',
+'acct_creation_throttle_hit' => 'Przikro nom, założůł(a)żeś już {{PLURAL:$1|1 kůnto|$1 kůnta}}. Ńy możesz założyć kolejnygo.',
+'emailauthenticated' => 'Twůj adres e-brifa zostoł uwjerzitelńůny $2 uo $3.',
+'emailnotauthenticated' => 'Twůj adres e-brifa ńy je uwjerzitelńůny. Půniższe funkcyje poczty ńy bydům dźołać.',
+'noemailprefs' => 'Muśisz podać adres e-brifa, coby te funkcyje dźołały.',
+'emailconfirmlink' => 'Potwjyrdź swůj adres e-brifa',
+'invalidemailaddress' => 'E-brif Å\84y bydźe zaakceptůwany skiż tygo co jigo format Å\84y speÅ\82Å\84o formalnych wymagaÅ\84. Prosza naszkryflaÄ\87 poprowny adres e-brifa abo wyczyśćić pole.',
 'cannotchangeemail' => 'Ńy możno pomjyńyc ausdruku e-mail.',
 'emaildisabled' => 'Ta zajta ńy je mogebna posyłać e-brify.',
-'accountcreated' => 'Utwůřůno kůnto',
+'accountcreated' => 'Utwůrzůno kůnto',
 'accountcreatedtext' => 'Kůnto lo [[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|dyskusyjo]]) je utwůrzůne.',
-'createaccount-title' => 'Stwořyńy kůnta na {{GRAMMAR:MS.lp|{{SITENAME}}}}',
-'createaccount-text' => 'Ktoś utworził na {{GRAMMAR:MS.lp|{{SITENAME}}}} ($4) dla Twojego adresa e-brif kůnto "$2". Aktualne hasło to "$3". Powińeżeś śe terozki zalogůwać a je zmjyńić.',
+'createaccount-title' => 'Stworzyńy kůnta na {{GRAMMAR:MS.lp|{{SITENAME}}}}',
+'createaccount-text' => 'Ftoś utworził na {{GRAMMAR:MS.lp|{{SITENAME}}}} ($4) lo Twojigo adresa e-brif kůnto "$2". Aktuelne hasło to "$3". Powińeżeś śe terozki zalogůwać a je zmjyńić.',
 'usernamehasherror' => 'Nazwa sprowjorza ńy może mjyć buchsztaby "#".',
 'login-throttled' => 'Wykonołżeś za wjela průb zalůgowańo śe na te kůnto. Uodczekej $1 ńym zaś sprůbujesz.',
 'login-abort-generic' => 'Felerne logowańe',
 'loginlanguagelabel' => 'Godka: $1',
-'suspicious-userlogout' => 'Żądanie wylogowania zostało odrzucone ponieważ wygląda na to, że zostało wysłane przez uszkodzoną przeglądarkę lub buforujący serwer proxy.',
+'suspicious-userlogout' => 'Polecyńe wylogowańo uostoło uodćepńynte skiż tygo co wyglůnda, aże uostoło posłane bez uszkodzůna przeglůndarka abo buforujůncy serwer proxy.',
+'createacct-another-realname-tip' => 'Wszkryflańy twojigo mjana a nazwiska ńy je końyczne.
+Kej bydźesz chćoł je podoć, bydům użyte, coby dokůmyntowoć Twoje autorstwo.',
 
 # Email sending
 'php-mail-error-unknown' => 'Ńyznany feler we funkcyji mail()',
-'user-mail-no-addy' => 'Próba wysłania e‐maila bez adresu odbiorcy',
+'user-mail-no-addy' => 'Průba posłańo e‐brifa bez adresu uodbjorcy',
 
 # Change password dialog
 'resetpass' => 'Pomjyńaj hasło',
-'resetpass_announce' => 'Zalůgowoužeś śe s tymčasowym kodym uotřimanym bez e-brif. Coby zakůńčyć proces logůwańo muśiš naštalować nowe hasuo:',
+'resetpass_announce' => 'Zalůgowołżeś śe ze tymczasowym kodym uotrzimanym bez e-brif. Coby zakůńczyć proces logůwańo muśisz nasztalować nowe hasło:',
 'resetpass_header' => 'Zmjyń hasło lů swojygo kůnta',
 'oldpassword' => 'Stare hasło',
 'newpassword' => 'Nowe hasło',
 'retypenew' => 'Naszkryflej jeszcze roz nowe hasło:',
-'resetpass_submit' => 'Naštaluj hasuo a zalůguj',
+'resetpass_submit' => 'Nasztaluj hasło a zaloguj',
 'changepassword-success' => 'Twoje hasło zostoło půmyślńy půmjyńone!',
-'resetpass_forbidden' => 'Ńy idźe sam půmjyńyć hasuůw.',
+'resetpass_forbidden' => 'Ńy idźe sam půmjyńyć hasłůw.',
 'resetpass-no-info' => 'Muśysz być zalogowany, coby uzyskać bezpostrzedńi dostymp do tyj zajty.',
 'resetpass-submit-loggedin' => 'Zmjyń hasło',
 'resetpass-submit-cancel' => 'Uodćepej',
@@ -594,31 +618,31 @@ Możliwe co właśńy zmjyńiłżeś swoje hasło abo poprosiłżeś uo nowe tym
 'passwordreset-capture-help' => 'Eli zaznaczysz to pole, łoboczysz wjadomość e-mail z hasłem.',
 'passwordreset-email' => 'E-brif:',
 'passwordreset-emailtitle' => 'Kůnto na {{GRAMMAR:MS.lp|{{SITENAME}}}}',
-'passwordreset-emailtext-ip' => 'Ftůś (cheba Ty, s IP $1)
+'passwordreset-emailtext-ip' => 'Ftoś (cheba Ty, s IP $1)
 pado, aże chce informacyji lo konta do {{GRAMMAR:MS.lp{{SITENAME}}}} ($4).
-Z tem ausdrukiem sum powjonzyne konta:
+Ze tym ausdrukym sům powjůnzane kůnta:
 $2
 
-{{PLURAL:$3|Tymczasowygo hasła|Tymczasowych haseł}} możno użyć w ciągu {{PLURAL:$5|jednego dnia|$5 dni}}.
+{{PLURAL:$3|Tymczasowygo hasła|Tymczasowych hasył}} możno użyć we {{PLURAL:$5|jedyn dźyń|$5 dńi}}.
 
-Jak chćołżeś gynał to zrobjyć, to zalůgůj śe terozki a podej swoje hasło.
+Jak chćołżeś gynał to zrobjyć, to zaloguj śe terozki a podej swoje hasło.
 
-Jak ktůś inkszy chćoł nowe hasło abo jak Ci śe przipůmńouo stare a ńy chcesz nowygo, to zignoruj to a używej starygo hasła.',
-'passwordreset-emailelement' => 'Nazwa sprowjorza: $1
+Jak ftoś inkszy chćoł nowe hasło abo jak Ci śe przipůmńoło stare a ńy chcysz nowygo, to zignoruj to a używej starygo hasła.',
+'passwordreset-emailelement' => 'Mjano sprowjorza: $1
 Tymczasowe hasło: $2',
 'passwordreset-emailsent' => 'E-brif posłany.',
 'passwordreset-emailsent-capture' => 'E-brif posłony, kerego widać niżej.',
-'passwordreset-emailerror-capture' => 'Ńy udało sie wysłać wjadomości lo sprowjorza: $1',
+'passwordreset-emailerror-capture' => 'Ńy udoło śe posłać wjadomości lo {{GENDER:$2|używocza|używoczki}}: $1',
 
 # Special:ChangeEmail
 'changeemail' => 'Pomjyno ausdruka e-mail',
 'changeemail-header' => 'Pomjyno ausduku e-mail',
 'changeemail-text' => 'Wypełnij formularz, podej nowy ausdruk a hasło.',
-'changeemail-no-info' => 'Muśysz być zalogowany, coby uzyskać bezpostrzedńi dostymp do tyj zajty.',
+'changeemail-no-info' => 'Muśisz być zalogowany, coby uzyskać bezpostrzedńi dostymp do tyj zajty.',
 'changeemail-oldemail' => 'Uobecny ausdruk:',
 'changeemail-newemail' => 'Nowy adresu e-brif',
 'changeemail-none' => 'podstawowo',
-'changeemail-submit' => 'Zapisz nowy',
+'changeemail-submit' => 'Spamjyntej nowy',
 'changeemail-cancel' => 'Uodćepej',
 
 # Edit page toolbar
@@ -636,37 +660,37 @@ Tymczasowe hasło: $2',
 'nowiki_tip' => 'Zignoruj formatowańy wiki',
 'image_tip' => 'Plik uosadzůny we zajće',
 'media_tip' => 'Link do plika',
-'sig_tip' => 'Twůj podpis z datumym i czasym',
+'sig_tip' => 'Twojo szrajbka ze datum a czasym',
 'hr_tip' => 'Poźůmo lińijo (używej mjyrńy)',
 
 # Edit pages
 'summary' => 'Popis půmjyńań:',
-'subject' => 'Tymat/naguůwek:',
+'subject' => 'Tyjma/iberszryft:',
 'minoredit' => 'To je ńywjelge sprowjyńy',
 'watchthis' => 'Dej pozůr',
 'savearticle' => 'Spamjyntej',
 'preview' => 'Uobźyrańy',
 'showpreview' => 'Uobźyrej',
-'showlivepreview' => 'Dynamičny podglůnd',
+'showlivepreview' => 'Dynamiczny podglůnd',
 'showdiff' => 'Pozdrzyj na půmjyńańy',
-'anoneditwarning' => 'Ńy jeżeś nalogowany. We gyszichće sprowjyń tyj zajty bydźe naszkryflůny twůj adres IP.',
-'anonpreviewwarning' => 'Ńy jeżeś zalogowany. Twój adres IP łostonie zapisany, eli ty bydzies sprowjać zajte.',
-'missingsummary' => "'''Připomńyńy:''' Ńy wprowadźiužeś uopisu pomjyńań. Kej go ńy chceš wprowadzać, naćiś knefel Škryflej ješče roz.",
-'missingcommenttext' => 'Wćepej kůmyntoř půńižyj.',
-'missingcommentheader' => "'''Dej pozůr:''' Treść nagłůwka je pusto - uzupeuńij go! Jeli tego ńy zrobisz, Twůj kůmyntorz bydźe naszkryflany bez naguůwka.",
+'anoneditwarning' => 'Ńy jeżeś terozki zalogowany. We gyszichće sprowjyń tyj zajty bydźe naszkryflůny twůj ausdruk IP.',
+'anonpreviewwarning' => 'Ńy jeżeś zalogowany. Twój IP ausdruk uostańy spamjyntany, eli ty bydźesz sprowjać zajte.',
+'missingsummary' => "'''Pozůr:''' Ńy wprowadźůł żeś uopisu pomjyńań. Kej go ńy chcesz wprowadzać, naćiś knefel Spamjyntej jeszcze roz.",
+'missingcommenttext' => 'Wćepej kůmyntorz půńiżyj.',
+'missingcommentheader' => "'''Dej pozůr:''' Treść nagłůwka je blank - uzupełńij go! Jeli tego ńy zrobisz, Twůj kůmyntorz bydźe naszkryflony bez nagłůwka.",
 'summary-preview' => 'Podglůnd uopisu:',
-'subject-preview' => 'Podglůnd tematu/naguůwka:',
-'blockedtitle' => 'Užytkowńik je zawarty uod sprowjyń',
-'blockedtext' => '\'\'\'Twoje kůnto abo adres IP sům zawarte.\'\'\'
+'subject-preview' => 'Podglůnd tyjmy/nagłůwka:',
+'blockedtitle' => 'Użytkowńik je zawarty uod sprowjyń',
+'blockedtext' => '\'\'\'Twoje kůnto abo IP ausdruk sům zawarte.\'\'\'
 
-Uo zawarću zdecydowou $1. Pado, aže skuli: \'\'$2\'\'.
+Uo zawarću zdecydowoł $1. Pado, aże skuli: \'\'$2\'\'.
 
 * Zawarte uod: $8
 * Uodymkńe śe: $6
-* Bez cůž: $7
+* Zawarće skiż: $7
 
-Coby wyjaśńić sprawa zawarćo, naškryflej do $1 abo inkšygo [[{{MediaWiki:Grouppage-sysop}}|admińistratora]].
\83y možeÅ¡ posuaÄ\87 e-brifa bez "poÅ\9blij e-brifa tymu užytkowÅ\84ikowi", jak Å¾eÅ\9b Å\84y podou dobrygo adresa e-brifa we [[Special:Preferences|preferencyjach kůnta]], abo jak e-brify moÅ¡ tyž zawarte. Terozki moÅ¡ adres IP $3 a nůmer zawarÄ\87a to #$5. ProÅ¡ymy podaÄ\87 jedyn abo uobadwa jak chceÅ¡ pouosprawjać uo zawarću.',
+Coby wyjaśńić sprawa zawarćo, naszkryflej do $1 abo inkszygo [[{{MediaWiki:Grouppage-sysop}}|admińistratora]].
\83y możesz posÅ\82\87 e-brifa bez "poÅ\9blij e-brifa tymu użytkowÅ\84ikowi", jak Å¼eÅ\9b Å\84y podoÅ\82 dobrygo ausdruku e-brifa we [[Special:Preferences|preferencyjach kůnta]], abo jak e-brify mosz tyż zawarte. Terozki mosz ausdruk IP $3 a nůmera zawarÄ\87o to #$5. Proszymy podaÄ\87 jedyn abo uoba jak chcysz poÅ\82osprawjać uo zawarću.',
 'autoblockedtext' => 'Tyn adres IP zostou zawarty automatyčńy, gdyž kořisto s ńygo inkšy užytkowńik, zawarty uod sprowjyń bez administratora $1.
 Powůd zawarćo:
 
@@ -676,12 +700,8 @@ Powůd zawarćo:
 * Zawarće wygaso: $6
 * Zawarće je skiž: $7
 
-Možyš skůntaktować śe s $1 abo jednym s pozostauych [[{{MediaWiki:Grouppage-sysop}}|admińistratorůw]] kejbyś chćou uzyskać informacyje uo zawarću.
-
-Pozůr: Kejžeś we [[Special:Preferences|preferencyjach]] ńy naštalowou prowiduowygo adresa e-brifa, abo e-brify moš tyž zawarte, ńy možeš skožystać s uopcyje "Poślij e-brifa tymu užytkowńikowi".
-
-Twůj adres IP je terozki $3. Idyntyfikator Twojij blokady to $5. Zanotuj śe go a podej admińistratorowi.',
-'blockednoreason' => 'ńy podano skuli čego',
+* Zawarte uod: $8 * Uodymkńe śe: $6 * Zawarće skiż: $7 Coby wyjaśńić sprawa zawarćo, naszkryflej do $1 abo inkszygo [[{{MediaWiki:Grouppage-sysop}}|admińistratora]]. Ńy możesz posłać e-brifa bez "poślij e-brifa tymu użytkowńikowi", jak żeś ńy podoł dobrygo ausdruku e-brifa we [[Special:Preferences|preferencyjach kůnta]], abo jak e-brify mosz tyż zawarte. Terozki mosz ausdruk IP $3 a nůmera zawarćo to #$5. Proszymy podać jedyn abo uoba jak chcysz połosprawjać uo zawarću.',
+'blockednoreason' => 'ńy podano skuli czygo',
 'whitelistedittext' => 'Muśiš $1 coby můc sprowjać artikle.',
 'confirmedittext' => 'Muśiš podać a potwjerdźić swůj e-brif, coby můc sam sprowjać.
 Možeš to zrobić we [[Special:Preferences|swojich štalowańach]].',
@@ -739,24 +759,24 @@ Twoje pomjyńańo sům we polu edycyji půnižyj.
 By wćepać swoje pomjyńańo muśiš pomjyńać tekst w polu na wjyrchu.
 '''Tylko''' tekst z pola na wjyrchu bydźe naškryflany we baźe jak wciśńeš \"{{int:savearticle}}\".",
 'yourtext' => 'Twůj tekst',
-'storedversion' => 'Naškryflano wersyjo',
+'storedversion' => 'Naszkryflano wersyjo',
 'nonunicodebrowser' => "'''Pozůr! Twoja přeglůndorka ńy umje poprowńy rozpoznować kodowańo UTF-8 (Unicode). Bestož wšyjske znoki, kerych Twoja přeglůndorka ńy umje rozpoznować, zamjeńůno na jejich kody heksadecymalne.'''",
 'editingold' => "'''Dej pozůr: Sprowjoš inkšo wersyjo zajty kej bježůnco. Jeli jům naškryfloš, wšyjske půźńyjše pomjyńańa bydům wyćepane.'''",
 'yourdiff' => 'Růžńice',
-'copyrightwarning' => "Pamjyntej uo tym, aže couki wkuod do {{SITENAME}} udostympÅ\84ůmy wedle zasad $2 (dokuadÅ\84ij w $1). Jak Å\84y chceÅ¡, coby koždy můg go zmjyÅ\84\87 i dali rozpowÅ¡ychÅ\84\87, Å\84y wÄ\87epuj go sam. Å kryflajůnc sam tukej poÅ\9bwjadÄ\8doÅ¡ tyž, co te pisaÅ\84y je twoje wuasne, abo Å¾eÅ\9b go wźůn(a) s materjouůw kere sům na ''public domain'', abo kůmpatybilne.<br />
-'''PROŠA ŃY WĆEPYWAĆ SAM MATYRJOUŮW KERE SŮM CHRŮŃONE PRAWYM AUTORSKIM BEZ DOZWOLEŃO WUAŚĆIĆELA!'''",
-'copyrightwarning2' => "Pamjyntej uo tym, aže couki wkuod do {{GRAMMAR:MS.lp|{{SITENAME}}}} može byÄ\87 sprowjany, pomjyÅ\84any abo wyÄ\87epany bez inkÅ¡ych užytkownikůw. Jak Å\84y chceÅ¡, coby koždy můg go zmjyÅ\84\87 i dali rozpowÅ¡ychÅ\84\87 bez uograniÄ\8dyń, ńy wćepuj go sam.<br />
-Škryflajůnc sam tukej pośwjadčoš tyž, co te pisańy je twoje wuasne, abo žeś go wźůn(a) s matyrjouůw kere sům na public domain, abo kůmpatybilne (kuknij tyž: $1).
-'''PROŠA ŃY WĆEPYWAĆ SAM MATYRJOUŮW KERE SŮM CHRŮŃONE PRAWYM AUTORSKIM BEZ DOZWOLEŃO WUAŚĆIĆELA!'''",
+'copyrightwarning' => "Pamjyntej uo tym, aże coÅ\82ki wkÅ\82od do {{SITENAME}} udostympÅ\84ůmy wedle zasad $2 (dokÅ\82adÅ\84ij we $1). Jak Å\84y chcesz, coby kożdy můg go půmjyÅ\84\87 a dalij rozpowszychÅ\84\87, Å\84y wÄ\87epuj uůnygo sam. Szkryflajůnc sam tukej poÅ\9bwjadczosz tyż, co te pisaÅ\84y je twoje wÅ\82asne, abo Å¼eÅ\9b go wźůn(a) ze materjoÅ\82ůw kere sům na ''public domain'', abo kůmpatybilne.<br />
+'''PROSZA ŃY WĆEPYWAĆ SAM MATYRJOŁŮW KERE SŮM CHRŮŃONE AUTORSKIM PRAWYM BEZ DOZWOLEŃO WŁAŚĆIĆELA!'''",
+'copyrightwarning2' => "Pamjyntej uo tym, aże coÅ\82ki wkÅ\82od do {{GRAMMAR:MS.lp|{{SITENAME}}}} może byÄ\87 sprowjany, pomjyÅ\84any abo wyÄ\87epany bez inkszych użytkownikůw. Jak Å\84y chcysz, coby kożdy můg uůnygo zmjyÅ\84\87 a dalij rozpowszychÅ\84\87 bez uograniczyń, ńy wćepuj go sam.<br />
+Szkryflajůnc sam tukej pośwjadczosz tyż, co te pisańy je twoje własne, abo żeś go wźůn(a) ze matyrjołůw kere sům na public domain, abo kůmpatybilne (kuknij tyż: $1).
+'''PROSZA ŃY WĆEPYWAĆ SAM MATYRJOŁŮW KERE SŮM CHRŮŃONE PRAWYM AUTORSKIM BEZ DOZWOLEŃO WŁAŚĆIĆELA!'''",
 'longpageerror' => "''Feler: Tekst kery żeś sam wćepywoł mo {{PLURAL:$1|jedyn kilobajt|$1 kilobajtůw}}. Maksymalno dugość tekstu ńy może być srogszo kej {{PLURAL:$2|jedyn kilobajt|$2 kilobajtůw}}. Twůj tekst ńy bydźe sam naszkryflany.'''",
 'readonlywarning' => "'''Dej pozůr: Baza danych zostoua filowo zawarto skuli potřeb admińistracyjnych. Bestůž ńy do śe terozki naškryflać Twojich pomjyńań. Radzymy přećepać nowy tekst kajś do plika tekstowego (wytnij/wklej) a wćepać sam zaś po uodymkńyńću bazy.'''
 
 Admińistrator kery zawar baza dou take wyjaśńyńe: $1",
-'protectedpagewarning' => "'''Dej pozůr: Sprowjańe tyj zajty zostoło zawarte. Mogům jům sprowjać ino użytkowńicy s uprawńyńami admińistratora.'''
+'protectedpagewarning' => "'''Dej pozůr: Sprowjańe tyj zajty zostoło zawarte. Mogům jům sprowjać ino użytkowńicy ze uprawńyńami admińistratora.'''
 Uostatńy wpis w rejerze je poniżej.",
 'semiprotectedpagewarning' => "'''Pozůr:''' Ta zajta zostoła zawarto a ino zaregiszterowani użytkownicy mogům jům sprowjać.
 Uostotńy wpis w rejerze je ńyżej.",
-'cascadeprotectedwarning' => "'''Dej pozůr:''' Ta zajta zostoua zawarto a ino užytkowńicy s uprawńyńami admińistratora mogům jům sprowjać. Zajta ta je podpjynto pod {{PLURAL:$1|nastympujůnco zajta, kero zostoua zawarto|nastympujůncych zajtach, kere zostouy zawarte}} ze zauůnčonům opcjům dźedźičyńo:",
+'cascadeprotectedwarning' => "'''Dej pozůr:''' Ta zajta zostoła zawarto a ino użytkowńicy ze uprawńyńami admińistratora mogům jům sprowjać. Zajta ta je podpjynto pod {{PLURAL:$1|nastympujůnco zajta, kero zostoła zawarto|nastympujůncych zajtach, kere zostouy zawarte}} ze załůnczonům uopcjům dźedźiczyńo:",
 'titleprotectedwarning' => "'''Dej pozůr: Zajta uo tym titlu zostoła zawarto a ino [[Special:ListGroupRights|ńykerzi użytkowńicy]] mogům jům wćepać.'''
 Uostatńy wpis z rejera je ńyżej.",
 'templatesused' => '{{PLURAL:$1|Szablon|Szablůny}} użyte na tyj zajće:',
@@ -782,7 +802,7 @@ Rejer wyćepań tyj zajty je podany půńiżej, cobyś mioł wygoda:",
 'edit-hook-aborted' => 'Sprowjyńy štopńynte skiž hoka.
 Ńy je wjadůme pů jakymu.',
 'edit-gone-missing' => 'Ńy idźe zaktualizować zajty.
-Zdowo śe, co zostoua wyćepano.',
+Zdowo śe, co zostoła wyćepano.',
 'edit-conflict' => 'Kůnflikt sprowjyń.',
 'edit-no-change' => 'Twoje sprowjyńe uostouo zignorowane pů takymu, co ńic žeś we tekśće ńy zmjyńiu.',
 'edit-already-exists' => 'Ńy idźe utwořić nowyj zajty.
@@ -799,7 +819,7 @@ Powinno być myńi jak $2 {{PLURAL:$2|wywouańy|wywouańo|wywouań}}, a terozki
 'post-expand-template-inclusion-category' => 'Zajty, na kerych dokuplowane mustry sům moc wjelge',
 'post-expand-template-argument-warning' => 'Dej pozůr: Ta zajta zawjyro přinojmyńi jedyn argument we šablůńe kery powoduje co je ůun za wjelgi. Te argumynty bydům pomińynte.',
 'post-expand-template-argument-category' => 'Zajty na kerych sům šablůny s pomińyntymi argumyntůma.',
-'parser-template-loop-warning' => 'Wykryto szablůn zapyntlyńo: [[$1]]',
+'parser-template-loop-warning' => 'Wykryto muster zapyntlyńo: [[$1]]',
 'parser-template-recursion-depth-warning' => 'Przekroczůno limit głymbokośći rekurencyji szablona ($1)',
 
 # "Undo" feature
@@ -831,12 +851,12 @@ Skuli: ''$2''",
 'page_last' => 'kůńec',
 'histlegend' => 'Wybůr růżńic do porůwnańo: postow kropki we boksach a naćiś enter abo knefel na dole.<br />
 Legynda: (akt.) - růżńice s wersyjům bjeżůncům, (poprz.) - růżńice s wersyjům poprzedzajůncům, d - drobne zmjany',
-'history-fieldset-title' => 'Přeglůndej historyjo',
+'history-fieldset-title' => 'Przeglůndej gyszichta',
 'history-show-deleted' => 'Jyno wyćepane',
 'histfirst' => 'uod počůnku',
 'histlast' => 'uod uostatka',
 'historysize' => '({{PLURAL:$1|1 bajt|$1 bajty|$1 bajtůw}})',
-'historyempty' => '(pusto)',
+'historyempty' => '(blank)',
 
 # Revision feed
 'history-feed-title' => 'Gyszichta wersyjůw',
@@ -879,7 +899,7 @@ Inkśi admińistratorzi {{GRAMMAR:D.lp|{{SITENAME}}}} dali bydům mjeć dostymp
 'revdelete-radio-unset' => 'Ńy',
 'revdelete-suppress' => 'Schrůń informacyje zarůwno před admińistratorůma jak i před inkšymi',
 'revdelete-unsuppress' => 'Usůń uograńičyńo lo wćepanej nazod historyje pomjyńań',
-'revdelete-log' => 'Čymu:',
+'revdelete-log' => 'Czymu:',
 'revdelete-submit' => 'Zaakceptuj do wybrany{{PLURAL:$1|j wersyji|ch wersyji}}',
 'revdelete-success' => 'Půmyślńy zmjyńůno widoczność wersyji.',
 'revdelete-failure' => 'Feler przi zmjyńůńu widoczności wersyji.
@@ -1001,10 +1021,10 @@ $1',
 'nonefound' => "'''Dej pozůr''': Důmyślńy přešukiwane sům ino ńykere přestřyńy mjan. Poprůbuj popředźić wyšukiwano fraza předrostkym ''all:'', co spowoduje přešukańy coukij zawartośći {{GRAMMAR:D.lp|{{SITENAME}}}} (wůunčńy ze zajtami godki, šablůnůma atp.), abo poprůbuj užyć kej předrostka wybranyj, jydnyj přestřyńi mjan.",
 'search-nonefound' => 'Ńy mo wynikůw, kere uodpadajům kryterjům zapytańo.',
 'powersearch' => 'Sznupańy zaawansowane',
-'powersearch-legend' => 'Šnupańy zaawansowane',
+'powersearch-legend' => 'Sznupańy zaawansowane',
 'powersearch-ns' => 'Sznupej we przestrzyńach mjan:',
 'powersearch-redir' => 'Pokož překerowańa',
-'powersearch-field' => 'Šnupej',
+'powersearch-field' => 'Sznupej',
 'powersearch-togglelabel' => 'Zaznocz:',
 'powersearch-toggleall' => 'Wszyjsko',
 'powersearch-togglenone' => 'żodno',
@@ -1074,7 +1094,7 @@ $1',
 'timezoneregion-pacific' => 'Uocean Spokojny',
 'allowemail' => 'Inkśi užytkowńicy můgům přesyuać mje e-brify',
 'prefs-searchoptions' => 'Sznupańe',
-'prefs-namespaces' => 'Přystřyńe mjan',
+'prefs-namespaces' => 'Raumy mjan',
 'defaultns' => 'Důmyślńy sznupej we nastympujůncych przystrzyńach mjan:',
 'default' => 'důmyślńy',
 'prefs-files' => 'Pliki',
@@ -1112,7 +1132,7 @@ $1',
 *Zaznačůne pole uoznačo přinoležność užytkowńika do danej grupy.
 *Ńy zaznačůne pole uoznačo, aže užytkowńik ńy noležy do danej grupy.
 * Gwjozdka * infomuje, co ńy možeš wyćepać s grupy po dodańu do ńij abo dodać po wyćepańu s grupy.',
-'userrights-reason' => 'Čymu:',
+'userrights-reason' => 'Czymu:',
 'userrights-no-interwiki' => 'Ńy moš dostympu do sprowjańo uprawńyń.',
 'userrights-nodatabase' => 'Baza danych $1 ńy istńije abo ńy je lokalno.',
 'userrights-nologin' => 'Muśiš [[Special:UserLogin|zalůgować śe]] na kůnto admińistratora, coby nadować uprawńyńo užytkowńikům.',
@@ -1127,8 +1147,8 @@ $1',
 'group-bot' => 'Boty',
 'group-sysop' => 'Admińi',
 'group-bureaucrat' => 'Bjurokraty',
-'group-suppress' => 'Rewizoře',
-'group-all' => '(wšyjscy)',
+'group-suppress' => 'Rewizorze',
+'group-all' => '(wszyjscy)',
 
 'group-user-member' => '{{GENDER:$1|używacz}}',
 'group-autoconfirmed-member' => 'Autůmatyczńy zatwjerdzůny używacz',
@@ -1137,21 +1157,21 @@ $1',
 'group-bureaucrat-member' => '{{GENDER:$1|bjurokrata}}',
 'group-suppress-member' => '{{GENDER:$1|rewizůr}}',
 
-'grouppage-user' => '{{ns:project}}:Sprowjorze',
+'grouppage-user' => '{{ns:project}}:Używacze',
 'grouppage-autoconfirmed' => '{{ns:project}}:Autůmatyczńy zatwjerdzyńi używacze',
 'grouppage-bot' => '{{ns:project}}:Boty',
 'grouppage-sysop' => '{{ns:project}}:Admińistratory',
 'grouppage-bureaucrat' => '{{ns:project}}:Bjurokraty',
-'grouppage-suppress' => '{{ns:project}}:Rewizoře',
+'grouppage-suppress' => '{{ns:project}}:Rewizorze',
 
 # Rights
-'right-read' => 'Čytej zajty',
+'right-read' => 'Czytej zajty',
 'right-edit' => 'Sprowjej zajty',
-'right-createpage' => 'Utwořůne zajty (kere ńy sům zajtůma godki)',
-'right-createtalk' => 'Utwořůne zajty godki',
+'right-createpage' => 'Utworzůne zajty (kere ńy sům zajtůma godki)',
+'right-createtalk' => 'Utworzůne zajty godki',
 'right-createaccount' => 'Utwořůne nowe kůnta užytkowńikůw',
-'right-minoredit' => 'Uoznoč půmjyńańo kej drobne',
-'right-move' => 'Přećepane zajty',
+'right-minoredit' => 'Uoznocz půmjyńańo kej drobne',
+'right-move' => 'Przećepane zajty',
 'right-move-subpages' => 'Przećep zajty wroz s jejich podzajtůma',
 'right-move-rootuserpages' => 'Překludzańy zajtůw uod užytkowńikůw',
 'right-movefile' => 'Przećepańe plikůw',
@@ -1367,7 +1387,7 @@ Idź nazod i wćepej tyn plik pod inkšym mjanym. [[File:$1|thumb|center|$1]]',
 'filename-bad-prefix' => "Mjano plika, kery wćepuješ, začyno śe uod '''\"\$1\"''' &ndash; je to mjano nojčynśćy připisywane autůmatyčńy bez cyfrowe fotoaparaty, uůno ńy dowo žodnych informacyji uo zawartośći plika. Prošymy cobyś nadou plikowi inkše, lepij zrozůmjaue mjano.",
 'upload-success-subj' => 'Wćepańe plika udouo śe',
 
-'upload-proto-error' => 'Ńyprowiduowy protokůu',
+'upload-proto-error' => 'Ńyprowidłowy protokůł',
 'upload-proto-error-text' => 'Zdalne přesůuańy plikůw wymago podańo adresu URL kery začyno śe na <code>http://</code> abo <code>ftp://</code>.',
 'upload-file-error' => 'Wewnyntřny feler',
 'upload-file-error-text' => 'Wystůmpiu wewnyntřny feler kej průbowano naškryflać tymčasowy plik na serweře. Skůntaktuj śe s [[Special:ListUsers/sysop|admińistratorym systemu]].',
@@ -1528,31 +1548,31 @@ Niżyj sům informacyje ze [$2 zajty popisu] tygo pliku.',
 'lonelypagestext' => 'Do zajtůw půńiżyj ńy adresuje żodno inkszo zajta we {{SITENAME}}.',
 'uncategorizedpages' => 'Zajty bez kategoryje',
 'uncategorizedcategories' => 'Kategoryje bez kategoriůw',
-'uncategorizedimages' => 'Pliki bez kategoriůw',
-'uncategorizedtemplates' => 'Šablôny bez kategorii',
-'unusedcategories' => 'Å\83yužywane kategoryje',
-'unusedimages' => 'Å\83yužywane pliki',
-'popularpages' => 'Zajty we kere nojčynśćej sam filujům',
-'wantedcategories' => 'Potřebne katygoryje',
-'wantedpages' => 'Nojpotřebńijše zajty',
+'uncategorizedimages' => 'Pliki bez kategoryjůw',
+'uncategorizedtemplates' => 'Mustry bez kategorii',
+'unusedcategories' => 'Å\83yużywane kategoryje',
+'unusedimages' => 'Å\83yużywane pliki',
+'popularpages' => 'Zajty we kere nojczynśćij sam filujům',
+'wantedcategories' => 'Potrzebne katygoryje',
+'wantedpages' => 'Nojpotrzebńijsze zajty',
 'wantedfiles' => 'Potrzebne pliki',
 'wantedtemplates' => 'Potrzebne szablůny',
-'mostlinked' => 'Nojčyńśćej adrésowane',
-'mostlinkedcategories' => 'Kategoryje we kerych je nojwjyncyi artikli',
-'mostlinkedtemplates' => 'Nojčyńśćej adrésowane šablôny',
-'mostcategories' => 'Zajty kere majům nojwiyncyi kategoriůw',
-'mostimages' => 'Nojčyńśćij adresowane pliki',
-'mostrevisions' => 'Nojčyńśćej sprowjane artikle',
+'mostlinked' => 'Nojczyńśćij adresowane',
+'mostlinkedcategories' => 'Kategoryje we kerych je nojwjyncyj artikli',
+'mostlinkedtemplates' => 'Nojczyńśćij adresowane mustry',
+'mostcategories' => 'Zajty kere majům nojwiyncyj kategoryjůw',
+'mostimages' => 'Nojczyńśćij adresowane pliki',
+'mostrevisions' => 'Nojczyńśćij sprowjane artikle',
 'prefixindex' => 'Wszyskie zajty wedle prefiksa',
-'shortpages' => 'Nojkrůtše zajty',
+'shortpages' => 'Nojkrůtsze zajty',
 'longpages' => 'Duge artikle',
 'deadendpages' => 'Artikle bez linkůw',
-'deadendpagestext' => 'Zajty wymjyÅ\84ůne půÅ\84¾ej Å\84y majům uodnoÅ\9bÅ\84ikůw do Å¾odnych inkÅ¡ych zajtůw kere sům na tej wiki.',
+'deadendpagestext' => 'Zajty wymjyÅ\84ůne půÅ\84¼yj Å\84y majům uodnoÅ\9bÅ\84ikůw do Å¼odnych inkszych zajtůw kere sům na tyj wiki.',
 'protectedpages' => 'Zawarte zajty',
-'protectedpages-indef' => 'Yno zabezpječyńo ńyokreślůne',
+'protectedpages-indef' => 'Ino zabezpjeczyńo ńyuokreślůne',
 'protectedpages-cascade' => 'Yno zajty zabezpjeczůne rekursywńy',
-'protectedpagestext' => 'Zajty wymjyÅ\84ůne půÅ\84¾ej sům zawarte uod prećepywańo i sprowjańo.',
-'protectedpagesempty' => 'Žodno zajta Å\84y je terozki zawarto s podanymi parametrami.',
+'protectedpagestext' => 'Zajty wymjyÅ\84ůne půÅ\84¼yj sům zawarte uod przećepywańo i sprowjańo.',
+'protectedpagesempty' => 'Å»odno zajta Å\84y je terozki zawarto ze podanymi parametrami.',
 'protectedtitles' => 'Zawarte mjana artikli',
 'protectedtitlestext' => 'Ůtwořyńy artikli uo nastympujůncych mjanach je zawarte',
 'protectedtitlesempty' => 'Do tych štalowań utwořyńy artikla uo dowolnym mjańy ńy je zawarte',
@@ -1579,7 +1599,7 @@ Niżyj sům informacyje ze [$2 zajty popisu] tygo pliku.',
 'booksources' => 'Kśůnžki',
 'booksources-search-legend' => 'Šnupej za zdřůduůma kśiůnžkowymi',
 'booksources-go' => 'Pokož',
-'booksources-text' => 'PůÅ\84¾ej znojdowo Å\9be lista uodnoÅ\9bÅ\84ikůw do inkÅ¡ych witryn, kere poÅ\9bredÅ\84\8dům we spÅ\99edažy nowych i užywanych kÅ\9b\85žek, a tyž můgům mjeÄ\87 dalÅ¡e informacyje uo poÅ¡ukiwany bez Ä\87ebje kÅ\9bůnžce',
+'booksources-text' => 'PůÅ\84¼yj je lista uodnoÅ\9bÅ\84ikůw do inkszych witryn, kere poÅ\9bredÅ\84iczům we sprzedaży nowych a używanych buchůw, a tyż můgům mjeÄ\87 dolsze informacyje uo poszukiwanym bez Ä\87ebje buchu.',
 'booksources-invalid-isbn' => 'Podany numer ISBN zostoł rozpoznany kej felerny. Sprowdź aże podany numer je zgodny s numerym kery je we zdrzůdle.',
 
 # Special:Log
@@ -1635,7 +1655,7 @@ Uobsůgiwane protokoły: <code>$1</code>',
 
 # Special:ListUsers
 'listusersfrom' => 'Pokaž užytkowńikůw začynojůnc uod:',
-'listusers-submit' => 'Pokož',
+'listusers-submit' => 'Uobejrzij',
 'listusers-noresult' => 'Ńy znejdźůno žodnygo užytkowńika.',
 
 # Special:ActiveUsers
@@ -1795,21 +1815,21 @@ Kto inkszy zdůnżůł już to zrobić abo wprowadźił własne poprowki do tre
 Autorym ostatńygo pomjyńyńo je terozki [[User:$3|$3]] ([[User talk:$3|godka]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).',
 'editcomment' => "Sprowjyńe uopisano: „''$1''”.",
 'revertpage' => 'Wycofano sprowjyńe użytkowńika [[Special:Contributions/$2|$2]] ([[User talk:$2|godka]]). Autor prziwrůcůnej wersyji to [[User:$1|$1]].',
-'rollback-success' => 'Wycofano sprowjyÅ\84a užytkowńika $1.
-Přiwrůcůno uostatńo wersyja autorstwa  $2.',
+'rollback-success' => 'Wycofano sprowjyÅ\84a użytkowńika $1.
+Prziwrůcůno uostatńo wersyja autorstwa  $2.',
 
 # Edit tokens
 'sessionfailure' => 'Feler weryfikacyji zalůgowańo.
-Polecyńy zostouo anulowane, aby ůńiknůńć přechwycyńo sesyji.
+Polecyńy zostoło anulowane, coby ůńiknůńć przechwycyńo sesyji.
 
-Naćiś „cofej”, přeuaduj zajta, a potym zaś wydej polecyńy',
+Naćiś knefel „cofej”, przeładuj zajta, a potym zaś wydej polecyńy',
 
 # Protect
 'protectlogpage' => 'Zawarte',
 'protectlogtext' => 'Půńižej znojdowo śe lista zawarć i uodymkńjyńć pojydynčych zajtůw.
 Coby přejřeć lista uobecńy zawartych zajtůw, přeńdź na zajta wykazu [[Special:ProtectedPages|zawartych zajtůw]].',
 'protectedarticle' => 'zawar [[$1]]',
-'modifiedarticleprotection' => 'pomjyńiu poziům zawarćo [[$1]]',
+'modifiedarticleprotection' => 'pomjyńiu poźům zawarćo [[$1]]',
 'unprotectedarticle' => 'uodymknyu [[$1]]',
 'movedarticleprotection' => 'przekludzůno sztalowańa zabezpjeczyńo s „[[$2]]” ku „[[$1]]”',
 'protect-title' => 'Pomjyńeńe poźomu zawarćo „$1”',
@@ -1877,14 +1897,14 @@ Naćiśńyńće '''Wyczyść''' usůńy wszyjstke zaznaczyńo a wyczyśći pole
 Jak uod czasu wyćepańo ktoś utworzůł nowo zajta uo idyntycznym mjańy, uodtwarzane wersyje znojdům śe w jeij historyji, a uobecno wersyjo pozostańy bez půmjyńań.',
 'undeleterevdel' => 'Wćepańy nazod zajty ńy bydźe přeprowadzůne kej by můguo spowodować tajlowe wyćepańy aktualnej wersyji.
 W takej sytuacyji noležy uodznačyć abo přiwrůćić widočność nojnowšym wyćepanym wersjům.',
-'undeletehistorynoadmin' => 'Ta zajta zostoua wyćepano.
-Powůd wyÄ\87epaÅ\84o je podany w podsůmowaÅ\84u půÅ\84¾ej, razym s danymi užytkowÅ\84ika, kery sprawjou zajta pÅ\99ed jei wyćepańym.
-Sama treść wyćepanych wersyji je dostympna ino do admińistratorůw',
+'undeletehistorynoadmin' => 'Ta zajta zostoła wyćepano.
+Powůd wyÄ\87epaÅ\84o je podany w podsůmowaÅ\84u půÅ\84¼yj, razym ze danymi użytkowÅ\84ika, kery sprowjoÅ\82 zajta przed jei wyćepańym.
+Samo treść wyćepanych wersyji je dostympna ino do admińistratorůw',
 'undelete-revision' => 'Wyćepńynto wersyjo $1 (s $5 $4) keryj autorym je $3:',
 'undeleterevision-missing' => 'Felerno abo brakujůnco wersyjo.
-MožeÅ¡ mjeÄ\87 felerny link abo wersyjo můgua zostaÄ\87 wÄ\87epano nazod, abo wyÄ\87epano s archiwům.',
+Możesz mjeÄ\87 felerny link abo wersyjo můgÅ\82a zostaÄ\87 wÄ\87epano nazod, abo wyÄ\87epano ze archiwům.',
 'undelete-nodiff' => 'Ńy znejdźono popřednich wersyji.',
-'undeletebtn' => 'Uodtwůř',
+'undeletebtn' => 'Uodtwůrz',
 'undeletelink' => 'ukoż abo uodtwůrz',
 'undeleteviewlink' => 'ukoż',
 'undeletereset' => 'Wyčyść',
@@ -1966,9 +1986,9 @@ $1',
 # Block/unblock
 'blockip' => 'Zawrzij sprowjorza',
 'blockip-legend' => 'Zawrzij sprowjorza',
-'blockiptext' => 'Tyn formulař suužy do zawjerańo sprowjyń spod uokreślůnygo adresu IP abo kůnkretnymu užytkowńikowi.
-ZawjeraÄ\87 noležy jydyÅ\84y po to, by zapobjec wandalizmům, zgodÅ\84y s [[{{MediaWiki:Policy-url}}|pÅ\99ijyntymi zasadami]].
-Podej powůd (np. umješčajůnc mjana zajtůw, na kerych dopuščůno śe wandalizmu).',
+'blockiptext' => 'Tyn formularz służy do zawjerańo sprowjyń spod uokreślůnygo adresu IP abo kůnkretnymu użytkowńikowi.
+ZawjeraÄ\87 noleży jydyÅ\84y po to, by zapobjec wandalizmům, zgodÅ\84y ze [[{{MediaWiki:Policy-url}}|przijyntymi reglůma]].
+Podej powůd (np. umjeszczajůnc mjana zajtůw, na kerych dopuszczůno śe wandalizmu).',
 'ipadressorusername' => 'Adres IP abo mjano użytkowńika',
 'ipbexpiry' => 'Wygaso:',
 'ipbreason' => 'Čymu:',
@@ -1982,12 +2002,12 @@ Podej powůd (np. umješčajůnc mjana zajtůw, na kerych dopuščůno śe wanda
 ** Ůsuwańy treśći zajtůw
 ** Wprowadzańy fołszywych informacyji
 ** Wulgaryzmy
-** Wypisywańy guůpot na zajtach',
+** Wypisywańy gůpot na zajtach',
 'ipbcreateaccount' => 'Ńy dozwůl utwožyć kůnta',
-'ipbemailban' => 'Zawřij možliwość wysůuańo e-brifůw',
+'ipbemailban' => 'Zawrzij mogebność wysůłańo e-brifůw',
 'ipbenableautoblock' => 'Zawřij uostatńi adres IP tygo užytkowńika i autůmatyčńy wšyjstke kolejne, s kerych bydźe průbowou sprowjać zajty',
 'ipbsubmit' => 'Zawřij uod sprowjyń tygo užytkowńika',
-'ipbother' => 'Ikšy čas',
+'ipbother' => 'Ikszy czas',
 'ipboptions' => '2 godźiny:2 hours,1 dźyń:1 day,3 dńi:3 days,1 tydźyń:1 week,2 tydńe:2 weeks,1 mjeśůnc:1 month,3 mjeśůnce:3 months,6 mjeśůncůw:6 months,1 rok:1 year,nawdy:infinite',
 'ipbotheroption' => 'inkšy',
 'ipbotherreason' => 'Inkšy powůd:',
@@ -2024,7 +2044,7 @@ Přyńdź do [[Special:BlockList|listy zawartych adresůw IP]] coby přejřeć z
 'unblocklink' => 'uodymknij',
 'change-blocklink' => 'půmjyń zawarće uod sprowjyń',
 'contribslink' => 'ajnzace',
-'autoblocker' => 'Zawarto Ci sprowjyńo autůmatyčńy, bez tůž co užywaš tygo samygo adresu IP, co užytkowńik „[[User:$1|$1]]”.
+'autoblocker' => 'Zawarto Ci sprowjyńo autůmatyczńy, bez tůż co używosz tygo samygo adresu IP, co używocz „[[User:$1|$1]]”.
 Powůd zawarća $1 to: „$2”',
 'blocklogpage' => 'Gyszichta zawjyrańo',
 'blocklogentry' => 'zawarto [[$1]], bydźe uodymkńynty: $2 $3',
@@ -2034,8 +2054,8 @@ Na li'śće ńy mo adresůw IP, kere zawarto w sposůb autůmatyčny.
 Coby přejřeć lista uobecńy aktywnych zawarć, přyńdź na zajta [[Special:BlockList|zawartych adresůw i užytkowńikůw]].",
 'unblocklogentry' => 'uodymknyu $1',
 'block-log-flags-anononly' => 'ino anůnimowi',
-'block-log-flags-nocreate' => 'tworzińy kůnta je zawrzite',
-'block-log-flags-noautoblock' => 'autůmatyčne zawjerańy uod sprawjyń wůuůnčůne',
+'block-log-flags-nocreate' => 'tworzińy kůnta je zawarte',
+'block-log-flags-noautoblock' => 'autůmatyczne zawjerańy uod sprawjyń wyłůnczůne',
 'block-log-flags-noemail' => 'e-brif zawarty',
 'block-log-flags-nousertalk' => 'ńy może sprowjać włosnyj zajty godki',
 'block-log-flags-angry-autoblock' => 'rozszerzůne automatyczne zawjyrańe załůnczůne',
@@ -2078,7 +2098,7 @@ Zawjerańy i uodmykańy bazy danych wymogo coby plik můgu być naškreflany bez
 
 # Move page
 'move-page' => 'Przećep $1',
-'move-page-legend' => 'Přećiś artikel',
+'move-page-legend' => 'Przećiś artikel',
 'movepagetext' => "Przi půmocy formularza půńiżej możesz půmjyńyć mjano zajty i przećepnůńć jej gyszichta. Pod downym mjanym uostańe śa zajta przekerowujůnca. Zajty adresowane na stare mjano uostanům jak bůły.
 
 Jak śe na to decydujesz, sprowdź, eli ńy je to [[Special:DoubleRedirects|podwůjne]] abo [[Special:BrokenRedirects|złomane przekerowańy]].
@@ -2095,23 +2115,23 @@ To może być drastyczno abo ńyprzewidywalno zmjano, jak przećepńysz jako pop
 *ńy přećepuješ zajty do inkšy přestřeńy mjan
 *ńy ma sam zajty godki o takiym mjańe
 W takiych razach tekst godki třa přećepać, a jak třeba to i pouůnčyć z tym co juž sam jest, rynčńe. Abo možeš sie namyślić i nie přećepywać wcale ("checkbox" půnižyi).',
-'movearticle' => 'Přećiś artikel:',
-'movenologin' => 'Ńy jestžeś zalůgowany',
+'movearticle' => 'Przećiś artikel:',
+'movenologin' => 'Ńy jeżeś zalůgowany',
 'movenologintext' => 'Muśyš być zarejerowanym i [[Special:UserLogin|zalůgowanym]] užytkowńikym coby můc přećepnůńć zajta.',
 'movenotallowed' => 'Ńy moš uprownień do přećepywańo zajtůw.',
 'cant-move-user-page' => 'Ńy mosz uprowńyń do przekludzańo zajtůw użytkowńikůw (wyjůntkym sům jejich podstrony).',
 'cant-move-to-user-page' => 'Ńy mosz uprowńyń coby przekludźić zajta na plac kaj je zajta użytkowńika (wyjůntkym sům podzajty użytkowńika).',
 'newtitle' => 'Nowy titel:',
 'move-watch' => 'Dej pozůr',
-'movepagebtn' => 'Přećiś artikel',
-'pagemovedsub' => 'Přećiśńjyńće gotowe',
-'movepage-moved' => '\'\'\'"$1" přećiśńjynto ku "$2"\'\'\'',
-'articleexists' => 'Artikel s takym mjanym juž je, abo mjano je zue.
-Wybjer inkše mjano.',
-'cantmove-titleprotected' => 'Å\83y možeÅ¡ pÅ\99\87epnůÅ\84Ä\87 zajty, bez tůž co jei nowe mjano je Å\84ydozwolůne s kuli zabezpjeÄ\8d\84o pÅ\99ed utwoÅ\99yńym',
-'talkexists' => 'Zajta artikla zostaua přećepano, ale zajta godki ńy - zajta godki uo nowym mjańe juž sam jest. Poůunč, proša, teksty oubydwůch godek rynčńe.',
-'movedto' => 'přećiśńjynto ku',
-'movetalk' => 'Přećiś godke, jak možno.',
+'movepagebtn' => 'Przećiś artikel',
+'pagemovedsub' => 'Przećiśńyńće je fertig',
+'movepage-moved' => '\'\'\'"$1" przećiśńjynto ku "$2"\'\'\'',
+'articleexists' => 'Artikel ze takym mjanym już je, abo mjano je złe.
+Wybjer inksze mjano.',
+'cantmove-titleprotected' => 'Å\83y możesz przeÄ\87epnůÅ\84Ä\87 zajty, beztuż co jeij nowe mjano je Å\84ydozwolůne skuli zabezpjeczyÅ\84o przed utworzyńym',
+'talkexists' => 'Zajta artikla zostoła przećepano, nale zajta godki ńy - zajta godki uo nowym mjańe już sam jest. Połuncz, prosza, teksty uobydwůch godek rynczńe.',
+'movedto' => 'przećiśńjynto ku',
+'movetalk' => 'Przećiś godke, jak możno.',
 'move-subpages' => 'Přećepńij podzajty',
 'move-talk-subpages' => 'Jeli je to możliwe przekludź wszyjstke zajty godki podzajtůw',
 'movepage-page-exists' => 'Zajta $1 już istńeje a ńy idźe jeij autůmatyczńy nadszkryflać.',
@@ -2120,12 +2140,12 @@ Wybjer inkše mjano.',
 'movepage-max-pages' => 'Przekludzůnych uostało $1 {{PLURAL:$1|zajta|zajty|zajtůw}}. Wjynkszyj liczby ńy idźe przekludźić automatyczńy.',
 'movelogpage' => 'Przećepńynte',
 'movelogpagetext' => 'Uoto lista zajtůw, kere uostatńo zostouy přećepane.',
-'movereason' => 'Čymu:',
+'movereason' => 'Czymu:',
 'revertmove' => 'cofej',
 'delete_and_move' => 'Wyćep i przećep',
-'delete_and_move_text' => '== Přećepańy wymaga wyćepańo inkšyj zajty ==
-Zajta docelowo â\80\9e[[:$1]]â\80\9d juž sam jest.
-Čy chceš jům wyćepać, coby zrobić plac do přećepywanej zajty?',
+'delete_and_move_text' => '== Przećepańy wymogo wyćepańo inkszyj zajty ==
+Zajta docelowo â\80\9e[[:$1]]â\80\9d już sam jest.
+Czy chcysz jům wyćepać, coby zrobić plac do przećepywanej zajty?',
 'delete_and_move_confirm' => 'Toć, wyćep zajta',
 'delete_and_move_reason' => 'Wyćepano coby zrobić plac do přećepywanyj zajty',
 'selfmove' => 'Mjana zajtůw zdřůdowyj i docelowyj sům take same.
@@ -2331,12 +2351,12 @@ Nojprawdopodobńij zostoło to spowodowane bez link do zewnyntrznyj zajty intern
 
 $1',
 'filedelete-missing' => 'Plika „$1” ńy idźe wyćepać, bo ńy istńije.',
-'filedelete-old-unregistered' => 'Žůndanyj wersyji plika „$1” ńy ma w baźe danych.',
+'filedelete-old-unregistered' => 'Å»ůndanyj wersyji plika „$1” ńy ma w baźe danych.',
 'filedelete-current-unregistered' => 'Plika „$1” ńy ma w baźe danych.',
-'filedelete-archive-read-only' => 'Serwer WWW Å\84y može naÅ¡kryflaÄ\87 w katalůgu s archiwůma „$1”.',
+'filedelete-archive-read-only' => 'Serwer WWW Å\84y może naszkryflaÄ\87 we katalogu ze archiwůma „$1”.',
 
 # Browsing diffs
-'previousdiff' => '← Popředńy sprowjyńy',
+'previousdiff' => '← Poprzedńy sprowjyńy',
 'nextdiff' => 'Nostympne sprowjyńy →',
 
 # Media information
@@ -2353,7 +2373,7 @@ $1',
 # Special:NewFiles
 'newimages' => 'Galerjo nowych uobrozkůw',
 'imagelisttext' => "Půnižyj na {{PLURAL:$1||posortowanyj $2}} liśće {{PLURAL:$1|znojdowo|znojdujům|znojdowo}} śe '''$1''' {{PLURAL:$1|plik|pliki|plikůw}}.",
-'newimages-summary' => 'Na tyi ekstra zajće prezyntowane sům uostatńo wćepńynte pliki.',
+'newimages-summary' => 'Na tyj ekstra zajće prezyntowane sům uostatńo wćepńynte pliki.',
 'newimages-legend' => 'Filtruj',
 'newimages-label' => 'Mjano plika (abo jygo tajla):',
 'showhidebots' => '($1 boty)',
@@ -2485,7 +2505,7 @@ Eli plik był modyfikowany, dane mogům w tajli ńy być we zgodźe ze parametr
 'exif-gpsmeasuremode' => 'Tryb půmjaru',
 'exif-gpsdop' => 'Precyzjo půmjaru',
 'exif-gpsspeedref' => 'Jydnostka gibkości',
-'exif-gpsspeed' => 'Gibkość poźomo',
+'exif-gpsspeed' => 'Gibkość poźůmo',
 'exif-gpstrackref' => 'Poprawka půmjyndzy kerůnkym i celym',
 'exif-gpstrack' => 'Kerunek ruchu',
 'exif-gpsimgdirectionref' => 'Poprawka do kerůnku zdjyńćo',
@@ -2605,7 +2625,7 @@ Eli plik był modyfikowany, dane mogům w tajli ńy być we zgodźe ze parametr
 'exif-gaincontrol-1' => 'ńiske wzmocńyńe',
 'exif-gaincontrol-2' => 'wysoke wzmocńyńe',
 'exif-gaincontrol-3' => 'ńiske uosuabjyńy',
-'exif-gaincontrol-4' => 'wysoke uosuabjyńy',
+'exif-gaincontrol-4' => 'wysoke uosłabjyńy',
 
 'exif-contrast-0' => 'normalny',
 'exif-contrast-1' => 'Lichy',
@@ -2780,9 +2800,9 @@ Možeš tyž [[Special:EditWatchlist|užyć standardowygo edytora]].',
 'version' => 'Wersjo',
 'version-extensions' => 'Zainstalowane rozšeřyńa',
 'version-specialpages' => 'Szpecjalne zajty',
-'version-parserhooks' => 'Haki analizatora skuadńi (ang. parser hooks)',
+'version-parserhooks' => 'Haki analizatora składńi (yng. parser hooks)',
 'version-variables' => 'Zmjynne',
-'version-other' => 'Inkše',
+'version-other' => 'Inksze',
 'version-mediahandlers' => 'Wtyčki uobsůgi medjůw',
 'version-hooks' => 'Haki (ang. hooks)',
 'version-parser-extensiontags' => 'Značńiki rozšeřyń do analizatora skuadńi',
index d2fd4c6..cecd1de 100644 (file)
@@ -94,7 +94,7 @@ $messages = array(
 'tog-extendwatchlist' => 'அனைத்து பொருத்தமான மாற்றங்களைக் காட்டுமாறு கவனிப்புப் பட்டியலை விரிவாக்கு',
 'tog-usenewrc' => 'அண்மைய மாற்றங்கள் மற்றும் கவனிப்புப் பட்டியல் பக்கத்தில் மாற்றங்களை பக்கத்தை பொறுத்து குழுவாக்கு',
 'tog-numberheadings' => 'தலைப்புகளுக்கு தானியங்கி இலக்கமிடு',
-'tog-showtoolbar' => 'கருவிப் பட்டையைக் காட்டு',
+'tog-showtoolbar' => 'கருவிப்பட்டையைக் காட்டு',
 'tog-editondblclick' => 'இரட்டைச் சொடுக்கில் பக்கங்களைத் தொகு',
 'tog-editsection' => '(தொகு) இணைப்புகளின் வழியாக பிரிவுத் தொகுத்தலை செயலாக்கவும்',
 'tog-editsectiononrightclick' => 'பிரிவுத் தலைப்பின் மீது வலச் சொடுக்குவதன் மூலம் பகுதித்  தொகுப்பை செயலாக்கவும்',
@@ -115,7 +115,7 @@ $messages = array(
 'tog-shownumberswatching' => 'கவனிக்கும் பயனர்களின் எண்ணிக்கையைக் காட்டவும்',
 'tog-oldsig' => 'நடப்பு கையொப்பம்:',
 'tog-fancysig' => 'வெற்றுக் கையொப்பம் (தானியங்கி இணைப்பின்றி)',
-'tog-uselivepreview' => 'நேரடி முன்தோற்றத்தைப் பயன்படுத்து (சோதனையிலுள்ளது)',
+'tog-uselivepreview' => 'நேரடி முன்தோற்றத்தைப் பயன்படுத்துங்கள் (சோதனையிலுள்ளது)',
 'tog-forceeditsummary' => 'தொகுப்புச் சுருக்கம் வெற்றாக இருக்கும் போது எனக்கு நினைவூட்டு',
 'tog-watchlisthideown' => 'எனது தொகுப்புக்களைக் கவனிப்புப் பட்டியலிலிருந்து மறை',
 'tog-watchlisthidebots' => 'தானியங்கித் தொகுப்புக்களைக் கவனிப்புப் பட்டியலிலிருந்து மறை',
@@ -229,7 +229,7 @@ $messages = array(
 'newwindow' => '(புதிய சாளரத்துள் திறக்கும்)',
 'cancel' => 'சேமிக்காமல் திரும்பு',
 'moredotdotdot' => 'மேலும்...',
-'morenotlisted' => 'இந்த பட்டியல்கல் முழுமையாக்கவில்லை.',
+'morenotlisted' => 'இந்தப் பட்டியல் முழுமையானதல்ல.',
 'mypage' => 'பக்கம்',
 'mytalk' => 'பேச்சு',
 'anontalk' => 'இந்த ஐ.பி. முகவரிக்கான பேச்சு',
@@ -304,7 +304,7 @@ $messages = array(
 'articlepage' => 'உள்ளடக்கப் பக்கத்தைப் பார்',
 'talk' => 'உரையாடல்',
 'views' => 'பார்வைகள்',
-'toolbox' => 'à®\95à®°à¯\81விபà¯\8d à®ªà¯\86à®\9fà¯\8dà®\9fி',
+'toolbox' => 'à®\95à®°à¯\81விà®\95ளà¯\8d',
 'userpage' => 'பயனர் பக்கத்தைப் பார்',
 'projectpage' => 'திட்டப் பக்கத்தைப் பார்',
 'imagepage' => 'கோப்புப் பக்கத்தை நோக்க',
@@ -334,7 +334,7 @@ $1',
 # All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).
 'aboutsite' => '{{SITENAME}} பற்றி',
 'aboutpage' => 'Project:விவரம்',
-'copyright' => 'உள்ளடக்கங்கள் $1 இன் கீழ் கிடைக்கின்றன.',
+'copyright' => 'à®\95à¯\81றிà®\95à¯\8dà®\95பà¯\8dபà®\9fபà¯\8dபà®\9fாவிà®\9fà¯\8dà®\9fாலà¯\8d à®\89ளà¯\8dளà®\9fà®\95à¯\8dà®\95à®\99à¯\8dà®\95ளà¯\8d $1 à®\87னà¯\8d à®\95à¯\80à®´à¯\8d à®\95ிà®\9fà¯\88à®\95à¯\8dà®\95ினà¯\8dறன.',
 'copyrightpage' => '{{ns:project}}:பதிப்புரிமை',
 'currentevents' => 'தற்போதைய நிகழ்வுகள்',
 'currentevents-url' => 'Project:நடப்பு நிகழ்வுகள்',
@@ -460,7 +460,7 @@ $1',
 'viewsource-title' => '$1க்கான மூலத்தைப்  பார்',
 'actionthrottled' => 'செயற்பாடு கட்டுப்படுத்தப்பட்டது',
 'actionthrottledtext' => 'எரிதக் காப்பு நடவடிக்கையாகப் பயனொருவர் குறித்த சிறு கால இடைவெளியில் இச்செயற்பாட்டை அதிகளவில் செய்வது தடுக்கப்பட்டுள்ளது. நீர் அவ்வெல்லையைத் தாண்டிவிட்டீர். அருள் கூர்ந்து சில நிமிடங்களில் முயலவும்.',
-'protectedpagetext' => 'இப்பக்கம் தொகுக்கப்படுவதை தவிர்ப்பதற்காக பூட்டப்பட்டுள்ளது.',
+'protectedpagetext' => 'இப்பக்கம் தொகுக்கப்படுவதையோ அல்லது பிற செயல்களைத் தவிர்ப்பதற்காகவோ பூட்டப்பட்டுள்ளது.',
 'viewsourcetext' => 'நீங்கள் இந்தப் பக்கத்தின் மூலத்தைப் பார்க்கவும் அதனை நகலெடுக்கவும் முடியும்:',
 'viewyourtext' => "நீங்கள் இந்த பக்கத்திற்கான ''' உங்கள் திருத்தங்களுக்கான ''' மூலத்தைக் காணவும்  நகலெடுக்கவும் முடியும்.",
 'protectedinterface' => 'இப்பக்கம் இம்மென் பொருளுக்கான பயனர் இடைமுக உரைகளை வழங்குகிறது, விசம தொகுப்புக்களை தவிர்ப்பதற்க்காக இப்பக்கம் பூட்டப்பட்டுள்ளது.',
@@ -529,6 +529,7 @@ $1',
 'userlogin-resetpassword-link' => 'உங்கள் கடவுச்சொல் மறந்துவிட்டதா?',
 'helplogin-url' => 'Help:புகுபதிகை',
 'userlogin-helplink' => '[[{{MediaWiki:helplogin-url}}|புகுபதிவதற்கான உதவி]]',
+'userlogin-createanother' => 'மற்றொரு கணக்கு ஒன்றை உருவாக்கவும்',
 'createacct-join' => 'உங்களின் தகவலை கீழிடவும்',
 'createacct-another-join' => 'கீழே புதிய கணக்கிற்கான தகவல்களை உள்ளிடவும்.',
 'createacct-emailrequired' => 'மின்னஞ்சல் முகவரி',
@@ -585,7 +586,8 @@ $1',
 'noemailcreate' => 'ஒரு செல்லத்தக்க மின்னஞ்சல் முகவரியை நீங்கள் தரவேண்டும்.',
 'passwordsent' => '"$1" பயனருக்கான மின்னஞ்சல் முகவரிக்கு ஒரு புதிய கடவுச்சொல் அனுப்பப்பட்டுள்ளது. பெற்றுக்கொண்டதும் தயவுசெய்து மீண்டும் புகுபதிகை செய்யவும்.',
 'blocked-mailpassword' => 'உங்கள் ஐ.பி. முகவரி தடுக்கப்பட்டுள்ளது, விசம செயற்பாடுகளைத் தவிர்க்க கடவுச்சொல் மீட்புச் செயலியை நீங்கள் பயன்படுத்து அனுமதிக்கப்படவில்லை.',
-'eauthentsent' => 'உறுதிப்படுத்தல் மின்னஞ்சலொன்று நீங்கள் கொடுத்த மின்னஞ்சல் முகவரிக்கு அனுப்பப் பட்டுள்ளது. மேலதிகமாக எந்த மின்னஞ்சலும் இந்த முகவரிக்கு அனுப்பப்படு முன்னர், மின்னலில் கொடுக்கப்பட்டுள்ள அறிவுறுத்தல்களின் படி, இம்மின்னஞ்சல் முகவரி உங்களுடையது என்பதை உறுதிப்படுத்தவும்.',
+'eauthentsent' => 'உறுதிப்படுத்தல் மின்னஞ்சலொன்று நீங்கள் கொடுத்த மின்னஞ்சல் முகவரிக்கு அனுப்பப் பட்டுள்ளது.
+மேலும் மின்னஞ்சல்கள் இந்த முகவரிக்கு அனுப்பப்படும் முன்னர், மின்னஞ்சலில் கொடுக்கப்பட்டுள்ள வழிமுறைகளை பின்பற்றி, இம்மின்னஞ்சல் முகவரி உங்களுடையது தான் என்பதை உறுதிப்படுத்தவும்.',
 'throttled-mailpassword' => 'கடந்த {{PLURAL:$1|மணிநேரத்துக்குள்|$1 மணிநேரங்களுக்குள்}} ஒரு கடவுச்சொல் நினைவூட்டல் மின்னஞ்சல் ஏற்கனவே அனுப்பப்பட்டுவிட்டது. விசமப் பயன்பாடுகளைத் தவிர்ப்பதற்காக {{PLURAL:$1|மணிநேரத்திற்கு|$1 மணிநேரங்களுக்கு}} ஒரு கடவுச்சொல் நினைவூட்டல் மின்னஞ்சல் மட்டுமே அனுப்பப்படும்.',
 'mailerror' => 'மின்னஞ்சல் அனுப்புவதில் தவறு: $1',
 'acct_creation_throttle_hit' => 'தங்களது IP முகவரியை பயன்படுத்தி இந்த விக்கியில் நேற்று {{PLURAL:$1|1 கணக்கு |$1 கணக்குகள்}} உருவாக்கப்பட்டுள்ளது.  தற்போது இதுவே மிக அதிகமாக அனுமதிக்கப்பட்ட அளவாகும்.
@@ -638,8 +640,11 @@ $1',
 
 # Special:PasswordReset
 'passwordreset' => 'கடவுச்சொல்லை மீட்டமை',
+'passwordreset-text-one' => 'உங்கள் கடவுச்சொல்லை மீட்டமைக்க இந்த படிவத்தை நிறைவு செய்க.',
+'passwordreset-text-many' => '{{PLURAL:$1|உங்கள் கடவுச்சொல்லை மீட்டமைக்க புலங்கள் ஒன்றினை நிரப்பவும்.}}',
 'passwordreset-legend' => 'கடவுச்சொல்லை மீட்டமை',
 'passwordreset-disabled' => 'கடவுச்சொல் மீட்டமைப்பு இந்த விக்கியில் செயலிழக்க செய்யப்பட்டுள்ளது.',
+'passwordreset-emaildisabled' => 'மின்னஞ்சல் வசதி இந்த விக்கியில் முடக்கப்பட்டுள்ளது.',
 'passwordreset-username' => 'பயனர் பெயர்:',
 'passwordreset-domain' => 'இணையதள முகவரி:',
 'passwordreset-capture' => 'விளைவு மின்னஞ்சலை காண்',
@@ -751,9 +756,7 @@ $1 எனும் பயனரையோ வேறு [[{{MediaWiki:Grouppage-sy
 'loginreqlink' => 'புகுபதிகை',
 'loginreqpagetext' => 'ஏனைய பக்கங்களைப் பார்க்க  நீங்கள் $1 செய்ய வேண்டும்.',
 'accmailtitle' => 'கடவுச்சொல் அனுப்பப்பட்டுள்ளது.',
-'accmailtext' => "தான்தோன்றித்தனமான ஒரு கடவுச்சொல்லை [[User talk:$1|பயனர் பேச்சு:$1|$1]] $2-க்கு அனுப்பி வைக்கப்பட்டுள்ளது.
-
-இந்த புதிய கணக்கிற்கான கடவுச்சொல்லை புகுபதிகை செய்தவுடன் மாற்றிக்கொள்ளவும் ''[[Special:ChangePassword|கடவுச்சொல்லை மாற்று]]''.",
+'accmailtext' => "தானியக்கமாக [[User talk:$1|$1]]-க்கு ஒரு கடவுச்சொலை உறுவாக்கி $2-க்கு அனுப்பி வைக்கப்பட்டுள்ளது. இதனை புகுபதிகை செய்தவுடன் ''[[Special:ChangePassword|கடவுச்சொல்லை மாற்று]]'' பக்கத்தில் மாற்றிக்கொள்ளளாம்.",
 'newarticle' => '(புதிது)',
 'newarticletext' => 'ஒரு இணைப்பினூடாக நீங்கள் வந்துள்ள இப்பக்கம் இன்னும் உருவாக்கப்படவில்லை. பக்கத்தை உருவாக்குவதற்குக் கீழேயுள்ள கட்டத்துள் தட்டச்சிடத் தொடங்குங்கள். (மேலதிக விபரங்களுக்கு [[{{MediaWiki:Helppage}}|உதவிப் பக்கத்தைப்]] பார்க்கவும்). நீங்கள் தவறுதலாக இங்கே வந்திருந்தால், உங்கள் உலாவியின் பின் செல்வதற்கான பொத்தானைச் சொடுக்கவும்.',
 'anontalkpagetext' => "----''இது இன்னும் கணக்கொன்று ஏற்படுத்தாத அல்லது வழமையாக பயனர் கணக்கை பயன்படுத்தாத பயனர்களுக்குரிய கலந்துரையாடல் பக்கமாகும். அதனால் நாங்கள் இவரை அடையாளம் காண்பதற்கு எண்சார்ந்த ஐபி முகவரியைப் பயன்படுத்த வேண்டியதாய் இருக்கின்றது. இவ்வாறான ஐபி முகவரிகள் பல பயனர்களினால் பகிர்ந்துகொள்ளப்படலாம்.
@@ -767,12 +770,11 @@ $1 எனும் பயனரையோ வேறு [[{{MediaWiki:Grouppage-sy
 'userpage-userdoesnotexist' => '"<nowiki>$1</nowiki>" என்றக் கணக்கு இன்னமும் பதிவுச் செய்யப்படவில்லை. இதை உருவாக்க/தொகுக்க வேண்டுமா என்பதை உறுதிப்படுத்தவும்.',
 'userpage-userdoesnotexist-view' => 'பயனர் கணக்கு "$1" பதியப்படவில்லை',
 'blocked-notice-logextract' => 'இந்தப் பயனர் தற்சமயம் தடை செய்யப்பட்டுள்ளார். இவரது தடை பதிகையின் அண்மைய மாற்றம் கீழே தரப்பட்டுள்ளது:',
-'clearyourcache' => "'''à®\95வனிà®\95à¯\8dà®\95''' - சேமித்த பின்னர், நீங்கள் செய்த மாற்றங்களைக் காண்பதற்கு உங்கள் உலவியின் இடைமாற்று அகற்றப்பட வேண்டும்.'''
+'clearyourcache' => "'''à®\95à¯\81றிபà¯\8dபà¯\81''' - சேமித்த பின்னர், நீங்கள் செய்த மாற்றங்களைக் காண்பதற்கு உங்கள் உலவியின் இடைமாற்று அகற்றப்பட வேண்டும்.'''
 *'''மொஸில்லா பயர்பாக்ஸ் / சபாரி:''' ''Shift+Reload'', அல்லது ''Ctrl-F5'' அல்லது ''Ctrl-R'' (''⌘-R'' Mac ல்)
 *'''கூகிள் குரோம்''' ''Ctrl-Shift-R'' அழுத்தவும். (''⌘-Shift-R''  Mac ல்) ;
-*'''கொன்குவெரர்: ''' ''Reload'' அல்லது ''F5'' கிளிக் செய்யவும்;
-*'''ஒபேரா:''' ''Tools → Preferences'' இல் இடைமாற்றை அகற்றவும்;
-*'''இண்டர்நெட் எக்ஸ்ப்ளோரர்:''' ''Ctrl-Refresh'' அல்லது ''Ctrl-F5'' ஐ அழுத்தவும்.",
+*'''இண்டர்நெட் எக்ஸ்ப்ளோரர்:''' ''Ctrl-Refresh'' அல்லது ''Ctrl-F5'' ஐ அழுத்தவும்.
+*'''ஒபேரா:''' ''Tools → Preferences'' இல் இடைமாற்றை அகற்றவும்;",
 'usercssyoucanpreview' => "'''சிறு உதவி:''' தங்களது சி.எஸ்.எஸ்(CSS)-ஐ சேமிப்பதற்கு முன்பாக \"{{int:showpreview}}\" பொத்தானைப் பயனபடுத்தி சோதனை செய்யவும்",
 'userjsyoucanpreview' => "'''சிறு உதவி:''' தங்களது ஜாவா-வரிவடிவத்தை (JavaScript-ஐ) சேமிப்பதற்கு முன்பாக \"{{int:showpreview}}\" பொத்தானைப் பயனபடுத்தி சோதனை செய்யவும்",
 'usercsspreview' => "'''உங்களது பயனர் சி.எஸ்.எஸ். இன் முன் தோற்றத்தை மட்டுமே காண்கிறீர்கள் என்பதை நினைவில் கொள்ளவும்.'''
@@ -925,7 +927,7 @@ $1 எனும் பயனரையோ வேறு [[{{MediaWiki:Grouppage-sy
 'history-fieldset-title' => 'வரலாற்றில் தேடவும்',
 'history-show-deleted' => 'நீக்கப்பட்டவை மட்டும்',
 'histfirst' => 'மிகப் பழைய',
-'histlast' => 'மிக புதிய',
+'histlast' => 'மிகப் புதிய',
 'historysize' => '({{PLURAL:$1|1 பைட்டு|$1 பைட்டுகள்}})',
 'historyempty' => '(வெற்று)',
 
@@ -1178,7 +1180,7 @@ $1",
 'prefs-rendering' => 'தோற்றம்',
 'saveprefs' => 'சேமி',
 'resetprefs' => 'சேமிக்காத மாற்றங்கள் நீக்குக',
-'restoreprefs' => 'எல்லோருக்கும் பொதுவான வடிவமைப்பைத் திரும்பக்கொண்டுவரவும்.',
+'restoreprefs' => 'எல்லோருக்கும் பொதுவான வடிவமைப்பைத் திரும்பக்கொண்டுவரவும் (எல்லா பிறிவுகளிலும்).',
 'prefs-editing' => 'தொகுத்தல்',
 'rows' => 'நிரைகள் (கிடை வரிசைகள்):',
 'columns' => 'நிரல்கள்',
@@ -1236,7 +1238,7 @@ $1",
 
 அது $1 {{PLURAL:$1|எழுத்து|எழுத்துக்களுக்கு}} மேல் இருக்கக்கூடாது.',
 'yourgender' => 'பால்:',
-'gender-unknown' => 'à®\95à¯\81றிபà¯\8dபிà®\9fபà¯\8dபà®\9fவில்லை',
+'gender-unknown' => 'நானà¯\8d à®\95à¯\81றிபà¯\8dபிà®\9f à®µà®¿à®°à¯\81à®®à¯\8dபவில்லை',
 'gender-male' => 'ஆண்',
 'gender-female' => 'பெண்',
 'prefs-help-gender' => 'விருப்பத் தேர்வுதான்: ஒருவரைக் குறிப்பிடும்பொழுது, அவருடைய பால் சரியானதாக இருக்க மென்கலம் பயன்படுத்தும் தகவல். இத்தகவல் பொதுவில் கிடைக்கும்படி இனி இருக்கும்.',
@@ -1250,7 +1252,7 @@ $1",
 'prefs-signature' => 'கையெழுத்து',
 'prefs-dateformat' => 'தேதியின் வடிவமைப்பு',
 'prefs-timeoffset' => 'நேர வித்தியாசம்',
-'prefs-advancedediting' => 'à®®à¯\87à®®à¯\8dபà®\9fà¯\8dà®\9f விருப்பத்தேர்வுகள்',
+'prefs-advancedediting' => 'பà¯\8aதà¯\81 விருப்பத்தேர்வுகள்',
 'prefs-editor' => 'தொகுப்பாளர்',
 'prefs-preview' => 'முன்தோற்றம்',
 'prefs-advancedrc' => 'மேம்பட்ட விருப்பத்தேர்வுகள்',
@@ -1261,6 +1263,7 @@ $1",
 'prefs-displaysearchoptions' => 'விருப்பத்தேர்வுகளைக் காட்டு',
 'prefs-displaywatchlist' => 'விருப்பத்தேர்வுகளைக் காட்டு',
 'prefs-diffs' => 'வித்தியாசங்கள்',
+'prefs-help-prefershttps' => 'இந்த விருப்பத்தேர்வு உங்களின் அடுத்த புகுபதிகையிலிருந்து செயல்பாட்டுக்கு வரும்.',
 
 # User preference: email validation using jQuery
 'email-address-validity-valid' => 'மின்னஞ்சல் முகவரி முறையானதாகத் தோன்றுகிறது',
@@ -1286,9 +1289,11 @@ $1",
 'userrights-no-interwiki' => 'ஏனைய விக்கிகளில் பயனர் உரிமைகளை மாற்றும் அனுமதி உங்களுக்குக் கிடையாது.',
 'userrights-nodatabase' => '$1 தரவுத்தளம் கிடையாது அல்லது உள்ளக விக்கியில் கிடையாது.',
 'userrights-nologin' => 'பயனர் உரிமைகளை வழங்குவதற்கு நீங்கள் நிர்வாகி கணக்கில் [[Special:UserLogin|புகுபதிகை]] செய்ய வேண்டும்.',
-'userrights-notallowed' => 'பயனரà¯\8d à®\89ரிமà¯\88à®\95ளà¯\88 à®®à®¾à®±à¯\8dà®±à¯\81à®®à¯\8d à®\85னà¯\81மதி à®\89à®\99à¯\8dà®\95ளà¯\8d à®ªà®¯à®©à®°à¯\8d à®\95ணà®\95à¯\8dà®\95à¯\81à®\95à¯\8d கிடையாது.',
+'userrights-notallowed' => 'பயனரà¯\8d à®\89ரிமà¯\88à®\95ளà¯\88 à®®à®¾à®±à¯\8dà®±à¯\81à®®à¯\8d à®\85னà¯\81மதி à®\89à®\99à¯\8dà®\95ளà¯\81à®\95à¯\8dà®\95à¯\81 கிடையாது.',
 'userrights-changeable-col' => 'நீங்கள் மாற்றக்கூடிய குழுக்கள்',
 'userrights-unchangeable-col' => 'நீங்கள் மாற்ற முடியாத குழுக்கள்',
+'userrights-conflict' => 'பயனர் உரிமைகளின் மாற்றங்களில் முரண்பாடு உள்ளது! மறு ஆய்வு செய்து, உங்கள் மாற்றங்களை உறுதி செய்க.',
+'userrights-removed-self' => 'நீங்கள் உங்களது சொந்த உரிமைகளை வெற்றிகரமாக நீக்கியுள்ளீர்கள். இதனால் நீங்கள் இனி இந்தப்பக்கத்தினை பார்க்க இயலாது.',
 
 # Groups
 'group' => 'குழு:',
@@ -1332,7 +1337,7 @@ $1",
 'right-reupload-shared' => 'பகிர்விலுள்ள ஊடகக் கிடங்கியில் உள்ள கோப்புகளை உள்ளகப் பயன்பாட்டுக்காக மேலுரிமையுடன் பதிவேற்று',
 'right-upload_by_url' => 'யூ.ஆர்.எல். ஒன்றிலிருந்து கோப்பை பதிவேற்றல்',
 'right-purge' => 'உறுதிப்படுத்தல் பக்கமெதுவுமின்றி பக்க இடைமாற்றை நீக்கல்',
-'right-autoconfirmed' => 'பà®\95à¯\81தியாà®\95 à®\95ாà®\95à¯\8dà®\95பà¯\8dபà®\9fà¯\8dà®\9f à®ªà®\95à¯\8dà®\95à®\99à¯\8dà®\95ளà¯\88 à®¤à¯\8aà®\95à¯\81தà¯\8dதல்',
+'right-autoconfirmed' => 'à®\87ணà¯\88ய à®¨à¯\86றிமà¯\81à®±à¯\88 à®®à¯\81à®\95வரியினà¯\8d (IP) à®\85à®\9fிபà¯\8dபà®\9fà¯\88யிலà¯\8d à®\89ளà¯\8dள à®µà®¿à®\95ித à®µà®°à®®à¯\8dபà¯\81à®\95ளினà¯\8d à®\95à®\9fà¯\8dà®\9fà¯\81பà¯\8dபாà®\9fà¯\8dà®\9fà¯\81à®\95à¯\8dà®\95à¯\81 à®\89à®\9fà¯\8dபà®\9fாதà¯\80à®°à¯\8dà®\95ள்',
 'right-bot' => 'தானாக செய்யப்பட்டசெயலாக கருதப்படும்',
 'right-nominornewtalk' => 'உரையாடல் பக்கங்களில் செய்யும் சிறு தொகுப்புகளை அறிவிக்கப் புதிய செய்தி ஏதும் அனுப்பவேண்டாம்.',
 'right-apihighlimits' => 'உயர் தரம் கொண்ட  API கேள்விளை பயன்படுத்தவும்',
@@ -1410,8 +1415,8 @@ $1",
 'action-block' => 'இப்பயனரை மேலும் தொகுக்க அனுமதிக்க வேண்டாம்',
 'action-protect' => 'இந்த பக்கத்திற்கான பாதுகாப்பு நிலைகளை மாற்றவும்',
 'action-rollback' => 'ஒரு குறிப்பிட்ட பக்கத்தை திருத்திய பயனரின் திருத்தங்களை பழைய நிலைமைக்கு மாற்றவும்.',
-'action-import' => 'மறà¯\8dà®±à¯\8aà®°à¯\81 à®µà®¿à®\95à¯\8dà®\95ியிலà¯\8d à®\87à®°à¯\81நà¯\8dதà¯\81 à®\87பà¯\8dபà®\95à¯\8dà®\95தà¯\8dதை இறக்குமதி செய்யவும்',
-'action-importupload' => 'à®\95à¯\8bபà¯\8dபà¯\81 à®ªà®¤à®¿à®µà¯\87à®±à¯\8dறதà¯\8dதிலிரà¯\81நà¯\8dதà¯\81 à®\87பà¯\8dபà®\95à¯\8dà®\95தà¯\8dதை இறக்கவும்',
+'action-import' => 'மறà¯\8dà®±à¯\8aà®°à¯\81 à®µà®¿à®\95à¯\8dà®\95ியிலà¯\8d à®\87à®°à¯\81நà¯\8dதà¯\81 à®ªà®\95à¯\8dà®\95à®\99à¯\8dà®\95ளை இறக்குமதி செய்யவும்',
+'action-importupload' => 'à®\95à¯\8bபà¯\8dபà¯\81 à®ªà®¤à®¿à®µà¯\87à®±à¯\8dறதà¯\8dதிலிரà¯\81நà¯\8dதà¯\81 à®ªà®\95à¯\8dà®\95à®\99à¯\8dà®\95ளை இறக்கவும்',
 'action-patrol' => 'மற்றவர்களின் தொகுப்புகளைப் பார்வையிட்டதாகக் குறிக்கவும்',
 'action-autopatrol' => 'உங்களது திருத்தத்தைப் பார்வையிட்டதாகக் குறிப்பிட்டுக் கொள்க.',
 'action-unwatchedpages' => 'கவனிக்கப்படாத பக்கங்களின் பட்டியலைப் பார்க்க',
@@ -1420,6 +1425,8 @@ $1",
 'action-userrights-interwiki' => 'ஏனைய விக்கித் தளங்களின் பயனர் உரிமைகளைத் தொகு',
 'action-siteadmin' => 'தரவுதளத்தை பூட்டு அல்லது  பூட்டாதே',
 'action-sendemail' => 'மின்னஞ்சல்கள் அனுப்பு',
+'action-editmywatchlist' => 'உங்கள் கவனிப்பு பட்டியலை தொகு',
+'action-viewmywatchlist' => 'உங்கள் கவனிப்பு பட்டியலை பார்',
 
 # Recent changes
 'nchanges' => '{{PLURAL:$1|ஓர் மாற்றம்|$1 மாற்றங்கள்}}',
@@ -1453,7 +1460,7 @@ $1",
 'rc_categories_any' => 'ஏதாவது',
 'rc-change-size-new' => '$1 {{PLURAL:$1|பைட்டு|பைட்டுகள்}} -மாற்றத்திற்குப் பிறகு',
 'newsectionsummary' => '/* $1 */ புதிய பகுதி',
-'rc-enhanced-expand' => 'விவரà®\99à¯\8dà®\95ளà¯\88à®\95à¯\8d à®\95ாà®\9fà¯\8dà®\9fà¯\81 (à®\9aாவாநிரலà¯\8d à®¤à¯\87வà¯\88)',
+'rc-enhanced-expand' => 'விவரதà¯\8dதà¯\88 à®\95ாà®\9fà¯\8dà®\9fà¯\81',
 'rc-enhanced-hide' => 'விவரங்களை மறை',
 'rc-old-title' => 'முதலில் "$1" என உருவாக்கப்பட்டது',
 
@@ -1472,7 +1479,7 @@ $1",
 'reuploaddesc' => 'பதிவேற்றத்தை நிறுத்திவிட்டு பதிவேற்றும் படிவத்துக்கு மீளச் செல்க',
 'upload-tryagain' => 'மாற்றம் செய்யப்பட்ட கோப்புத் தகவலைச் சமர்ப்பிக்கவும்',
 'uploadnologin' => 'புகுபதிகை செய்யப்படவில்லை',
-'uploadnologintext' => 'கோப்புகளைப் பதிவேற்றம் செய்வதற்கு நீங்கள் [[Special:UserLogin|புகுபதிகை]] செய்திருக்க வேண்டும்.',
+'uploadnologintext' => 'கோப்புகளைப் பதிவேற்ற நீங்கள் $1 செய்திருக்க வேண்டும்.',
 'upload_directory_missing' => 'தரவேற்றப்படும் அடைவு (டைரெக்டரி) ( $1 ) ஐ காணவில்லை, அதோடு வலைத்தளவழங்கி வழியாக உருவாக்கவும் இயலவில்லை.',
 'upload_directory_read_only' => 'பதிவேற்ற அடைவு ($1) வழங்கனால் எழுதப்படமுடியாது.',
 'uploaderror' => 'பதிவேற்றத் தவறு',
@@ -1646,9 +1653,9 @@ $1',
 # img_auth script messages
 'img-auth-accessdenied' => 'அனுமதி மறுக்கப்பட்டது',
 'img-auth-nopathinfo' => 'PATH_INFO காணவில்லை.
-உங்கள் வழங்கி  இந்தத் தகவலை அனுப்புமாறு அமைக்கப்படவில்லை
-இது  சிச்சிஐ (CGI)- அடிப்படையிலானதாக இருக்கலாம் மற்றும் img_authக்கும் ஆதரவு கிடையாது.
-பார்க்கவும் : https://www.mediawiki.org/wiki/Manual:Image_Authorization See image authorization.',
+உங்கள் வழங்கி இந்தத் தகவலை அனுப்ப அமைக்கப்படவில்லை
+இது சிஜிஐ (CGI)- அடிப்படையிலானதாகவோ img_auth-ஐ ஆதரக்காததாகவோ இருக்கலாம் .
+பார்க்கவும் https://www.mediawiki.org/wiki/Manual:Image_Authorization.',
 'img-auth-notindir' => 'கோரிய பாதை இந்த குறிப்பிட்ட தகவலேற்று கோப்புறையில் இல்லை.',
 'img-auth-badtitle' => 'செல்லத்தக்க தலைப்பு "$1" ஐ கட்ட இயலவில்லை.',
 'img-auth-nologinnWL' => 'நீங்கள் புகுபதிகை செய்யவில்லை, மேலும் "$1"  அனுமதிக்கப்பெற்ற பட்டியலில் இல்லை',
@@ -1684,8 +1691,7 @@ $1',
 'upload_source_file' => ' (உங்கள் கணணியில் உள்ள கோப்பு)',
 
 # Special:ListFiles
-'listfiles-summary' => 'இந்த சிறப்புப் பக்கம் அனைத்து தரவேற்றப்பட்ட கோப்புகளையும் காண்பிக்கும்.
-பயனர் பெயர் மூலம் வடிகட்டும் போது, அந்த பயனர் தரவேற்றிய கோப்பின் மிக சமீபத்திய பதிப்பு மட்டும் காண்பிக்கப்பட்டுள்ளது.',
+'listfiles-summary' => 'இச்சிறப்புப் பக்கம் பதிவேற்றப்பட்ட கோப்புகளைப் பட்டியலிடுகிறது.',
 'listfiles_search_for' => 'பின்வரும் பெயருள்ள ஊடகக் கோப்பைத் தேடு:',
 'imgfile' => 'கோப்பு',
 'listfiles' => 'படிமங்களின் பட்டியல்',
@@ -1976,8 +1982,9 @@ $1',
 
 # Special:ListGroupRights
 'listgrouprights' => 'பயனர் குழு உரிமைகள்',
-'listgrouprights-key' => '<span class="listgrouprights-granted">உரிமை வழங்கப்பட்டது</span>
- * <span class="listgrouprights-revoked">உரிமை பறிக்கபட்டது</span>',
+'listgrouprights-key' => 'குறியீட்டு விளக்கம்:
+* <span class="listgrouprights-granted">உரிமை வழங்கப்பட்டது</span>
+* <span class="listgrouprights-revoked">உரிமை பறிக்கபட்டது</span>',
 'listgrouprights-group' => 'குழு',
 'listgrouprights-rights' => 'உரிமைகள்',
 'listgrouprights-helppage' => 'Help:குழு உரிமைகள்',
@@ -2049,8 +2056,8 @@ $1',
 'notanarticle' => 'ஒரு கட்டுரைப் பக்கமல்ல',
 'notvisiblerev' => 'திருத்தம் நீக்கப்பட்டுள்ளது',
 'watchlist-details' => 'பேச்சுப் பக்கங்களைத் தவிர்த்து, {{PLURAL:$1|$1 பக்கம் கவனிக்கப்பட்டது.|$1 பக்கங்கள் கவனிக்கப்பட்டன.}}',
-'wlheader-enotif' => 'மினà¯\8dனà®\9eà¯\8dà®\9aலà¯\8d à®\85றிவிதà¯\8dதலà¯\8dகள் செயல்படுத்தப்பட்டுள்ளன.',
-'wlheader-showupdated' => "à®\89மதà¯\81 à®\95à®\9fà¯\88à®\9aி à®µà®°à¯\81à®\95à¯\88à®\95à¯\8dà®\95à¯\81பà¯\8d à®ªà®¿à®©à¯\8dனரà¯\8d à®®à®¾à®±à¯\8dà®±à®\99à¯\8dà®\95ளà¯\8d à®\9aà¯\86யà¯\8dயபà¯\8dபà®\9fà¯\8dà®\9f à®ªà®\95à¯\8dà®\95à®\99à¯\8dà®\95ளà¯\8d '''தà®\9fிதà¯\8dத à®\8eà®´à¯\81தà¯\8dதà¯\81à®\95à¯\8dà®\95ளால்''' காட்டப்பட்டுள்ளன",
+'wlheader-enotif' => 'மினà¯\8dனà®\9eà¯\8dà®\9aலà¯\8d à®\85றிவிபà¯\8dபà¯\81கள் செயல்படுத்தப்பட்டுள்ளன.',
+'wlheader-showupdated' => "à®\89à®\99à¯\8dà®\95ளà¯\8d à®\95à®\9fà¯\88à®\9aி à®µà®°à¯\81à®\95à¯\88à®\95à¯\8dà®\95à¯\81பà¯\8d à®ªà®¿à®©à¯\8dனரà¯\8d à®®à®¾à®±à¯\8dà®±à®\99à¯\8dà®\95ளà¯\8d à®\9aà¯\86யà¯\8dயபà¯\8dபà®\9fà¯\8dà®\9f à®ªà®\95à¯\8dà®\95à®\99à¯\8dà®\95ளà¯\8d '''தà®\9fிதà¯\8dத à®\8eà®´à¯\81தà¯\8dதà¯\81à®\95à¯\8dà®\95ளில்''' காட்டப்பட்டுள்ளன",
 'watchmethod-recent' => 'கவனிக்கப்படுகின்ற பக்கங்களுக்காக, அண்மைய தொகுப்புகள் தேடிப் பார்க்கப்படுகிறன',
 'watchmethod-list' => 'அண்மைய தொகுப்புகளுக்காக, கவனிக்கப்படுகின்ற பக்கங்கள் தேடிப் பார்க்கப்படுகிறன',
 'watchlistcontains' => 'உங்கள் கவனிப்புப் பட்டியல் {{PLURAL:$1|ஒரு பக்கத்தைக்|$1 பக்கங்களைக்}} கொண்டுள்ளது.',
@@ -2119,10 +2126,12 @@ $NEWPAGE
 'deletecomment' => 'காரணம்:',
 'deleteotherreason' => 'வேறு மேலதிக காரணம்:',
 'deletereasonotherlist' => 'வேறு காரணம்',
-'deletereason-dropdown' => '*பொதுவான நீக்கல் காரணங்கள்
-** à®\95ாபà¯\8dபà¯\81ரிமà¯\88 à®®à¯\80றபà¯\8dபà®\9fà¯\8dà®\9fà®®à¯\88
+'deletereason-dropdown' => '* பொதுவான நீக்கல் காரணங்கள்
+** à®\8eரிதமà¯\8d/வà¯\80ணà¯\8dà®\9aà¯\86யà¯\8dதிà®\95ளà¯\8d
 ** விசமத் தொகுப்பு
-** ஆசிரியர் வேண்டுகோள்',
+** காப்புரிமை மீறப்பட்டமை
+** ஆசிரியர் வேண்டுகோள்
+** உடைந்த வழிமாற்று',
 'delete-edit-reasonlist' => 'நீக்கல் காரணங்களைத் தொகு',
 'delete-toobig' => 'இப்பக்கம் அதிகமான திருத்தங்களை கொண்டுள்ளது, குறிப்பாக $1 {{PLURAL:$1|திருத்தத்திற்கு|திருத்தங்களிற்கு}} மேல்.
 {{SITENAME}} தளத்தின் தரவுகள் தற்செயலாக அழிந்துப்போவதை தடுப்பதற்க்காக இவ்வாறான பக்கங்கள் நீக்கப்படுவது முடக்கப்பட்டுள்ளது.',
@@ -2143,7 +2152,7 @@ $NEWPAGE
 இப்பக்கத்தை கடைசியாகத் தொகுத்தவர் [[User:$3|$3]] ([[User talk:$3|Talk]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).',
 'editcomment' => "தொகுப்பிற்கான சிறுகுறிப்புக்கள் இவை:\"''\$1''\".",
 'revertpage' => '[[Special:Contributions/$2|$2]] ([[User talk:$2|பேச்சு]]) செய்தத் தொகுப்புகள் நீக்கப்பட்டு [[User:$1|$1]] இன் பதிப்புக்கு முன்நிலையாக்கப்பட்டது',
-'revertpage-nouser' => '(பயனர் பெயர் நீக்கப்பட்டது) செய்த தொகுப்புகளை இல்லாது செய்து, [[User:$1|$1]] கடைசியாகச் செய்த திருத்தத்துக்கு மாற்றப்பட்டது',
+'revertpage-nouser' => 'மறைக்கப்பட்ட பயனர் செய்த தொகுப்புகள் இல்லாது செய்யப்பட்டு, {{GENDER:$1|[[User:$1|$1]]}} கடைசியாகச் செய்த திருத்தத்துக்கு முன்நிலையாக்கப்பட்டது',
 'rollback-success' => '$1 செய்தத் தொகுப்புகள் நீக்கப்பட்டு $2 இன் பதிப்புக்கு முன்நிலையாக்கப்பட்டது.',
 
 # Edit tokens
@@ -2177,8 +2186,8 @@ $NEWPAGE
 '''$1''' பக்கத்துக்கான நடப்பு அமைப்புகள் பின்வருமாறு:",
 'protect-cascadeon' => 'இந்தப்பக்கம் படிநிலை காப்புகுட்படுத்தப்பட்ட {{PLURAL:$1|பக்கத்திற்கு|பக்கங்களிற்கு}} இணைக்கப்பட்டுள்ளமையால் காப்புச் செய்யப்பட்டுள்ளது. இந்த பக்கத்தின் காப்பு நிலையை நீங்கள் மாற்றம் செய்யலாம் எனினும் இது படிநிலை காப்பினை மாற்றம் செய்யாது.',
 'protect-default' => 'அனைத்துப் பயனரையும் உள்ளிடு',
-'protect-fallback' => '"$1" à®\85னà¯\81மதி à®¤à¯\87வà¯\88',
-'protect-level-autoconfirmed' => 'பà¯\81திய, à®ªà®¤à®¿à®µà¯\81 à®\9aà¯\86யà¯\8dயாத à®ªà®¯à®©à®°à¯\8dà®\95ளà¯\88தà¯\8d à®¤à®\9fà¯\88 à®\9aà¯\86யà¯\8d',
+'protect-fallback' => '"$1" à®\85னà¯\81மதி à®ªà¯\86à®±à¯\8dà®± à®ªà®¯à®©à®°à¯\8dà®\95ளà¯\81à®\95à¯\8dà®\95à¯\81 à®®à®\9fà¯\8dà®\9fà¯\81à®®à¯\8d à®\87à®\9aà¯\88வளி',
+'protect-level-autoconfirmed' => 'தானாà®\95 à®\89à®±à¯\81தியளிà®\95à¯\8dà®\95பà¯\8dபà®\9fà¯\8dà®\9f à®ªà®¯à®©à®°à¯\8dà®\95ளà¯\88 à®®à®\9fà¯\8dà®\9fà¯\81à®®à¯\8d à®\85னà¯\81மதி',
 'protect-level-sysop' => 'நிருவாகிகளை மட்டும் அனுமதிக்கவும்',
 'protect-summary-cascade' => 'படிநிலை',
 'protect-expiring' => '$1 (UTC) மணிக்கு காலாவதியாகிறது',
@@ -2244,7 +2253,8 @@ $NEWPAGE
 'undeletedrevisions' => '{{PLURAL:$1|1 திருத்தம் மீட்கப்பட்டது|$1 திருத்தங்கள் மீட்கப்பட்டன}}',
 'undeletedrevisions-files' => '{{PLURAL:$1|1 திருத்தம்|$1 திருத்தங்கள்}} மற்றும் {{PLURAL:$2|1 கோப்பு|$2 கோப்புகள்}} மீட்கப்பட்டன.',
 'undeletedfiles' => '{{PLURAL:$1|ஒரு கோப்பு மீட்டெடுக்கப்பட்டது|$1 கோப்புகள் மீட்டெடுக்கப்பட்டன}}',
-'cannotundelete' => 'மீள்வித்தல் தோல்வி: $1',
+'cannotundelete' => 'மீள்வித்தல் தோல்வி:
+$1',
 'undeletedpage' => "'''$1 மீட்கப்பட்டது'''
 
 அண்மைய நீக்கல்களுக்கும் மீட்புக்களுக்கும் [[Special:Log/delete|நீக்கல் பதிவைப்]] பார்க்கவும்.",
@@ -2274,7 +2284,7 @@ $1',
 'blanknamespace' => '(முதன்மை)',
 
 # Contributions
-'contributions' => 'பயனர் பங்களிப்புக்கள்',
+'contributions' => '{{GENDER:$1|பயனர்}} பங்களிப்புக்கள்',
 'contributions-title' => '$1 இற்கான பயனர் பங்களிப்புகள்',
 'mycontris' => 'பங்களிப்புக்கள்',
 'contribsub2' => '$1 பயனரின் ($2)',
@@ -2358,7 +2368,7 @@ $1',
 'badipaddress' => 'செல்லுபடியற்ற ஐ.பி. முகவரி',
 'blockipsuccesssub' => 'தடுப்பு வெற்றி',
 'blockipsuccesstext' => '[[Special:Contributions/$1|$1]] தடுக்கப்பட்டுள்ளார்.<br />
-தà®\9fà¯\81பà¯\8dபà¯\88 à®®à¯\80ளாயà¯\8dவà¯\81 à®\9aà¯\86யà¯\8dய [[Special:BlockList|தà®\9fà¯\81à®\95à¯\8dà®\95பà¯\8dபà®\9fà¯\8dà®\9f à®\90.பி. à®®à¯\81à®\95வரிà®\95ளினà¯\8d à®ªà®\9fà¯\8dà®\9fியலà¯\88பà¯\8d]] à®ªà®¾à®°்.',
+தà®\9fà¯\81பà¯\8dபà¯\88 à®®à®±à¯\81à®\86யà¯\8dவà¯\81 à®\9aà¯\86யà¯\8dய [[Special:BlockList|தà®\9fà¯\81பà¯\8dபà¯\81 à®ªà®\9fà¯\8dà®\9fியலà¯\88பà¯\8d]] à®ªà®¾à®°à¯\8dà®\95வà¯\81à®®்.',
 'ipb-blockingself' => 'நீங்கள் உங்களையே தடுக்க முயல்கிறீர்கள்! உறுதியாக இதை செய்ய விரும்புகிறீர்களா?',
 'ipb-edit-dropdown' => 'தடை காரணங்கள் தொகு',
 'ipb-unblock-addr' => '$1 இன் தடையை நீக்கு',
@@ -2760,13 +2770,13 @@ $1',
 'pageinfo-length' => 'பக்க நீளம் (எண்ணுண்மிகளில்)',
 'pageinfo-article-id' => 'பக்க அடையாள இலக்கம்',
 'pageinfo-language' => 'பக்க உள்ளடக்க மொழி',
-'pageinfo-robot-policy' => 'தà¯\87à®\9fà®±à¯\8dபà¯\8aறி à®¨à®¿à®²à¯\88à®®à¯\88',
-'pageinfo-robot-index' => 'வà®\95à¯\88பà¯\8dபà®\9fà®\95à¯\8dà®\95à¯\82à®\9fியது',
-'pageinfo-robot-noindex' => 'வà®\95à¯\88பà¯\8dபà®\9fாததà¯\81.',
+'pageinfo-robot-policy' => 'தானியà®\99à¯\8dà®\95ி à®®à¯\82லமà¯\8d à®\85à®\9fà¯\8dà®\9fவணà¯\88பà¯\8dபà®\9fà¯\81தà¯\8dதலà¯\8d',
+'pageinfo-robot-index' => 'à®\85னà¯\81மதிà®\95à¯\8dà®\95பà¯\8dபà®\9fà¯\81à®\95ிறது',
+'pageinfo-robot-noindex' => 'à®\85னà¯\81மதிà®\95à¯\8dà®\95பà¯\8dபà®\9fாததà¯\81',
 'pageinfo-views' => 'காட்சிகள் எண்ணிக்கை',
 'pageinfo-watchers' => 'பக்கப் பார்வையாளர்கள் எண்ணிக்கை',
 'pageinfo-few-watchers' => 'விட குறைவானது $1 {{PLURAL:$1|watcher|watchers}}',
-'pageinfo-redirects-name' => 'à®\87நà¯\8dதபà¯\8d à®ªà®\95à¯\8dà®\95தà¯\8dதிறà¯\8dà®\95ான à®µà®´à®¿à®®à®¾à®±à¯\8dà®±à¯\81à®\95ளà¯\8d',
+'pageinfo-redirects-name' => 'à®\87நà¯\8dதபà¯\8d à®ªà®\95à¯\8dà®\95தà¯\8dதிறà¯\8dà®\95ான à®µà®´à®¿à®®à®¾à®±à¯\8dà®±à¯\81à®\95ளினà¯\8d à®\8eணà¯\8dணிà®\95à¯\8dà®\95à¯\88',
 'pageinfo-subpages-name' => 'இந்தப் பக்கத்தின் துணைப் பக்கங்கள்',
 'pageinfo-subpages-value' => '$1 ($2 {{PLURAL:$2|வழிமாற்று|வழிமாற்றுகள்}}; $3 {{PLURAL:$3|வழிமாற்றில்லாதது|வழிமாற்றில்லாதவை}})',
 'pageinfo-firstuser' => 'பக்க உருவாக்குநர்',
@@ -3085,7 +3095,7 @@ $1',
 'exif-compression-1' => 'சுருக்கப்படாத',
 
 'exif-copyrighted-true' => 'பதிப்புரிமைப்பட்டது',
-'exif-copyrighted-false' => 'பà¯\8aதà¯\81 à®\95ளமà¯\8d',
+'exif-copyrighted-false' => 'பதிபà¯\8dபà¯\81ரிமà¯\88 à®¨à®¿à®²à¯\88யà¯\88 à®¤à®¿à®°à®¿à®µà¯\81à®\9aà¯\86யà¯\8dயபà¯\8dபà®\9fவிலà¯\8dலà¯\88',
 
 'exif-unknowndate' => 'நாள் தெரியாது',
 
@@ -3500,7 +3510,7 @@ $5
  * <span class="mw-specialpagerestricted">வரையறுத்த சிறப்புப் பக்கங்கள்.</span>',
 'specialpages-group-maintenance' => 'பராமரிப்பு அறிக்கைகள்',
 'specialpages-group-other' => 'ஏனைய சிறப்புப் பக்கங்கள்',
-'specialpages-group-login' => 'புகுபதிகை / கணக்கு தொடக்கம்',
+'specialpages-group-login' => 'புகுபதிகை/பயனர் கணக்கு தொடக்கம்',
 'specialpages-group-changes' => 'அண்மைய மாற்றங்களும் பதிகைகளும்',
 'specialpages-group-media' => 'ஊடக அறிக்கைகளும் பதிவேற்றங்களும்',
 'specialpages-group-users' => 'பயனர்களும் உரிமைகளும்',
@@ -3581,7 +3591,7 @@ $5
 'sqlite-no-fts' => '$1 முழு-உரை தேடல் ஆதரவு இல்லாமல்',
 
 # New logging system
-'logentry-delete-delete' => '$3 பக்கத்தை $1 நீக்கினார்',
+'logentry-delete-delete' => '$3 பக்கத்தை $1 {{GENDER:$2|நீக்கினார்}}',
 'logentry-delete-restore' => '$1 பயனரால் $3 பக்கம் மீட்டமைக்கப்பட்டது',
 'logentry-delete-event' => '$1 மாற்றிய காட்சித்தன்மை  {{PLURAL:$5| ஒரு நிகழ்வு குறிப்பேடு| $5  நிகழ்வுகள் குறிப்பேடு}} இதில்   $3 :$4',
 'logentry-delete-revision' => '$1 மாற்றப்பட்ட  காட்சித்தன்மைக்கு  {{PLURAL:$5| ஒருபரிசீலனை| $5  பரிசீலனைகளுக்கும்}} இந்த பக்கம்  $3 :$4',
index becffb7..028870d 100644 (file)
@@ -285,8 +285,6 @@ $messages = array(
 'broken-file-category' => 'Mga pahina na may sirang mga kawing ng talaksan',
 'categoryviewer-pagedlinks' => '($1) ($2)',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'Patungkol',
 'article' => 'Pahina ng nilalaman',
 'newwindow' => '(magbubukas sa bagong bintana)',
index f25aebf..f6b9404 100644 (file)
@@ -348,8 +348,6 @@ $messages = array(
 'noindex-category' => 'Индексланмаган битләр',
 'broken-file-category' => 'Файлларга эшләми торган сылтамалар булган битләр',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'Тасвирлама',
 'article' => 'Мәкалә',
 'newwindow' => '(яңа тәрәзәдә ачыла)',
index 6d17c36..8f96b5b 100644 (file)
@@ -216,8 +216,6 @@ $messages = array(
 'index-category' => 'İndekslanğan bitlär',
 'noindex-category' => 'İndekslanmağan bitlär',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'Taswirlama',
 'article' => 'Mäqälä',
 'newwindow' => '(yaña täräzädä açıla)',
index ac03870..9900e24 100644 (file)
@@ -44,6 +44,7 @@ $namespaceAliases = array(
 $namespaceGenderAliases = array();
 
 $linkTrail = '/^([a-zа-яёӝӟӥӧӵ“»]+)(.*)$/sDu';
+$linkPrefixCharset = '„«';
 $fallback8bitEncoding = 'windows-1251';
 $separatorTransformTable = array( ',' => "\xc2\xa0", '.' => ',' );
 
@@ -151,8 +152,6 @@ $messages = array(
 'index-category' => 'Индексировать кароно бамъёс',
 'noindex-category' => 'Индексировать каронтэм бамъёс',
 
-'linkprefix' => '/^((?>.*(?<!(?:„|«)$)))(.+)$/sDu',
-
 'about' => 'Та сярысь',
 'article' => 'Статья',
 'mypage' => 'Ас бам',
index ab5ee42..aad3464 100644 (file)
@@ -173,8 +173,6 @@ $messages = array(
 'broken-file-category' => 'ھۆججەت ئۇلىنىشى بۇزۇلغان بەتلەر',
 'categoryviewer-pagedlinks' => '($1) ($2)',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'ھەققىدە',
 'article' => 'مۇندەرىجە',
 'newwindow' => '(يېڭى كۆزنەكتە ئاچ)',
index 105bef1..a135875 100644 (file)
@@ -361,6 +361,7 @@ $magicWords = array(
 );
 
 $linkTrail = '/^([a-zабвгґдеєжзиіїйклмнопрстуфхцчшщьєюяёъы“»]+)(.*)$/sDu';
+$linkPrefixCharset = '„«';
 
 $messages = array(
 # User preference toggles
@@ -504,8 +505,6 @@ $messages = array(
 'broken-file-category' => 'Сторінки, що посилаються на неіснуючі файли',
 'categoryviewer-pagedlinks' => '($1) ($2)',
 
-'linkprefix' => '/^(.*?)(„|«)$/sD',
-
 'about' => 'Про',
 'article' => 'Стаття',
 'newwindow' => '(відкривається в новому вікні)',
@@ -894,8 +893,8 @@ $1',
 'passwordsent' => 'Новий пароль був надісланий на адресу електронної пошти, зазначену для "$1".
 Будь ласка, ввійдіть до системи після отримання пароля.',
 'blocked-mailpassword' => 'Редагування з вашої IP-адреси заборонено, заблокована також функція відновлення пароля.',
-'eauthentsent' => 'Ð\9dа Ð·Ð°Ð·Ð½Ð°Ñ\87енÑ\83 Ð°Ð´Ñ\80еÑ\81Ñ\83 ÐµÐ»ÐµÐºÑ\82Ñ\80онноÑ\97 Ð¿Ð¾Ñ\88Ñ\82и Ð½Ð°Ð´Ñ\96Ñ\81ланий Ð»Ð¸Ñ\81Ñ\82 Ñ\96з Ð·Ð°Ð¿Ð¸Ñ\82ом Ð½Ð° Ð¿Ñ\96дÑ\82веÑ\80дженнÑ\8f Ð·Ð¼Ñ\96ни Ð°Ð´Ñ\80еÑ\81и.
£ Ð»Ð¸Ñ\81Ñ\82Ñ\96 Ñ\82акож Ð¾Ð¿Ð¸Ñ\81анÑ\96 Ð´Ñ\96Ñ\97, Ñ\8fкÑ\96 Ð¿Ð¾Ñ\82Ñ\80Ñ\96бно Ð²Ð¸ÐºÐ¾Ð½Ð°Ñ\82и Ð´Ð»Ñ\8f Ð¿Ñ\96дÑ\82веÑ\80дженнÑ\8f Ñ\82ого, Ñ\89о Ñ\86Ñ\8f Ð°Ð´Ñ\80еÑ\81а ÐµÐ»ÐµÐºÑ\82Ñ\80онноÑ\97 Ð¿Ð¾Ñ\88Ñ\82и Ñ\81пÑ\80авдÑ\96 належить вам.',
+'eauthentsent' => 'Ð\9dа Ð²ÐºÐ°Ð·Ð°Ð½Ñ\83 Ð°Ð´Ñ\80еÑ\81Ñ\83 ÐµÐ»ÐµÐºÑ\82Ñ\80онноÑ\97 Ð¿Ð¾Ñ\88Ñ\82и Ð²Ñ\96дпÑ\80авлено Ð»Ð¸Ñ\81Ñ\82.
©Ð¾Ð± Ð¾Ñ\82Ñ\80имÑ\83ваÑ\82и Ð»Ð¸Ñ\81Ñ\82и Ð½Ð°Ð´Ð°Ð»Ñ\96, Ð´Ð¾Ñ\82Ñ\80имÑ\83йÑ\82еÑ\81Ñ\8c Ð²Ð¸ÐºÐ»Ð°Ð´ÐµÐ½Ð¸Ñ\85 Ñ\82ам Ñ\96нÑ\81Ñ\82Ñ\80Ñ\83кÑ\86Ñ\96й Ð´Ð»Ñ\8f Ð¿Ñ\96дÑ\82веÑ\80дженнÑ\8f Ñ\82ого, Ñ\89о Ñ\86Ñ\8f Ð°Ð´Ñ\80еÑ\81а належить вам.',
 'throttled-mailpassword' => 'Інструкція по відновленню паролю вже була вислана електронною поштою протягом {{PLURAL:$1|останньої години|останніх $1 годин}}.
 Для попередження зловживань дозволено надсилати тільки одну інструкцію за {{PLURAL:$1|годину|$1 години|$1 годин}}.',
 'mailerror' => 'Помилка при відправці пошти: $1',
@@ -4220,7 +4219,7 @@ MediaWiki поширюється в надії, що вона буде кори
 # Special:Redirect
 'redirect' => 'Перенаправлення за файлом, користувачем або ID версії',
 'redirect-legend' => 'Перенаправити на файл чи сторінку',
-'redirect-summary' => 'Ця спеціальна сторінка перенаправляє на файл (за поданою назвою файлу), сторінку (за поданим ID версії) або сторінку користувача (за поданим числовим ID користувача).',
+'redirect-summary' => 'Ця спеціальна сторінка перенаправляє на файл (за поданою назвою файлу), сторінку (за поданим ID версії) або сторінку користувача (за поданим числовим ID користувача). Використання: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]] або [[{{#Special:Redirect}}/user/101]].',
 'redirect-submit' => 'Перейти',
 'redirect-lookup' => 'Шукати:',
 'redirect-value' => 'Значення:',
index e8746b3..b154b14 100644 (file)
@@ -107,6 +107,7 @@ $magicWords = array(
 );
 
 $linkTrail = '/^([a-zʻʼ“»]+)(.*)$/sDu';
+$linkPrefixCharset = 'a-zA-Z\\x80-\\xffʻʼ«„';
 
 $messages = array(
 # User preference toggles
@@ -236,8 +237,6 @@ $messages = array(
 'noindex-category' => 'Indekslanmaydigan sahifalar',
 'broken-file-category' => 'Ishlamaydigan fayl havolalari bor sahifalar',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xffʻʼ«„]+)$/sDu',
-
 'about' => 'Haqida',
 'article' => 'Sahifa',
 'newwindow' => '(yangi oynada ochiladi)',
index f244cd6..a2868eb 100644 (file)
@@ -279,7 +279,7 @@ $messages = array(
 'vector-view-history' => 'Logön jenotemi',
 'vector-view-view' => 'Reidön',
 'vector-view-viewsource' => 'Logön fonäti',
-'actions' => 'Duns',
+'actions' => 'Dunots',
 'namespaces' => 'Nemaspads',
 
 'errorpagetitle' => 'Pöl',
index bd99d85..14db086 100644 (file)
@@ -203,8 +203,6 @@ $messages = array(
 'noindex-category' => 'Mga diri nakatudlokan nga pagkli',
 'broken-file-category' => 'Mga pakli nga mayda utod nga mga sumpay hin paypay',
 
-'linkprefix' => '/^(.*?)([a-zA-Z\\x80-\\xff]+)$/sD',
-
 'about' => 'Mahitungod han',
 'article' => 'Pakli hin sulod',
 'newwindow' => '(nabuklad hin bag-o nga tamboan o bintana)',
index 22c9ed3..1910ca5 100644 (file)
@@ -726,7 +726,7 @@ $2',
 'passwordsent' => 'א ניי פאסווארט איז געשיקט געווארן צום ע-פאסט אדרעס רעגיסטרירט פאר "$1".
 ביטע ווידער אריינלאגירן נאך דעם וואס איר באקומט עס.',
 'blocked-mailpassword' => 'אייער איי פי אדרעס איז בלאקירט צו רעדאקטירן, דערוועגן זענט איר נישט ערלויבט צו באניצן מיטן פאסווארט ווידעראויפלעבונג פֿונקציע כדי צו פארמיידן סיסטעם קרומבאניץ.',
-'eauthentsent' => '×\90 ×\91×\90ש×\98×¢×\98×\99×\92×\95× ×\92 ×¢-×\91ר×\99×\95×\95 ×\90×\99×\96 ×\92עש×\99ק×\98 ×\92×¢×\95×\95×\90ר×\9f ×¦×\95 ×\93×¢×\9d ×\91×\90ש×\98×\99×\9e×\98×\9f ×¢-פ×\90ס×\98 ×\90×\93רעס. ×\90×\99×\99×\93ער ×¡×\99×\99 ×\95×\95×\90ס אנדערע ע-פאסט וועט ווערן געשיקט צו דער קאנטע, וועט איר דארפן פאלגן די אנווייזונגען אין דער מעלדונג כדי צו זיין זיכער אז די קאנטע איז טאקע אייערס.',
+'eauthentsent' => '×\90 ×\91×\90ש×\98×¢×\98×\99×\92×\95× ×\92 ×¢-×\91ר×\99×\95×\95 ×\90×\99×\96 ×\92עש×\99ק×\98 ×\92×¢×\95×\95×\90ר×\9f ×¦×\95 ×\93×¢×\9d ×\91×\90ש×\98×\99×\9e×\98×\9f ×¢-פ×\90ס×\98 ×\90×\93רעס. ×\90×\99×\99×\93ער ×¡×\99×\99 ×\95×\95×¢×\9c×\9b×¢ אנדערע ע-פאסט וועט ווערן געשיקט צו דער קאנטע, וועט איר דארפן פאלגן די אנווייזונגען אין דער מעלדונג כדי צו זיין זיכער אז די קאנטע איז טאקע אייערס.',
 'throttled-mailpassword' => "מ'האט שוין געשיקט א בליצבריוו צוריקצושטעלן דאס פאסווארט, אין {{PLURAL:$1|דער לעצטער שעה|די לעצטע $1 שעה'ן}}. כדי צו פארמײַדן שלעכט באניצן, נאר איין פאסווארט צוריקשטעלן בליצבריוו וועט געשיקט ווערן אין {{PLURAL:$1|א שעה |$1 שעה'ן}}.",
 'mailerror' => 'פֿעלער שיקנדיג פאסט: $1',
 'acct_creation_throttle_hit' => 'באַזוכער צו דער וויקי וואס באַניצן אייער IP אַדרעס האָבן שױן באַשאַפֿן {{PLURAL:$1|1 קאנטע|$1 קאנטעס}} במשך דעם לעצטן טאָג, דעם מאַקסימום וואָס מען ערלויבט אין דעם פעריאד.
index 6a37a48..cf98e7a 100644 (file)
@@ -26,6 +26,7 @@
  * @author Fengchao
  * @author Franklsf95
  * @author Gaoxuewei
+ * @author GeneralNFS
  * @author Gzdavidwong
  * @author Happy
  * @author Hercule
@@ -397,7 +398,7 @@ $messages = array(
 'tog-watchlisthideliu' => '隐藏监视列表中的登录用户的编辑',
 'tog-watchlisthideanons' => '隐藏监视列表中的匿名用户的编辑',
 'tog-watchlisthidepatrolled' => '隐藏监视列表中的已巡查编辑',
-'tog-ccmeonemails' => '给我发送我发送给其他用户的电子邮件的副本',
+'tog-ccmeonemails' => '把我给其他用户发送的电子邮件的副本发送给我',
 'tog-diffonly' => '不在差异下面显示页面内容',
 'tog-showhiddencats' => '显示隐藏分类',
 'tog-noconvertlink' => '停用链接文字转换',
@@ -929,7 +930,7 @@ $2',
 'passwordreset-capture-help' => '如果您选中此框,电子邮件(包括临时密码)将显示,并发送给用户。',
 'passwordreset-email' => '电子邮件地址:',
 'passwordreset-emailtitle' => '在 {{SITENAME}} 的帐户详细信息',
-'passwordreset-emailtext-ip' => '有人通过IP地址$1(可能是您请求重设{{SITENAME}}($4)上相关账户的密码。{{PLURAL:$3|以下账户|此账户}}与该电子邮件地址关联:
+'passwordreset-emailtext-ip' => '有人(可能是您,来自IP地址$1)请求重设{{SITENAME}}($4)上相关账户的密码。{{PLURAL:$3|以下账户|此账户}}与该电子邮件地址关联:
 
 $2
 
@@ -3061,7 +3062,7 @@ $2',
 'tooltip-undo' => '“撤销”可以恢复该编辑并在预览模式下打开编辑表单。它允许在摘要中加入原因。',
 'tooltip-preferences-save' => '保存系统设置',
 'tooltip-summary' => '请输入简短的摘要',
-'tooltip-iwiki' => '$1 – 2',
+'tooltip-iwiki' => '$1 – $2',
 
 # Stylesheets
 'common.css' => '/* 此处的 CSS 将应用于所有的皮肤 */',
@@ -3906,7 +3907,7 @@ MediaWiki发表时预期有用,但对此'''无任何保证''',亦无隐含
 # Special:Redirect
 'redirect' => '重定向(按文件、用户或版本ID)',
 'redirect-legend' => '重定向至文件或页面',
-'redirect-summary' => '本特殊页面可以跳转至一个文件(提供文件名)、页面(提供版本ID)或用户页面(提供数字用户ID)。',
+'redirect-summary' => '本特殊页面可以跳转至一个文件(提供文件名)、页面(提供版本ID)或用户页面(提供数字用户ID)。用法:[[{{#Special:Redirect}}/file/Example.jpg]]、[[{{#Special:Redirect}}/revision/328429]]或[[{{#Special:Redirect}}/user/101]]。',
 'redirect-submit' => '提交',
 'redirect-lookup' => '基于:',
 'redirect-value' => '值:',
index 28ab8f6..4da841d 100644 (file)
@@ -483,7 +483,7 @@ $messages = array(
 'articlepage' => '檢視內容頁面',
 'talk' => '討論',
 'views' => '檢視',
-'toolbox' => '工具',
+'toolbox' => '工具',
 'userpage' => '檢視用戶頁面',
 'projectpage' => '檢視計劃頁面',
 'imagepage' => '檢視檔案頁面',
@@ -3039,6 +3039,7 @@ $2',
 'tooltip-undo' => '「復原」可以在編輯模式上開啟編輯表格以便恢復。它容許在摘要中加入原因。',
 'tooltip-preferences-save' => '儲存使用偏好',
 'tooltip-summary' => '輸入一個簡短的摘要',
+'tooltip-iwiki' => '$1 – $2',
 
 # Stylesheets
 'common.css' => '/* 此處的 CSS 將應用於所有的面板 */',
index c2ba555..ba7c3cf 100644 (file)
@@ -81,10 +81,9 @@ class UploadStashCleanup extends Maintenance {
                                try {
                                        $stash->getFile( $key, true );
                                        $stash->removeFileNoAuth( $key );
-                               } catch ( UploadStashBadPathException $ex ) {
-                                       $this->output( "Failed removing stashed upload with key: $key\n" );
-                               } catch ( UploadStashZeroLengthFileException $ex ) {
-                                       $this->output( "Failed removing stashed upload with key: $key\n" );
+                               } catch ( UploadStashException $ex ) {
+                                       $type = get_class( $ex );
+                                       $this->output( "Failed removing stashed upload with key: $key ($type)\n" );
                                }
                                if ( $i % 100 == 0 ) {
                                        $this->output( "$i\n" );
index f96902d..c595980 100644 (file)
@@ -9,6 +9,7 @@
                                        "mw.Map",
                                        "mw.Message",
                                        "mw.loader",
+                                       "mw.loader.store",
                                        "mw.log",
                                        "mw.html",
                                        "mw.html.Cdata",
index 8676d74..267666d 100644 (file)
@@ -266,7 +266,6 @@ $wgIgnoredMessages = array(
 
 /** Optional messages, which may be translated only if changed in the target language. */
 $wgOptionalMessages = array(
-       'linkprefix',
        'feed-atom',
        'feed-rss',
        'unit-pixel',
index a4fc922..1ac9500 100644 (file)
@@ -169,9 +169,6 @@ $wgMessageStructure = array(
                'broken-file-category',
                'categoryviewer-pagedlinks',
        ),
-       'mainpage' => array(
-               'linkprefix',
-       ),
        'miscellaneous1' => array(
                'about',
                'article',
index acfabc3..607d7b9 100644 (file)
@@ -2,7 +2,7 @@
 define mw_prefix='{$wgDBprefix}';
 
 
-CREATE SEQUENCE user_user_id_seq MINVALUE 0 START WITH 0;
+CREATE SEQUENCE user_user_id_seq;
 CREATE TABLE &mw_prefix.mwuser ( -- replace reserved word 'user'
   user_id                   NUMBER  NOT NULL,
   user_name                 VARCHAR2(255)     NOT NULL,
@@ -27,7 +27,7 @@ CREATE INDEX &mw_prefix.mwuser_i02 ON &mw_prefix.mwuser (user_email, user_name);
 
 -- Create a dummy user to satisfy fk contraints especially with revisions
 INSERT INTO &mw_prefix.mwuser
-  VALUES (user_user_id_seq.nextval,'Anonymous',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, '', current_timestamp, current_timestamp, 0);
+  VALUES (0,'Anonymous',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL, '', current_timestamp, current_timestamp, 0);
 
 CREATE TABLE &mw_prefix.user_groups (
   ug_user   NUMBER      DEFAULT 0 NOT NULL,
index ec6c71b..463dec8 100644 (file)
@@ -103,7 +103,7 @@ return array(
                        'common/commonElements.css' => array( 'media' => 'screen' ),
                        'common/commonContent.css' => array( 'media' => 'screen' ),
                        'common/commonInterface.css' => array( 'media' => 'screen' ),
-                       'vector/styles-beta.less' => array( 'media' => 'screen' ),
+                       'vector/styles-beta.less',
                ),
                'remoteBasePath' => $GLOBALS['wgStylePath'],
                'localBasePath' => $GLOBALS['wgStyleDirectory'],
@@ -1011,6 +1011,9 @@ return array(
                'scripts' => 'resources/mediawiki.special/mediawiki.special.preferences.js',
                'styles' => 'resources/mediawiki.special/mediawiki.special.preferences.css',
                'position' => 'top',
+               'skinStyles' => array(
+                       'vector' => 'skins/vector/special.preferences.less',
+               ),
        ),
        'mediawiki.special.recentchanges' => array(
                'scripts' => 'resources/mediawiki.special/mediawiki.special.recentchanges.js',
@@ -1116,12 +1119,6 @@ return array(
                'localBasePath' => $GLOBALS['wgStyleDirectory'],
                'dependencies' => 'mediawiki.legacy.wikibits',
        ),
-       'mediawiki.legacy.IEFixes' => array(
-               'scripts' => 'common/IEFixes.js',
-               'remoteBasePath' => $GLOBALS['wgStylePath'],
-               'localBasePath' => $GLOBALS['wgStyleDirectory'],
-               'dependencies' => 'mediawiki.legacy.wikibits',
-       ),
        'mediawiki.legacy.protect' => array(
                'scripts' => 'common/protect.js',
                'remoteBasePath' => $GLOBALS['wgStylePath'],
index 9ffe11c..768a9c6 100644 (file)
@@ -33,28 +33,9 @@ section.mw-form-header {
        font-size: 0.9em;
        margin: 0 0 1em 0;
        padding: 0.5em;
-       border: 1px solid;
        word-wrap: break-word;
 }
 
-.mw-ui-vform .errorbox {
-       color: #cc0000;
-       border-color: #fac5c5;
-       background-color: #fae3e3;
-}
-
-.mw-ui-vform .warningbox {
-       color: #705000;
-       border-color: #fde29b;
-       background-color: #fdf1d1;
-}
-
-.mw-ui-vform .successbox {
-       color: #009000;
-       border-color: #b7fdb5;
-       background-color: #e1fddf;
-}
-
 /*
  * Override the right margin of the form to give space in case a benefits
  * column appears to the side.
index 346e783..d93254b 100644 (file)
                } );
        }
 
+       function humanSize( bytes ) {
+               if ( !$.isNumeric( bytes ) || bytes === 0 ) { return bytes; }
+               var i = 0, units = [ '', ' kB', ' MB', ' GB', ' TB', ' PB' ];
+               for ( ; bytes >= 1024; bytes /= 1024 ) { i++; }
+               return bytes.toFixed( 1 ) + units[i];
+       }
+
        /**
         * @class mw.inspect
         * @singleton
         */
        var inspect = {
 
+               /**
+                * Return a map of all dependency relationships between loaded modules.
+                *
+                * @return {Object} Maps module names to objects. Each sub-object has
+                *  two properties, 'requires' and 'requiredBy'.
+                */
+               getDependencyGraph: function () {
+                       var modules = inspect.getLoadedModules(), graph = {};
+
+                       $.each( modules, function ( moduleIndex, moduleName ) {
+                               var dependencies = mw.loader.moduleRegistry[moduleName].dependencies || [];
+
+                               graph[moduleName] = graph[moduleName] || { requiredBy: [] };
+                               graph[moduleName].requires = dependencies;
+
+                               $.each( dependencies, function ( depIndex, depName ) {
+                                       graph[depName] = graph[depName] || { requiredBy: [] };
+                                       graph[depName].requiredBy.push( moduleName );
+                               } );
+                       } );
+                       return graph;
+               },
+
                /**
                 * Calculate the byte size of a ResourceLoader module.
                 *
                                // Use Function.prototype#call to force an exception on Firefox,
                                // which doesn't define console#table but doesn't complain if you
                                // try to invoke it.
-                               console.table.call( console.table, data );
+                               console.table.call( console, data );
                                return;
                        } catch (e) {}
                        try {
 
                                // Convert size to human-readable string.
                                $.each( modules, function ( i, module ) {
-                                       module.size = module.size > 1024 ?
-                                               ( module.size / 1024 ).toFixed( 2 ) + ' KB' :
-                                               ( module.size !== null ? module.size + ' B' : null );
+                                       module.size = humanSize( module.size );
                                } );
 
                                return modules;
                                sortByProperty( modules, 'allSelectors', true );
                                return modules;
                        },
+
+                       /**
+                        * Report stats on mw.loader.store: the number of localStorage
+                        * cache hits and misses, the number of items purged from the
+                        * cache, and the total size of the module blob in localStorage.
+                        */
+                       store: function () {
+                               var raw, stats = { enabled: mw.loader.store.enabled };
+                               if ( stats.enabled ) {
+                                       $.extend( stats, mw.loader.store.stats );
+                                       try {
+                                               raw = localStorage.getItem( mw.loader.store.getStoreKey() );
+                                               stats.totalSize = humanSize( $.byteLength( raw ) );
+                                       } catch (e) {}
+                               }
+                               return [stats];
+                       }
                }
        };
 
index 03248ba..9267a49 100644 (file)
@@ -21,6 +21,7 @@ var mw = ( function ( $, undefined ) {
         * fine. No need for optimization here, which would only result in losing logs.
         *
         * @private
+        * @method log_
         * @param {string} msg text for the log entry.
         * @param {Error} [e]
         */
@@ -318,6 +319,9 @@ var mw = ( function ( $, undefined ) {
                }
        };
 
+       /**
+        * @class mw
+        */
        return {
                /* Public Members */
 
@@ -848,8 +852,9 @@ var mw = ( function ( $, undefined ) {
                                }
 
                                if ( registry[module].state === 'ready' ) {
-                                       // The current module became 'ready'. Recursively execute all dependent modules that are loaded
-                                       // and now have all dependencies satisfied.
+                                       // The current module became 'ready'. Set it in the module store, and recursively execute all
+                                       // dependent modules that are loaded and now have all dependencies satisfied.
+                                       mw.loader.store.set( module, registry[module] );
                                        for ( m in registry ) {
                                                if ( registry[m].state === 'loaded' && allReady( registry[m].dependencies ) ) {
                                                        execute( m );
@@ -1203,7 +1208,7 @@ var mw = ( function ( $, undefined ) {
                                addScript( sourceLoadScript + '?' + $.param( request ) + '&*', null, async );
                        }
 
-                       /* Public Methods */
+                       /* Public Members */
                        return {
                                /**
                                 * The module registry is exposed as an aid for debugging and inspecting page
@@ -1226,7 +1231,7 @@ var mw = ( function ( $, undefined ) {
                                 */
                                work: function () {
                                        var     reqBase, splits, maxQueryLength, q, b, bSource, bGroup, bSourceGroup,
-                                               source, group, g, i, modules, maxVersion, sourceLoadScript,
+                                               source, concatSource, group, g, i, modules, maxVersion, sourceLoadScript,
                                                currReqBase, currReqBaseLength, moduleMap, l,
                                                lastDotIndex, prefix, suffix, bytesAdded, async;
 
@@ -1252,6 +1257,21 @@ var mw = ( function ( $, undefined ) {
                                                        }
                                                }
                                        }
+
+                                       mw.loader.store.init();
+                                       if ( mw.loader.store.enabled ) {
+                                               concatSource = [];
+                                               batch = $.grep( batch, function ( module ) {
+                                                       var source = mw.loader.store.get( module );
+                                                       if ( source ) {
+                                                               concatSource.push( source );
+                                                               return false;
+                                                       }
+                                                       return true;
+                                               } );
+                                               $.globalEval( concatSource.join( ';' ) );
+                                       }
+
                                        // Early exit if there's nothing to load...
                                        if ( !batch.length ) {
                                                return;
@@ -1446,16 +1466,19 @@ var mw = ( function ( $, undefined ) {
                                 * @param {Function|Array} script Function with module code or Array of URLs to
                                 *  be used as the src attribute of a new `<script>` tag.
                                 * @param {Object} style Should follow one of the following patterns:
+                                *
                                 *     { "css": [css, ..] }
                                 *     { "url": { <media>: [url, ..] } }
+                                *
                                 * And for backwards compatibility (needs to be supported forever due to caching):
+                                *
                                 *     { <media>: css }
                                 *     { <media>: [url, ..] }
                                 *
                                 * The reason css strings are not concatenated anymore is bug 31676. We now check
                                 * whether it's safe to extend the stylesheet (see #canExpandStylesheetWith).
                                 *
-                                * @param {Object} msgs List of key/value pairs to be added to {@link mw#messages}.
+                                * @param {Object} msgs List of key/value pairs to be added to mw#messages.
                                 */
                                implement: function ( module, script, style, msgs ) {
                                        // Validate input
@@ -1703,8 +1726,241 @@ var mw = ( function ( $, undefined ) {
                                        mw.loader.using( 'mediawiki.inspect', function () {
                                                mw.inspect.runReports.apply( mw.inspect, args );
                                        } );
-                               }
+                               },
+
+                               /**
+                                * On browsers that implement the localStorage API, the module store serves as a
+                                * smart complement to the browser cache. Unlike the browser cache, the module store
+                                * can slice a concatenated response from ResourceLoader into its constituent
+                                * modules and cache each of them separately, using each module's versioning scheme
+                                * to determine when the cache should be invalidated.
+                                *
+                                * @singleton
+                                * @class mw.loader.store
+                                */
+                               store: {
+                                       // Whether the store is in use on this page.
+                                       enabled: null,
+
+                                       // The contents of the store, mapping '[module name]@[version]' keys
+                                       // to module implementations.
+                                       items: {},
+
+                                       // Cache hit stats
+                                       stats: { hits: 0, misses: 0, expired: 0 },
+
+                                       /**
+                                        * Construct a JSON-serializable object representing the content of the store.
+                                        * @return {Object} Module store contents.
+                                        */
+                                       toJSON: function () {
+                                               return { items: mw.loader.store.items, vary: mw.loader.store.getVary() };
+                                       },
+
+                                       /**
+                                        * Get the localStorage key for the entire module store. The key references
+                                        * $wgDBname to prevent clashes between wikis which share a common host.
+                                        *
+                                        * @return {string} localStorage item key
+                                        */
+                                       getStoreKey: function () {
+                                               return 'MediaWikiModuleStore:' + mw.config.get( 'wgDBname' );
+                                       },
+
+                                       /**
+                                        * Get a string key on which to vary the module cache.
+                                        * @return {string} String of concatenated vary conditions.
+                                        */
+                                       getVary: function () {
+                                               return [
+                                                       mw.config.get( 'skin' ),
+                                                       mw.config.get( 'wgResourceLoaderStorageVersion' ),
+                                                       mw.config.get( 'wgUserLanguage' )
+                                               ].join(':');
+                                       },
+
+                                       /**
+                                        * Get a string key for a specific module. The key format is '[name]@[version]'.
+                                        *
+                                        * @param {string} module Module name
+                                        * @return {string|null} Module key or null if module does not exist
+                                        */
+                                       getModuleKey: function ( module ) {
+                                               return typeof registry[module] === 'object' ?
+                                                       ( module + '@' + registry[module].version ) : null;
+                                       },
+
+                                       /**
+                                        * Initialize the store by retrieving it from localStorage and (if successfully
+                                        * retrieved) decoding the stored JSON value to a plain object.
+                                        *
+                                        * The try / catch block is used for JSON & localStorage feature detection.
+                                        * See the in-line documentation for Modernizr's localStorage feature detection
+                                        * code for a full account of why we need a try / catch: <http://git.io/4NEwKg>.
+                                        */
+                                       init: function () {
+                                               var raw, data, optedIn;
 
+                                               if ( mw.loader.store.enabled !== null ) {
+                                                       // #init already ran.
+                                                       return;
+                                               }
+
+                                               // Temporarily allow users to opt-in during mw.loader.store test phase by
+                                               // manually setting a cookie (bug 56397).
+                                               optedIn = /ResourceLoaderStorageEnabled=1/.test( document.cookie );
+
+                                               if ( !( mw.config.get( 'wgResourceLoaderStorageEnabled' ) || optedIn ) || mw.config.get( 'debug' ) ) {
+                                                       // Disabled by configuration, or because debug mode is set.
+                                                       mw.loader.store.enabled = false;
+                                                       return;
+                                               }
+
+                                               try {
+                                                       raw = localStorage.getItem( mw.loader.store.getStoreKey() );
+                                                       // If we get here, localStorage is available; mark enabled.
+                                                       mw.loader.store.enabled = true;
+                                                       data = JSON.parse( raw );
+                                                       if ( data && typeof data.items === 'object' && data.vary === mw.loader.store.getVary() ) {
+                                                               mw.loader.store.items = data.items;
+                                                               return;
+                                                       }
+                                               } catch (e) {}
+
+                                               if ( raw === undefined ) {
+                                                       mw.loader.store.enabled = false;  // localStorage failed; disable store.
+                                               } else {
+                                                       mw.loader.store.update();
+                                               }
+                                       },
+
+                                       /**
+                                        * Retrieve a module from the store and update cache hit stats.
+                                        *
+                                        * @param {string} module Module name
+                                        * @return {string|boolean} Module implementation or false if unavailable
+                                        */
+                                       get: function ( module ) {
+                                               var key;
+
+                                               if ( mw.loader.store.enabled !== true ) {
+                                                       return false;
+                                               }
+
+                                               key = mw.loader.store.getModuleKey( module );
+                                               if ( key in mw.loader.store.items ) {
+                                                       mw.loader.store.stats.hits++;
+                                                       return mw.loader.store.items[key];
+                                               }
+                                               mw.loader.store.stats.misses++;
+                                               return false;
+                                       },
+
+                                       /**
+                                        * Stringify a module and queue it for storage.
+                                        *
+                                        * @param {string} module Module name
+                                        * @param {Object} descriptor The module's descriptor as set in the registry
+                                        */
+                                       set: function ( module, descriptor ) {
+                                               var args, key;
+
+                                               if ( mw.loader.store.enabled !== true ) {
+                                                       return false;
+                                               }
+
+                                               key = mw.loader.store.getModuleKey( module );
+
+                                               if ( key in mw.loader.store.items ) {
+                                                       // Already set; decline to store.
+                                                       return false;
+                                               }
+
+                                               if ( descriptor.state !== 'ready' ) {
+                                                       // Module failed to load; decline to store.
+                                                       return false;
+                                               }
+
+                                               if ( !descriptor.version || $.inArray( descriptor.group, [ 'private', 'user', 'site' ] ) !== -1 ) {
+                                                       // Unversioned, private, or site-/user-specific; decline to store.
+                                                       return false;
+                                               }
+
+                                               if ( $.inArray( undefined, [ descriptor.script, descriptor.style, descriptor.messages ] ) !== -1 ) {
+                                                       // Partial descriptor; decline to store.
+                                                       return false;
+                                               }
+
+                                               try {
+                                                       args = [
+                                                               JSON.stringify( module ),
+                                                               typeof descriptor.script === 'function' ?
+                                                                       String( descriptor.script ) : JSON.stringify( descriptor.script ),
+                                                               JSON.stringify( descriptor.style ),
+                                                               JSON.stringify( descriptor.messages )
+                                                       ];
+                                               } catch (e) {
+                                                       return;
+                                               }
+                                               mw.loader.store.items[key] = 'mw.loader.implement(' + args.join(',') + ');';
+                                               mw.loader.store.update();
+                                       },
+
+                                       /**
+                                        * Iterate through the module store, removing any item that does not correspond
+                                        * (in name and version) to an item in the module registry.
+                                        */
+                                       prune: function () {
+                                               var key, module;
+
+                                               if ( mw.loader.store.enabled !== true ) {
+                                                       return false;
+                                               }
+
+                                               for ( key in mw.loader.store.items ) {
+                                                       module = key.substring( 0, key.indexOf( '@' ) );
+                                                       if ( mw.loader.store.getModuleKey( module ) !== key ) {
+                                                               mw.loader.store.stats.expired++;
+                                                               delete mw.loader.store.items[key];
+                                                       }
+                                               }
+                                       },
+
+                                       /**
+                                        * Sync modules to localStorage.
+                                        *
+                                        * This function debounces localStorage updates. When called multiple times in
+                                        * quick succession, the calls are coalesced into a single update operation.
+                                        * This allows us to call #update without having to consider the module load
+                                        * queue; the call to localStorage.setItem will be naturally deferred until the
+                                        * page is quiescent.
+                                        *
+                                        * Because localStorage is shared by all pages with the same origin, if multiple
+                                        * pages are loaded with different module sets, the possibility exists that
+                                        * modules saved by one page will be clobbered by another. But the impact would
+                                        * be minor and the problem would be corrected by subsequent page views.
+                                        */
+                                       update: ( function () {
+                                               var timer;
+
+                                               function flush() {
+                                                       var data;
+                                                       if ( mw.loader.store.enabled !== true ) {
+                                                               return false;
+                                                       }
+                                                       mw.loader.store.prune();
+                                                       try {
+                                                               data = JSON.stringify( mw.loader.store );
+                                                               localStorage.setItem( mw.loader.store.getStoreKey(), data );
+                                                       } catch (e) {}
+                                               }
+
+                                               return function () {
+                                                       clearTimeout( timer );
+                                                       timer = setTimeout( flush, 2000 );
+                                               };
+                                       }() )
+                               }
                        };
                }() ),
 
index 09f0910..e35fcd1 100644 (file)
@@ -1,9 +1,12 @@
-// IE fixes javascript
+// IE fixes javascript loaded by wikibits.js for IE <= 6.0
 ( function ( mw, $ ) {
 
 var doneIEAlphaFix, doneIETransform, expandedURLs, fixalpha, isMSIE55,
-       relativeforfloats, setrelative;
+       relativeforfloats, setrelative, hasClass;
 
+// Also returns true for IE6, 7, 8, 9 and 10. createPopup is removed in IE11.
+// Good thing this is only loaded for IE <= 6 by wikibits.
+// Might as well set it to true.
 isMSIE55 = window.isMSIE55 = ( window.showModalDialog && window.clipboardData && window.createPopup );
 doneIETransform = window.doneIETransform = undefined;
 doneIEAlphaFix = window.doneIEAlphaFix = undefined;
@@ -99,8 +102,8 @@ setrelative = window.setrelative = function ( nodes ) {
 };
 
 // Expand links for printing
-String.prototype.hasClass = function ( classWanted ) {
-       var i = 0, classArr = this.split(/\s/);
+hasClass = function ( classText, classWanted ) {
+       var i = 0, classArr = classText.split(/\s/);
        for ( i = 0; i < classArr.length; i++ ) {
                if ( classArr[i].toLowerCase() === classWanted.toLowerCase() ) {
                        return true;
@@ -121,7 +124,7 @@ window.onbeforeprint = function () {
                allLinks = contentEl.getElementsByTagName( 'a' );
 
                for ( i = 0; i < allLinks.length; i++ ) {
-                       if ( allLinks[i].className.hasClass( 'external' ) && !allLinks[i].className.hasClass( 'free' ) ) {
+                       if ( hasClass( allLinks[i].className, 'external' ) && !hasClass( allLinks[i].className, 'free' ) ) {
                                expandedLink = document.createElement( 'span' );
                                expandedText = document.createTextNode( ' (' + allLinks[i].href + ')' );
                                expandedLink.appendChild( expandedText );
index 76ec4af..742f839 100644 (file)
@@ -157,15 +157,12 @@ dd {
        margin-bottom: .1em;
 }
 
+/* IE 6 and 7 lack support for quotes aroud the <q> element ('::before' and '::after'
+   pseudoelements, 'quotes' property). Let's italicize it instead (using the star hack). */
 q {
-       font-family: Times, "Times New Roman", serif;
-       font-style: italic;
-}
-/* Disabled for now
-blockquote {
-       font-family: Times, "Times New Roman", serif;
-       font-style: italic;
-}*/
+       *font-style: italic;
+}
+
 pre, code, tt, kbd, samp, .mw-code {
        /*
         * Some browsers will render the monospace text too small, namely Firefox, Chrome and Safari.
index 7cc58e3..ac7265a 100644 (file)
@@ -535,52 +535,57 @@ table.collapsed tr.collapsable {
 }
 
 /* success and error messages */
+.error,
+.warning,
 .success {
-       color: green;
        font-size: larger;
 }
+.error {
+       color: #cc0000;
+}
 .warning {
-       color: #FFA500; /* orange */
-       font-size: larger;
+       color: #705000;
 }
-.error {
-       color: red;
-       font-size: larger;
+.success {
+       color: #009000;
 }
+
 .errorbox,
 .warningbox,
 .successbox {
-       font-size: larger;
-       border: 2px solid;
+       border: 1px solid;
        padding: .5em 1em;
-       margin-bottom: 2em;
-       color: #000;
+       margin-bottom: 1em;
        display: -moz-inline-block;
        display: inline-block;
        zoom: 1;
        *display: inline;
 }
-.errorbox {
-       border-color: red;
-       background-color: #fff2f2;
-}
-.warningbox {
-       border-color: #FF8C00; /* darkorange */
-       background-color: #FFFFC0;
-}
-.successbox {
-       border-color: green;
-       background-color: #dfd;
-}
 .errorbox h2,
 .warningbox h2,
 .successbox h2 {
        font-size: 1em;
+       color: inherit;
        font-weight: bold;
        display: inline;
        margin: 0 .5em 0 0;
        border: none;
 }
+.errorbox {
+       color: #cc0000;
+       border-color: #fac5c5;
+       background-color: #fae3e3;
+}
+.warningbox {
+       color: #705000;
+       border-color: #fde29b;
+       background-color: #fdf1d1;
+}
+.successbox {
+       color: #009000;
+       border-color: #b7fdb5;
+       background-color: #e1fddf;
+}
 
 /* general info/warning box for SP */
 .mw-infobox {
index 7dc4a95..65db555 100644 (file)
 /**
  * MediaWiki legacy wikibits
  */
-/*jshint quotmark:false, onevar:false */
 ( function ( mw, $ ) {
-       var isIE6, isGecko,
+       var msg,
+               win = window,
                ua = navigator.userAgent.toLowerCase(),
-               uaMsg = 'Use feature detection or module jquery.client instead.';
-
-/**
- * User-agent sniffing.
- * To be removed in MediaWiki 1.23.
- *
- * @deprecated since 1.17 Use jquery.client instead.
- */
-mw.log.deprecate( window, 'clientPC', ua, uaMsg );
-$.each([
-               'is_gecko',
-               'is_chrome_mac',
-               'is_chrome',
-               'webkit_version',
-               'is_safari_win',
-               'is_safari',
-               'webkit_match',
-               'is_ff2',
-               'ff2_bugs',
-               'is_ff2_win',
-               'is_ff2_x11',
-               'opera95_bugs',
-               'opera7_bugs',
-               'opera6_bugs',
-               'is_opera_95',
-               'is_opera_preseven',
-               'is_opera',
-               'ie6_bugs'
-       ],
-       function ( i, key ) {
-               mw.log.deprecate( window, key, false, uaMsg );
-       }
-);
-if ( /msie ([0-9]{1,}[\.0-9]{0,})/.exec( ua ) && parseFloat( RegExp.$1 ) <= 6.0 ) {
-       isIE6 = true;
-}
-isGecko = /gecko/.test( ua ) && !/khtml|spoofer|netscape\/7\.0/.test( ua );
-
-// add any onload functions in this hook (please don't hard-code any events in the xhtml source)
-window.doneOnloadHook = undefined;
-
-if ( !window.onloadFuncts ) {
-       window.onloadFuncts = [];
-}
-
-window.addOnloadHook = function( hookFunct ) {
-       // Allows add-on scripts to add onload functions
-       if( !window.doneOnloadHook ) {
-               window.onloadFuncts[window.onloadFuncts.length] = hookFunct;
-       } else {
-               hookFunct(); // bug in MSIE script loading
-       }
-};
-
-window.importScript = function( page ) {
-       var uri = mw.config.get( 'wgScript' ) + '?title=' +
-               mw.util.wikiUrlencode( page ) +
-               '&action=raw&ctype=text/javascript';
-       return window.importScriptURI( uri );
-};
-
-window.loadedScripts = {}; // included-scripts tracker
-window.importScriptURI = function( url ) {
-       if ( window.loadedScripts[url] ) {
-               return null;
-       }
-       window.loadedScripts[url] = true;
-       var s = document.createElement( 'script' );
-       s.setAttribute( 'src', url );
-       s.setAttribute( 'type', 'text/javascript' );
-       document.getElementsByTagName('head')[0].appendChild( s );
-       return s;
-};
-
-window.importStylesheet = function( page ) {
-       return window.importStylesheetURI( mw.config.get( 'wgScript' ) + '?action=raw&ctype=text/css&title=' + mw.util.wikiUrlencode( page ) );
-};
-
-window.importStylesheetURI = function( url, media ) {
-       var l = document.createElement( 'link' );
-       l.rel = 'stylesheet';
-       l.href = url;
-       if ( media ) {
-               l.media = media;
-       }
-       document.getElementsByTagName('head')[0].appendChild( l );
-       return l;
-};
-
-window.appendCSS = function( text ) {
-       var s = document.createElement( 'style' );
-       s.type = 'text/css';
-       s.rel = 'stylesheet';
-       if ( s.styleSheet ) {
-               s.styleSheet.cssText = text; // IE
-       } else {
-               s.appendChild( document.createTextNode( text + '' ) ); // Safari sometimes borks on null
-       }
-       document.getElementsByTagName('head')[0].appendChild( s );
-       return s;
-};
+               isIE6 = ( /msie ([0-9]{1,}[\.0-9]{0,})/.exec( ua ) && parseFloat( RegExp.$1 ) <= 6.0 ),
+               isGecko = /gecko/.test( ua ) && !/khtml|spoofer|netscape\/7\.0/.test( ua ),
+               onloadFuncts = [];
 
 if ( mw.config.get( 'wgBreakFrames' ) ) {
        // Note: In IE < 9 strict comparison to window is non-standard (the standard didn't exist yet)
        // it works only comparing to window.self or window.window (http://stackoverflow.com/q/4850978/319266)
-       if ( window.top !== window.self ) {
+       if ( win.top !== win.self ) {
                // Un-trap us from framesets
-               window.top.location = window.location;
+               win.top.location = win.location;
        }
 }
 
-window.changeText = function( el, newText ) {
-       // Safari work around
-       if ( el.innerText ) {
-               el.innerText = newText;
-       } else if ( el.firstChild && el.firstChild.nodeValue ) {
-               el.firstChild.nodeValue = newText;
-       }
-};
-
-window.killEvt = function( evt ) {
-       evt = evt || window.event || window.Event; // W3C, IE, Netscape
-       if ( typeof evt.preventDefault !== 'undefined' ) {
-               evt.preventDefault(); // Don't follow the link
-               evt.stopPropagation();
-       } else {
-               evt.cancelBubble = true; // IE
-       }
-       return false; // Don't follow the link (IE)
-};
-
-window.mwEditButtons = [];
-mw.log.deprecate( window, 'mwCustomEditButtons', [], 'Use mw.toolbar.addButton instead.' );
-
-window.escapeQuotes = function( text ) {
-       var re = new RegExp( "'", "g" );
-       text = text.replace( re, "\\'" );
-       re = new RegExp( "\\n", "g" );
-       text = text.replace( re, "\\n" );
-       return window.escapeQuotesHTML( text );
-};
-
-window.escapeQuotesHTML = function( text ) {
-       var re = new RegExp( '&', "g" );
-       text = text.replace( re, "&amp;" );
-       re = new RegExp( '"', "g" );
-       text = text.replace( re, "&quot;" );
-       re = new RegExp( '<', "g" );
-       text = text.replace( re, "&lt;" );
-       re = new RegExp( '>', "g" );
-       text = text.replace( re, "&gt;" );
-       return text;
-};
-
-/**
- * Accesskey prefix utilities.
- * To be removed in MediaWiki 1.23.
- *
- * @deprecated since 1.17 Use mediawiki.util instead.
- */
-mw.log.deprecate( window, 'tooltipAccessKeyPrefix', 'alt-', 'Use mediawiki.util instead.' );
-mw.log.deprecate( window, 'tooltipAccessKeyRegexp', /\[(alt-)?(.)\]$/, 'Use mediawiki.util instead.' );
-mw.log.deprecate( window, 'updateTooltipAccessKeys', mw.util.updateTooltipAccessKeys, 'Use mediawiki.util instead.' );
-
-/**
- * Add a link to one of the portlet menus on the page, including:
- *
- * p-cactions: Content actions (shown as tabs above the main content in Monobook)
- * p-personal: Personal tools (shown at the top right of the page in Monobook)
- * p-navigation: Navigation
- * p-tb: Toolbox
- *
- * This function exists for the convenience of custom JS authors.  All
- * but the first three parameters are optional, though providing at
- * least an id and a tooltip is recommended.
- *
- * By default the new link will be added to the end of the list.  To
- * add the link before a given existing item, pass the DOM node of
- * that item (easily obtained with document.getElementById()) as the
- * nextnode parameter; to add the link _after_ an existing item, pass
- * the node's nextSibling instead.
- *
- * @param portlet String id of the target portlet ("p-cactions", "p-personal", "p-navigation" or "p-tb")
- * @param href String link URL
- * @param text String link text (will be automatically lowercased by CSS for p-cactions in Monobook)
- * @param id String id of the new item, should be unique and preferably have the appropriate prefix ("ca-", "pt-", "n-" or "t-")
- * @param tooltip String text to show when hovering over the link, without accesskey suffix
- * @param accesskey String accesskey to activate this link (one character, try to avoid conflicts)
- * @param nextnode Node the DOM node before which the new item should be added, should be another item in the same list
- *
- * @return Node -- the DOM node of the new item (an LI element) or null
- */
-window.addPortletLink = function( portlet, href, text, id, tooltip, accesskey, nextnode ) {
-       var root = document.getElementById( portlet );
-       if ( !root ) {
-               return null;
-       }
-       var uls = root.getElementsByTagName( 'ul' );
-       var node;
-       if ( uls.length > 0 ) {
-               node = uls[0];
-       } else {
-               node = document.createElement( 'ul' );
-               var lastElementChild = null;
-               for ( var i = 0; i < root.childNodes.length; ++i ) { /* get root.lastElementChild */
-                       if ( root.childNodes[i].nodeType === 1 ) {
-                               lastElementChild = root.childNodes[i];
-                       }
-               }
-               if ( lastElementChild && lastElementChild.nodeName.match( /div/i ) ) {
-                       /* Insert into the menu divs */
-                       lastElementChild.appendChild( node );
-               } else {
-                       root.appendChild( node );
-               }
-       }
-       if ( !node ) {
-               return null;
-       }
-
-       // unhide portlet if it was hidden before
-       root.className = root.className.replace( /(^| )emptyPortlet( |$)/, "$2" );
-
-       var link = document.createElement( 'a' );
-       link.appendChild( document.createTextNode( text ) );
-       link.href = href;
-
-       // Wrap in a span - make it work with vector tabs and has no effect on any other portlets
-       var span = document.createElement( 'span' );
-       span.appendChild( link );
-
-       var item = document.createElement( 'li' );
-       item.appendChild( span );
-       if ( id ) {
-               item.id = id;
-       }
-
-       if ( accesskey ) {
-               link.setAttribute( 'accesskey', accesskey );
-               tooltip += ' [' + accesskey + ']';
-       }
-       if ( tooltip ) {
-               link.setAttribute( 'title', tooltip );
-       }
-       if ( accesskey && tooltip ) {
-               mw.util.updateTooltipAccessKeys( [link] );
-       }
-
-       if ( nextnode && nextnode.parentNode === node ) {
-               node.insertBefore( item, nextnode );
-       } else {
-               node.appendChild( item );  // IE compatibility (?)
-       }
-
-       return item;
-};
-
-window.getInnerText = function( el ) {
-       if ( typeof el === 'string' ) {
-               return el;
-       }
-       if ( typeof el === 'undefined' ) {
-               return el;
-       }
-       // Custom sort value through 'data-sort-value' attribute
-       // (no need to prepend hidden text to change sort value)
-       if ( el.nodeType && el.getAttribute( 'data-sort-value' ) !== null ) {
-               // Make sure it's a valid DOM element (.nodeType) and that the attribute is set (!null)
-               return el.getAttribute( 'data-sort-value' );
-       }
-       if ( el.textContent ) {
-               return el.textContent; // not needed but it is faster
-       }
-       if ( el.innerText ) {
-               return el.innerText; // IE doesn't have textContent
-       }
-       var str = '';
-
-       var cs = el.childNodes;
-       var l = cs.length;
-       for ( var i = 0; i < l; i++ ) {
-               switch ( cs[i].nodeType ) {
-                       case 1: // ELEMENT_NODE
-                               str += window.getInnerText( cs[i] );
-                               break;
-                       case 3: // TEXT_NODE
-                               str += cs[i].nodeValue;
-                               break;
-               }
-       }
-       return str;
-};
-
-/**
- * Toggle checkboxes with shift selection.
- * To be removed in MediaWiki 1.23.
- *
- * @deprecated since 1.17 Use jquery.checkboxShiftClick instead.
- */
-$.each({
-       checkboxes: [],
-       lastCheckbox: null,
-       setupCheckboxShiftClick: $.noop,
-       addCheckboxClickHandlers: $.noop,
-       checkboxClickHandler: $.noop
-}, function ( key, val ) {
-       mw.log.deprecate( window, key, val, 'Use jquery.checkboxShiftClick instead.' );
-} );
-
-/*
-       Written by Jonathan Snook, http://www.snook.ca/jonathan
-       Add-ons by Robert Nyman, http://www.robertnyman.com
-       Author says "The credit comment is all it takes, no license. Go crazy with it!:-)"
-       From http://www.robertnyman.com/2005/11/07/the-ultimate-getelementsbyclassname/
-*/
-window.getElementsByClassName = function( oElm, strTagName, oClassNames ) {
-       var arrReturnElements = [];
-       if ( typeof oElm.getElementsByClassName === 'function' ) {
-               /* Use a native implementation where possible FF3, Saf3.2, Opera 9.5 */
-               var arrNativeReturn = oElm.getElementsByClassName( oClassNames );
-               if ( strTagName === '*' ) {
-                       return arrNativeReturn;
-               }
-               for ( var h = 0; h < arrNativeReturn.length; h++ ) {
-                       if( arrNativeReturn[h].tagName.toLowerCase() === strTagName.toLowerCase() ) {
-                               arrReturnElements[arrReturnElements.length] = arrNativeReturn[h];
-                       }
-               }
-               return arrReturnElements;
-       }
-       var arrElements = ( strTagName === '*' && oElm.all ) ? oElm.all : oElm.getElementsByTagName( strTagName );
-       var arrRegExpClassNames = [];
-       if( typeof oClassNames === 'object' ) {
-               for( var i = 0; i < oClassNames.length; i++ ) {
-                       arrRegExpClassNames[arrRegExpClassNames.length] =
-                               new RegExp("(^|\\s)" + oClassNames[i].replace(/\-/g, "\\-") + "(\\s|$)");
-               }
-       } else {
-               arrRegExpClassNames[arrRegExpClassNames.length] =
-                       new RegExp("(^|\\s)" + oClassNames.replace(/\-/g, "\\-") + "(\\s|$)");
-       }
-       var oElement;
-       var bMatchesAll;
-       for( var j = 0; j < arrElements.length; j++ ) {
-               oElement = arrElements[j];
-               bMatchesAll = true;
-               for( var k = 0; k < arrRegExpClassNames.length; k++ ) {
-                       if( !arrRegExpClassNames[k].test( oElement.className ) ) {
-                               bMatchesAll = false;
-                               break;
-                       }
-               }
-               if( bMatchesAll ) {
-                       arrReturnElements[arrReturnElements.length] = oElement;
-               }
-       }
-       return ( arrReturnElements );
-};
-
-window.redirectToFragment = function( fragment ) {
+win.redirectToFragment = function ( fragment ) {
        var webKitVersion,
-               match = navigator.userAgent.match(/AppleWebKit\/(\d+)/);
+               match = navigator.userAgent.match( /AppleWebKit\/(\d+)/ );
        if ( match ) {
                webKitVersion = parseInt( match[1], 10 );
                if ( webKitVersion < 420 ) {
@@ -375,8 +29,8 @@ window.redirectToFragment = function( fragment ) {
                        return;
                }
        }
-       if ( !window.location.hash ) {
-               window.location.hash = fragment;
+       if ( !win.location.hash ) {
+               win.location.hash = fragment;
 
                // Mozilla needs to wait until after load, otherwise the window doesn't
                // scroll.  See <https://bugzilla.mozilla.org/show_bug.cgi?id=516293>.
@@ -386,8 +40,8 @@ window.redirectToFragment = function( fragment ) {
                // well.
                if ( isGecko ) {
                        $( function () {
-                               if ( window.location.hash === fragment ) {
-                                       window.location.hash = fragment;
+                               if ( win.location.hash === fragment ) {
+                                       win.location.hash = fragment;
                                }
                        } );
                }
@@ -395,107 +49,200 @@ window.redirectToFragment = function( fragment ) {
 };
 
 /**
- * Add a cute little box at the top of the screen to inform the user of
- * something, replacing any preexisting message.
+ * User-agent sniffing.
+ * To be removed in MediaWiki 1.23.
  *
- * @deprecated since 1.17 Use the 'mediawiki.notify' module instead.
- * @param {string|HTMLElement} message To be put inside the message box.
+ * @deprecated since 1.17 Use jquery.client instead
  */
-mw.log.deprecate( window, 'jsMsg', mw.util.jsMessage, 'Use mediawiki.notify instead.' );
+
+msg = 'Use feature detection or module jquery.client instead';
+
+mw.log.deprecate( win, 'clientPC', ua, msg );
+
+// Ignored dummy values
+mw.log.deprecate( win, 'is_gecko', false, msg );
+mw.log.deprecate( win, 'is_chrome_mac', false, msg );
+mw.log.deprecate( win, 'is_chrome', false, msg );
+mw.log.deprecate( win, 'webkit_version', false, msg );
+mw.log.deprecate( win, 'is_safari_win', false, msg );
+mw.log.deprecate( win, 'is_safari', false, msg );
+mw.log.deprecate( win, 'webkit_match', false, msg );
+mw.log.deprecate( win, 'is_ff2', false, msg );
+mw.log.deprecate( win, 'ff2_bugs', false, msg );
+mw.log.deprecate( win, 'is_ff2_win', false, msg );
+mw.log.deprecate( win, 'is_ff2_x11', false, msg );
+mw.log.deprecate( win, 'opera95_bugs', false, msg );
+mw.log.deprecate( win, 'opera7_bugs', false, msg );
+mw.log.deprecate( win, 'opera6_bugs', false, msg );
+mw.log.deprecate( win, 'is_opera_95', false, msg );
+mw.log.deprecate( win, 'is_opera_preseven', false, msg );
+mw.log.deprecate( win, 'is_opera', false, msg );
+mw.log.deprecate( win, 'ie6_bugs', false, msg );
 
 /**
- * Inject a cute little progress spinner after the specified element
+ * DOM utilities for handling of events, text nodes and selecting elements
+ *
+ * To be removed in MediaWiki 1.23.
  *
- * @param element Element to inject after
- * @param id Identifier string (for use with removeSpinner(), below)
+ * @deprecated since 1.17 Use jQuery instead
  */
-window.injectSpinner = function( element, id ) {
-       var spinner = document.createElement( 'img' );
-       spinner.id = 'mw-spinner-' + id;
-       spinner.src = mw.config.get( 'stylepath' ) + '/common/images/spinner.gif';
-       spinner.alt = spinner.title = '...';
-       if( element.nextSibling ) {
-               element.parentNode.insertBefore( spinner, element.nextSibling );
+msg = 'Use jQuery instead';
+
+// Ignored dummy values
+mw.log.deprecate( win, 'doneOnloadHook', undefined );
+mw.log.deprecate( win, 'onloadFuncts', [] );
+mw.log.deprecate( win, 'runOnloadHook', $.noop );
+mw.log.deprecate( win, 'changeText', $.noop );
+mw.log.deprecate( win, 'killEvt', $.noop );
+mw.log.deprecate( win, 'addHandler', $.noop );
+mw.log.deprecate( win, 'hookEvent', $.noop );
+mw.log.deprecate( win, 'addClickHandler', $.noop );
+mw.log.deprecate( win, 'removeHandler', $.noop );
+mw.log.deprecate( win, 'getElementsByClassName', function () { return []; } );
+mw.log.deprecate( win, 'getInnerText', function () { return ''; } );
+
+// Run a function after the window onload event is fired
+mw.log.deprecate( win, 'addOnloadHook', function ( hookFunct ) {
+       if ( onloadFuncts ) {
+               onloadFuncts.push(hookFunct);
        } else {
-               element.parentNode.appendChild( spinner );
+               // If func queue is gone the event has happened already,
+               // run immediately instead of queueing.
+               hookFunct();
        }
-};
+} );
 
-/**
- * Remove a progress spinner added with injectSpinner()
- *
- * @param id Identifier string
- */
-window.removeSpinner = function( id ) {
-       var spinner = document.getElementById( 'mw-spinner-' + id );
-       if( spinner ) {
-               spinner.parentNode.removeChild( spinner );
-       }
-};
+$( win ).on( 'load', function () {
+       var i, functs;
 
-window.runOnloadHook = function() {
-       // don't run anything below this for non-dom browsers
-       if ( window.doneOnloadHook || !( document.getElementById && document.getElementsByTagName ) ) {
+       // Don't run twice
+       if ( !onloadFuncts ) {
                return;
        }
 
-       // set this before running any hooks, since any errors below
-       // might cause the function to terminate prematurely
-       window.doneOnloadHook = true;
+       // Deference and clear onloadFuncts before running any
+       // hooks to make sure we don't miss any addOnloadHook
+       // calls.
+       functs = onloadFuncts.slice();
+       onloadFuncts = undefined;
 
-       // Run any added-on functions
-       for ( var i = 0; i < window.onloadFuncts.length; i++ ) {
-               window.onloadFuncts[i]();
+       // Execute the queued functions
+       for ( i = 0; i < functs.length; i++ ) {
+               functs[i]();
        }
-};
+} );
 
 /**
- * Add an event handler to an element
+ * Toggle checkboxes with shift selection
  *
- * @param element Element to add handler to
- * @param attach String Event to attach to
- * @param handler callable Event handler callback
+ * To be removed in MediaWiki 1.23.
+ *
+ * @deprecated since 1.17 Use jquery.checkboxShiftClick instead
  */
-window.addHandler = function( element, attach, handler ) {
-       if( element.addEventListener ) {
-               element.addEventListener( attach, handler, false );
-       } else if( element.attachEvent ) {
-               element.attachEvent( 'on' + attach, handler );
-       }
-};
+msg = 'Use jquery.checkboxShiftClick instead';
+mw.log.deprecate( win, 'checkboxes', [], msg );
+mw.log.deprecate( win, 'lastCheckbox', null, msg );
+mw.log.deprecate( win, 'setupCheckboxShiftClick', $.noop, msg );
+mw.log.deprecate( win, 'addCheckboxClickHandlers', $.noop, msg );
+mw.log.deprecate( win, 'checkboxClickHandler', $.noop, msg );
 
-window.hookEvent = function( hookName, hookFunct ) {
-       window.addHandler( window, hookName, hookFunct );
-};
+/**
+ * Add a button to the default editor toolbar
+ *
+ * To be removed in MediaWiki 1.23.
+ *
+ * @deprecated since 1.17 Use mw.toolbar instead
+ */
+mw.log.deprecate( win, 'mwEditButtons', [], 'Use mw.toolbar instead' );
+mw.log.deprecate( win, 'mwCustomEditButtons', [], 'Use mw.toolbar instead' );
 
 /**
- * Add a click event handler to an element
+ * Spinner creation, injection and removal
+ *
+ * To be removed in MediaWiki 1.23.
  *
- * @param element Element to add handler to
- * @param handler callable Event handler callback
+ * @deprecated since 1.18 Use jquery.spinner instead
  */
-window.addClickHandler = function( element, handler ) {
-       window.addHandler( element, 'click', handler );
-};
+mw.log.deprecate( win, 'injectSpinner', $.noop, 'Use jquery.spinner instead' );
+mw.log.deprecate( win, 'removeSpinner', $.noop, 'Use jquery.spinner instead' );
 
 /**
- * Removes an event handler from an element
+ * Escape utilities
+ *
+ * To be removed in MediaWiki 1.23.
  *
- * @param element Element to remove handler from
- * @param remove String Event to remove
- * @param handler callable Event handler callback to remove
+ * @deprecated since 1.18 Use mw.html instead
  */
-window.removeHandler = function( element, remove, handler ) {
-       if( window.removeEventListener ) {
-               element.removeEventListener( remove, handler, false );
-       } else if( window.detachEvent ) {
-               element.detachEvent( 'on' + remove, handler );
+mw.log.deprecate( win, 'escapeQuotes', $.noop,'Use mw.html instead' );
+mw.log.deprecate( win, 'escapeQuotesHTML', $.noop,'Use mw.html instead' );
+
+/**
+ * Display a message to the user
+ *
+ * To be removed in MediaWiki 1.23.
+ *
+ * @deprecated since 1.17 Use mediawiki.notify instead
+ * @param {string|HTMLElement} message To be put inside the message box
+ */
+mw.log.deprecate( win, 'jsMsg', mw.util.jsMessage, 'Use mediawiki.notify instead' );
+
+/**
+ * Misc. utilities
+ *
+ * To be removed in MediaWiki 1.23.
+ *
+ * @deprecated since 1.17 Use mediawiki.util instead
+ */
+msg = 'Use mediawiki.util instead';
+mw.log.deprecate( win, 'tooltipAccessKeyPrefix', 'alt-', msg );
+mw.log.deprecate( win, 'tooltipAccessKeyRegexp', /\[(alt-)?(.)\]$/, msg );
+mw.log.deprecate( win, 'updateTooltipAccessKeys', mw.util.updateTooltipAccessKeys, msg );
+mw.log.deprecate( win, 'addPortletLink', mw.util.addPortletLink, msg );
+mw.log.deprecate( win, 'appendCSS', mw.util.addCSS );
+
+/**
+ * Wikipage import methods
+ */
+
+// included-scripts tracker
+win.loadedScripts = {};
+
+win.importScript = function ( page ) {
+       var uri = mw.config.get( 'wgScript' ) + '?title=' +
+               mw.util.wikiUrlencode( page ) +
+               '&action=raw&ctype=text/javascript';
+       return win.importScriptURI( uri );
+};
+
+win.importScriptURI = function ( url ) {
+       if ( win.loadedScripts[url] ) {
+               return null;
        }
+       win.loadedScripts[url] = true;
+       var s = document.createElement( 'script' );
+       s.setAttribute( 'src', url );
+       s.setAttribute( 'type', 'text/javascript' );
+       document.getElementsByTagName( 'head' )[0].appendChild( s );
+       return s;
+};
+
+win.importStylesheet = function( page ) {
+       return win.importStylesheetURI( mw.config.get( 'wgScript' ) + '?action=raw&ctype=text/css&title=' + mw.util.wikiUrlencode( page ) );
+};
+
+win.importStylesheetURI = function( url, media ) {
+       var l = document.createElement( 'link' );
+       l.rel = 'stylesheet';
+       l.href = url;
+       if ( media ) {
+               l.media = media;
+       }
+       document.getElementsByTagName('head')[0].appendChild( l );
+       return l;
 };
-window.hookEvent( 'load', window.runOnloadHook );
 
 if ( isIE6 ) {
-       window.importScriptURI( mw.config.get( 'stylepath' ) + '/common/IEFixes.js' );
+       win.importScriptURI( mw.config.get( 'stylepath' ) + '/common/IEFixes.js' );
 }
 
 }( mediaWiki, jQuery ) );
index 67313c9..45258e5 100644 (file)
@@ -7,17 +7,28 @@
 
        // Use the same function for all navigation headings - don't repeat
        function toggle( $element ) {
+               var isCollapsed = $element.parent().is( '.collapsed' );
+
                $.cookie(
                        'vector-nav-' + $element.parent().attr( 'id' ),
-                       $element.parent().is( '.collapsed' ),
+                       isCollapsed,
                        { 'expires': 30, 'path': '/' }
                );
+
                $element
                        .parent()
                        .toggleClass( 'expanded' )
                        .toggleClass( 'collapsed' )
                        .find( '.body' )
                        .slideToggle( 'fast' );
+               isCollapsed = !isCollapsed;
+
+               $element
+                       .find( '> a' )
+                       .attr( {
+                               'aria-pressed': isCollapsed ? 'false' : 'true',
+                               'aria-expanded': isCollapsed ? 'false' : 'true'
+                       } );
        }
 
        /* Browser Support */
                        .each( function ( i ) {
                                var id = $(this).attr( 'id' ),
                                        state = $.cookie( 'vector-nav-' + id );
+                               $(this).find( 'ul:first' ).attr( 'id', id + '-list' );
                                // Add anchor tag to heading for better accessibility
-                               $( this ).find( 'h3' ).wrapInner( $( '<a href="#"></a>' ).click( false ) );
+                               $( this ).find( 'h3' ).wrapInner(
+                                       $( '<a>' )
+                                               .attr( {
+                                                       href: '#',
+                                                       'aria-haspopup': 'true',
+                                                       'aria-controls': id + '-list',
+                                                       role: 'button'
+                                               } )
+                                               .click( false )
+                               );
                                // In the case that we are not showing the new version, let's show the languages by default
                                if (
                                        state === 'true' ||
                                                .find( '.body' )
                                                .hide() // bug 34450
                                                .show();
+                                       $(this).find( 'h3 > a' )
+                                               .attr( {
+                                                       'aria-pressed': 'true',
+                                                       'aria-expanded': 'true'
+                                               } );
                                } else {
                                        $(this)
                                                .addClass( 'collapsed' )
                                                .removeClass( 'expanded' );
+                                       $(this).find( 'h3 > a' )
+                                               .attr( {
+                                                       'aria-pressed': 'false',
+                                                       'aria-expanded': 'false'
+                                               } );
                                }
                                // Re-save cookie
                                if ( state !== null ) {
diff --git a/skins/vector/images/preferences-break.png b/skins/vector/images/preferences-break.png
deleted file mode 100644 (file)
index b529308..0000000
Binary files a/skins/vector/images/preferences-break.png and /dev/null differ
diff --git a/skins/vector/images/preferences-fade.png b/skins/vector/images/preferences-fade.png
deleted file mode 100644 (file)
index 638084d..0000000
Binary files a/skins/vector/images/preferences-fade.png and /dev/null differ
diff --git a/skins/vector/images/preferences/break.png b/skins/vector/images/preferences/break.png
new file mode 100644 (file)
index 0000000..b529308
Binary files /dev/null and b/skins/vector/images/preferences/break.png differ
diff --git a/skins/vector/images/preferences/fade.png b/skins/vector/images/preferences/fade.png
new file mode 100644 (file)
index 0000000..638084d
Binary files /dev/null and b/skins/vector/images/preferences/fade.png differ
index de5fd18..f8be097 100644 (file)
@@ -378,6 +378,9 @@ div#simpleSearch {
 div#simpleSearch input:focus {
        outline: none;
 }
+div#simpleSearch input {
+       color: black;
+}
 div#simpleSearch input.placeholder {
        color: #999;
 }
@@ -407,7 +410,6 @@ div#simpleSearch input#searchInput {
         * this from ever being shown anyways.
        */
        font-size: 13px;
-       color: black;
        background-color: transparent;
        direction: ltr;
 }
@@ -547,104 +549,6 @@ div#footer #footer-places li {
        text-decoration: none;
 }
 
-/*
- *
- * The following code is highly modified from monobook. It would be nice if the
- * preftoc id was more human readable like preferences-toc for instance,
- * howerver this would require backporting the other skins.
- */
-
-/* Preferences */
-#preftoc {
-       /* Tabs */
-       width: 100%;
-       float: left;
-       clear: both;
-       margin: 0 !important;
-       padding: 0 !important;
-       .background-image('images/preferences-break.png');
-       background-position: bottom left;
-       background-repeat: no-repeat;
-}
-       #preftoc li {
-               /* Tab */
-               float: left;
-               margin: 0;
-               padding: 0;
-               padding-right: 1px;
-               height: 2.25em;
-               white-space: nowrap;
-               list-style-type: none;
-               list-style-image: none;
-               .background-image('images/preferences-break.png');
-               background-position: bottom right;
-               background-repeat: no-repeat;
-       }
-       /* Sadly, IE6 won't understand this */
-       #preftoc li:first-child {
-               margin-left: 1px;
-       }
-       #preftoc a,
-       #preftoc a:active {
-               display: inline-block;
-               position: relative;
-               color: @menu-link-color;
-               padding: 0.5em;
-               text-decoration: none;
-               background-image: none;
-               font-size: 0.9em;
-       }
-       #preftoc a:hover,
-       #preftoc a:focus {
-               text-decoration: underline;
-       }
-       #preftoc li.selected a {
-               .background-image('images/preferences-fade.png');
-               background-position: bottom;
-               background-repeat: repeat-x;
-               color: #333;
-               text-decoration: none;
-       }
-#preferences {
-       float: left;
-       width: 100%;
-       margin: 0;
-       margin-top: -2px;
-       clear: both;
-       border: solid 1px #ccc;
-       background-color: #fafafa;
-}
-#preferences fieldset {
-       border: none;
-       border-top: solid 1px #ccc;
-}
-#preferences fieldset.prefsection {
-       border: none;
-       padding: 0;
-       margin: 1em;
-}
-#preferences legend {
-       color: #666;
-}
-#preferences fieldset.prefsection legend.mainLegend {
-       display: none;
-}
-#preferences td {
-       padding-left: 0.5em;
-       padding-right: 0.5em;
-}
-.htmlform-tip {
-       font-size: x-small;
-       padding: .2em 2em;
-       color: #666;
-}
-#preferences div.mw-prefs-buttons {
-       padding: 1em;
-}
-#preferences div.mw-prefs-buttons input {
-       margin-right: 0.25em;
-}
-
 ul {
        list-style-type: disc;
        .list-style-image('images/bullet-icon.png');
diff --git a/skins/vector/special.preferences.less b/skins/vector/special.preferences.less
new file mode 100644 (file)
index 0000000..a9b1006
--- /dev/null
@@ -0,0 +1,114 @@
+@import "mediawiki.mixins";
+@import "variables";
+
+/**
+ * The following code is highly modified from monobook. It would be nice if the
+ * preftoc id was more human readable like preferences-toc for instance,
+ * howerver this would require backporting the other skins.
+ */
+
+#preftoc {
+       /* Tabs */
+       width: 100%;
+       float: left;
+       clear: both;
+       margin: 0 !important;
+       padding: 0 !important;
+       .background-image('images/preferences/break.png');
+       background-position: bottom left;
+       background-repeat: no-repeat;
+
+       li {
+               /* Tab */
+               float: left;
+               margin: 0;
+               padding: 0;
+               padding-right: 1px;
+               height: 2.25em;
+               white-space: nowrap;
+               list-style-type: none;
+               list-style-image: none;
+               .background-image('images/preferences/break.png');
+               background-position: bottom right;
+               background-repeat: no-repeat;
+
+               /* Sadly, IE6 won't understand this */
+               &:first-child {
+                       margin-left: 1px;
+               }
+
+               &.selected {
+                       a {
+                               .background-image('images/preferences/fade.png');
+                               background-position: bottom;
+                               background-repeat: repeat-x;
+                               color: #333;
+                               text-decoration: none;
+                       }
+               }
+       }
+
+       a,
+       a:active {
+               display: inline-block;
+               position: relative;
+               color: @menu-link-color;
+               padding: 0.5em;
+               text-decoration: none;
+               background-image: none;
+               font-size: 0.9em;
+       }
+
+       a:hover,
+       a:focus {
+               text-decoration: underline;
+       }
+}
+
+#preferences {
+       float: left;
+       width: 100%;
+       margin: 0;
+       margin-top: -2px;
+       clear: both;
+       border: solid 1px #ccc;
+       background-color: #fafafa;
+
+       fieldset {
+               border: none;
+               border-top: solid 1px #ccc;
+
+               &.prefsection {
+                       border: none;
+                       padding: 0;
+                       margin: 1em;
+
+                       legend.mainLegend {
+                               display: none;
+                       }
+               }
+       }
+
+       legend {
+               color: #666;
+       }
+
+       td {
+               padding-left: 0.5em;
+               padding-right: 0.5em;
+       }
+
+       div.mw-prefs-buttons {
+               padding: 1em;
+
+               input {
+                       margin-right: 0.25em;
+               }
+       }
+}
+
+.htmlform-tip {
+       font-size: x-small;
+       padding: .2em 2em;
+       color: #666;
+}
index 62e3d55..a76b639 100644 (file)
@@ -1,9 +1,13 @@
 @import "variables.less";
 @import "beta/variables.less";
-@import "screen.less";
-@import "beta/screen.less";
-@import "externalLinks.less";
-@import "collapsibleNav.less";
+
+@media screen {
+       @import "screen.less";
+       @import "beta/screen.less";
+       @import "externalLinks.less";
+       @import "collapsibleNav.less";
+}
+
 @media screen and (min-width: 982px) {
        @import "screen-hd.less";
 }
index a5db63c..bd45851 100644 (file)
@@ -1,7 +1,10 @@
 @import "variables.less";
-@import "screen.less";
-@import "externalLinks.less";
-@import "collapsibleNav.less";
+
+@media screen {
+       @import "screen.less";
+       @import "externalLinks.less";
+       @import "collapsibleNav.less";
+}
 
 @media screen and (min-width: 982px) {
        @import "screen-hd.less";
index 02a66b5..13312bb 100644 (file)
@@ -1419,11 +1419,21 @@ Bug 52763: Preformatted in <blockquote>
 !! input
 <blockquote>
  Blah
+{|
+|
+ indented cell (no pre-wrapping!)
+|}
 </blockquote>
 !! result
 <blockquote>
 <p> Blah
 </p>
+<table>
+<tr>
+<td>
+<p> indented cell (no pre-wrapping!)
+</p>
+</td></tr></table>
 </blockquote>
 
 !! end
@@ -1893,7 +1903,7 @@ data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},
 !! end
 
 !! test
-Templates: Dont escape already nowiki-escaped text in template parameters
+Templates: Don't escape already nowiki-escaped text in template parameters
 !! options
 parsoid=html2wt,wt2wt
 !! input
@@ -2172,9 +2182,9 @@ parsoid=wt2html,wt2wt
  [[Category:foo]] <!-- No pre-wrapping -->
 {{echo| [[Category:foo]]}} <!-- No pre-wrapping -->
 !! result
- <link rel="mw:WikiLink/Category" href="./Category:Foo"> <!-- No pre-wrapping -->
+ <link rel="mw:PageProp/Category" href="./Category:Foo"> <!-- No pre-wrapping -->
 <span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":" [[Category:foo]]"}},"i":0}}]}'> </span>
-<link rel="mw:WikiLink/Category" href="./Category:Foo" about="#mwt1"> <!-- No pre-wrapping -->
+<link rel="mw:PageProp/Category" href="./Category:Foo" about="#mwt1"> <!-- No pre-wrapping -->
 !! end
 
 !! test
@@ -2186,9 +2196,9 @@ parsoid=wt2html,wt2wt
  [[Category:foo]] {{echo|b}}
 !! result
 <pre>
-<link rel="mw:WikiLink/Category" href="./Category:Foo"> a
+<link rel="mw:PageProp/Category" href="./Category:Foo"> a
 
-<link rel="mw:WikiLink/Category" href="./Category:Foo"> <span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"b"}},"i":0}}]}'>b</span></pre>
+<link rel="mw:PageProp/Category" href="./Category:Foo"> <span about="#mwt1" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"b"}},"i":0}}]}'>b</span></pre>
 !! end
 
 ###
@@ -3640,6 +3650,8 @@ External links: link text with spaces
 
 !! test
 External links: wiki links within external link (Bug 3695)
+!! options
+php
 !! input
 [http://example.com [[wikilink]] embedded in ext link]
 !! result
@@ -3647,6 +3659,16 @@ External links: wiki links within external link (Bug 3695)
 </p>
 !! end
 
+!! test
+Parsoid: External links: wiki links within external link (Bug 3695)
+!! options
+parsoid
+!! input
+[http://example.com [[wikilink]] embedded in ext link]
+!! result
+<p><a rel="mw:ExtLink" href="http://example.com"></a><a rel="mw:WikiLink" href="./Wikilink">wikilink</a><span> embedded in ext link</span></p>
+!! end
+
 !! test
 BUG 787: Links with one slash after the url protocol are invalid
 !! input
@@ -3791,6 +3813,8 @@ External link containing double-single-quotes in text embedded in italics (bug 4
 
 !! test
 External link containing double-single-quotes with no space separating the url from text in italics
+!! options
+php
 !! input
 [http://www.musee-picasso.fr/pages/page_id18528_u1l2.htm''La muerte de Casagemas'' (1901) en el sitio de [[Museo Picasso (París)|Museo Picasso]].]
 !! result
@@ -3798,6 +3822,16 @@ External link containing double-single-quotes with no space separating the url f
 </p>
 !! end
 
+!! test
+Parsoid:External link containing double-single-quotes with no space separating the url from text in italics
+!! options
+parsoid
+!! input
+[http://www.musee-picasso.fr/pages/page_id18528_u1l2.htm''La muerte de Casagemas'' (1901) en el sitio de [[Museo Picasso (París)|Museo Picasso]].]
+!! result
+<p><a rel="mw:ExtLink" href="http://www.musee-picasso.fr/pages/page_id18528_u1l2.htm"><i>La muerte de Casagemas</i> (1901) en el sitio de </a><a rel="mw:WikiLink" href="./Museo_Picasso_(París)">Museo Picasso</a><span>.</span></p>
+!! end
+
 !! test
 External link with comments in link text
 !! input
@@ -4899,6 +4933,19 @@ Namespace takes precedence over interwiki link (bug 51680)
 </p>
 !! end
 
+# The previous test doesn't work correctly in html2*, due to not recognizing the
+# link as an internal one. This one checks for the correct behavior.
+!! test
+Link to namespace preferred over interwiki with correct rel attribute
+!! options
+parsoid=html2wt,html2html
+!! input
+[[MemoryAlpha:AlphaTest]]
+!! result
+<p><a rel="mw:WikiLink" href="./MemoryAlpha:AlphaTest">MemoryAlpha:AlphaTest</a>
+</p>
+!! end
+
 !! test
 Piped link to namespace
 !! input
@@ -5332,6 +5379,16 @@ Parsoid-centric test: Whitespace in ext- and wiki-links should be preserved
 </p>
 !! end
 
+!! test
+Parsoid: Scoped parsing should handle mixed transclusions and plain text
+!! options
+parsoid
+!! input
+[[Foo|{{echo|a}} b {{echo|c}}]]
+!! result
+<p data-parsoid='{"dsr":[0,20,0,0]}'><a rel="mw:WikiLink" href="Foo"><span about="#mwt2" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"a"}},"i":0}}]}'>a</span> b <span about="#mwt3" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"c"}},"i":0}}]}'>c</span></a></p>
+!! end
+
 ###
 ### Interwiki links (see maintenance/interwiki.sql)
 ###
@@ -5378,6 +5435,22 @@ Interwiki link with fragment (bug 2130)
 </p>
 !! end
 
+# Ideally the wikipedia: prefix here should be proto-relative too
+!! test
+Different interwiki prefixes mapping to the same URL
+!! options
+parsoid
+!! input
+[[wikipedia:Foo]]
+
+[[:en:Foo]]
+!! result
+<p data-parsoid='{"dsr":[0,17,0,0]}'><a rel="mw:ExtLink" href="http://en.wikipedia.org/wiki/Foo" data-parsoid='{"stx":"simple","a":{"href":"http://en.wikipedia.org/wiki/Foo"},"sa":{"href":"wikipedia:Foo"},"isIW":true,"dsr":[0,17,null,1]}'>wikipedia:Foo</a></p>
+
+
+<p data-parsoid='{"dsr":[19,30,0,0]}'><a rel="mw:ExtLink" href="//en.wikipedia.org/wiki/Foo" data-parsoid='{"stx":"simple","a":{"href":"//en.wikipedia.org/wiki/Foo"},"sa":{"href":":en:Foo"},"isIW":true,"dsr":[19,30,null,1]}'>en:Foo</a></p>
+!! end
+
 
 ###
 ### Interlanguage links
@@ -5477,7 +5550,7 @@ parsoid
 [[ko:]]
 !! result
 <p>
-<link rel="mw:WikiLink/Language" href="http://ko.wikipedia.org/wiki/"></p>
+<link rel="mw:PageProp/Language" href="http://ko.wikipedia.org/wiki/"></p>
 !! end
 
 !! test
@@ -5487,7 +5560,7 @@ parsoid
 !! input
 [[:ko:]]
 !! result
-<p><a rel="mw:WikiLink/Interwiki" href="http://ko.wikipedia.org/wiki/">ko:</a></p>
+<p><a rel="mw:ExtLink" href="//ko.wikipedia.org/wiki/">ko:</a></p>
 !! end
 
 ###
@@ -5550,7 +5623,7 @@ parsoid=wt2html
 !! input
 #REDIRECT [[Category:Foo]]
 !! result
-<link rel="mw:PageProp/redirect" href="./Category:Foo"><link rel="mw:WikiLink/Category" href="./Category:Foo">
+<link rel="mw:PageProp/redirect" href="./Category:Foo"><link rel="mw:PageProp/Category" href="./Category:Foo">
 !! end
 
 !! test
@@ -5560,7 +5633,7 @@ parsoid=wt2html
 !! input
 #REDIRECT [[Category%3AFoo]]
 !! result
-<link rel="mw:PageProp/redirect" href="./Category:Foo"><link rel="mw:WikiLink/Category" href="./Category:Foo">
+<link rel="mw:PageProp/redirect" href="./Category:Foo"><link rel="mw:PageProp/Category" href="./Category:Foo">
 !! end
 
 !! test
@@ -8278,9 +8351,9 @@ parsoid=wt2html,wt2wt
 <!--Two categories (Bug 50330)-->
 <table>[[Category:bar1]][[Category:bar2]]<tr><td>foo</td></tr></table>
 !!result
-<link rel="mw:WikiLink/Category" href="./Category:Foo1"><table><tbody><tr><td>foo</td></tr></tbody></table>
+<link rel="mw:PageProp/Category" href="./Category:Foo1"><table><tbody><tr><td>foo</td></tr></tbody></table>
 <!--Two categories (Bug 50330)-->
-<link rel="mw:WikiLink/Category" href="./Category:Bar1"><link rel="mw:WikiLink/Category" href="./Category:Bar2"><table><tbody><tr><td>foo</td></tr></tbody></table>
+<link rel="mw:PageProp/Category" href="./Category:Bar1"><link rel="mw:PageProp/Category" href="./Category:Bar2"><table><tbody><tr><td>foo</td></tr></tbody></table>
 !!end
 
 !!test
@@ -10134,6 +10207,30 @@ parsoid
 #</span>
 #</p>
 
+!! test
+Caption with a template in it
+!! options
+parsoid
+!! input
+[[Image:Foobar.jpg|thumb|200px|This caption has a {{echo|transclusion}} in it.]]
+!! result
+<figure typeof="mw:Image/Thumb"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg" height="23" width="200"></a><figcaption>This caption has a <span about="#mwt1" typeof="mw:Transclusion" data-mw="{&quot;parts&quot;:[{&quot;template&quot;:{&quot;target&quot;:{&quot;wt&quot;:&quot;echo&quot;,&quot;href&quot;:&quot;./Template:Echo&quot;},&quot;params&quot;:{&quot;1&quot;:{&quot;wt&quot;:&quot;transclusion&quot;}},&quot;i&quot;:0}}]}">transclusion</span> in it.</figcaption></figure>
+!! end
+
+!! test
+Caption with unbalanced tags in it
+!! options
+parsoid
+!! input
+foo
+[[Image:Foobar.jpg|thumb|200px|This caption has a <center>unbalanced tag in it.]]
+bar
+!! result
+<p>foo</p>
+<figure typeof="mw:Image/Thumb"><a href="File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/3/3a/Foobar.jpg" height="23" width="200"></a><figcaption>This caption has a <center>unbalanced tag in it.</center></figcaption></figure>
+<p>bar</p>
+!! end
+
 
 ###
 ### Subpages
@@ -10404,7 +10501,7 @@ language=is
 !! input
 x[[Category:Foo]]y
 !! result
-<p>x<link rel="mw:WikiLink/Category" href="Category:Foo">y</p>
+<p>x<link rel="mw:PageProp/Category" href="Category:Foo">y</p>
 !! end
 
 !! test
@@ -10430,8 +10527,8 @@ parsoid
 [[Category:Foo]]
 [[Category:Foo|Bar]]
 !! result
-<link rel="mw:WikiLink/Category" href="Category:Foo">
-<link rel="mw:WikiLink/Category" href="Category:Foo#Bar">
+<link rel="mw:PageProp/Category" href="Category:Foo">
+<link rel="mw:PageProp/Category" href="Category:Foo#Bar">
 !! end
 
 ###
@@ -16386,10 +16483,10 @@ A <ref>
 
 <references />
 !!result
-<p>A <span about="#mwt1" class="reference" data-mw='{"name":"ref","body":{"html":"This is a <b data-parsoid=\"{&amp;quot;dsr&amp;quot;:[19,40,3,3]}\"><a rel=\"mw:WikiLink\" href=\"./Bolded_link\" data-parsoid=\"{&amp;quot;stx&amp;quot;:&amp;quot;simple&amp;quot;,&amp;quot;a&amp;quot;:{&amp;quot;href&amp;quot;:&amp;quot;./Bolded_link&amp;quot;},&amp;quot;sa&amp;quot;:{&amp;quot;href&amp;quot;:&amp;quot;bolded link&amp;quot;},&amp;quot;dsr&amp;quot;:[22,37,2,2]}\">bolded link</a></b> and this is a <span about=\"#mwt5\" typeof=\"mw:Transclusion\" data-mw=\"{&amp;quot;parts&amp;quot;:[{&amp;quot;template&amp;quot;:{&amp;quot;target&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;echo&amp;quot;,&amp;quot;href&amp;quot;:&amp;quot;./Template:Echo&amp;quot;},&amp;quot;params&amp;quot;:{&amp;quot;1&amp;quot;:{&amp;quot;wt&amp;quot;:&amp;quot;transclusion&amp;quot;}},&amp;quot;i&amp;quot;:0}}]}\" data-parsoid=\"{&amp;quot;dsr&amp;quot;:[55,76,null,null],&amp;quot;pi&amp;quot;:[[{&amp;quot;k&amp;quot;:&amp;quot;1&amp;quot;,&amp;quot;spc&amp;quot;:[&amp;quot;&amp;quot;,&amp;quot;&amp;quot;,&amp;quot;&amp;quot;,&amp;quot;&amp;quot;]}]]}\">transclusion</span>\n"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span></p>
+<p>A <span about="#mwt2" class="reference" data-mw='{"name":"ref","body":{"html":"This is a &lt;b data-parsoid=&#39;{\"dsr\":[19,40,3,3]}&#39;>&lt;a rel=\"mw:WikiLink\" href=\"./Bolded_link\" data-parsoid=&#39;{\"stx\":\"simple\",\"a\":{\"href\":\"./Bolded_link\"},\"sa\":{\"href\":\"bolded link\"},\"dsr\":[22,37,2,2]}&#39;>bolded link&lt;/a>&lt;/b> and this is a &lt;span about=\"#mwt5\" typeof=\"mw:Transclusion\" data-mw=&#39;{\"parts\":[{\"template\":{\"target\":{\"wt\":\"echo\",\"href\":\"./Template:Echo\"},\"params\":{\"1\":{\"wt\":\"transclusion\"}},\"i\":0}}]}&#39; data-parsoid=&#39;{\"dsr\":[55,76,null,null],\"pi\":[[{\"k\":\"1\",\"spc\":[\"\",\"\",\"\",\"\"]}]]}&#39;>transclusion&lt;/span>\n"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span></p>
 
-<ol class="references" typeof="mw:Extension/references" about="#mwt2" data-mw='{"name":"references","attrs":{}}'>
-<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> This is a <b><a rel="mw:WikiLink" href="./Bolded_link">bolded link</a></b> and this is a <span about="#mwt3" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"transclusion"}},"i":0}}]}'>transclusion</span>
+<ol class="references" typeof="mw:Extension/references" about="#mwt4" data-mw='{"name":"references","attrs":{}}'>
+<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> This is a <b><a rel="mw:WikiLink" href="./Bolded_link">bolded link</a></b> and this is a <span about="#mwt5" typeof="mw:Transclusion" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"transclusion"}},"i":0}}]}'>transclusion</span>
 </li>
 </ol>
 !!end
@@ -16463,10 +16560,9 @@ A <ref> foo {{echo|</ref> B C}}
 
 <references />
 !!result
-<p>A <span about="#mwt1" class="reference" data-mw='{"name":"ref","body":{"html":"foo <span typeof=\"mw:Nowiki\" data-parsoid=\"{&amp;quot;src&amp;quot;:&amp;quot;{{&amp;quot;,&amp;quot;dsr&amp;quot;:[12,14,0,0]}\">{{</span>echo|"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span> B C<span typeof="mw:Nowiki">}}</span></p>
-
-<ol about="#mwt2" class="references" typeof="mw:Extension/references" data-mw='{"name":"references","attrs":{}}'>
-<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> foo <span typeof="mw:Nowiki">{{</span>echo|</li>
+<p>A <span class="reference" data-mw="{&quot;name&quot;:&quot;ref&quot;,&quot;body&quot;:{&quot;html&quot;:&quot;foo <span typeof=\&quot;mw:Nowiki\&quot; data-parsoid='{\&quot;src\&quot;:\&quot;{{\&quot;,\&quot;dsr\&quot;:[12,14,0,0]}'>{{</span>echo|&quot;},&quot;attrs&quot;:{}}" id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span> B C<span typeof="mw:Nowiki">}}</span></p>
+<ol class="references" typeof="mw:Extension/references" data-mw="{&quot;name&quot;:&quot;references&quot;,&quot;attrs&quot;:{}}">
+<li id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> foo <span typeof="mw:Nowiki">{{</span>echo|</li>
 </ol>
 !!end
 
@@ -16476,13 +16572,11 @@ Ref: 9. unclosed comments should not leak out of ref-body
 parsoid
 !!input
 A <ref> foo <!--</ref> B C
-
 <references />
 !!result
-<p>A <span about="#mwt1" class="reference" data-mw='{"name":"ref","body":{"html":"foo <!---->"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span> B C</p>
-
-<ol about="#mwt2" class="references" typeof="mw:Extension/references" data-mw='{"name":"references","attrs":{}}'>
-<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> foo <!----></li>
+<p>A <span class="reference" data-mw='{"name":"ref","body":{"html":"foo &lt;!---->"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span> B C</p>
+<ol class="references" typeof="mw:Extension/references" data-mw='{"name":"references","attrs":{}}'>
+<li id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> foo </li>
 </ol>
 !!end
 
@@ -16495,10 +16589,11 @@ A <ref> <b> foo </ref> B C
 
 <references />
 !!result
-<p>A <span about="#mwt1" class="reference" data-mw='{"name":"ref","body":{"html":"<b data-parsoid=\"{&amp;quot;stx&amp;quot;:&amp;quot;html&amp;quot;,&amp;quot;autoInsertedEnd&amp;quot;:true,&amp;quot;dsr&amp;quot;:[8,16,3,0]}\"> foo </b>"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span> B C</p>
+<p data-parsoid='{"dsr":[0,26,0,0]}'>A <span about="#mwt2" class="reference" data-mw='{"name":"ref","body":{"html":"&lt;b data-parsoid=&#39;{\"stx\":\"html\",\"autoInsertedEnd\":true,\"dsr\":[8,16,3,0]}&#39;> foo &lt;/b>"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref" data-parsoid='{"src":"&lt;ref> &lt;b> foo &lt;/ref>","dsr":[2,22,5,6]}'><a href="#cite_note-1">[1]</a></span> B C</p>
 
-<ol about="#mwt2" class="references" typeof="mw:Extension/references" data-mw='{"name":"references","attrs":{}}'>
-<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> <b> foo </b></li>
+
+<ol class="references" typeof="mw:Extension/references" about="#mwt4" data-parsoid='{"src":"&lt;references />","dsr":[28,42,2,2]}' data-mw='{"name":"references","attrs":{}}'>
+<li about="#cite_note-1" id="cite_note-1" data-parsoid="{}"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> <b data-parsoid='{"stx":"html","autoInsertedEnd":true,"dsr":[8,16,3,0]}'> foo </b></li>
 </ol>
 !!end
 
@@ -16510,8 +16605,8 @@ parsoid
 A <ref>foo</ref> B
 C <ref>bar</ref> D
 !!result
-<p>A <span about="#mwt1" class="reference" data-mw='{"name":"ref","body":{"html":"foo"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span> B
-C <span about="#mwt2" class="reference" data-mw='{"name":"ref","body":{"html":"bar"},"attrs":{}}' id="cite_ref-2-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-2">[2]</a></span> D</p>
+<p data-parsoid='{"dsr":[0,37,0,0]}'>A <span about="#mwt2" class="reference" data-mw='{"name":"ref","body":{"html":"foo"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref" data-parsoid='{"src":"&lt;ref>foo&lt;/ref>","dsr":[2,16,5,6]}'><a href="#cite_note-1">[1]</a></span> B
+C <span about="#mwt4" class="reference" data-mw='{"name":"ref","body":{"html":"bar"},"attrs":{}}' id="cite_ref-2-0" rel="dc:references" typeof="mw:Extension/ref" data-parsoid='{"src":"&lt;ref>bar&lt;/ref>","dsr":[21,35,5,6]}'><a href="#cite_note-2">[2]</a></span> D</p>
 !!end
 
 !!test
@@ -16558,10 +16653,10 @@ parsoid
 
 <references />
 !!result
-<p><span about="#mwt1" class="reference" data-mw="{&quot;name&quot;:&quot;ref&quot;,&quot;body&quot;:{&quot;html&quot;:&quot;foo &amp;lt;ref&amp;gt;bar&amp;lt;/ref&amp;gt; baz&quot;},&quot;attrs&quot;:{}}" id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span></p>
+<p data-parsoid='{"dsr":[0,33,0,0]}'><span about="#mwt2" class="reference" data-mw='{"name":"ref","body":{"html":"foo &amp;lt;ref>bar&amp;lt;/ref> baz"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref" data-parsoid='{"src":"&lt;ref>foo &lt;ref>bar&lt;/ref> baz&lt;/ref>","dsr":[0,33,5,6]}'><a href="#cite_note-1">[1]</a></span></p>
 
-<ol class="references" typeof="mw:Extension/references" about="#mwt2" data-mw="{&quot;name&quot;:&quot;references&quot;,&quot;attrs&quot;:{}}">
-<li about="#cite_note-1" id="cite_note-1" data-parsoid="{}"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> foo &lt;ref&gt;bar&lt;/ref&gt; baz</li>
+<ol class="references" typeof="mw:Extension/references" about="#mwt5" data-parsoid='{"src":"&lt;references />","dsr":[35,49,2,2]}' data-mw='{"name":"references","attrs":{}}'>
+<li about="#cite_note-1" id="cite_note-1" data-parsoid="{}"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> foo &lt;ref>bar&lt;/ref> baz</li>
 </ol>
 !!end
 
@@ -16672,10 +16767,13 @@ B <ref name="b">bar</ref>
 This should just get lost.
 </references>
 !!result
-<p>A <span about="#mwt2" class="reference" data-mw='{"name":"ref","attrs":{"name":"a"}}' id="cite_ref-a-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-a-1">[1]</a></span>
-B <span about="#mwt4" class="reference" data-mw='{"name":"ref","body":{"html":"bar"},"attrs":{"name":"b"}}' id="cite_ref-b-2-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-b-2">[2]</a></span></p>
+<p data-parsoid='{"dsr":[0,57,0,0]}'>A <span about="#mwt2" class="reference" data-mw='{"name":"ref","attrs":{"name":"a"}}' id="cite_ref-a-1-0" rel="dc:references" typeof="mw:Extension/ref" data-parsoid='{"src":"&lt;ref name=\"a\" />","dsr":[2,18,16,0]}'><a href="#cite_note-a-1">[1]</a></span>
+B <span about="#mwt4" class="reference" data-mw='{"name":"ref","body":{"html":"bar"},"attrs":{"name":"b"}}' id="cite_ref-b-2-0" rel="dc:references" typeof="mw:Extension/ref" data-parsoid='{"src":"&lt;ref name=\"b\">bar&lt;/ref>","dsr":[21,44,14,6]}'><a href="#cite_note-b-2">[2]</a></span></p>
+
 
-<ol class="references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","body":{"extsrc":"<ref name=\"a\">foo</ref>\nThis should just get lost.","html":"\n<span about=\"#mwt8\" class=\"reference\" data-mw=\"{&amp;quot;name&amp;quot;:&amp;quot;ref&amp;quot;,&amp;quot;body&amp;quot;:{&amp;quot;html&amp;quot;:&amp;quot;foo&amp;quot;},&amp;quot;attrs&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;a&amp;quot;}}\" rel=\"dc:references\" typeof=\"mw:Extension/ref\"><a href=\"#cite_note-a-1\">[1]</a></span>\n"},"attrs":{}}'><li about="#cite_note-a-1" id="cite_note-a-1"><span rel="mw:referencedBy"><a href="#cite_ref-a-1-0">↑</a></span> foo</li><li about="#cite_note-b-2" id="cite_note-b-2"><span rel="mw:referencedBy"><a href="#cite_ref-b-2-0">↑</a></span> bar</li>
+<ol class="references" typeof="mw:Extension/references" about="#mwt6" data-parsoid='{"src":"&lt;references>\n&lt;ref name=\"a\">foo&lt;/ref>\nThis should just get lost.\n&lt;/references>","dsr":[46,123,2,2]}' data-mw='{"name":"references","body":{"extsrc":"&lt;ref name=\"a\">foo&lt;/ref>\nThis should just get lost.","html":"\n&lt;span about=\"#mwt8\" class=\"reference\" data-mw=&#39;{\"name\":\"ref\",\"body\":{\"html\":\"foo\"},\"attrs\":{\"name\":\"a\"}}&#39; rel=\"dc:references\" typeof=\"mw:Extension/ref\">&lt;a href=\"#cite_note-a-1\">[1]&lt;/a>&lt;/span>\n"},"attrs":{}}'>
+<li about="#cite_note-a-1" id="cite_note-a-1" data-parsoid="{}"><span rel="mw:referencedBy"><a href="#cite_ref-a-1-0">↑</a></span> foo</li>
+<li about="#cite_note-b-2" id="cite_note-b-2" data-parsoid="{}"><span rel="mw:referencedBy"><a href="#cite_ref-b-2-0">↑</a></span> bar</li>
 </ol>
 !!end
 
@@ -16703,16 +16801,17 @@ B <ref name="b" />
 <ref name="b">foo</ref>
 </references>
 !! result
-<p>A <span about="#mwt2" class="reference" data-mw='{"name":"ref","body":{"html":"foo bar for a"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-1">[1]</a></span>
-B <span about="#mwt4" class="reference" data-mw='{"name":"ref","attrs":{"name":"b"}}' id="cite_ref-b-2-0" rel="dc:references" typeof="mw:Extension/ref"><a href="#cite_note-b-2">[2]</a></span></p>
+<p data-parsoid='{"dsr":[0,45,0,0]}'>A <span about="#mwt2" class="reference" data-mw='{"name":"ref","body":{"html":"foo bar for a"},"attrs":{}}' id="cite_ref-1-0" rel="dc:references" typeof="mw:Extension/ref" data-parsoid='{"src":"&lt;ref>foo bar for a&lt;/ref>","dsr":[2,26,5,6]}'><a href="#cite_note-1">[1]</a></span>
+B <span about="#mwt4" class="reference" data-mw='{"name":"ref","attrs":{"name":"b"}}' id="cite_ref-b-2-0" rel="dc:references" typeof="mw:Extension/ref" data-parsoid='{"src":"&lt;ref name=\"b\" />","dsr":[29,45,16,0]}'><a href="#cite_note-b-2">[2]</a></span></p>
 
-<ol class="references" typeof="mw:Extension/references" about="#mwt6" data-mw='{"name":"references","attrs":{}}'>
-<li about="#cite_note-1" id="cite_note-1"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> foo bar for a</li>
-<li about="#cite_note-b-2" id="cite_note-b-2"><span rel="mw:referencedBy"><a href="#cite_ref-b-2-0">↑</a></span> </li>
-</ol>
 
-<ol class="references" typeof="mw:Extension/references" about="#mwt8" data-mw='{"name":"references","body":{"extsrc":"<ref name=\"b\">foo</ref>","html":"\n<span about=\"#mwt10\" class=\"reference\" data-mw=\"{&amp;quot;name&amp;quot;:&amp;quot;ref&amp;quot;,&amp;quot;body&amp;quot;:{&amp;quot;html&amp;quot;:&amp;quot;foo&amp;quot;},&amp;quot;attrs&amp;quot;:{&amp;quot;name&amp;quot;:&amp;quot;b&amp;quot;}}\" rel=\"dc:references\" typeof=\"mw:Extension/ref\"><a href=\"#cite_note-b-1\">[1]</a></span>\n"},"attrs":{}}'>
-<li about="#cite_note-b-1" id="cite_note-b-1"><span rel="mw:referencedBy">↑</span> foo</li>
+<ol class="references" typeof="mw:Extension/references" about="#mwt6" data-parsoid='{"src":"&lt;references />","dsr":[47,61,2,2]}' data-mw='{"name":"references","attrs":{}}'>
+<li about="#cite_note-1" id="cite_note-1" data-parsoid="{}"><span rel="mw:referencedBy"><a href="#cite_ref-1-0">↑</a></span> foo bar for a</li>
+<li about="#cite_note-b-2" id="cite_note-b-2" data-parsoid="{}"><span rel="mw:referencedBy"><a href="#cite_ref-b-2-0">↑</a></span> </li></ol>
+
+
+<ol class="references" typeof="mw:Extension/references" about="#mwt8" data-parsoid='{"src":"&lt;references>\n&lt;ref name=\"b\">foo&lt;/ref>\n&lt;/references>","dsr":[63,113,2,2]}' data-mw='{"name":"references","body":{"extsrc":"&lt;ref name=\"b\">foo&lt;/ref>","html":"\n&lt;span about=\"#mwt10\" class=\"reference\" data-mw=&#39;{\"name\":\"ref\",\"body\":{\"html\":\"foo\"},\"attrs\":{\"name\":\"b\"}}&#39; rel=\"dc:references\" typeof=\"mw:Extension/ref\">&lt;a href=\"#cite_note-b-1\">[1]&lt;/a>&lt;/span>\n"},"attrs":{}}'>
+<li about="#cite_note-b-1" id="cite_note-b-1" data-parsoid="{}"><span rel="mw:referencedBy">↑</span> foo</li>
 </ol>
 !! end
 
@@ -18356,6 +18455,63 @@ parsoid=html2wt
 </ul>
 !! end
 
+!! test
+Don't strip leading whitespace when handling indent-pre suppressing tags
+!! options
+parsoid=html2wt
+!! input
+{|
+  | indented row
+|}
+<blockquote>
+ '''This is very bold of you!'''
+
+{|
+|
+ indented cell (no pre-wrapping!)
+|}
+</blockquote>
+foo
+ <div>bar</div>
+!! result
+<table>
+  <tr><td> indented row</td></tr>
+</table>
+<blockquote><p>
+ <b>This is very bold of you!</b>
+</p>
+<table><tr><td>
+ indented cell (no pre-wrapping!)
+</td></tr></table>
+</blockquote>
+<p>foo</p>
+ <div>bar</div>
+!! end
+
+!! test
+Strip leading whitespace when handling indent-pre inducing tags
+!! options
+parsoid=html2wt
+!! input
+foo
+<span>bar</span>
+
+<span>foo2
+</span>bar2
+
+<div>foo</div>
+<span>bar</span>
+!! result
+<p>foo</p>
+ <span>bar</span>
+
+<span>foo2
+ </span>bar2
+
+<div>foo</div>
+ <span>bar</span>
+!! end
+
 # Wacky -- the leading newline in input is required because
 # that is what the serializer emits. To be fixed. Not fixing
 # the test because this test is required to test serialization of
@@ -18503,6 +18659,16 @@ parsoid=html2wt
 <p><i>A</i><b data-parsoid='{}'><i data-parsoid='{}'>B</i></b></p>
 !! end
 
+!!test
+5. Bug 54262: New entities
+!! options
+parsoid=html2wt
+!! input
+foo
+!! result
+<span typeof="mw:Entity">foo</span>
+!! end
+
 # -----------------------------------------------------------------
 # End of section for Parsoid-only html2wt tests for serialization
 # of new content
index fe3bc68..4b489a9 100644 (file)
@@ -36,3 +36,8 @@ http://commons.wikimedia.org/wiki/File:Animated_PNG_example_bouncing_beach_ball.
 Public Domain
 Holger Will
 
+Tux.svg
+https://commons.wikimedia.org/wiki/File:Tux.svg
+Larry Ewing, Simon Budig, Anja Gerwinski
+"The copyright holder of this file allows anyone to use it for any purpose, provided that the copyright holder is properly attributed. Redistribution, derivative work, commercial use, and all other use is permitted."
+
diff --git a/tests/phpunit/data/media/Tux.svg b/tests/phpunit/data/media/Tux.svg
new file mode 100644 (file)
index 0000000..3956107
--- /dev/null
@@ -0,0 +1,902 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>\r
+<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="100%" width="100%" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="0 0 349.46883 405.12272">\r
+ <title>Tux</title>\r
+ <desc>For more information see: http://commons.wikimedia.org/wiki/Image:Tux.svg</desc>\r
+ <radialGradient id="ag" gradientUnits="userSpaceOnUse" cy="-551.04" cx="274.822" gradientTransform="matrix(.5671 0 0 -.2835 81.263 201.645)" r="165.384">\r
+  <stop stop-opacity=".502" offset="0"/>\r
+  <stop stop-opacity="0" offset="1"/>\r
+ </radialGradient>\r
+ <path fill="url(#ag)" d="m330.892 357.885c0 25.898-41.989 46.893-93.785 46.893-51.795 0-93.784-20.994-93.784-46.893s41.989-46.893 93.784-46.893c51.795 0.001 93.785 20.995 93.785 46.893z"/>\r
+ <radialGradient id="ak" gradientUnits="userSpaceOnUse" cy="-551.042" cx="268.794" gradientTransform="matrix(.5823 0 0 -.2835 -61.6052 201.14)" r="165.383">\r
+  <stop stop-opacity=".502" offset="0"/>\r
+  <stop stop-opacity="0" offset="1"/>\r
+ </radialGradient>\r
+ <path fill="url(#ak)" d="m191.223 357.381c0 25.897-43.117 46.892-96.306 46.892-53.188 0-96.305-20.994-96.305-46.892s43.117-46.893 96.305-46.893c53.188 0.001 96.306 20.995 96.306 46.893z"/>\r
+ <g transform="translate(8.99996 9.00046)">\r
+  <path d="m292.327 256.606c-4.752 19.584-28.872 60.48-41.688 78.48-12.815 18.072-11.231 34.344-34.92 28.008-23.616-6.336-30.24-5.184-54.647-3.744-24.265 1.439-19.009-0.721-34.2 6.12-15.12 6.84-65.88-82.944-69.984-99.647-4.031-16.705-5.976-14.689 4.536-32.761 10.513-18.071 12.024-35.928 25.92-57.816 13.896-21.96 29.952-33.12 28.8-49.896-4.535-62.28-8.136-93.384 19.513-107.784 26.352-13.68 48.384-5.544 57.096-0.864 3.744 2.016 11.376 5.904 17.064 12.744 5.688 6.696 10.8 16.848 13.68 29.664 5.904 25.704-2.448 17.208 4.248 46.656 6.624 29.375 20.088 43.775 36.504 67.031 16.414 23.257 33.55 61.633 28.078 83.809z"/>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path fill="#666" d="m148.47 94.049c4.319-1.728 3.592-1.958 6.472-8.222 2.304-4.824 4.328-6.898 4.256-14.242 0-7.2-2.232-9.648-5.616-14.328-3.24-4.464-8.424-4.68-11.664-4.104-1.872 0.288-4.319 2.664-5.976 6.192-1.08 2.376-1.944 5.4-2.017 8.568-0.216 8.496 0.505 11.736 2.448 17.496 2.305 6.769 7.921 10.297 12.097 8.64z"/>\r
+   <path fill="#6d6d6d" d="m148.47 94.023c4.293-1.717 3.563-1.954 6.425-8.178 2.289-4.793 4.312-6.861 4.271-14.164 0.027-7.152-2.162-9.702-5.488-14.201-3.296-4.345-8.376-4.509-11.593-3.953-1.916 0.283-4.354 2.569-6.038 5.968-1.159 2.31-2.016 5.353-2.087 8.535-0.212 8.438 0.547 11.691 2.46 17.417 2.268 6.731 7.901 10.221 12.05 8.576z"/>\r
+   <path fill="#757575" d="m148.471 93.996c4.264-1.706 3.533-1.95 6.377-8.133 2.273-4.762 4.296-6.823 4.288-14.085 0.053-7.105-2.093-9.756-5.363-14.075-3.35-4.225-8.327-4.338-11.52-3.801-1.961 0.278-4.389 2.474-6.099 5.744-1.242 2.245-2.089 5.305-2.16 8.501-0.207 8.38 0.591 11.647 2.473 17.34 2.231 6.691 7.881 10.144 12.004 8.509z"/>\r
+   <path fill="#7c7c7c" d="m148.471 93.969c4.235-1.694 3.506-1.946 6.329-8.089 2.26-4.731 4.28-6.786 4.304-14.006 0.081-7.058-2.021-9.811-5.236-13.948-3.403-4.105-8.278-4.167-11.446-3.649-2.006 0.273-4.424 2.379-6.16 5.519-1.322 2.179-2.161 5.257-2.232 8.468-0.202 8.323 0.636 11.603 2.486 17.261 2.191 6.654 7.859 10.068 11.955 8.444z"/>\r
+   <path fill="#848484" d="m148.471 93.943c4.209-1.684 3.477-1.942 6.282-8.045 2.245-4.7 4.266-6.749 4.319-13.928 0.107-7.01-1.95-9.864-5.109-13.821-3.458-3.985-8.23-3.996-11.375-3.498-2.049 0.268-4.458 2.284-6.222 5.295-1.403 2.114-2.233 5.21-2.303 8.435-0.198 8.265 0.679 11.559 2.498 17.183 2.156 6.615 7.842 9.992 11.91 8.379z"/>\r
+   <path fill="#8c8c8c" d="m148.471 93.916c4.181-1.672 3.448-1.938 6.235-8 2.23-4.668 4.249-6.711 4.335-13.85 0.134-6.962-1.88-9.918-4.982-13.695-3.513-3.865-8.183-3.825-11.303-3.347-2.094 0.263-4.492 2.189-6.283 5.07-1.484 2.049-2.306 5.163-2.375 8.401-0.193 8.207 0.723 11.515 2.511 17.105 2.118 6.58 7.821 9.919 11.862 8.316z"/>\r
+   <path fill="#939393" d="m148.472 93.889c4.152-1.661 3.419-1.934 6.188-7.956 2.215-4.638 4.233-6.674 4.35-13.771 0.161-6.915-1.809-9.972-4.854-13.568-3.567-3.746-8.134-3.654-11.23-3.195-2.138 0.259-4.527 2.094-6.345 4.847-1.564 1.983-2.378 5.115-2.447 8.368-0.188 8.149 0.767 11.47 2.523 17.026 2.079 6.54 7.8 9.841 11.815 8.249z"/>\r
+   <path fill="#9b9b9b" d="m148.472 93.863c4.125-1.65 3.391-1.93 6.141-7.912 2.2-4.607 4.217-6.637 4.366-13.693 0.188-6.868-1.739-10.026-4.729-13.441-3.621-3.626-8.085-3.484-11.157-3.044-2.183 0.253-4.562 1.999-6.406 4.622-1.646 1.918-2.45 5.068-2.52 8.335-0.185 8.091 0.811 11.426 2.535 16.948 2.044 6.502 7.782 9.766 11.77 8.185z"/>\r
+   <path fill="#a3a3a3" d="m148.472 93.836c4.097-1.639 3.361-1.926 6.094-7.867 2.185-4.576 4.201-6.599 4.382-13.614 0.214-6.82-1.669-10.081-4.603-13.315-3.676-3.506-8.036-3.313-11.084-2.893-2.229 0.249-4.598 1.904-6.47 4.398-1.726 1.852-2.521 5.021-2.591 8.301-0.18 8.034 0.854 11.382 2.548 16.87 2.008 6.465 7.763 9.691 11.724 8.12z"/>\r
+   <path fill="#aaa" d="m148.472 93.809c4.069-1.628 3.334-1.922 6.047-7.823 2.17-4.544 4.185-6.562 4.396-13.536 0.242-6.772-1.597-10.134-4.475-13.188-3.73-3.387-7.989-3.142-11.013-2.741-2.271 0.243-4.632 1.809-6.53 4.173-1.808 1.787-2.594 4.974-2.662 8.268-0.176 7.976 0.897 11.337 2.56 16.792 1.97 6.427 7.743 9.615 11.677 8.055z"/>\r
+   <path fill="#b2b2b2" d="m148.473 93.782c4.041-1.617 3.304-1.918 5.999-7.778 2.154-4.514 4.169-6.524 4.412-13.458 0.269-6.725-1.526-10.188-4.349-13.062-3.784-3.267-7.939-2.971-10.939-2.589-2.316 0.238-4.666 1.714-6.592 3.949-1.888 1.721-2.667 4.926-2.734 8.234-0.171 7.918 0.941 11.293 2.572 16.713 1.933 6.391 7.723 9.541 11.631 7.991z"/>\r
+   <path fill="#bababa" d="m148.473 93.756c4.014-1.606 3.275-1.914 5.951-7.734 2.141-4.482 4.153-6.487 4.43-13.379 0.295-6.678-1.457-10.243-4.223-12.935-3.839-3.147-7.892-2.8-10.867-2.438-2.36 0.233-4.701 1.619-6.653 3.725-1.969 1.656-2.739 4.879-2.806 8.201-0.167 7.86 0.984 11.249 2.585 16.636 1.895 6.35 7.702 9.462 11.583 7.924z"/>\r
+   <path fill="#c1c1c1" d="m148.473 93.729c3.985-1.595 3.247-1.91 5.904-7.69 2.125-4.451 4.138-6.45 4.445-13.3 0.321-6.63-1.387-10.297-4.096-12.808-3.894-3.028-7.844-2.629-10.795-2.287-2.405 0.229-4.735 1.524-6.716 3.5-2.049 1.59-2.811 4.831-2.878 8.167-0.161 7.802 1.029 11.205 2.599 16.557 1.859 6.314 7.683 9.389 11.537 7.861z"/>\r
+   <path fill="#c9c9c9" d="m148.473 93.702c3.958-1.583 3.219-1.906 5.857-7.646 2.11-4.42 4.121-6.412 4.46-13.222 0.35-6.583-1.315-10.351-3.969-12.682-3.947-2.908-7.794-2.458-10.722-2.135-2.45 0.224-4.771 1.429-6.777 3.276-2.13 1.525-2.883 4.784-2.95 8.135-0.157 7.745 1.073 11.16 2.611 16.479 1.821 6.276 7.663 9.313 11.49 7.795z"/>\r
+   <path fill="#d1d1d1" d="m148.474 93.676c3.93-1.573 3.188-1.902 5.809-7.601 2.097-4.389 4.107-6.375 4.477-13.144 0.375-6.535-1.245-10.404-3.842-12.555-4.002-2.788-7.747-2.287-10.65-1.984-2.493 0.219-4.805 1.334-6.837 3.052-2.213 1.459-2.957 4.736-3.022 8.101-0.153 7.687 1.116 11.116 2.623 16.401 1.782 6.237 7.642 9.237 11.442 7.73z"/>\r
+   <path fill="#d8d8d8" d="m148.474 93.649c3.901-1.562 3.16-1.898 5.762-7.557 2.082-4.358 4.091-6.338 4.493-13.065 0.401-6.487-1.176-10.458-3.716-12.428-4.057-2.668-7.698-2.116-10.578-1.832-2.538 0.214-4.839 1.239-6.899 2.827-2.292 1.394-3.029 4.689-3.094 8.068-0.148 7.629 1.16 11.072 2.636 16.322 1.746 6.2 7.623 9.161 11.396 7.665z"/>\r
+   <path fill="#e0e0e0" d="m148.474 93.622c3.875-1.55 3.132-1.894 5.715-7.512 2.066-4.327 4.075-6.3 4.508-12.987 0.429-6.44-1.104-10.513-3.588-12.302-4.111-2.549-7.65-1.945-10.506-1.681-2.582 0.209-4.874 1.144-6.961 2.604-2.373 1.328-3.102 4.642-3.165 8.034-0.145 7.571 1.204 11.027 2.647 16.244 1.709 6.162 7.604 9.086 11.35 7.6z"/>\r
+   <path fill="#e8e8e8" d="m148.474 93.596c3.847-1.54 3.104-1.89 5.668-7.468 2.052-4.296 4.059-6.263 4.523-12.908 0.456-6.393-1.034-10.567-3.462-12.175-4.165-2.429-7.601-1.774-10.433-1.529-2.627 0.204-4.908 1.049-7.023 2.379-2.453 1.263-3.173 4.594-3.236 8.001-0.141 7.514 1.247 10.983 2.659 16.166 1.673 6.123 7.585 9.008 11.304 7.534z"/>\r
+   <path fill="#efefef" d="m148.475 93.569c3.817-1.528 3.073-1.886 5.62-7.424 2.036-4.265 4.043-6.226 4.539-12.83 0.482-6.345-0.964-10.621-3.336-12.048-4.219-2.31-7.552-1.604-10.359-1.378-2.672 0.199-4.943 0.954-7.084 2.155-2.535 1.197-3.246 4.546-3.311 7.967-0.135 7.456 1.292 10.939 2.673 16.087 1.636 6.087 7.565 8.935 11.258 7.471z"/>\r
+   <path fill="#f7f7f7" d="m148.475 93.542c3.791-1.517 3.046-1.882 5.572-7.379 2.022-4.234 4.027-6.188 4.556-12.751 0.51-6.297-0.894-10.675-3.208-11.921-4.274-2.19-7.505-1.433-10.289-1.227-2.715 0.194-4.978 0.859-7.146 1.93-2.614 1.132-3.317 4.5-3.381 7.935-0.131 7.398 1.335 10.895 2.686 16.009 1.597 6.047 7.544 8.858 11.21 7.404z"/>\r
+   <path fill="#fff" d="m148.475 93.516c3.763-1.506 3.017-1.878 5.525-7.335 2.007-4.203 4.012-6.151 4.571-12.673 0.536-6.25-0.823-10.729-3.082-11.795-4.328-2.07-7.456-1.262-10.216-1.075-2.76 0.189-5.012 0.764-7.207 1.706-2.696 1.066-3.39 4.452-3.453 7.901-0.126 7.34 1.379 10.85 2.698 15.931 1.561 6.01 7.525 8.782 11.164 7.34z"/>\r
+  </g>\r
+  <path d="m132.033 74.7465c2.16 0 4.896 1.44 6.191 3.384 1.368 1.944 2.376 4.68 2.376 7.776 0 4.608-0.504 9.72-3.239 11.304-0.864 0.504-2.736 0.936-3.816 0.936-2.448 0-2.664-1.584-4.968-3.96-0.792-0.864-3.168-5.04-3.168-8.496 0-2.16-0.504-5.256 1.368-7.992 1.296-2.016 2.952-2.952 5.256-2.952z"/>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path d="m143.862 68.608c0.844-1.305 4.222-0.69 5.45 1.996 1.229 2.687 0.998 8.522 0.153 8.829-2.226 0.691-1.535-2.534-3.454-5.451-1.919-2.762-2.994-4.067-2.149-5.374z"/>\r
+   <path fill="#070707" d="m143.916 68.664c0.833-1.289 4.169-0.681 5.381 1.971 1.215 2.653 0.985 8.414 0.152 8.717-2.198 0.682-1.516-2.502-3.411-5.382-1.895-2.728-2.956-4.017-2.122-5.306z"/>\r
+   <path fill="#0f0f0f" d="m143.97 68.719c0.822-1.272 4.114-0.673 5.312 1.945 1.198 2.619 0.973 8.306 0.15 8.605-2.169 0.673-1.497-2.47-3.367-5.313-1.871-2.692-2.918-3.964-2.095-5.237z"/>\r
+   <path fill="#161616" d="m144.024 68.774c0.812-1.255 4.062-0.664 5.243 1.92 1.182 2.585 0.96 8.198 0.147 8.493-2.141 0.665-1.477-2.438-3.323-5.244-1.846-2.657-2.88-3.913-2.067-5.169z"/>\r
+   <path fill="#1e1e1e" d="m144.078 68.829c0.801-1.239 4.008-0.655 5.174 1.895 1.167 2.551 0.947 8.09 0.146 8.381-2.113 0.656-1.458-2.405-3.28-5.174-1.821-2.623-2.842-3.863-2.04-5.102z"/>\r
+   <path fill="#262626" d="m144.132 68.884c0.791-1.222 3.955-0.646 5.105 1.87 1.151 2.517 0.935 7.982 0.144 8.27-2.085 0.647-1.438-2.374-3.235-5.105-1.798-2.589-2.805-3.812-2.014-5.035z"/>\r
+   <path fill="#2d2d2d" d="m144.186 68.939c0.779-1.206 3.9-0.638 5.036 1.844 1.135 2.483 0.922 7.874 0.142 8.158-2.057 0.639-1.419-2.341-3.192-5.037-1.773-2.552-2.766-3.758-1.986-4.965z"/>\r
+   <path fill="#353535" d="m144.24 68.994c0.769-1.189 3.848-0.629 4.967 1.819 1.12 2.449 0.909 7.766 0.141 8.046-2.028 0.629-1.399-2.31-3.148-4.967-1.75-2.518-2.73-3.708-1.96-4.898z"/>\r
+   <path fill="#3d3d3d" d="m144.294 69.049c0.76-1.172 3.794-0.621 4.898 1.793 1.104 2.415 0.896 7.658 0.138 7.934-2 0.621-1.38-2.277-3.104-4.898-1.725-2.482-2.691-3.655-1.932-4.829z"/>\r
+   <path fill="#444" d="m144.348 69.104c0.748-1.156 3.74-0.612 4.829 1.768 1.088 2.38 0.884 7.55 0.136 7.822-1.973 0.612-1.36-2.245-3.062-4.829-1.699-2.448-2.651-3.604-1.903-4.761z"/>\r
+   <path fill="#4c4c4c" d="m144.402 69.16c0.737-1.14 3.687-0.603 4.76 1.743 1.073 2.347 0.871 7.442 0.134 7.71-1.943 0.604-1.341-2.213-3.017-4.76-1.676-2.414-2.614-3.554-1.877-4.693z"/>\r
+   <path fill="#545454" d="m144.456 69.215c0.727-1.123 3.634-0.595 4.691 1.717 1.057 2.313 0.857 7.334 0.132 7.598-1.916 0.595-1.321-2.181-2.973-4.691-1.652-2.378-2.577-3.501-1.85-4.624z"/>\r
+   <path fill="#5b5b5b" d="m144.51 69.27c0.717-1.106 3.58-0.585 4.622 1.692 1.041 2.278 0.847 7.226 0.131 7.486-1.888 0.586-1.303-2.149-2.93-4.622-1.628-2.343-2.539-3.45-1.823-4.556z"/>\r
+   <path fill="#636363" d="m144.564 69.325c0.705-1.09 3.526-0.577 4.553 1.667 1.026 2.245 0.833 7.118 0.128 7.375-1.858 0.577-1.282-2.117-2.885-4.553-1.604-2.309-2.501-3.399-1.796-4.489z"/>\r
+   <path fill="#6b6b6b" d="m144.618 69.38c0.694-1.073 3.473-0.568 4.483 1.642 1.011 2.21 0.82 7.01 0.127 7.263-1.831 0.568-1.264-2.084-2.842-4.484-1.578-2.274-2.462-3.347-1.768-4.421z"/>\r
+   <path fill="#727272" d="m144.672 69.435c0.685-1.057 3.42-0.56 4.414 1.617 0.995 2.176 0.81 6.902 0.125 7.15-1.803 0.56-1.243-2.053-2.798-4.415-1.554-2.238-2.425-3.295-1.741-4.352z"/>\r
+   <path fill="#7a7a7a" d="m144.726 69.49c0.673-1.041 3.365-0.551 4.345 1.591 0.979 2.143 0.796 6.794 0.123 7.039-1.775 0.551-1.224-2.021-2.754-4.346-1.53-2.203-2.387-3.244-1.714-4.284z"/>\r
+   <path fill="#828282" d="m144.78 69.545c0.662-1.023 3.313-0.542 4.276 1.566 0.964 2.108 0.782 6.686 0.121 6.926-1.746 0.542-1.204-1.988-2.711-4.276-1.505-2.167-2.348-3.192-1.686-4.216z"/>\r
+   <path fill="#898989" d="m144.834 69.6c0.652-1.007 3.259-0.533 4.207 1.541s0.771 6.578 0.119 6.815c-1.718 0.534-1.185-1.956-2.666-4.207-1.482-2.134-2.311-3.142-1.66-4.149z"/>\r
+   <path fill="#919191" d="m144.888 69.655c0.641-0.99 3.206-0.524 4.138 1.516 0.933 2.04 0.758 6.47 0.117 6.703-1.69 0.525-1.165-1.924-2.623-4.138-1.457-2.098-2.273-3.09-1.632-4.081z"/>\r
+   <path fill="#999" d="m144.942 69.71c0.63-0.974 3.152-0.516 4.069 1.49s0.744 6.362 0.114 6.591c-1.662 0.516-1.146-1.892-2.579-4.069-1.432-2.062-2.234-3.037-1.604-4.012z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path fill="#666" d="m193.11 94.985c10.8-1.152 14.616-5.328 16.56-12.6 1.729-6.48 1.801-13.68-3.023-22.104-4.536-8.063-7.128-9.36-13.681-9.864-10.079-0.864-14.832 6.192-17.063 11.232-2.376 5.472-1.872 4.68-1.729 11.592 0.145 7.272 4.245 9.299 6.766 13.835 2.519 4.465 10.946 7.982 12.17 7.909z"/>\r
+   <path fill="#6d6d6d" d="m193.115 94.944c10.759-1.131 14.618-5.354 16.515-12.569 1.701-6.525 1.785-13.686-3.002-21.912-4.434-7.797-7.038-9.081-13.512-9.581-10.049-0.861-14.941 5.873-17.181 10.874-2.304 5.28-1.878 4.718-1.726 11.539 0.16 7.268 4.268 9.223 6.784 13.76 2.521 4.475 10.898 7.962 12.122 7.889z"/>\r
+   <path fill="#757575" d="m193.12 94.902c10.718-1.11 14.62-5.379 16.469-12.538 1.676-6.57 1.771-13.692-2.979-21.721-4.331-7.53-6.947-8.801-13.344-9.297-10.018-0.858-15.05 5.553-17.298 10.516-2.229 5.087-1.885 4.757-1.722 11.487 0.176 7.264 4.289 9.146 6.803 13.686 2.52 4.485 10.848 7.942 12.071 7.867z"/>\r
+   <path fill="#7c7c7c" d="m193.126 94.861c10.675-1.09 14.621-5.405 16.423-12.507 1.648-6.616 1.756-13.698-2.958-21.529-4.229-7.263-6.856-8.522-13.176-9.014-9.985-0.854-15.158 5.234-17.414 10.158-2.156 4.895-1.891 4.795-1.719 11.434 0.193 7.26 4.31 9.07 6.822 13.611 2.52 4.495 10.798 7.922 12.022 7.847z"/>\r
+   <path fill="#848484" d="m193.131 94.82c10.635-1.069 14.623-5.431 16.377-12.476 1.622-6.661 1.741-13.704-2.936-21.337-4.126-6.996-6.767-8.242-13.008-8.73-9.955-0.852-15.267 4.915-17.53 9.8-2.084 4.703-1.896 4.833-1.716 11.38 0.209 7.256 4.332 8.995 6.841 13.537 2.52 4.505 10.748 7.902 11.972 7.826z"/>\r
+   <path fill="#8c8c8c" d="m193.136 94.778c10.593-1.048 14.625-5.457 16.331-12.445 1.596-6.706 1.726-13.709-2.913-21.145-4.025-6.729-6.678-7.963-12.841-8.447-9.924-0.848-15.375 4.595-17.647 9.441-2.01 4.51-1.903 4.872-1.712 11.328 0.225 7.251 4.354 8.918 6.858 13.462 2.521 4.517 10.7 7.883 11.924 7.806z"/>\r
+   <path fill="#939393" d="m193.141 94.737c10.552-1.027 14.627-5.482 16.286-12.414 1.568-6.751 1.711-13.715-2.893-20.954-3.922-6.462-6.586-7.683-12.672-8.163-9.892-0.845-15.483 4.276-17.764 9.083-1.938 4.318-1.909 4.91-1.709 11.275 0.24 7.247 4.375 8.842 6.878 13.387 2.521 4.528 10.651 7.863 11.874 7.786z"/>\r
+   <path fill="#9b9b9b" d="m193.146 94.695c10.51-1.007 14.63-5.508 16.241-12.382 1.542-6.796 1.694-13.721-2.87-20.762-3.82-6.195-6.496-7.404-12.504-7.879-9.861-0.842-15.592 3.956-17.882 8.725-1.863 4.126-1.915 4.949-1.706 11.223 0.258 7.243 4.397 8.766 6.897 13.313 2.521 4.535 10.601 7.841 11.824 7.762z"/>\r
+   <path fill="#a3a3a3" d="m193.151 94.654c10.469-0.986 14.632-5.534 16.196-12.351 1.515-6.842 1.68-13.727-2.85-20.57-3.717-5.928-6.405-7.125-12.335-7.596-9.83-0.839-15.7 3.637-17.998 8.367-1.791 3.933-1.922 4.987-1.703 11.169 0.273 7.239 4.419 8.689 6.916 13.238 2.521 4.547 10.551 7.822 11.774 7.743z"/>\r
+   <path fill="#aaa" d="m193.157 94.612c10.427-0.965 14.633-5.56 16.149-12.32 1.488-6.887 1.666-13.733-2.826-20.379-3.615-5.661-6.316-6.845-12.168-7.313-9.799-0.835-15.809 3.317-18.114 8.009-1.718 3.741-1.928 5.025-1.7 11.117 0.29 7.235 4.44 8.613 6.936 13.163 2.519 4.558 10.499 7.804 11.723 7.723z"/>\r
+   <path fill="#b2b2b2" d="m193.162 94.571c10.386-0.944 14.635-5.585 16.104-12.289 1.462-6.932 1.649-13.739-2.806-20.188-3.512-5.394-6.225-6.565-11.999-7.029-9.768-0.833-15.917 2.998-18.23 7.651-1.646 3.549-1.935 5.064-1.697 11.064 0.306 7.231 4.462 8.537 6.954 13.088 2.52 4.569 10.451 7.784 11.674 7.703z"/>\r
+   <path fill="#bababa" d="m193.167 94.529c10.345-0.923 14.638-5.611 16.059-12.258 1.436-6.977 1.636-13.744-2.782-19.995-3.41-5.127-6.135-6.286-11.832-6.746-9.736-0.829-16.025 2.679-18.347 7.293-1.572 3.356-1.941 5.103-1.694 11.011 0.322 7.227 4.484 8.461 6.973 13.014 2.519 4.579 10.4 7.764 11.623 7.681z"/>\r
+   <path fill="#c1c1c1" d="m193.172 94.488c10.304-0.903 14.64-5.637 16.014-12.227 1.409-7.022 1.62-13.75-2.762-19.804-3.308-4.86-6.044-6.006-11.662-6.462-9.705-0.826-16.135 2.359-18.466 6.935-1.498 3.164-1.945 5.141-1.689 10.958 0.338 7.223 4.506 8.385 6.991 12.939 2.519 4.59 10.351 7.744 11.574 7.661z"/>\r
+   <path fill="#c9c9c9" d="m193.177 94.447c10.262-0.882 14.641-5.663 15.967-12.196 1.383-7.068 1.605-13.756-2.738-19.612-3.206-4.593-5.954-5.727-11.496-6.179-9.673-0.823-16.242 2.04-18.581 6.577-1.425 2.972-1.952 5.179-1.687 10.906 0.354 7.219 4.526 8.308 7.01 12.865 2.52 4.598 10.302 7.723 11.525 7.639z"/>\r
+   <path fill="#d1d1d1" d="m193.182 94.405c10.221-0.861 14.643-5.688 15.922-12.165 1.355-7.113 1.591-13.762-2.717-19.42-3.104-4.326-5.864-5.448-11.327-5.895-9.644-0.82-16.352 1.721-18.698 6.219-1.353 2.779-1.959 5.217-1.684 10.853 0.369 7.214 4.549 8.232 7.028 12.79 2.521 4.609 10.254 7.703 11.476 7.618z"/>\r
+   <path fill="#d8d8d8" d="m193.187 94.364c10.179-0.841 14.645-5.714 15.876-12.133 1.33-7.158 1.576-13.768-2.694-19.229-3.001-4.059-5.773-5.168-11.16-5.612-9.61-0.817-16.459 1.401-18.813 5.861-1.279 2.586-1.965 5.256-1.682 10.8 0.387 7.21 4.571 8.156 7.049 12.715 2.519 4.619 10.202 7.684 11.424 7.598z"/>\r
+   <path fill="#e0e0e0" d="m193.193 94.322c10.137-0.82 14.646-5.74 15.83-12.103 1.303-7.203 1.561-13.773-2.673-19.037-2.898-3.792-5.684-4.889-10.991-5.328-9.58-0.813-16.568 1.082-18.931 5.502-1.206 2.395-1.972 5.294-1.679 10.747 0.403 7.207 4.592 8.08 7.067 12.641 2.521 4.631 10.154 7.666 11.377 7.578z"/>\r
+   <path fill="#e8e8e8" d="m193.198 94.281c10.096-0.799 14.648-5.766 15.785-12.071 1.275-7.249 1.545-13.779-2.651-18.845-2.796-3.525-5.593-4.609-10.823-5.044-9.549-0.81-16.677 0.762-19.048 5.145-1.133 2.202-1.978 5.333-1.675 10.694 0.419 7.202 4.614 8.003 7.086 12.566 2.52 4.638 10.103 7.643 11.326 7.555z"/>\r
+   <path fill="#efefef" d="m193.203 94.239c10.055-0.778 14.65-5.792 15.739-12.04 1.25-7.293 1.531-13.785-2.629-18.653-2.694-3.258-5.502-4.33-10.655-4.761-9.517-0.807-16.785 0.443-19.165 4.786-1.059 2.01-1.983 5.372-1.671 10.642 0.435 7.198 4.636 7.928 7.104 12.492 2.52 4.649 10.055 7.624 11.277 7.534z"/>\r
+   <path fill="#f7f7f7" d="m193.208 94.198c10.014-0.757 14.652-5.817 15.694-12.009 1.223-7.339 1.516-13.792-2.607-18.462-2.592-2.991-5.413-4.05-10.486-4.478-9.487-0.804-16.895 0.124-19.282 4.428-0.986 1.817-1.989 5.41-1.668 10.589 0.451 7.194 4.657 7.851 7.123 12.417 2.519 4.661 10.004 7.605 11.226 7.515z"/>\r
+   <path fill="#fff" d="m193.213 94.156c9.973-0.737 14.654-5.843 15.648-11.978 1.197-7.384 1.501-13.797-2.585-18.27-2.489-2.724-5.322-3.771-10.319-4.194-9.455-0.801-17.002-0.196-19.397 4.07-0.913 1.625-1.996 5.448-1.665 10.536 0.467 7.19 4.679 7.775 7.142 12.342 2.519 4.671 9.954 7.586 11.176 7.494z"/>\r
+  </g>\r
+  <path d="m179.841 74.4585c5.4 0 8.568 4.824 9.648 11.016 0.432 2.808-0.216 6.048-1.944 8.28-1.944 2.592-5.4 4.176-8.208 4.176-2.664 0-5.688 0.432-7.271-1.728-1.584-2.232-1.944-7.2-1.944-10.728 0-3.96 1.152-6.768 3.168-9 1.511-1.657 4.247-2.016 6.551-2.016z"/>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path d="m192.591 66.68c0.98-0.653 2.612 0 4.489 2.122 2.039 2.285 2.938 4.08 0.489 5.385-1.877 0.98-2.448-1.958-3.59-3.182-1.795-1.959-3.346-3.02-1.388-4.325z"/>\r
+   <path fill="#070707" d="m192.631 66.738c0.96-0.649 2.573 0 4.423 2.09 2.009 2.251 2.864 4.02 0.481 5.305-1.837 0.977-2.403-1.929-3.525-3.135-1.768-1.925-3.296-2.965-1.379-4.26z"/>\r
+   <path fill="#0f0f0f" d="m192.671 66.797c0.939-0.645 2.534 0 4.356 2.059 1.978 2.217 2.792 3.958 0.474 5.225-1.798 0.974-2.357-1.9-3.46-3.087-1.742-1.895-3.247-2.913-1.37-4.197z"/>\r
+   <path fill="#161616" d="m192.711 66.855c0.919-0.641 2.495 0 4.289 2.027 1.948 2.184 2.721 3.898 0.467 5.146-1.759 0.971-2.313-1.871-3.396-3.041-1.715-1.861-3.197-2.858-1.36-4.132z"/>\r
+   <path fill="#1e1e1e" d="m192.751 66.914c0.899-0.637 2.457 0 4.223 1.996 1.918 2.149 2.647 3.838 0.46 5.065-1.72 0.968-2.269-1.842-3.331-2.993-1.689-1.83-3.148-2.805-1.352-4.068z"/>\r
+   <path fill="#262626" d="m192.791 66.973c0.878-0.633 2.418 0 4.155 1.964 1.888 2.116 2.576 3.777 0.453 4.986-1.68 0.965-2.224-1.813-3.267-2.946-1.661-1.798-3.097-2.752-1.341-4.004z"/>\r
+   <path fill="#2d2d2d" d="m192.831 67.031c0.858-0.629 2.379 0 4.089 1.933 1.857 2.082 2.503 3.717 0.445 4.906-1.641 0.961-2.178-1.784-3.201-2.898-1.636-1.767-3.048-2.7-1.333-3.941z"/>\r
+   <path fill="#353535" d="m192.87 67.09c0.838-0.625 2.341 0 4.023 1.902 1.827 2.047 2.431 3.656 0.438 4.826-1.601 0.958-2.133-1.755-3.137-2.852-1.608-1.735-2.998-2.646-1.324-3.876z"/>\r
+   <path fill="#3d3d3d" d="m192.91 67.148c0.818-0.621 2.302 0 3.956 1.87 1.797 2.014 2.359 3.596 0.431 4.746-1.562 0.956-2.088-1.726-3.071-2.804-1.583-1.702-2.95-2.592-1.316-3.812z"/>\r
+   <path fill="#444" d="m192.95 67.207c0.798-0.617 2.263 0 3.889 1.839 1.768 1.98 2.287 3.535 0.425 4.666-1.523 0.952-2.043-1.697-3.008-2.757-1.556-1.671-2.899-2.539-1.306-3.748z"/>\r
+   <path fill="#4c4c4c" d="m192.99 67.266c0.777-0.614 2.224 0 3.823 1.807 1.735 1.946 2.214 3.474 0.416 4.586-1.483 0.949-1.998-1.667-2.942-2.709-1.529-1.639-2.85-2.486-1.297-3.684z"/>\r
+   <path fill="#545454" d="m193.03 67.325c0.757-0.61 2.185 0 3.756 1.775 1.706 1.912 2.143 3.414 0.409 4.506-1.444 0.946-1.953-1.639-2.878-2.663-1.502-1.606-2.799-2.431-1.287-3.618z"/>\r
+   <path fill="#5b5b5b" d="m193.07 67.383c0.736-0.605 2.146 0 3.688 1.744 1.677 1.878 2.07 3.353 0.402 4.426-1.405 0.943-1.908-1.609-2.813-2.615-1.475-1.575-2.749-2.378-1.277-3.555z"/>\r
+   <path fill="#636363" d="m193.11 67.442c0.716-0.602 2.106 0 3.622 1.712 1.646 1.844 1.998 3.293 0.395 4.347-1.364 0.94-1.862-1.581-2.748-2.568-1.449-1.543-2.701-2.326-1.269-3.491z"/>\r
+   <path fill="#6b6b6b" d="m193.15 67.5c0.696-0.598 2.069 0 3.556 1.681 1.615 1.811 1.925 3.232 0.387 4.267-1.325 0.937-1.818-1.552-2.683-2.521-1.423-1.511-2.651-2.272-1.26-3.427z"/>\r
+   <path fill="#727272" d="m193.19 67.559c0.675-0.594 2.03 0 3.489 1.649 1.585 1.777 1.853 3.172 0.38 4.187-1.287 0.935-1.774-1.522-2.619-2.473-1.396-1.48-2.601-2.219-1.25-3.363z"/>\r
+   <path fill="#7a7a7a" d="m193.23 67.618c0.654-0.59 1.991 0 3.422 1.618 1.555 1.743 1.781 3.111 0.373 4.107-1.247 0.931-1.729-1.494-2.554-2.426-1.369-1.448-2.551-2.166-1.241-3.299z"/>\r
+   <path fill="#828282" d="m193.269 67.677c0.635-0.586 1.953 0 3.355 1.586 1.525 1.708 1.709 3.05 0.366 4.026-1.208 0.928-1.684-1.464-2.489-2.378-1.342-1.416-2.501-2.112-1.232-3.234z"/>\r
+   <path fill="#898989" d="m193.309 67.735c0.614-0.582 1.914 0 3.29 1.555 1.493 1.675 1.636 2.99 0.357 3.947-1.169 0.925-1.639-1.435-2.424-2.332-1.316-1.384-2.452-2.058-1.223-3.17z"/>\r
+   <path fill="#919191" d="m193.349 67.794c0.595-0.578 1.875 0 3.223 1.523 1.464 1.641 1.564 2.93 0.351 3.867-1.129 0.922-1.594-1.406-2.359-2.284-1.29-1.352-2.403-2.005-1.215-3.106z"/>\r
+   <path fill="#999" d="m193.389 67.853c0.573-0.574 1.836 0 3.155 1.492 1.435 1.607 1.492 2.869 0.345 3.787-1.091 0.919-1.55-1.377-2.295-2.237-1.263-1.32-2.353-1.953-1.205-3.042z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path d="m165.498 69.906c1.693-0.654 3.012-0.69 5.63 1.036 3.166 2.088 1.705 5.245-0.779 4.601-2.146-0.556-2.417-0.681-4.391-1.086-3.101-0.648-3.641-3.322-0.46-4.551z"/>\r
+   <path fill="#050505" d="m165.564 70.033c1.658-0.629 2.973-0.656 5.555 1.026 3.066 2.009 1.654 5.012-0.805 4.38-2.131-0.547-2.345-0.656-4.284-1.052-3.055-0.634-3.587-3.173-0.466-4.354z"/>\r
+   <path fill="#0a0a0a" d="m165.63 70.16c1.623-0.604 2.935-0.622 5.481 1.015 2.965 1.93 1.602 4.779-0.83 4.159-2.119-0.539-2.274-0.63-4.179-1.018-3.009-0.618-3.533-3.022-0.472-4.156z"/>\r
+   <path fill="#0f0f0f" d="m165.696 70.287c1.587-0.579 2.895-0.587 5.406 1.005 2.864 1.851 1.551 4.546-0.855 3.938-2.105-0.53-2.203-0.605-4.073-0.983-2.963-0.604-3.48-2.873-0.478-3.96z"/>\r
+   <path fill="#141414" d="m165.761 70.413c1.553-0.553 2.856-0.553 5.331 0.995 2.766 1.772 1.5 4.313-0.88 3.717-2.092-0.521-2.131-0.58-3.967-0.949-2.916-0.588-3.425-2.723-0.484-3.763z"/>\r
+   <path fill="#191919" d="m165.827 70.54c1.519-0.528 2.818-0.519 5.258 0.984 2.664 1.693 1.448 4.079-0.905 3.497-2.079-0.513-2.06-0.554-3.861-0.915-2.873-0.573-3.373-2.573-0.492-3.566z"/>\r
+   <path fill="#1e1e1e" d="m165.893 70.667c1.482-0.503 2.778-0.484 5.183 0.974 2.564 1.614 1.397 3.846-0.93 3.276-2.067-0.504-1.989-0.529-3.756-0.88-2.826-0.559-3.319-2.425-0.497-3.37z"/>\r
+   <path fill="#232323" d="m165.959 70.793c1.447-0.478 2.74-0.45 5.108 0.964 2.464 1.535 1.345 3.613-0.955 3.055-2.053-0.496-1.917-0.503-3.651-0.846-2.779-0.543-3.264-2.274-0.502-3.173z"/>\r
+   <path fill="#282828" d="m166.025 70.92c1.412-0.453 2.701-0.416 5.034 0.954 2.362 1.456 1.293 3.38-0.981 2.834-2.04-0.487-1.845-0.478-3.545-0.812-2.733-0.528-3.21-2.125-0.508-2.976z"/>\r
+   <path fill="#2d2d2d" d="m166.09 71.047c1.378-0.428 2.663-0.382 4.96 0.943 2.264 1.377 1.242 3.146-1.006 2.613-2.026-0.478-1.773-0.453-3.438-0.777-2.688-0.513-3.158-1.974-0.516-2.779z"/>\r
+   <path fill="#333" d="m166.156 71.173c1.343-0.402 2.624-0.347 4.885 0.933 2.163 1.298 1.191 2.914-1.029 2.392-2.015-0.47-1.703-0.428-3.334-0.743-2.642-0.498-3.104-1.824-0.522-2.582z"/>\r
+   <path fill="#383838" d="m166.222 71.3c1.307-0.377 2.585-0.313 4.81 0.922 2.063 1.219 1.14 2.681-1.055 2.171-2.001-0.461-1.631-0.402-3.229-0.708-2.594-0.483-3.048-1.674-0.526-2.385z"/>\r
+   <path fill="#3d3d3d" d="m166.288 71.427c1.272-0.352 2.546-0.279 4.736 0.913 1.962 1.14 1.088 2.447-1.081 1.95-1.988-0.452-1.56-0.377-3.122-0.674-2.55-0.469-2.995-1.526-0.533-2.189z"/>\r
+   <path fill="#424242" d="m166.354 71.554c1.236-0.327 2.507-0.245 4.661 0.902 1.861 1.061 1.037 2.214-1.106 1.729-1.974-0.444-1.488-0.352-3.016-0.64-2.504-0.453-2.942-1.375-0.539-1.991z"/>\r
+   <path fill="#474747" d="m166.419 71.68c1.203-0.302 2.469-0.21 4.587 0.892 1.762 0.982 0.986 1.98-1.13 1.508-1.962-0.435-1.417-0.326-2.911-0.606-2.458-0.437-2.888-1.224-0.546-1.794z"/>\r
+   <path fill="#4c4c4c" d="m166.485 71.807c1.167-0.276 2.429-0.176 4.513 0.882 1.66 0.903 0.935 1.748-1.156 1.288-1.948-0.426-1.345-0.301-2.805-0.572-2.412-0.423-2.834-1.076-0.552-1.598z"/>\r
+   <path fill="#515151" d="m166.551 71.934c1.133-0.251 2.391-0.142 4.438 0.871 1.56 0.824 0.883 1.515-1.181 1.067-1.936-0.417-1.274-0.275-2.699-0.537-2.366-0.408-2.781-0.926-0.558-1.401z"/>\r
+   <path fill="#565656" d="m166.617 72.061c1.097-0.227 2.351-0.108 4.363 0.861 1.46 0.745 0.831 1.281-1.206 0.846-1.922-0.409-1.202-0.25-2.594-0.503-2.319-0.393-2.726-0.777-0.563-1.204z"/>\r
+   <path fill="#5b5b5b" d="m166.683 72.187c1.062-0.201 2.312-0.073 4.289 0.851 1.358 0.666 0.778 1.048-1.231 0.625-1.91-0.4-1.131-0.225-2.489-0.469-2.274-0.377-2.672-0.626-0.569-1.007z"/>\r
+   <path fill="#606060" d="m166.748 72.314c1.027-0.176 2.274-0.04 4.215 0.84 1.26 0.587 0.729 0.815-1.256 0.404-1.896-0.392-1.06-0.2-2.383-0.435-2.228-0.361-2.619-0.475-0.576-0.809z"/>\r
+   <path fill="#666" d="m166.814 72.44c0.992-0.151 2.234-0.005 4.14 0.83 1.159 0.508 0.677 0.582-1.281 0.183-1.883-0.383-0.987-0.174-2.276-0.4-2.183-0.346-2.566-0.325-0.583-0.613z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path fill="#666" d="m159.99 128.249c-9.36 0.36-24.192-25.848-24.552-14.976-0.288 9.216 0.216 9.072 0.216 18 0 5.976-2.736 6.408-8.64 15.408-3.024 4.752-5.4 9.864-7.272 15.048-1.152 3.096-2.232 6.336-3.096 9.504-0.36 1.584-1.008 3.24-1.368 4.824-2.952 10.872-13.464 24.264-15.912 35.136-2.448 10.8-5.328 17.712-4.968 32.185 0.36 14.472 0.504 10.295 4.896 13.896 4.32 3.601 8.784 6.983 15.624 13.032 7.2 6.264 22.177 17.208 24.192 20.592 2.16 3.456 2.088 11.232 0.792 13.752-1.296 2.448-12.6 3.816-12.528 3.816-0.071 0 9.864 13.68 11.809 15.623 1.872 1.873 9.936 10.873 42.768 4.752 18.504-3.455 32.832-13.823 43.2-23.832 13.392-13.031 6.624-16.775 8.352-23.327 2.521-9.433 10.729-12.96 12.601-23.616 0.216-1.512 0.72-2.664 2.088-4.896 2.088-3.168 1.584-9.432 1.584-15.191 0-14.977-1.729-30.24-5.185-41.472-3.168-10.512-8.208-17.856-12.527-27.36-8.641-18.936-8.208-27.432-15.912-39.528-8.784-13.968-4.464-23.256-16.128-22.68-14.546 0.79-26.282 20.734-40.034 21.31z"/>\r
+   <path fill="#6d6d6d" d="m159.973 129.334c-9.281 0.353-23.746-25.511-24.242-15.179-0.316 8.755 0.1 8.678 0.03 17.247-0.15 5.87-2.953 6.637-8.727 15.481-3.013 4.763-5.273 9.812-6.993 14.877-0.968 3.253-1.56 6.422-2.43 9.526-0.415 1.642-1.497 3.187-2.185 5.042-3.254 10.78-13.545 24.182-15.961 34.877-2.466 10.81-5.37 17.694-4.961 32.141 0.366 14 0.395 10.177 4.773 13.816 4.283 3.616 8.839 7.069 15.662 13.103 7.183 6.248 22.237 17.216 24.243 20.588 2.149 3.444 2.131 11.317 0.844 13.823-1.284 2.439-12.579 3.875-12.508 3.875-0.071 0 9.815 13.566 11.757 15.508 1.87 1.87 9.902 10.809 42.678 4.704 18.524-3.455 33.124-13.753 43.078-23.856 12.789-12.762 6.107-16.773 7.826-23.291 2.513-9.416 11.277-12.961 13.143-23.602 0.216-1.508 0.754-2.654 2.113-4.876 2.096-3.202 1.561-9.447 1.582-15.185 0.067-15.027-1.705-30.234-5.159-41.434-3.171-10.483-8.204-17.817-12.515-27.305-8.624-18.906-8.221-27.415-15.933-39.474-8.586-13.613-4.601-22.583-16.011-21.99-14.374 0.826-26.375 21.016-40.104 21.584z"/>\r
+   <path fill="#757575" d="m159.955 130.419c-9.201 0.346-23.299-25.175-23.931-15.383-0.344 8.295-0.017 8.284-0.156 16.494-0.301 5.764-3.17 6.867-8.812 15.555-3.002 4.774-5.148 9.76-6.714 14.706-0.784 3.41-0.889 6.508-1.764 9.548-0.471 1.699-1.986 3.133-3.003 5.259-3.554 10.688-13.624 24.1-16.009 34.619-2.483 10.82-5.411 17.678-4.954 32.097 0.373 13.528 0.285 10.058 4.651 13.739 4.244 3.632 8.893 7.154 15.699 13.171 7.167 6.233 22.299 17.224 24.294 20.585 2.142 3.432 2.175 11.404 0.896 13.896-1.271 2.428-12.558 3.932-12.486 3.932-0.071 0 9.768 13.453 11.705 15.392 1.867 1.867 9.867 10.744 42.588 4.655 18.545-3.453 33.415-13.682 42.956-23.879 12.187-12.492 5.591-16.771 7.3-23.258 2.507-9.398 11.826-12.959 13.687-23.586 0.215-1.5 0.788-2.643 2.138-4.854 2.104-3.235 1.538-9.462 1.58-15.178 0.133-15.076-1.681-30.228-5.135-41.394-3.173-10.455-8.199-17.779-12.501-27.25-8.609-18.877-8.234-27.399-15.952-39.42-8.389-13.258-4.739-21.911-15.895-21.301-14.21 0.859-26.474 21.295-40.182 21.855z"/>\r
+   <path fill="#7c7c7c" d="m159.938 131.504c-9.122 0.338-22.854-24.838-23.622-15.586-0.37 7.833-0.131 7.89-0.341 15.741-0.452 5.657-3.388 7.096-8.899 15.628-2.99 4.785-5.021 9.708-6.433 14.535-0.602 3.566-0.218 6.594-1.099 9.57-0.526 1.756-2.475 3.08-3.82 5.477-3.854 10.596-13.703 24.016-16.057 34.361-2.501 10.829-5.453 17.66-4.948 32.052 0.38 13.059 0.177 9.939 4.529 13.66 4.208 3.648 8.948 7.239 15.739 13.242 7.149 6.217 22.358 17.232 24.345 20.581 2.13 3.42 2.216 11.489 0.946 13.968-1.259 2.417-12.538 3.99-12.466 3.99-0.072 0 9.718 13.34 11.653 15.275 1.865 1.864 9.833 10.681 42.498 4.607 18.565-3.453 33.706-13.609 42.834-23.902 11.583-12.223 5.074-16.771 6.774-23.223 2.499-9.382 12.375-12.959 14.229-23.57 0.215-1.496 0.821-2.633 2.162-4.834 2.111-3.271 1.516-9.478 1.578-15.173 0.199-15.125-1.657-30.221-5.109-41.354-3.177-10.427-8.196-17.741-12.488-27.195-8.594-18.848-8.247-27.383-15.972-39.366-8.192-12.903-4.877-21.239-15.779-20.612-14.041 0.894-26.569 21.576-40.254 22.128z"/>\r
+   <path fill="#848484" d="m159.921 132.589c-9.043 0.331-22.406-24.502-23.312-15.79-0.398 7.373-0.247 7.496-0.527 14.988-0.602 5.551-3.604 7.326-8.984 15.702-2.98 4.796-4.896 9.656-6.154 14.364-0.417 3.723 0.455 6.679-0.432 9.592-0.582 1.813-2.964 3.026-4.639 5.694-4.153 10.504-13.782 23.936-16.104 34.104-2.519 10.838-5.495 17.643-4.941 32.008 0.387 12.586 0.067 9.819 4.407 13.582 4.171 3.664 9.002 7.324 15.777 13.311 7.132 6.201 22.419 17.24 24.396 20.576 2.12 3.41 2.259 11.578 0.998 14.041-1.247 2.408-12.517 4.049-12.446 4.049-0.07 0 9.67 13.227 11.604 15.16 1.861 1.861 9.798 10.615 42.409 4.558 18.584-3.45 33.996-13.538 42.711-23.926 10.979-11.952 4.557-16.769 6.248-23.187 2.491-9.367 12.924-12.959 14.771-23.557 0.215-1.49 0.856-2.622 2.188-4.813 2.118-3.305 1.491-9.494 1.575-15.166 0.267-15.174-1.635-30.215-5.086-41.314-3.179-10.399-8.19-17.703-12.473-27.141-8.579-18.818-8.262-27.366-15.994-39.312-7.993-12.547-5.013-20.565-15.661-19.922-13.876 0.927-26.669 21.855-40.331 22.399z"/>\r
+   <path fill="#8c8c8c" d="m159.903 133.674c-8.963 0.323-21.961-24.165-23.001-15.994-0.426 6.912-0.363 7.102-0.713 14.236-0.753 5.445-3.821 7.554-9.071 15.775-2.969 4.807-4.768 9.604-5.875 14.192-0.232 3.881 1.128 6.766 0.234 9.615-0.638 1.87-3.452 2.972-5.455 5.911-4.455 10.413-13.862 23.853-16.153 33.845-2.537 10.849-5.537 17.625-4.935 31.963 0.393 12.115-0.042 9.701 4.285 13.505 4.133 3.68 9.057 7.409 15.814 13.38 7.116 6.188 22.48 17.248 24.447 20.574 2.109 3.398 2.301 11.662 1.049 14.113-1.235 2.396-12.496 4.104-12.425 4.104-0.071 0 9.622 13.114 11.552 15.045 1.86 1.858 9.763 10.552 42.319 4.509 18.604-3.449 34.288-13.467 42.589-23.949 10.377-11.682 4.04-16.766 5.721-23.15 2.486-9.35 13.474-12.959 15.316-23.542 0.214-1.483 0.89-2.611 2.213-4.793 2.126-3.339 1.468-9.507 1.573-15.158 0.333-15.224-1.611-30.208-5.062-41.276-3.181-10.37-8.186-17.664-12.459-27.085-8.563-18.789-8.275-27.35-16.014-39.258-7.796-12.192-5.151-19.893-15.545-19.233-13.707 0.961-26.763 22.134-40.404 22.671z"/>\r
+   <path fill="#939393" d="m159.886 134.759c-8.885 0.316-21.516-23.829-22.691-16.197-0.454 6.451-0.479 6.708-0.899 13.482-0.903 5.339-4.038 7.784-9.157 15.849-2.957 4.818-4.642 9.552-5.595 14.021-0.05 4.037 1.799 6.852 0.9 9.637-0.693 1.928-3.941 2.919-6.273 6.129-4.756 10.32-13.941 23.77-16.201 33.587-2.555 10.858-5.579 17.608-4.928 31.92 0.399 11.644-0.151 9.581 4.162 13.424 4.096 3.697 9.111 7.494 15.854 13.451 7.099 6.17 22.541 17.256 24.498 20.569 2.1 3.387 2.344 11.75 1.101 14.186-1.223 2.387-12.476 4.163-12.404 4.163-0.071 0 9.573 13.001 11.5 14.929 1.857 1.856 9.729 10.488 42.229 4.461 18.625-3.449 34.579-13.396 42.467-23.973 9.774-11.412 3.523-16.764 5.195-23.115 2.479-9.334 14.022-12.959 15.858-23.527 0.214-1.479 0.924-2.601 2.238-4.772 2.134-3.373 1.445-9.522 1.571-15.151 0.399-15.273-1.587-30.201-5.036-41.237-3.185-10.342-8.184-17.625-12.446-27.03-8.548-18.76-8.288-27.333-16.034-39.204-7.598-11.837-5.289-19.221-15.428-18.544-13.543 0.994-26.863 22.413-40.481 22.942z"/>\r
+   <path fill="#9b9b9b" d="m159.868 135.844c-8.805 0.308-21.068-23.492-22.381-16.401-0.481 5.991-0.594 6.314-1.085 12.73-1.053 5.232-4.253 8.013-9.243 15.922-2.946 4.829-4.515 9.5-5.314 13.85 0.133 4.194 2.471 6.937 1.565 9.658-0.749 1.986-4.43 2.866-7.091 6.347-5.056 10.229-14.021 23.689-16.249 33.329-2.572 10.868-5.621 17.591-4.921 31.876 0.405 11.172-0.261 9.463 4.04 13.346 4.058 3.713 9.166 7.58 15.892 13.521 7.082 6.155 22.601 17.265 24.548 20.567 2.092 3.373 2.388 11.834 1.152 14.256-1.21 2.377-12.454 4.222-12.383 4.222-0.071 0 9.523 12.888 11.45 14.813 1.854 1.854 9.692 10.424 42.138 4.412 18.645-3.447 34.871-13.324 42.345-23.996 9.171-11.143 3.007-16.762 4.669-23.08 2.472-9.317 14.572-12.959 16.401-23.514 0.214-1.473 0.958-2.588 2.265-4.75 2.142-3.408 1.421-9.539 1.568-15.145 0.466-15.324-1.564-30.196-5.012-41.198-3.187-10.313-8.179-17.587-12.433-26.976-8.533-18.73-8.301-27.316-16.054-39.149-7.401-11.482-5.426-18.548-15.313-17.855-13.373 1.029-26.958 22.694-40.554 23.215z"/>\r
+   <path fill="#a3a3a3" d="m159.851 136.929c-8.727 0.301-20.622-23.156-22.071-16.604-0.509 5.529-0.71 5.919-1.271 11.976-1.203 5.126-4.47 8.243-9.328 15.996-2.936 4.84-4.39 9.448-5.036 13.679 0.316 4.351 3.143 7.023 2.231 9.68-0.804 2.043-4.919 2.812-7.908 6.563-5.356 10.137-14.101 23.607-16.298 33.072-2.589 10.877-5.661 17.574-4.913 31.832 0.412 10.699-0.37 9.342 3.918 13.268 4.021 3.729 9.221 7.664 15.93 13.59 7.064 6.139 22.661 17.271 24.599 20.563 2.081 3.363 2.43 11.922 1.204 14.33-1.198 2.365-12.434 4.278-12.363 4.278-0.07 0 9.477 12.774 11.399 14.697 1.851 1.851 9.659 10.36 42.048 4.364 18.666-3.447 35.162-13.254 42.223-24.021 8.568-10.873 2.49-16.761 4.144-23.045 2.464-9.301 15.121-12.958 16.943-23.498 0.215-1.467 0.992-2.579 2.29-4.729 2.148-3.441 1.398-9.553 1.566-15.139 0.532-15.373-1.541-30.189-4.987-41.158-3.188-10.285-8.174-17.549-12.419-26.921-8.518-18.701-8.313-27.3-16.073-39.096-7.204-11.126-5.564-17.875-15.196-17.165-13.21 1.064-27.058 22.975-40.632 23.488z"/>\r
+   <path fill="#aaa" d="m159.834 138.014c-8.646 0.293-20.176-22.819-21.761-16.808-0.536 5.069-0.826 5.526-1.457 11.224-1.354 5.02-4.687 8.472-9.416 16.069-2.924 4.851-4.262 9.396-4.756 13.508 0.501 4.507 3.814 7.109 2.897 9.702-0.858 2.1-5.406 2.759-8.725 6.782-5.657 10.045-14.181 23.524-16.347 32.812-2.606 10.888-5.703 17.557-4.906 31.787 0.418 10.229-0.479 9.225 3.795 13.189 3.984 3.745 9.275 7.749 15.968 13.66 7.048 6.124 22.723 17.279 24.651 20.559 2.07 3.352 2.472 12.008 1.255 14.402-1.186 2.355-12.414 4.337-12.343 4.337-0.071 0 9.428 12.66 11.348 14.581 1.85 1.848 9.624 10.297 41.958 4.314 18.687-3.444 35.453-13.18 42.102-24.043 7.965-10.602 1.973-16.758 3.616-23.01 2.457-9.283 15.67-12.957 17.487-23.482 0.214-1.461 1.026-2.568 2.315-4.709 2.155-3.477 1.375-9.568 1.563-15.131 0.6-15.424-1.518-30.184-4.963-41.119-3.192-10.257-8.17-17.511-12.405-26.866-8.502-18.672-8.328-27.284-16.095-39.042-7.005-10.771-5.701-17.203-15.078-16.476-13.04 1.098-27.152 23.255-40.703 23.76z"/>\r
+   <path fill="#b2b2b2" d="m159.816 139.099c-8.567 0.286-19.729-22.483-21.45-17.012-0.563 4.608-0.942 5.132-1.643 10.471-1.506 4.914-4.904 8.701-9.502 16.143-2.913 4.862-4.137 9.344-4.477 13.336 0.685 4.665 4.486 7.195 3.564 9.725-0.915 2.157-5.897 2.705-9.543 6.999-5.958 9.953-14.262 23.443-16.396 32.554-2.624 10.898-5.745 17.54-4.9 31.744 0.426 9.757-0.588 9.105 3.674 13.111 3.945 3.761 9.33 7.834 16.006 13.729 7.032 6.109 22.783 17.288 24.702 20.557 2.06 3.338 2.515 12.094 1.306 14.473-1.173 2.346-12.392 4.395-12.321 4.395-0.07 0 9.379 12.549 11.296 14.465 1.847 1.848 9.591 10.234 41.868 4.268 18.706-3.444 35.745-13.11 41.979-24.066 7.361-10.332 1.456-16.757 3.091-22.974 2.45-9.269 16.219-12.958 18.03-23.47 0.213-1.455 1.06-2.557 2.34-4.688 2.164-3.509 1.352-9.583 1.562-15.124 0.665-15.473-1.494-30.177-4.938-41.08-3.195-10.228-8.166-17.472-12.393-26.811-8.486-18.642-8.341-27.267-16.114-38.987-6.809-10.416-5.838-16.531-14.962-15.787-12.873 1.129-27.25 23.531-40.779 24.029z"/>\r
+   <path fill="#bababa" d="m159.799 140.184c-8.487 0.279-19.282-22.146-21.141-17.215-0.591 4.147-1.057 4.737-1.828 9.717-1.656 4.808-5.121 8.931-9.588 16.217-2.902 4.873-4.01 9.292-4.197 13.165 0.868 4.822 5.158 7.281 4.23 9.747-0.971 2.215-6.385 2.651-10.361 7.216-6.258 9.861-14.339 23.36-16.442 32.297-2.643 10.906-5.787 17.521-4.894 31.699 0.432 9.285-0.697 8.986 3.552 13.032 3.908 3.776 9.384 7.919 16.043 13.799 7.016 6.093 22.845 17.296 24.753 20.552 2.051 3.328 2.559 12.18 1.358 14.547-1.161 2.334-12.372 4.451-12.301 4.451-0.071 0 9.33 12.436 11.245 14.35 1.844 1.844 9.555 10.17 41.777 4.219 18.727-3.443 36.036-13.039 41.857-24.09 6.759-10.063 0.939-16.756 2.565-22.939 2.442-9.25 16.768-12.957 18.572-23.453 0.213-1.451 1.095-2.547 2.365-4.668 2.171-3.543 1.329-9.599 1.56-15.117 0.732-15.522-1.471-30.172-4.913-41.042-3.197-10.2-8.161-17.433-12.379-26.756-8.471-18.612-8.354-27.25-16.135-38.933-6.609-10.061-5.976-15.858-14.845-15.098-12.706 1.165-27.347 23.813-40.853 24.303z"/>\r
+   <path fill="#c1c1c1" d="m159.781 141.269c-8.408 0.271-18.837-21.81-20.83-17.419-0.619 3.687-1.173 4.344-2.014 8.965-1.808 4.701-5.338 9.16-9.674 16.29-2.892 4.884-3.885 9.24-3.918 12.994 1.052 4.978 5.829 7.367 4.896 9.769-1.026 2.272-6.874 2.598-11.178 7.434-6.56 9.769-14.419 23.277-16.491 32.039-2.66 10.916-5.829 17.504-4.887 31.656 0.438 8.813-0.807 8.867 3.43 12.953 3.87 3.793 9.438 8.004 16.082 13.868 6.997 6.077 22.904 17.304 24.803 20.55 2.041 3.314 2.601 12.266 1.409 14.617-1.149 2.324-12.351 4.51-12.28 4.51-0.07 0 9.282 12.321 11.194 14.233 1.841 1.842 9.521 10.106 41.688 4.17 18.746-3.44 36.326-12.967 41.734-24.112 6.156-9.793 0.423-16.754 2.038-22.904 2.438-9.235 17.318-12.957 19.117-23.438 0.212-1.444 1.128-2.536 2.39-4.647 2.18-3.578 1.306-9.613 1.558-15.11 0.799-15.571-1.447-30.165-4.889-41.002-3.2-10.172-8.156-17.395-12.364-26.701-8.456-18.583-8.367-27.234-16.155-38.88-6.413-9.705-6.114-15.185-14.729-14.408-12.541 1.197-27.445 24.091-40.93 24.573z"/>\r
+   <path fill="#c9c9c9" d="m159.764 142.354c-8.329 0.264-18.392-21.473-20.521-17.622-0.646 3.225-1.289 3.949-2.2 8.211-1.957 4.596-5.555 9.39-9.761 16.364-2.879 4.895-3.757 9.188-3.638 12.823 1.235 5.135 6.502 7.453 5.562 9.791-1.081 2.329-7.362 2.544-11.995 7.651-6.859 9.677-14.499 23.195-16.54 31.78-2.677 10.927-5.87 17.488-4.879 31.611 0.444 8.344-0.916 8.748 3.307 12.875 3.834 3.81 9.492 8.09 16.121 13.939 6.98 6.061 22.965 17.311 24.854 20.545 2.031 3.303 2.643 12.352 1.461 14.69-1.137 2.313-12.33 4.567-12.26 4.567-0.07 0 9.233 12.209 11.143 14.117 1.839 1.84 9.486 10.043 41.599 4.122 18.767-3.44 36.618-12.896 41.612-24.137 5.554-9.522-0.094-16.751 1.513-22.868 2.43-9.219 17.866-12.957 19.659-23.424 0.213-1.439 1.162-2.525 2.415-4.627 2.188-3.612 1.282-9.629 1.556-15.104 0.865-15.621-1.424-30.158-4.864-40.962-3.202-10.144-8.153-17.357-12.351-26.646-8.441-18.554-8.381-27.218-16.176-38.826-6.216-9.35-6.251-14.513-14.612-13.719-12.374 1.235-27.543 24.375-41.005 24.849z"/>\r
+   <path fill="#d1d1d1" d="m159.747 143.439c-8.25 0.256-17.944-21.137-20.21-17.826-0.675 2.765-1.406 3.555-2.386 7.459-2.108 4.489-5.772 9.619-9.847 16.437-2.869 4.906-3.631 9.136-3.358 12.652 1.419 5.292 7.174 7.538 6.228 9.812-1.137 2.387-7.852 2.491-12.813 7.869-7.161 9.586-14.579 23.114-16.588 31.522-2.695 10.938-5.912 17.471-4.873 31.568 0.451 7.871-1.025 8.629 3.185 12.797 3.796 3.824 9.547 8.174 16.158 14.008 6.964 6.047 23.026 17.32 24.905 20.541 2.021 3.292 2.686 12.439 1.513 14.764-1.125 2.303-12.31 4.625-12.239 4.625-0.07 0 9.186 12.094 11.092 14.002 1.836 1.836 9.45 9.978 41.509 4.072 18.786-3.439 36.909-12.824 41.49-24.16 4.948-9.252-0.611-16.748 0.985-22.832 2.423-9.203 18.415-12.957 20.203-23.41 0.212-1.434 1.196-2.514 2.44-4.605 2.193-3.646 1.259-9.645 1.553-15.098 0.932-15.67-1.4-30.151-4.84-40.922-3.205-10.115-8.148-17.319-12.336-26.592-8.427-18.524-8.396-27.201-16.197-38.771-6.017-8.995-6.388-13.84-14.495-13.03-12.207 1.266-27.64 24.652-41.079 25.118z"/>\r
+   <path fill="#d8d8d8" d="m159.729 144.524c-8.17 0.249-17.498-20.8-19.9-18.03-0.702 2.304-1.521 3.162-2.571 6.706-2.259 4.383-5.988 9.848-9.933 16.511-2.858 4.917-3.504 9.084-3.079 12.48 1.604 5.449 7.846 7.625 6.895 9.835-1.193 2.444-8.342 2.438-13.631 8.087-7.461 9.493-14.658 23.031-16.637 31.262-2.712 10.947-5.953 17.455-4.865 31.524 0.458 7.399-1.135 8.511 3.063 12.718 3.758 3.842 9.601 8.26 16.196 14.078 6.946 6.031 23.087 17.328 24.956 20.538 2.011 3.28 2.729 12.524 1.563 14.835-1.112 2.293-12.289 4.684-12.218 4.684-0.071 0 9.136 11.981 11.04 13.886 1.834 1.834 9.417 9.913 41.419 4.024 18.807-3.438 37.2-12.752 41.368-24.184 4.346-8.982-1.128-16.747 0.46-22.798 2.416-9.187 18.964-12.956 20.746-23.394 0.211-1.429 1.229-2.504 2.465-4.586 2.202-3.681 1.236-9.658 1.551-15.091 0.998-15.72-1.377-30.146-4.814-40.884-3.208-10.086-8.145-17.28-12.323-26.536-8.411-18.495-8.408-27.185-16.217-38.717-5.82-8.64-6.526-13.168-14.38-12.341-12.04 1.303-27.736 24.934-41.154 25.393z"/>\r
+   <path fill="#e0e0e0" d="m159.712 145.609c-8.091 0.241-17.052-20.464-19.59-18.233-0.729 1.843-1.637 2.767-2.757 5.953-2.409 4.276-6.206 10.077-10.02 16.584-2.847 4.928-3.378 9.032-2.8 12.309 1.787 5.606 8.519 7.711 7.561 9.857-1.248 2.502-8.829 2.384-14.448 8.304-7.761 9.402-14.738 22.95-16.684 31.006-2.731 10.955-5.996 17.436-4.859 31.48 0.464 6.928-1.244 8.389 2.939 12.639 3.722 3.857 9.656 8.344 16.234 14.148 6.932 6.014 23.148 17.336 25.008 20.533 2 3.268 2.771 12.611 1.615 14.907-1.1 2.282-12.268 4.741-12.198 4.741-0.069 0 9.089 11.867 10.989 13.77 1.831 1.831 9.382 9.85 41.329 3.977 18.827-3.438 37.492-12.683 41.246-24.207 3.743-8.715-1.646-16.746-0.066-22.762 2.409-9.171 19.514-12.957 21.289-23.381 0.211-1.422 1.265-2.494 2.49-4.564 2.21-3.715 1.213-9.674 1.549-15.084 1.065-15.77-1.354-30.139-4.791-40.844-3.21-10.058-8.14-17.241-12.309-26.481-8.396-18.466-8.421-27.168-16.237-38.664-5.622-8.284-6.663-12.495-14.262-11.651-11.872 1.335-27.833 25.212-41.228 25.663z"/>\r
+   <path fill="#e8e8e8" d="m159.694 146.694c-8.012 0.234-16.605-20.127-19.279-18.437-0.757 1.383-1.753 2.373-2.943 5.2-2.56 4.171-6.423 10.307-10.105 16.658-2.835 4.939-3.251 8.979-2.52 12.138 1.97 5.763 9.189 7.796 8.226 9.879-1.303 2.559-9.318 2.33-15.265 8.521-8.063 9.31-14.818 22.867-16.733 30.748-2.748 10.967-6.037 17.419-4.853 31.436 0.472 6.457-1.353 8.271 2.818 12.562 3.685 3.873 9.711 8.429 16.273 14.218 6.913 6 23.207 17.344 25.058 20.529 1.991 3.257 2.814 12.697 1.666 14.98-1.087 2.271-12.247 4.799-12.177 4.799-0.07 0 9.04 11.755 10.938 13.654 1.829 1.828 9.349 9.785 41.239 3.926 18.847-3.435 37.783-12.609 41.124-24.229 3.14-8.444-2.161-16.743-0.592-22.728 2.401-9.152 20.062-12.955 21.831-23.364 0.211-1.417 1.298-2.483 2.516-4.544 2.217-3.748 1.19-9.689 1.547-15.076 1.132-15.82-1.331-30.133-4.766-40.806-3.213-10.03-8.136-17.203-12.296-26.427-8.38-18.436-8.435-27.151-16.257-38.609-5.425-7.929-6.802-11.822-14.146-10.962-11.706 1.368-27.931 25.491-41.304 25.934z"/>\r
+   <path fill="#efefef" d="m159.677 147.779c-7.934 0.226-16.16-19.791-18.97-18.64-0.785 0.921-1.869 1.979-3.13 4.447-2.71 4.064-6.639 10.536-10.19 16.731-2.824 4.95-3.125 8.928-2.24 11.967 2.152 5.919 9.86 7.882 8.892 9.901-1.358 2.616-9.808 2.277-16.083 8.739-8.363 9.218-14.896 22.784-16.781 30.489-2.766 10.977-6.079 17.402-4.846 31.393 0.478 5.984-1.462 8.152 2.696 12.482 3.646 3.889 9.765 8.514 16.311 14.287 6.896 5.983 23.269 17.352 25.109 20.526 1.98 3.245 2.855 12.782 1.718 15.052-1.076 2.262-12.227 4.857-12.156 4.857-0.07 0 8.991 11.641 10.887 13.537 1.826 1.826 9.313 9.723 41.148 3.879 18.868-3.434 38.074-12.538 41.002-24.254 2.537-8.174-2.678-16.741-1.119-22.69 2.396-9.138 20.612-12.957 22.375-23.351 0.212-1.412 1.332-2.473 2.541-4.523 2.226-3.783 1.166-9.704 1.545-15.07 1.197-15.869-1.307-30.125-4.741-40.766-3.215-10.002-8.131-17.165-12.282-26.372-8.365-18.407-8.447-27.135-16.277-38.555-5.228-7.574-6.938-11.15-14.029-10.272-11.541 1.402-28.029 25.771-41.38 26.206z"/>\r
+   <path fill="#f7f7f7" d="m159.66 148.864c-7.854 0.219-15.714-19.454-18.66-18.844-0.812 0.461-1.983 1.585-3.314 3.694-2.86 3.958-6.856 10.766-10.278 16.805-2.813 4.961-2.998 8.876-1.96 11.796 2.337 6.076 10.533 7.968 9.558 9.923-1.415 2.673-10.296 2.223-16.899 8.956-8.664 9.126-14.978 22.702-16.83 30.23-2.783 10.986-6.121 17.386-4.839 31.349 0.484 5.515-1.571 8.033 2.573 12.403 3.609 3.906 9.82 8.6 16.35 14.357 6.88 5.969 23.329 17.36 25.16 20.523 1.971 3.232 2.898 12.869 1.77 15.124-1.063 2.252-12.206 4.915-12.136 4.915-0.07 0 8.942 11.527 10.835 13.422 1.824 1.822 9.279 9.658 41.059 3.83 18.889-3.434 38.366-12.467 40.881-24.278 1.934-7.903-3.195-16.739-1.646-22.655 2.388-9.121 21.161-12.955 22.918-23.336 0.211-1.404 1.366-2.461 2.566-4.502 2.232-3.816 1.143-9.719 1.542-15.063 1.265-15.92-1.283-30.12-4.717-40.727-3.219-9.974-8.128-17.127-12.269-26.317-8.349-18.378-8.461-27.119-16.298-38.501-5.029-7.219-7.075-10.478-13.912-9.584-11.373 1.438-28.126 26.053-41.454 26.48z"/>\r
+   <path fill="#fff" d="m159.642 149.949c-7.774 0.211-15.268-19.118-18.35-19.048-0.84 0-2.1 1.191-3.501 2.941-3.011 3.852-7.072 10.995-10.363 16.878-2.803 4.972-2.872 8.824-1.682 11.625 2.521 6.233 11.205 8.054 10.225 9.945-1.471 2.731-10.785 2.17-17.719 9.174-8.964 9.034-15.056 22.621-16.877 29.973-2.801 10.995-6.163 17.368-4.832 31.304 0.49 5.043-1.681 7.914 2.451 12.325 3.571 3.923 9.874 8.685 16.387 14.427 6.863 5.953 23.391 17.368 25.211 20.521 1.962 3.221 2.942 12.954 1.821 15.196-1.051 2.24-12.185 4.972-12.115 4.972-0.069 0 8.895 11.416 10.784 13.307 1.821 1.82 9.244 9.595 40.97 3.781 18.907-3.431 38.656-12.396 40.758-24.302 1.331-7.633-3.712-16.736-2.171-22.619 2.381-9.104 21.71-12.956 23.461-23.321 0.21-1.399 1.399-2.45 2.591-4.481 2.24-3.852 1.12-9.734 1.54-15.057 1.331-15.968-1.26-30.113-4.692-40.688-3.221-9.945-8.123-17.088-12.255-26.262-8.334-18.348-8.474-27.102-16.318-38.447-4.832-6.863-7.213-9.805-13.796-8.894-11.205 1.469-28.222 26.33-41.528 26.75z"/>\r
+  </g>\r
+  <path fill="#995900" d="m152.553 88.8575c5.256-0.648 12.456 0.648 15.769 3.096 3.096 2.304 5.256 3.528 8.063 4.464 9.433 3.096 21.816 4.536 21.24 13.032-0.648 10.151-3.6 14.688-12.024 17.351-6.768 2.088-18.863 13.824-28.224 13.824-4.176 0-10.008 0.216-13.392-1.008-3.24-1.152-7.776-6.624-13.104-11.016-5.328-4.32-10.296-8.928-10.439-14.976-0.217-6.407 3.96-8.496 9.863-13.607 3.097-2.736 8.712-7.272 12.601-9.288 3.599-1.799 5.903-1.439 9.647-1.872z"/>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path fill="#9e5f00" d="m165.068 78.951c5.225-0.644 12.384 0.645 15.677 3.079 3.078 2.29 5.227 3.51 8.018 4.438 9.375 3.078 21.729 4.529 21.159 12.973-0.641 10.09-3.669 14.581-12.041 17.223-6.723 2.073-18.768 13.589-28.07 13.64-4.21 0.032-9.926 0.234-13.287-0.977-3.215-1.142-7.737-6.608-13.031-10.969-5.291-4.292-10.26-8.774-10.317-14.765-0.153-6.252 3.912-8.411 9.773-13.488 3.071-2.71 8.594-7.303 12.463-9.333 3.563-1.803 5.933-1.392 9.656-1.821z"/>\r
+   <path fill="#a36400" d="m165.177 79.044c5.195-0.641 12.313 0.64 15.587 3.06 3.06 2.278 5.194 3.494 7.971 4.413 9.317 3.06 21.641 4.522 21.078 12.914-0.634 10.027-3.737 14.474-12.058 17.094-6.678 2.057-18.673 13.352-27.919 13.454-4.241 0.064-9.842 0.252-13.18-0.945-3.19-1.133-7.7-6.592-12.96-10.921-5.254-4.264-10.222-8.622-10.192-14.555-0.093-6.099 3.863-8.329 9.681-13.369 3.048-2.685 8.478-7.335 12.328-9.379 3.526-1.804 5.963-1.338 9.664-1.766z"/>\r
+   <path fill="#a86a00" d="m165.287 79.138c5.165-0.637 12.241 0.637 15.496 3.043 3.042 2.264 5.165 3.476 7.924 4.387 9.26 3.042 21.556 4.515 20.998 12.854-0.627 9.968-3.805 14.368-12.074 16.967-6.633 2.042-18.576 13.117-27.766 13.27-4.276 0.096-9.759 0.27-13.075-0.914-3.165-1.123-7.661-6.577-12.887-10.874-5.217-4.236-10.187-8.468-10.069-14.345-0.03-5.943 3.815-8.244 9.589-13.249 3.023-2.66 8.36-7.366 12.191-9.424 3.49-1.808 5.993-1.291 9.673-1.715z"/>\r
+   <path fill="#ad7000" d="m165.396 79.23c5.135-0.633 12.17 0.633 15.404 3.025 3.025 2.251 5.137 3.46 7.88 4.361 9.201 3.025 21.467 4.508 20.917 12.796-0.62 9.905-3.874 14.26-12.093 16.837-6.586 2.027-18.479 12.882-27.611 13.086-4.311 0.127-9.677 0.287-12.971-0.883-3.14-1.113-7.622-6.561-12.814-10.826-5.18-4.208-10.148-8.315-9.945-14.135 0.031-5.789 3.768-8.16 9.497-13.129 2.999-2.635 8.244-7.398 12.055-9.47 3.454-1.808 6.023-1.24 9.681-1.662z"/>\r
+   <path fill="#b27600" d="m165.506 79.325c5.105-0.63 12.099 0.629 15.314 3.007 3.007 2.237 5.104 3.442 7.832 4.335 9.145 3.007 21.38 4.501 20.837 12.737-0.614 9.844-3.943 14.154-12.109 16.709-6.541 2.011-18.385 12.645-27.46 12.9-4.342 0.16-9.592 0.306-12.862-0.851-3.115-1.103-7.584-6.545-12.743-10.779-5.144-4.18-10.112-8.162-9.821-13.924 0.092-5.634 3.718-8.076 9.405-13.009 2.975-2.609 8.126-7.429 11.919-9.514 3.416-1.812 6.052-1.192 9.688-1.611z"/>\r
+   <path fill="#b77b00" d="m165.615 79.417c5.075-0.626 12.026 0.626 15.224 2.989 2.989 2.225 5.075 3.425 7.786 4.31 9.087 2.989 21.292 4.494 20.756 12.678-0.606 9.781-4.012 14.046-12.126 16.581-6.496 1.997-18.289 12.41-27.307 12.716-4.376 0.191-9.51 0.323-12.758-0.82-3.09-1.094-7.546-6.53-12.671-10.732-5.106-4.152-10.074-8.008-9.697-13.713 0.155-5.479 3.67-7.992 9.313-12.889 2.951-2.585 8.011-7.461 11.783-9.56 3.38-1.814 6.083-1.143 9.697-1.56z"/>\r
+   <path fill="#bc8100" d="m165.725 79.511c5.044-0.622 11.954 0.622 15.133 2.972 2.971 2.211 5.045 3.408 7.739 4.284 9.029 2.971 21.205 4.487 20.675 12.619-0.6 9.719-4.079 13.939-12.143 16.451-6.45 1.982-18.192 12.175-27.153 12.532-4.41 0.223-9.428 0.341-12.653-0.789-3.065-1.084-7.507-6.514-12.598-10.684-5.069-4.124-10.038-7.855-9.574-13.504 0.217-5.324 3.622-7.908 9.222-12.77 2.926-2.559 7.894-7.492 11.646-9.605 3.344-1.816 6.113-1.092 9.706-1.506z"/>\r
+   <path fill="#c18700" d="m165.834 79.604c5.015-0.618 11.883 0.619 15.043 2.954 2.953 2.198 5.015 3.391 7.693 4.259 8.972 2.953 21.118 4.48 20.594 12.559-0.593 9.66-4.147 13.833-12.159 16.324-6.405 1.967-18.098 11.94-27.002 12.347-4.441 0.255-9.343 0.359-12.546-0.757-3.04-1.074-7.469-6.498-12.526-10.637-5.032-4.096-10-7.701-9.45-13.293 0.278-5.169 3.574-7.823 9.13-12.649 2.903-2.534 7.776-7.524 11.511-9.651 3.306-1.821 6.141-1.044 9.712-1.456z"/>\r
+   <path fill="#c68d00" d="m165.944 79.697c4.984-0.615 11.811 0.614 14.952 2.936 2.935 2.184 4.983 3.374 7.646 4.233 8.915 2.935 21.031 4.473 20.515 12.5-0.586 9.597-4.218 13.726-12.177 16.195-6.36 1.951-18.002 11.703-26.849 12.162-4.476 0.287-9.261 0.377-12.441-0.726-3.015-1.064-7.431-6.482-12.453-10.589-4.995-4.068-9.965-7.549-9.326-13.083 0.34-5.015 3.524-7.741 9.038-12.531 2.878-2.508 7.658-7.555 11.374-9.696 3.269-1.82 6.171-0.992 9.721-1.401z"/>\r
+   <path fill="#cc9200" d="m166.054 79.791c4.952-0.61 11.738 0.611 14.86 2.918 2.918 2.172 4.954 3.357 7.601 4.207 8.857 2.918 20.942 4.466 20.432 12.442-0.578 9.536-4.285 13.62-12.192 16.066-6.314 1.936-17.906 11.468-26.696 11.978-4.509 0.319-9.178 0.394-12.335-0.696-2.989-1.054-7.393-6.466-12.382-10.541-4.959-4.04-9.928-7.395-9.202-12.873 0.401-4.859 3.477-7.655 8.945-12.411 2.854-2.482 7.542-7.586 11.239-9.741 3.233-1.824 6.201-0.943 9.73-1.349z"/>\r
+   <path fill="#d19800" d="m166.163 79.883c4.923-0.606 11.668 0.608 14.771 2.901 2.9 2.158 4.924 3.339 7.554 4.181 8.801 2.9 20.855 4.459 20.352 12.383-0.571 9.474-4.353 13.512-12.21 15.938-6.269 1.921-17.81 11.233-26.543 11.793-4.542 0.351-9.094 0.413-12.229-0.664-2.965-1.044-7.354-6.45-12.311-10.494-4.921-4.012-9.89-7.241-9.079-12.662 0.465-4.705 3.431-7.571 8.855-12.29 2.83-2.458 7.425-7.618 11.102-9.787 3.197-1.827 6.231-0.893 9.738-1.299z"/>\r
+   <path fill="#d69e00" d="m166.273 79.978c4.893-0.603 11.596 0.603 14.679 2.882 2.883 2.145 4.895 3.323 7.507 4.156 8.744 2.882 20.77 4.452 20.272 12.324-0.565 9.412-4.422 13.406-12.228 15.81-6.224 1.905-17.714 10.996-26.39 11.608-4.576 0.383-9.012 0.431-12.124-0.633-2.94-1.034-7.316-6.434-12.237-10.446-4.884-3.984-9.854-7.089-8.955-12.452 0.525-4.551 3.382-7.489 8.764-12.171 2.805-2.432 7.307-7.649 10.965-9.832 3.16-1.829 6.261-0.845 9.747-1.246z"/>\r
+   <path fill="#dba300" d="m166.382 80.07c4.863-0.599 11.525 0.6 14.59 2.865 2.864 2.131 4.862 3.305 7.461 4.13 8.686 2.864 20.682 4.445 20.19 12.264-0.559 9.352-4.491 13.299-12.244 15.681-6.179 1.89-17.619 10.761-26.237 11.423-4.608 0.415-8.929 0.449-12.018-0.601-2.915-1.024-7.277-6.418-12.166-10.399-4.847-3.956-9.815-6.935-8.831-12.241 0.587-4.396 3.333-7.404 8.671-12.051 2.782-2.407 7.191-7.681 10.83-9.878 3.123-1.829 6.29-0.793 9.754-1.193z"/>\r
+   <path fill="#e0a900" d="m166.492 80.164c4.832-0.595 11.453 0.596 14.498 2.847 2.847 2.118 4.833 3.289 7.414 4.104 8.629 2.846 20.595 4.438 20.111 12.205-0.553 9.29-4.56 13.193-12.262 15.553-6.134 1.875-17.522 10.526-26.085 11.239-4.642 0.447-8.845 0.467-11.912-0.57-2.89-1.015-7.238-6.402-12.093-10.351-4.81-3.928-9.78-6.782-8.708-12.032 0.649-4.241 3.285-7.32 8.58-11.932 2.757-2.381 7.073-7.712 10.693-9.923 3.088-1.831 6.321-0.743 9.764-1.14z"/>\r
+   <path fill="#e5af00" d="m166.601 80.257c4.803-0.592 11.382 0.592 14.407 2.829 2.829 2.105 4.804 3.271 7.368 4.079 8.571 2.828 20.507 4.431 20.029 12.146-0.544 9.228-4.627 13.085-12.277 15.423-6.089 1.861-17.427 10.29-25.932 11.055-4.676 0.478-8.763 0.484-11.807-0.539-2.865-1.005-7.2-6.387-12.021-10.304-4.772-3.9-9.742-6.629-8.583-11.821 0.711-4.085 3.236-7.236 8.487-11.812 2.732-2.357 6.957-7.744 10.557-9.968 3.052-1.834 6.351-0.694 9.772-1.088z"/>\r
+   <path fill="#eab500" d="m166.711 80.351c4.772-0.588 11.31 0.589 14.317 2.811 2.811 2.092 4.771 3.254 7.321 4.054 8.514 2.81 20.42 4.424 19.948 12.087-0.538 9.165-4.695 12.979-12.294 15.295-6.044 1.845-17.332 10.054-25.779 10.869-4.708 0.511-8.68 0.503-11.7-0.507-2.84-0.995-7.163-6.371-11.949-10.257-4.736-3.872-9.706-6.475-8.46-11.61 0.773-3.931 3.188-7.152 8.396-11.692 2.709-2.331 6.839-7.775 10.421-10.013 3.013-1.839 6.38-0.645 9.779-1.037z"/>\r
+   <path fill="#efba00" d="m166.82 80.443c4.742-0.584 11.238 0.585 14.226 2.794 2.794 2.078 4.743 3.237 7.276 4.027 8.456 2.793 20.332 4.417 19.868 12.029-0.531 9.104-4.766 12.872-12.313 15.167-5.997 1.83-17.234 9.819-25.626 10.685-4.742 0.542-8.596 0.52-11.595-0.476-2.815-0.985-7.124-6.355-11.877-10.209-4.699-3.844-9.668-6.322-8.336-11.4 0.835-3.778 3.14-7.068 8.304-11.573 2.686-2.306 6.724-7.807 10.285-10.059 2.978-1.84 6.411-0.595 9.788-0.985z"/>\r
+   <path fill="#f4c000" d="m166.93 80.537c4.711-0.58 11.166 0.582 14.135 2.776 2.775 2.066 4.713 3.22 7.229 4.002 8.399 2.775 20.246 4.41 19.787 11.969-0.522 9.043-4.832 12.765-12.328 15.039-5.952 1.815-17.139 9.584-25.473 10.501-4.776 0.574-8.513 0.538-11.49-0.445-2.79-0.976-7.085-6.34-11.804-10.162-4.662-3.816-9.632-6.168-8.213-11.189 0.896-3.623 3.092-6.984 8.213-11.454 2.66-2.281 6.604-7.838 10.147-10.104 2.942-1.844 6.441-0.547 9.797-0.933z"/>\r
+   <path fill="#f9c600" d="m167.039 80.63c4.683-0.577 11.095 0.577 14.045 2.758 2.758 2.052 4.683 3.203 7.184 3.976 8.341 2.757 20.157 4.403 19.706 11.91-0.518 8.981-4.901 12.659-12.346 14.911-5.906 1.799-17.044 9.347-25.32 10.315-4.809 0.606-8.431 0.556-11.384-0.413-2.765-0.966-7.048-6.324-11.732-10.114-4.625-3.788-9.594-6.016-8.088-10.98 0.958-3.467 3.043-6.9 8.12-11.333 2.637-2.256 6.488-7.87 10.013-10.15 2.902-1.844 6.468-0.495 9.802-0.88z"/>\r
+  </g>\r
+  <path fill="#fc0" d="m154.744 90.7245c4.65-0.573 11.022 0.574 13.954 2.74 2.739 2.039 4.651 3.186 7.136 3.951 8.284 2.739 20.071 4.396 19.626 11.851-0.51 8.919-4.97 12.551-12.362 14.781-5.861 1.784-16.947 9.112-25.168 10.131-4.842 0.638-8.347 0.574-11.277-0.382-2.74-0.956-7.01-6.308-11.66-10.067-4.588-3.76-9.559-5.862-7.965-10.769 1.02-3.313 2.995-6.816 8.028-11.213 2.612-2.23 6.371-7.901 9.876-10.195 2.867-1.847 6.499-0.447 9.812-0.828z"/>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path fill="#fc0" d="m167.982 83.609c1.008 2.088 3.6 2.376 5.328 3.312 1.655 0.936 2.592 1.152 3.239 0.792 1.44-0.792 0.36-3.384-1.079-4.32-1.368-0.935-8.064-1.151-7.488 0.216z"/>\r
+   <path fill="#f9c600" d="m168.125 83.631c0.982 2.035 3.508 2.316 5.193 3.229 1.614 0.912 2.526 1.123 3.158 0.771 1.402-0.771 0.35-3.298-1.054-4.21-1.332-0.913-7.859-1.123-7.297 0.21z"/>\r
+   <path fill="#f4c000" d="m168.267 83.653c0.957 1.982 3.418 2.255 5.058 3.144 1.572 0.889 2.461 1.094 3.076 0.752 1.367-0.752 0.342-3.213-1.025-4.101-1.299-0.889-7.656-1.094-7.109 0.205z"/>\r
+   <path fill="#efba00" d="m168.409 83.674c0.932 1.929 3.327 2.195 4.924 3.06 1.53 0.865 2.395 1.064 2.993 0.732 1.331-0.732 0.333-3.127-0.998-3.992-1.264-0.864-7.451-1.064-6.919 0.2z"/>\r
+   <path fill="#eab500" d="m168.552 83.696c0.905 1.876 3.234 2.135 4.787 2.977 1.488 0.841 2.329 1.035 2.912 0.711 1.294-0.711 0.323-3.041-0.971-3.882-1.228-0.841-7.246-1.036-6.728 0.194z"/>\r
+   <path fill="#e5af00" d="m168.694 83.718c0.881 1.823 3.144 2.075 4.653 2.892 1.446 0.818 2.264 1.006 2.83 0.692 1.257-0.692 0.313-2.956-0.943-3.773-1.195-0.818-7.043-1.007-6.54 0.189z"/>\r
+   <path fill="#e0a900" d="m168.837 83.739c0.855 1.771 3.053 2.015 4.519 2.809 1.403 0.793 2.198 0.977 2.747 0.671 1.221-0.671 0.306-2.87-0.916-3.664-1.161-0.793-6.839-0.976-6.35 0.184z"/>\r
+   <path fill="#dba300" d="m168.979 83.761c0.829 1.718 2.962 1.955 4.383 2.725 1.363 0.77 2.132 0.948 2.666 0.651 1.184-0.651 0.296-2.784-0.889-3.554-1.125-0.77-6.634-0.948-6.16 0.178z"/>\r
+   <path fill="#d69e00" d="m169.121 83.782c0.804 1.665 2.871 1.895 4.249 2.641 1.32 0.747 2.066 0.918 2.583 0.631 1.148-0.631 0.287-2.698-0.861-3.444-1.091-0.746-6.43-0.919-5.971 0.172z"/>\r
+   <path fill="#d19800" d="m169.264 83.804c0.777 1.612 2.778 1.834 4.112 2.557 1.279 0.723 2.001 0.889 2.501 0.611 1.112-0.611 0.278-2.612-0.834-3.335-1.055-0.722-6.224-0.889-5.779 0.167z"/>\r
+   <path fill="#cc9200" d="m169.406 83.826c0.753 1.559 2.688 1.774 3.979 2.473 1.236 0.699 1.936 0.86 2.42 0.591 1.074-0.591 0.269-2.527-0.808-3.226-1.021-0.699-6.021-0.86-5.591 0.162z"/>\r
+   <path fill="#c68c00" d="m169.549 83.847c0.728 1.506 2.597 1.714 3.844 2.389 1.194 0.675 1.869 0.831 2.337 0.571 1.039-0.571 0.26-2.441-0.779-3.116-0.988-0.675-5.818-0.831-5.402 0.156z"/>\r
+   <path fill="#c18700" d="m169.691 83.869c0.702 1.453 2.506 1.654 3.709 2.305 1.152 0.652 1.803 0.802 2.254 0.551 1.002-0.551 0.251-2.355-0.751-3.006-0.953-0.652-5.613-0.802-5.212 0.15z"/>\r
+   <path fill="#bc8100" d="m169.833 83.89c0.677 1.4 2.415 1.594 3.574 2.221 1.111 0.628 1.738 0.772 2.173 0.531 0.965-0.531 0.241-2.27-0.725-2.897-0.917-0.627-5.408-0.772-5.022 0.145z"/>\r
+   <path fill="#b77b00" d="m169.976 83.912c0.65 1.347 2.322 1.533 3.438 2.137 1.069 0.604 1.673 0.743 2.091 0.511 0.93-0.511 0.233-2.184-0.696-2.788-0.884-0.603-5.205-0.743-4.833 0.14z"/>\r
+   <path fill="#b27500" d="m170.118 83.934c0.626 1.294 2.232 1.473 3.304 2.053 1.027 0.581 1.606 0.714 2.009 0.491 0.893-0.491 0.224-2.098-0.669-2.678-0.85-0.58-5.001-0.715-4.644 0.134z"/>\r
+   <path fill="#ad7000" d="m170.261 83.955c0.6 1.242 2.14 1.413 3.168 1.97 0.984 0.557 1.541 0.685 1.927 0.47 0.855-0.47 0.214-2.012-0.644-2.569-0.812-0.555-4.794-0.684-4.451 0.129z"/>\r
+   <path fill="#a86a00" d="m170.403 83.977c0.574 1.189 2.05 1.353 3.034 1.886 0.942 0.533 1.475 0.656 1.844 0.45 0.82-0.45 0.205-1.926-0.615-2.459-0.779-0.533-4.591-0.656-4.263 0.123z"/>\r
+   <path fill="#a36400" d="m170.545 83.998c0.55 1.136 1.959 1.292 2.899 1.802 0.901 0.509 1.41 0.626 1.762 0.43 0.783-0.43 0.197-1.841-0.587-2.35-0.745-0.508-4.387-0.626-4.074 0.118z"/>\r
+   <path fill="#9e5e00" d="m170.688 84.02c0.522 1.083 1.867 1.232 2.764 1.718 0.859 0.486 1.343 0.597 1.68 0.41 0.746-0.41 0.188-1.755-0.561-2.241-0.709-0.484-4.182-0.597-3.883 0.113z"/>\r
+   <path fill="#995900" d="m170.83 84.042c0.498 1.03 1.776 1.172 2.629 1.634 0.817 0.462 1.278 0.568 1.599 0.39 0.71-0.39 0.178-1.669-0.533-2.131-0.676-0.461-3.979-0.568-3.695 0.107z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path fill="#fc0" d="m152.875 86.29c-0.325 0.813 1.952 2.359 3.091 1.301 1.222-1.057 2.686-2.033 3.175-2.359 2.195-1.465 1.383-2.522-2.278-1.871-3.663 0.651-3.663 2.115-3.988 2.929z"/>\r
+   <path fill="#f9c600" d="m152.934 86.279c-0.318 0.794 1.906 2.304 3.019 1.271 1.193-1.033 2.623-1.986 3.102-2.305 2.145-1.431 1.351-2.463-2.226-1.828-3.578 0.637-3.578 2.067-3.895 2.862z"/>\r
+   <path fill="#f4c000" d="m152.993 86.269c-0.31 0.775 1.861 2.25 2.948 1.241 1.164-1.008 2.56-1.939 3.026-2.25 2.095-1.397 1.319-2.405-2.173-1.784-3.491 0.62-3.491 2.017-3.801 2.793z"/>\r
+   <path fill="#efba00" d="m153.051 86.258c-0.302 0.757 1.817 2.195 2.878 1.211 1.136-0.984 2.497-1.892 2.952-2.195 2.044-1.363 1.287-2.347-2.118-1.741-3.409 0.606-3.409 1.968-3.712 2.725z"/>\r
+   <path fill="#eab500" d="m153.11 86.248c-0.295 0.738 1.771 2.141 2.805 1.181 1.108-0.959 2.437-1.845 2.88-2.141 1.993-1.329 1.255-2.289-2.066-1.698-3.324 0.591-3.324 1.92-3.619 2.658z"/>\r
+   <path fill="#e5af00" d="m153.169 86.238c-0.287 0.719 1.727 2.086 2.733 1.151 1.08-0.935 2.374-1.798 2.807-2.086 1.942-1.296 1.224-2.23-2.015-1.655s-3.238 1.87-3.525 2.59z"/>\r
+   <path fill="#e0a900" d="m153.228 86.228c-0.28 0.7 1.681 2.032 2.661 1.121 1.052-0.91 2.312-1.751 2.732-2.031 1.893-1.262 1.191-2.172-1.961-1.611-3.152 0.559-3.152 1.82-3.432 2.521z"/>\r
+   <path fill="#dba300" d="m153.286 86.217c-0.271 0.681 1.636 1.977 2.591 1.09 1.023-0.886 2.25-1.704 2.659-1.977 1.84-1.228 1.159-2.114-1.909-1.568-3.068 0.547-3.068 1.773-3.341 2.455z"/>\r
+   <path fill="#d69e00" d="m153.345 86.207c-0.265 0.662 1.591 1.922 2.519 1.061 0.995-0.862 2.188-1.657 2.586-1.922 1.789-1.194 1.127-2.055-1.855-1.525-2.985 0.53-2.985 1.723-3.25 2.386z"/>\r
+   <path fill="#d19800" d="m153.404 86.197c-0.257 0.643 1.546 1.868 2.447 1.03 0.967-0.837 2.126-1.61 2.512-1.868 1.739-1.16 1.095-1.997-1.803-1.481-2.899 0.516-2.899 1.674-3.156 2.319z"/>\r
+   <path fill="#cc9200" d="m153.463 86.187c-0.25 0.625 1.5 1.813 2.375 1 0.939-0.813 2.064-1.563 2.439-1.813 1.688-1.126 1.063-1.938-1.75-1.438-2.814 0.5-2.814 1.625-3.064 2.251z"/>\r
+   <path fill="#c68c00" d="m153.521 86.176c-0.242 0.605 1.456 1.758 2.304 0.97 0.911-0.788 2.002-1.516 2.366-1.758 1.637-1.092 1.031-1.88-1.698-1.395-2.729 0.486-2.729 1.576-2.972 2.183z"/>\r
+   <path fill="#c18700" d="m153.58 86.166c-0.233 0.587 1.41 1.704 2.233 0.939 0.882-0.763 1.938-1.469 2.292-1.704 1.586-1.058 0.999-1.822-1.646-1.352-2.644 0.472-2.644 1.529-2.879 2.117z"/>\r
+   <path fill="#bc8100" d="m153.639 86.156c-0.228 0.568 1.364 1.649 2.16 0.91 0.854-0.739 1.878-1.422 2.219-1.649 1.536-1.024 0.967-1.764-1.593-1.308s-2.559 1.477-2.786 2.047z"/>\r
+   <path fill="#b77b00" d="m153.698 86.146c-0.22 0.549 1.32 1.594 2.089 0.879 0.825-0.715 1.815-1.375 2.146-1.595 1.484-0.99 0.935-1.705-1.54-1.265s-2.475 1.43-2.695 1.981z"/>\r
+   <path fill="#b27500" d="m153.756 86.135c-0.211 0.53 1.275 1.54 2.019 0.85 0.797-0.69 1.753-1.328 2.072-1.54 1.434-0.957 0.902-1.646-1.487-1.221s-2.391 1.38-2.604 1.911z"/>\r
+   <path fill="#ad7000" d="m153.815 86.125c-0.204 0.512 1.229 1.486 1.946 0.82 0.769-0.666 1.69-1.281 1.997-1.486 1.385-0.922 0.871-1.588-1.434-1.178s-2.304 1.331-2.509 1.844z"/>\r
+   <path fill="#a86a00" d="m153.874 86.114c-0.196 0.493 1.185 1.431 1.875 0.79 0.74-0.642 1.628-1.234 1.924-1.431 1.332-0.889 0.84-1.53-1.381-1.135s-2.221 1.283-2.418 1.776z"/>\r
+   <path fill="#a36400" d="m153.933 86.104c-0.189 0.474 1.139 1.376 1.803 0.759 0.712-0.617 1.566-1.187 1.851-1.376 1.281-0.855 0.808-1.472-1.329-1.092-2.135 0.38-2.135 1.234-2.325 1.709z"/>\r
+   <path fill="#9e5e00" d="m153.991 86.094c-0.181 0.455 1.095 1.322 1.732 0.729 0.684-0.592 1.504-1.14 1.776-1.321 1.231-0.821 0.775-1.414-1.274-1.048-2.051 0.364-2.051 1.184-2.234 1.64z"/>\r
+   <path fill="#995900" d="m154.05 86.083c-0.174 0.436 1.05 1.267 1.66 0.699 0.656-0.568 1.442-1.093 1.704-1.267 1.181-0.787 0.743-1.355-1.223-1.005s-1.966 1.136-2.141 1.573z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path fill="#fc0" d="m156.951 107.887c-0.229 2.858 6.343-4.286 6.743-4.915 0.856-1.543 3.715-5.886 4.172-7.715 0.857-3.2 2.401-5.543 1.429-8.915-0.343-1.086-2.742-1.372-3.829-0.687-3.086 1.829-2.629 4.058-2.972 6.115-1.143 5.831-5.143 11.717-5.543 16.117z"/>\r
+   <path fill="#ffcc02" d="m157.22 107.441c-0.22 2.787 6.178-4.188 6.566-4.802 0.833-1.506 3.614-5.745 4.056-7.529 0.831-3.122 2.333-5.408 1.382-8.695-0.337-1.058-2.678-1.333-3.735-0.663-3.006 1.788-2.557 3.96-2.889 5.967-1.105 5.685-4.997 11.431-5.38 15.722z"/>\r
+   <path fill="#ffcc05" d="m157.488 106.995c-0.209 2.715 6.014-4.091 6.392-4.69 0.811-1.469 3.513-5.603 3.941-7.342 0.804-3.043 2.264-5.273 1.331-8.474-0.329-1.031-2.61-1.295-3.64-0.64-2.927 1.747-2.486 3.863-2.806 5.818-1.068 5.541-4.851 11.146-5.218 15.328z"/>\r
+   <path fill="#ffcc07" d="m157.757 106.548c-0.198 2.645 5.85-3.993 6.217-4.577 0.785-1.431 3.409-5.461 3.824-7.156 0.779-2.964 2.196-5.138 1.282-8.253-0.322-1.003-2.543-1.257-3.545-0.618-2.847 1.706-2.414 3.766-2.722 5.67-1.031 5.398-4.706 10.862-5.056 14.934z"/>\r
+   <path fill="#ffcd0a" d="m158.026 106.102c-0.189 2.573 5.684-3.896 6.04-4.465 0.762-1.394 3.309-5.32 3.709-6.969 0.753-2.886 2.129-5.004 1.233-8.033-0.315-0.976-2.478-1.219-3.45-0.595-2.768 1.665-2.343 3.668-2.64 5.522-0.993 5.254-4.558 10.577-4.892 14.54z"/>\r
+   <path fill="#ffcd0c" d="m158.294 105.655c-0.179 2.503 5.52-3.798 5.865-4.351 0.738-1.357 3.207-5.179 3.594-6.783 0.727-2.807 2.061-4.869 1.185-7.813-0.309-0.948-2.411-1.18-3.356-0.572-2.687 1.623-2.271 3.571-2.556 5.374-0.958 5.11-4.414 10.291-4.732 14.145z"/>\r
+   <path fill="#ffcd0f" d="m158.563 105.209c-0.169 2.431 5.354-3.701 5.688-4.239 0.715-1.319 3.106-5.037 3.479-6.596 0.7-2.728 1.992-4.734 1.135-7.592-0.301-0.92-2.344-1.142-3.261-0.549-2.608 1.583-2.199 3.474-2.473 5.226-0.919 4.965-4.267 10.005-4.568 13.75z"/>\r
+   <path fill="#ffcd11" d="m158.831 104.762c-0.159 2.361 5.19-3.602 5.515-4.126 0.69-1.282 3.004-4.896 3.361-6.409 0.674-2.649 1.924-4.599 1.087-7.372-0.295-0.893-2.277-1.104-3.167-0.526-2.527 1.541-2.128 3.376-2.389 5.077-0.883 4.822-4.122 9.721-4.407 13.356z"/>\r
+   <path fill="#ffce14" d="m159.1 104.316c-0.149 2.289 5.024-3.505 5.338-4.014 0.667-1.244 2.901-4.754 3.247-6.223 0.646-2.571 1.854-4.464 1.037-7.151-0.287-0.865-2.211-1.065-3.072-0.504-2.448 1.5-2.056 3.279-2.306 4.929-0.845 4.679-3.976 9.437-4.244 12.963z"/>\r
+   <path fill="#ffce16" d="m159.369 103.869c-0.139 2.219 4.86-3.407 5.162-3.9 0.643-1.208 2.801-4.613 3.131-6.037 0.622-2.492 1.787-4.329 0.988-6.93-0.28-0.838-2.146-1.027-2.978-0.481-2.368 1.459-1.983 3.182-2.223 4.781-0.807 4.533-3.829 9.151-4.08 12.567z"/>\r
+   <path fill="#ffce19" d="m159.637 103.423c-0.13 2.147 4.695-3.31 4.986-3.788 0.62-1.17 2.699-4.471 3.016-5.85 0.596-2.414 1.719-4.195 0.939-6.71-0.273-0.81-2.079-0.989-2.883-0.458-2.289 1.418-1.913 3.084-2.139 4.632-0.77 4.391-3.684 8.866-3.919 12.174z"/>\r
+   <path fill="#ffce1c" d="m159.906 102.977c-0.119 2.076 4.531-3.213 4.811-3.676 0.597-1.133 2.599-4.33 2.899-5.664 0.57-2.335 1.651-4.06 0.891-6.49-0.267-0.782-2.012-0.95-2.787-0.435-2.21 1.377-1.842 2.987-2.057 4.484-0.734 4.247-3.539 8.582-3.757 11.781z"/>\r
+   <path fill="#ffcf1e" d="m160.174 102.53c-0.108 2.005 4.366-3.115 4.637-3.563 0.571-1.096 2.496-4.189 2.784-5.478 0.543-2.256 1.581-3.925 0.841-6.269-0.26-0.754-1.945-0.912-2.693-0.412-2.129 1.336-1.77 2.889-1.973 4.336-0.697 4.103-3.394 8.297-3.596 11.386z"/>\r
+   <path fill="#ffcf21" d="m160.443 102.084c-0.099 1.934 4.201-3.018 4.46-3.45 0.548-1.059 2.394-4.047 2.668-5.291 0.517-2.178 1.514-3.79 0.793-6.049-0.253-0.727-1.879-0.874-2.599-0.39-2.051 1.295-1.698 2.792-1.891 4.188-0.658 3.959-3.246 8.012-3.431 10.992z"/>\r
+   <path fill="#ffcf23" d="m160.712 101.637c-0.089 1.863 4.036-2.919 4.283-3.337 0.526-1.021 2.294-3.905 2.553-5.104 0.491-2.099 1.447-3.655 0.744-5.828-0.246-0.699-1.813-0.835-2.505-0.367-1.969 1.253-1.625 2.694-1.805 4.04-0.623 3.814-3.101 7.726-3.27 10.596z"/>\r
+   <path fill="#ffcf26" d="m160.98 101.191c-0.079 1.792 3.872-2.822 4.107-3.225 0.502-0.984 2.192-3.764 2.438-4.918 0.464-2.02 1.378-3.52 0.694-5.607-0.238-0.672-1.746-0.797-2.41-0.344-1.89 1.212-1.555 2.597-1.723 3.891-0.583 3.671-2.953 7.442-3.106 10.203z"/>\r
+   <path fill="#ffd028" d="m161.249 100.744c-0.068 1.721 3.707-2.724 3.933-3.112 0.478-0.947 2.091-3.623 2.321-4.731 0.439-1.942 1.311-3.386 0.646-5.387-0.232-0.645-1.68-0.758-2.316-0.321-1.81 1.171-1.481 2.5-1.639 3.743-0.548 3.527-2.809 7.156-2.945 9.808z"/>\r
+   <path fill="#ffd02b" d="m161.517 100.298c-0.06 1.65 3.543-2.627 3.757-2.999 0.454-0.91 1.989-3.481 2.206-4.545 0.413-1.863 1.242-3.25 0.597-5.167-0.225-0.617-1.613-0.72-2.221-0.298-1.73 1.13-1.411 2.402-1.557 3.595-0.509 3.383-2.662 6.871-2.782 9.414z"/>\r
+   <path fill="#ffd02d" d="m161.786 99.852c-0.049 1.579 3.377-2.529 3.581-2.887 0.431-0.872 1.887-3.34 2.091-4.359 0.387-1.784 1.173-3.116 0.547-4.946-0.217-0.589-1.546-0.682-2.126-0.275-1.649 1.089-1.339 2.305-1.472 3.446-0.474 3.24-2.518 6.587-2.621 9.021z"/>\r
+   <path fill="#ffd030" d="m162.055 99.405c-0.039 1.508 3.212-2.432 3.404-2.773 0.407-0.835 1.786-3.199 1.976-4.172 0.359-1.706 1.104-2.981 0.499-4.726-0.211-0.562-1.481-0.644-2.032-0.253-1.571 1.048-1.268 2.208-1.389 3.298-0.436 3.096-2.372 6.302-2.458 8.626z"/>\r
+   <path fill="#ffd133" d="m162.323 98.958c-0.029 1.437 3.048-2.334 3.23-2.661 0.383-0.798 1.684-3.057 1.858-3.986 0.334-1.627 1.037-2.846 0.45-4.505-0.204-0.534-1.414-0.605-1.938-0.23-1.49 1.007-1.195 2.11-1.306 3.15-0.397 2.953-2.224 6.018-2.294 8.232z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path fill="#fc0" d="m179.646 95.994c-3.168 3.456-5.4 6.767-7.2 9-1.872 2.304-6.48 5.04-4.176 7.704 1.943 2.376 9.936-1.944 16.128-6.552 6.12-4.608 15.696-8.711 11.016-13.967-2.448-2.664-8.208-2.088-10.439-0.648-1.729 1.078-2.737 1.655-5.329 4.463z"/>\r
+   <path fill="#ffcc02" d="m179.782 96.147c-3.118 3.378-5.313 6.628-7.086 8.809-1.841 2.249-6.375 4.945-4.13 7.534 1.893 2.31 9.724-1.943 15.795-6.469 6.001-4.525 15.38-8.574 10.82-13.682-2.387-2.588-8.022-1.998-10.209-0.584-1.692 1.062-2.665 1.67-5.19 4.392z"/>\r
+   <path fill="#ffcc05" d="m179.919 96.3c-3.068 3.3-5.227 6.488-6.973 8.619-1.81 2.193-6.271 4.85-4.085 7.364 1.843 2.243 9.513-1.943 15.463-6.386 5.882-4.442 15.064-8.437 10.623-13.396-2.323-2.513-7.835-1.907-9.978-0.52-1.655 1.044-2.593 1.684-5.05 4.319z"/>\r
+   <path fill="#ffcc07" d="m180.055 96.454c-3.02 3.222-5.14 6.347-6.859 8.428-1.78 2.138-6.166 4.754-4.04 7.194 1.793 2.177 9.302-1.942 15.131-6.303 5.762-4.359 14.748-8.299 10.427-13.11-2.261-2.437-7.648-1.817-9.747-0.456-1.619 1.025-2.522 1.698-4.912 4.247z"/>\r
+   <path fill="#ffcd0a" d="m180.191 96.607c-2.97 3.143-5.052 6.207-6.745 8.237-1.749 2.082-6.063 4.659-3.994 7.023 1.743 2.111 9.09-1.941 14.798-6.219 5.644-4.276 14.433-8.162 10.231-12.824-2.199-2.361-7.463-1.727-9.518-0.392-1.581 1.008-2.45 1.713-4.772 4.175z"/>\r
+   <path fill="#ffcd0c" d="m180.327 96.761c-2.92 3.065-4.965 6.066-6.631 8.047-1.718 2.027-5.957 4.564-3.949 6.853 1.693 2.044 8.878-1.94 14.466-6.136 5.524-4.194 14.116-8.024 10.034-12.538-2.137-2.286-7.275-1.636-9.285-0.328-1.546 0.988-2.38 1.726-4.635 4.102z"/>\r
+   <path fill="#ffcd0f" d="m180.464 96.914c-2.871 2.987-4.879 5.926-6.518 7.857-1.688 1.971-5.854 4.468-3.903 6.683 1.643 1.978 8.666-1.94 14.133-6.053 5.404-4.111 13.801-7.887 9.839-12.251-2.075-2.21-7.091-1.546-9.056-0.264-1.509 0.969-2.308 1.738-4.495 4.028z"/>\r
+   <path fill="#ffcd11" d="m180.6 97.067c-2.821 2.909-4.792 5.786-6.404 7.667-1.657 1.916-5.748 4.373-3.858 6.512 1.593 1.912 8.455-1.938 13.802-5.969 5.284-4.028 13.484-7.75 9.641-11.966-2.012-2.134-6.902-1.456-8.823-0.199-1.474 0.951-2.238 1.752-4.358 3.955z"/>\r
+   <path fill="#ffce14" d="m180.736 97.221c-2.771 2.83-4.705 5.645-6.29 7.476-1.626 1.86-5.644 4.278-3.813 6.342 1.542 1.845 8.244-1.938 13.47-5.886 5.166-3.945 13.169-7.612 9.444-11.68-1.949-2.059-6.716-1.365-8.592-0.135-1.437 0.933-2.166 1.766-4.219 3.883z"/>\r
+   <path fill="#ffce16" d="m180.872 97.375c-2.722 2.752-4.617 5.504-6.176 7.286-1.595 1.805-5.539 4.182-3.767 6.172 1.49 1.779 8.031-1.937 13.136-5.803 5.046-3.862 12.853-7.475 9.249-11.394-1.889-1.983-6.53-1.274-8.362-0.071-1.4 0.914-2.095 1.779-4.08 3.81z"/>\r
+   <path fill="#ffce19" d="m181.009 97.528c-2.673 2.674-4.53 5.364-6.063 7.095-1.564 1.749-5.435 4.087-3.722 6.001 1.44 1.713 7.82-1.936 12.804-5.719 4.927-3.78 12.537-7.338 9.052-11.108-1.825-1.907-6.343-1.185-8.13-0.007-1.364 0.896-2.024 1.793-3.941 3.738z"/>\r
+   <path fill="#ffce1c" d="m181.145 97.682c-2.623 2.595-4.444 5.225-5.949 6.904-1.534 1.693-5.33 3.992-3.676 5.831 1.39 1.646 7.608-1.935 12.471-5.636 4.808-3.697 12.221-7.2 8.856-10.822-1.764-1.832-6.157-1.094-7.9 0.057-1.327 0.878-1.952 1.807-3.802 3.666z"/>\r
+   <path fill="#ffcf1e" d="m181.281 97.835c-2.573 2.517-4.357 5.084-5.835 6.714-1.503 1.638-5.226 3.896-3.631 5.661 1.34 1.58 7.396-1.935 12.139-5.553 4.689-3.614 11.905-7.063 8.659-10.536-1.701-1.756-5.97-1.004-7.668 0.121-1.291 0.86-1.881 1.821-3.664 3.593z"/>\r
+   <path fill="#ffcf21" d="m181.417 97.988c-2.522 2.439-4.27 4.944-5.721 6.524-1.472 1.582-5.121 3.801-3.586 5.491 1.29 1.513 7.186-1.934 11.807-5.47 4.569-3.531 11.589-6.926 8.463-10.25-1.639-1.68-5.783-0.914-7.438 0.186-1.254 0.84-1.809 1.834-3.525 3.519z"/>\r
+   <path fill="#ffcf23" d="m181.554 98.142c-2.476 2.361-4.185 4.803-5.608 6.333-1.441 1.527-5.017 3.706-3.54 5.32 1.24 1.447 6.974-1.933 11.474-5.386 4.45-3.448 11.273-6.788 8.268-9.964-1.577-1.605-5.599-0.823-7.207 0.25-1.22 0.822-1.74 1.848-3.387 3.447z"/>\r
+   <path fill="#ffcf26" d="m181.69 98.295c-2.425 2.283-4.098 4.663-5.494 6.143-1.411 1.471-4.912 3.61-3.495 5.15 1.19 1.381 6.763-1.932 11.142-5.303 4.331-3.366 10.957-6.65 8.07-9.679-1.514-1.529-5.411-0.732-6.976 0.313-1.182 0.805-1.667 1.863-3.247 3.376z"/>\r
+   <path fill="#ffd028" d="m181.826 98.449c-2.375 2.204-4.009 4.522-5.38 5.952-1.38 1.416-4.808 3.515-3.449 4.98 1.14 1.314 6.551-1.932 10.81-5.22 4.211-3.283 10.641-6.513 7.874-9.393-1.452-1.454-5.226-0.642-6.745 0.378-1.147 0.786-1.597 1.876-3.11 3.303z"/>\r
+   <path fill="#ffd02b" d="m181.962 98.602c-2.324 2.127-3.922 4.382-5.266 5.762-1.349 1.36-4.703 3.42-3.404 4.809 1.089 1.248 6.34-1.93 10.478-5.136 4.092-3.2 10.325-6.376 7.677-9.106-1.389-1.378-5.038-0.552-6.513 0.441-1.111 0.768-1.526 1.89-2.972 3.23z"/>\r
+   <path fill="#ffd02d" d="m182.099 98.756c-2.276 2.048-3.836 4.241-5.153 5.571-1.318 1.305-4.599 3.324-3.359 4.639 1.039 1.182 6.128-1.93 10.146-5.053 3.973-3.117 10.009-6.238 7.48-8.82-1.328-1.303-4.852-0.462-6.282 0.506-1.074 0.748-1.454 1.903-2.832 3.157z"/>\r
+   <path fill="#ffd030" d="m182.235 98.909c-2.228 1.97-3.749 4.101-5.039 5.381-1.288 1.249-4.494 3.229-3.313 4.469 0.988 1.115 5.916-1.929 9.813-4.97 3.853-3.034 9.693-6.101 7.285-8.535-1.267-1.227-4.666-0.371-6.052 0.57-1.038 0.731-1.384 1.918-2.694 3.085z"/>\r
+   <path fill="#ffd133" d="m182.371 99.063c-2.177 1.892-3.662 3.96-4.925 5.19-1.257 1.193-4.39 3.133-3.268 4.298 0.938 1.049 5.704-1.928 9.479-4.886 3.734-2.952 9.377-5.963 7.088-8.249-1.203-1.151-4.479-0.281-5.821 0.634-0.999 0.713-1.31 1.931-2.553 3.013z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path fill="#fff" d="m186.414 168.569c0.864-2.808 28.872-9.432 33.48-7.272 4.536 2.16 26.279 33.768 22.392 35.496-3.888 1.657-12.24-10.512-24.408-16.128s-32.328-9.216-31.464-12.096z"/>\r
+   <path fill="#f9f9f9" d="m187.239 168.626c0.848-2.761 28.145-9.076 32.69-6.997 4.476 2.079 25.768 32.897 21.943 34.591-3.824 1.625-11.965-10.346-23.94-15.874-11.976-5.527-31.541-8.89-30.693-11.72z"/>\r
+   <path fill="#f4f4f4" d="m188.063 168.683c0.832-2.714 27.418-8.72 31.899-6.722 4.417 1.998 25.259 32.026 21.497 33.685-3.76 1.595-11.689-10.18-23.474-15.619-11.782-5.438-30.754-8.565-29.922-11.344z"/>\r
+   <path fill="#efefef" d="m188.888 168.74c0.814-2.668 26.69-8.364 31.109-6.447 4.357 1.917 24.746 31.155 21.049 32.779-3.695 1.563-11.416-10.014-23.007-15.364-11.59-5.349-29.967-8.239-29.151-10.968z"/>\r
+   <path fill="#eaeaea" d="m189.712 168.797c0.801-2.621 25.964-8.009 30.32-6.173 4.299 1.837 24.235 30.285 20.603 31.874-3.633 1.532-11.142-9.847-22.54-15.109-11.4-5.261-29.182-7.914-28.383-10.592z"/>\r
+   <path fill="#e5e5e5" d="m190.537 168.853c0.783-2.573 25.236-7.652 29.53-5.897 4.239 1.756 23.723 29.414 20.155 30.968-3.569 1.501-10.867-9.681-22.074-14.854-11.206-5.172-28.395-7.589-27.611-10.217z"/>\r
+   <path fill="#e0e0e0" d="m191.361 168.91c0.768-2.527 24.51-7.296 28.74-5.622 4.18 1.675 23.212 28.543 19.708 30.063-3.505 1.469-10.593-9.516-21.607-14.6-11.014-5.083-27.608-7.263-26.841-9.841z"/>\r
+   <path fill="#dbdbdb" d="m192.186 168.967c0.751-2.48 23.781-6.941 27.95-5.347 4.119 1.593 22.7 27.671 19.26 29.157-3.441 1.438-10.318-9.349-21.141-14.345-10.821-4.994-26.821-6.938-26.069-9.465z"/>\r
+   <path fill="#d6d6d6" d="m193.01 169.024c0.735-2.433 23.057-6.585 27.16-5.073 4.062 1.513 22.19 26.801 18.813 28.252-3.377 1.407-10.043-9.183-20.673-14.09-10.629-4.906-26.035-6.612-25.3-9.089z"/>\r
+   <path fill="#d1d1d1" d="m193.835 169.081c0.72-2.387 22.328-6.229 26.37-4.798 4.001 1.432 21.678 25.93 18.365 27.346-3.313 1.376-9.768-9.017-20.206-13.835-10.437-4.817-25.248-6.287-24.529-8.713z"/>\r
+   <path fill="#ccc" d="m194.659 169.137c0.703-2.339 21.603-5.873 25.58-4.521 3.942 1.351 21.167 25.059 17.918 26.44-3.249 1.345-9.493-8.851-19.739-13.58-10.245-4.729-24.462-5.963-23.759-8.339z"/>\r
+   <path fill="#c6c6c6" d="m195.484 169.194c0.687-2.292 20.874-5.517 24.79-4.247 3.882 1.27 20.655 24.188 17.47 25.535-3.185 1.314-9.219-8.685-19.271-13.326-10.054-4.639-23.676-5.636-22.989-7.962z"/>\r
+   <path fill="#c1c1c1" d="m196.308 169.251c0.671-2.246 20.147-5.161 24-3.973 3.822 1.19 20.145 23.318 17.022 24.63-3.121 1.283-8.943-8.519-18.805-13.071-9.859-4.551-22.888-5.311-22.217-7.586z"/>\r
+   <path fill="#bcbcbc" d="m197.133 169.308c0.654-2.199 19.421-4.805 23.21-3.698 3.764 1.109 19.634 22.447 16.575 23.724-3.057 1.252-8.669-8.353-18.338-12.816-9.668-4.462-22.102-4.985-21.447-7.21z"/>\r
+   <path fill="#b7b7b7" d="m197.957 169.365c0.64-2.152 18.693-4.45 22.42-3.423 3.705 1.027 19.122 21.575 16.129 22.818-2.993 1.221-8.395-8.186-17.872-12.561-9.476-4.373-21.315-4.66-20.677-6.834z"/>\r
+   <path fill="#b2b2b2" d="m198.782 169.421c0.622-2.105 17.966-4.093 21.63-3.147 3.646 0.946 18.61 20.704 15.681 21.912-2.93 1.19-8.12-8.02-17.404-12.306-9.284-4.284-20.53-4.335-19.907-6.459z"/>\r
+   <path fill="#adadad" d="m199.606 169.478c0.606-2.058 17.239-3.737 20.84-2.873 3.586 0.866 18.099 19.834 15.234 21.008-2.866 1.158-7.847-7.855-16.938-12.052-9.091-4.196-19.742-4.009-19.136-6.083z"/>\r
+   <path fill="#a8a8a8" d="m200.431 169.535c0.59-2.011 16.512-3.382 20.05-2.598 3.525 0.785 17.588 18.963 14.786 20.102-2.803 1.127-7.571-7.688-16.472-11.797-8.898-4.107-18.955-3.684-18.364-5.707z"/>\r
+   <path fill="#a3a3a3" d="m201.255 169.592c0.574-1.965 15.785-3.026 19.261-2.323 3.467 0.704 17.076 18.092 14.339 19.196-2.738 1.096-7.296-7.522-16.004-11.542-8.707-4.018-18.17-3.358-17.596-5.331z"/>\r
+   <path fill="#9e9e9e" d="m202.08 169.649c0.559-1.918 15.059-2.67 18.47-2.048 3.407 0.623 16.565 17.221 13.892 18.29-2.674 1.065-7.022-7.356-15.537-11.287-8.515-3.929-17.383-3.033-16.825-4.955z"/>\r
+   <path fill="#999" d="m202.904 169.705c0.542-1.871 14.331-2.314 17.68-1.773 3.349 0.542 16.055 16.35 13.444 17.385-2.61 1.034-6.747-7.19-15.07-11.032-8.322-3.841-16.596-2.708-16.054-4.58z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path fill="#fff" d="m151.134 211.625c2.881 0.144 0.145 16.271 0.145 32.903s2.231 22.464 0.144 24.552-5.688-5.399-5.688-22.031c-0.001-16.632 2.519-35.568 5.399-35.424z"/>\r
+   <path fill="#f9f9f9" d="m151.105 212.016c2.783 0.162 0.109 16.052 0.097 32.419-0.012 16.366 2.188 22.208 0.164 24.237-2.02 2.029-5.561-5.383-5.546-21.752 0.012-16.367 2.502-35.065 5.285-34.904z"/>\r
+   <path fill="#f4f4f4" d="m151.075 212.407c2.687 0.18 0.076 15.832 0.051 31.934-0.023 16.102 2.143 21.951 0.185 23.924-1.953 1.968-5.435-5.367-5.405-21.473 0.024-16.103 2.484-34.564 5.169-34.385z"/>\r
+   <path fill="#efefef" d="m151.046 212.797c2.588 0.197 0.041 15.613 0.004 31.449-0.036 15.836 2.098 21.694 0.204 23.609-1.886 1.907-5.308-5.352-5.263-21.195 0.037-15.835 2.467-34.058 5.055-33.863z"/>\r
+   <path fill="#eaeaea" d="m151.017 213.189c2.49 0.214 0.007 15.392-0.043 30.962-0.05 15.571 2.052 21.439 0.224 23.297-1.818 1.848-5.181-5.334-5.122-20.916 0.049-15.571 2.45-33.557 4.941-33.343z"/>\r
+   <path fill="#e5e5e5" d="m150.987 213.581c2.394 0.23-0.027 15.17-0.089 30.477s2.007 21.182 0.244 22.982c-1.751 1.787-5.055-5.32-4.98-20.638 0.061-15.305 2.431-33.053 4.825-32.821z"/>\r
+   <path fill="#e0e0e0" d="m150.958 213.971c2.297 0.248-0.062 14.951-0.136 29.99-0.074 15.041 1.962 20.927 0.264 22.668-1.683 1.728-4.928-5.301-4.839-20.356 0.074-15.04 2.414-32.551 4.711-32.302z"/>\r
+   <path fill="#dbdbdb" d="m150.928 214.362c2.199 0.266-0.096 14.73-0.182 29.506-0.087 14.775 1.915 20.67 0.282 22.354-1.615 1.667-4.8-5.286-4.696-20.078 0.087-14.776 2.397-32.048 4.596-31.782z"/>\r
+   <path fill="#d6d6d6" d="m150.899 214.752c2.102 0.283-0.13 14.511-0.229 29.021-0.099 14.511 1.87 20.413 0.303 22.04-1.549 1.607-4.674-5.27-4.556-19.799 0.1-14.51 2.38-31.545 4.482-31.262z"/>\r
+   <path fill="#d1d1d1" d="m150.87 215.144c2.005 0.301-0.165 14.29-0.274 28.535-0.112 14.245 1.824 20.155 0.321 21.725-1.479 1.548-4.547-5.252-4.413-19.519 0.11-14.245 2.361-31.043 4.366-30.741z"/>\r
+   <path fill="#ccc" d="m150.84 215.536c1.908 0.317-0.197 14.069-0.32 28.049-0.124 13.979 1.779 19.899 0.342 21.412-1.413 1.486-4.42-5.238-4.272-19.242 0.122-13.979 2.343-30.54 4.25-30.219z"/>\r
+   <path fill="#c6c6c6" d="m150.811 215.926c1.811 0.334-0.233 13.85-0.368 27.564-0.136 13.713 1.735 19.643 0.362 21.096-1.346 1.428-4.293-5.219-4.131-18.961 0.136-13.712 2.327-30.035 4.137-29.699z"/>\r
+   <path fill="#c1c1c1" d="m150.781 216.317c1.714 0.354-0.267 13.629-0.414 27.078s1.69 19.387 0.382 20.783c-1.277 1.367-4.166-5.203-3.989-18.682 0.148-13.449 2.308-29.533 4.021-29.179z"/>\r
+   <path fill="#bcbcbc" d="m150.752 216.708c1.616 0.371-0.301 13.41-0.461 26.594-0.161 13.184 1.646 19.13 0.402 20.469-1.211 1.307-4.04-5.188-3.847-18.402 0.16-13.185 2.29-29.033 3.906-28.661z"/>\r
+   <path fill="#b7b7b7" d="m150.723 217.099c1.519 0.387-0.336 13.188-0.509 26.106-0.173 12.92 1.601 18.875 0.423 20.156-1.144 1.246-3.913-5.171-3.706-18.123 0.172-12.919 2.273-28.529 3.792-28.139z"/>\r
+   <path fill="#b2b2b2" d="m150.693 217.491c1.422 0.404-0.37 12.969-0.554 25.621-0.186 12.653 1.555 18.617 0.441 19.842-1.076 1.187-3.786-5.156-3.563-17.846 0.184-12.652 2.255-28.024 3.676-27.617z"/>\r
+   <path fill="#adadad" d="m150.664 217.881c1.325 0.422-0.404 12.748-0.601 25.136-0.198 12.388 1.51 18.36 0.462 19.528-1.008 1.125-3.66-5.139-3.423-17.566 0.197-12.389 2.238-27.521 3.562-27.098z"/>\r
+   <path fill="#a8a8a8" d="m150.634 218.272c1.229 0.439-0.438 12.527-0.646 24.65-0.21 12.123 1.464 18.104 0.48 19.213-0.939 1.066-3.531-5.121-3.279-17.285 0.208-12.123 2.219-27.019 3.445-26.578z"/>\r
+   <path fill="#a3a3a3" d="m150.605 218.663c1.13 0.457-0.474 12.309-0.694 24.166-0.222 11.857 1.419 17.848 0.501 18.899-0.873 1.006-3.405-5.106-3.139-17.009 0.222-11.855 2.202-26.515 3.332-26.056z"/>\r
+   <path fill="#9e9e9e" d="m150.576 219.054c1.033 0.474-0.507 12.088-0.741 23.68-0.234 11.593 1.374 17.591 0.521 18.585-0.806 0.946-3.279-5.089-2.997-16.729 0.233-11.591 2.184-26.011 3.217-25.536z"/>\r
+   <path fill="#999" d="m150.546 219.444c0.937 0.492-0.541 11.868-0.787 23.195-0.246 11.326 1.329 17.335 0.541 18.271-0.737 0.885-3.151-5.074-2.855-16.449 0.245-11.328 2.166-25.509 3.101-25.017z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path fill="#fff" d="m157.434 167.161c1.735 0.192 12.437-2.218 12.822-1.254 0.386 0.772-6.651 2.893-8.966 5.303-0.771 0.771-2.796 2.603-4.049 2.41-0.964-0.096-1.543-2.121-2.989-3.664-3.471-3.47-5.688-3.181-5.013-4.531 0.579-1.06 5.207 1.446 8.195 1.736z"/>\r
+   <path fill="#fbfbfb" d="m157.479 167.201c1.7 0.188 12.176-2.171 12.554-1.227 0.377 0.755-6.512 2.832-8.778 5.191-0.755 0.755-2.736 2.549-3.964 2.36-0.942-0.094-1.51-2.077-2.926-3.587-3.398-3.397-5.568-3.115-4.907-4.436 0.565-1.038 5.096 1.415 8.021 1.699z"/>\r
+   <path fill="#f8f8f8" d="m157.525 167.241c1.663 0.184 11.914-2.124 12.283-1.201 0.369 0.739-6.372 2.771-8.589 5.08-0.738 0.739-2.679 2.494-3.879 2.309-0.924-0.092-1.479-2.032-2.863-3.51-3.325-3.324-5.449-3.048-4.803-4.341 0.555-1.015 4.988 1.385 7.851 1.663z"/>\r
+   <path fill="#f5f5f5" d="m157.57 167.281c1.626 0.18 11.652-2.078 12.014-1.175 0.361 0.723-6.231 2.711-8.4 4.969-0.723 0.722-2.619 2.439-3.793 2.258-0.903-0.09-1.446-1.987-2.802-3.433-3.252-3.251-5.329-2.981-4.695-4.245 0.54-0.993 4.876 1.354 7.676 1.626z"/>\r
+   <path fill="#f2f2f2" d="m157.615 167.321c1.59 0.176 11.391-2.031 11.745-1.148 0.352 0.706-6.093 2.649-8.212 4.856-0.707 0.707-2.562 2.385-3.709 2.208-0.883-0.088-1.413-1.943-2.738-3.356-3.179-3.178-5.209-2.914-4.591-4.15 0.529-0.971 4.768 1.324 7.505 1.59z"/>\r
+   <path fill="#efefef" d="m157.66 167.361c1.554 0.172 11.13-1.985 11.475-1.122 0.346 0.69-5.952 2.589-8.022 4.745-0.69 0.69-2.503 2.33-3.624 2.157-0.863-0.086-1.381-1.898-2.675-3.279-3.106-3.105-5.09-2.847-4.486-4.055 0.517-0.948 4.658 1.294 7.332 1.554z"/>\r
+   <path fill="#ebebeb" d="m157.705 167.401c1.518 0.168 10.868-1.938 11.206-1.096 0.336 0.674-5.813 2.528-7.835 4.634-0.674 0.674-2.444 2.275-3.539 2.106-0.842-0.084-1.348-1.853-2.612-3.202-3.032-3.032-4.97-2.78-4.38-3.959 0.505-0.926 4.549 1.263 7.16 1.517z"/>\r
+   <path fill="#e8e8e8" d="m157.751 167.441c1.48 0.164 10.606-1.892 10.936-1.069 0.329 0.657-5.673 2.467-7.646 4.522-0.658 0.657-2.385 2.22-3.453 2.055-0.822-0.082-1.315-1.809-2.549-3.124-2.96-2.96-4.851-2.714-4.275-3.865 0.491-0.904 4.438 1.233 6.987 1.481z"/>\r
+   <path fill="#e5e5e5" d="m157.796 167.481c1.444 0.16 10.346-1.845 10.666-1.043 0.32 0.641-5.532 2.406-7.458 4.41-0.641 0.642-2.325 2.166-3.367 2.005-0.803-0.08-1.284-1.764-2.486-3.047-2.887-2.887-4.732-2.647-4.17-3.769 0.48-0.882 4.329 1.202 6.815 1.444z"/>\r
+   <path fill="#e2e2e2" d="m157.841 167.521c1.407 0.156 10.083-1.799 10.397-1.017 0.312 0.625-5.394 2.346-7.271 4.299-0.625 0.625-2.267 2.111-3.282 1.954-0.782-0.078-1.251-1.719-2.423-2.97-2.814-2.814-4.612-2.58-4.065-3.674 0.469-0.859 4.221 1.172 6.644 1.408z"/>\r
+   <path fill="#dfdfdf" d="m157.886 167.56c1.37 0.152 9.821-1.751 10.127-0.99 0.304 0.609-5.254 2.285-7.081 4.188-0.609 0.609-2.208 2.056-3.198 1.903-0.761-0.076-1.218-1.675-2.36-2.893-2.741-2.741-4.492-2.513-3.959-3.579 0.456-0.837 4.111 1.142 6.471 1.371z"/>\r
+   <path fill="#dbdbdb" d="m157.931 167.6c1.335 0.148 9.561-1.704 9.857-0.963 0.296 0.592-5.114 2.223-6.893 4.076-0.593 0.593-2.149 2.001-3.113 1.853-0.741-0.074-1.186-1.631-2.297-2.817-2.668-2.667-4.373-2.446-3.854-3.483 0.445-0.815 4.003 1.111 6.3 1.334z"/>\r
+   <path fill="#d8d8d8" d="m157.977 167.64c1.298 0.144 9.299-1.658 9.587-0.937 0.288 0.576-4.974 2.163-6.704 3.964-0.576 0.577-2.091 1.947-3.027 1.803-0.721-0.072-1.153-1.586-2.234-2.74-2.596-2.594-4.253-2.379-3.748-3.388 0.431-0.792 3.891 1.081 6.126 1.298z"/>\r
+   <path fill="#d5d5d5" d="m158.022 167.68c1.261 0.14 9.037-1.611 9.317-0.911 0.28 0.56-4.834 2.102-6.516 3.853-0.56 0.561-2.032 1.892-2.942 1.752-0.7-0.07-1.12-1.541-2.172-2.663-2.521-2.521-4.133-2.312-3.643-3.292 0.421-0.77 3.784 1.05 5.956 1.261z"/>\r
+   <path fill="#d2d2d2" d="m158.067 167.72c1.225 0.136 8.775-1.564 9.049-0.884 0.271 0.543-4.695 2.041-6.327 3.741-0.545 0.544-1.974 1.837-2.857 1.701-0.682-0.068-1.09-1.497-2.109-2.585-2.449-2.449-4.014-2.246-3.538-3.198 0.407-0.748 3.673 1.02 5.782 1.225z"/>\r
+   <path fill="#cfcfcf" d="m158.112 167.76c1.188 0.132 8.515-1.518 8.779-0.858 0.264 0.527-4.555 1.98-6.139 3.63-0.527 0.528-1.915 1.782-2.772 1.65-0.66-0.066-1.057-1.452-2.046-2.508-2.376-2.376-3.895-2.179-3.433-3.103 0.397-0.725 3.565 0.99 5.611 1.189z"/>\r
+   <path fill="#ccc" d="m158.157 167.8c1.152 0.128 8.253-1.472 8.51-0.832 0.255 0.511-4.415 1.92-5.95 3.518-0.512 0.512-1.855 1.728-2.688 1.6-0.64-0.064-1.023-1.407-1.983-2.431-2.303-2.303-3.773-2.112-3.326-3.007 0.383-0.703 3.454 0.959 5.437 1.152z"/>\r
+   <path fill="#c8c8c8" d="m158.203 167.84c1.115 0.124 7.991-1.425 8.239-0.805 0.248 0.494-4.274 1.858-5.761 3.406-0.496 0.496-1.798 1.673-2.603 1.549-0.62-0.063-0.992-1.363-1.921-2.354-2.229-2.229-3.655-2.045-3.221-2.912 0.372-0.681 3.346 0.929 5.267 1.116z"/>\r
+   <path fill="#c5c5c5" d="m158.248 167.88c1.079 0.12 7.73-1.379 7.97-0.779 0.239 0.478-4.135 1.798-5.572 3.295-0.479 0.479-1.739 1.618-2.518 1.498-0.6-0.06-0.959-1.318-1.857-2.277-2.157-2.157-3.535-1.978-3.116-2.816 0.359-0.659 3.235 0.898 5.093 1.079z"/>\r
+   <path fill="#c2c2c2" d="m158.293 167.92c1.042 0.116 7.469-1.332 7.701-0.753 0.231 0.462-3.995 1.737-5.385 3.184-0.463 0.463-1.68 1.563-2.432 1.447-0.579-0.058-0.927-1.273-1.796-2.2-2.084-2.084-3.415-1.911-3.01-2.721 0.348-0.636 3.127 0.868 4.922 1.043z"/>\r
+   <path fill="#bfbfbf" d="m158.338 167.959c1.007 0.112 7.207-1.285 7.432-0.726 0.223 0.446-3.855 1.676-5.196 3.072-0.447 0.447-1.621 1.509-2.347 1.397-0.56-0.056-0.895-1.229-1.732-2.123-2.011-2.011-3.296-1.844-2.905-2.626 0.334-0.614 3.016 0.838 4.748 1.006z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path d="m194.253 11.922c-1.222 2.631-3.812 23.214-0.248 20.892 3.594-2.341 13.57-5.312 19.886-7.013 7.003-1.886-17.188-19.463-19.638-13.879z"/>\r
+   <path fill="#060606" d="m194.485 12.307c-1.21 2.594-3.704 22.255-0.234 20.007 3.491-2.262 13.077-5.1 19.039-6.782 6.59-1.905-16.436-18.609-18.805-13.225z"/>\r
+   <path fill="#0c0c0c" d="m194.717 12.691c-1.198 2.557-3.595 21.296-0.221 19.124 3.391-2.184 12.586-4.888 18.194-6.551 6.177-1.924-15.684-17.757-17.973-12.573z"/>\r
+   <path fill="#131313" d="m194.949 13.076c-1.187 2.52-3.487 20.337-0.207 18.239 3.288-2.105 12.093-4.676 17.348-6.321 5.763-1.941-14.933-16.903-17.141-11.918z"/>\r
+   <path fill="#191919" d="m195.181 13.46c-1.177 2.483-3.379 19.378-0.193 17.355 3.187-2.027 11.6-4.464 16.502-6.09 5.349-1.959-14.182-16.05-16.309-11.265z"/>\r
+   <path fill="#1f1f1f" d="m195.413 13.845c-1.164 2.446-3.27 18.419-0.18 16.471 3.086-1.949 11.107-4.252 15.657-5.859 4.935-1.978-13.43-15.198-15.477-10.612z"/>\r
+   <path fill="#262626" d="m195.645 14.229c-1.153 2.409-3.162 17.46-0.166 15.586 2.983-1.87 10.616-4.04 14.811-5.628 4.521-1.995-12.679-14.344-14.645-9.958z"/>\r
+   <path fill="#2c2c2c" d="m195.878 14.614c-1.142 2.372-3.055 16.501-0.152 14.702 2.882-1.792 10.123-3.828 13.965-5.398 4.107-2.013-11.929-13.49-13.813-9.304z"/>\r
+   <path fill="#333" d="m196.11 14.999c-1.131 2.335-2.946 15.542-0.14 13.817 2.78-1.713 9.631-3.616 13.119-5.167 3.695-2.031-11.175-12.637-12.979-8.65z"/>\r
+   <path fill="#393939" d="m196.342 15.383c-1.118 2.299-2.838 14.583-0.126 12.934 2.68-1.636 9.139-3.404 12.274-4.937 3.28-2.049-10.425-11.784-12.148-7.997z"/>\r
+   <path fill="#3f3f3f" d="m196.574 15.768c-1.108 2.261-2.729 13.624-0.112 12.049 2.577-1.557 8.646-3.192 11.429-4.706 2.865-2.068-9.675-10.931-11.317-7.343z"/>\r
+   <path fill="#464646" d="m196.806 16.152c-1.097 2.225-2.622 12.665-0.1 11.165 2.477-1.479 8.154-2.98 10.583-4.475 2.453-2.086-8.922-10.078-10.483-6.69z"/>\r
+   <path fill="#4c4c4c" d="m197.038 16.537c-1.085 2.188-2.513 11.706-0.085 10.28 2.374-1.4 7.661-2.768 9.737-4.244 2.039-2.104-8.171-9.225-9.652-6.036z"/>\r
+   <path fill="#525252" d="m197.27 16.921c-1.073 2.151-2.405 10.747-0.071 9.396 2.272-1.322 7.168-2.556 8.891-4.013 1.625-2.122-7.42-8.371-8.82-5.383z"/>\r
+   <path fill="#595959" d="m197.502 17.306c-1.062 2.113-2.297 9.788-0.058 8.512 2.172-1.244 6.677-2.344 8.046-3.783 1.211-2.14-6.669-7.518-7.988-4.729z"/>\r
+   <path fill="#5f5f5f" d="m197.734 17.69c-1.05 2.077-2.188 8.829-0.044 7.627 2.069-1.165 6.184-2.132 7.2-3.552 0.797-2.157-5.917-6.664-7.156-4.075z"/>\r
+   <path fill="#666" d="m197.966 18.075c-1.038 2.04-2.079 7.87-0.029 6.743 1.968-1.087 5.69-1.92 6.354-3.321 0.382-2.176-5.167-5.812-6.325-3.422z"/>\r
+   <path fill="#6c6c6c" d="m198.198 18.459c-1.027 2.003-1.972 6.911-0.017 5.859 1.866-1.008 5.198-1.708 5.509-3.09-0.03-2.194-4.415-4.959-5.492-2.769z"/>\r
+   <path fill="#727272" d="m198.43 18.844c-1.017 1.966-1.863 5.952-0.003 4.975 1.765-0.93 4.706-1.496 4.662-2.86-0.443-2.212-3.662-4.106-4.659-2.115z"/>\r
+   <path fill="#797979" d="m198.662 19.228c-1.004 1.929-1.755 4.993 0.011 4.09 1.663-0.852 4.215-1.284 3.817-2.629-0.858-2.23-2.912-3.251-3.828-1.461z"/>\r
+   <path fill="#7f7f7f" d="m198.894 19.612c-0.993 1.892-1.647 4.034 0.023 3.206 1.563-0.773 3.723-1.072 2.973-2.398-1.272-2.248-2.161-2.399-2.996-0.808z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path d="m143.502 46.386c-0.72 2.16 8.712 5.112 10.801 6.984 2.808 2.52 3.023 7.488 6.336 5.472 2.159-1.296 0.504-4.176-3.456-8.568-5.833-6.481-13.033-5.689-13.681-3.888z"/>\r
+   <path fill="#050505" d="m143.991 46.582c-0.716 2.073 8.275 4.9 10.336 6.741 2.745 2.457 2.961 7.249 6.146 5.313 2.094-1.254 0.449-4.072-3.343-8.28-5.574-6.203-12.491-5.505-13.139-3.774z"/>\r
+   <path fill="#0a0a0a" d="m144.479 46.779c-0.71 1.987 7.839 4.688 9.873 6.498 2.682 2.394 2.897 7.009 5.956 5.154 2.028-1.212 0.395-3.968-3.228-7.993-5.319-5.926-11.953-5.321-12.601-3.659z"/>\r
+   <path fill="#0f0f0f" d="m144.967 46.976c-0.704 1.9 7.403 4.476 9.41 6.254 2.62 2.33 2.835 6.77 5.766 4.995 1.964-1.171 0.342-3.864-3.112-7.706-5.064-5.647-11.415-5.137-12.064-3.543z"/>\r
+   <path fill="#141414" d="m145.456 47.172c-0.701 1.813 6.966 4.263 8.946 6.011 2.557 2.266 2.772 6.53 5.575 4.835 1.897-1.129 0.287-3.76-2.998-7.418-4.807-5.369-10.874-4.952-11.523-3.428z"/>\r
+   <path fill="#191919" d="m145.944 47.369c-0.696 1.726 6.53 4.051 8.483 5.768 2.493 2.203 2.71 6.291 5.385 4.676 1.833-1.087 0.231-3.656-2.884-7.13-4.551-5.093-10.335-4.769-10.984-3.314z"/>\r
+   <path fill="#1e1e1e" d="m146.433 47.565c-0.692 1.64 6.093 3.839 8.019 5.525 2.431 2.14 2.647 6.052 5.194 4.517 1.768-1.046 0.179-3.552-2.77-6.843-4.293-4.814-9.794-4.585-10.443-3.199z"/>\r
+   <path fill="#232323" d="m146.921 47.762c-0.686 1.553 5.657 3.627 7.558 5.282 2.367 2.076 2.583 5.813 5.003 4.357 1.702-1.003 0.124-3.448-2.654-6.555-4.04-4.537-9.257-4.401-9.907-3.084z"/>\r
+   <path fill="#282828" d="m147.409 47.959c-0.681 1.466 5.221 3.415 7.094 5.039 2.305 2.013 2.521 5.573 4.813 4.198 1.637-0.962 0.07-3.344-2.54-6.268-3.782-4.26-8.717-4.218-9.367-2.969z"/>\r
+   <path fill="#2d2d2d" d="m147.898 48.156c-0.677 1.379 4.784 3.203 6.63 4.795 2.242 1.949 2.457 5.333 4.622 4.039 1.572-0.92 0.016-3.24-2.425-5.98-3.526-3.983-8.177-4.034-8.827-2.854z"/>\r
+   <path fill="#333" d="m148.386 48.353c-0.673 1.292 4.348 2.99 6.167 4.552 2.179 1.886 2.394 5.095 4.432 3.88 1.506-0.878-0.038-3.136-2.312-5.693-3.268-3.705-7.636-3.85-8.287-2.739z"/>\r
+   <path fill="#383838" d="m148.875 48.549c-0.668 1.206 3.911 2.778 5.703 4.309 2.116 1.823 2.331 4.855 4.242 3.721 1.439-0.836-0.093-3.032-2.197-5.405-3.013-3.428-7.098-3.667-7.748-2.625z"/>\r
+   <path fill="#3d3d3d" d="m149.363 48.746c-0.662 1.119 3.475 2.566 5.24 4.065 2.053 1.759 2.268 4.616 4.052 3.562 1.375-0.795-0.147-2.928-2.082-5.118-2.757-3.15-6.559-3.483-7.21-2.509z"/>\r
+   <path fill="#424242" d="m149.851 48.942c-0.657 1.032 3.039 2.354 4.776 3.823 1.99 1.696 2.205 4.376 3.861 3.402 1.31-0.753-0.201-2.824-1.967-4.831-2.5-2.871-6.018-3.298-6.67-2.394z"/>\r
+   <path fill="#474747" d="m150.34 49.139c-0.652 0.946 2.603 2.142 4.313 3.58 1.927 1.632 2.143 4.137 3.671 3.243 1.244-0.712-0.256-2.72-1.853-4.543-2.245-2.595-5.48-3.115-6.131-2.28z"/>\r
+   <path fill="#4c4c4c" d="m150.828 49.336c-0.647 0.859 2.166 1.93 3.851 3.336 1.863 1.569 2.079 3.898 3.48 3.084 1.179-0.67-0.31-2.616-1.739-4.255-1.988-2.317-4.94-2.932-5.592-2.165z"/>\r
+   <path fill="#515151" d="m151.317 49.533c-0.645 0.772 1.729 1.718 3.386 3.093 1.802 1.505 2.018 3.658 3.29 2.925 1.114-0.628-0.364-2.512-1.624-3.968-1.732-2.04-4.4-2.748-5.052-2.05z"/>\r
+   <path fill="#565656" d="m151.805 49.729c-0.639 0.685 1.293 1.505 2.923 2.85 1.738 1.442 1.954 3.419 3.1 2.766 1.048-0.586-0.418-2.408-1.509-3.681-1.476-1.762-3.862-2.563-4.514-1.935z"/>\r
+   <path fill="#5b5b5b" d="m152.293 49.926c-0.633 0.598 0.857 1.293 2.46 2.606 1.677 1.379 1.892 3.18 2.91 2.606 0.983-0.544-0.473-2.304-1.395-3.393-1.22-1.483-3.322-2.379-3.975-1.819z"/>\r
+   <path fill="#606060" d="m152.782 50.123c-0.629 0.512 0.42 1.081 1.996 2.363 1.613 1.315 1.828 2.94 2.719 2.447 0.918-0.502-0.525-2.2-1.28-3.105-0.963-1.207-2.782-2.196-3.435-1.705z"/>\r
+   <path fill="#666" d="m153.27 50.319c-0.624 0.425-0.017 0.869 1.533 2.12 1.55 1.252 1.765 2.701 2.528 2.288 0.853-0.461-0.581-2.096-1.166-2.818-0.706-0.929-2.242-2.012-2.895-1.59z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path d="m193.47 45.594c-0.072 1.08 2.951 1.728 4.896 2.448 1.944 0.648 5.76 3.24 7.56 5.256 1.801 1.944 5.688 7.704 6.553 6.192 0.863-1.368-2.017-5.328-2.809-6.984s-3.239-5.256-7.128-6.48c-3.384-1.008-9-1.297-9.072-0.432z"/>\r
+   <path fill="#060606" d="m193.779 45.67c-0.071 1.05 2.869 1.685 4.758 2.387 1.891 0.633 5.598 3.161 7.345 5.122 1.747 1.893 5.535 7.493 6.376 6.027 0.84-1.328-1.936-5.173-2.738-6.795-0.8-1.622-3.175-5.09-6.952-6.301-3.282-0.995-8.718-1.28-8.789-0.44z"/>\r
+   <path fill="#0c0c0c" d="m194.088 45.747c-0.07 1.021 2.785 1.641 4.62 2.327 1.836 0.618 5.436 3.081 7.131 4.988 1.692 1.842 5.382 7.282 6.198 5.862 0.814-1.288-1.855-5.019-2.67-6.607-0.808-1.587-3.11-4.925-6.776-6.122-3.178-0.983-8.433-1.264-8.503-0.448z"/>\r
+   <path fill="#131313" d="m194.397 45.824c-0.071 0.991 2.702 1.598 4.481 2.267 1.782 0.603 5.272 3.001 6.916 4.854 1.64 1.791 5.229 7.071 6.022 5.697 0.788-1.248-1.776-4.865-2.603-6.418-0.815-1.554-3.044-4.759-6.599-5.942-3.073-0.973-8.148-1.251-8.217-0.458z"/>\r
+   <path fill="#191919" d="m194.706 45.9c-0.069 0.961 2.618 1.555 4.345 2.206 1.728 0.588 5.109 2.922 6.7 4.721 1.586 1.74 5.075 6.86 5.846 5.531 0.764-1.207-1.696-4.711-2.532-6.23-0.824-1.519-2.979-4.593-6.424-5.763-2.972-0.959-7.866-1.234-7.935-0.465z"/>\r
+   <path fill="#1f1f1f" d="m195.015 45.977c-0.07 0.931 2.534 1.511 4.207 2.146 1.672 0.573 4.945 2.843 6.485 4.586 1.531 1.689 4.921 6.649 5.668 5.367 0.738-1.167-1.616-4.557-2.464-6.042-0.832-1.485-2.914-4.428-6.247-5.583-2.868-0.948-7.581-1.219-7.649-0.474z"/>\r
+   <path fill="#262626" d="m195.324 46.054c-0.069 0.901 2.451 1.468 4.069 2.085 1.618 0.557 4.784 2.763 6.271 4.453 1.479 1.638 4.769 6.438 5.491 5.201 0.714-1.127-1.536-4.402-2.396-5.854-0.839-1.451-2.848-4.263-6.07-5.404-2.765-0.934-7.298-1.202-7.365-0.481z"/>\r
+   <path fill="#2c2c2c" d="m195.632 46.13c-0.067 0.872 2.369 1.424 3.933 2.025 1.563 0.542 4.621 2.684 6.056 4.318 1.424 1.587 4.615 6.228 5.315 5.036 0.688-1.086-1.456-4.248-2.326-5.665-0.848-1.416-2.783-4.097-5.896-5.224-2.662-0.923-7.015-1.187-7.082-0.49z"/>\r
+   <path fill="#333" d="m195.941 46.207c-0.068 0.842 2.285 1.381 3.794 1.964 1.51 0.527 4.458 2.605 5.842 4.185 1.37 1.536 4.461 6.016 5.138 4.871 0.662-1.046-1.377-4.093-2.258-5.476-0.855-1.382-2.718-3.932-5.718-5.045-2.56-0.911-6.732-1.172-6.798-0.499z"/>\r
+   <path fill="#393939" d="m196.25 46.284c-0.066 0.813 2.202 1.338 3.656 1.904 1.456 0.512 4.296 2.525 5.627 4.051 1.317 1.485 4.308 5.805 4.961 4.706 0.638-1.006-1.296-3.939-2.188-5.288-0.863-1.348-2.652-3.766-5.542-4.866-2.457-0.899-6.449-1.157-6.514-0.507z"/>\r
+   <path fill="#3f3f3f" d="m196.559 46.36c-0.067 0.783 2.118 1.295 3.518 1.844 1.402 0.497 4.133 2.446 5.412 3.917 1.263 1.434 4.155 5.594 4.785 4.541 0.612-0.966-1.217-3.785-2.12-5.1-0.872-1.313-2.587-3.6-5.366-4.687-2.353-0.886-6.165-1.141-6.229-0.515z"/>\r
+   <path fill="#464646" d="m196.868 46.437c-0.065 0.753 2.035 1.251 3.38 1.783 1.349 0.482 3.972 2.367 5.197 3.783 1.21 1.383 4.002 5.383 4.608 4.375 0.588-0.926-1.137-3.63-2.052-4.911-0.879-1.279-2.521-3.435-5.189-4.507-2.25-0.874-5.881-1.125-5.944-0.523z"/>\r
+   <path fill="#4c4c4c" d="m197.177 46.514c-0.066 0.723 1.95 1.208 3.241 1.723 1.293 0.467 3.809 2.287 4.983 3.649 1.155 1.332 3.848 5.172 4.431 4.21 0.563-0.885-1.057-3.476-1.982-4.723-0.888-1.245-2.456-3.269-5.014-4.328-2.146-0.862-5.597-1.109-5.659-0.531z"/>\r
+   <path fill="#525252" d="m197.486 46.591c-0.066 0.693 1.868 1.164 3.104 1.662 1.239 0.452 3.646 2.208 4.769 3.515 1.102 1.281 3.695 4.961 4.254 4.045 0.537-0.845-0.976-3.321-1.913-4.534-0.896-1.21-2.391-3.103-4.838-4.148-2.044-0.851-5.315-1.095-5.376-0.54z"/>\r
+   <path fill="#595959" d="m197.795 46.667c-0.064 0.664 1.784 1.121 2.968 1.602 1.184 0.437 3.481 2.128 4.552 3.381 1.049 1.23 3.542 4.75 4.078 3.88 0.512-0.805-0.897-3.167-1.846-4.346-0.902-1.176-2.325-2.938-4.66-3.969-1.942-0.838-5.031-1.078-5.092-0.548z"/>\r
+   <path fill="#5f5f5f" d="m198.104 46.744c-0.065 0.634 1.701 1.078 2.829 1.541 1.13 0.421 3.318 2.049 4.338 3.248 0.994 1.179 3.388 4.539 3.899 3.715 0.487-0.765-0.815-3.013-1.775-4.157-0.911-1.142-2.261-2.772-4.485-3.79-1.837-0.826-4.746-1.064-4.806-0.557z"/>\r
+   <path fill="#666" d="m198.413 46.821c-0.063 0.604 1.617 1.034 2.691 1.481 1.076 0.406 3.157 1.969 4.123 3.113 0.94 1.128 3.234 4.328 3.724 3.55 0.462-0.725-0.737-2.858-1.707-3.969-0.919-1.108-2.195-2.606-4.309-3.61-1.734-0.814-4.463-1.049-4.522-0.565z"/>\r
+   <path fill="#6c6c6c" d="m198.721 46.897c-0.063 0.574 1.534 0.991 2.554 1.42 1.021 0.391 2.994 1.89 3.908 2.979 0.887 1.077 3.082 4.117 3.548 3.384 0.436-0.685-0.657-2.704-1.64-3.78-0.927-1.074-2.13-2.44-4.132-3.431-1.631-0.8-4.179-1.031-4.238-0.572z"/>\r
+   <path fill="#727272" d="m199.03 46.974c-0.063 0.544 1.451 0.948 2.416 1.36 0.967 0.376 2.831 1.811 3.694 2.846 0.833 1.026 2.928 3.906 3.369 3.219 0.411-0.644-0.576-2.549-1.569-3.592-0.936-1.04-2.064-2.275-3.956-3.251-1.528-0.79-3.896-1.017-3.954-0.582z"/>\r
+   <path fill="#797979" d="m199.339 47.051c-0.062 0.515 1.368 0.904 2.278 1.299 0.913 0.361 2.669 1.731 3.479 2.712 0.779 0.975 2.774 3.695 3.193 3.054 0.386-0.604-0.497-2.396-1.501-3.403-0.942-1.005-1.999-2.11-3.78-3.072-1.424-0.778-3.612-1.002-3.669-0.59z"/>\r
+   <path fill="#7f7f7f" d="m199.648 47.127c-0.063 0.485 1.284 0.861 2.14 1.239 0.859 0.346 2.506 1.652 3.265 2.578 0.726 0.924 2.621 3.484 3.017 2.889 0.361-0.564-0.417-2.241-1.432-3.215-0.951-0.971-1.935-1.944-3.604-2.893-1.323-0.765-3.33-0.986-3.386-0.598z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path fill="#995900" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-20.017 11.592-31.104 19.152-13.968 9.576-18.792 13.824-23.328 18.359-7.056 7.057-13.752 9.432-24.48 9.432s-15.552-2.231-18.863-5.184c-3.313-2.88-6.984-10.225-6.624-21.168 0.288-10.872 3.744-20.809 5.399-37.729 0.721-7.271 0.648-16.271 0.648-24.264 0-10.08 0.144-18.648 2.304-19.943 3.889-2.448 4.752-2.592 9.36-2.592 4.607 0 6.696 0.287 8.208 1.799 1.439 1.44 0.864 4.752 0.359 9.433-0.432 4.681 1.801 6.192 4.032 8.136 2.232 1.872 4.248 4.248 11.305 4.824 7.056 0.504 9.647-0.648 12.96-2.736 3.312-2.088 7.991-5.832 9.72-7.992 1.656-2.088 5.76-9.287 6.552-9.287 0.719 0 5.472-1.656 8.136 2.232z"/>\r
+   <path fill="#9e5e00" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-20.12 11.556-31.26 19.008-13.885 9.371-18.903 13.54-23.521 17.902-6.912 6.74-13.414 9.084-23.915 9.019-10.411-0.047-15.116-2.181-18.414-5.118-3.297-2.867-6.931-9.966-6.613-20.578 0.205-10.851 3.701-20.683 5.256-37.279 0.666-7.379 0.407-16.303 0.335-24.375-0.076-10.068-0.072-18.627 2.084-19.922 3.889-2.444 4.752-2.592 9.36-2.592 4.607 0 6.7 0.291 8.208 1.799 1.491 1.492 0.767 4.887 0.205 9.408-0.63 4.658 1.458 6.486 3.795 8.607 2.34 2.059 4.489 4.471 11.534 5.021 7.232 0.482 10.015-0.832 13.362-3.106 3.303-2.207 7.773-5.903 9.513-8.168 1.641-2.132 5.727-9.386 6.519-9.386 0.719 0 5.472-1.656 8.136 2.232z"/>\r
+   <path fill="#a36400" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-20.226 11.52-31.414 18.863-13.803 9.166-19.016 13.256-23.717 17.447-6.768 6.422-13.075 8.733-23.35 8.604-10.094-0.094-14.682-2.131-17.964-5.055-3.283-2.852-6.876-9.705-6.603-19.987 0.122-10.828 3.657-20.556 5.112-36.828 0.612-7.487 0.165-16.336 0.021-24.487-0.15-10.058-0.287-18.605 1.865-19.9 3.889-2.44 4.752-2.592 9.36-2.592 4.607 0 6.703 0.295 8.208 1.799 1.541 1.541 0.67 5.02 0.051 9.383-0.828 4.637 1.116 6.781 3.556 9.078 2.448 2.248 4.731 4.695 11.766 5.221 7.409 0.461 10.383-1.016 13.767-3.477 3.29-2.326 7.552-5.977 9.302-8.346 1.627-2.175 5.695-9.482 6.487-9.482 0.72-0.001 5.473-1.657 8.137 2.231z"/>\r
+   <path fill="#a86a00" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-20.329 11.484-31.568 18.72-13.721 8.961-19.127 12.972-23.911 16.989-6.624 6.105-12.737 8.384-22.785 8.189-9.777-0.141-14.245-2.08-17.514-4.99-3.27-2.836-6.822-9.445-6.591-19.396 0.038-10.807 3.613-20.43 4.968-36.378 0.558-7.597-0.076-16.368-0.292-24.599-0.228-10.047-0.504-18.584 1.645-19.879 3.889-2.438 4.752-2.592 9.36-2.592 4.607 0 6.707 0.299 8.208 1.799 1.591 1.593 0.573 5.152-0.104 9.357-1.025 4.615 0.774 7.077 3.319 9.551 2.556 2.434 4.972 4.918 11.995 5.418 7.585 0.439 10.75-1.199 14.17-3.849 3.279-2.444 7.333-6.048 9.093-8.521 1.613-2.219 5.663-9.58 6.455-9.58 0.719 0.001 5.472-1.655 8.136 2.233z"/>\r
+   <path fill="#ad7000" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-20.434 11.447-31.724 18.576-13.637 8.756-19.238 12.687-24.106 16.531-6.479 5.789-12.397 8.035-22.219 7.776-9.461-0.188-13.809-2.03-17.063-4.925-3.254-2.823-6.769-9.188-6.581-18.807-0.043-10.785 3.571-20.305 4.824-35.928 0.504-7.705-0.316-16.402-0.604-24.711-0.303-10.037-0.72-18.563 1.425-19.857 3.889-2.434 4.752-2.592 9.36-2.592 4.607 0 6.711 0.303 8.208 1.799 1.642 1.643 0.475 5.285-0.259 9.332-1.225 4.594 0.432 7.373 3.082 10.022 2.664 2.621 5.212 5.142 12.225 5.616 7.762 0.418 11.117-1.383 14.573-4.219 3.269-2.563 7.113-6.121 8.885-8.698 1.598-2.261 5.63-9.677 6.422-9.677 0.719 0.002 5.472-1.654 8.136 2.234z"/>\r
+   <path fill="#b27500" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-20.538 11.412-31.879 18.432-13.554 8.551-19.35 12.402-24.3 16.074-6.336 5.473-12.06 7.686-21.654 7.361-9.144-0.233-13.374-1.979-16.613-4.859-3.24-2.809-6.714-8.928-6.57-18.216-0.126-10.765 3.528-20.179 4.68-35.478 0.45-7.813-0.558-16.435-0.918-24.822-0.378-10.026-0.936-18.541 1.206-19.836 3.889-2.43 4.752-2.592 9.36-2.592 4.607 0 6.714 0.305 8.208 1.799 1.691 1.693 0.378 5.418-0.414 9.307-1.422 4.572 0.09 7.668 2.844 10.494 2.772 2.808 5.454 5.363 12.456 5.814 7.938 0.396 11.484-1.566 14.977-4.591 3.258-2.682 6.894-6.192 8.676-8.874 1.584-2.304 5.598-9.773 6.39-9.773 0.718 0 5.471-1.656 8.135 2.232z"/>\r
+   <path fill="#b77b00" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-20.643 11.376-32.033 18.288-13.472 8.345-19.461 12.118-24.494 15.616-6.192 5.156-11.723 7.338-21.089 6.949-8.827-0.281-12.938-1.93-16.164-4.795-3.226-2.795-6.66-8.67-6.56-17.627-0.209-10.742 3.485-20.052 4.536-35.027 0.396-7.92-0.799-16.467-1.23-24.934-0.454-10.015-1.152-18.52 0.985-19.814 3.889-2.426 4.752-2.592 9.36-2.592 4.607 0 6.718 0.31 8.208 1.799 1.743 1.744 0.281 5.553-0.569 9.281-1.62 4.551-0.252 7.963 2.607 10.967 2.88 2.994 5.694 5.586 12.686 6.012 8.115 0.373 11.852-1.75 15.379-4.961 3.248-2.801 6.676-6.264 8.469-9.051 1.568-2.348 5.564-9.871 6.356-9.871 0.72 0 5.473-1.656 8.137 2.232z"/>\r
+   <path fill="#bc8100" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-20.747 11.34-32.188 18.145-13.389 8.139-19.574 11.834-24.689 15.159-6.048 4.839-11.383 6.988-20.523 6.534-8.51-0.328-12.503-1.879-15.714-4.73-3.211-2.779-6.606-8.41-6.549-17.035-0.292-10.721 3.441-19.926 4.393-34.578 0.342-8.028-1.041-16.498-1.545-25.045-0.529-10.004-1.368-18.498 0.767-19.793 3.889-2.422 4.752-2.592 9.36-2.592 4.607 0 6.721 0.313 8.208 1.799 1.793 1.793 0.184 5.686-0.723 9.256-1.818 4.529-0.595 8.259 2.367 11.438 2.988 3.184 5.938 5.811 12.917 6.211 8.291 0.352 12.22-1.934 15.783-5.332 3.236-2.92 6.454-6.336 8.258-9.227 1.556-2.391 5.533-9.969 6.325-9.969 0.72-0.001 5.473-1.657 8.137 2.231z"/>\r
+   <path fill="#c18700" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-20.852 11.304-32.343 18-13.306 7.936-19.685 11.549-24.883 14.703-5.904 4.521-11.045 6.638-19.959 6.119-8.193-0.375-12.067-1.828-15.264-4.666-3.197-2.764-6.553-8.149-6.537-16.444-0.375-10.699 3.397-19.8 4.248-34.128 0.288-8.137-1.282-16.531-1.858-25.156-0.604-9.994-1.584-18.477 0.547-19.771 3.889-2.42 4.752-2.592 9.36-2.592 4.607 0 6.725 0.316 8.208 1.799 1.843 1.845 0.087 5.818-0.878 9.231-2.017 4.507-0.937 8.554 2.131 11.909 3.096 3.369 6.178 6.033 13.146 6.408 8.468 0.33 12.587-2.117 16.187-5.703 3.225-3.038 6.235-6.408 8.049-9.402 1.541-2.436 5.501-10.066 6.293-10.066 0.72-0.001 5.473-1.657 8.137 2.231z"/>\r
+   <path fill="#c68c00" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-20.955 11.268-32.498 17.855-13.223 7.73-19.796 11.266-25.077 14.246-5.761 4.204-10.706 6.289-19.394 5.707-7.877-0.423-11.631-1.779-14.813-4.602-3.183-2.751-6.498-7.891-6.527-15.855-0.457-10.677 3.355-19.674 4.104-33.678 0.234-8.244-1.521-16.563-2.17-25.268-0.681-9.983-1.8-18.455 0.327-19.75 3.889-2.416 4.752-2.592 9.36-2.592 4.607 0 6.729 0.32 8.208 1.799 1.894 1.895-0.011 5.951-1.033 9.207-2.214 4.484-1.278 8.848 1.895 12.379 3.203 3.558 6.418 6.258 13.377 6.607 8.644 0.309 12.952-2.301 16.589-6.074 3.215-3.156 6.016-6.479 7.841-9.58 1.526-2.477 5.468-10.162 6.26-10.162 0.718 0.001 5.471-1.655 8.135 2.233z"/>\r
+   <path fill="#cc9200" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-21.061 11.232-32.652 17.712-13.141 7.524-19.908 10.979-25.272 13.788-5.616 3.888-10.368 5.939-18.828 5.292-7.56-0.468-11.195-1.728-14.363-4.536-3.168-2.736-6.444-7.632-6.517-15.264-0.54-10.656 3.313-19.549 3.96-33.229 0.181-8.352-1.764-16.596-2.483-25.38-0.757-9.972-2.017-18.433 0.107-19.728 3.889-2.412 4.752-2.592 9.36-2.592 4.607 0 6.731 0.323 8.208 1.799 1.944 1.945-0.108 6.084-1.188 9.181-2.412 4.464-1.62 9.144 1.656 12.853 3.313 3.744 6.66 6.479 13.608 6.803 8.819 0.289 13.319-2.483 16.991-6.443 3.204-3.275 5.797-6.553 7.633-9.756 1.512-2.52 5.436-10.26 6.228-10.26 0.719 0 5.472-1.656 8.136 2.232z"/>\r
+   <path fill="#d19800" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-21.164 11.195-32.808 17.568-13.057 7.318-20.019 10.695-25.466 13.33-5.473 3.571-10.03 5.592-18.263 4.879-7.243-0.516-10.761-1.678-13.914-4.472-3.153-2.722-6.391-7.372-6.506-14.674-0.623-10.634 3.27-19.422 3.816-32.778 0.126-8.459-2.005-16.627-2.797-25.49-0.832-9.961-2.232-18.412-0.112-19.707 3.889-2.408 4.752-2.592 9.36-2.592 4.607 0 6.736 0.328 8.208 1.799 1.995 1.996-0.205 6.219-1.343 9.156-2.61 4.442-1.962 9.438 1.419 13.323 3.42 3.931 6.9 6.703 13.838 7.002 8.997 0.267 13.687-2.668 17.395-6.815 3.194-3.395 5.577-6.623 7.424-9.932 1.497-2.564 5.403-10.357 6.195-10.357 0.721 0 5.474-1.656 8.138 2.232z"/>\r
+   <path fill="#d69e00" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-21.27 11.16-32.962 17.424-12.975 7.114-20.132 10.412-25.661 12.874-5.327 3.254-9.69 5.242-17.697 4.464-6.927-0.563-10.325-1.627-13.464-4.406-3.14-2.707-6.337-7.113-6.494-14.084-0.706-10.611 3.225-19.295 3.672-32.328 0.072-8.567-2.247-16.66-3.111-25.603-0.907-9.95-2.448-18.39-0.331-19.685 3.889-2.404 4.752-2.592 9.36-2.592 4.607 0 6.739 0.332 8.208 1.799 2.045 2.045-0.302 6.352-1.497 9.131-2.808 4.421-2.304 9.734 1.18 13.795 3.528 4.119 7.144 6.927 14.069 7.199 9.173 0.246 14.055-2.851 17.799-7.185 3.182-3.514 5.356-6.696 7.214-10.108 1.483-2.607 5.371-10.455 6.163-10.455 0.719 0 5.472-1.656 8.136 2.232z"/>\r
+   <path fill="#dba300" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-21.373 11.124-33.116 17.279-12.893 6.91-20.243 10.127-25.855 12.418-5.184 2.937-9.353 4.892-17.133 4.05-6.609-0.609-9.889-1.577-13.014-4.343-3.125-2.692-6.282-6.854-6.483-13.492-0.789-10.592 3.182-19.17 3.528-31.879 0.018-8.676-2.488-16.692-3.425-25.713-0.982-9.94-2.664-18.369-0.551-19.664 3.889-2.401 4.752-2.592 9.36-2.592 4.607 0 6.743 0.334 8.208 1.799 2.095 2.097-0.399 6.484-1.652 9.105-3.006 4.398-2.646 10.029 0.943 14.268 3.636 4.305 7.384 7.148 14.299 7.397 9.349 0.224 14.422-3.034 18.202-7.558 3.171-3.631 5.137-6.768 7.005-10.284 1.469-2.649 5.339-10.552 6.131-10.552 0.72 0.001 5.473-1.655 8.137 2.233z"/>\r
+   <path fill="#e0a900" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-21.478 11.088-33.271 17.136-12.81 6.704-20.354 9.843-26.05 11.96-5.04 2.62-9.015 4.543-16.567 3.637-6.293-0.656-9.453-1.527-12.563-4.277-3.11-2.68-6.229-6.596-6.474-12.903-0.871-10.569 3.141-19.044 3.384-31.428-0.035-8.784-2.728-16.726-3.735-25.826-1.06-9.929-2.88-18.347-0.771-19.642 3.889-2.397 4.752-2.592 9.36-2.592 4.607 0 6.747 0.338 8.208 1.799 2.146 2.146-0.497 6.617-1.808 9.08-3.203 4.377-2.987 10.324 0.706 14.738 3.744 4.493 7.624 7.373 14.529 7.596 9.526 0.203 14.789-3.217 18.605-7.926 3.161-3.752 4.918-6.841 6.797-10.463 1.454-2.693 5.306-10.648 6.098-10.648 0.719-0.001 5.472-1.657 8.136 2.231z"/>\r
+   <path fill="#e5af00" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-21.582 11.052-33.427 16.992-12.726 6.498-20.466 9.558-26.244 11.502-4.896 2.304-8.676 4.193-16.002 3.222-5.976-0.702-9.018-1.476-12.114-4.212-3.096-2.664-6.174-6.336-6.462-12.313-0.953-10.547 3.097-18.918 3.24-30.978-0.09-8.892-2.97-16.758-4.05-25.938-1.134-9.918-3.096-18.324-0.99-19.619 3.889-2.395 4.752-2.592 9.36-2.592 4.607 0 6.75 0.342 8.208 1.799 2.196 2.197-0.594 6.75-1.962 9.055-3.402 4.355-3.33 10.619 0.468 15.21 3.852 4.681 7.866 7.597 14.76 7.794 9.702 0.18 15.156-3.402 19.008-8.298 3.15-3.87 4.698-6.912 6.589-10.638 1.439-2.736 5.273-10.746 6.065-10.746 0.72 0 5.473-1.656 8.137 2.232z"/>\r
+   <path fill="#eab500" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-21.687 11.016-33.582 16.848-12.643 6.293-20.576 9.274-26.438 11.045-4.752 1.987-8.338 3.846-15.438 2.809-5.658-0.75-8.582-1.426-11.664-4.147-3.08-2.649-6.119-6.077-6.45-11.722-1.037-10.526 3.053-18.792 3.096-30.528-0.144-9-3.211-16.79-4.363-26.049-1.21-9.907-3.312-18.304-1.21-19.599 3.889-2.391 4.752-2.592 9.36-2.592 4.607 0 6.754 0.346 8.208 1.799 2.247 2.248-0.691 6.885-2.117 9.029-3.6 4.336-3.672 10.916 0.231 15.682 3.96 4.867 8.106 7.82 14.989 7.992 9.879 0.158 15.523-3.586 19.411-8.668 3.141-3.99 4.479-6.984 6.38-10.814 1.426-2.78 5.241-10.844 6.033-10.844 0.721-0.001 5.474-1.657 8.138 2.231z"/>\r
+   <path fill="#efba00" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-21.791 10.98-33.735 16.703-12.562 6.089-20.69 8.99-26.633 10.589-4.608 1.67-7.999 3.496-14.872 2.394-5.343-0.796-8.147-1.375-11.215-4.082-3.066-2.636-6.065-5.818-6.439-11.132-1.12-10.504 3.009-18.666 2.952-30.077-0.198-9.109-3.453-16.822-4.677-26.162-1.285-9.896-3.528-18.281-1.43-19.576 3.889-2.387 4.752-2.592 9.36-2.592 4.607 0 6.757 0.35 8.208 1.799 2.297 2.297-0.788 7.018-2.271 9.004-3.798 4.314-4.014 11.211-0.008 16.154 4.068 5.055 8.35 8.043 15.221 8.189 10.056 0.137 15.892-3.77 19.815-9.039 3.128-4.107 4.258-7.057 6.17-10.99 1.411-2.824 5.209-10.941 6.001-10.941 0.72-0.001 5.473-1.657 8.137 2.231z"/>\r
+   <path fill="#f4c000" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-21.896 10.943-33.891 16.561-12.478 5.883-20.801 8.704-26.827 10.131-4.464 1.353-7.661 3.146-14.307 1.979-5.025-0.843-7.711-1.325-10.765-4.019-3.053-2.621-6.012-5.558-6.429-10.541-1.203-10.482 2.966-18.539 2.809-29.627-0.253-9.217-3.694-16.855-4.99-26.272-1.361-9.886-3.744-18.261-1.649-19.556 3.889-2.383 4.752-2.592 9.36-2.592 4.607 0 6.761 0.353 8.208 1.799 2.347 2.349-0.885 7.15-2.426 8.979-3.996 4.291-4.356 11.505-0.245 16.625 4.176 5.241 8.59 8.265 15.451 8.388 10.23 0.115 16.258-3.953 20.218-9.41 3.117-4.227 4.039-7.129 5.961-11.168 1.396-2.865 5.177-11.037 5.969-11.037 0.72 0 5.473-1.656 8.137 2.232z"/>\r
+   <path fill="#f9c600" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-21.999 10.908-34.046 16.416-12.395 5.678-20.912 8.421-27.021 9.674-4.32 1.036-7.322 2.797-13.741 1.566-4.709-0.891-7.275-1.275-10.314-3.953-3.037-2.607-5.958-5.299-6.419-9.951-1.284-10.461 2.925-18.414 2.664-29.178-0.306-9.324-3.934-16.887-5.302-26.385-1.437-9.875-3.96-18.238-1.869-19.533 3.889-2.379 4.752-2.592 9.36-2.592 4.607 0 6.765 0.356 8.208 1.799 2.397 2.398-0.983 7.283-2.581 8.955-4.194 4.269-4.698 11.799-0.482 17.096 4.284 5.429 8.83 8.488 15.682 8.586 10.407 0.094 16.625-4.137 20.621-9.781 3.106-4.345 3.819-7.199 5.753-11.344 1.382-2.909 5.144-11.135 5.936-11.135 0.718 0 5.471-1.656 8.135 2.232z"/>\r
+   <path fill="#fc0" d="m303.631 262.529c4.464 6.479-0.145 14.904 3.096 20.089 5.328 8.496 16.056 17.063 20.16 19.439 2.952 1.8 7.128 3.527 6.983 8.783-0.216 5.977-3.168 7.561-4.823 9.217-3.313 3.313-22.104 10.872-34.2 16.271-12.313 5.473-21.024 8.137-27.217 9.217-4.176 0.72-6.983 2.447-13.176 1.151-4.392-0.937-6.84-1.224-9.864-3.888-3.023-2.592-5.903-5.04-6.407-9.359-1.368-10.441 2.88-18.289 2.52-28.729-0.36-9.432-4.176-16.92-5.616-26.496-1.512-9.864-4.176-18.217-2.088-19.512 3.889-2.377 4.752-2.592 9.36-2.592 4.607 0 6.768 0.359 8.208 1.799 2.448 2.449-1.08 7.416-2.736 8.929-4.392 4.248-5.04 12.096-0.72 17.567 4.392 5.617 9.072 8.713 15.912 8.785 10.584 0.071 16.992-4.32 21.023-10.152 3.097-4.465 3.601-7.272 5.544-11.521 1.368-2.952 5.112-11.231 5.904-11.231 0.72 0.001 5.473-1.655 8.137 2.233z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path fill="#fc0" d="m236.263 275.762c-0.709-0.258-3.932-15.209-2.191-16.239 3.351-1.997 4.253-2.319 8.377-2.319s6.057 0.322 7.346 1.61c2.126 2.127-1.159 6.702-2.448 7.991-3.738 3.673-10.375 9.215-11.084 8.957z"/>\r
+   <path fill="#ffcc02" d="m236.492 275.286c-0.77-0.326-4.102-14.719-2.368-15.742 3.344-1.992 4.278-2.211 8.322-2.211 4.124 0 5.976 0.269 7.282 1.602 2.105 2.136-1.119 6.572-2.398 7.852-3.727 3.663-10.081 8.811-10.838 8.499z"/>\r
+   <path fill="#ffcc05" d="m236.721 274.809c-0.832-0.393-4.273-14.229-2.547-15.247 3.339-1.983 4.306-2.101 8.269-2.101 4.124 0 5.896 0.213 7.217 1.592 2.087 2.146-1.076 6.443-2.346 7.714-3.718 3.653-9.788 8.409-10.593 8.042z"/>\r
+   <path fill="#ffcc07" d="m236.949 274.333c-0.892-0.461-4.443-13.74-2.722-14.75 3.331-1.979 4.33-1.992 8.213-1.992 4.124 0 5.814 0.158 7.151 1.582 2.068 2.156-1.033 6.316-2.294 7.574-3.707 3.644-9.494 8.007-10.348 7.586z"/>\r
+   <path fill="#ffcd0a" d="m237.178 273.855c-0.954-0.528-4.614-13.249-2.9-14.254 3.326-1.972 4.355-1.882 8.158-1.882 4.124 0 5.734 0.104 7.089 1.572 2.048 2.166-0.993 6.187-2.243 7.437-3.699 3.634-9.202 7.605-10.104 7.127z"/>\r
+   <path fill="#ffcd0c" d="m237.407 273.377c-1.015-0.596-4.785-12.758-3.077-13.758 3.319-1.965 4.382-1.771 8.104-1.771 4.124 0 5.653 0.049 7.023 1.563 2.029 2.176-0.951 6.057-2.19 7.299-3.69 3.623-8.91 7.201-9.86 6.667z"/>\r
+   <path fill="#ffcd0f" d="m237.636 272.901c-1.077-0.662-4.956-12.27-3.256-13.261 3.313-1.959 4.408-1.663 8.05-1.663 4.124 0 5.573-0.006 6.959 1.553 2.01 2.186-0.908 5.93-2.14 7.159-3.679 3.614-8.615 6.8-9.613 6.212z"/>\r
+   <path fill="#ffcd11" d="m237.864 272.424c-1.137-0.731-5.126-11.779-3.431-12.766 3.306-1.951 4.433-1.553 7.994-1.553 4.123 0 5.492-0.061 6.895 1.543 1.991 2.193-0.867 5.801-2.089 7.021-3.669 3.606-8.321 6.397-9.369 5.755z"/>\r
+   <path fill="#ffce14" d="m238.093 271.948c-1.197-0.799-5.297-11.289-3.607-12.27 3.299-1.946 4.459-1.443 7.938-1.443 4.124 0 5.412-0.115 6.83 1.533 1.973 2.204-0.824 5.671-2.037 6.883-3.659 3.596-8.028 5.993-9.124 5.297z"/>\r
+   <path fill="#ffce16" d="m238.322 271.471c-1.26-0.867-5.468-10.801-3.786-11.773 3.293-1.939 4.485-1.334 7.884-1.334 4.124 0 5.332-0.171 6.767 1.524 1.953 2.213-0.783 5.542-1.985 6.743-3.651 3.586-7.736 5.591-8.88 4.84z"/>\r
+   <path fill="#ffce19" d="m238.551 270.995c-1.32-0.935-5.639-10.312-3.963-11.277 3.286-1.934 4.511-1.225 7.829-1.225 4.124 0 5.252-0.226 6.702 1.514 1.934 2.224-0.741 5.414-1.934 6.605-3.64 3.576-7.442 5.187-8.634 4.383z"/>\r
+   <path fill="#ffce1c" d="m238.779 270.517c-1.382-1.002-5.809-9.821-4.14-10.781 3.279-1.926 4.535-1.114 7.774-1.114 4.124 0 5.171-0.279 6.637 1.504 1.914 2.233-0.698 5.285-1.882 6.467-3.63 3.566-7.148 4.784-8.389 3.924z"/>\r
+   <path fill="#ffcf1e" d="m239.008 270.04c-1.442-1.068-5.979-9.33-4.316-10.283 3.272-1.922 4.562-1.006 7.72-1.006 4.124 0 5.09-0.334 6.572 1.494 1.895 2.244-0.657 5.156-1.83 6.328-3.622 3.556-6.857 4.383-8.146 3.467z"/>\r
+   <path fill="#ffcf21" d="m239.237 269.563c-1.505-1.137-6.151-8.841-4.495-9.788 3.267-1.914 4.588-0.896 7.666-0.896 4.124 0 5.009-0.389 6.508 1.486 1.875 2.252-0.616 5.025-1.778 6.188-3.613 3.548-6.564 3.981-7.901 3.01z"/>\r
+   <path fill="#ffcf23" d="m239.466 269.086c-1.565-1.205-6.321-8.352-4.672-9.293 3.262-1.906 4.613-0.785 7.61-0.785 4.124 0 4.93-0.444 6.444 1.476 1.855 2.261-0.573 4.897-1.728 6.052-3.601 3.537-6.269 3.575-7.654 2.55z"/>\r
+   <path fill="#ffcf26" d="m239.694 268.61c-1.627-1.273-6.492-7.861-4.849-8.796 3.255-1.901 4.64-0.677 7.556-0.677 4.124 0 4.849-0.499 6.379 1.466 1.837 2.271-0.531 4.769-1.675 5.912-3.593 3.528-5.977 3.174-7.411 2.095z"/>\r
+   <path fill="#ffd028" d="m239.923 268.133c-1.688-1.34-6.663-7.373-5.025-8.301 3.248-1.895 4.665-0.566 7.501-0.566 4.124 0 4.768-0.555 6.314 1.456 1.817 2.28-0.489 4.64-1.624 5.774-3.583 3.519-5.684 2.771-7.166 1.637z"/>\r
+   <path fill="#ffd02b" d="m240.152 267.657c-1.749-1.408-6.834-6.883-5.203-7.805 3.241-1.889 4.69-0.457 7.446-0.457 4.124 0 4.687-0.609 6.25 1.447 1.798 2.289-0.448 4.51-1.573 5.635-3.572 3.509-5.39 2.367-6.92 1.18z"/>\r
+   <path fill="#ffd02d" d="m240.381 267.178c-1.811-1.475-7.005-6.391-5.381-7.307 3.235-1.881 4.717-0.348 7.393-0.348 4.124 0 4.606-0.664 6.185 1.438 1.779 2.299-0.405 4.381-1.521 5.496-3.564 3.501-5.098 1.965-6.676 0.721z"/>\r
+   <path fill="#ffd030" d="m240.609 266.702c-1.871-1.543-7.175-5.902-5.557-6.811 3.228-1.875 4.741-0.238 7.336-0.238 4.124 0 4.526-0.719 6.122 1.428 1.759 2.31-0.364 4.252-1.471 5.357-3.552 3.49-4.803 1.562-6.43 0.264z"/>\r
+   <path fill="#ffd133" d="m240.838 266.225c-1.933-1.611-7.346-5.413-5.734-6.314 3.222-1.869 4.768-0.129 7.281-0.129 4.124 0 4.446-0.773 6.058 1.418 1.74 2.318-0.322 4.123-1.418 5.219-3.545 3.48-4.512 1.16-6.187-0.194z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path fill="#fc0" d="m302.769 263.374c3.742 5.461-0.062 12.76 2.638 17.117-6.809-6.258-9.938-8.834-19.324 0.367 2.577-3.742 3.129-6.257 4.725-9.814 1.104-2.455 4.354-9.57 5.029-9.57 0.613-0.001 4.724-1.35 6.932 1.9z"/>\r
+   <path fill="#ffcc02" d="m302.73 263.413c3.655 5.334 0.035 12.601 2.578 16.723-6.668-6.098-9.729-8.666-18.908 0.322 2.421-3.527 3.025-6.094 4.605-9.592 1.122-2.462 4.243-9.302 4.951-9.311 0.628-0.008 4.617-1.318 6.774 1.858z"/>\r
+   <path fill="#ffcc05" d="m302.691 263.45c3.568 5.209 0.132 12.441 2.517 16.332-6.526-5.938-9.519-8.5-18.492 0.277 2.268-3.314 2.924-5.934 4.488-9.372 1.141-2.468 4.132-9.032 4.873-9.052 0.641-0.013 4.508-1.284 6.614 1.815z"/>\r
+   <path fill="#ffcc07" d="m302.652 263.487c3.48 5.086 0.229 12.282 2.457 15.939-6.386-5.777-9.311-8.332-18.076 0.232 2.111-3.1 2.819-5.771 4.369-9.15 1.158-2.475 4.021-8.762 4.795-8.791 0.655-0.019 4.399-1.254 6.455 1.77z"/>\r
+   <path fill="#ffcd0a" d="m302.614 263.524c3.393 4.96 0.323 12.123 2.396 15.549-6.245-5.617-9.102-8.164-17.66 0.188 1.955-2.887 2.716-5.611 4.251-8.93 1.176-2.481 3.91-8.494 4.716-8.533 0.67-0.027 4.291-1.219 6.297 1.726z"/>\r
+   <path fill="#ffcd0c" d="m302.575 263.562c3.306 4.835 0.419 11.964 2.335 15.155-6.104-5.457-8.891-7.996-17.244 0.143 1.8-2.673 2.613-5.449 4.133-8.707 1.194-2.488 3.8-8.225 4.638-8.273 0.684-0.036 4.182-1.189 6.138 1.682z"/>\r
+   <path fill="#ffcd0f" d="m302.536 263.599c3.219 4.71 0.517 11.805 2.275 14.765-5.963-5.299-8.683-7.83-16.828 0.098 1.644-2.461 2.51-5.289 4.015-8.486 1.212-2.496 3.688-7.956 4.559-8.016 0.698-0.04 4.075-1.155 5.979 1.639z"/>\r
+   <path fill="#ffcd11" d="m302.497 263.637c3.131 4.585 0.612 11.645 2.216 14.371-5.822-5.137-8.474-7.661-16.413 0.053 1.489-2.245 2.406-5.125 3.896-8.264 1.229-2.504 3.576-7.688 4.479-7.756 0.714-0.046 3.968-1.123 5.822 1.596z"/>\r
+   <path fill="#ffce14" d="m302.458 263.674c3.044 4.459 0.708 11.486 2.154 13.979-5.681-4.978-8.263-7.493-15.996 0.009 1.334-2.033 2.303-4.965 3.779-8.043 1.247-2.511 3.464-7.418 4.4-7.498 0.728-0.052 3.859-1.089 5.663 1.553z"/>\r
+   <path fill="#ffce16" d="m302.42 263.711c2.956 4.336 0.804 11.328 2.094 13.588-5.54-4.817-8.055-7.326-15.58-0.036 1.178-1.819 2.199-4.804 3.659-7.822 1.267-2.517 3.354-7.149 4.323-7.237 0.741-0.061 3.75-1.058 5.504 1.507z"/>\r
+   <path fill="#ffce19" d="m302.381 263.749c2.868 4.211 0.9 11.168 2.033 13.196-5.398-4.657-7.845-7.159-15.164-0.081 1.022-1.605 2.097-4.642 3.542-7.601 1.283-2.524 3.241-6.88 4.244-6.979 0.755-0.067 3.642-1.026 5.345 1.465z"/>\r
+   <path fill="#ffce1c" d="m302.342 263.788c2.78 4.084 0.997 11.008 1.973 12.803-5.258-4.498-7.635-6.991-14.748-0.127 0.867-1.391 1.994-4.479 3.424-7.379 1.302-2.531 3.13-6.61 4.166-6.719 0.768-0.074 3.532-0.992 5.185 1.422z"/>\r
+   <path fill="#ffcf1e" d="m302.302 263.825c2.693 3.959 1.093 10.85 1.913 12.411-5.117-4.338-7.427-6.825-14.333-0.172 0.713-1.177 1.891-4.317 3.307-7.157 1.318-2.537 3.018-6.342 4.086-6.461 0.784-0.08 3.426-0.959 5.027 1.379z"/>\r
+   <path fill="#ffcf21" d="m302.263 263.862c2.606 3.834 1.188 10.689 1.853 12.02-4.976-4.178-7.217-6.657-13.916-0.217 0.556-0.963 1.786-4.156 3.188-6.936 1.337-2.545 2.906-6.072 4.008-6.202 0.797-0.086 3.318-0.927 4.867 1.335z"/>\r
+   <path fill="#ffcf23" d="m302.225 263.899c2.519 3.71 1.285 10.531 1.791 11.628-4.835-4.019-7.008-6.489-13.5-0.262 0.4-0.75 1.684-3.994 3.068-6.714 1.356-2.553 2.797-5.805 3.931-5.943 0.813-0.093 3.209-0.895 4.71 1.291z"/>\r
+   <path fill="#ffcf26" d="m302.186 263.937c2.431 3.584 1.381 10.371 1.73 11.235-4.693-3.857-6.798-6.322-13.084-0.307 0.245-0.535 1.58-3.832 2.951-6.492 1.373-2.56 2.686-5.535 3.852-5.685 0.828-0.1 3.101-0.861 4.551 1.249z"/>\r
+   <path fill="#ffd028" d="m302.147 263.974c2.344 3.46 1.477 10.213 1.671 10.845-4.553-3.699-6.589-6.156-12.668-0.354 0.089-0.321 1.477-3.67 2.832-6.271 1.392-2.565 2.574-5.267 3.772-5.425 0.842-0.104 2.994-0.828 4.393 1.205z"/>\r
+   <path fill="#ffd02b" d="m302.108 264.012c2.257 3.334 1.573 10.053 1.61 10.451-4.412-3.537-6.38-5.987-12.253-0.396-0.064-0.109 1.374-3.51 2.716-6.05 1.408-2.573 2.462-4.997 3.693-5.166 0.856-0.112 2.886-0.796 4.234 1.161z"/>\r
+   <path fill="#ffd02d" d="m302.069 264.049c2.17 3.209 1.67 9.894 1.55 10.061-4.271-3.379-6.17-5.82-11.836-0.441-0.221 0.104 1.271-3.35 2.596-5.83 1.428-2.58 2.352-4.728 3.615-4.906 0.87-0.12 2.777-0.765 4.075 1.116z"/>\r
+   <path fill="#ffd030" d="m302.03 264.086c2.082 3.084 1.767 9.736 1.49 9.668-4.131-3.219-5.961-5.652-11.42-0.486-0.377 0.318 1.167-3.188 2.478-5.607 1.445-2.586 2.239-4.459 3.536-4.647 0.884-0.127 2.669-0.732 3.916 1.072z"/>\r
+   <path fill="#ffd133" d="m301.991 264.124c1.995 2.959 1.862 9.576 1.43 9.277-3.989-3.059-5.752-5.486-11.005-0.531-0.532 0.531 1.064-3.027 2.36-5.387 1.463-2.594 2.128-4.189 3.458-4.389 0.899-0.133 2.561-0.699 3.757 1.03z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path fill="#fc0" d="m305.862 283.481c5.977 7.848 17.064 16.271 21.024 18.576 2.88 1.656 7.056 3.6 6.983 8.783-0.144 5.904-3.168 7.561-4.823 9.217-3.313 3.313-22.177 10.943-34.2 16.271-12.24 5.4-21.097 8.209-27.217 9.217-4.104 0.647-7.056 2.375-13.176 1.151-4.32-0.864-6.912-1.296-9.864-3.888-2.951-2.52-5.976-5.184-6.407-9.359-1.225-10.369 3.672-16.921 8.424-25.921 3.888-7.2 11.735-8.64 16.632-7.991 17.568 2.375 16.416-8.641 21.24-13.465 4.464-4.463 17.208-8.064 21.384-2.591z"/>\r
+   <path fill="#ffcc02" d="m305.81 283.553c5.962 7.83 17.024 16.234 20.975 18.533 2.873 1.652 7.039 3.592 6.969 8.764-0.145 5.891-3.161 7.542-4.813 9.195-3.304 3.304-22.34 10.992-34.24 16.088-12.259 5.176-20.647 7.873-26.802 8.959-4.077 0.684-7.156 2.394-13.258 1.177-4.304-0.854-6.767-1.231-9.707-3.812-2.939-2.51-5.756-4.961-6.185-9.117-1.211-10.34 3.365-16.657 8.044-25.65 3.89-7.375 11.791-8.434 16.665-7.777 17.514 2.414 16.206-8.959 21.02-13.772 4.452-4.454 17.166-8.047 21.332-2.588z"/>\r
+   <path fill="#ffcc05" d="m305.76 283.627c5.946 7.812 16.982 16.195 20.925 18.488 2.866 1.648 7.022 3.584 6.951 8.743-0.144 5.876-3.153 7.524-4.801 9.173-3.298 3.297-22.506 11.043-34.28 15.907-12.279 4.949-20.201 7.538-26.389 8.699-4.051 0.721-7.256 2.413-13.341 1.202-4.286-0.846-6.619-1.168-9.55-3.733-2.924-2.501-5.536-4.741-5.959-8.877-1.198-10.312 3.058-16.394 7.664-25.379 3.89-7.552 11.845-8.229 16.698-7.563 17.458 2.453 15.995-9.279 20.797-14.08 4.443-4.443 17.127-8.026 21.285-2.58z"/>\r
+   <path fill="#ffcc07" d="m305.707 283.702c5.935 7.791 16.943 16.156 20.876 18.444 2.859 1.644 7.007 3.574 6.935 8.722-0.144 5.862-3.146 7.508-4.79 9.151-3.288 3.289-22.668 11.093-34.319 15.726-12.298 4.723-19.753 7.202-25.974 8.44-4.024 0.756-7.357 2.431-13.423 1.226-4.27-0.836-6.473-1.101-9.394-3.654-2.911-2.492-5.317-4.52-5.735-8.635-1.185-10.285 2.75-16.133 7.284-25.109 3.892-7.727 11.9-8.023 16.731-7.35 17.402 2.494 15.785-9.599 20.575-14.389 4.433-4.432 17.087-8.007 21.234-2.572z"/>\r
+   <path fill="#ffcd0a" d="m305.655 283.774c5.92 7.773 16.904 16.119 20.826 18.4 2.854 1.642 6.99 3.566 6.919 8.703-0.143 5.848-3.138 7.488-4.779 9.129-3.28 3.281-22.832 11.143-34.358 15.543-12.317 4.498-19.307 6.867-25.561 8.182-3.997 0.793-7.457 2.45-13.506 1.251-4.252-0.828-6.325-1.036-9.236-3.577-2.896-2.482-5.096-4.298-5.51-8.393-1.172-10.258 2.443-15.869 6.904-24.84 3.892-7.901 11.955-7.818 16.763-7.135 17.349 2.532 15.576-9.918 20.355-14.697 4.422-4.422 17.046-7.987 21.183-2.566z"/>\r
+   <path fill="#ffcd0c" d="m305.603 283.847c5.906 7.756 16.864 16.081 20.777 18.358 2.846 1.635 6.973 3.557 6.901 8.68-0.142 5.835-3.131 7.472-4.767 9.107-3.273 3.273-22.997 11.193-34.399 15.361-12.337 4.273-18.858 6.533-25.146 7.924-3.971 0.829-7.557 2.469-13.588 1.276-4.234-0.82-6.179-0.972-9.078-3.5-2.884-2.474-4.878-4.076-5.287-8.152-1.158-10.229 2.137-15.606 6.523-24.569 3.895-8.076 12.01-7.611 16.797-6.92 17.293 2.571 15.366-10.236 20.134-15.004 4.412-4.411 17.006-7.969 21.133-2.561z"/>\r
+   <path fill="#ffcd0f" d="m305.552 283.92c5.892 7.736 16.824 16.043 20.728 18.313 2.839 1.634 6.957 3.55 6.886 8.66-0.142 5.821-3.124 7.454-4.756 9.086-3.266 3.267-23.161 11.243-34.438 15.179-12.356 4.047-18.411 6.198-24.733 7.666-3.943 0.865-7.656 2.486-13.67 1.301-4.218-0.812-6.032-0.908-8.922-3.422-2.869-2.465-4.656-3.855-5.063-7.91-1.145-10.203 1.83-15.344 6.145-24.299 3.895-8.252 12.064-7.408 16.83-6.707 17.237 2.61 15.154-10.557 19.912-15.313 4.399-4.399 16.963-7.949 21.081-2.554z"/>\r
+   <path fill="#ffcd11" d="m305.501 283.993c5.877 7.719 16.782 16.004 20.678 18.271 2.833 1.628 6.939 3.54 6.869 8.639-0.143 5.807-3.116 7.436-4.745 9.064-3.257 3.258-23.324 11.293-34.479 14.996-12.375 3.822-17.963 5.863-24.319 7.408-3.917 0.9-7.757 2.504-13.752 1.324-4.2-0.802-5.886-0.842-8.765-3.344-2.856-2.454-4.438-3.633-4.838-7.669-1.132-10.173 1.521-15.081 5.764-24.029 3.896-8.426 12.119-7.2 16.863-6.491 17.183 2.649 14.945-10.875 19.689-15.621 4.391-4.389 16.926-7.93 21.035-2.548z"/>\r
+   <path fill="#ffce14" d="m305.448 284.066c5.863 7.701 16.743 15.966 20.629 18.228 2.826 1.625 6.924 3.531 6.853 8.619-0.142 5.793-3.108 7.418-4.733 9.043-3.25 3.25-23.489 11.342-34.518 14.813-12.396 3.598-17.517 5.529-23.905 7.148-3.891 0.938-7.857 2.524-13.834 1.351-4.185-0.793-5.74-0.776-8.609-3.267-2.841-2.444-4.217-3.412-4.613-7.426-1.118-10.146 1.216-14.818 5.385-23.76 3.896-8.602 12.174-6.996 16.896-6.277 17.128 2.688 14.735-11.195 19.468-15.929 4.378-4.38 16.883-7.911 20.981-2.543z"/>\r
+   <path fill="#ffce16" d="m305.396 284.139c5.85 7.682 16.703 15.928 20.579 18.184 2.82 1.62 6.907 3.523 6.837 8.598-0.141 5.779-3.101 7.4-4.722 9.021-3.242 3.242-23.653 11.393-34.559 14.631-12.414 3.372-17.068 5.194-23.491 6.891-3.862 0.975-7.957 2.543-13.917 1.375-4.167-0.783-5.592-0.713-8.451-3.188-2.827-2.437-3.997-3.19-4.389-7.187-1.105-10.117 0.908-14.555 5.004-23.487 3.898-8.776 12.229-6.79 16.928-6.063 17.074 2.728 14.525-11.515 19.248-16.236 4.37-4.371 16.845-7.894 20.933-2.539z"/>\r
+   <path fill="#ffce19" d="m305.344 284.211c5.836 7.664 16.663 15.891 20.529 18.141 2.813 1.617 6.892 3.516 6.82 8.578-0.14 5.765-3.093 7.382-4.71 8.999-3.235 3.233-23.817 11.442-34.598 14.448-12.434 3.146-16.621 4.859-23.077 6.633-3.837 1.01-8.058 2.561-13.999 1.4-4.15-0.775-5.446-0.648-8.295-3.111-2.814-2.426-3.777-2.969-4.164-6.944-1.094-10.09 0.601-14.293 4.624-23.22 3.898-8.951 12.282-6.584 16.961-5.848 17.019 2.767 14.314-11.834 19.025-16.545 4.361-4.359 16.806-7.872 20.884-2.531z"/>\r
+   <path fill="#ffce1c" d="m305.292 284.286c5.822 7.646 16.623 15.852 20.481 18.096 2.806 1.613 6.874 3.507 6.804 8.558-0.141 5.751-3.086 7.364-4.699 8.978-3.227 3.227-23.981 11.492-34.638 14.267-12.453 2.921-16.173 4.524-22.663 6.374-3.81 1.046-8.158 2.578-14.082 1.424-4.133-0.766-5.299-0.583-8.137-3.033-2.801-2.416-3.558-2.748-3.94-6.703-1.08-10.062 0.293-14.029 4.244-22.947 3.9-9.127 12.338-6.379 16.994-5.635 16.964 2.805 14.105-12.152 18.805-16.853 4.348-4.351 16.763-7.856 20.831-2.526z"/>\r
+   <path fill="#ffcf1e" d="m305.241 284.358c5.808 7.627 16.582 15.814 20.432 18.053 2.799 1.609 6.856 3.498 6.787 8.536-0.141 5.738-3.079 7.347-4.688 8.957-3.219 3.218-24.145 11.541-34.677 14.084-12.473 2.695-15.727 4.188-22.25 6.115-3.783 1.083-8.258 2.599-14.163 1.448-4.116-0.756-5.153-0.518-7.981-2.954-2.786-2.408-3.337-2.526-3.716-6.462-1.066-10.034-0.013-13.766 3.864-22.678 3.901-9.303 12.393-6.172 17.027-5.42 16.908 2.844 13.896-12.473 18.583-17.16 4.338-4.337 16.723-7.835 20.782-2.519z"/>\r
+   <path fill="#ffcf21" d="m305.189 284.431c5.793 7.608 16.542 15.776 20.382 18.009 2.792 1.605 6.84 3.49 6.771 8.516-0.14 5.725-3.071 7.33-4.677 8.936-3.211 3.211-24.309 11.591-34.717 13.902-12.491 2.47-15.278 3.854-21.836 5.856-3.756 1.119-8.357 2.616-14.246 1.474-4.099-0.748-5.006-0.453-7.823-2.877-2.772-2.398-3.118-2.306-3.492-6.22-1.053-10.007-0.319-13.505 3.484-22.407 3.903-9.479 12.448-5.969 17.062-5.207 16.853 2.883 13.684-12.791 18.36-17.469 4.328-4.328 16.683-7.819 20.732-2.513z"/>\r
+   <path fill="#ffcf23" d="m305.137 284.504c5.778 7.59 16.503 15.736 20.332 17.965 2.786 1.602 6.825 3.482 6.755 8.496-0.139 5.709-3.064 7.311-4.665 8.912-3.204 3.203-24.474 11.642-34.759 13.721-12.51 2.244-14.829 3.52-21.421 5.598-3.729 1.155-8.457 2.635-14.327 1.499-4.082-0.739-4.86-0.389-7.667-2.8-2.76-2.389-2.897-2.083-3.268-5.979-1.04-9.979-0.627-13.24 3.104-22.138 3.903-9.653 12.503-5.762 17.093-4.991 16.799 2.922 13.475-13.111 18.141-17.777 4.318-4.316 16.643-7.799 20.682-2.506z"/>\r
+   <path fill="#ffcf26" d="m305.086 284.579c5.765 7.57 16.463 15.697 20.282 17.92 2.779 1.599 6.809 3.474 6.738 8.476-0.139 5.696-3.056 7.293-4.654 8.892-3.194 3.194-24.637 11.69-34.797 13.536-12.529 2.021-14.382 3.185-21.007 5.341-3.703 1.191-8.559 2.652-14.411 1.523-4.065-0.73-4.713-0.324-7.509-2.723-2.745-2.379-2.679-1.861-3.043-5.735-1.027-9.952-0.936-12.979 2.724-21.868 3.905-9.828 12.557-5.557 17.126-4.777 16.744 2.961 13.265-13.431 17.919-18.084 4.307-4.309 16.602-7.783 20.632-2.501z"/>\r
+   <path fill="#ffd028" d="m305.033 284.651c5.752 7.553 16.423 15.66 20.234 17.878 2.771 1.593 6.791 3.464 6.722 8.454-0.139 5.682-3.049 7.275-4.643 8.869-3.188 3.188-24.801 11.74-34.838 13.355-12.548 1.793-13.935 2.85-20.593 5.082-3.676 1.228-8.658 2.67-14.493 1.547-4.048-0.721-4.565-0.258-7.353-2.644-2.731-2.37-2.457-1.64-2.818-5.495-1.014-9.923-1.242-12.716 2.345-21.597 3.905-10.004 12.611-5.351 17.158-4.563 16.689 3 13.055-13.75 17.698-18.393 4.297-4.295 16.562-7.761 20.581-2.493z"/>\r
+   <path fill="#ffd02b" d="m304.982 284.724c5.737 7.534 16.382 15.622 20.184 17.834 2.766 1.59 6.774 3.456 6.705 8.433-0.138 5.67-3.041 7.26-4.631 8.85-3.18 3.179-24.966 11.789-34.877 13.172-12.568 1.568-13.487 2.515-20.179 4.824-3.65 1.263-8.759 2.688-14.575 1.572-4.031-0.713-4.42-0.195-7.196-2.566-2.718-2.361-2.238-1.42-2.594-5.254-1.001-9.896-1.549-12.453 1.964-21.328 3.907-10.178 12.666-5.145 17.192-4.348 16.634 3.039 12.844-14.068 17.476-18.701 4.285-4.286 16.521-7.743 20.531-2.488z"/>\r
+   <path fill="#ffd02d" d="m304.93 284.797c5.723 7.516 16.342 15.584 20.135 17.789 2.758 1.588 6.758 3.449 6.688 8.414-0.138 5.654-3.034 7.24-4.619 8.826-3.173 3.172-25.13 11.84-34.918 12.99-12.587 1.344-13.039 2.18-19.766 4.564-3.622 1.301-8.856 2.709-14.657 1.599-4.014-0.704-4.272-0.13-7.039-2.489-2.702-2.352-2.018-1.197-2.369-5.012-0.987-9.868-1.855-12.189 1.584-21.057 3.908-10.354 12.722-4.94 17.226-4.135 16.578 3.078 12.634-14.389 17.254-19.009 4.275-4.273 16.481-7.721 20.481-2.48z"/>\r
+   <path fill="#ffd030" d="m304.879 284.87c5.709 7.498 16.302 15.547 20.085 17.748 2.752 1.582 6.741 3.438 6.673 8.391-0.139 5.642-3.027 7.224-4.609 8.806-3.164 3.164-25.293 11.89-34.956 12.808-12.606 1.119-12.592 1.844-19.352 4.308-3.596 1.336-8.958 2.726-14.739 1.622-3.997-0.695-4.127-0.065-6.882-2.411-2.69-2.343-1.799-0.976-2.146-4.771-0.974-9.84-2.163-11.928 1.204-20.787 3.91-10.529 12.777-4.734 17.258-3.92 16.524 3.117 12.424-14.707 17.034-19.316 4.263-4.267 16.439-7.706 20.43-2.478z"/>\r
+   <path fill="#ffd133" d="m304.826 284.943c5.695 7.479 16.263 15.509 20.036 17.703 2.745 1.579 6.726 3.431 6.656 8.372-0.137 5.627-3.02 7.205-4.597 8.783-3.157 3.156-25.458 11.939-34.997 12.625-12.626 0.893-12.145 1.51-18.938 4.049-3.569 1.373-9.058 2.745-14.822 1.646-3.979-0.686-3.979 0-6.725-2.332-2.676-2.334-1.578-0.756-1.921-4.529-0.961-9.813-2.471-11.665 0.824-20.516 3.91-10.705 12.83-4.529 17.29-3.707 16.47 3.156 12.215-15.027 16.813-19.625 4.255-4.253 16.401-7.684 20.381-2.469z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path fill="#995900" d="m52.494 273.618c-6.479 4.68-22.896 4.248-27.072 9.719-4.104 5.473 0.145 13.393 0.072 28.08 0 6.265-1.08 11.017-1.8 14.832-1.008 4.824-1.656 8.209 0.36 11.664 3.672 6.121 9.575 7.633 43.344 14.688 18.072 3.744 35.136 13.464 46.584 14.399 11.448 0.865 13.896-2.951 20.88-9.144 6.912-6.192 9.144-4.248 8.928-17.856-0.216-13.535-8.928-17.567-18.792-33.191s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-8.208 13.248-14.688 17.929z"/>\r
+   <path fill="#9e5e00" d="m52.598 273.905c-6.397 4.702-22.475 3.788-27.062 9.512-4.154 5.414 0.228 13.276 0.098 27.955-0.025 6.23-1.152 10.881-1.937 14.877-1.037 4.871-1.678 8.201 0.349 11.619 3.787 6.162 9.695 7.123 43.456 14.168 18.061 3.737 34.541 13.307 46.343 14.112 11.186 0.792 13.564-2.829 20.463-8.96 6.896-6.195 9.024-4.277 8.858-17.406-0.075-13.521-8.305-17.349-18.169-32.973s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-8.153 13.479-14.583 18.216z"/>\r
+   <path fill="#a36400" d="m52.703 274.193c-6.314 4.724-22.054 3.327-27.051 9.304-4.204 5.356 0.31 13.16 0.123 27.828-0.051 6.198-1.225 10.748-2.074 14.924-1.065 4.918-1.699 8.194 0.339 11.571 3.902 6.206 9.813 6.617 43.567 13.651 18.05 3.73 33.948 13.146 46.101 13.824 10.923 0.72 13.234-2.707 20.045-8.777 6.885-6.199 8.907-4.305 8.792-16.956 0.064-13.507-7.683-17.129-17.547-32.753s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-8.1 13.709-14.479 18.504z"/>\r
+   <path fill="#a86a00" d="m52.807 274.481c-6.231 4.745-21.633 2.866-27.04 9.094-4.255 5.299 0.393 13.047 0.148 27.702-0.076 6.167-1.297 10.616-2.211 14.972-1.095 4.965-1.721 8.188 0.328 11.524 4.018 6.25 9.932 6.108 43.679 13.134 18.039 3.721 33.354 12.988 45.86 13.535 10.659 0.648 12.902-2.585 19.627-8.593 6.869-6.203 8.788-4.335 8.723-16.507 0.205-13.492-7.06-16.909-16.924-32.533s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-8.045 13.94-14.374 18.792z"/>\r
+   <path fill="#ad7000" d="m52.912 274.769c-6.149 4.767-21.211 2.405-27.029 8.885-4.306 5.242 0.476 12.931 0.173 27.576-0.101 6.136-1.368 10.483-2.347 15.019-1.123 5.012-1.742 8.181 0.316 11.478 4.133 6.293 10.052 5.603 43.791 12.614 18.028 3.716 32.76 12.83 45.619 13.248 10.396 0.576 12.57-2.463 19.21-8.409 6.854-6.206 8.668-4.363 8.653-16.056 0.347-13.479-6.437-16.69-16.301-32.314s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-7.991 14.169-14.269 19.079z"/>\r
+   <path fill="#b27500" d="m53.016 275.057c-6.066 4.787-20.79 1.943-27.019 8.676-4.355 5.184 0.559 12.816 0.198 27.45-0.126 6.103-1.44 10.351-2.484 15.065-1.151 5.059-1.764 8.172 0.307 11.431 4.248 6.336 10.17 5.094 43.901 12.096 18.019 3.708 32.166 12.672 45.378 12.96 10.135 0.504 12.24-2.34 18.792-8.227 6.841-6.209 8.551-4.391 8.586-15.605 0.486-13.464-5.813-16.47-15.678-32.094s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-7.937 14.399-14.165 19.368z"/>\r
+   <path fill="#b77b00" d="m53.121 275.344c-5.983 4.811-20.369 1.484-27.008 8.469-4.406 5.126 0.641 12.7 0.224 27.324-0.151 6.068-1.512 10.216-2.621 15.111-1.181 5.105-1.785 8.166 0.295 11.385 4.363 6.379 10.289 4.586 44.014 11.576 18.008 3.701 31.572 12.515 45.137 12.672 9.872 0.433 11.909-2.217 18.375-8.041 6.825-6.215 8.431-4.422 8.518-15.156 0.627-13.45-5.191-16.251-15.056-31.875s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-7.884 14.631-14.062 19.655z"/>\r
+   <path fill="#bc8100" d="m53.225 275.633c-5.9 4.832-19.948 1.023-26.997 8.259-4.457 5.069 0.724 12.585 0.249 27.198-0.177 6.037-1.584 10.082-2.758 15.158-1.21 5.152-1.808 8.158 0.284 11.338 4.479 6.422 10.407 4.078 44.125 11.059 17.997 3.693 30.979 12.355 44.896 12.384 9.608 0.36 11.578-2.095 17.957-7.858 6.812-6.217 8.313-4.449 8.45-14.707 0.766-13.435-4.569-16.03-14.434-31.654s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-7.829 14.859-13.956 19.943z"/>\r
+   <path fill="#c18700" d="m53.329 275.92c-5.817 4.854-19.526 0.563-26.985 8.051-4.507 5.011 0.807 12.47 0.273 27.072-0.201 6.004-1.655 9.949-2.894 15.205-1.239 5.199-1.829 8.151 0.273 11.291 4.594 6.465 10.526 3.57 44.236 10.541 17.985 3.686 30.384 12.196 44.655 12.096 9.345 0.287 11.245-1.973 17.539-7.676 6.797-6.221 8.193-4.479 8.381-14.256 0.906-13.42-3.946-15.812-13.811-31.436s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-7.774 15.094-13.851 20.232z"/>\r
+   <path fill="#c68c00" d="m53.433 276.209c-5.734 4.875-19.104 0.101-26.975 7.84-4.558 4.955 0.89 12.355 0.299 26.947-0.227 5.973-1.728 9.816-3.031 15.252-1.267 5.246-1.851 8.145 0.263 11.244 4.709 6.508 10.646 3.063 44.349 10.022 17.975 3.679 29.79 12.038 44.413 11.808 9.083 0.217 10.915-1.851 17.122-7.492 6.782-6.224 8.074-4.506 8.313-13.806 1.048-13.405-3.323-15.592-13.188-31.216s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-7.722 15.322-13.749 20.521z"/>\r
+   <path fill="#cc9200" d="m53.538 276.497c-5.651 4.896-18.684-0.359-26.964 7.633-4.607 4.896 0.972 12.24 0.324 26.82-0.252 5.939-1.8 9.684-3.168 15.299-1.296 5.293-1.872 8.137 0.252 11.196 4.824 6.552 10.764 2.556 44.46 9.505 17.964 3.672 29.196 11.879 44.172 11.52 8.82 0.144 10.584-1.729 16.704-7.309 6.768-6.228 7.956-4.535 8.244-13.355 1.188-13.393-2.7-15.372-12.564-30.996s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-7.668 15.551-13.644 20.807z"/>\r
+   <path fill="#d19800" d="m53.642 276.786c-5.569 4.918-18.263-0.82-26.953 7.424-4.658 4.838 1.055 12.123 0.35 26.693-0.277 5.907-1.872 9.551-3.305 15.346-1.325 5.34-1.894 8.129 0.241 11.15 4.938 6.596 10.883 2.048 44.571 8.984 17.953 3.666 28.602 11.723 43.931 11.232 8.558 0.072 10.253-1.605 16.287-7.123 6.753-6.232 7.837-4.566 8.175-12.906 1.329-13.379-2.077-15.153-11.941-30.777s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-7.614 15.783-13.54 21.097z"/>\r
+   <path fill="#d69e00" d="m53.747 277.073c-5.486 4.94-17.842-1.281-26.942 7.215-4.709 4.781 1.138 12.01 0.374 26.568-0.302 5.875-1.943 9.417-3.441 15.393-1.354 5.387-1.915 8.123 0.23 11.104 5.055 6.639 11.002 1.541 44.684 8.467 17.942 3.658 28.008 11.563 43.688 10.944 8.296 0 9.923-1.483 15.869-6.941 6.74-6.235 7.72-4.593 8.108-12.456 1.468-13.363-1.455-14.933-11.319-30.557s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-7.56 16.012-13.435 21.383z"/>\r
+   <path fill="#dba300" d="m53.851 277.361c-5.403 4.962-17.421-1.741-26.932 7.007-4.759 4.723 1.221 11.893 0.399 26.441-0.327 5.843-2.016 9.283-3.578 15.439-1.383 5.434-1.937 8.115 0.22 11.057 5.169 6.682 11.12 1.033 44.795 7.949 17.932 3.649 27.414 11.404 43.448 10.656 8.031-0.072 9.59-1.361 15.451-6.758 6.725-6.238 7.6-4.623 8.039-12.006 1.609-13.35-0.832-14.714-10.696-30.338s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-7.504 16.245-13.33 21.673z"/>\r
+   <path fill="#e0a900" d="m53.956 277.649c-5.321 4.982-16.999-2.203-26.921 6.797-4.81 4.666 1.303 11.779 0.425 26.316-0.353 5.811-2.088 9.15-3.715 15.486-1.411 5.48-1.959 8.108 0.208 11.01 5.285 6.725 11.239 0.525 44.907 7.431 17.92 3.644 26.819 11.246 43.207 10.368 7.769-0.145 9.259-1.239 15.034-6.574 6.71-6.242 7.479-4.65 7.97-11.557 1.75-13.334-0.209-14.493-10.073-30.117s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-7.452 16.474-13.226 21.96z"/>\r
+   <path fill="#e5af00" d="m54.06 277.937c-5.238 5.004-16.578-2.664-26.91 6.588-4.86 4.608 1.386 11.664 0.45 26.19-0.378 5.777-2.16 9.018-3.853 15.533-1.439 5.526-1.979 8.101 0.198 10.963 5.4 6.768 11.358 0.018 45.018 6.912 17.91 3.635 26.227 11.088 42.967 10.08 7.506-0.217 8.928-1.117 14.615-6.391 6.696-6.246 7.362-4.68 7.902-11.105 1.89-13.32 0.414-14.274-9.45-29.898s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-7.397 16.704-13.121 22.248z"/>\r
+   <path fill="#eab500" d="m54.165 278.225c-5.155 5.025-16.157-3.124-26.899 6.38-4.91 4.55 1.469 11.548 0.476 26.063-0.403 5.746-2.232 8.885-3.989 15.58-1.469 5.573-2.002 8.094 0.188 10.916 5.515 6.812 11.477-0.49 45.129 6.394 17.899 3.629 25.633 10.93 42.725 9.792 7.243-0.288 8.598-0.993 14.199-6.206 6.681-6.25 7.243-4.709 7.833-10.656 2.031-13.306 1.037-14.055-8.827-29.679s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-7.346 16.935-13.019 22.536z"/>\r
+   <path fill="#efba00" d="m54.269 278.513c-5.073 5.048-15.736-3.585-26.889 6.171-4.961 4.492 1.552 11.434 0.5 25.938-0.428 5.713-2.304 8.752-4.125 15.627-1.498 5.621-2.023 8.086 0.176 10.869 5.631 6.854 11.596-0.996 45.241 5.875 17.889 3.623 25.038 10.771 42.483 9.504 6.981-0.359 8.266-0.871 13.781-6.022 6.668-6.253 7.125-4.737 7.766-10.206 2.17-13.291 1.659-13.835-8.205-29.459s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-7.289 17.164-12.912 22.823z"/>\r
+   <path fill="#f4c000" d="m54.374 278.801c-4.99 5.068-15.314-4.047-26.878 5.962-5.012 4.435 1.634 11.317 0.525 25.812-0.453 5.682-2.376 8.618-4.263 15.674-1.526 5.668-2.045 8.08 0.166 10.822 5.745 6.898 11.714-1.505 45.353 5.357 17.878 3.613 24.444 10.613 42.243 9.216 6.717-0.433 7.934-0.749 13.362-5.839 6.653-6.258 7.007-4.768 7.697-9.756 2.312-13.277 2.282-13.616-7.582-29.24s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-7.235 17.395-12.807 23.112z"/>\r
+   <path fill="#f9c600" d="m54.477 279.088c-4.906 5.092-14.893-4.506-26.866 5.754-5.062 4.377 1.717 11.203 0.551 25.686-0.479 5.648-2.448 8.485-4.399 15.721-1.555 5.715-2.066 8.072 0.155 10.775 5.86 6.941 11.833-2.012 45.464 4.839 17.867 3.607 23.851 10.454 42.001 8.929 6.455-0.504 7.604-0.627 12.946-5.656 6.638-6.26 6.886-4.795 7.628-9.307 2.452-13.262 2.905-13.396-6.959-29.02s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-7.182 17.626-12.705 23.399z"/>\r
+   <path fill="#fc0" d="m54.582 279.377c-4.823 5.111-14.472-4.969-26.855 5.543-5.112 4.32 1.8 11.088 0.576 25.561-0.504 5.616-2.521 8.352-4.536 15.768-1.584 5.76-2.088 8.064 0.144 10.729 5.977 6.984 11.952-2.52 45.576 4.32 17.856 3.6 23.256 10.295 41.76 8.64 6.192-0.576 7.272-0.504 12.528-5.472 6.624-6.264 6.768-4.824 7.56-8.856 2.592-13.248 3.528-13.176-6.336-28.8s-11.448-18.504-18-28.872c-6.552-10.224-19.512-28.8-26.928-29.017-5.904-0.144-9.216 3.024-12.888 6.769s-7.129 17.855-12.601 23.687z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path fill="#fc0" d="m57.701 285.002c-4.278 4.539-18.28-1.36-24.892 3.631-4.732 3.5 2.398 7.908 1.426 20.873-0.389 4.926-3.824 5.834-2.398 12.64 1.103 5.121 2.27 4.991 4.214 7.325 5.315 6.223 4.084 1.686 34.355 7.777 16.011 3.242 20.938 9.271 37.596 7.779 5.575-0.518 6.612-0.453 11.279-4.926 5.964-5.575 2.917-4.408 3.565-7.973 2.269-11.863 0.453-13.938-8.428-28.004-8.88-14.066-8.102-14.844-13.936-24.113-5.834-9.141-13.872-25.799-20.549-25.93-5.25-0.129-8.297 2.723-11.603 6.094-3.305 3.372-5.768 19.643-10.629 24.827z"/>\r
+   <path fill="#ffcc02" d="m57.995 285.094c-4.461 4.701-18.604-1.196-25.06 3.705-4.701 3.514 2.578 8.05 1.608 20.68-0.4 4.896-3.733 5.877-2.458 12.634 0.986 5.093 2.357 4.938 4.334 7.201 5.686 6.131 4.673 1.826 34.119 7.743 15.918 3.211 20.815 9.215 37.372 7.732 5.541-0.513 6.479-0.463 11.155-4.849 5.865-5.407 2.858-4.287 3.412-8.058 2.066-11.787 0.442-13.981-8.188-27.649-8.83-13.983-8.143-14.698-13.941-23.913-5.8-9.085-13.701-25.805-20.338-25.934-5.219-0.129-8.248 2.705-11.533 6.057-3.287 3.352-5.644 19.505-10.482 24.651z"/>\r
+   <path fill="#ffcc05" d="m58.289 285.185c-4.644 4.866-18.93-1.031-25.229 3.78-4.669 3.527 2.759 8.191 1.792 20.488-0.413 4.866-3.642 5.918-2.519 12.625 0.872 5.066 2.447 4.887 4.455 7.078 6.057 6.04 5.263 1.969 33.883 7.709 15.825 3.18 20.693 9.159 37.148 7.686 5.508-0.506 6.345-0.471 11.03-4.77 5.768-5.24 2.803-4.168 3.26-8.142 1.865-11.716 0.432-14.027-7.949-27.298-8.78-13.899-8.183-14.553-13.948-23.713-5.764-9.03-13.529-25.811-20.126-25.938-5.188-0.129-8.198 2.688-11.465 6.021-3.266 3.331-5.517 19.368-10.332 24.474z"/>\r
+   <path fill="#ffcc07" d="m58.584 285.276c-4.827 5.03-19.255-0.865-25.397 3.855-4.639 3.541 2.938 8.332 1.975 20.295-0.425 4.838-3.551 5.961-2.578 12.619 0.757 5.039 2.536 4.834 4.575 6.955 6.428 5.949 5.852 2.108 33.646 7.674 15.733 3.147 20.571 9.103 36.925 7.639 5.475-0.501 6.211-0.48 10.905-4.693 5.67-5.072 2.745-4.045 3.107-8.224 1.663-11.642 0.42-14.073-7.711-26.946-8.73-13.814-8.223-14.406-13.952-23.511-5.729-8.976-13.358-25.817-19.916-25.944-5.156-0.127-8.148 2.674-11.396 5.984s-5.391 19.229-10.183 24.297z"/>\r
+   <path fill="#ffcd0a" d="m58.878 285.368c-5.01 5.193-19.579-0.701-25.565 3.93-4.607 3.555 3.117 8.475 2.157 20.102-0.437 4.809-3.459 6.004-2.639 12.613 0.643 5.011 2.626 4.781 4.695 6.83 6.799 5.857 6.442 2.25 33.411 7.64 15.641 3.118 20.45 9.048 36.701 7.593 5.44-0.494 6.076-0.488 10.781-4.615 5.57-4.904 2.688-3.926 2.954-8.308 1.462-11.569 0.409-14.118-7.472-26.593-8.68-13.732-8.263-14.262-13.958-23.311-5.695-8.922-13.188-25.824-19.705-25.951-5.125-0.127-8.1 2.658-11.326 5.949-3.227 3.289-5.266 19.091-10.034 24.121z"/>\r
+   <path fill="#ffcd0c" d="m59.173 285.458c-5.193 5.358-19.905-0.535-25.734 4.008-4.577 3.566 3.297 8.613 2.34 19.908-0.449 4.778-3.368 6.045-2.698 12.605 0.526 4.982 2.715 4.729 4.815 6.707 7.17 5.766 7.031 2.391 33.175 7.604 15.549 3.088 20.328 8.994 36.477 7.547 5.408-0.488 5.943-0.498 10.657-4.537 5.472-4.737 2.63-3.805 2.802-8.393 1.261-11.494 0.398-14.162-7.232-26.24-8.63-13.647-8.304-14.117-13.964-23.109-5.66-8.867-13.017-25.829-19.494-25.956-5.094-0.126-8.05 2.642-11.257 5.912-3.21 3.27-5.142 18.955-9.887 23.944z"/>\r
+   <path fill="#ffcd0f" d="m59.467 285.547c-5.376 5.523-20.229-0.369-25.903 4.084-4.545 3.58 3.478 8.756 2.523 19.715-0.461 4.75-3.277 6.088-2.758 12.599 0.411 4.955 2.804 4.677 4.936 6.584 7.541 5.675 7.621 2.532 32.938 7.569 15.456 3.056 20.206 8.938 36.253 7.5 5.375-0.482 5.811-0.506 10.533-4.459 5.374-4.57 2.572-3.686 2.649-8.477 1.058-11.42 0.387-14.209-6.995-25.888-8.58-13.563-8.344-13.972-13.969-22.909-5.626-8.813-12.846-25.834-19.283-25.961-5.063-0.125-8 2.625-11.188 5.877-3.188 3.251-5.015 18.818-9.736 23.766z"/>\r
+   <path fill="#ffcd11" d="m59.76 285.639c-5.559 5.688-20.555-0.205-26.071 4.158-4.515 3.594 3.657 8.896 2.706 19.521-0.473 4.721-3.186 6.131-2.818 12.594 0.297 4.926 2.894 4.623 5.057 6.459 7.911 5.584 8.21 2.674 32.703 7.535 15.362 3.025 20.084 8.883 36.027 7.453 5.342-0.477 5.677-0.515 10.409-4.381 5.275-4.402 2.516-3.564 2.497-8.561 0.855-11.348 0.375-14.254-6.756-25.535-8.53-13.479-8.385-13.825-13.976-22.709-5.59-8.758-12.673-25.842-19.071-25.965-5.031-0.125-7.951 2.608-11.119 5.838-3.168 3.232-4.888 18.684-9.588 23.593z"/>\r
+   <path fill="#ffce14" d="m60.055 285.73c-5.742 5.851-20.88-0.04-26.24 4.233-4.483 3.607 3.837 9.039 2.889 19.33-0.485 4.69-3.095 6.172-2.878 12.584 0.182 4.9 2.982 4.572 5.177 6.338 8.282 5.492 8.8 2.814 32.467 7.498 15.271 2.996 19.962 8.828 35.804 7.408 5.309-0.472 5.543-0.523 10.285-4.304 5.177-4.235 2.458-3.444 2.344-8.644 0.654-11.273 0.364-14.299-6.518-25.184-8.479-13.395-8.425-13.679-13.98-22.507-5.556-8.704-12.502-25.849-18.86-25.972-5-0.123-7.901 2.593-11.05 5.803s-4.765 18.547-9.44 23.417z"/>\r
+   <path fill="#ffce16" d="m60.349 285.821c-5.925 6.016-21.205 0.125-26.408 4.309-4.453 3.621 4.017 9.18 3.07 19.137-0.496 4.662-3.002 6.215-2.938 12.579 0.066 4.872 3.072 4.518 5.298 6.213 8.653 5.401 9.389 2.956 32.23 7.464 15.178 2.964 19.84 8.771 35.58 7.361 5.275-0.465 5.409-0.532 10.159-4.225 5.079-4.068 2.401-3.324 2.192-8.729 0.452-11.2 0.354-14.346-6.279-24.831-8.429-13.312-8.464-13.534-13.985-22.306-5.521-8.65-12.331-25.854-18.649-25.978-4.969-0.123-7.853 2.577-10.98 5.767-3.129 3.19-4.637 18.409-9.29 23.239z"/>\r
+   <path fill="#ffce19" d="m60.643 285.911c-6.107 6.18-21.529 0.291-26.577 4.385-4.421 3.635 4.197 9.322 3.254 18.945-0.508 4.631-2.911 6.256-2.997 12.571-0.049 4.845 3.161 4.465 5.418 6.089 9.023 5.309 9.979 3.098 31.994 7.43 15.085 2.934 19.718 8.717 35.355 7.314 5.242-0.459 5.276-0.541 10.036-4.148 4.98-3.899 2.344-3.203 2.039-8.811 0.25-11.127 0.342-14.391-6.04-24.479-8.38-13.228-8.505-13.389-13.991-22.105-5.486-8.596-12.16-25.859-18.438-25.982-4.938-0.123-7.803 2.561-10.911 5.73-3.109 3.17-4.513 18.272-9.142 23.061z"/>\r
+   <path fill="#ffce1c" d="m60.938 286.002c-6.291 6.344-21.855 0.455-26.746 4.459-4.391 3.648 4.377 9.463 3.437 18.752-0.521 4.603-2.82 6.299-3.058 12.564-0.163 4.817 3.251 4.413 5.539 5.965 9.395 5.219 10.567 3.24 31.758 7.396 14.993 2.903 19.597 8.661 35.132 7.269 5.209-0.453 5.143-0.55 9.912-4.07 4.882-3.732 2.286-3.082 1.887-8.895 0.048-11.053 0.329-14.436-5.802-24.126-8.33-13.144-8.545-13.243-13.997-21.905-5.451-8.541-11.988-25.865-18.228-25.988-4.906-0.121-7.753 2.545-10.843 5.695-3.088 3.149-4.385 18.132-8.991 22.884z"/>\r
+   <path fill="#ffcf1e" d="m61.232 286.092c-6.473 6.51-22.18 0.621-26.914 4.535-4.359 3.662 4.557 9.604 3.619 18.559-0.532 4.574-2.729 6.342-3.117 12.559-0.278 4.789 3.34 4.36 5.659 5.842 9.766 5.127 11.157 3.381 31.521 7.359 14.9 2.872 19.475 8.607 34.908 7.223 5.176-0.447 5.008-0.559 9.787-3.992 4.784-3.565 2.229-2.963 1.735-8.979-0.155-10.98 0.317-14.482-5.564-23.773-8.279-13.061-8.585-13.098-14.002-21.705-5.416-8.485-11.817-25.871-18.017-25.992-4.875-0.121-7.704 2.527-10.773 5.658-3.068 3.128-4.26 17.995-8.842 22.706z"/>\r
+   <path fill="#ffcf21" d="m61.526 286.184c-6.655 6.673-22.505 0.785-27.082 4.611-4.328 3.674 4.736 9.744 3.802 18.365-0.544 4.543-2.638 6.383-3.178 12.551-0.394 4.761 3.43 4.308 5.78 5.718 10.136 5.036 11.746 3.522 31.285 7.325 14.808 2.841 19.353 8.551 34.685 7.176 5.142-0.441 4.875-0.567 9.663-3.914 4.685-3.398 2.171-2.842 1.582-9.063-0.357-10.906 0.307-14.527-5.325-23.422-8.23-12.976-8.625-12.951-14.008-21.503-5.382-8.432-11.646-25.878-17.806-25.999-4.844-0.119-7.654 2.512-10.704 5.622-3.049 3.109-4.134 17.859-8.694 22.533z"/>\r
+   <path fill="#ffcf23" d="m61.821 286.275c-6.839 6.837-22.83 0.95-27.251 4.687-4.298 3.688 4.916 9.886 3.984 18.172-0.557 4.515-2.546 6.426-3.237 12.545-0.509 4.732 3.519 4.255 5.9 5.594 10.507 4.945 12.336 3.663 31.05 7.29 14.715 2.812 19.23 8.496 34.46 7.129 5.108-0.435 4.74-0.575 9.538-3.835 4.587-3.23 2.113-2.723 1.43-9.146-0.559-10.834 0.296-14.572-5.086-23.069-8.18-12.892-8.666-12.806-14.014-21.302-5.347-8.377-11.476-25.885-17.595-26.004-4.813-0.119-7.605 2.496-10.635 5.584-3.029 3.088-4.008 17.722-8.544 22.355z"/>\r
+   <path fill="#ffcf26" d="m62.115 286.366c-7.021 7.002-23.154 1.115-27.42 4.762-4.266 3.701 5.096 10.027 4.168 17.979-0.568 4.486-2.455 6.469-3.297 12.538-0.624 4.706 3.607 4.203 6.021 5.472 10.878 4.852 12.925 3.803 30.813 7.254 14.622 2.78 19.108 8.441 34.236 7.084 5.075-0.43 4.606-0.584 9.413-3.759 4.489-3.063 2.058-2.601 1.277-9.229-0.761-10.76 0.284-14.618-4.848-22.717-8.129-12.809-8.706-12.66-14.019-21.102-5.313-8.322-11.304-25.891-17.384-26.01-4.781-0.118-7.556 2.48-10.566 5.55-3.008 3.068-3.881 17.584-8.394 22.178z"/>\r
+   <path fill="#ffd028" d="m62.409 286.456c-7.204 7.166-23.479 1.281-27.588 4.838-4.235 3.715 5.275 10.168 4.351 17.787-0.58 4.455-2.364 6.51-3.357 12.53-0.738 4.679 3.697 4.149 6.142 5.347 11.249 4.763 13.515 3.946 30.577 7.221 14.529 2.748 18.986 8.386 34.012 7.037 5.043-0.424 4.474-0.594 9.289-3.68 4.392-2.897 2-2.481 1.125-9.314-0.963-10.686 0.273-14.664-4.608-22.364-8.079-12.726-8.746-12.517-14.024-20.901-5.277-8.269-11.133-25.896-17.173-26.015-4.75-0.116-7.506 2.464-10.497 5.513-2.992 3.047-3.759 17.447-8.249 22.001z"/>\r
+   <path fill="#ffd02b" d="m62.704 286.547c-7.388 7.33-23.805 1.445-27.757 4.912-4.204 3.729 5.455 10.311 4.533 17.594-0.593 4.427-2.272 6.553-3.417 12.523-0.854 4.651 3.786 4.098 6.262 5.225 11.619 4.671 14.104 4.087 30.341 7.185 14.438 2.72 18.864 8.33 33.787 6.991 5.01-0.418 4.341-0.604 9.166-3.604 4.292-2.729 1.942-2.359 0.972-9.396-1.163-10.613 0.263-14.709-4.369-22.012-8.03-12.641-8.787-12.371-14.03-20.7-5.243-8.214-10.962-25.903-16.962-26.021-4.719-0.117-7.457 2.447-10.428 5.477-2.972 3.029-3.632 17.313-8.098 21.826z"/>\r
+   <path fill="#ffd02d" d="m62.998 286.638c-7.57 7.493-24.13 1.61-27.925 4.987-4.174 3.742 5.635 10.451 4.716 17.4-0.604 4.398-2.182 6.596-3.478 12.518-0.969 4.623 3.875 4.045 6.382 5.101 11.991 4.579 14.694 4.228 30.105 7.149 14.345 2.688 18.743 8.275 33.563 6.944 4.977-0.411 4.207-0.61 9.042-3.524 4.193-2.562 1.884-2.239 0.819-9.481-1.366-10.538 0.251-14.754-4.133-21.659-7.979-12.557-8.825-12.224-14.034-20.498-5.208-8.16-10.791-25.91-16.751-26.027-4.688-0.114-7.407 2.433-10.358 5.441-2.951 3.01-3.505 17.174-7.948 21.649z"/>\r
+   <path fill="#ffd030" d="m63.292 286.729c-7.753 7.658-24.454 1.775-28.094 5.063-4.142 3.756 5.815 10.594 4.899 17.209-0.616 4.367-2.09 6.638-3.537 12.51-1.084 4.596 3.964 3.992 6.502 4.977 12.362 4.488 15.283 4.369 29.869 7.115 14.252 2.656 18.621 8.22 33.34 6.898 4.943-0.406 4.073-0.621 8.917-3.447 4.095-2.395 1.828-2.119 0.667-9.565-1.568-10.465 0.239-14.8-3.894-21.307-7.929-12.474-8.866-12.078-14.04-20.298-5.173-8.105-10.619-25.916-16.54-26.031-4.656-0.115-7.357 2.415-10.289 5.404-2.932 2.988-3.38 17.036-7.8 21.472z"/>\r
+   <path fill="#ffd133" d="m63.587 286.82c-7.937 7.822-24.78 1.94-28.263 5.138-4.111 3.77 5.995 10.734 5.081 17.016-0.627 4.339-1.998 6.68-3.597 12.504-1.199 4.568 4.054 3.939 6.623 4.854 12.732 4.396 15.873 4.51 29.633 7.08 14.16 2.625 18.499 8.164 33.116 6.852 4.909-0.4 3.939-0.629 8.793-3.369 3.997-2.227 1.77-1.999 0.514-9.648-1.771-10.393 0.228-14.846-3.654-20.955-7.88-12.39-8.907-11.934-14.046-20.098-5.139-8.051-10.448-25.922-16.329-26.037-4.625-0.113-7.309 2.398-10.221 5.368s-3.254 16.897-7.65 21.295z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path d="m88.782 218.681c-0.936 2.088-1.728 20.017 2.952 27 4.68 6.911 3.312 10.872-1.872 5.616-5.4-5.112-8.928-12.816-9-18.145 0-3.096 2.376-15.84 3.313-17.208 1.007-1.44 5.327 1.153 4.607 2.737z"/>\r
+   <path fill="#030303" d="m88.692 219.032c-0.903 2.34-1.656 19.698 3.01 26.668 4.665 6.901 3.186 10.49-1.84 5.356-5.23-4.997-8.607-12.47-8.741-17.787-0.043-3.114 2.236-15.419 3.133-16.791 0.961-1.394 5.126 0.989 4.438 2.554z"/>\r
+   <path fill="#070707" d="m88.602 219.379c-0.871 2.593-1.584 19.383 3.067 26.338 4.65 6.891 3.06 10.109-1.808 5.098-5.062-4.882-8.287-12.125-8.481-17.432-0.087-3.131 2.095-14.998 2.952-16.373 0.915-1.345 4.926 0.828 4.27 2.369z"/>\r
+   <path fill="#0b0b0b" d="m88.512 219.729c-0.839 2.844-1.513 19.066 3.124 26.006 4.638 6.881 2.935 9.729-1.774 4.84-4.893-4.768-7.967-11.779-8.223-17.076-0.129-3.149 1.955-14.576 2.772-15.955 0.868-1.299 4.724 0.665 4.101 2.185z"/>\r
+   <path fill="#0f0f0f" d="m88.422 220.079c-0.806 3.096-1.439 18.748 3.183 25.674 4.623 6.869 2.809 9.347-1.742 4.58-4.724-4.651-7.646-11.434-7.963-16.719-0.173-3.168 1.814-14.154 2.592-15.537 0.819-1.252 4.52 0.504 3.93 2.002z"/>\r
+   <path fill="#131313" d="m88.332 220.426c-0.773 3.349-1.368 18.433 3.24 25.345 4.608 6.858 2.682 8.964-1.71 4.319-4.554-4.535-7.326-11.088-7.704-16.361-0.216-3.186 1.674-13.734 2.412-15.12 0.774-1.206 4.32 0.343 3.762 1.817z"/>\r
+   <path fill="#161616" d="m88.242 220.777c-0.741 3.601-1.296 18.115 3.298 25.013 4.594 6.848 2.556 8.582-1.678 4.061-4.385-4.421-7.006-10.742-7.444-16.006-0.26-3.203 1.533-13.313 2.231-14.702 0.728-1.16 4.118 0.18 3.593 1.634z"/>\r
+   <path fill="#1a1a1a" d="m88.152 221.125c-0.709 3.853-1.224 17.799 3.355 24.682 4.579 6.837 2.43 8.201-1.646 3.802-4.216-4.306-6.686-10.397-7.186-15.649-0.303-3.221 1.394-12.892 2.052-14.285 0.682-1.112 3.918 0.018 3.425 1.45z"/>\r
+   <path fill="#1e1e1e" d="m88.062 221.475c-0.677 4.104-1.152 17.482 3.413 24.35 4.564 6.826 2.304 7.82-1.613 3.543-4.046-4.191-6.365-10.051-6.927-15.293-0.345-3.24 1.253-12.47 1.872-13.867 0.634-1.066 3.716-0.144 3.255 1.267z"/>\r
+   <path fill="#222" d="m87.972 221.825c-0.645 4.355-1.08 17.164 3.47 24.018 4.551 6.816 2.179 7.439-1.58 3.285-3.877-4.076-6.044-9.707-6.667-14.938-0.389-3.258 1.112-12.049 1.691-13.449 0.587-1.019 3.514-0.306 3.086 1.084z"/>\r
+   <path fill="#262626" d="m87.883 222.172c-0.612 4.609-1.009 16.849 3.527 23.688 4.536 6.804 2.052 7.056-1.548 3.024-3.708-3.961-5.724-9.36-6.408-14.58-0.432-3.276 0.973-11.629 1.513-13.032 0.54-0.971 3.311-0.467 2.916 0.9z"/>\r
+   <path fill="#2a2a2a" d="m87.792 222.523c-0.579 4.86-0.936 16.531 3.586 23.356 4.521 6.793 1.926 6.675-1.516 2.765-3.539-3.845-5.403-9.015-6.148-14.224-0.476-3.293 0.831-11.207 1.332-12.614 0.493-0.925 3.11-0.63 2.746 0.717z"/>\r
+   <path fill="#2d2d2d" d="m87.702 222.872c-0.547 5.112-0.864 16.215 3.644 23.025 4.507 6.783 1.8 6.293-1.483 2.506-3.369-3.73-5.083-8.669-5.89-13.867-0.519-3.312 0.691-10.785 1.152-12.197 0.446-0.878 2.908-0.792 2.577 0.533z"/>\r
+   <path fill="#313131" d="m87.612 223.221c-0.515 5.363-0.792 15.898 3.701 22.693 4.492 6.772 1.674 5.912-1.451 2.248-3.2-3.615-4.763-8.324-5.63-13.512-0.563-3.33 0.551-10.363 0.972-11.779 0.399-0.831 2.707-0.953 2.408 0.35z"/>\r
+   <path fill="#353535" d="m87.522 223.57c-0.482 5.616-0.72 15.581 3.758 22.363 4.479 6.761 1.549 5.53-1.418 1.987-3.031-3.5-4.442-7.978-5.371-13.154-0.604-3.348 0.41-9.943 0.792-11.361 0.352-0.785 2.506-1.115 2.239 0.165z"/>\r
+   <path fill="#393939" d="m87.432 223.918c-0.45 5.869-0.648 15.265 3.815 22.033 4.464 6.75 1.422 5.147-1.386 1.728-2.862-3.384-4.122-7.632-5.112-12.798-0.647-3.366 0.271-9.522 0.612-10.944 0.307-0.737 2.305-1.278 2.071-0.019z"/>\r
+   <path fill="#3d3d3d" d="m87.343 224.269c-0.418 6.12-0.576 14.946 3.873 21.7 4.45 6.74 1.296 4.767-1.354 1.469-2.692-3.27-3.802-7.286-4.853-12.441-0.691-3.383 0.129-9.101 0.432-10.527 0.26-0.691 2.103-1.44 1.902-0.201z"/>\r
+   <path fill="#414141" d="m87.252 224.618c-0.385 6.373-0.504 14.631 3.932 21.369 4.436 6.729 1.17 4.385-1.321 1.211-2.523-3.154-3.481-6.941-4.594-12.086-0.734-3.402-0.011-8.68 0.252-10.109 0.212-0.644 1.901-1.602 1.731-0.385z"/>\r
+   <path fill="#444" d="m87.162 224.967c-0.353 6.623-0.432 14.314 3.989 21.037 4.421 6.719 1.044 4.004-1.289 0.951-2.354-3.039-3.161-6.595-4.334-11.729-0.778-3.42-0.151-8.258 0.071-9.691 0.166-0.597 1.7-1.763 1.563-0.568z"/>\r
+   <path fill="#484848" d="m87.072 225.316c-0.32 6.876-0.36 13.997 4.047 20.707 4.406 6.707 0.918 3.622-1.257 0.692-2.186-2.924-2.841-6.25-4.075-11.373-0.82-3.438-0.292-7.838-0.108-9.273 0.119-0.551 1.498-1.926 1.393-0.753z"/>\r
+   <path fill="#4c4c4c" d="m86.982 225.665c-0.288 7.129-0.288 13.68 4.104 20.377 4.393 6.695 0.792 3.24-1.224 0.432s-2.52-5.904-3.816-11.016c-0.863-3.457-0.432-7.416-0.287-8.856 0.071-0.505 1.295-2.089 1.223-0.937z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path d="m88.782 218.681c4.32-9.433 6.696-19.584 12.888-29.448 6.12-9.792 3.672-13.608-0.863-8.64-4.536 4.968-9.504 15.48-9.504 15.48s-5.832 9.216-7.128 19.872c-0.217 1.8 3.887 4.248 4.607 2.736z"/>\r
+   <path fill="#020202" d="m88.968 218.071c4.279-9.5 6.615-19.246 12.586-28.802 5.901-9.488 3.608-13.279-0.764-8.472-4.403 4.847-9.302 15.236-9.368 15.381 0 0-5.637 8.993-6.877 19.239-0.209 1.771 3.72 4.145 4.423 2.654z"/>\r
+   <path fill="#050505" d="m89.152 217.459c4.239-9.566 6.535-18.908 12.284-28.155 5.683-9.183 3.547-12.95-0.663-8.303-4.27 4.725-9.099 14.991-9.231 15.282 0 0-5.442 8.766-6.627 18.605-0.201 1.741 3.554 4.042 4.237 2.571z"/>\r
+   <path fill="#070707" d="m89.338 216.849c4.199-9.634 6.454-18.572 11.981-27.51 5.465-8.878 3.484-12.621-0.563-8.135-4.137 4.605-8.896 14.748-9.096 15.184 0 0-5.247 8.542-6.376 17.972-0.192 1.713 3.387 3.937 4.054 2.489z"/>\r
+   <path fill="#0a0a0a" d="m89.522 216.239c4.159-9.701 6.375-18.234 11.68-26.865 5.247-8.573 3.422-12.292-0.461-7.966-4.005 4.483-8.693 14.503-8.96 15.085 0 0-5.053 8.317-6.126 17.337-0.186 1.684 3.219 3.837 3.867 2.409z"/>\r
+   <path fill="#0c0c0c" d="m89.708 215.627c4.118-9.769 6.294-17.897 11.376-26.218 5.029-8.268 3.36-11.962-0.359-7.797-3.871 4.361-8.49 14.259-8.823 14.985 0 0-4.858 8.093-5.876 16.706-0.179 1.653 3.051 3.731 3.682 2.324z"/>\r
+   <path fill="#0f0f0f" d="m89.893 215.017c4.077-9.836 6.213-17.56 11.073-25.573 4.812-7.963 3.298-11.633-0.259-7.629-3.738 4.241-8.288 14.015-8.688 14.887 0 0-4.663 7.868-5.625 16.072-0.169 1.624 2.886 3.628 3.499 2.243z"/>\r
+   <path fill="#111" d="m90.078 214.407c4.037-9.904 6.133-17.224 10.772-24.928 4.593-7.658 3.234-11.304-0.159-7.46-3.605 4.119-8.085 13.771-8.551 14.788 0 0-4.47 7.645-5.375 15.439-0.162 1.594 2.718 3.524 3.313 2.161z"/>\r
+   <path fill="#141414" d="m90.263 213.795c3.997-9.971 6.052-16.885 10.47-24.281 4.374-7.353 3.172-10.975-0.059-7.291-3.472 3.998-7.882 13.526-8.415 14.689 0 0-4.273 7.418-5.124 14.805-0.154 1.565 2.551 3.421 3.128 2.078z"/>\r
+   <path fill="#161616" d="m90.449 213.184c3.956-10.037 5.972-16.548 10.167-23.635 4.156-7.048 3.11-10.645 0.043-7.123-3.34 3.877-7.68 13.283-8.279 14.591 0 0-4.08 7.194-4.874 14.172-0.147 1.535 2.383 3.317 2.943 1.995z"/>\r
+   <path fill="#191919" d="m90.634 212.573c3.916-10.105 5.892-16.209 9.865-22.989 3.938-6.743 3.047-10.316 0.144-6.954-3.207 3.755-7.477 13.038-8.143 14.491 0 0-3.886 6.969-4.624 13.54-0.139 1.506 2.217 3.213 2.758 1.912z"/>\r
+   <path fill="#1c1c1c" d="m90.819 211.963c3.875-10.174 5.811-15.874 9.563-22.344 3.721-6.438 2.984-9.987 0.244-6.785-3.073 3.634-7.274 12.793-8.007 14.392 0 0-3.69 6.745-4.373 12.905-0.131 1.477 2.049 3.112 2.573 1.832z"/>\r
+   <path fill="#1e1e1e" d="m91.004 211.352c3.836-10.24 5.73-15.536 9.262-21.698 3.501-6.133 2.922-9.658 0.344-6.617-2.94 3.513-7.071 12.55-7.87 14.293 0 0-3.496 6.521-4.123 12.272-0.124 1.447 1.881 3.009 2.387 1.75z"/>\r
+   <path fill="#212121" d="m91.189 210.741c3.795-10.307 5.649-15.198 8.958-21.052 3.284-5.828 2.859-9.328 0.445-6.448-2.808 3.392-6.868 12.305-7.734 14.195 0 0-3.301 6.295-3.872 11.639-0.115 1.418 1.715 2.904 2.203 1.666z"/>\r
+   <path fill="#232323" d="m91.375 210.129c3.754-10.373 5.569-14.86 8.655-20.405 3.066-5.523 2.797-8.999 0.547-6.279-2.675 3.27-6.666 12.061-7.599 14.097 0 0-3.106 6.07-3.622 11.004-0.107 1.388 1.548 2.801 2.019 1.583z"/>\r
+   <path fill="#262626" d="m91.559 209.519c3.714-10.44 5.489-14.523 8.354-19.76 2.847-5.218 2.735-8.67 0.647-6.111-2.542 3.149-6.463 11.817-7.462 13.997 0 0-2.912 5.848-3.372 10.373-0.099 1.357 1.381 2.697 1.833 1.501z"/>\r
+   <path fill="#282828" d="m91.745 208.909c3.674-10.509 5.408-14.187 8.051-19.115 2.629-4.913 2.672-8.341 0.748-5.942-2.409 3.028-6.261 11.573-7.326 13.898 0 0-2.717 5.621-3.121 9.738-0.092 1.33 1.213 2.595 1.648 1.421z"/>\r
+   <path fill="#2b2b2b" d="m91.929 208.297c3.634-10.575 5.328-13.848 7.75-18.468 2.41-4.608 2.609-8.011 0.848-5.773-2.275 2.906-6.058 11.328-7.189 13.799 0 0-2.522 5.397-2.871 9.106-0.084 1.299 1.046 2.491 1.462 1.336z"/>\r
+   <path fill="#2d2d2d" d="m92.115 207.687c3.593-10.643 5.247-13.512 7.447-17.823 2.191-4.303 2.547-7.682 0.949-5.605-2.144 2.786-5.855 11.085-7.055 13.701 0 0-2.327 5.172-2.62 8.473-0.076 1.269 0.881 2.386 1.279 1.254z"/>\r
+   <path fill="#303030" d="m92.301 207.077c3.552-10.71 5.167-13.175 7.145-17.178 1.974-3.998 2.484-7.353 1.05-5.436-2.011 2.664-5.652 10.84-6.918 13.602 0 0-2.133 4.947-2.37 7.839-0.07 1.24 0.712 2.283 1.093 1.173z"/>\r
+   <path fill="#333" d="m92.485 206.465c3.513-10.778 5.087-12.837 6.843-16.531s2.422-7.024 1.15-5.268c-1.877 2.543-5.449 10.596-6.781 13.502 0 0-1.938 4.724-2.12 7.207-0.061 1.211 0.545 2.18 0.908 1.09z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path d="m273.03 225.592c2.088-5.903 1.872-20.951-3.456-30.671-1.872-3.528-3.672-7.632-4.752-7.848-1.152-0.216-3.24 2.088-3.024 2.448 0.288 0.576 10.009 14.256 7.992 32.832-0.144 1.513 2.736 4.608 3.24 3.239z"/>\r
+   <path fill="#030303" d="m272.936 224.831c2.025-5.719 1.753-20.379-3.344-29.663-1.815-3.407-3.535-7.374-4.595-7.59-1.122-0.214-3.125 2.022-2.925 2.367 0.258 0.562 9.605 13.778 7.729 31.753-0.129 1.487 2.647 4.456 3.135 3.133z"/>\r
+   <path fill="#070707" d="m272.841 224.068c1.967-5.532 1.637-19.808-3.228-28.654-1.762-3.286-3.401-7.115-4.44-7.332-1.091-0.211-3.009 1.956-2.824 2.287 0.227 0.548 9.201 13.299 7.466 30.672-0.118 1.463 2.555 4.305 3.026 3.027z"/>\r
+   <path fill="#0b0b0b" d="m272.747 223.305c1.904-5.348 1.518-19.235-3.115-27.645-1.706-3.165-3.265-6.856-4.282-7.073-1.062-0.21-2.896 1.889-2.727 2.206 0.197 0.534 8.799 12.821 7.203 29.592-0.103 1.44 2.466 4.153 2.921 2.92z"/>\r
+   <path fill="#0f0f0f" d="m272.652 222.542c1.843-5.162 1.398-18.662-3.001-26.635-1.65-3.044-3.13-6.598-4.127-6.815-1.03-0.207-2.779 1.823-2.626 2.126 0.167 0.52 8.396 12.341 6.94 28.511-0.09 1.416 2.376 4.001 2.814 2.813z"/>\r
+   <path fill="#131313" d="m272.558 221.779c1.78-4.976 1.279-18.09-2.889-25.626-1.595-2.923-2.994-6.34-3.97-6.557-1-0.205-2.664 1.756-2.527 2.045 0.137 0.506 7.993 11.861 6.679 27.432-0.078 1.392 2.285 3.849 2.707 2.706z"/>\r
+   <path fill="#161616" d="m272.463 221.016c1.721-4.79 1.163-17.518-2.773-24.617-1.539-2.802-2.858-6.081-3.814-6.299-0.969-0.203-2.548 1.691-2.427 1.965 0.106 0.492 7.59 11.383 6.415 26.352-0.065 1.369 2.194 3.697 2.599 2.599z"/>\r
+   <path fill="#1a1a1a" d="m272.369 220.254c1.659-4.605 1.044-16.946-2.66-23.609-1.483-2.681-2.723-5.822-3.658-6.04-0.938-0.201-2.434 1.624-2.326 1.884 0.074 0.478 7.185 10.904 6.15 25.271-0.051 1.344 2.106 3.546 2.494 2.494z"/>\r
+   <path fill="#1e1e1e" d="m272.274 219.491c1.598-4.42 0.926-16.373-2.546-22.6-1.43-2.56-2.587-5.564-3.501-5.782-0.908-0.198-2.319 1.558-2.229 1.804 0.044 0.464 6.782 10.425 5.889 24.19-0.037 1.321 2.016 3.396 2.387 2.388z"/>\r
+   <path fill="#222" d="m272.18 218.728c1.535-4.233 0.807-15.802-2.434-21.59-1.373-2.439-2.452-5.306-3.345-5.524-0.877-0.197-2.203 1.491-2.128 1.723 0.014 0.45 6.379 9.947 5.625 23.111-0.023 1.297 1.927 3.242 2.282 2.28z"/>\r
+   <path fill="#262626" d="m272.085 217.965c1.476-4.049 0.69-15.229-2.318-20.582-1.317-2.317-2.316-5.046-3.188-5.265-0.847-0.194-2.088 1.425-2.029 1.642-0.016 0.436 5.977 9.468 5.362 22.032-0.011 1.272 1.835 3.089 2.173 2.173z"/>\r
+   <path fill="#2a2a2a" d="m271.991 217.202c1.413-3.861 0.571-14.656-2.206-19.572-1.262-2.196-2.18-4.788-3.032-5.007-0.815-0.191-1.973 1.36-1.929 1.562-0.047 0.422 5.573 8.988 5.099 20.951 0.003 1.247 1.746 2.939 2.068 2.066z"/>\r
+   <path fill="#2d2d2d" d="m271.896 216.439c1.353-3.677 0.453-14.084-2.091-18.563-1.207-2.076-2.045-4.529-2.876-4.749-0.786-0.19-1.858 1.293-1.83 1.481-0.077 0.408 5.17 8.51 4.836 19.87 0.017 1.226 1.656 2.789 1.961 1.961z"/>\r
+   <path fill="#313131" d="m271.802 215.676c1.29-3.491 0.334-13.512-1.979-17.553-1.151-1.956-1.909-4.272-2.72-4.493-0.755-0.187-1.742 1.227-1.73 1.401-0.106 0.394 4.768 8.031 4.573 18.791 0.031 1.202 1.567 2.637 1.856 1.854z"/>\r
+   <path fill="#353535" d="m271.707 214.915c1.23-3.307 0.217-12.94-1.864-16.545-1.096-1.834-1.773-4.014-2.563-4.234-0.725-0.185-1.627 1.16-1.631 1.32-0.138 0.38 4.364 7.552 4.311 17.71 0.042 1.176 1.475 2.485 1.747 1.749z"/>\r
+   <path fill="#393939" d="m271.613 214.151c1.168-3.119 0.098-12.367-1.751-15.535-1.04-1.714-1.638-3.755-2.407-3.976-0.693-0.183-1.512 1.094-1.531 1.24-0.168 0.366 3.962 7.074 4.049 16.63 0.055 1.153 1.384 2.332 1.64 1.641z"/>\r
+   <path fill="#3d3d3d" d="m271.518 213.388c1.106-2.935-0.021-11.796-1.638-14.527-0.983-1.592-1.502-3.496-2.25-3.716-0.664-0.181-1.396 1.028-1.432 1.159-0.198 0.352 3.558 6.594 3.785 15.549 0.07 1.13 1.296 2.183 1.535 1.535z"/>\r
+   <path fill="#414141" d="m271.424 212.625c1.047-2.75-0.139-11.223-1.522-13.518-0.93-1.471-1.367-3.238-2.094-3.459-0.635-0.178-1.282 0.962-1.333 1.079-0.229 0.338 3.153 6.114 3.521 14.47 0.083 1.106 1.205 2.03 1.428 1.428z"/>\r
+   <path fill="#444" d="m271.329 211.862c0.985-2.563-0.256-10.65-1.409-12.508-0.874-1.35-1.23-2.979-1.938-3.2-0.604-0.177-1.166 0.895-1.233 0.998-0.259 0.324 2.751 5.636 3.259 13.39 0.096 1.082 1.115 1.876 1.321 1.32z"/>\r
+   <path fill="#484848" d="m271.235 211.099c0.923-2.377-0.375-10.077-1.296-11.499-0.818-1.229-1.097-2.72-1.781-2.942-0.573-0.174-1.052 0.829-1.134 0.918-0.289 0.309 2.348 5.156 2.996 12.309 0.11 1.058 1.025 1.726 1.215 1.214z"/>\r
+   <path fill="#4c4c4c" d="m271.14 210.336c0.861-2.192-0.493-9.506-1.183-10.49-0.763-1.107-0.96-2.463-1.625-2.684-0.542-0.172-0.936 0.762-1.034 0.836-0.319 0.297 1.945 4.68 2.733 11.229 0.124 1.035 0.936 1.576 1.109 1.109z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path d="m264.822 187.073c-10.224-13.968-23.472-18.504-22.104-14.112 0 0 10.152 5.76 19.08 16.56 1.728 2.088 4.608-0.288 3.024-2.448z"/>\r
+   <path fill="#030303" d="m264.372 186.687c-9.924-13.495-22.894-17.912-21.539-13.7 0.018 0.012 9.901 5.614 18.609 16.071 1.678 2.016 4.467-0.285 2.93-2.371z"/>\r
+   <path fill="#070707" d="m263.922 186.3c-9.624-13.022-22.316-17.319-20.975-13.287 0.036 0.023 9.652 5.467 18.139 15.582 1.628 1.943 4.325-0.283 2.836-2.295z"/>\r
+   <path fill="#0b0b0b" d="m263.472 185.913c-9.324-12.549-21.739-16.726-20.41-12.874 0.053 0.034 9.4 5.32 17.668 15.093 1.578 1.871 4.183-0.28 2.742-2.219z"/>\r
+   <path fill="#0f0f0f" d="m263.022 185.527c-9.024-12.077-21.162-16.134-19.847-12.462 0.071 0.045 9.151 5.174 17.198 14.604 1.529 1.798 4.043-0.278 2.649-2.142z"/>\r
+   <path fill="#131313" d="m262.571 185.14c-8.723-11.603-20.583-15.541-19.28-12.049 0.088 0.056 8.901 5.027 16.728 14.114 1.477 1.726 3.899-0.275 2.552-2.065z"/>\r
+   <path fill="#161616" d="m262.121 184.753c-8.423-11.13-20.006-14.948-18.716-11.636 0.106 0.067 8.651 4.88 16.257 13.625 1.428 1.654 3.759-0.272 2.459-1.989z"/>\r
+   <path fill="#1a1a1a" d="m261.671 184.367c-8.124-10.658-19.428-14.356-18.15-11.224 0.124 0.078 8.399 4.734 15.785 13.136 1.378 1.581 3.617-0.27 2.365-1.912z"/>\r
+   <path fill="#1e1e1e" d="m261.221 183.98c-7.824-10.185-18.851-13.763-17.588-10.811 0.143 0.089 8.151 4.587 15.315 12.647 1.33 1.508 3.477-0.267 2.273-1.836z"/>\r
+   <path fill="#222" d="m260.771 183.593c-7.524-9.711-18.273-13.17-17.022-10.398 0.159 0.1 7.9 4.44 14.844 12.158 1.279 1.436 3.335-0.265 2.178-1.76z"/>\r
+   <path fill="#262626" d="m260.321 183.206c-7.224-9.238-17.695-12.578-16.458-9.985 0.177 0.111 7.65 4.293 14.374 11.668 1.229 1.364 3.193-0.262 2.084-1.683z"/>\r
+   <path fill="#2a2a2a" d="m259.871 182.82c-6.924-8.766-17.118-11.986-15.893-9.573 0.193 0.122 7.398 4.147 13.902 11.179 1.18 1.291 3.053-0.259 1.991-1.606z"/>\r
+   <path fill="#2d2d2d" d="m259.42 182.433c-6.623-8.293-16.539-11.393-15.328-9.16 0.214 0.133 7.15 4 13.434 10.69 1.128 1.219 2.909-0.257 1.894-1.53z"/>\r
+   <path fill="#313131" d="m258.97 182.046c-6.323-7.819-15.963-10.8-14.764-8.747 0.23 0.144 6.899 3.853 12.962 10.201 1.08 1.146 2.769-0.254 1.802-1.454z"/>\r
+   <path fill="#353535" d="m258.52 181.66c-6.023-7.347-15.384-10.208-14.199-8.335 0.248 0.155 6.649 3.707 12.492 9.712 1.028 1.073 2.627-0.252 1.707-1.377z"/>\r
+   <path fill="#393939" d="m258.07 181.273c-5.723-6.874-14.807-9.615-13.634-7.922 0.265 0.166 6.398 3.56 12.021 9.222 0.978 1.002 2.485-0.249 1.613-1.3z"/>\r
+   <path fill="#3d3d3d" d="m257.62 180.886c-5.423-6.401-14.229-9.021-13.07-7.509 0.283 0.177 6.149 3.413 11.552 8.733 0.927 0.929 2.343-0.246 1.518-1.224z"/>\r
+   <path fill="#414141" d="m257.17 180.5c-5.124-5.928-13.65-8.43-12.505-7.097 0.301 0.188 5.898 3.267 11.079 8.244 0.879 0.856 2.203-0.244 1.426-1.147z"/>\r
+   <path fill="#444" d="m256.719 180.113c-4.823-5.455-13.073-7.837-11.94-6.684 0.319 0.199 5.649 3.12 10.609 7.755 0.829 0.784 2.061-0.241 1.331-1.071z"/>\r
+   <path fill="#484848" d="m256.269 179.726c-4.523-4.982-12.495-7.244-11.375-6.271 0.336 0.21 5.397 2.973 10.138 7.266 0.779 0.711 1.92-0.239 1.237-0.995z"/>\r
+   <path fill="#4c4c4c" d="m255.819 179.339c-4.223-4.509-11.918-6.652-10.812-5.859 0.354 0.222 5.148 2.827 9.668 6.777 0.73 0.639 1.779-0.236 1.144-0.918z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path d="m273.03 225.592c0.144 6.265-5.76 22.248-7.992 21.673-2.52-0.576 0.504-5.257 2.809-13.177 0.936-3.312 1.655-11.447 1.943-11.735 0.936-0.936 3.24 1.728 3.24 3.239z"/>\r
+   <path fill="#050505" d="m272.846 226.116c0.103 6.082-5.638 21.594-7.797 21.018-2.422-0.567 0.548-5.146 2.814-12.928 0.899-3.178 1.595-10.947 1.887-11.25 0.914-0.927 3.147 1.527 3.096 3.16z"/>\r
+   <path fill="#0a0a0a" d="m272.662 226.637c0.063 5.902-5.514 20.939-7.601 20.363-2.323-0.558 0.592-5.035 2.82-12.681 0.861-3.041 1.534-10.446 1.83-10.761 0.89-0.917 3.054 1.327 2.951 3.079z"/>\r
+   <path fill="#0f0f0f" d="m272.478 227.159c0.021 5.721-5.392 20.284-7.405 19.711-2.224-0.55 0.635-4.927 2.827-12.434 0.824-2.907 1.473-9.945 1.773-10.273 0.866-0.911 2.959 1.125 2.805 2.996z"/>\r
+   <path fill="#141414" d="m272.294 227.68c-0.02 5.539-5.268 19.63-7.21 19.057-2.125-0.541 0.68-4.816 2.835-12.186 0.786-2.771 1.412-9.445 1.717-9.786 0.84-0.901 2.863 0.924 2.658 2.915z"/>\r
+   <path fill="#191919" d="m272.11 228.202c-0.063 5.359-5.146 18.977-7.014 18.403-2.027-0.532 0.722-4.706 2.841-11.938 0.748-2.637 1.351-8.944 1.659-9.299 0.818-0.893 2.77 0.722 2.514 2.834z"/>\r
+   <path fill="#1e1e1e" d="m271.926 228.723c-0.103 5.179-5.021 18.322-6.818 17.75-1.928-0.523 0.766-4.596 2.848-11.691 0.711-2.5 1.29-8.443 1.603-8.811 0.792-0.885 2.674 0.522 2.367 2.752z"/>\r
+   <path fill="#232323" d="m271.742 229.245c-0.144 4.998-4.9 17.668-6.623 17.096-1.83-0.514 0.811-4.485 2.854-11.442 0.673-2.366 1.229-7.944 1.545-8.325 0.771-0.876 2.583 0.32 2.224 2.671z"/>\r
+   <path fill="#282828" d="m271.558 229.766c-0.186 4.816-4.777 17.013-6.428 16.443-1.731-0.506 0.854-4.377 2.86-11.196 0.636-2.231 1.168-7.443 1.488-7.837 0.749-0.866 2.49 0.119 2.08 2.59z"/>\r
+   <path fill="#2d2d2d" d="m271.374 230.288c-0.226 4.637-4.653 16.359-6.231 15.789-1.632-0.496 0.896-4.266 2.866-10.947 0.6-2.098 1.107-6.943 1.433-7.351 0.722-0.859 2.393-0.081 1.932 2.509z"/>\r
+   <path fill="#333" d="m271.19 230.809c-0.268 4.456-4.531 15.705-6.036 15.136-1.534-0.489 0.94-4.155 2.873-10.7 0.561-1.961 1.046-6.443 1.375-6.863 0.7-0.848 2.3-0.282 1.788 2.427z"/>\r
+   <path fill="#383838" d="m271.006 231.331c-0.308 4.275-4.407 15.051-5.841 14.482-1.435-0.48 0.984-4.046 2.88-10.453 0.524-1.826 0.985-5.941 1.318-6.375 0.676-0.84 2.206-0.483 1.643 2.346z"/>\r
+   <path fill="#3d3d3d" d="m270.822 231.853c-0.35 4.093-4.285 14.396-5.645 13.828-1.338-0.472 1.027-3.937 2.886-10.206 0.485-1.691 0.924-5.441 1.261-5.887 0.653-0.832 2.113-0.684 1.498 2.265z"/>\r
+   <path fill="#424242" d="m270.638 232.374c-0.392 3.914-4.162 13.742-5.45 13.176-1.238-0.463 1.072-3.826 2.893-9.959 0.449-1.555 0.863-4.94 1.204-5.399 0.629-0.824 2.019-0.886 1.353 2.182z"/>\r
+   <path fill="#474747" d="m270.454 232.896c-0.432 3.731-4.039 13.087-5.254 12.521-1.14-0.453 1.115-3.715 2.9-9.711 0.411-1.42 0.802-4.439 1.146-4.912 0.606-0.815 1.925-1.086 1.208 2.102z"/>\r
+   <path fill="#4c4c4c" d="m270.27 233.417c-0.474 3.553-3.916 12.434-5.06 11.869-1.041-0.445 1.159-3.606 2.907-9.463 0.373-1.287 0.741-3.941 1.089-4.427 0.583-0.806 1.832-1.287 1.064 2.021z"/>\r
+   <path fill="#515151" d="m270.086 233.939c-0.514 3.37-3.793 11.778-4.862 11.214-0.942-0.436 1.201-3.496 2.913-9.216 0.336-1.151 0.68-3.438 1.032-3.938 0.558-0.797 1.736-1.489 0.917 1.94z"/>\r
+   <path fill="#565656" d="m269.902 234.461c-0.555 3.188-3.671 11.123-4.667 10.56-0.844-0.429 1.246-3.386 2.919-8.968 0.298-1.016 0.619-2.939 0.976-3.451 0.535-0.788 1.643-1.689 0.772 1.859z"/>\r
+   <path fill="#5b5b5b" d="m269.718 234.982c-0.597 3.009-3.548 10.47-4.472 9.907-0.745-0.42 1.29-3.275 2.926-8.721 0.262-0.881 0.559-2.438 0.919-2.963 0.511-0.781 1.549-1.89 0.627 1.777z"/>\r
+   <path fill="#606060" d="m269.534 235.504c-0.638 2.828-3.425 9.814-4.276 9.252-0.646-0.409 1.333-3.166 2.933-8.473 0.224-0.746 0.497-1.938 0.862-2.476 0.487-0.769 1.454-2.09 0.481 1.697z"/>\r
+   <path fill="#666" d="m269.35 236.025c-0.68 2.647-3.303 9.161-4.081 8.599-0.548-0.4 1.377-3.056 2.938-8.225 0.187-0.611 0.437-1.438 0.806-1.988 0.464-0.763 1.361-2.293 0.337 1.614z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path d="m251.07 187.865c-1.537 1.622-2.903 9.991 0.938 12.893 3.844 2.818 10.59-2.391 10.59-5.379-0.086-6.746-9.991-9.222-11.528-7.514z"/>\r
+   <path fill="#010101" d="m251.207 188.006c-1.559 1.611-2.876 9.823 0.857 12.667 3.731 2.764 10.349-2.273 10.384-5.279-0.047-6.576-9.681-9.083-11.241-7.388z"/>\r
+   <path fill="#030303" d="m251.344 188.146c-1.582 1.601-2.85 9.653 0.774 12.438 3.62 2.709 10.109-2.154 10.178-5.177-0.007-6.404-9.37-8.943-10.952-7.261z"/>\r
+   <path fill="#050505" d="m251.481 188.287c-1.604 1.589-2.823 9.484 0.691 12.211 3.511 2.653 9.869-2.037 9.975-5.078 0.031-6.232-9.061-8.802-10.666-7.133z"/>\r
+   <path fill="#070707" d="m251.617 188.427c-1.626 1.579-2.795 9.316 0.611 11.984 3.397 2.6 9.629-1.918 9.768-4.976 0.071-6.062-8.751-8.664-10.379-7.008z"/>\r
+   <path fill="#090909" d="m251.754 188.567c-1.648 1.568-2.768 9.146 0.529 11.758 3.287 2.543 9.389-1.802 9.563-4.875 0.109-5.892-8.441-8.525-10.092-6.883z"/>\r
+   <path fill="#0b0b0b" d="m251.891 188.708c-1.671 1.557-2.741 8.978 0.445 11.529 3.177 2.489 9.15-1.683 9.358-4.774 0.15-5.72-8.13-8.385-9.803-6.755z"/>\r
+   <path fill="#0d0d0d" d="m252.028 188.848c-1.694 1.546-2.715 8.809 0.364 11.302 3.064 2.435 8.908-1.565 9.152-4.673 0.189-5.549-7.82-8.245-9.516-6.629z"/>\r
+   <path fill="#0f0f0f" d="m252.165 188.989c-1.716 1.535-2.688 8.64 0.282 11.074 2.953 2.38 8.669-1.447 8.948-4.572 0.226-5.378-7.512-8.106-9.23-6.502z"/>\r
+   <path fill="#111" d="m252.301 189.129c-1.737 1.524-2.659 8.471 0.2 10.847 2.844 2.325 8.431-1.33 8.743-4.471 0.266-5.207-7.201-7.966-8.943-6.376z"/>\r
+   <path fill="#131313" d="m252.438 189.269c-1.76 1.514-2.633 8.304 0.118 10.619 2.73 2.271 8.189-1.212 8.538-4.369 0.305-5.036-6.892-7.827-8.656-6.25z"/>\r
+   <path fill="#151515" d="m252.575 189.41c-1.783 1.503-2.606 8.133 0.036 10.391 2.62 2.216 7.949-1.094 8.332-4.268 0.344-4.865-6.581-7.687-8.368-6.123z"/>\r
+   <path fill="#161616" d="m252.712 189.55c-1.805 1.492-2.58 7.965-0.046 10.164 2.508 2.162 7.709-0.975 8.127-4.167 0.383-4.694-6.272-7.548-8.081-5.997z"/>\r
+   <path fill="#181818" d="m252.849 189.691c-1.828 1.481-2.554 7.796-0.129 9.937 2.397 2.105 7.47-0.857 7.922-4.066 0.423-4.524-5.961-7.409-7.793-5.871z"/>\r
+   <path fill="#1a1a1a" d="m252.985 189.831c-1.85 1.47-2.525 7.626-0.21 9.708 2.286 2.053 7.229-0.74 7.717-3.964 0.461-4.352-5.652-7.269-7.507-5.744z"/>\r
+   <path fill="#1c1c1c" d="m253.122 189.971c-1.872 1.46-2.499 7.459-0.292 9.482 2.175 1.996 6.989-0.623 7.511-3.865 0.501-4.18-5.341-7.128-7.219-5.617z"/>\r
+   <path fill="#1e1e1e" d="m253.259 190.112c-1.895 1.448-2.472 7.289-0.375 9.254 2.064 1.942 6.75-0.504 7.308-3.763 0.539-4.01-5.033-6.99-6.933-5.491z"/>\r
+   <path fill="#202020" d="m253.396 190.252c-1.917 1.438-2.445 7.122-0.457 9.027 1.953 1.888 6.51-0.386 7.102-3.662 0.578-3.839-4.722-6.85-6.645-5.365z"/>\r
+   <path fill="#222" d="m253.533 190.393c-1.939 1.426-2.418 6.951-0.539 8.799 1.841 1.832 6.271-0.268 6.896-3.561 0.618-3.668-4.412-6.711-6.357-5.238z"/>\r
+   <path fill="#242424" d="m253.669 190.533c-1.961 1.416-2.391 6.783-0.621 8.572 1.731 1.776 6.03-0.149 6.692-3.46 0.657-3.497-4.102-6.571-6.071-5.112z"/>\r
+   <path fill="#262626" d="m253.806 190.673c-1.984 1.405-2.364 6.615-0.703 8.344 1.619 1.724 5.79-0.032 6.485-3.358 0.697-3.326-3.791-6.432-5.782-4.986z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path d="m250.71 256.698c1.513 1.512 2.809-2.232 4.32-3.457 1.512-1.224 3.96-3.888 8.856-3.888s4.535-0.144 4.319-2.017c-0.144-1.799-1.584-1.655-5.903-1.008-4.32 0.576-7.2 2.809-8.929 4.824-1.654 1.945-3.527 4.681-2.663 5.546z"/>\r
+   <path fill="#050505" d="m251.043 256.331c1.459 1.449 2.703-2.121 4.205-3.308 1.501-1.187 3.931-3.731 8.64-3.731 4.71-0.002 4.415-0.129 4.209-1.94-0.139-1.743-1.543-1.593-5.749-0.979-4.207 0.543-7.029 2.703-8.712 4.648-1.616 1.877-3.438 4.474-2.593 5.31z"/>\r
+   <path fill="#0a0a0a" d="m251.376 255.961c1.406 1.389 2.6-2.008 4.089-3.156 1.491-1.148 3.901-3.576 8.425-3.577 4.521-0.001 4.293-0.11 4.096-1.862-0.132-1.688-1.501-1.531-5.594-0.951-4.093 0.51-6.857 2.596-8.495 4.471-1.575 1.812-3.348 4.271-2.521 5.075z"/>\r
+   <path fill="#0f0f0f" d="m251.709 255.594c1.354 1.326 2.494-1.895 3.975-3.008 1.479-1.111 3.871-3.42 8.207-3.422s4.171-0.094 3.984-1.785c-0.126-1.631-1.46-1.467-5.438-0.924-3.979 0.479-6.687 2.492-8.28 4.297-1.533 1.747-3.257 4.066-2.448 4.842z"/>\r
+   <path fill="#141414" d="m252.042 255.226c1.302 1.265 2.39-1.783 3.858-2.856 1.47-1.076 3.842-3.266 7.991-3.268s4.05-0.077 3.873-1.709c-0.119-1.575-1.419-1.404-5.284-0.895-3.865 0.444-6.515 2.385-8.063 4.121-1.49 1.678-3.165 3.861-2.375 4.607z"/>\r
+   <path fill="#191919" d="m252.374 254.859c1.249 1.202 2.285-1.671 3.744-2.708 1.458-1.037 3.813-3.109 7.774-3.111 3.963-0.004 3.929-0.061 3.761-1.633-0.113-1.519-1.377-1.341-5.128-0.867-3.751 0.414-6.344 2.279-7.847 3.945-1.449 1.613-3.075 3.656-2.304 4.374z"/>\r
+   <path fill="#1e1e1e" d="m252.707 254.491c1.196 1.141 2.182-1.559 3.628-2.558 1.448-1 3.783-2.954 7.56-2.957 3.775-0.003 3.806-0.043 3.648-1.556-0.106-1.461-1.336-1.277-4.974-0.838-3.637 0.379-6.172 2.174-7.63 3.77-1.408 1.546-2.984 3.451-2.232 4.139z"/>\r
+   <path fill="#232323" d="m253.04 254.124c1.144 1.078 2.076-1.447 3.514-2.41 1.437-0.961 3.753-2.797 7.342-2.799 3.589-0.004 3.685-0.027 3.537-1.48-0.101-1.405-1.294-1.215-4.818-0.811-3.524 0.349-6.001 2.068-7.415 3.594-1.367 1.48-2.894 3.245-2.16 3.906z"/>\r
+   <path fill="#282828" d="m253.373 253.754c1.09 1.018 1.972-1.334 3.398-2.258 1.426-0.926 3.723-2.642 7.125-2.646 3.402-0.005 3.563-0.011 3.426-1.403-0.095-1.349-1.253-1.15-4.664-0.781-3.409 0.314-5.829 1.961-7.198 3.418-1.325 1.415-2.803 3.041-2.087 3.67z"/>\r
+   <path fill="#2d2d2d" d="m253.706 253.388c1.038 0.954 1.866-1.222 3.282-2.11 1.416-0.887 3.694-2.486 6.909-2.49 3.217-0.004 3.441 0.008 3.313-1.326-0.088-1.293-1.212-1.088-4.508-0.754-3.296 0.283-5.658 1.857-6.981 3.244-1.284 1.345-2.712 2.836-2.015 3.436z"/>\r
+   <path fill="#333" d="m254.039 253.02c0.985 0.893 1.762-1.109 3.167-1.96s3.664-2.33 6.693-2.335c3.029-0.006 3.32 0.023 3.202-1.249-0.082-1.237-1.171-1.024-4.354-0.726-3.182 0.25-5.485 1.75-6.765 3.066-1.243 1.282-2.622 2.634-1.943 3.204z"/>\r
+   <path fill="#383838" d="m254.372 252.652c0.933 0.831 1.657-0.998 3.051-1.81 1.396-0.813 3.636-2.174 6.478-2.18 2.843-0.006 3.199 0.039 3.09-1.172-0.076-1.181-1.129-0.963-4.198-0.697-3.068 0.217-5.314 1.644-6.55 2.891-1.202 1.214-2.531 2.427-1.871 2.968z"/>\r
+   <path fill="#3d3d3d" d="m254.704 252.284c0.88 0.771 1.553-0.885 2.938-1.66 1.383-0.775 3.604-2.019 6.26-2.024 2.656-0.007 3.078 0.058 2.979-1.095-0.069-1.125-1.088-0.899-4.044-0.67-2.954 0.185-5.144 1.539-6.333 2.715-1.16 1.148-2.441 2.222-1.8 2.734z"/>\r
+   <path fill="#424242" d="m255.037 251.917c0.827 0.707 1.448-0.773 2.821-1.51 1.373-0.738 3.576-1.863 6.044-1.871 2.47-0.007 2.956 0.074 2.867-1.018-0.063-1.068-1.046-0.836-3.889-0.641-2.841 0.151-4.973 1.433-6.116 2.539-1.119 1.083-2.35 2.018-1.727 2.501z"/>\r
+   <path fill="#474747" d="m255.37 251.549c0.774 0.645 1.344-0.661 2.706-1.362 1.362-0.7 3.545-1.706 5.828-1.713 2.283-0.009 2.834 0.09 2.754-0.942-0.057-1.012-1.005-0.773-3.733-0.613-2.727 0.119-4.801 1.328-5.899 2.365-1.078 1.013-2.26 1.81-1.656 2.265z"/>\r
+   <path fill="#4c4c4c" d="m255.703 251.181c0.721 0.584 1.239-0.55 2.59-1.212 1.353-0.663 3.517-1.551 5.612-1.559 2.096-0.009 2.713 0.107 2.643-0.865-0.051-0.955-0.964-0.709-3.577-0.584-2.613 0.086-4.631 1.222-5.686 2.188-1.035 0.949-2.168 1.607-1.582 2.032z"/>\r
+   <path fill="#515151" d="m256.036 250.813c0.669 0.521 1.134-0.436 2.476-1.063 1.341-0.625 3.485-1.395 5.395-1.402 1.91-0.01 2.591 0.124 2.531-0.789-0.044-0.898-0.922-0.646-3.423-0.557-2.499 0.055-4.458 1.117-5.469 2.014-0.994 0.882-2.077 1.402-1.51 1.797z"/>\r
+   <path fill="#565656" d="m256.369 250.446c0.616 0.459 1.029-0.324 2.36-0.912 1.33-0.589 3.456-1.24 5.178-1.248 1.723-0.01 2.47 0.141 2.42-0.713-0.039-0.842-0.881-0.582-3.268-0.527-2.387 0.021-4.287 1.01-5.252 1.836-0.953 0.816-1.987 1.199-1.438 1.564z"/>\r
+   <path fill="#5b5b5b" d="m256.701 250.079c0.564 0.397 0.926-0.213 2.245-0.764s3.427-1.084 4.963-1.093c1.536-0.011 2.348 0.157 2.307-0.636-0.031-0.785-0.839-0.52-3.112-0.5-2.271-0.01-4.116 0.906-5.035 1.662-0.913 0.751-1.898 0.993-1.368 1.331z"/>\r
+   <path fill="#606060" d="m257.034 249.709c0.511 0.336 0.821-0.1 2.13-0.612 1.309-0.515 3.397-0.929 4.746-0.938 1.351-0.01 2.227 0.176 2.196-0.558-0.026-0.729-0.799-0.457-2.958-0.472-2.158-0.043-3.945 0.799-4.82 1.486-0.87 0.682-1.805 0.788-1.294 1.094z"/>\r
+   <path fill="#666" d="m257.367 249.342c0.458 0.273 0.716 0.012 2.014-0.465 1.299-0.476 3.368-0.771 4.53-0.781 1.163-0.012 2.105 0.191 2.084-0.482-0.02-0.673-0.757-0.393-2.803-0.443-2.044-0.076-3.773 0.693-4.604 1.311-0.828 0.616-1.714 0.582-1.221 0.86z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path d="m270.222 247.265c0 2.304 4.68 3.096 9.144 3.743 4.392 0.648 7.92 1.513 8.136 6.121 0.216 4.535-0.936 7.775 1.08 7.416 4.32-0.793 5.904-5.473 5.832-7.633 0-2.16-3.168-6.047-8.855-8.207-4.177-1.584-7.2-2.305-10.872-2.449-4.897-0.214-4.465 1.009-4.465 1.009z"/>\r
+   <path fill="#030303" d="m270.348 247.304c0.012 2.239 4.643 2.97 9.049 3.639 4.352 0.675 7.75 1.511 8.11 6.002 0.345 4.415-0.854 7.515 1.136 7.188 4.145-0.739 5.688-5.2 5.607-7.332-0.015-2.151-3.118-5.91-8.733-8.038-4.129-1.563-7.11-2.298-10.74-2.452-4.754-0.217-4.438 0.952-4.429 0.993z"/>\r
+   <path fill="#070707" d="m270.474 247.342c0.023 2.174 4.605 2.845 8.953 3.533 4.312 0.701 7.58 1.508 8.087 5.885 0.471 4.295-0.771 7.252 1.189 6.963 3.97-0.689 5.472-4.93 5.384-7.033-0.028-2.145-3.068-5.773-8.61-7.87-4.082-1.543-7.022-2.291-10.609-2.456-4.612-0.218-4.412 0.896-4.394 0.978z"/>\r
+   <path fill="#0b0b0b" d="m270.6 247.379c0.035 2.109 4.568 2.721 8.857 3.431 4.271 0.728 7.411 1.506 8.063 5.767 0.599 4.174-0.689 6.988 1.245 6.735 3.795-0.638 5.257-4.66 5.159-6.733-0.044-2.137-3.019-5.636-8.488-7.701-4.035-1.522-6.933-2.285-10.478-2.459-4.469-0.219-4.384 0.837-4.358 0.96z"/>\r
+   <path fill="#0f0f0f" d="m270.726 247.418c0.047 2.043 4.531 2.595 8.762 3.324 4.232 0.754 7.242 1.504 8.039 5.649 0.725 4.052-0.608 6.728 1.299 6.509 3.621-0.586 5.041-4.389 4.937-6.436-0.06-2.127-2.971-5.496-8.367-7.53-3.988-1.502-6.842-2.278-10.346-2.464-4.328-0.22-4.359 0.784-4.324 0.948z"/>\r
+   <path fill="#131313" d="m270.852 247.458c0.059 1.978 4.495 2.469 8.667 3.219 4.19 0.781 7.071 1.502 8.014 5.531 0.854 3.932-0.526 6.465 1.354 6.283 3.445-0.535 4.824-4.119 4.712-6.137-0.074-2.119-2.92-5.359-8.245-7.361-3.941-1.482-6.753-2.271-10.213-2.469-4.186-0.221-4.333 0.726-4.289 0.934z"/>\r
+   <path fill="#161616" d="m270.978 247.495c0.07 1.914 4.458 2.344 8.57 3.115 4.151 0.807 6.903 1.5 7.99 5.413 0.98 3.812-0.443 6.202 1.409 6.056 3.271-0.482 4.609-3.848 4.488-5.836-0.088-2.111-2.871-5.222-8.123-7.193-3.894-1.461-6.663-2.264-10.081-2.471-4.043-0.223-4.306 0.67-4.253 0.916z"/>\r
+   <path fill="#1a1a1a" d="m271.104 247.534c0.082 1.848 4.422 2.219 8.476 3.01 4.111 0.832 6.734 1.498 7.965 5.295 1.108 3.69-0.36 5.94 1.464 5.83 3.097-0.433 4.394-3.578 4.265-5.537-0.104-2.104-2.821-5.084-8-7.023-3.848-1.441-6.575-2.26-9.949-2.477-3.905-0.223-4.283 0.613-4.221 0.902z"/>\r
+   <path fill="#1e1e1e" d="m271.23 247.573c0.094 1.781 4.385 2.092 8.381 2.904 4.07 0.859 6.563 1.495 7.939 5.178 1.236 3.568-0.278 5.678 1.52 5.602 2.921-0.381 4.177-3.305 4.04-5.237-0.118-2.097-2.771-4.946-7.878-6.854-3.8-1.42-6.485-2.252-9.817-2.479-3.762-0.226-4.256 0.556-4.185 0.886z"/>\r
+   <path fill="#222" d="m271.356 247.61c0.105 1.717 4.348 1.969 8.285 2.801 4.029 0.885 6.395 1.493 7.916 5.059 1.362 3.449-0.197 5.416 1.573 5.377 2.746-0.329 3.962-3.036 3.816-4.939-0.133-2.087-2.722-4.808-7.756-6.684-3.753-1.4-6.396-2.247-9.686-2.484-3.618-0.227-4.227 0.499-4.148 0.87z"/>\r
+   <path fill="#262626" d="m271.482 247.648c0.118 1.651 4.311 1.843 8.189 2.694 3.99 0.914 6.226 1.492 7.892 4.943 1.491 3.328-0.115 5.152 1.629 5.149 2.571-0.278 3.746-2.765 3.592-4.64-0.146-2.08-2.672-4.672-7.634-6.516-3.705-1.38-6.306-2.24-9.553-2.488-3.478-0.225-4.203 0.446-4.115 0.858z"/>\r
+   <path fill="#2a2a2a" d="m271.608 247.687c0.129 1.587 4.273 1.716 8.094 2.59 3.95 0.938 6.056 1.489 7.867 4.825 1.618 3.206-0.033 4.891 1.684 4.922 2.396-0.227 3.53-2.494 3.368-4.34-0.162-2.072-2.623-4.534-7.512-6.348-3.658-1.358-6.216-2.232-9.421-2.492-3.336-0.226-4.177 0.39-4.08 0.843z"/>\r
+   <path fill="#2d2d2d" d="m271.734 247.725c0.141 1.521 4.237 1.591 7.999 2.484 3.909 0.967 5.886 1.488 7.842 4.707 1.746 3.086 0.05 4.629 1.739 4.697 2.221-0.176 3.313-2.225 3.144-4.041-0.177-2.064-2.572-4.396-7.389-6.178-3.612-1.339-6.128-2.227-9.29-2.497-3.194-0.227-4.151 0.334-4.045 0.828z"/>\r
+   <path fill="#313131" d="m271.86 247.763c0.153 1.456 4.2 1.466 7.903 2.381 3.869 0.991 5.717 1.485 7.817 4.589 1.873 2.965 0.132 4.365 1.794 4.469 2.046-0.123 3.099-1.953 2.92-3.742-0.191-2.055-2.523-4.258-7.267-6.008-3.565-1.318-6.038-2.22-9.158-2.5-3.051-0.229-4.124 0.276-4.009 0.811z"/>\r
+   <path fill="#353535" d="m271.986 247.801c0.165 1.392 4.163 1.341 7.808 2.275 3.829 1.018 5.547 1.483 7.794 4.473 2 2.843 0.213 4.103 1.848 4.242 1.873-0.074 2.883-1.683 2.696-3.443-0.206-2.047-2.474-4.12-7.145-5.838-3.517-1.299-5.948-2.215-9.026-2.506-2.91-0.229-4.099 0.221-3.975 0.797z"/>\r
+   <path fill="#393939" d="m272.112 247.84c0.176 1.326 4.126 1.215 7.712 2.17 3.789 1.045 5.378 1.482 7.771 4.354 2.127 2.723 0.295 3.842 1.901 4.016 1.698-0.021 2.667-1.412 2.474-3.144-0.222-2.038-2.426-3.981-7.023-5.669-3.47-1.277-5.859-2.208-8.894-2.509-2.769-0.231-4.074 0.164-3.941 0.782z"/>\r
+   <path fill="#3d3d3d" d="m272.238 247.877c0.188 1.262 4.089 1.09 7.617 2.066 3.749 1.071 5.208 1.479 7.745 4.236 2.255 2.602 0.377 3.578 1.957 3.789 1.522 0.029 2.451-1.141 2.249-2.844-0.236-2.033-2.375-3.846-6.901-5.5-3.423-1.258-5.769-2.203-8.762-2.514-2.626-0.231-4.046 0.109-3.905 0.767z"/>\r
+   <path fill="#414141" d="m272.364 247.917c0.2 1.195 4.052 0.965 7.522 1.961 3.708 1.098 5.037 1.477 7.72 4.119 2.383 2.479 0.46 3.315 2.012 3.562 1.348 0.081 2.236-0.87 2.025-2.545-0.251-2.022-2.325-3.707-6.778-5.331-3.377-1.237-5.681-2.195-8.631-2.518-2.485-0.233-4.02 0.05-3.87 0.752z"/>\r
+   <path fill="#444" d="m272.49 247.956c0.212 1.129 4.015 0.838 7.426 1.855 3.668 1.124 4.869 1.475 7.696 4 2.51 2.359 0.542 3.055 2.067 3.336 1.173 0.133 2.02-0.6 1.801-2.246-0.266-2.016-2.276-3.568-6.656-5.16-3.329-1.218-5.591-2.189-8.499-2.521-2.343-0.235-3.994-0.007-3.835 0.736z"/>\r
+   <path fill="#484848" d="m272.616 247.993c0.223 1.065 3.979 0.715 7.331 1.752 3.628 1.15 4.699 1.473 7.671 3.883 2.638 2.238 0.624 2.791 2.122 3.108 0.998 0.185 1.804-0.329 1.577-1.946-0.28-2.008-2.227-3.432-6.534-4.992-3.282-1.196-5.501-2.182-8.367-2.525-2.201-0.235-3.968-0.064-3.8 0.72z"/>\r
+   <path fill="#4c4c4c" d="m272.742 248.032c0.235 1 3.941 0.588 7.235 1.645 3.588 1.178 4.529 1.472 7.646 3.766 2.766 2.118 0.706 2.529 2.177 2.883 0.823 0.235 1.589-0.059 1.354-1.648-0.295-1.998-2.177-3.293-6.412-4.822-3.235-1.176-5.412-2.176-8.235-2.529-2.059-0.239-3.942-0.119-3.765 0.705z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path fill="#4c4c4c" d="m287.565 252.854c1.646 1 1.353 2.059 2.412 2.766 0.528 0.352 1.412 0.352 0.882-1-0.706-1.588-1.294-2.472-4.941-3.943-2.353-0.941-1.882 0.059 1.647 2.177z"/>\r
+   <path fill="#505050" d="m287.609 252.868c1.605 0.975 1.32 2.008 2.353 2.696 0.517 0.343 1.377 0.343 0.86-0.976-0.689-1.549-1.262-2.41-4.82-3.846-2.295-0.917-1.836 0.059 1.607 2.126z"/>\r
+   <path fill="#545454" d="m287.652 252.879c1.567 0.951 1.287 1.957 2.294 2.629 0.503 0.334 1.343 0.334 0.839-0.951-0.671-1.51-1.23-2.35-4.699-3.749-2.238-0.893-1.79 0.058 1.566 2.071z"/>\r
+   <path fill="#575757" d="m287.696 252.891c1.526 0.926 1.254 1.908 2.235 2.563 0.489 0.325 1.308 0.325 0.816-0.928-0.653-1.471-1.199-2.289-4.578-3.652-2.179-0.872-1.743 0.055 1.527 2.017z"/>\r
+   <path fill="#5b5b5b" d="m287.74 252.903c1.485 0.902 1.22 1.857 2.175 2.494 0.479 0.318 1.274 0.318 0.796-0.902-0.637-1.432-1.167-2.229-4.457-3.556-2.122-0.849-1.697 0.054 1.486 1.964z"/>\r
+   <path fill="#5f5f5f" d="m287.783 252.915c1.446 0.879 1.188 1.808 2.117 2.426 0.464 0.31 1.239 0.31 0.773-0.877-0.619-1.394-1.136-2.168-4.336-3.459-2.064-0.826-1.65 0.052 1.446 1.91z"/>\r
+   <path fill="#636363" d="m287.827 252.926c1.405 0.854 1.154 1.758 2.057 2.359 0.452 0.301 1.205 0.301 0.754-0.853-0.603-1.354-1.104-2.108-4.216-3.363-2.007-0.801-1.605 0.053 1.405 1.857z"/>\r
+   <path fill="#676767" d="m287.871 252.94c1.364 0.828 1.121 1.705 1.998 2.29 0.438 0.292 1.17 0.292 0.731-0.828-0.585-1.315-1.072-2.047-4.095-3.267-1.948-0.779-1.558 0.05 1.366 1.805z"/>\r
+   <path fill="#6b6b6b" d="m287.914 252.952c1.325 0.805 1.088 1.655 1.94 2.223 0.425 0.283 1.135 0.283 0.709-0.803-0.568-1.277-1.041-1.988-3.974-3.17-1.891-0.757-1.512 0.047 1.325 1.75z"/>\r
+   <path fill="#6e6e6e" d="m287.958 252.963c1.284 0.779 1.056 1.605 1.88 2.156 0.413 0.274 1.102 0.274 0.688-0.779-0.551-1.238-1.009-1.926-3.853-3.073-1.833-0.733-1.466 0.046 1.285 1.696z"/>\r
+   <path fill="#727272" d="m288.002 252.976c1.243 0.755 1.021 1.554 1.821 2.087 0.399 0.266 1.066 0.266 0.666-0.755-0.533-1.199-0.977-1.865-3.731-2.976-1.776-0.71-1.421 0.045 1.244 1.644z"/>\r
+   <path fill="#767676" d="m288.045 252.989c1.203 0.73 0.989 1.504 1.763 2.02 0.387 0.257 1.031 0.257 0.645-0.731-0.516-1.159-0.946-1.805-3.61-2.879-1.72-0.688-1.376 0.042 1.202 1.59z"/>\r
+   <path fill="#7a7a7a" d="m288.089 253c1.163 0.705 0.955 1.453 1.703 1.951 0.373 0.25 0.997 0.25 0.623-0.705-0.499-1.121-0.914-1.744-3.489-2.783-1.661-0.664-1.329 0.041 1.163 1.537z"/>\r
+   <path fill="#7e7e7e" d="m288.133 253.012c1.122 0.682 0.923 1.404 1.644 1.885 0.361 0.24 0.962 0.24 0.602-0.682-0.481-1.082-0.882-1.684-3.367-2.687-1.605-0.64-1.285 0.041 1.121 1.484z"/>\r
+   <path fill="#828282" d="m288.176 253.025c1.082 0.657 0.89 1.353 1.586 1.815 0.348 0.232 0.927 0.232 0.578-0.656-0.463-1.043-0.85-1.623-3.245-2.59-1.547-0.618-1.237 0.039 1.081 1.431z"/>\r
+   <path fill="#858585" d="m288.22 253.038c1.042 0.631 0.855 1.301 1.524 1.748 0.335 0.223 0.894 0.223 0.559-0.633-0.446-1.005-0.818-1.563-3.125-2.492-1.488-0.596-1.19 0.037 1.042 1.377z"/>\r
+   <path fill="#898989" d="m288.264 253.049c1.001 0.607 0.821 1.252 1.466 1.681 0.322 0.214 0.857 0.214 0.536-0.608-0.43-0.965-0.786-1.502-3.004-2.396-1.43-0.573-1.144 0.034 1.002 1.323z"/>\r
+   <path fill="#8d8d8d" d="m288.307 253.061c0.961 0.584 0.79 1.201 1.407 1.613 0.309 0.205 0.823 0.205 0.515-0.584-0.412-0.926-0.755-1.441-2.883-2.299-1.373-0.548-1.098 0.034 0.961 1.27z"/>\r
+   <path fill="#919191" d="m288.351 253.073c0.921 0.559 0.756 1.151 1.348 1.547 0.296 0.196 0.789 0.196 0.493-0.56-0.395-0.888-0.723-1.381-2.762-2.204-1.315-0.525-1.052 0.033 0.921 1.217z"/>\r
+   <path fill="#959595" d="m288.395 253.084c0.88 0.535 0.723 1.102 1.289 1.479 0.282 0.189 0.754 0.189 0.471-0.534-0.377-0.849-0.691-1.321-2.641-2.106-1.257-0.505-1.006 0.031 0.881 1.161z"/>\r
+   <path fill="#999" d="m288.438 253.097c0.84 0.51 0.689 1.05 1.229 1.409 0.271 0.181 0.721 0.181 0.45-0.51-0.36-0.81-0.66-1.26-2.52-2.01-1.199-0.48-0.959 0.031 0.841 1.111z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path d="m222.275 107.427c-0.738 0.902 0.574 8.365 5.412 13.285 4.839 4.838 7.791 4.838 9.759 2.706 3.771-4.018 0.738-7.791-1.558-10.415-2.297-2.624-5.248-1.722-7.955-4.346-2.706-2.624-4.592-2.46-5.658-1.23z"/>\r
+   <path fill="#050505" d="m222.345 107.494c-0.732 0.895 0.569 8.3 5.369 13.182 4.803 4.801 7.731 4.801 9.685 2.685 3.742-3.987 0.731-7.73-1.546-10.334-2.278-2.604-5.208-1.709-7.894-4.312-2.684-2.604-4.556-2.441-5.614-1.221z"/>\r
+   <path fill="#0a0a0a" d="m222.416 107.561c-0.727 0.888 0.565 8.235 5.328 13.079 4.763 4.763 7.67 4.763 9.607 2.664 3.713-3.956 0.727-7.67-1.534-10.253-2.26-2.584-5.166-1.696-7.831-4.279-2.664-2.583-4.521-2.422-5.57-1.211z"/>\r
+   <path fill="#0f0f0f" d="m222.486 107.628c-0.721 0.881 0.561 8.17 5.286 12.976 4.726 4.725 7.608 4.725 9.532 2.642 3.684-3.924 0.72-7.609-1.522-10.172-2.243-2.563-5.126-1.682-7.77-4.244-2.643-2.563-4.485-2.403-5.526-1.202z"/>\r
+   <path fill="#141414" d="m222.556 107.695c-0.716 0.874 0.557 8.105 5.243 12.872 4.689 4.688 7.55 4.688 9.456 2.622 3.655-3.893 0.716-7.549-1.51-10.091-2.224-2.543-5.085-1.669-7.707-4.211-2.621-2.543-4.449-2.384-5.482-1.192z"/>\r
+   <path fill="#191919" d="m222.627 107.762c-0.71 0.867 0.552 8.04 5.202 12.769 4.65 4.65 7.488 4.65 9.379 2.601 3.626-3.862 0.71-7.489-1.497-10.011s-5.044-1.655-7.646-4.177-4.414-2.364-5.438-1.182z"/>\r
+   <path fill="#1e1e1e" d="m222.697 107.829c-0.704 0.86 0.547 7.975 5.16 12.666 4.613 4.612 7.428 4.612 9.304 2.579 3.596-3.83 0.703-7.427-1.486-9.929-2.188-2.502-5.003-1.642-7.584-4.143-2.579-2.502-4.378-2.346-5.394-1.173z"/>\r
+   <path fill="#232323" d="m222.767 107.896c-0.697 0.853 0.543 7.91 5.117 12.562 4.576 4.575 7.367 4.575 9.229 2.559 3.567-3.8 0.698-7.367-1.473-9.848-2.171-2.482-4.963-1.629-7.522-4.11s-4.343-2.326-5.351-1.163z"/>\r
+   <path fill="#282828" d="m222.838 107.963c-0.691 0.846 0.538 7.845 5.076 12.459 4.537 4.537 7.307 4.537 9.152 2.538 3.537-3.769 0.691-7.307-1.461-9.768-2.154-2.461-4.922-1.615-7.461-4.076-2.538-2.461-4.307-2.307-5.306-1.153z"/>\r
+   <path fill="#2d2d2d" d="m222.908 108.03c-0.686 0.839 0.534 7.78 5.034 12.355 4.5 4.499 7.246 4.499 9.076 2.516 3.509-3.737 0.686-7.246-1.449-9.686-2.135-2.441-4.881-1.602-7.399-4.042-2.516-2.44-4.271-2.287-5.262-1.143z"/>\r
+   <path fill="#333" d="m222.978 108.096c-0.681 0.832 0.529 7.715 4.992 12.253 4.463 4.462 7.186 4.462 9.001 2.496 3.479-3.706 0.68-7.186-1.438-9.605-2.118-2.42-4.841-1.588-7.337-4.008s-4.235-2.27-5.218-1.136z"/>\r
+   <path fill="#383838" d="m223.048 108.163c-0.674 0.825 0.526 7.65 4.95 12.15 4.425 4.425 7.125 4.425 8.925 2.475 3.45-3.675 0.676-7.125-1.425-9.525-2.099-2.4-4.8-1.575-7.274-3.975-2.475-2.399-4.201-2.25-5.176-1.125z"/>\r
+   <path fill="#3d3d3d" d="m223.119 108.23c-0.669 0.818 0.521 7.585 4.908 12.047 4.387 4.387 7.063 4.387 8.849 2.453 3.42-3.643 0.669-7.064-1.413-9.443-2.082-2.38-4.759-1.562-7.214-3.941-2.453-2.38-4.164-2.231-5.13-1.116z"/>\r
+   <path fill="#424242" d="m223.189 108.297c-0.663 0.811 0.516 7.52 4.866 11.944 4.35 4.349 7.004 4.349 8.772 2.432 3.392-3.612 0.663-7.004-1.4-9.363-2.064-2.359-4.719-1.548-7.151-3.907-2.433-2.359-4.129-2.212-5.087-1.106z"/>\r
+   <path fill="#474747" d="m223.259 108.364c-0.656 0.804 0.513 7.455 4.824 11.84 4.313 4.312 6.944 4.312 8.697 2.412 3.362-3.582 0.658-6.944-1.388-9.282-2.046-2.339-4.679-1.535-7.09-3.874-2.411-2.338-4.093-2.192-5.043-1.096z"/>\r
+   <path fill="#4c4c4c" d="m223.33 108.431c-0.651 0.797 0.507 7.39 4.782 11.737 4.274 4.274 6.882 4.274 8.621 2.39 3.332-3.55 0.651-6.883-1.377-9.201s-4.636-1.521-7.028-3.839c-2.39-2.318-4.057-2.174-4.998-1.087z"/>\r
+   <path fill="#515151" d="m223.4 108.498c-0.646 0.79 0.503 7.325 4.74 11.634 4.236 4.236 6.821 4.236 8.545 2.369 3.304-3.519 0.646-6.822-1.364-9.12s-4.596-1.508-6.966-3.806c-2.369-2.298-4.022-2.154-4.955-1.077z"/>\r
+   <path fill="#565656" d="m223.47 108.565c-0.641 0.783 0.499 7.26 4.697 11.53 4.199 4.199 6.763 4.199 8.471 2.349 3.273-3.488 0.64-6.762-1.353-9.039-1.993-2.278-4.556-1.495-6.905-3.773-2.347-2.277-3.985-2.135-4.91-1.067z"/>\r
+   <path fill="#5b5b5b" d="m223.541 108.632c-0.635 0.776 0.493 7.195 4.656 11.427 4.161 4.161 6.701 4.161 8.393 2.327 3.245-3.456 0.636-6.701-1.34-8.958-1.975-2.257-4.514-1.48-6.843-3.738-2.327-2.257-3.95-2.116-4.866-1.058z"/>\r
+   <path fill="#606060" d="m223.611 108.699c-0.629 0.769 0.489 7.13 4.614 11.324 4.124 4.123 6.64 4.123 8.317 2.306 3.215-3.425 0.628-6.641-1.328-8.877-1.957-2.237-4.474-1.468-6.78-3.705-2.306-2.236-3.915-2.097-4.823-1.048z"/>\r
+   <path fill="#666" d="m223.681 108.765c-0.623 0.762 0.484 7.065 4.571 11.221 4.086 4.086 6.58 4.086 8.242 2.285 3.187-3.394 0.623-6.58-1.315-8.796-1.939-2.217-4.434-1.455-6.72-3.671-2.284-2.216-3.878-2.078-4.778-1.039z"/>\r
+  </g>\r
+  <g transform="translate(-12.4048,10.0005)">\r
+   <path fill="#fc0" d="m137.79 109.277c1.978 1.366 2.031 1.607 4.948 3.514 4.64 3.768 12.885 4.616 16.922 4.75 9.233 1.467 25.738-7.161 32.273-11.111 3.291-2.463 9.38-7.551 11.659-7.637 1.405 1.485-0.66 1.792-3.587 3.775-3.906 2.779-7.25 5.156-13.172 8.515-6.338 3.316-16.078 8.794-28.548 8.054-6.542-0.959-6.566-1.024-10.606-3.086-2.4-1.732-7.901-4.608-9.889-6.774z"/>\r
+   <linearGradient id="al" x1="129.342" gradientUnits="userSpaceOnUse" x2="195.598" gradientTransform="matrix(1,0,0,-1,8.3999,368.3)" y1="259.305" y2="259.305">\r
+    <stop stop-color="#FAC700" offset="0"/>\r
+    <stop stop-color="#F7C400" offset=".415"/>\r
+    <stop stop-color="#F7C400" offset="1"/>\r
+   </linearGradient>\r
+   <path fill="url(#al)" d="m137.742 109.259c1.926 1.274 2.165 1.643 5.083 3.554 4.616 3.734 12.716 4.616 16.796 4.763 9.365 1.452 26.05-7.294 32.356-11.159 3.357-2.506 9.344-7.498 11.595-7.604 1.365 1.472-0.728 1.768-3.688 3.814-3.889 2.753-7.119 5.065-12.972 8.383-6.29 3.291-16.078 8.795-28.536 8.104-6.561-0.945-6.851-1.07-10.758-3.079-2.468-1.755-7.876-4.587-9.876-6.776z"/>\r
+   <linearGradient id="am" x1="129.293" gradientUnits="userSpaceOnUse" x2="195.554" gradientTransform="matrix(1,0,0,-1,8.3999,368.3)" y1="259.311" y2="259.311">\r
+    <stop stop-color="#F6C200" offset="0"/>\r
+    <stop stop-color="#EFBC00" offset=".415"/>\r
+    <stop stop-color="#EFBC00" offset="1"/>\r
+   </linearGradient>\r
+   <path fill="url(#am)" d="m137.693 109.24c1.876 1.183 2.3 1.68 5.218 3.595 4.593 3.7 12.548 4.616 16.67 4.776 9.498 1.437 26.364-7.428 32.44-11.207 3.425-2.55 9.308-7.444 11.528-7.57 1.326 1.457-0.795 1.743-3.788 3.854-3.87 2.725-6.99 4.973-12.771 8.25-6.243 3.266-16.078 8.796-28.525 8.154-6.579-0.931-7.134-1.117-10.908-3.073-2.536-1.779-7.852-4.567-9.864-6.779z"/>\r
+   <linearGradient id="an" x1="129.245" gradientUnits="userSpaceOnUse" x2="195.51" gradientTransform="matrix(1,0,0,-1,8.3999,368.3)" y1="259.317" y2="259.317">\r
+    <stop stop-color="#F1BD00" offset="0"/>\r
+    <stop stop-color="#E8B500" offset=".415"/>\r
+    <stop stop-color="#E8B500" offset="1"/>\r
+   </linearGradient>\r
+   <path fill="url(#an)" d="m137.645 109.222c1.825 1.091 2.434 1.715 5.352 3.635 4.569 3.665 12.38 4.615 16.544 4.789 9.631 1.422 26.677-7.562 32.524-11.255 3.491-2.594 9.271-7.392 11.463-7.538 1.287 1.442-0.861 1.718-3.889 3.893-3.853 2.699-6.86 4.882-12.57 8.119-6.195 3.241-16.078 8.797-28.513 8.203-6.6-0.916-7.418-1.163-11.061-3.066-2.603-1.801-7.826-4.546-9.85-6.78z"/>\r
+   <linearGradient id="ao" x1="129.196" gradientUnits="userSpaceOnUse" x2="195.465" gradientTransform="matrix(1,0,0,-1,8.3999,368.3)" y1="259.322" y2="259.322">\r
+    <stop stop-color="#EDB800" offset="0"/>\r
+    <stop stop-color="#E0AD00" offset=".415"/>\r
+    <stop stop-color="#E0AD00" offset="1"/>\r
+   </linearGradient>\r
+   <path fill="url(#ao)" d="m137.596 109.203c1.774 1 2.568 1.752 5.487 3.676 4.545 3.631 12.211 4.615 16.418 4.801 9.764 1.408 26.989-7.695 32.608-11.302 3.557-2.637 9.236-7.338 11.396-7.505 1.247 1.427-0.928 1.693-3.99 3.932-3.833 2.672-6.729 4.791-12.369 7.986-6.148 3.217-16.078 8.799-28.501 8.254-6.619-0.902-7.702-1.21-11.21-3.059-2.672-1.825-7.803-4.525-9.839-6.783z"/>\r
+   <linearGradient id="ap" x1="129.148" gradientUnits="userSpaceOnUse" x2="195.422" gradientTransform="matrix(1,0,0,-1,8.3999,368.3)" y1="259.327" y2="259.327">\r
+    <stop stop-color="#E9B300" offset="0"/>\r
+    <stop stop-color="#D8A500" offset=".415"/>\r
+    <stop stop-color="#D8A500" offset="1"/>\r
+   </linearGradient>\r
+   <path fill="url(#ap)" d="m137.548 109.184c1.724 0.909 2.703 1.788 5.622 3.717 4.522 3.597 12.043 4.615 16.292 4.814 9.896 1.393 27.303-7.829 32.692-11.35 3.624-2.681 9.2-7.286 11.331-7.472 1.208 1.412-0.995 1.668-4.092 3.972-3.814 2.644-6.6 4.698-12.168 7.853-6.101 3.192-16.077 8.8-28.489 8.303-6.638-0.887-7.986-1.256-11.361-3.052-2.741-1.848-7.779-4.504-9.827-6.785z"/>\r
+   <linearGradient id="aq" x1="129.099" gradientUnits="userSpaceOnUse" x2="195.379" gradientTransform="matrix(1,0,0,-1,8.3999,368.3)" y1="259.332" y2="259.332">\r
+    <stop stop-color="#E4AE00" offset="0"/>\r
+    <stop stop-color="#D19E00" offset=".415"/>\r
+    <stop stop-color="#D19E00" offset="1"/>\r
+   </linearGradient>\r
+   <path fill="url(#aq)" d="m137.499 109.166c1.673 0.817 2.838 1.824 5.757 3.757 4.499 3.562 11.875 4.614 16.166 4.827 10.029 1.378 27.615-7.963 32.776-11.398 3.691-2.725 9.164-7.232 11.265-7.439 1.169 1.397-1.061 1.644-4.191 4.01-3.796 2.618-6.469 4.608-11.968 7.722-6.053 3.167-16.077 8.801-28.478 8.353-6.657-0.873-8.27-1.303-11.512-3.046-2.809-1.87-7.755-4.483-9.815-6.786z"/>\r
+   <linearGradient id="ar" x1="129.051" gradientUnits="userSpaceOnUse" x2="195.338" gradientTransform="matrix(1,0,0,-1,8.3999,368.3)" y1="259.336" y2="259.336">\r
+    <stop stop-color="#E0A900" offset="0"/>\r
+    <stop stop-color="#C99600" offset=".415"/>\r
+    <stop stop-color="#C99600" offset="1"/>\r
+   </linearGradient>\r
+   <path fill="url(#ar)" d="m137.451 109.147c1.622 0.726 2.972 1.86 5.892 3.798 4.475 3.528 11.705 4.614 16.04 4.84 10.161 1.362 27.929-8.097 32.859-11.447 3.757-2.767 9.128-7.178 11.2-7.405 1.13 1.382-1.128 1.619-4.294 4.049-3.777 2.592-6.339 4.517-11.767 7.589-6.005 3.143-16.077 8.803-28.466 8.404-6.676-0.859-8.553-1.35-11.663-3.039-2.876-1.894-7.729-4.462-9.801-6.789z"/>\r
+   <linearGradient id="w" x1="129.003" gradientUnits="userSpaceOnUse" x2="195.296" gradientTransform="matrix(1,0,0,-1,8.3999,368.3)" y1="259.34" y2="259.34">\r
+    <stop stop-color="#DCA400" offset="0"/>\r
+    <stop stop-color="#C18E00" offset=".415"/>\r
+    <stop stop-color="#C18E00" offset="1"/>\r
+   </linearGradient>\r
+   <path fill="url(#w)" d="m137.403 109.129c1.571 0.634 3.105 1.896 6.026 3.838 4.45 3.494 11.536 4.614 15.913 4.852 10.295 1.348 28.241-8.23 32.943-11.494 3.824-2.811 9.092-7.125 11.134-7.373 1.092 1.367-1.194 1.594-4.394 4.088-3.759 2.565-6.209 4.425-11.566 7.457-5.957 3.118-16.077 8.804-28.454 8.453-6.694-0.844-8.837-1.396-11.813-3.032-2.945-1.916-7.706-4.44-9.789-6.789z"/>\r
+   <linearGradient id="x" x1="128.954" gradientUnits="userSpaceOnUse" x2="195.255" gradientTransform="matrix(1,0,0,-1,8.3999,368.3)" y1="259.343" y2="259.343">\r
+    <stop stop-color="#D79F00" offset="0"/>\r
+    <stop stop-color="#BA8700" offset=".415"/>\r
+    <stop stop-color="#BA8700" offset="1"/>\r
+   </linearGradient>\r
+   <path fill="url(#x)" d="m137.354 109.11c1.521 0.543 3.241 1.932 6.161 3.879 4.428 3.459 11.368 4.613 15.788 4.865 10.427 1.333 28.554-8.364 33.026-11.542 3.892-2.855 9.057-7.073 11.068-7.339 1.052 1.353-1.261 1.569-4.495 4.127-3.74 2.538-6.078 4.334-11.365 7.325-5.91 3.093-16.077 8.805-28.441 8.503-6.716-0.83-9.121-1.443-11.966-3.026-3.012-1.939-7.68-4.42-9.776-6.792z"/>\r
+   <linearGradient id="y" x1="128.906" gradientUnits="userSpaceOnUse" x2="195.216" gradientTransform="matrix(1,0,0,-1,8.3999,368.3)" y1="259.348" y2="259.348">\r
+    <stop stop-color="#D39B00" offset="0"/>\r
+    <stop stop-color="#B27F00" offset=".415"/>\r
+    <stop stop-color="#B27F00" offset="1"/>\r
+   </linearGradient>\r
+   <path fill="url(#y)" d="m137.306 109.091c1.47 0.451 3.375 1.969 6.296 3.919 4.403 3.426 11.2 4.614 15.662 4.879 10.559 1.318 28.865-8.498 33.109-11.59 3.958-2.899 9.021-7.02 11.003-7.306 1.013 1.337-1.328 1.544-4.596 4.167-3.722 2.511-5.949 4.242-11.165 7.192-5.862 3.068-16.077 8.807-28.43 8.553-6.734-0.816-9.405-1.489-12.116-3.019-3.08-1.963-7.656-4.4-9.763-6.795z"/>\r
+   <linearGradient id="z" x1="128.857" gradientUnits="userSpaceOnUse" x2="195.176" gradientTransform="matrix(1,0,0,-1,8.3999,368.3)" y1="259.35" y2="259.35">\r
+    <stop stop-color="#CF9600" offset="0"/>\r
+    <stop stop-color="#a70" offset=".415"/>\r
+    <stop stop-color="#a70" offset="1"/>\r
+   </linearGradient>\r
+   <path fill="url(#z)" d="m137.257 109.073c1.421 0.359 3.511 2.005 6.432 3.959 4.38 3.392 11.032 4.614 15.536 4.892 10.691 1.303 29.179-8.631 33.193-11.638 4.024-2.942 8.984-6.967 10.938-7.274 0.973 1.323-1.396 1.521-4.697 4.206-3.703 2.484-5.818 4.151-10.964 7.06-5.814 3.043-16.077 8.808-28.418 8.603-6.753-0.802-9.689-1.535-12.268-3.012-3.149-1.986-7.632-4.378-9.752-6.796z"/>\r
+   <linearGradient id="aa" x1="128.809" gradientUnits="userSpaceOnUse" x2="195.137" gradientTransform="matrix(1,0,0,-1,8.3999,368.3)" y1="259.354" y2="259.354">\r
+    <stop stop-color="#CA9100" offset="0"/>\r
+    <stop stop-color="#A37000" offset=".415"/>\r
+    <stop stop-color="#A37000" offset="1"/>\r
+   </linearGradient>\r
+   <path fill="url(#aa)" d="m137.209 109.054c1.368 0.268 3.645 2.041 6.565 4 4.356 3.357 10.864 4.613 15.41 4.904 10.824 1.289 29.493-8.764 33.277-11.685 4.092-2.986 8.948-6.914 10.871-7.241 0.935 1.308-1.461 1.496-4.797 4.245-3.685 2.457-5.688 4.06-10.763 6.928-5.768 3.018-16.077 8.809-28.407 8.653-6.771-0.788-9.972-1.582-12.418-3.006-3.216-2.008-7.607-4.357-9.738-6.798z"/>\r
+   <linearGradient id="ab" x1="128.76" gradientUnits="userSpaceOnUse" x2="195.099" gradientTransform="matrix(1,0,0,-1,8.3999,368.3)" y1="259.356" y2="259.356">\r
+    <stop stop-color="#C68C00" offset="0"/>\r
+    <stop stop-color="#9B6800" offset=".415"/>\r
+    <stop stop-color="#9B6800" offset="1"/>\r
+   </linearGradient>\r
+   <path fill="url(#ab)" d="m137.16 109.036c1.318 0.176 3.779 2.077 6.701 4.04 4.333 3.323 10.695 4.613 15.284 4.917 10.957 1.274 29.805-8.898 33.36-11.733 4.158-3.03 8.912-6.86 10.807-7.208 0.894 1.292-1.528 1.471-4.899 4.284-3.666 2.43-5.558 3.968-10.562 6.796-5.72 2.993-16.077 8.81-28.396 8.702-6.791-0.773-10.256-1.628-12.568-2.999-3.285-2.031-7.583-4.336-9.727-6.799z"/>\r
+   <linearGradient id="ac" x1="128.712" gradientUnits="userSpaceOnUse" x2="195.062" gradientTransform="matrix(1,0,0,-1,8.3999,368.3)" y1="259.358" y2="259.358">\r
+    <stop stop-color="#C28700" offset="0"/>\r
+    <stop stop-color="#936000" offset=".415"/>\r
+    <stop stop-color="#936000" offset="1"/>\r
+   </linearGradient>\r
+   <path fill="url(#ac)" d="m137.112 109.017c1.267 0.085 3.912 2.113 6.835 4.081 4.31 3.289 10.527 4.613 15.158 4.93 11.09 1.258 30.118-9.032 33.445-11.782 4.225-3.072 8.877-6.807 10.739-7.174 0.855 1.278-1.595 1.446-5 4.323-3.647 2.404-5.427 3.877-10.36 6.663-5.673 2.969-16.077 8.812-28.384 8.753-6.811-0.759-10.54-1.675-12.719-2.992-3.353-2.055-7.559-4.315-9.714-6.802z"/>\r
+   <linearGradient id="ad" x1="128.663" gradientUnits="userSpaceOnUse" x2="195.025" gradientTransform="matrix(1,0,0,-1,8.3999,368.3)" y1="259.36" y2="259.36">\r
+    <stop stop-color="#BD8200" offset="0"/>\r
+    <stop stop-color="#8C5900" offset=".415"/>\r
+    <stop stop-color="#8C5900" offset="1"/>\r
+   </linearGradient>\r
+   <path fill="url(#ad)" d="m137.063 108.998c1.217-0.006 4.047 2.15 6.97 4.122 4.286 3.254 10.359 4.612 15.032 4.943 11.223 1.243 30.431-9.166 33.53-11.83 4.289-3.116 8.84-6.753 10.673-7.141 0.815 1.263-1.661 1.421-5.101 4.362-3.63 2.377-5.298 3.785-10.161 6.531-5.624 2.944-16.075 8.813-28.37 8.802-6.83-0.744-10.824-1.721-12.87-2.985-3.422-2.077-7.535-4.294-9.703-6.804z"/>\r
+   <linearGradient id="ae" x1="128.615" gradientUnits="userSpaceOnUse" x2="194.989" gradientTransform="matrix(1,0,0,-1,8.3999,368.3)" y1="259.362" y2="259.362">\r
+    <stop stop-color="#B97D00" offset="0"/>\r
+    <stop stop-color="#845100" offset=".415"/>\r
+    <stop stop-color="#845100" offset="1"/>\r
+   </linearGradient>\r
+   <path fill="url(#ae)" d="m137.015 108.98c1.166-0.098 4.181 2.185 7.104 4.162 4.262 3.22 10.19 4.612 14.906 4.955 11.354 1.229 30.743-9.299 33.613-11.877 4.356-3.16 8.804-6.7 10.607-7.108 0.776 1.248-1.728 1.397-5.202 4.401-3.61 2.35-5.167 3.694-9.96 6.399-5.576 2.919-16.076 8.814-28.358 8.852-6.85-0.73-11.108-1.768-13.021-2.979-3.489-2.1-7.51-4.273-9.689-6.805z"/>\r
+   <linearGradient id="af" x1="128.567" gradientUnits="userSpaceOnUse" x2="194.954" gradientTransform="matrix(1,0,0,-1,8.3999,368.3)" y1="259.364" y2="259.364">\r
+    <stop stop-color="#B57800" offset="0"/>\r
+    <stop stop-color="#7C4900" offset=".415"/>\r
+    <stop stop-color="#7C4900" offset="1"/>\r
+   </linearGradient>\r
+   <path fill="url(#af)" d="m136.967 108.961c1.115-0.189 4.315 2.222 7.239 4.203 4.239 3.186 10.021 4.612 14.78 4.968 11.488 1.214 31.057-9.433 33.697-11.925 4.424-3.203 8.768-6.647 10.542-7.075 0.736 1.233-1.795 1.372-5.303 4.44-3.593 2.323-5.038 3.603-9.759 6.266-5.529 2.895-16.077 8.816-28.348 8.903-6.868-0.716-11.392-1.815-13.172-2.972-3.557-2.124-7.485-4.252-9.676-6.808z"/>\r
+   <linearGradient id="ah" x1="128.518" gradientUnits="userSpaceOnUse" x2="194.918" gradientTransform="matrix(1,0,0,-1,8.3999,368.3)" y1="259.366" y2="259.366">\r
+    <stop stop-color="#B07300" offset="0"/>\r
+    <stop stop-color="#754200" offset=".415"/>\r
+    <stop stop-color="#754200" offset="1"/>\r
+   </linearGradient>\r
+   <path fill="url(#ah)" d="m136.918 108.943c1.064-0.281 4.45 2.257 7.374 4.243 4.216 3.151 9.854 4.611 14.654 4.981 11.621 1.199 31.37-9.567 33.781-11.973 4.49-3.247 8.731-6.594 10.476-7.042 0.698 1.218-1.861 1.347-5.403 4.479-3.573 2.296-4.906 3.511-9.558 6.134-5.48 2.87-16.076 8.817-28.336 8.952-6.887-0.701-11.675-1.861-13.323-2.965-3.626-2.146-7.462-4.231-9.665-6.809z"/>\r
+   <linearGradient id="ai" x1="128.47" gradientUnits="userSpaceOnUse" x2="194.886" gradientTransform="matrix(1,0,0,-1,8.3999,368.3)" y1="259.367" y2="259.367">\r
+    <stop stop-color="#AC6E00" offset="0"/>\r
+    <stop stop-color="#6D3A00" offset=".415"/>\r
+    <stop stop-color="#6D3A00" offset="1"/>\r
+   </linearGradient>\r
+   <path fill="url(#ai)" d="m136.87 108.924c1.013-0.372 4.584 2.294 7.509 4.284 4.191 3.117 9.685 4.611 14.528 4.994 11.753 1.184 31.682-9.701 33.864-12.021 4.557-3.291 8.695-6.542 10.411-7.009 0.657 1.203-1.929 1.322-5.504 4.518-3.557 2.269-4.777 3.42-9.358 6.002-5.434 2.845-16.076 8.818-28.324 9.002-6.907-0.687-11.959-1.908-13.474-2.959-3.694-2.169-7.437-4.21-9.652-6.811z"/>\r
+   <linearGradient id="aj" x1="128.421" gradientUnits="userSpaceOnUse" x2="194.85" gradientTransform="matrix(1,0,0,-1,8.3999,368.3)" y1="259.369" y2="259.369">\r
+    <stop stop-color="#A86A00" offset="0"/>\r
+    <stop stop-color="#663200" offset=".415"/>\r
+    <stop stop-color="#663200" offset="1"/>\r
+   </linearGradient>\r
+   <path fill="url(#aj)" d="m136.821 108.905c0.963-0.464 4.719 2.33 7.644 4.324 4.168 3.083 9.517 4.611 14.402 5.007 11.886 1.169 31.995-9.834 33.948-12.069 4.624-3.334 8.66-6.487 10.345-6.976 0.619 1.188-1.995 1.298-5.604 4.558-3.537 2.242-4.647 3.328-9.157 5.869-5.386 2.82-16.076 8.82-28.313 9.052-6.926-0.673-12.243-1.954-13.625-2.952-3.762-2.192-7.413-4.189-9.64-6.813z"/>\r
+  </g>\r
+ </g>\r
+</svg>\r
diff --git a/tests/phpunit/includes/MWExceptionHandlerTest.php b/tests/phpunit/includes/MWExceptionHandlerTest.php
new file mode 100644 (file)
index 0000000..aebd65f
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+/**
+ * Tests for includes/Exception.php.
+ *
+ * @author Antoine Musso
+ * @copyright Copyright © 2013, Antoine Musso
+ * @copyright Copyright © 2013, Wikimedia Foundation Inc.
+ * @file
+ */
+
+class MWExceptionHandlerTest extends MediaWikiTestCase {
+
+       /**
+        * @covers MWExceptionHandler::getRedactedTrace
+        */
+       function testGetRedactedTrace() {
+               $refvar = 'value';
+               try {
+                       $array = array( 'a', 'b' );
+                       $object = new StdClass();
+                       self::helperThrowAnException( $array, $object, $refvar );
+               } catch (Exception $e) {
+               }
+
+               # Make sure our strack trace contains an array and an object passed to
+               # some function in the stacktrace. Else, we can not assert the trace
+               # redaction achieved its job.
+               $trace = $e->getTrace();
+               $hasObject = false;
+               $hasArray = false;
+               foreach ( $trace as $frame ) {
+                       if ( ! isset( $frame['args'] ) ) {
+                               continue;
+                       }
+                       foreach ( $frame['args'] as $arg ) {
+                               $hasObject = $hasObject || is_object( $arg );
+                               $hasArray = $hasArray || is_array( $arg );
+                       }
+
+                       if( $hasObject && $hasArray ) {
+                               break;
+                       }
+               }
+               $this->assertTrue( $hasObject,
+                       "The stacktrace must have a function having an object has parameter" );
+               $this->assertTrue( $hasArray,
+                       "The stacktrace must have a function having an array has parameter" );
+
+               # Now we redact the trace.. and make sure no function arguments are
+               # arrays or objects.
+               $redacted = MWExceptionHandler::getRedactedTrace( $e );
+
+               foreach ( $redacted as $frame ) {
+                       if ( ! isset( $frame['args'] ) ) {
+                               continue;
+                       }
+                       foreach ( $frame['args'] as $arg ) {
+                               $this->assertNotInternalType( 'array', $arg);
+                               $this->assertNotInternalType( 'object', $arg);
+                       }
+               }
+
+               $this->assertEquals( 'value', $refvar, 'Ensuring reference variable wasn\'t changed' );
+       }
+
+       /**
+        * Helper function for testExpandArgumentsInCall
+        *
+        * Pass it an object and an array, and something by reference :-)
+        *
+        * @throws Exception
+        */
+       protected static function helperThrowAnException( $a, $b, &$c ) {
+               throw new Exception();
+       }
+}
index 81246d3..97abf80 100644 (file)
@@ -235,7 +235,7 @@ class SanitizerTest extends MediaWikiTestCase {
                        array( 'align="left"', 'tr' ),
                        array( 'align="center"', 'div' ),
                        array( 'align="left"', 'h1' ),
-                       array( 'align="left"', 'span' ),
+                       array( 'align="left"', 'p' ),
                );
        }
 
diff --git a/tests/phpunit/includes/diff/DifferenceEngineTest.php b/tests/phpunit/includes/diff/DifferenceEngineTest.php
new file mode 100644 (file)
index 0000000..738121a
--- /dev/null
@@ -0,0 +1,120 @@
+<?php
+
+/**
+ * @covers DifferenceEngine
+ *
+ * @todo tests for the rest of DifferenceEngine!
+ *
+ * @group Database
+ *
+ * @licence GNU GPL v2+
+ * @author Katie Filbert < aude.wiki@gmail.com >
+ */
+class DifferenceEngineTest extends MediaWikiTestCase {
+
+       protected $context;
+
+       private static $revisions;
+
+       public function setUp() {
+               parent::setUp();
+
+               $title = $this->getTitle();
+
+               $this->context = new RequestContext();
+               $this->context->setTitle( $title );
+
+               if ( !self::$revisions ) {
+                       self::$revisions = $this->doEdits();
+               }
+       }
+
+       /**
+        * @return Title
+        */
+       protected function getTitle() {
+               $namespace = $this->getDefaultWikitextNS();
+               return Title::newFromText( 'Kitten', $namespace );
+       }
+
+       /**
+        * @return int[] revision ids
+        */
+       protected function doEdits() {
+               $title = $this->getTitle();
+               $page = WikiPage::factory( $title );
+
+               $strings = array( "it is a kitten", "two kittens", "three kittens", "four kittens" );
+               $revisions = array();
+
+               foreach( $strings as $string ) {
+                       $content = ContentHandler::makeContent( $string, $title );
+                       $page->doEditContent( $content, 'edit page' );
+                       $revisions[] = $page->getLatest();
+               }
+
+               return $revisions;
+       }
+
+       public function testMapDiffPrevNext() {
+               $cases = $this->getMapDiffPrevNextCases();
+
+               foreach( $cases as $case ) {
+                       list( $expected, $old, $new, $message ) = $case;
+
+                       $diffEngine = new DifferenceEngine( $this->context, $old, $new, 2, true, false );
+                       $diffMap = $diffEngine->mapDiffPrevNext( $old, $new );
+                       $this->assertEquals( $expected, $diffMap, $message );
+               }
+       }
+
+       private function getMapDiffPrevNextCases() {
+               $revs = self::$revisions;
+
+               return array(
+                       array( array( $revs[1], $revs[2] ), $revs[2], 'prev', 'diff=prev' ),
+                       array( array( $revs[2], $revs[3] ), $revs[2], 'next', 'diff=next' ),
+                       array( array( $revs[1], $revs[3] ), $revs[1], $revs[3], 'diff=' . $revs[3] )
+               );
+       }
+
+       public function testLoadRevisionData() {
+               $cases = $this->getLoadRevisionDataCases();
+
+               foreach( $cases as $case ) {
+                       list( $expectedOld, $expectedNew, $old, $new, $message ) = $case;
+
+                       $diffEngine = new DifferenceEngine( $this->context, $old, $new, 2, true, false );
+                       $diffEngine->loadRevisionData();
+
+                       $this->assertEquals( $diffEngine->getOldid(), $expectedOld, $message );
+                       $this->assertEquals( $diffEngine->getNewid(), $expectedNew, $message );
+               }
+       }
+
+       private function getLoadRevisionDataCases() {
+               $revs = self::$revisions;
+
+               return array(
+                       array( $revs[2], $revs[3], $revs[3], 'prev', 'diff=prev' ),
+                       array( $revs[2], $revs[3], $revs[2], 'next', 'diff=next' ),
+                       array( $revs[1], $revs[3], $revs[1], $revs[3], 'diff=' . $revs[3] ),
+                       array( $revs[1], $revs[3], $revs[1], 0, 'diff=0' )
+               );
+       }
+
+       public function testGetOldid() {
+               $revs = self::$revisions;
+
+               $diffEngine = new DifferenceEngine( $this->context, $revs[1], $revs[2], 2, true, false );
+               $this->assertEquals( $revs[1], $diffEngine->getOldid(), 'diff get old id' );
+       }
+
+       public function testGetNewid() {
+               $revs = self::$revisions;
+
+               $diffEngine = new DifferenceEngine( $this->context, $revs[1], $revs[2], 2, true, false );
+               $this->assertEquals( $revs[2], $diffEngine->getNewid(), 'diff get new id' );
+       }
+
+}
index a8e420f..6ff928e 100644 (file)
@@ -52,6 +52,39 @@ class FormatMetadataTest extends MediaWikiTestCase {
                        'File with invalid date metadata (bug 29471)' );
        }
 
+       /**
+        * @param $filename String
+        * @param $expected Integer Total image area
+        * @dataProvider provideFlattenArray
+        * @covers FormatMetadata::flattenArray
+        */
+       public function testFlattenArray( $vals, $type, $noHtml, $ctx, $expected ) {
+               $actual = FormatMetadata::flattenArray( $vals, $type, $noHtml, $ctx );
+               $this->assertEquals( $expected, $actual );
+       }
+
+       public static function provideFlattenArray() {
+               return array(
+                       array (
+                               array(1 ,2 ,3), 'ul', false, false,
+                               "<ul><li>1</li>\n<li>2</li>\n<li>3</li></ul>",
+                       ),
+                       array (
+                               array(1 ,2 ,3), 'ol', false, false,
+                               "<ol><li>1</li>\n<li>2</li>\n<li>3</li></ol>",
+                       ),
+                       array (
+                               array(1 ,2 ,3), 'ul', true, false,
+                               "\n*1\n*2\n*3",
+                       ),
+                       array (
+                               array(1 ,2 ,3), 'ol', true, false,
+                               "\n#1\n#2\n#3",
+                       ),
+                       // TODO: more test cases
+               );
+       }
+
        private function dataFile( $name, $type ) {
                return new UnregisteredLocalFile( false, $this->repo,
                        "mwstore://localtesting/data/$name", $type );
index c8e729c..4350cbb 100644 (file)
@@ -113,6 +113,42 @@ class GIFHandlerTest extends MediaWikiTestCase {
                );
        }
 
+       /**
+        * @param $filename String
+        * @param $expected String Serialized array
+        * @dataProvider provideGetIndependentMetaArray
+        * @covers GIFHandler::getCommonMetaArray
+        */
+       public function testGetIndependentMetaArray( $filename, $expected ) {
+               $file = $this->dataFile( $filename, 'image/gif' );
+               $actual = $this->handler->getCommonMetaArray( $file );
+               $this->assertEquals( $expected, $actual );
+       }
+
+       public function provideGetIndependentMetaArray() {
+               return array(
+                       array( 'nonanimated.gif', array(
+                               'GIFFileComment' => array(
+                                       'GIF test file ⁕ Created with GIMP',
+                               ),
+                       ) ),
+                       array( 'animated-xmp.gif',
+                               array(
+                                       'Artist' => 'Bawolff',
+                                       'ImageDescription' => array(
+                                               'x-default' => 'A file to test GIF',
+                                               '_type' => 'lang',
+                                       ),
+                                       'SublocationDest' => 'The interwebs',
+                                       'GIFFileComment' =>
+                                       array(
+                                               'GIƒ·test·file',
+                                       ),
+                               )
+                       ),
+               );
+       }
+
        private function dataFile( $name, $type ) {
                return new UnregisteredLocalFile( false, $this->repo,
                        "mwstore://localtesting/data/$name", $type );
index ce956ba..5157228 100644 (file)
@@ -16,20 +16,58 @@ class JpegTest extends MediaWikiTestCase {
 
 
                $this->setMwGlobals( 'wgShowEXIF', true );
+
+               $this->backend = new FSFileBackend( array(
+                       'name' => 'localtesting',
+                       'lockManager' => 'nullLockManager',
+                       'containerPaths' => array( 'data' => $this->filePath )
+               ) );
+               $this->repo = new FSRepo( array(
+                       'name' => 'temp',
+                       'url' => 'http://localhost/thumbtest',
+                       'backend' => $this->backend
+               ) );
+
+               $this->handler = new JpegHandler;
        }
 
        public function testInvalidFile() {
-               $jpeg = new JpegHandler;
-               $res = $jpeg->getMetadata( null, $this->filePath . 'README' );
+               $file = $this->dataFile( 'README', 'image/jpeg' );
+               $res = $this->handler->getMetadata( $file, $this->filePath . 'README' );
                $this->assertEquals( ExifBitmapHandler::BROKEN_FILE, $res );
        }
 
        public function testJpegMetadataExtraction() {
-               $h = new JpegHandler;
-               $res = $h->getMetadata( null, $this->filePath . 'test.jpg' );
+               $file = $this->dataFile( 'test.jpg', 'image/jpeg' );
+               $res = $this->handler->getMetadata( $file, $this->filePath . 'test.jpg' );
                $expected = 'a:7:{s:16:"ImageDescription";s:9:"Test file";s:11:"XResolution";s:4:"72/1";s:11:"YResolution";s:4:"72/1";s:14:"ResolutionUnit";i:2;s:16:"YCbCrPositioning";i:1;s:15:"JPEGFileComment";a:1:{i:0;s:17:"Created with GIMP";}s:22:"MEDIAWIKI_EXIF_VERSION";i:2;}';
 
                // Unserialize in case serialization format ever changes.
                $this->assertEquals( unserialize( $expected ), unserialize( $res ) );
        }
+
+       /**
+        * @covers JpegHandler::getCommonMetaArray
+        */
+       public function testGetIndependentMetaArray() {
+               $file = $this->dataFile( 'test.jpg', 'image/jpeg' );
+               $res = $this->handler->getCommonMetaArray( $file );
+               $expected = array(
+                       'ImageDescription' => 'Test file',
+                       'XResolution' => '72/1',
+                       'YResolution' => '72/1',
+                       'ResolutionUnit' => 2,
+                       'YCbCrPositioning' => 1,
+                       'JPEGFileComment' => array(
+                               'Created with GIMP',
+                       ),
+               );
+
+               $this->assertEquals( $res, $expected );
+       }
+
+       private function dataFile( $name, $type ) {
+               return new UnregisteredLocalFile( false, $this->repo,
+                       "mwstore://localtesting/data/$name", $type );
+       }
 }
index ad4c249..2cb7426 100644 (file)
@@ -116,6 +116,29 @@ class PNGHandlerTest extends MediaWikiTestCase {
                );
        }
 
+       /**
+        * @param $filename String
+        * @param $expected Array Expected standard metadata
+        * @dataProvider provideGetIndependentMetaArray
+        * @covers PNGHandler::getCommonMetaArray
+        */
+       public function testGetIndependentMetaArray( $filename, $expected ) {
+               $file = $this->dataFile( $filename, 'image/png' );
+               $actual = $this->handler->getCommonMetaArray( $file );
+               $this->assertEquals( $expected, $actual );
+       }
+
+       public function provideGetIndependentMetaArray() {
+               return array(
+                       array( 'rgb-na-png.png', array() ),
+                       array( 'xmp.png',
+                               array(
+                                       'SerialNumber' => '123456789',
+                               )
+                       ),
+               );
+       }
+
        private function dataFile( $name, $type ) {
                return new UnregisteredLocalFile( false, $this->repo,
                        "mwstore://localtesting/data/$name", $type );
index 5dd7b24..d00a33d 100644 (file)
@@ -83,6 +83,17 @@ class SVGMetadataExtractorTest extends MediaWikiTestCase {
                                        'originalWidth' => '385',
                                        'originalHeight' => '385.0004883',
                                )
+                       ),
+                       array(
+                               "$base/Tux.svg",
+                               array(
+                                       'width' => 512,
+                                       'height' => 594,
+                                       'originalWidth' => '100%',
+                                       'originalHeight' => '100%',
+                                       'title' => 'Tux',
+                                       'description' => 'For more information see: http://commons.wikimedia.org/wiki/Image:Tux.svg',
+                               )
                        )
                );
        }
diff --git a/tests/phpunit/includes/media/SVGTest.php b/tests/phpunit/includes/media/SVGTest.php
new file mode 100644 (file)
index 0000000..b28ee56
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+class SVGTest extends MediaWikiTestCase {
+
+       protected function setUp() {
+               parent::setUp();
+
+               $this->filePath = __DIR__ . '/../../data/media/';
+
+               $this->setMwGlobals( 'wgShowEXIF', true );
+
+               $this->backend = new FSFileBackend( array(
+                       'name' => 'localtesting',
+                       'lockManager' => 'nullLockManager',
+                       'containerPaths' => array( 'data' => $this->filePath )
+               ) );
+               $this->repo = new FSRepo( array(
+                       'name' => 'temp',
+                       'url' => 'http://localhost/thumbtest',
+                       'backend' => $this->backend
+               ) );
+
+               $this->handler = new SVGHandler;
+       }
+
+       /**
+        * @param $filename String
+        * @param $expected Array The expected independent metadata
+        * @dataProvider providerGetIndependentMetaArray
+        * @covers SvgHandler::getCommonMetaArray
+        */
+       public function testGetIndependentMetaArray( $filename, $expected ) {
+               $file = $this->dataFile( $filename, 'image/svg+xml' );
+               $res = $this->handler->getCommonMetaArray( $file );
+
+               $this->assertEquals( $res, $expected );
+       }
+
+       public function providerGetIndependentMetaArray() {
+               return array(
+                       array( 'Tux.svg', array(
+                               'ObjectName' => 'Tux',
+                               'ImageDescription' => 'For more information see: http://commons.wikimedia.org/wiki/Image:Tux.svg',
+                       ) ),
+                       array( 'Wikimedia-logo.svg', array() )
+               );
+       }
+
+       private function dataFile( $name, $type ) {
+               return new UnregisteredLocalFile( false, $this->repo,
+                       "mwstore://localtesting/data/$name", $type );
+       }
+}
index fe823fa..746cb70 100644 (file)
@@ -26,27 +26,98 @@ class ResourcesTest extends MediaWikiTestCase {
        }
 
        /**
-        * This ask the ResouceLoader for all registered files from modules
-        * created by ResourceLoaderFileModule (or one of its descendants).
-        *
-        *
-        * Since the raw data is stored in protected properties, we have to
-        * overrride this through ReflectionObject methods.
+        * @dataProvider provideMediaStylesheets
         */
-       public static function provideResourceFiles() {
+       public function testStyleMedia( $moduleName, $media, $filename, $css ) {
+               $cssText = CSSMin::minify( $css->cssText );
+
+               $this->assertTrue( strpos( $cssText, '@media' ) === false, 'Stylesheets should not both specify "media" and contain @media' );
+       }
+
+       /**
+        * Get all registered modules from ResouceLoader.
+        */
+       protected static function getAllModules() {
                global $wgEnableJavaScriptTest;
 
                // Test existance of test suite files as well
                // (can't use setUp or setMwGlobals because providers are static)
-               $live_wgEnableJavaScriptTest = $wgEnableJavaScriptTest;
+               $org_wgEnableJavaScriptTest = $wgEnableJavaScriptTest;
                $wgEnableJavaScriptTest = true;
 
-               // Array with arguments for the test function
-               $cases = array();
-
                // Initialize ResourceLoader
                $rl = new ResourceLoader();
 
+               $modules = array();
+
+               foreach ( $rl->getModuleNames() as $moduleName ) {
+                       $modules[$moduleName] = $rl->getModule( $moduleName );
+               }
+
+               // Restore settings
+               $wgEnableJavaScriptTest = $org_wgEnableJavaScriptTest;
+
+               return array(
+                       'modules' => $modules,
+                       'resourceloader' => $rl,
+                       'context' => new ResourceLoaderContext( $rl, new FauxRequest() )
+               );
+       }
+
+       /**
+        * Get all stylesheet files from modules that are an instance of
+        * ResourceLoaderFileModule (or one of its subclasses).
+        */
+       public static function provideMediaStylesheets() {
+               $data = self::getAllModules();
+               $cases = array();
+
+               foreach ( $data['modules'] as $moduleName => $module ) {
+                       if ( !$module instanceof ResourceLoaderFileModule ) {
+                               continue;
+                       }
+
+                       $reflectedModule = new ReflectionObject( $module );
+
+                       $getStyleFiles = $reflectedModule->getMethod( 'getStyleFiles' );
+                       $getStyleFiles->setAccessible( true );
+
+                       $readStyleFile = $reflectedModule->getMethod( 'readStyleFile' );
+                       $readStyleFile->setAccessible( true );
+
+                       $styleFiles = $getStyleFiles->invoke( $module, $data['context'] );
+
+                       $flip = $module->getFlip( $data['context'] );
+
+                       foreach ( $styleFiles as $media => $files ) {
+                               if ( $media && $media !== 'all' ) {
+                                       foreach ( $files as $file ) {
+                                               $cases[] = array(
+                                                       $moduleName,
+                                                       $media,
+                                                       $file,
+                                                       // XXX: Wrapped in an object to keep it out of PHPUnit output
+                                                       (object) array( 'cssText' => $readStyleFile->invoke( $module, $file, $flip ) ),
+                                               );
+                                       }
+                               }
+                       }
+               }
+
+               return $cases;
+       }
+
+       /**
+        * Get all resource files from modules that are an instance of
+        * ResourceLoaderFileModule (or one of its subclasses).
+        *
+        * Since the raw data is stored in protected properties, we have to
+        * overrride this through ReflectionObject methods.
+        */
+       public static function provideResourceFiles() {
+               $data = self::getAllModules();
+               $cases = array();
+
                // See also ResourceLoaderFileModule::__construct
                $filePathProps = array(
                        // Lists of file paths
@@ -65,8 +136,7 @@ class ResourcesTest extends MediaWikiTestCase {
                        ),
                );
 
-               foreach ( $rl->getModuleNames() as $moduleName ) {
-                       $module = $rl->getModule( $moduleName );
+               foreach ( $data['modules'] as $moduleName => $module ) {
                        if ( !$module instanceof ResourceLoaderFileModule ) {
                                continue;
                        }
@@ -117,14 +187,12 @@ class ResourcesTest extends MediaWikiTestCase {
                        foreach ( $files as $file ) {
                                $cases[] = array(
                                        $method->invoke( $module, $file ),
-                                       $module->getName(),
+                                       $moduleName,
                                        $file,
                                );
                        }
                }
 
-               // Restore settings
-               $wgEnableJavaScriptTest = $live_wgEnableJavaScriptTest;
 
                return $cases;
        }