Merge "Simplify interlanguage links creation by early return"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 27 Nov 2013 13:19:26 +0000 (13:19 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 27 Nov 2013 13:19:26 +0000 (13:19 +0000)
63 files changed:
includes/AutoLoader.php
includes/DefaultSettings.php
includes/MimeMagic.php
includes/cache/LocalisationCache.php
includes/changes/EnhancedChangesList.php
includes/deferred/LinksUpdate.php
includes/externalstore/ExternalStore.php
includes/externalstore/ExternalStoreDB.php
includes/externalstore/ExternalStoreMedium.php
includes/externalstore/ExternalStoreMwstore.php
includes/filerepo/file/ArchivedFile.php
includes/filerepo/file/File.php
includes/interwiki/Interwiki.php
includes/job/Job.php
includes/job/JobQueue.php
includes/job/JobQueueDB.php
includes/job/JobQueueFederated.php
includes/job/JobQueueGroup.php
includes/job/JobQueueRedis.php
includes/job/aggregator/JobQueueAggregator.php
includes/job/aggregator/JobQueueAggregatorRedis.php
includes/job/jobs/DoubleRedirectJob.php
includes/job/jobs/DuplicateJob.php
includes/job/jobs/EnotifNotifyJob.php
includes/job/jobs/HTMLCacheUpdateJob.php
includes/job/jobs/NullJob.php
includes/job/jobs/RefreshLinksJob.php
includes/job/jobs/RefreshLinksJob2.php [new file with mode: 0644]
includes/job/jobs/UploadFromUrlJob.php
includes/job/utils/BacklinkJobUtils.php [new file with mode: 0644]
includes/resourceloader/ResourceLoaderUserGroupsModule.php
includes/resourceloader/ResourceLoaderUserModule.php
includes/resourceloader/ResourceLoaderUserOptionsModule.php
includes/resourceloader/ResourceLoaderUserTokensModule.php
languages/messages/MessagesAr.php
languages/messages/MessagesBn.php
languages/messages/MessagesBr.php
languages/messages/MessagesCs.php
languages/messages/MessagesDiq.php
languages/messages/MessagesEt.php
languages/messages/MessagesGl.php
languages/messages/MessagesHe.php
languages/messages/MessagesIa.php
languages/messages/MessagesIt.php
languages/messages/MessagesKk_cyrl.php
languages/messages/MessagesNl.php
languages/messages/MessagesPl.php
languages/messages/MessagesPt.php
languages/messages/MessagesQqq.php
languages/messages/MessagesSk.php
languages/messages/MessagesSr_ec.php
languages/messages/MessagesSr_el.php
languages/messages/MessagesTr.php
languages/messages/MessagesVep.php
languages/messages/MessagesZh_hans.php
maintenance/updateCollation.php
resources/Resources.php
resources/mediawiki.ui/components/default/buttons.less
resources/mediawiki.ui/mixins/forms.less
resources/mediawiki/mediawiki.js
tests/phpunit/includes/StatusTest.php
tests/phpunit/includes/jobqueue/RefreshLinksPartitionTest.php [new file with mode: 0644]
tests/phpunit/languages/LanguageTest.php

index 3c538b0..60ee2a8 100644 (file)
@@ -657,11 +657,14 @@ $wgAutoloadLocalClasses = array(
        'HTMLCacheUpdateJob' => 'includes/job/jobs/HTMLCacheUpdateJob.php',
        'NullJob' => 'includes/job/jobs/NullJob.php',
        'RefreshLinksJob' => 'includes/job/jobs/RefreshLinksJob.php',
-       'RefreshLinksJob2' => 'includes/job/jobs/RefreshLinksJob.php',
+       'RefreshLinksJob2' => 'includes/job/jobs/RefreshLinksJob2.php',
        'UploadFromUrlJob' => 'includes/job/jobs/UploadFromUrlJob.php',
        'AssembleUploadChunksJob' => 'includes/job/jobs/AssembleUploadChunksJob.php',
        'PublishStashedFileJob' => 'includes/job/jobs/PublishStashedFileJob.php',
 
+       # includes/job/utils
+       'BacklinkJobUtils' => 'includes/job/utils/BacklinkJobUtils.php',
+
        # includes/json
        'FormatJson' => 'includes/json/FormatJson.php',
 
index fc6fc65..951dc21 100644 (file)
@@ -6054,7 +6054,7 @@ $wgHooks = array();
  */
 $wgJobClasses = array(
        'refreshLinks' => 'RefreshLinksJob',
-       'refreshLinks2' => 'RefreshLinksJob2',
+       'refreshLinks2' => 'RefreshLinksJob2', // b/c
        'htmlCacheUpdate' => 'HTMLCacheUpdateJob',
        'sendMail' => 'EmaillingJob',
        'enotifNotify' => 'EnotifNotifyJob',
index 8220e92..f5c28ab 100644 (file)
@@ -167,7 +167,7 @@ class MimeMagic {
 
        /** The singleton instance
         */
-       private static $instance;
+       private static $instance = null;
 
        /** Initializes the MimeMagic object. This is called by MimeMagic::singleton().
         *
@@ -336,7 +336,7 @@ class MimeMagic {
         * Get an instance of this class
         * @return MimeMagic
         */
-       public static function &singleton() {
+       public static function singleton() {
                if ( self::$instance === null ) {
                        self::$instance = new MimeMagic;
                }
index 7d029bc..f02e0a1 100644 (file)
@@ -1193,18 +1193,26 @@ class LCStoreCDB implements LCStore {
                if ( !isset( $this->readers[$code] ) ) {
                        $fileName = $this->getFileName( $code );
 
-                       if ( !file_exists( $fileName ) ) {
-                               $this->readers[$code] = false;
-                       } else {
-                               $this->readers[$code] = CdbReader::open( $fileName );
+                       $this->readers[$code] = false;
+                       if ( file_exists( $fileName ) ) {
+                               try {
+                                       $this->readers[$code] = CdbReader::open( $fileName );
+                               } catch( CdbException $e ) {
+                                       wfDebug( __METHOD__ . ": unable to open cdb file for reading" );
+                               }
                        }
                }
 
                if ( !$this->readers[$code] ) {
                        return null;
                } else {
-                       $value = $this->readers[$code]->get( $key );
-
+                       $value = false;
+                       try {
+                               $value = $this->readers[$code]->get( $key );
+                       } catch ( CdbException $e ) {
+                               wfDebug( __METHOD__ . ": CdbException caught, error message was "
+                                       . $e->getMessage() );
+                       }
                        if ( $value === false ) {
                                return null;
                        }
@@ -1226,13 +1234,21 @@ class LCStoreCDB implements LCStore {
                        $this->readers[$code]->close();
                }
 
-               $this->writer = CdbWriter::open( $this->getFileName( $code ) );
+               try {
+                       $this->writer = CdbWriter::open( $this->getFileName( $code ) );
+               } catch ( CdbException $e ) {
+                       throw new MWException( $e->getMessage() );
+               }
                $this->currentLang = $code;
        }
 
        public function finishWrite() {
                // Close the writer
-               $this->writer->close();
+               try {
+                       $this->writer->close();
+               } catch ( CdbException $e ) {
+                       throw new MWException( $e->getMessage() );
+               }
                $this->writer = null;
                unset( $this->readers[$this->currentLang] );
                $this->currentLang = null;
@@ -1242,7 +1258,11 @@ class LCStoreCDB implements LCStore {
                if ( is_null( $this->writer ) ) {
                        throw new MWException( __CLASS__ . ': must call startWrite() before calling set()' );
                }
-               $this->writer->set( $key, serialize( $value ) );
+               try {
+                       $this->writer->set( $key, serialize( $value ) );
+               } catch ( CdbException $e ) {
+                       throw new MWException( $e->getMessage() );
+               }
        }
 
        protected function getFileName( $code ) {
index c8439be..1727da0 100644 (file)
@@ -57,14 +57,9 @@ class EnhancedChangesList extends ChangesList {
        public function recentChangesLine( &$baseRC, $watched = false ) {
                wfProfileIn( __METHOD__ );
 
-               # Create a specialised object
-               $cacheEntry = RCCacheEntry::newFromParent( $baseRC );
-
-               $curIdEq = array( 'curid' => $cacheEntry->mAttribs['rc_cur_id'] );
-
                # If it's a new day, add the headline and flush the cache
                $date = $this->getLanguage()->userDate(
-                       $cacheEntry->mAttribs['rc_timestamp'],
+                       $baseRC->mAttribs['rc_timestamp'],
                        $this->getUser()
                );
 
@@ -78,6 +73,11 @@ class EnhancedChangesList extends ChangesList {
                        $this->lastdate = $date;
                }
 
+               # Create a specialised object
+               $cacheEntry = RCCacheEntry::newFromParent( $baseRC );
+
+               $curIdEq = array( 'curid' => $cacheEntry->mAttribs['rc_cur_id'] );
+
                # Should patrol-related stuff be shown?
                $cacheEntry->unpatrolled = $this->showAsUnpatrolled( $cacheEntry );
 
index a3f180c..9cd7708 100644 (file)
@@ -246,10 +246,11 @@ class LinksUpdate extends SqlDataUpdate {
        public static function queueRecursiveJobsForTable( Title $title, $table ) {
                wfProfileIn( __METHOD__ );
                if ( $title->getBacklinkCache()->hasLinks( $table ) ) {
-                       $job = new RefreshLinksJob2(
+                       $job = new RefreshLinksJob(
                                $title,
                                array(
-                                       'table' => $table,
+                                       'table'     => $table,
+                                       'recursive' => true,
                                ) + Job::newRootJobParams( // "overall" refresh links job info
                                        "refreshlinks:{$table}:{$title->getPrefixedText()}"
                                )
index 462b0b9..9e9d976 100644 (file)
@@ -59,6 +59,7 @@ class ExternalStore {
                }
 
                $class = 'ExternalStore' . ucfirst( $proto );
+
                // Any custom modules should be added to $wgAutoLoadClasses for on-demand loading
                return class_exists( $class ) ? new $class( $params ) : false;
        }
@@ -120,6 +121,7 @@ class ExternalStore {
                                $retval[$url] = false;
                        }
                }
+
                return $retval;
        }
 
index 46a8937..8c2147a 100644 (file)
@@ -42,6 +42,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
                if ( $itemID !== false && $ret !== false ) {
                        return $ret->getItem( $itemID );
                }
+
                return $ret;
        }
 
@@ -66,6 +67,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
                $ret = array();
                foreach ( $batched as $cluster => $batchByCluster ) {
                        $res = $this->batchFetchBlobs( $cluster, $batchByCluster );
+                       /** @var HistoryBlob $blob */
                        foreach ( $res as $id => $blob ) {
                                foreach ( $batchByCluster[$id] as $itemID ) {
                                        $url = $inverseUrlMap[$cluster][$id][$itemID];
@@ -77,6 +79,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
                                }
                        }
                }
+
                return $ret;
        }
 
@@ -96,6 +99,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
                if ( $dbw->getFlag( DBO_TRX ) ) {
                        $dbw->commit( __METHOD__ );
                }
+
                return "DB://$cluster/$id";
        }
 
@@ -142,6 +146,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
        function &getMaster( $cluster ) {
                $wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
                $lb =& $this->getLoadBalancer( $cluster );
+
                return $lb->getConnection( DB_MASTER, array(), $wiki );
        }
 
@@ -156,6 +161,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
                if ( is_null( $table ) ) {
                        $table = 'blobs';
                }
+
                return $table;
        }
 
@@ -182,6 +188,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
                if ( isset( $externalBlobCache[$cacheID] ) ) {
                        wfDebugLog( 'ExternalStoreDB-cache',
                                "ExternalStoreDB::fetchBlob cache hit on $cacheID\n" );
+
                        return $externalBlobCache[$cacheID];
                }
 
@@ -209,6 +216,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
                }
 
                $externalBlobCache = array( $cacheID => &$ret );
+
                return $ret;
        }
 
@@ -217,7 +225,8 @@ class ExternalStoreDB extends ExternalStoreMedium {
         *
         * @param string $cluster A cluster name valid for use with LBFactory
         * @param array $ids A map from the blob_id's to look for to the requested itemIDs in the blobs
-        * @return array A map from the blob_id's requested to their content.  Unlocated ids are not represented
+        * @return array A map from the blob_id's requested to their content.
+        *   Unlocated ids are not represented
         */
        function batchFetchBlobs( $cluster, array $ids ) {
                $dbr = $this->getSlave( $cluster );
@@ -248,6 +257,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
                                " master on '$cluster' failed locating items: " .
                                implode( ',', array_keys( $ids ) ) . "\n" );
                }
+
                return $ret;
        }
 
@@ -274,6 +284,7 @@ class ExternalStoreDB extends ExternalStoreMedium {
 
        protected function parseURL( $url ) {
                $path = explode( '/', $url );
+
                return array(
                        $path[2], // cluster
                        $path[3], // id
index 6ab1f8c..a526df6 100644 (file)
@@ -64,6 +64,7 @@ abstract class ExternalStoreMedium {
                                $retval[$url] = $data;
                        }
                }
+
                return $retval;
        }
 
index 1721007..f329b73 100644 (file)
@@ -43,6 +43,7 @@ class ExternalStoreMwstore extends ExternalStoreMedium {
                        // backends should at least have "read-after-create" consistency.
                        return $be->getFileContents( array( 'src' => $url ) );
                }
+
                return false;
        }
 
@@ -66,6 +67,7 @@ class ExternalStoreMwstore extends ExternalStoreMedium {
                        $be = FileBackendGroup::singleton()->get( $backendName );
                        $blobs = $blobs + $be->getFileContentsMulti( array( 'srcs' => $paths ) );
                }
+
                return $blobs;
        }
 
@@ -90,6 +92,7 @@ class ExternalStoreMwstore extends ExternalStoreMedium {
                                return $url;
                        }
                }
+
                return false;
        }
 }
index 5d72c0c..91c2fb4 100644 (file)
  * @ingroup FileAbstraction
  */
 class ArchivedFile {
-       /**#@+
-        * @private
-        */
-       var $id, # filearchive row ID
-               $name, # image name
-               $group, # FileStore storage group
-               $key, # FileStore sha1 key
-               $size, # file dimensions
-               $bits, # size in bytes
-               $width, # width
-               $height, # height
-               $metadata, # metadata string
-               $mime, # mime type
-               $media_type, # media type
-               $description, # upload description
-               $user, # user ID of uploader
-               $user_text, # user name of uploader
-               $timestamp, # time of upload
-               $dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx)
-               $deleted, # Bitfield akin to rev_deleted
-               $sha1, # sha1 hash of file content
-               $pageCount,
-               $archive_name;
-
-       /**
-        * @var MediaHandler
-        */
-       var $handler;
-       /**
-        * @var Title
-        */
-       var $title; # image title
-
-       /**#@-*/
+       /** @var int filearchive row ID */
+       private $id;
+
+       /** @var string File name */
+       private $name;
+
+       /** @var string FileStore storage group */
+       private $group;
+
+       /** @var string FileStore SHA-1 key */
+       private $key;
+
+       /** @var int File size in bytes */
+       private $size;
+
+       /** @var int size in bytes */
+       private $bits;
+
+       /** @var int Width */
+       private $width;
+
+       /** @var int Height */
+       private $height;
+
+       /** @var string Metadata string */
+       private $metadata;
+
+       /** @var string MIME type */
+       private $mime;
+
+       /** @var string Media type */
+       private $media_type;
+
+       /** @var string Upload description */
+       private $description;
+
+       /** @var int User ID of uploader */
+       private $user;
+
+       /** @var string User name of uploader */
+       private $user_text;
+
+       /** @var string Time of upload */
+       private $timestamp;
+
+       /** @var bool Whether or not all this has been loaded from the database (loadFromXxx) */
+       private $dataLoaded;
+
+       /** @var int Bitfield akin to rev_deleted */
+       private $deleted;
+
+       /** @var string SHA-1 hash of file content */
+       private $sha1;
+
+       /** @var string Number of pages of a multipage document, or false for
+        * documents which aren't multipage documents
+        */
+       private $pageCount;
+
+       /** @var string Original base filename */
+       private $archive_name;
+
+       /** @var MediaHandler */
+       protected $handler;
+
+       /** @var Title */
+       protected $title; # image title
 
        /**
         * @throws MWException
index 85aeac0..cf4ddf5 100644 (file)
@@ -91,43 +91,62 @@ abstract class File {
         * The following member variables are not lazy-initialised
         */
 
-       /**
-        * @var FileRepo|bool
-        */
-       var $repo;
+       /** @var FileRepo|bool */
+       public $repo;
 
-       /**
-        * @var Title
-        */
-       var $title;
+       /** @var Title|string|bool */
+       protected $title;
 
-       var $lastError, $redirected, $redirectedTitle;
+       /** @var string Text of last error  */
+       protected $lastError;
 
-       /**
-        * @var FSFile|bool False if undefined
-        */
+       /** @var string Main part of the title, with underscores (Title::getDBkey)  */
+       protected $redirected;
+
+       /** @var Title */
+       protected $redirectedTitle;
+
+       /** @var FSFile|bool False if undefined */
        protected $fsFile;
 
-       /**
-        * @var MediaHandler
-        */
+       /** @var MediaHandler */
        protected $handler;
 
-       /**
-        * @var string
+       /** @var string The URL corresponding to one of the four basic zones */
+       protected $url;
+
+       /** @var string File extension */
+       protected $extension;
+
+       /** @var string The name of a file from its title object */
+       protected $name;
+
+       /** @var string The storage path corresponding to one of the zones */
+       protected $path;
+
+       /** @var string Relative path including trailing slash */
+       protected $hashPath;
+
+       /** @var string number of pages of a multipage document, or false for
+        *    documents which aren't multipage documents
         */
-       protected $url, $extension, $name, $path, $hashPath, $pageCount, $transformScript;
+       protected $pageCount;
 
+       /** @var string URL of transformscript (for example thumb.php) */
+       protected $transformScript;
+
+       /** @var Title */
        protected $redirectTitle;
 
-       /**
-        * @var bool
-        */
-       protected $canRender, $isSafeFile;
+       /** @var bool Wether the output of transform() for this file is likely to be valid. */
+       protected $canRender;
 
-       /**
-        * @var string Required Repository class type
+       /** @var bool Wether this media file is in a format that is unlikely to
+        *    contain viruses or malicious content
         */
+       protected $isSafeFile;
+
+       /** @var string Required Repository class type */
        protected $repoClass = 'FileRepo';
 
        /**
@@ -1933,7 +1952,7 @@ abstract class File {
        }
 
        /**
-        * @param  $from
+        * @param $from
         * @return void
         */
        function redirectedFrom( $from ) {
index 4003fa8..3c237bf 100644 (file)
@@ -123,28 +123,34 @@ class Interwiki {
                static $db, $site;
 
                wfDebug( __METHOD__ . "( $prefix )\n" );
-               if ( !$db ) {
-                       $db = CdbReader::open( $wgInterwikiCache );
-               }
-               /* Resolve site name */
-               if ( $wgInterwikiScopes >= 3 && !$site ) {
-                       $site = $db->get( '__sites:' . wfWikiID() );
-                       if ( $site == '' ) {
-                               $site = $wgInterwikiFallbackSite;
+               $value = false;
+               try {
+                       if ( !$db ) {
+                               $db = CdbReader::open( $wgInterwikiCache );
+                       }
+                       /* Resolve site name */
+                       if ( $wgInterwikiScopes >= 3 && !$site ) {
+                               $site = $db->get( '__sites:' . wfWikiID() );
+                               if ( $site == '' ) {
+                                       $site = $wgInterwikiFallbackSite;
+                               }
                        }
-               }
 
-               $value = $db->get( wfMemcKey( $prefix ) );
-               // Site level
-               if ( $value == '' && $wgInterwikiScopes >= 3 ) {
-                       $value = $db->get( "_{$site}:{$prefix}" );
-               }
-               // Global Level
-               if ( $value == '' && $wgInterwikiScopes >= 2 ) {
-                       $value = $db->get( "__global:{$prefix}" );
-               }
-               if ( $value == 'undef' ) {
-                       $value = '';
+                       $value = $db->get( wfMemcKey( $prefix ) );
+                       // Site level
+                       if ( $value == '' && $wgInterwikiScopes >= 3 ) {
+                               $value = $db->get( "_{$site}:{$prefix}" );
+                       }
+                       // Global Level
+                       if ( $value == '' && $wgInterwikiScopes >= 2 ) {
+                               $value = $db->get( "__global:{$prefix}" );
+                       }
+                       if ( $value == 'undef' ) {
+                               $value = '';
+                       }
+               } catch ( CdbException $e ) {
+                       wfDebug( __METHOD__ . ": CdbException caught, error message was "
+                               . $e->getMessage() );
                }
 
                return $value;
@@ -232,51 +238,55 @@ class Interwiki {
                static $db, $site;
 
                wfDebug( __METHOD__ . "()\n" );
-               if ( !$db ) {
-                       $db = CdbReader::open( $wgInterwikiCache );
-               }
-               /* Resolve site name */
-               if ( $wgInterwikiScopes >= 3 && !$site ) {
-                       $site = $db->get( '__sites:' . wfWikiID() );
-                       if ( $site == '' ) {
-                               $site = $wgInterwikiFallbackSite;
-                       }
-               }
-
-               // List of interwiki sources
-               $sources = array();
-               // Global Level
-               if ( $wgInterwikiScopes >= 2 ) {
-                       $sources[] = '__global';
-               }
-               // Site level
-               if ( $wgInterwikiScopes >= 3 ) {
-                       $sources[] = '_' . $site;
-               }
-               $sources[] = wfWikiID();
-
                $data = array();
-
-               foreach ( $sources as $source ) {
-                       $list = $db->get( "__list:{$source}" );
-                       foreach ( explode( ' ', $list ) as $iw_prefix ) {
-                               $row = $db->get( "{$source}:{$iw_prefix}" );
-                               if ( !$row ) {
-                                       continue;
+               try {
+                       if ( !$db ) {
+                               $db = CdbReader::open( $wgInterwikiCache );
+                       }
+                       /* Resolve site name */
+                       if ( $wgInterwikiScopes >= 3 && !$site ) {
+                               $site = $db->get( '__sites:' . wfWikiID() );
+                               if ( $site == '' ) {
+                                       $site = $wgInterwikiFallbackSite;
                                }
+                       }
 
-                               list( $iw_local, $iw_url ) = explode( ' ', $row );
-
-                               if ( $local !== null && $local != $iw_local ) {
-                                       continue;
+                       // List of interwiki sources
+                       $sources = array();
+                       // Global Level
+                       if ( $wgInterwikiScopes >= 2 ) {
+                               $sources[] = '__global';
+                       }
+                       // Site level
+                       if ( $wgInterwikiScopes >= 3 ) {
+                               $sources[] = '_' . $site;
+                       }
+                       $sources[] = wfWikiID();
+
+                       foreach ( $sources as $source ) {
+                               $list = $db->get( "__list:{$source}" );
+                               foreach ( explode( ' ', $list ) as $iw_prefix ) {
+                                       $row = $db->get( "{$source}:{$iw_prefix}" );
+                                       if ( !$row ) {
+                                               continue;
+                                       }
+
+                                       list( $iw_local, $iw_url ) = explode( ' ', $row );
+
+                                       if ( $local !== null && $local != $iw_local ) {
+                                               continue;
+                                       }
+
+                                       $data[$iw_prefix] = array(
+                                               'iw_prefix' => $iw_prefix,
+                                               'iw_url' => $iw_url,
+                                               'iw_local' => $iw_local,
+                                       );
                                }
-
-                               $data[$iw_prefix] = array(
-                                       'iw_prefix' => $iw_prefix,
-                                       'iw_url' => $iw_url,
-                                       'iw_local' => $iw_local,
-                               );
                        }
+               } catch ( CdbException $e ) {
+                       wfDebug( __METHOD__ . ": CdbException caught, error message was "
+                               . $e->getMessage() );
                }
 
                ksort( $data );
index b1400de..3f44a91 100644 (file)
@@ -55,7 +55,7 @@ abstract class Job {
 
        /**
         * Run the job
-        * @return boolean success
+        * @return bool Success
         */
        abstract public function run();
 
@@ -67,7 +67,7 @@ abstract class Job {
         * Create the appropriate object to handle a specific job
         *
         * @param string $command Job command
-        * @param $title Title: Associated title
+        * @param Title $title Associated title
         * @param array|bool $params Job parameters
         * @param int $id Job identifier
         * @throws MWException
@@ -130,7 +130,7 @@ abstract class Job {
         * Pop a job off the front of the queue.
         * This is subject to $wgJobTypesExcludedFromDefaultQueue.
         *
-        * @return Job or false if there's no jobs
+        * @return Job|bool False if there are no jobs
         * @deprecated since 1.21
         */
        public static function pop() {
@@ -153,11 +153,12 @@ abstract class Job {
                $this->params = $params;
                $this->id = $id;
 
-               $this->removeDuplicates = false; // expensive jobs may set this to true
+               // expensive jobs may set this to true
+               $this->removeDuplicates = false;
        }
 
        /**
-        * @return integer May be 0 for jobs stored outside the DB
+        * @return int May be 0 for jobs stored outside the DB
         * @deprecated since 1.22
         */
        public function getId() {
@@ -186,7 +187,7 @@ abstract class Job {
        }
 
        /**
-        * @return integer|null UNIX timestamp to delay running this job until, otherwise null
+        * @return int|null UNIX timestamp to delay running this job until, otherwise null
         * @since 1.22
         */
        public function getReleaseTimestamp() {
@@ -216,7 +217,7 @@ abstract class Job {
         * only checked if ignoreDuplicates() returns true, meaning that duplicate
         * jobs are supposed to be ignored.
         *
-        * @return Array Map of key/values
+        * @return array Map of key/values
         * @since 1.21
         */
        public function getDeduplicationInfo() {
@@ -240,7 +241,7 @@ abstract class Job {
        /**
         * @see JobQueue::deduplicateRootJob()
         * @param string $key A key that identifies the task
-        * @return Array
+        * @return array
         * @since 1.21
         */
        public static function newRootJobParams( $key ) {
@@ -252,7 +253,7 @@ abstract class Job {
 
        /**
         * @see JobQueue::deduplicateRootJob()
-        * @return Array
+        * @return array
         * @since 1.21
         */
        public function getRootJobParams() {
index b8a612c..8d93dc0 100644 (file)
  * @since 1.21
  */
 abstract class JobQueue {
-       protected $wiki; // string; wiki ID
-       protected $type; // string; job type
-       protected $order; // string; job priority for pop()
-       protected $claimTTL; // integer; seconds
-       protected $maxTries; // integer; maximum number of times to try a job
-       protected $checkDelay; // boolean; allow delayed jobs
+       /** @var string Wiki ID */
+       protected $wiki;
+
+       /** @var string Job type */
+       protected $type;
+
+       /** @var string Job priority for pop() */
+       protected $order;
+
+       /** @var int Time to live in seconds */
+       protected $claimTTL;
+
+       /** @var int Maximum number of times to try a job */
+       protected $maxTries;
+
+       /** @var bool Allow delayed jobs */
+       protected $checkDelay;
 
        /** @var BagOStuff */
        protected $dupCache;
@@ -44,7 +55,8 @@ abstract class JobQueue {
        const ROOTJOB_TTL = 2419200; // integer; seconds to remember root jobs (28 days)
 
        /**
-        * @param $params array
+        * @param array $params
+        * @throws MWException
         */
        protected function __construct( array $params ) {
                $this->wiki = $params['wiki'];
@@ -93,7 +105,7 @@ abstract class JobQueue {
         *
         * Queue classes should throw an exception if they do not support the options given.
         *
-        * @param $params array
+        * @param array $params
         * @return JobQueue
         * @throws MWException
         */
@@ -142,7 +154,7 @@ abstract class JobQueue {
        /**
         * Get the allowed queue orders for configuration validation
         *
-        * @return Array Subset of (random, timestamp, fifo, undefined)
+        * @return array Subset of (random, timestamp, fifo, undefined)
         */
        abstract protected function supportedOrders();
 
@@ -156,7 +168,7 @@ abstract class JobQueue {
        /**
         * Find out if delayed jobs are supported for configuration validation
         *
-        * @return boolean Whether delayed jobs are supported
+        * @return bool Whether delayed jobs are supported
         */
        protected function supportsDelayedJobs() {
                return false; // not implemented
@@ -194,7 +206,7 @@ abstract class JobQueue {
         *
         * If caching is used, this number might be out of date for a minute.
         *
-        * @return integer
+        * @return int
         * @throws JobQueueError
         */
        final public function getSize() {
@@ -207,7 +219,7 @@ abstract class JobQueue {
 
        /**
         * @see JobQueue::getSize()
-        * @return integer
+        * @return int
         */
        abstract protected function doGetSize();
 
@@ -217,7 +229,7 @@ abstract class JobQueue {
         *
         * If caching is used, this number might be out of date for a minute.
         *
-        * @return integer
+        * @return int
         * @throws JobQueueError
         */
        final public function getAcquiredCount() {
@@ -230,7 +242,7 @@ abstract class JobQueue {
 
        /**
         * @see JobQueue::getAcquiredCount()
-        * @return integer
+        * @return int
         */
        abstract protected function doGetAcquiredCount();
 
@@ -240,7 +252,7 @@ abstract class JobQueue {
         *
         * If caching is used, this number might be out of date for a minute.
         *
-        * @return integer
+        * @return int
         * @throws JobQueueError
         * @since 1.22
         */
@@ -254,7 +266,7 @@ abstract class JobQueue {
 
        /**
         * @see JobQueue::getDelayedCount()
-        * @return integer
+        * @return int
         */
        protected function doGetDelayedCount() {
                return 0; // not implemented
@@ -266,7 +278,7 @@ abstract class JobQueue {
         *
         * If caching is used, this number might be out of date for a minute.
         *
-        * @return integer
+        * @return int
         * @throws JobQueueError
         */
        final public function getAbandonedCount() {
@@ -279,7 +291,7 @@ abstract class JobQueue {
 
        /**
         * @see JobQueue::getAbandonedCount()
-        * @return integer
+        * @return int
         */
        protected function doGetAbandonedCount() {
                return 0; // not implemented
@@ -290,8 +302,8 @@ abstract class JobQueue {
         * This does not require $wgJobClasses to be set for the given job type.
         * Outside callers should use JobQueueGroup::push() instead of this function.
         *
-        * @param $jobs Job|Array
-        * @param $flags integer Bitfield (supports JobQueue::QOS_ATOMIC)
+        * @param Job|array $jobs A single job or an array of Jobs
+        * @param int $flags Bitfield (supports JobQueue::QOS_ATOMIC)
         * @return bool Returns false on failure
         * @throws JobQueueError
         */
@@ -305,9 +317,9 @@ abstract class JobQueue {
         * Outside callers should use JobQueueGroup::push() instead of this function.
         *
         * @param array $jobs List of Jobs
-        * @param $flags integer Bitfield (supports JobQueue::QOS_ATOMIC)
+        * @param int $flags Bitfield (supports JobQueue::QOS_ATOMIC)
+        * @throws MWException
         * @return bool Returns false on failure
-        * @throws JobQueueError
         */
        final public function batchPush( array $jobs, $flags = 0 ) {
                if ( !count( $jobs ) ) {
@@ -333,6 +345,8 @@ abstract class JobQueue {
 
        /**
         * @see JobQueue::batchPush()
+        * @param array $jobs
+        * @param $flags
         * @return bool
         */
        abstract protected function doBatchPush( array $jobs, $flags );
@@ -342,8 +356,8 @@ abstract class JobQueue {
         * This requires $wgJobClasses to be set for the given job type.
         * Outside callers should use JobQueueGroup::pop() instead of this function.
         *
+        * @throws MWException
         * @return Job|bool Returns false if there are no jobs
-        * @throws JobQueueError
         */
        final public function pop() {
                global $wgJobClasses;
@@ -384,9 +398,9 @@ abstract class JobQueue {
         * This does nothing for certain queue classes or if "claimTTL" is not set.
         * Outside callers should use JobQueueGroup::ack() instead of this function.
         *
-        * @param $job Job
+        * @param Job $job
+        * @throws MWException
         * @return bool
-        * @throws JobQueueError
         */
        final public function ack( Job $job ) {
                if ( $job->getType() !== $this->type ) {
@@ -401,6 +415,7 @@ abstract class JobQueue {
 
        /**
         * @see JobQueue::ack()
+        * @param Job $job
         * @return bool
         */
        abstract protected function doAck( Job $job );
@@ -432,9 +447,9 @@ abstract class JobQueue {
         *
         * This does nothing for certain queue classes.
         *
-        * @param $job Job
+        * @param Job $job
+        * @throws MWException
         * @return bool
-        * @throws JobQueueError
         */
        final public function deduplicateRootJob( Job $job ) {
                if ( $job->getType() !== $this->type ) {
@@ -449,7 +464,8 @@ abstract class JobQueue {
 
        /**
         * @see JobQueue::deduplicateRootJob()
-        * @param $job Job
+        * @param Job $job
+        * @throws MWException
         * @return bool
         */
        protected function doDeduplicateRootJob( Job $job ) {
@@ -476,9 +492,9 @@ abstract class JobQueue {
        /**
         * Check if the "root" job of a given job has been superseded by a newer one
         *
-        * @param $job Job
+        * @param Job $job
+        * @throws MWException
         * @return bool
-        * @throws JobQueueError
         */
        final protected function isRootJobOldDuplicate( Job $job ) {
                if ( $job->getType() !== $this->type ) {
@@ -537,6 +553,7 @@ abstract class JobQueue {
 
        /**
         * @see JobQueue::delete()
+        * @throws MWException
         * @return bool Success
         */
        protected function doDelete() {
@@ -574,7 +591,7 @@ abstract class JobQueue {
         *   - callback : a PHP callable that performs the task
         *   - period   : the period in seconds corresponding to the task frequency
         *
-        * @return Array
+        * @return array
         */
        final public function getPeriodicTasks() {
                $tasks = $this->doGetPeriodicTasks();
@@ -587,7 +604,7 @@ abstract class JobQueue {
 
        /**
         * @see JobQueue::getPeriodicTasks()
-        * @return Array
+        * @return array
         */
        protected function doGetPeriodicTasks() {
                return array();
@@ -697,7 +714,7 @@ abstract class JobQueue {
         *
         * @param string $key Event type
         * @param string $type Job type
-        * @param integer $delta
+        * @param int $delta
         * @since 1.22
         */
        public static function incrStats( $key, $type, $delta = 1 ) {
@@ -708,7 +725,7 @@ abstract class JobQueue {
        /**
         * Namespace the queue with a key to isolate it for testing
         *
-        * @param $key string
+        * @param string $key
         * @return void
         * @throws MWException
         */
index 59d3de1..b795695 100644 (file)
@@ -37,7 +37,8 @@ class JobQueueDB extends JobQueue {
        /** @var BagOStuff */
        protected $cache;
 
-       protected $cluster = false; // string; name of an external DB cluster
+       /** @var bool|string Name of an external DB cluster. False if not set */
+       protected $cluster = false;
 
        /**
         * Additional parameters include:
@@ -45,7 +46,7 @@ class JobQueueDB extends JobQueue {
         *               If not specified, the primary DB cluster for the wiki will be used.
         *               This can be overridden with a custom cluster so that DB handles will
         *               be retrieved via LBFactory::getExternalLB() and getConnection().
-        * @param $params array
+        * @param array $params
         */
        protected function __construct( array $params ) {
                global $wgMemc;
@@ -94,7 +95,7 @@ class JobQueueDB extends JobQueue {
 
        /**
         * @see JobQueue::doGetSize()
-        * @return integer
+        * @return int
         */
        protected function doGetSize() {
                $key = $this->getCacheKey( 'size' );
@@ -120,7 +121,7 @@ class JobQueueDB extends JobQueue {
 
        /**
         * @see JobQueue::doGetAcquiredCount()
-        * @return integer
+        * @return int
         */
        protected function doGetAcquiredCount() {
                if ( $this->claimTTL <= 0 ) {
@@ -150,7 +151,7 @@ class JobQueueDB extends JobQueue {
 
        /**
         * @see JobQueue::doGetAbandonedCount()
-        * @return integer
+        * @return int
         * @throws MWException
         */
        protected function doGetAbandonedCount() {
@@ -209,12 +210,12 @@ class JobQueueDB extends JobQueue {
        /**
         * This function should *not* be called outside of JobQueueDB
         *
-        * @param DatabaseBase $dbw
+        * @param IDatabase $dbw
         * @param array $jobs
         * @param int $flags
         * @param string $method
-        * @return boolean
-        * @throws type
+        * @throws DBError
+        * @return bool
         */
        public function doBatchPushInternal( IDatabase $dbw, array $jobs, $flags, $method ) {
                if ( !count( $jobs ) ) {
@@ -258,8 +259,11 @@ class JobQueueDB extends JobQueue {
                                $dbw->insert( 'job', $rowBatch, $method );
                        }
                        JobQueue::incrStats( 'job-insert', $this->type, count( $rows ) );
-                       JobQueue::incrStats( 'job-insert-duplicate', $this->type,
-                               count( $rowSet ) + count( $rowList ) - count( $rows ) );
+                       JobQueue::incrStats(
+                               'job-insert-duplicate',
+                               $this->type,
+                               count( $rowSet ) + count( $rowList ) - count( $rows )
+                       );
                } catch ( DBError $e ) {
                        if ( $flags & self::QOS_ATOMIC ) {
                                $dbw->rollback( $method );
@@ -336,7 +340,7 @@ class JobQueueDB extends JobQueue {
         * @param string $uuid 32 char hex string
         * @param $rand integer Random unsigned integer (31 bits)
         * @param bool $gte Search for job_random >= $random (otherwise job_random <= $random)
-        * @return Row|false
+        * @return stdClass|bool Row|false
         */
        protected function claimRandom( $uuid, $rand, $gte ) {
                $dbw = $this->getMasterDB();
@@ -386,6 +390,7 @@ class JobQueueDB extends JobQueue {
                                        continue; // use job_random
                                }
                        }
+
                        if ( $row ) { // claim the job
                                $dbw->update( 'job', // update by PK
                                        array(
@@ -412,7 +417,7 @@ class JobQueueDB extends JobQueue {
         * Reserve a row with a single UPDATE without holding row locks over RTTs...
         *
         * @param string $uuid 32 char hex string
-        * @return Row|false
+        * @return stdClass|bool Row|false
         */
        protected function claimOldest( $uuid ) {
                $dbw = $this->getMasterDB();
@@ -557,7 +562,7 @@ class JobQueueDB extends JobQueue {
        }
 
        /**
-        * @return Array
+        * @return array
         */
        protected function doGetPeriodicTasks() {
                return array(
@@ -639,7 +644,7 @@ class JobQueueDB extends JobQueue {
        /**
         * Recycle or destroy any jobs that have been claimed for too long
         *
-        * @return integer Number of jobs recycled/deleted
+        * @return int Number of jobs recycled/deleted
         */
        public function recycleAndDeleteStaleJobs() {
                $now = time();
@@ -721,7 +726,7 @@ class JobQueueDB extends JobQueue {
        }
 
        /**
-        * @param $job Job
+        * @param Job $job
         * @return array
         */
        protected function insertFields( Job $job ) {
@@ -745,6 +750,7 @@ class JobQueueDB extends JobQueue {
        }
 
        /**
+        * @throws JobQueueConnectionError
         * @return DBConnRef
         */
        protected function getSlaveDB() {
@@ -756,6 +762,7 @@ class JobQueueDB extends JobQueue {
        }
 
        /**
+        * @throws JobQueueConnectionError
         * @return DBConnRef
         */
        protected function getMasterDB() {
@@ -779,6 +786,7 @@ class JobQueueDB extends JobQueue {
        }
 
        /**
+        * @param $property
         * @return string
         */
        private function getCacheKey( $property ) {
index f598d29..589bed6 100644 (file)
  * @since 1.22
  */
 class JobQueueFederated extends JobQueue {
-       /** @var Array (partition name => weight) reverse sorted by weight */
+       /** @var array (partition name => weight) reverse sorted by weight */
        protected $partitionMap = array();
 
-       /** @var Array (partition name => JobQueue) reverse sorted by weight */
+       /** @var array (partition name => JobQueue) reverse sorted by weight */
        protected $partitionQueues = array();
 
        /** @var HashRing */
@@ -59,7 +59,8 @@ class JobQueueFederated extends JobQueue {
        /** @var BagOStuff */
        protected $cache;
 
-       protected $maxPartitionsTry; // integer; maximum number of partitions to try
+       /** @var int Maximum number of partitions to try */
+       protected $maxPartitionsTry;
 
        const CACHE_TTL_SHORT = 30; // integer; seconds to cache info without re-validating
        const CACHE_TTL_LONG = 300; // integer; seconds to cache info that is kept up to date
@@ -82,6 +83,7 @@ class JobQueueFederated extends JobQueue {
         *                          during failure, at the cost of added latency and somewhat
         *                          less reliable job de-duplication mechanisms.
         * @param array $params
+        * @throws MWException
         */
        protected function __construct( array $params ) {
                parent::__construct( $params );
@@ -184,7 +186,7 @@ class JobQueueFederated extends JobQueue {
        /**
         * @param string $type
         * @param string $method
-        * @return integer
+        * @return int
         */
        protected function getCrossPartitionSum( $type, $method ) {
                $key = $this->getCacheKey( $type );
@@ -228,7 +230,8 @@ class JobQueueFederated extends JobQueue {
        /**
         * @param array $jobs
         * @param HashRing $partitionRing
-        * @param integer $flags
+        * @param int $flags
+        * @throws JobQueueError
         * @return array List of Job object that could not be inserted
         */
        protected function tryJobInsertions( array $jobs, HashRing &$partitionRing, $flags ) {
@@ -238,6 +241,7 @@ class JobQueueFederated extends JobQueue {
                // to use a consistent hash to avoid allowing duplicate jobs per partition.
                // When inserting a batch of de-duplicated jobs, QOS_ATOMIC is disregarded.
                $uJobsByPartition = array(); // (partition name => job list)
+               /** @var Job $job */
                foreach ( $jobs as $key => $job ) {
                        if ( $job->ignoreDuplicates() ) {
                                $sha1 = sha1( serialize( $job->getDeduplicationInfo() ) );
@@ -257,6 +261,7 @@ class JobQueueFederated extends JobQueue {
 
                // Insert the de-duplicated jobs into the queues...
                foreach ( $uJobsByPartition as $partition => $jobBatch ) {
+                       /** @var JobQueue $queue */
                        $queue = $this->partitionQueues[$partition];
                        try {
                                $ok = $queue->doBatchPush( $jobBatch, $flags | self::QOS_ATOMIC );
@@ -316,6 +321,8 @@ class JobQueueFederated extends JobQueue {
                        if ( $partition === false ) {
                                break; // all partitions at 0 weight
                        }
+
+                       /** @var JobQueue $queue */
                        $queue = $this->partitionQueues[$partition];
                        try {
                                $job = $queue->pop();
@@ -374,6 +381,7 @@ class JobQueueFederated extends JobQueue {
        }
 
        protected function doDelete() {
+               /** @var JobQueue $queue */
                foreach ( $this->partitionQueues as $queue ) {
                        try {
                                $queue->doDelete();
@@ -384,6 +392,7 @@ class JobQueueFederated extends JobQueue {
        }
 
        protected function doWaitForBackups() {
+               /** @var JobQueue $queue */
                foreach ( $this->partitionQueues as $queue ) {
                        try {
                                $queue->waitForBackups();
@@ -395,6 +404,7 @@ class JobQueueFederated extends JobQueue {
 
        protected function doGetPeriodicTasks() {
                $tasks = array();
+               /** @var JobQueue $queue */
                foreach ( $this->partitionQueues as $partition => $queue ) {
                        foreach ( $queue->getPeriodicTasks() as $task => $def ) {
                                $tasks["{$partition}:{$task}"] = $def;
@@ -412,9 +422,12 @@ class JobQueueFederated extends JobQueue {
                        'delayedcount',
                        'abandonedcount'
                );
+
                foreach ( $types as $type ) {
                        $this->cache->delete( $this->getCacheKey( $type ) );
                }
+
+               /** @var JobQueue $queue */
                foreach ( $this->partitionQueues as $queue ) {
                        $queue->doFlushCaches();
                }
@@ -422,6 +435,8 @@ class JobQueueFederated extends JobQueue {
 
        public function getAllQueuedJobs() {
                $iterator = new AppendIterator();
+
+               /** @var JobQueue $queue */
                foreach ( $this->partitionQueues as $queue ) {
                        $iterator->append( $queue->getAllQueuedJobs() );
                }
@@ -431,6 +446,8 @@ class JobQueueFederated extends JobQueue {
 
        public function getAllDelayedJobs() {
                $iterator = new AppendIterator();
+
+               /** @var JobQueue $queue */
                foreach ( $this->partitionQueues as $queue ) {
                        $iterator->append( $queue->getAllDelayedJobs() );
                }
@@ -445,6 +462,8 @@ class JobQueueFederated extends JobQueue {
 
        protected function doGetSiblingQueuesWithJobs( array $types ) {
                $result = array();
+
+               /** @var JobQueue $queue */
                foreach ( $this->partitionQueues as $queue ) {
                        try {
                                $nonEmpty = $queue->doGetSiblingQueuesWithJobs( $types );
@@ -466,6 +485,8 @@ class JobQueueFederated extends JobQueue {
 
        protected function doGetSiblingQueueSizes( array $types ) {
                $result = array();
+
+               /** @var JobQueue $queue */
                foreach ( $this->partitionQueues as $queue ) {
                        try {
                                $sizes = $queue->doGetSiblingQueueSizes( $types );
@@ -485,12 +506,14 @@ class JobQueueFederated extends JobQueue {
        }
 
        public function setTestingPrefix( $key ) {
+               /** @var JobQueue $queue */
                foreach ( $this->partitionQueues as $queue ) {
                        $queue->setTestingPrefix( $key );
                }
        }
 
        /**
+        * @param $property
         * @return string
         */
        private function getCacheKey( $property ) {
index 70820d5..a3ec8a7 100644 (file)
  * @since 1.21
  */
 class JobQueueGroup {
-       /** @var Array */
+       /** @var array */
        protected static $instances = array();
 
        /** @var ProcessCacheLRU */
        protected $cache;
 
-       protected $wiki; // string; wiki ID
+       /** @var string Wiki ID */
+       protected $wiki;
 
        /** @var array Map of (bucket => (queue => JobQueue, types => list of types) */
        protected $coalescedQueues;
@@ -58,7 +59,7 @@ class JobQueueGroup {
        }
 
        /**
-        * @param string $wiki Wiki ID
+        * @param bool|string $wiki Wiki ID
         * @return JobQueueGroup
         */
        public static function singleton( $wiki = false ) {
@@ -82,7 +83,7 @@ class JobQueueGroup {
        /**
         * Get the job queue object for a given queue type
         *
-        * @param $type string
+        * @param string $type
         * @return JobQueue
         */
        public function get( $type ) {
@@ -104,12 +105,15 @@ class JobQueueGroup {
         * This inserts the jobs into the queue specified by $wgJobTypeConf
         * and updates the aggregate job queue information cache as needed.
         *
-        * @param $jobs Job|array A single Job or a list of Jobs
+        * @param Job|array $jobs A single Job or a list of Jobs
         * @throws MWException
         * @return bool
         */
        public function push( $jobs ) {
                $jobs = is_array( $jobs ) ? $jobs : array( $jobs );
+               if ( !count( $jobs ) ) {
+                       return true;
+               }
 
                $jobsByType = array(); // (job type => list of jobs)
                foreach ( $jobs as $job ) {
@@ -145,8 +149,8 @@ class JobQueueGroup {
         * This pops a job off a queue as specified by $wgJobTypeConf and
         * updates the aggregate job queue information cache as needed.
         *
-        * @param $qtype integer|string JobQueueGroup::TYPE_DEFAULT or type string
-        * @param $flags integer Bitfield of JobQueueGroup::USE_* constants
+        * @param int|string $qtype JobQueueGroup::TYPE_DEFAULT or type string
+        * @param int $flags Bitfield of JobQueueGroup::USE_* constants
         * @return Job|bool Returns false on failure
         */
        public function pop( $qtype = self::TYPE_DEFAULT, $flags = 0 ) {
@@ -195,7 +199,7 @@ class JobQueueGroup {
        /**
         * Acknowledge that a job was completed
         *
-        * @param $job Job
+        * @param Job $job
         * @return bool
         */
        public function ack( Job $job ) {
@@ -206,7 +210,7 @@ class JobQueueGroup {
         * Register the "root job" of a given job into the queue for de-duplication.
         * This should only be called right *after* all the new jobs have been inserted.
         *
-        * @param $job Job
+        * @param Job $job
         * @return bool
         */
        public function deduplicateRootJob( Job $job ) {
@@ -255,7 +259,7 @@ class JobQueueGroup {
        /**
         * Get the list of job types that have non-empty queues
         *
-        * @return Array List of job types that have non-empty queues
+        * @return array List of job types that have non-empty queues
         */
        public function getQueuesWithJobs() {
                $types = array();
@@ -278,7 +282,7 @@ class JobQueueGroup {
        /**
         * Get the size of the queus for a list of job types
         *
-        * @return Array Map of (job type => size)
+        * @return array Map of (job type => size)
         */
        public function getQueueSizes() {
                $sizeMap = array();
@@ -331,7 +335,7 @@ class JobQueueGroup {
         * This is only used for performance, such as to avoid spamming
         * the queue with many sub-jobs before they actually get run.
         *
-        * @param $type string
+        * @param string $type
         * @return bool
         */
        public function isQueueDeprioritized( $type ) {
@@ -339,9 +343,11 @@ class JobQueueGroup {
                        return $this->cache->get( 'isDeprioritized', $type );
                }
                if ( $type === 'refreshLinks2' ) {
-                       // Don't keep converting refreshLinks2 => refreshLinks jobs if the
+                       // Don't keep converting refreshLinksPartition => refreshLinks jobs if the
                        // later jobs have not been done yet. This helps throttle queue spam.
-                       $deprioritized = !$this->get( 'refreshLinks' )->isEmpty();
+                       // @TODO: this is mostly a WMF-specific hack and should be removed when
+                       // refreshLinks2 jobs are drained.
+                       $deprioritized = !$this->get( 'refreshLinks' )->getSize() > 10000;
                        $this->cache->set( 'isDeprioritized', $type, $deprioritized );
 
                        return $deprioritized;
@@ -357,7 +363,7 @@ class JobQueueGroup {
         * the defined run period. Concurrent calls to this function will cause tasks
         * to be attempted twice, so they may need their own methods of mutual exclusion.
         *
-        * @return integer Number of tasks run
+        * @return int Number of tasks run
         */
        public function executeReadyPeriodicTasks() {
                global $wgMemc;
index 3bed28e..e8c475d 100644 (file)
@@ -60,12 +60,16 @@ class JobQueueRedis extends JobQueue {
        /** @var RedisConnectionPool */
        protected $redisPool;
 
-       protected $server; // string; server address
-       protected $compression; // string; compression method to use
+       /** @var string Server address */
+       protected $server;
+
+       /** @var string Compression method to use */
+       protected $compression;
 
        const MAX_AGE_PRUNE = 604800; // integer; seconds a job can live once claimed (7 days)
 
-       protected $key; // string; key to prefix the queue keys with (used for testing)
+       /** @var string Key to prefix the queue keys with (used for testing) */
+       protected $key;
 
        /**
         * @params include:
@@ -108,7 +112,7 @@ class JobQueueRedis extends JobQueue {
 
        /**
         * @see JobQueue::doGetSize()
-        * @return integer
+        * @return int
         * @throws MWException
         */
        protected function doGetSize() {
@@ -122,8 +126,8 @@ class JobQueueRedis extends JobQueue {
 
        /**
         * @see JobQueue::doGetAcquiredCount()
-        * @return integer
-        * @throws MWException
+        * @return int
+        * @throws JobQueueError
         */
        protected function doGetAcquiredCount() {
                if ( $this->claimTTL <= 0 ) {
@@ -143,8 +147,8 @@ class JobQueueRedis extends JobQueue {
 
        /**
         * @see JobQueue::doGetDelayedCount()
-        * @return integer
-        * @throws MWException
+        * @return int
+        * @throws JobQueueError
         */
        protected function doGetDelayedCount() {
                if ( !$this->checkDelay ) {
@@ -160,8 +164,8 @@ class JobQueueRedis extends JobQueue {
 
        /**
         * @see JobQueue::doGetAbandonedCount()
-        * @return integer
-        * @throws MWException
+        * @return int
+        * @throws JobQueueError
         */
        protected function doGetAbandonedCount() {
                if ( $this->claimTTL <= 0 ) {
@@ -180,7 +184,7 @@ class JobQueueRedis extends JobQueue {
         * @param array $jobs
         * @param $flags
         * @return bool
-        * @throws MWException
+        * @throws JobQueueError
         */
        protected function doBatchPush( array $jobs, $flags ) {
                // Convert the jobs into field maps (de-duplicated against each other)
@@ -234,7 +238,7 @@ class JobQueueRedis extends JobQueue {
        /**
         * @param RedisConnRef $conn
         * @param array $items List of results from JobQueueRedis::getNewJobFields()
-        * @return integer Number of jobs inserted (duplicates are ignored)
+        * @return int Number of jobs inserted (duplicates are ignored)
         * @throws RedisException
         */
        protected function pushBlobs( RedisConnRef $conn, array $items ) {
@@ -287,7 +291,7 @@ LUA;
        /**
         * @see JobQueue::doPop()
         * @return Job|bool
-        * @throws MWException
+        * @throws JobQueueError
         */
        protected function doPop() {
                $job = false;
@@ -401,7 +405,7 @@ LUA;
         * @see JobQueue::doAck()
         * @param Job $job
         * @return Job|bool
-        * @throws MWException
+        * @throws MWException|JobQueueError
         */
        protected function doAck( Job $job ) {
                if ( !isset( $job->metadata['uuid'] ) ) {
@@ -445,7 +449,7 @@ LUA;
         * @see JobQueue::doDeduplicateRootJob()
         * @param Job $job
         * @return bool
-        * @throws MWException
+        * @throws MWException|JobQueueError
         */
        protected function doDeduplicateRootJob( Job $job ) {
                if ( !$job->hasRootJobParams() ) {
@@ -473,6 +477,7 @@ LUA;
         * @see JobQueue::doIsRootJobOldDuplicate()
         * @param Job $job
         * @return bool
+        * @throws JobQueueError
         */
        protected function doIsRootJobOldDuplicate( Job $job ) {
                if ( !$job->hasRootJobParams() ) {
@@ -495,6 +500,7 @@ LUA;
        /**
         * @see JobQueue::doDelete()
         * @return bool
+        * @throws JobQueueError
         */
        protected function doDelete() {
                static $props = array( 'l-unclaimed', 'z-claimed', 'z-abandoned',
@@ -595,7 +601,7 @@ LUA;
         * @param $uid string
         * @param $conn RedisConnRef
         * @return Job|bool Returns false if the job does not exist
-        * @throws MWException
+        * @throws MWException|JobQueueError
         */
        public function getJobFromUidInternal( $uid, RedisConnRef $conn ) {
                try {
@@ -620,8 +626,8 @@ LUA;
        /**
         * Release any ready delayed jobs into the queue
         *
-        * @return integer Number of jobs released
-        * @throws MWException
+        * @return int Number of jobs released
+        * @throws JobQueueError
         */
        public function releaseReadyDelayedJobs() {
                $count = 0;
@@ -657,8 +663,8 @@ LUA;
        /**
         * Recycle or destroy any jobs that have been claimed for too long
         *
-        * @return integer Number of jobs recycled/deleted
-        * @throws MWException
+        * @return int Number of jobs recycled/deleted
+        * @throws MWException|JobQueueError
         */
        public function recycleAndDeleteStaleJobs() {
                if ( $this->claimTTL <= 0 ) { // sanity
@@ -732,7 +738,7 @@ LUA;
        }
 
        /**
-        * @return Array
+        * @return array
         */
        protected function doGetPeriodicTasks() {
                $tasks = array();
@@ -753,7 +759,7 @@ LUA;
        }
 
        /**
-        * @param $job Job
+        * @param Job $job
         * @return array
         */
        protected function getNewJobFields( Job $job ) {
@@ -829,8 +835,8 @@ LUA;
        /**
         * Get a connection to the server that handles all sub-queues for this queue
         *
-        * @return Array (server name, Redis instance)
-        * @throws MWException
+        * @return RedisConnRef
+        * @throws JobQueueConnectionError
         */
        protected function getConnection() {
                $conn = $this->redisPool->getConnection( $this->server );
@@ -845,7 +851,7 @@ LUA;
         * @param $server string
         * @param $conn RedisConnRef
         * @param $e RedisException
-        * @throws MWException
+        * @throws JobQueueError
         */
        protected function throwRedisException( $server, RedisConnRef $conn, $e ) {
                $this->redisPool->handleException( $server, $conn, $e );
index 142b162..8600eed 100644 (file)
@@ -38,6 +38,7 @@ abstract class JobQueueAggregator {
        }
 
        /**
+        * @throws MWException
         * @return JobQueueAggregator
         */
        final public static function singleton() {
@@ -107,7 +108,7 @@ abstract class JobQueueAggregator {
        /**
         * Get the list of all of the queues with jobs
         *
-        * @return Array (job type => (list of wiki IDs))
+        * @return array (job type => (list of wiki IDs))
         */
        final public function getAllReadyWikiQueues() {
                wfProfileIn( __METHOD__ );
@@ -144,7 +145,7 @@ abstract class JobQueueAggregator {
         * Get all databases that have a pending job.
         * This poll all the queues and is this expensive.
         *
-        * @return Array (job type => (list of wiki IDs))
+        * @return array (job type => (list of wiki IDs))
         */
        protected function findPendingWikiQueues() {
                global $wgLocalDatabases;
index ca8975a..057a587 100644 (file)
@@ -32,7 +32,7 @@ class JobQueueAggregatorRedis extends JobQueueAggregator {
        /** @var RedisConnectionPool */
        protected $redisPool;
 
-       /** @var Array List of Redis server addresses */
+       /** @var array List of Redis server addresses */
        protected $servers;
 
        /**
index 24b5521..f5f0d63 100644 (file)
@@ -81,6 +81,11 @@ class DoubleRedirectJob extends Job {
                JobQueueGroup::singleton()->push( $jobs );
        }
 
+       /**
+        * @param Title $title
+        * @param array|bool $params
+        * @param int $id
+        */
        function __construct( $title, $params = false, $id = 0 ) {
                parent::__construct( 'fixDoubleRedirect', $title, $params, $id );
                $this->reason = $params['reason'];
index b3f3371..7e5bd3c 100644 (file)
@@ -30,7 +30,7 @@ final class DuplicateJob extends Job {
        /**
         * Callers should use DuplicateJob::newFromJob() instead
         *
-        * @param $title Title
+        * @param Title $title
         * @param array $params job parameters
         * @param $id Integer: job id
         */
index 816b531..97a7af6 100644 (file)
@@ -38,7 +38,7 @@ class EnotifNotifyJob extends Job {
                        $editor = User::newFromId( $this->params['editorID'] );
                // B/C, only the name might be given.
                } else {
-                       # FIXME: newFromName could return false on a badly configured wiki.
+                       # @todo FIXME: newFromName could return false on a badly configured wiki.
                        $editor = User::newFromName( $this->params['editor'], false );
                }
                $enotif->actuallyNotifyOnPageChange(
index 1dd77ed..8885e25 100644 (file)
@@ -47,7 +47,11 @@ class HTMLCacheUpdateJob extends Job {
        /** @var BacklinkCache */
        protected $blCache;
 
-       protected $rowsPerJob, $rowsPerQuery;
+       /** @var int Number of rows to update per job, see $wgUpdateRowsPerJob */
+       protected $rowsPerJob;
+
+       /** @var int Number of rows to update per query, see $wgUpdateRowsPerQuery */
+       protected $rowsPerQuery;
 
        /**
         * Construct a job
@@ -133,7 +137,7 @@ class HTMLCacheUpdateJob extends Job {
         * using a pre-calculated title array which gives the links in that range.
         * Queue the resulting jobs.
         *
-        * @param array $titleArray
+        * @param array|TitleArrayFromResult $titleArray
         * @param array $rootJobParams
         */
        protected function insertJobsFromTitles( $titleArray, $rootJobParams = array() ) {
@@ -145,6 +149,7 @@ class HTMLCacheUpdateJob extends Job {
                $jobs = array();
                $start = $this->params['start']; # start of the current job
                $numTitles = 0;
+               /** @var Title $title */
                foreach ( $titleArray as $title ) {
                        $id = $title->getArticleID();
                        # $numTitles is now the number of titles in the current job not
@@ -213,7 +218,7 @@ class HTMLCacheUpdateJob extends Job {
 
        /**
         * Invalidate an array (or iterator) of Title objects, right now
-        * @param array $titleArray
+        * @param array|TitleArrayFromResult $titleArray
         */
        protected function invalidateTitles( $titleArray ) {
                global $wgUseFileCache, $wgUseSquid;
index 77f6572..f62419c 100644 (file)
@@ -46,9 +46,9 @@
  */
 class NullJob extends Job {
        /**
-        * @param $title Title (can be anything)
+        * @param Title $title
         * @param array $params job parameters (lives, usleep)
-        * @param $id Integer: job id
+        * @param int $id Job id
         */
        function __construct( $title, $params, $id = 0 ) {
                parent::__construct( 'null', $title, $params, $id );
index 377fea5..0372d85 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * Job to update links for a given title.
+ * Job to update link tables for pages
  *
  * 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
  */
 
 /**
- * Background job to update links for a given title.
+ * Job to update link tables for pages
+ *
+ * This job comes in a few variants:
+ *   - a) Recursive jobs to update links for backlink pages for a given title
+ *   - b) Jobs to update links for a set of titles (the job title is ignored)
+ *   - c) Jobs to update links for a single title (the job title)
  *
  * @ingroup JobQueue
  */
 class RefreshLinksJob extends Job {
+       const VERSION = 1;
+
        function __construct( $title, $params = '', $id = 0 ) {
                parent::__construct( 'refreshLinks', $title, $params, $id );
-               $this->removeDuplicates = true; // job is expensive
+               $this->params['version'] = self::VERSION;
+               // Base backlink update jobs and per-title update jobs can be de-duplicated.
+               // If template A changes twice before any jobs run, a clean queue will have:
+               //              (A base, A base)
+               // The second job is ignored by the queue on insertion.
+               // Suppose, many pages use template A, and that template itself uses template B.
+               // An edit to both will first create two base jobs. A clean FIFO queue will have:
+               //              (A base, B base)
+               // When these jobs run, the queue will have per-title and remnant partition jobs:
+               //              (titleX,titleY,titleZ,...,A remnant,titleM,titleN,titleO,...,B remnant)
+               // Some these jobs will be the same, and will automatically be ignored by
+               // the queue upon insertion. Some title jobs will run before the duplicate is
+               // inserted, so the work will still be done twice in those cases. More titles
+               // can be de-duplicated as the remnant jobs continue to be broken down. This
+               // works best when $wgUpdateRowsPerJob, and either the pages have few backlinks
+               // and/or the backlink sets for pages A and B are almost identical.
+               $this->removeDuplicates = !isset( $params['range'] )
+                       && ( !isset( $params['pages'] ) || count( $params['pages'] ) == 1 );
        }
 
-       /**
-        * Run a refreshLinks job
-        * @return boolean success
-        */
        function run() {
-               $linkCache = LinkCache::singleton();
-               $linkCache->clear();
+               global $wgUpdateRowsPerJob;
 
                if ( is_null( $this->title ) ) {
-                       $this->error = "refreshLinks: Invalid title";
+                       $this->setLastError( "Invalid page title" );
+                       return false;
+               }
+
+               // Job to update all (or a range of) backlink pages for a page
+               if ( isset( $this->params['recursive'] ) ) {
+                       // Carry over information for de-duplication
+                       $extraParams = $this->getRootJobParams();
+                       // Avoid slave lag when fetching templates.
+                       // When the outermost job is run, we know that the caller that enqueued it must have
+                       // committed the relevant changes to the DB by now. At that point, record the master
+                       // position and pass it along as the job recursively breaks into smaller range jobs.
+                       // Hopefully, when leaf jobs are popped, the slaves will have reached that position.
+                       if ( isset( $this->params['masterPos'] ) ) {
+                               $extraParams['masterPos'] = $this->params['masterPos'];
+                       } elseif ( wfGetLB()->getServerCount() > 1 ) {
+                               $extraParams['masterPos'] = wfGetLB()->getMasterPos();
+                       } else {
+                               $extraParams['masterPos'] = false;
+                       }
+                       // Convert this into no more than $wgUpdateRowsPerJob RefreshLinks per-title
+                       // jobs and possibly a recursive RefreshLinks job for the rest of the backlinks
+                       $jobs = BacklinkJobUtils::partitionBacklinkJob(
+                               $this,
+                               $wgUpdateRowsPerJob,
+                               1, // job-per-title
+                               array( 'params' => $extraParams )
+                       );
+                       JobQueueGroup::singleton()->push( $jobs );
+               // Job to update link tables for for a set of titles
+               } elseif ( isset( $this->params['pages'] ) ) {
+                       foreach ( $this->params['pages'] as $pageId => $nsAndKey ) {
+                               list( $ns, $dbKey ) = $nsAndKey;
+                               $this->runForTitle( Title::makeTitleSafe( $ns, $dbKey ) );
+                       }
+               // Job to update link tables for a given title
+               } else {
+                       $this->runForTitle( $this->mTitle );
+               }
+
+               return true;
+       }
+
+       protected function runForTitle( Title $title = null ) {
+               $linkCache = LinkCache::singleton();
+               $linkCache->clear();
 
+               if ( is_null( $title ) ) {
+                       $this->setLastError( "refreshLinks: Invalid title" );
                        return false;
                }
 
-               # Wait for the DB of the current/next slave DB handle to catch up to the master.
-               # This way, we get the correct page_latest for templates or files that just changed
-               # milliseconds ago, having triggered this job to begin with.
+               // Wait for the DB of the current/next slave DB handle to catch up to the master.
+               // This way, we get the correct page_latest for templates or files that just changed
+               // milliseconds ago, having triggered this job to begin with.
                if ( isset( $this->params['masterPos'] ) && $this->params['masterPos'] !== false ) {
                        wfGetLB()->waitFor( $this->params['masterPos'] );
                }
 
-               $revision = Revision::newFromTitle( $this->title, false, Revision::READ_NORMAL );
+               $revision = Revision::newFromTitle( $title, false, Revision::READ_NORMAL );
                if ( !$revision ) {
-                       $this->error = 'refreshLinks: Article not found "' .
-                               $this->title->getPrefixedDBkey() . '"';
-
+                       $this->setLastError( "refreshLinks: Article not found {$title->getPrefixedDBkey()}" );
                        return false; // XXX: what if it was just deleted?
                }
 
-               self::runForTitleInternal( $this->title, $revision, __METHOD__ );
-
-               return true;
-       }
-
-       /**
-        * @return Array
-        */
-       public function getDeduplicationInfo() {
-               $info = parent::getDeduplicationInfo();
-               // Don't let highly unique "masterPos" values ruin duplicate detection
-               if ( is_array( $info['params'] ) ) {
-                       unset( $info['params']['masterPos'] );
-               }
-
-               return $info;
-       }
-
-       /**
-        * @param $title Title
-        * @param $revision Revision
-        * @param $fname string
-        * @return void
-        */
-       public static function runForTitleInternal( Title $title, Revision $revision, $fname ) {
-               wfProfileIn( $fname );
                $content = $revision->getContent( Revision::RAW );
-
                if ( !$content ) {
-                       // if there is no content, pretend the content is empty
+                       // If there is no content, pretend the content is empty
                        $content = $revision->getContentHandler()->makeEmptyContent();
                }
 
@@ -102,125 +139,20 @@ class RefreshLinksJob extends Job {
 
                InfoAction::invalidateCache( $title );
 
-               wfProfileOut( $fname );
-       }
-}
-
-/**
- * Background job to update links for a given title.
- * Newer version for high use templates.
- *
- * @ingroup JobQueue
- */
-class RefreshLinksJob2 extends Job {
-       function __construct( $title, $params, $id = 0 ) {
-               parent::__construct( 'refreshLinks2', $title, $params, $id );
-               // Base jobs for large templates can easily be de-duplicated
-               $this->removeDuplicates = !isset( $params['start'] ) && !isset( $params['end'] );
-       }
-
-       /**
-        * Run a refreshLinks2 job
-        * @return boolean success
-        */
-       function run() {
-               global $wgUpdateRowsPerJob;
-
-               $linkCache = LinkCache::singleton();
-               $linkCache->clear();
-
-               if ( is_null( $this->title ) ) {
-                       $this->error = "refreshLinks2: Invalid title";
-
-                       return false;
-               }
-
-               // Back compat for pre-r94435 jobs
-               $table = isset( $this->params['table'] ) ? $this->params['table'] : 'templatelinks';
-
-               // Avoid slave lag when fetching templates.
-               // When the outermost job is run, we know that the caller that enqueued it must have
-               // committed the relevant changes to the DB by now. At that point, record the master
-               // position and pass it along as the job recursively breaks into smaller range jobs.
-               // Hopefully, when leaf jobs are popped, the slaves will have reached that position.
-               if ( isset( $this->params['masterPos'] ) ) {
-                       $masterPos = $this->params['masterPos'];
-               } elseif ( wfGetLB()->getServerCount() > 1 ) {
-                       $masterPos = wfGetLB()->getMasterPos();
-               } else {
-                       $masterPos = false;
-               }
-
-               $tbc = $this->title->getBacklinkCache();
-
-               $jobs = array(); // jobs to insert
-               if ( isset( $this->params['start'] ) && isset( $this->params['end'] ) ) {
-                       # This is a partition job to trigger the insertion of leaf jobs...
-                       $jobs = array_merge( $jobs, $this->getSingleTitleJobs( $table, $masterPos ) );
-               } else {
-                       # This is a base job to trigger the insertion of partitioned jobs...
-                       if ( $tbc->getNumLinks( $table, $wgUpdateRowsPerJob + 1 ) <= $wgUpdateRowsPerJob ) {
-                               # Just directly insert the single per-title jobs
-                               $jobs = array_merge( $jobs, $this->getSingleTitleJobs( $table, $masterPos ) );
-                       } else {
-                               # Insert the partition jobs to make per-title jobs
-                               foreach ( $tbc->partition( $table, $wgUpdateRowsPerJob ) as $batch ) {
-                                       list( $start, $end ) = $batch;
-                                       $jobs[] = new RefreshLinksJob2( $this->title,
-                                               array(
-                                                       'table' => $table,
-                                                       'start' => $start,
-                                                       'end' => $end,
-                                                       'masterPos' => $masterPos,
-                                               ) + $this->getRootJobParams() // carry over information for de-duplication
-                                       );
-                               }
-                       }
-               }
-
-               if ( count( $jobs ) ) {
-                       JobQueueGroup::singleton()->push( $jobs );
-               }
-
                return true;
        }
 
-       /**
-        * @param $table string
-        * @param $masterPos mixed
-        * @return Array
-        */
-       protected function getSingleTitleJobs( $table, $masterPos ) {
-               # The "start"/"end" fields are not set for the base jobs
-               $start = isset( $this->params['start'] ) ? $this->params['start'] : false;
-               $end = isset( $this->params['end'] ) ? $this->params['end'] : false;
-               $titles = $this->title->getBacklinkCache()->getLinks( $table, $start, $end );
-               # Convert into single page refresh links jobs.
-               # This handles well when in sapi mode and is useful in any case for job
-               # de-duplication. If many pages use template A, and that template itself
-               # uses template B, then an edit to both will create many duplicate jobs.
-               # Roughly speaking, for each page, one of the "RefreshLinksJob" jobs will
-               # get run first, and when it does, it will remove the duplicates. Of course,
-               # one page could have its job popped when the other page's job is still
-               # buried within the logic of a refreshLinks2 job.
-               $jobs = array();
-               foreach ( $titles as $title ) {
-                       $jobs[] = new RefreshLinksJob( $title,
-                               array( 'masterPos' => $masterPos ) + $this->getRootJobParams()
-                       ); // carry over information for de-duplication
-               }
-
-               return $jobs;
-       }
-
-       /**
-        * @return Array
-        */
        public function getDeduplicationInfo() {
                $info = parent::getDeduplicationInfo();
-               // Don't let highly unique "masterPos" values ruin duplicate detection
                if ( is_array( $info['params'] ) ) {
+                       // Don't let highly unique "masterPos" values ruin duplicate detection
                        unset( $info['params']['masterPos'] );
+                       // For per-pages jobs, the job title is that of the template that changed
+                       // (or similar), so remove that since it ruins duplicate detection
+                       if ( isset( $info['pages'] ) ) {
+                               unset( $info['namespace'] );
+                               unset( $info['title'] );
+                       }
                }
 
                return $info;
diff --git a/includes/job/jobs/RefreshLinksJob2.php b/includes/job/jobs/RefreshLinksJob2.php
new file mode 100644 (file)
index 0000000..332f625
--- /dev/null
@@ -0,0 +1,141 @@
+<?php
+/**
+ * Job to update links for 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 JobQueue
+ */
+
+/**
+ * Background job to update links for titles in certain backlink range by page ID.
+ * Newer version for high use templates. This is deprecated by RefreshLinksPartitionJob.
+ *
+ * @ingroup JobQueue
+ * @deprecated 1.23
+ */
+class RefreshLinksJob2 extends Job {
+       function __construct( $title, $params, $id = 0 ) {
+               parent::__construct( 'refreshLinks2', $title, $params, $id );
+               // Base jobs for large templates can easily be de-duplicated
+               $this->removeDuplicates = !isset( $params['start'] ) && !isset( $params['end'] );
+       }
+
+       /**
+        * Run a refreshLinks2 job
+        * @return boolean success
+        */
+       function run() {
+               global $wgUpdateRowsPerJob;
+
+               $linkCache = LinkCache::singleton();
+               $linkCache->clear();
+
+               if ( is_null( $this->title ) ) {
+                       $this->error = "refreshLinks2: Invalid title";
+                       return false;
+               }
+
+               // Back compat for pre-r94435 jobs
+               $table = isset( $this->params['table'] ) ? $this->params['table'] : 'templatelinks';
+
+               // Avoid slave lag when fetching templates.
+               // When the outermost job is run, we know that the caller that enqueued it must have
+               // committed the relevant changes to the DB by now. At that point, record the master
+               // position and pass it along as the job recursively breaks into smaller range jobs.
+               // Hopefully, when leaf jobs are popped, the slaves will have reached that position.
+               if ( isset( $this->params['masterPos'] ) ) {
+                       $masterPos = $this->params['masterPos'];
+               } elseif ( wfGetLB()->getServerCount() > 1 ) {
+                       $masterPos = wfGetLB()->getMasterPos();
+               } else {
+                       $masterPos = false;
+               }
+
+               $tbc = $this->title->getBacklinkCache();
+
+               $jobs = array(); // jobs to insert
+               if ( isset( $this->params['start'] ) && isset( $this->params['end'] ) ) {
+                       # This is a partition job to trigger the insertion of leaf jobs...
+                       $jobs = array_merge( $jobs, $this->getSingleTitleJobs( $table, $masterPos ) );
+               } else {
+                       # This is a base job to trigger the insertion of partitioned jobs...
+                       if ( $tbc->getNumLinks( $table, $wgUpdateRowsPerJob + 1 ) <= $wgUpdateRowsPerJob ) {
+                               # Just directly insert the single per-title jobs
+                               $jobs = array_merge( $jobs, $this->getSingleTitleJobs( $table, $masterPos ) );
+                       } else {
+                               # Insert the partition jobs to make per-title jobs
+                               foreach ( $tbc->partition( $table, $wgUpdateRowsPerJob ) as $batch ) {
+                                       list( $start, $end ) = $batch;
+                                       $jobs[] = new RefreshLinksJob2( $this->title,
+                                               array(
+                                                       'table' => $table,
+                                                       'start' => $start,
+                                                       'end' => $end,
+                                                       'masterPos' => $masterPos,
+                                               ) + $this->getRootJobParams() // carry over information for de-duplication
+                                       );
+                               }
+                       }
+               }
+
+               if ( count( $jobs ) ) {
+                       JobQueueGroup::singleton()->push( $jobs );
+               }
+
+               return true;
+       }
+
+       /**
+        * @param $table string
+        * @param $masterPos mixed
+        * @return Array
+        */
+       protected function getSingleTitleJobs( $table, $masterPos ) {
+               # The "start"/"end" fields are not set for the base jobs
+               $start = isset( $this->params['start'] ) ? $this->params['start'] : false;
+               $end = isset( $this->params['end'] ) ? $this->params['end'] : false;
+               $titles = $this->title->getBacklinkCache()->getLinks( $table, $start, $end );
+               # Convert into single page refresh links jobs.
+               # This handles well when in sapi mode and is useful in any case for job
+               # de-duplication. If many pages use template A, and that template itself
+               # uses template B, then an edit to both will create many duplicate jobs.
+               # Roughly speaking, for each page, one of the "RefreshLinksJob" jobs will
+               # get run first, and when it does, it will remove the duplicates. Of course,
+               # one page could have its job popped when the other page's job is still
+               # buried within the logic of a refreshLinks2 job.
+               $jobs = array();
+               foreach ( $titles as $title ) {
+                       $jobs[] = new RefreshLinksJob( $title,
+                               array( 'masterPos' => $masterPos ) + $this->getRootJobParams()
+                       ); // carry over information for de-duplication
+               }
+               return $jobs;
+       }
+
+       /**
+        * @return Array
+        */
+       public function getDeduplicationInfo() {
+               $info = parent::getDeduplicationInfo();
+               // Don't let highly unique "masterPos" values ruin duplicate detection
+               if ( is_array( $info['params'] ) ) {
+                       unset( $info['params']['masterPos'] );
+               }
+               return $info;
+       }
+}
index 1354169..15d523f 100644 (file)
 class UploadFromUrlJob extends Job {
        const SESSION_KEYNAME = 'wsUploadFromUrlJobData';
 
-       /**
-        * @var UploadFromUrl
-        */
+       /** @var UploadFromUrl */
        public $upload;
 
-       /**
-        * @var User
-        */
+       /** @var User */
        protected $user;
 
        public function __construct( $title, $params, $id = 0 ) {
@@ -87,6 +83,8 @@ class UploadFromUrlJob extends Job {
                                # Stash the upload
                                $key = $this->upload->stashFile();
 
+                               // @todo FIXME: This has been broken for a while.
+                               // User::leaveUserMessage() does not exist.
                                if ( $this->params['leaveMessage'] ) {
                                        $this->user->leaveUserMessage(
                                                wfMessage( 'upload-warning-subj' )->text(),
@@ -121,17 +119,19 @@ class UploadFromUrlJob extends Job {
         * Leave a message on the user talk page or in the session according to
         * $params['leaveMessage'].
         *
-        * @param $status Status
+        * @param Status $status
         */
        protected function leaveMessage( $status ) {
                if ( $this->params['leaveMessage'] ) {
                        if ( $status->isGood() ) {
+                               // @todo FIXME: user->leaveUserMessage does not exist.
                                $this->user->leaveUserMessage( wfMessage( 'upload-success-subj' )->text(),
                                        wfMessage( 'upload-success-msg',
                                                $this->upload->getTitle()->getText(),
                                                $this->params['url']
                                        )->text() );
                        } else {
+                               // @todo FIXME: user->leaveUserMessage does not exist.
                                $this->user->leaveUserMessage( wfMessage( 'upload-failure-subj' )->text(),
                                        wfMessage( 'upload-failure-msg',
                                                $status->getWikiText(),
@@ -157,7 +157,7 @@ class UploadFromUrlJob extends Job {
         *
         * @param string $result the result (Success|Warning|Failure)
         * @param string $dataKey the key of the extra data
-        * @param $dataValue Mixed: the extra data itself
+        * @param mixed $dataValue The extra data itself
         */
        protected function storeResultInSession( $result, $dataKey, $dataValue ) {
                $session =& self::getSessionData( $this->params['sessionKey'] );
diff --git a/includes/job/utils/BacklinkJobUtils.php b/includes/job/utils/BacklinkJobUtils.php
new file mode 100644 (file)
index 0000000..b0b6ccd
--- /dev/null
@@ -0,0 +1,122 @@
+<?php
+/**
+ * Job to update links for 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 JobQueue
+ * @author Aaron Schulz
+ */
+
+/**
+ * Class with Backlink related Job helper methods
+ *
+ * @ingroup JobQueue
+ * @since 1.23
+ */
+class BacklinkJobUtils {
+       /**
+        * Break down $job into approximately ($bSize/$cSize) leaf jobs and a single partition
+        * job that covers the remaining backlink range (if needed). Jobs for the first $bSize
+        * titles are collated ($cSize per job) into leaf jobs to do actual work. All the
+        * resulting jobs are of the same class as $job. No partition job is returned if the
+        * range covered by $job was less than $bSize, as the leaf jobs have full coverage.
+        *
+        * The leaf jobs have the 'pages' param set to a (<page ID>:(<namespace>,<DB key>),...)
+        * map so that the run() function knows what pages to act on. The leaf jobs will keep
+        * the same job title as the parent job (e.g. $job).
+        *
+        * The partition jobs have the 'range' parameter set to a map of the format
+        * (start:<integer>, end:<integer>, batchSize:<integer>, subranges:((<start>,<end>),...)),
+        * the 'table' parameter set to that of $job, and the 'recursive' parameter set to true.
+        * This method can be called on the resulting job to repeat the process again.
+        *
+        * The job provided ($job) must have the 'recursive' parameter set to true and the 'table'
+        * parameter must be set to a backlink table. The job title will be used as the title to
+        * find backlinks for. Any 'range' parameter must follow the same format as mentioned above.
+        * This should be managed by recursive calls to this method.
+        *
+        * The first jobs return are always the leaf jobs. This lets the caller use push() to
+        * put them directly into the queue and works well if the queue is FIFO. In such a queue,
+        * the leaf jobs have to get finished first before anything can resolve the next partition
+        * job, which keeps the queue very small.
+        *
+        * $opts includes:
+        *   - params : extra job parameters to include in each job
+        *
+        * @param Job $job
+        * @param int $bSize BacklinkCache partition size; usually $wgUpdateRowsPerJob
+        * @param int $cSize Max titles per leaf job; Usually 1 or a modest value
+        * @param array $opts Optional parameter map
+        * @return array List of Job objects
+        */
+       public static function partitionBacklinkJob( Job $job, $bSize, $cSize, $opts = array() ) {
+               $class = get_class( $job );
+               $title = $job->getTitle();
+               $params = $job->getParams();
+
+               if ( isset( $params['pages'] ) || empty( $params['recursive'] ) ) {
+                       $ranges = array(); // sanity; this is a leaf node
+                       wfWarn( __METHOD__ . " called on {$job->getType()} leaf job (explosive recursion)." );
+               } elseif ( isset( $params['range'] ) ) {
+                       // This is a range job to trigger the insertion of partitioned/title jobs...
+                       $ranges = $params['range']['subranges'];
+                       $realBSize = $params['range']['batchSize'];
+               } else {
+                       // This is a base job to trigger the insertion of partitioned jobs...
+                       $ranges = $title->getBacklinkCache()->partition( $params['table'], $bSize );
+                       $realBSize = $bSize;
+               }
+
+               $extraParams = isset( $opts['params'] ) ? $opts['params'] : array();
+
+               $jobs = array();
+               // Combine the first range (of size $bSize) backlinks into leaf jobs
+               if ( isset( $ranges[0] ) ) {
+                       list( $start, $end ) = $ranges[0];
+                       $titles = $title->getBacklinkCache()->getLinks( $params['table'], $start, $end );
+                       foreach ( array_chunk( iterator_to_array( $titles ), $cSize ) as $titleBatch ) {
+                               $pages = array();
+                               foreach ( $titleBatch as $tl ) {
+                                       $pages[$tl->getArticleId()] = array( $tl->getNamespace(), $tl->getDBKey() );
+                               }
+                               $jobs[] = new $class(
+                                       $title, // maintain parent job title
+                                       array( 'pages' => $pages ) + $extraParams
+                               );
+                       }
+               }
+               // Take all of the remaining ranges and build a partition job from it
+               if ( isset( $ranges[1] ) ) {
+                       $jobs[] = new $class(
+                               $title, // maintain parent job title
+                               array(
+                                       'recursive'     => true,
+                                       'table'         => $params['table'],
+                                       'range'         => array(
+                                               'start'     => $ranges[1][0],
+                                               'end'       => $ranges[count( $ranges ) - 1][1],
+                                               'batchSize' => $realBSize,
+                                               'subranges' => array_slice( $ranges, 1 )
+                                       ),
+                               ) + $extraParams
+                       );
+               }
+
+               return $jobs;
+       }
+}
index 9064263..9004267 100644 (file)
  */
 class ResourceLoaderUserGroupsModule extends ResourceLoaderWikiModule {
 
-       /* Protected Methods */
+       /* Protected Members */
+
        protected $origin = self::ORIGIN_USER_SITEWIDE;
+       protected $targets = array( 'desktop', 'mobile' );
+
+       /* Protected Methods */
 
        /**
         * @param $context ResourceLoaderContext
index 7a04e47..7454b65 100644 (file)
  */
 class ResourceLoaderUserModule extends ResourceLoaderWikiModule {
 
-       /* Protected Methods */
+       /* Protected Members */
+
        protected $origin = self::ORIGIN_USER_INDIVIDUAL;
 
+       /* Protected Methods */
+
        /**
         * @param $context ResourceLoaderContext
         * @return array
index 0b7e196..1df8c56 100644 (file)
@@ -33,6 +33,8 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
 
        protected $origin = self::ORIGIN_CORE_INDIVIDUAL;
 
+       protected $targets = array( 'desktop', 'mobile' );
+
        /* Methods */
 
        /**
index 92ebbe9..cdc9611 100644 (file)
@@ -30,6 +30,8 @@ class ResourceLoaderUserTokensModule extends ResourceLoaderModule {
 
        protected $origin = self::ORIGIN_CORE_INDIVIDUAL;
 
+       protected $targets = array( 'desktop', 'mobile' );
+
        /* Methods */
 
        /**
index 7aa1ec8..896a459 100644 (file)
@@ -752,8 +752,8 @@ $1',
 'youhavenewmessages' => 'لك $1 ($2).',
 'youhavenewmessagesfromusers' => 'لديك $1 من {{PLURAL:$3|مستخدم واحد|مستخدم واحد|مستخدمين اثنين|$3 مستخدمين|$3 مستخدما|$3 مستخدم}} ($2).',
 'youhavenewmessagesmanyusers' => 'لديك $1 من مستخدمين كثر ($2).',
-'newmessageslinkplural' => '{{PLURAL:$1|رسالة جديدة|رسائل جديدة}}',
-'newmessagesdifflinkplural' => 'أحدث {{PLURAL:$1|تغيير|تغييرات}}',
+'newmessageslinkplural' => '{{PLURAL:$1|رسالة جديدة|999=رسائل جديدة}}',
+'newmessagesdifflinkplural' => 'أحدث {{PLURAL:$1|تغيير|999=تغييرات}}',
 'youhavenewmessagesmulti' => 'لديك رسائل جديدة في $1',
 'editsection' => 'عدل',
 'editold' => 'عدل',
@@ -887,7 +887,8 @@ $2',
 'invalidtitle-knownnamespace' => 'عنوان غير صالح في النطاق «$2» مع نص «$3»',
 'invalidtitle-unknownnamespace' => 'عنوان غير صالح ذو نطاق غير معروف رقم $1 ونص «$2»',
 'exception-nologin' => 'غير مسجل الدخول',
-'exception-nologin-text' => 'تتطلب هذه الصفحة أو الفعل منك القيام بتسجيل الدخول على هذه الويكي أولا.',
+'exception-nologin-text' => 'الرجاء [[Special:Userlogin|تسجيل الدخول]] لتتمكن من الوصول لهذه الصفحة أو أداء هذا الإجراء.',
+'exception-nologin-text-manual' => 'الرجاء $1 لتتمكن من الوصول لهذه الصفحة أو أداء هذا الإجراء.',
 
 # Virus scanner
 'virus-badscanner' => "ضبط سيء: ماسح فيروسات غير معروف: ''$1''",
@@ -1111,6 +1112,7 @@ $2
 'resettokens-legend' => 'غير المفاتيح',
 'resettokens-tokens' => 'مفاتيح:',
 'resettokens-token-label' => '$1 (القيمة الحالية: $2)',
+'resettokens-watchlist-token' => 'رمز تغذية الوب (آتوم/آس إس إس) [[Special:Watchlist|للتغيرات التي على قائمة مراقبتك]]',
 'resettokens-done' => 'تغيير المفاتيح',
 'resettokens-resetbutton' => 'غير المفاتيح المختارة',
 
@@ -1451,15 +1453,16 @@ $2
 لكن أجزاء من محتواها لن يكون مسموحا للعامة برؤيتها.",
 'revdelete-confirm' => 'الإداريون الآخرون في {{SITENAME}} سيظل بإمكانهم رؤية المحتوى المخفي ويمكنهم استرجاعه مجددا من خلال هذه الواجهة نفسها، مالم يتم وضع قيود إضافية.
 من فضلك أكد أنك تنوي فعل هذا، وأنك تفهم العواقب، وأنك تفعل هذا بالتوافق مع [[{{MediaWiki:Policy-url}}|السياسة]].',
-'revdelete-suppress-text' => "الإخفاء ينبغي أن يتم استخدامه '''فقط''' في الحالات التالية:
+'revdelete-suppress-text' => "ينبغي للإخفاء أن يستخدم '''فقط''' في الحالات التالية:
+* معلومات يحتمل أن تكون تشهيرية
 * معلومات شخصية غير ملائمة
-*: ''عناوين المنازل وأرقام التليفونات، أرقام الضمان الاجتماعي، إلى آخره.''",
+*: ''عناوين المنازل وأرقام الهواتف وأرقام الهويات الوطنية إلى آخره.''",
 'revdelete-legend' => 'وضع ضوابط رؤية',
-'revdelete-hide-text' => 'أخف نص المراجعة',
+'revdelete-hide-text' => 'نص المراجعة',
 'revdelete-hide-image' => 'أخف محتوى الملف',
 'revdelete-hide-name' => 'أخف الفعل والهدف',
-'revdelete-hide-comment' => 'أخف تعليق التعديل',
-'revdelete-hide-user' => 'أخÙ\81 Ø§Ø³Ù\85/Ø¢Ù\8aبÙ\8a Ø§Ù\84Ù\85ستخدÙ\85',
+'revdelete-hide-comment' => 'ملخص التعديل',
+'revdelete-hide-user' => 'اسÙ\85 Ø§Ù\84Ù\85ستخدÙ\85/عÙ\86Ù\88اÙ\86 Ø§Ù\84Ø¢Ù\8aبÙ\8a',
 'revdelete-hide-restricted' => 'أخف البيانات عن الإداريين إضافة إلى الآخرين',
 'revdelete-radio-same' => '(لا تغير)',
 'revdelete-radio-set' => 'مخفي',
@@ -1622,6 +1625,7 @@ $1",
 'preferences' => 'تفضيلات',
 'mypreferences' => 'تفضيلات',
 'prefs-edits' => 'عدد التعديلات:',
+'prefsnologintext2' => 'الرجاء $1 لضبط تفضيلات المستخدم.',
 'changepassword' => 'غير كلمة السر',
 'prefs-skin' => 'واجهة',
 'skin-preview' => 'عرض مسبق',
@@ -1919,7 +1923,7 @@ $1",
 'recentchanges-label-minor' => 'هذا تعديل طفيف',
 'recentchanges-label-bot' => 'أُجْرِيَ هذا التعديل بواسطة بوت',
 'recentchanges-label-unpatrolled' => 'لم يراجع هذا التعديل إلى الآن',
-'recentchanges-legend-newpage' => '$1 - صفحة جديدة',
+'recentchanges-legend-newpage' => '(راجع أيضا [[Special:NewPages|قائمة الصفحات الجديدة]])',
 'rcnote' => "بالأسفل {{PLURAL:$1|لا توجد تغييرات|التغيير الأخير|آخر تغييرين|آخر '''$1''' تغييرات|آخر '''$1''' تغييرا|آخر '''$1''' تغيير}} في {{PLURAL:$2||'''اليوم''' الماضي|'''اليومين''' الماضيين|ال'''$2''' أيام الماضية|ال'''$2''' يوما الماضيا|ال'''$2''' يوم الماضي}}، كما في $5، $4.",
 'rcnotefrom' => "بالأسفل التغييرات منذ '''$2''' (إلى '''$1''' معروضة).",
 'rclistfrom' => 'أظهر التغييرات بدءا من $1',
@@ -2727,7 +2731,7 @@ $UNWATCHURL
 آخر تعديل كان بواسطة [[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' => 'استرجع تعديلات $1؛
 استرجع حتى آخر نسخة بواسطة $2.',
 
@@ -2873,7 +2877,7 @@ $1',
 'contributions' => 'مساهمات {{GENDER:$1|المستخدم|المستخدمة}}',
 'contributions-title' => 'مساهمات {{GENDER:$1|المستخدم|المستخدمة}} $1',
 'mycontris' => 'مساهماتي',
-'contribsub2' => 'ل$1 ($2)',
+'contribsub2' => 'ل{{GENDER:$3|$1}} ($2)',
 'nocontribs' => 'لم يتم العثور على تغييرات تطابق هذه المحددات.',
 'uctop' => 'حالي',
 'month' => 'من شهر (وأقدم):',
@@ -4307,7 +4311,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' => 'الوجهة',
@@ -4331,8 +4335,7 @@ $5
 'specialpages' => 'الصفحات الخاصة',
 'specialpages-note-top' => 'المفتاح',
 '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' => 'دخول / إنشاء حساب',
index 34b5e34..e52596c 100644 (file)
@@ -346,8 +346,8 @@ $1',
 'youhavenewmessages' => 'আপনার $1 এসেছে ($2)৷',
 'youhavenewmessagesfromusers' => 'আপনি {{PLURAL:$3|অন্য ব্যবহারকারীর|$3 ব্যবহারকারীর}} কাছ থেকে $1 পেয়েছেন ($2)।',
 'youhavenewmessagesmanyusers' => 'আপনি অনেক ব্যবহারকারীর কাছ থেকে $1 পেয়েছেন ($2)।',
-'newmessageslinkplural' => '{{PLURAL:$1|একটি নতুন বার্তা|নতুন বার্তা}}',
-'newmessagesdifflinkplural' => 'সর্বশেষ {{PLURAL:$1|পরিবর্তন|পরিবর্তনসমূহ}}',
+'newmessageslinkplural' => '{{PLURAL:$1|একটি নতুন বার্তা|999=নতুন বার্তা}}',
+'newmessagesdifflinkplural' => 'সর্বশেষ {{PLURAL:$1|পরিবর্তন|999=পরিবর্তনসমূহ}}',
 'youhavenewmessagesmulti' => 'আপনার $1টি নতুন বার্তা এসেছে',
 'editsection' => 'সম্পাদনা',
 'editold' => 'সম্পাদনা',
@@ -477,7 +477,8 @@ $2',
 'invalidtitle-knownnamespace' => 'অবৈধ শিরোনাম, যেখানে নামস্থান "$2" এবং লেখা হয়েছে "$3"',
 'invalidtitle-unknownnamespace' => 'অবৈধ শিরোনাম, যেখানে ব্যবহৃত হয়েছে অপরিচিত নামস্থান সংখ্যা $1 এবং লেখা হয়েছে "$2"',
 'exception-nologin' => 'লগইন করা হয়নি',
-'exception-nologin-text' => 'এই কাজটি করার জন্য উইকিতে লগইন করা প্রয়োজন।',
+'exception-nologin-text' => 'এই কাজটি করার জন্য উইকিতে [[Special:Userlogin|লগইন]] করা প্রয়োজন।',
+'exception-nologin-text-manual' => 'অনুগ্রহ করে এই পাতা দেখতে অথবা পরিবর্তন করতে $1 করুন।',
 
 # Virus scanner
 'virus-badscanner' => "ভুল কনফিগারেশন: অজ্ঞাত ভাইরাস স্কেনার: ''$1''",
@@ -1020,10 +1021,10 @@ $3-এর দেয়া কারণ হল ''$2''",
 
 {{SITENAME}} এর অন্যান্য প্রশাসকগণ লুকানো এই বিষয়বস্তু দেখতে পাবেন এবং বাড়তি কোনো সীমাবদ্ধতা না থাকলে একই ইন্টারফেসের মাধ্যমে এটি পুনরুদ্ধার করতে পারবেন।",
 'revdelete-confirm' => 'অনুগ্রহ করে নিশ্চিত করুন যে আপনি এটি করতে চাচ্ছিলেন, আপনি এর ফলাফল সম্পর্কে অবগত আছেন, এবং [[{{MediaWiki:Policy-url}}|নীতিমালার]] উপর ভিত্তি করেই এই কাজটি করছেন।',
-'revdelete-suppress-text' => "'''কেবলমাত্র''' নিচের বিষয়গুলোর ক্ষেত্রেই চাপাচাপি করা যাবে:
+'revdelete-suppress-text' => "নিচের বিষয়গুলোর ক্ষেত্রেই '''কেবলমাত্র'''  চাপাচাপি করা যাবে:
 * সম্ভাব্য মানহানিকর তথ্য
 * ভুল ব্যক্তিগত তথ্য
-*:  ''বাসার ঠিকানা এবং ফোন নম্বর, সোসাল সিকিউরিটি নম্বর, ইত্যাদি।''",
+*: ''বাসার ঠিকানা এবং ফোন নম্বর, সোসাল সিকিউরিটি নম্বর, ইত্যাদি।''",
 'revdelete-legend' => 'দৃষ্টিপাত সীমাবদ্ধ করো',
 'revdelete-hide-text' => 'সংস্করণের লেখা',
 'revdelete-hide-image' => 'ফাইলের বিষয়বস্তু আড়াল করো',
@@ -1188,6 +1189,7 @@ $1",
 'preferences' => 'আমার পছন্দ',
 'mypreferences' => 'পছন্দসমূহ',
 'prefs-edits' => 'সম্পাদনা সংখ্যা:',
+'prefsnologintext2' => 'ব্যবহারকারী পছন্দসমূহ নির্ধারনের জন্য $1 করুন।',
 'changepassword' => 'শব্দচাবি পরিবর্তন',
 'prefs-skin' => 'আবরণ (Skin)',
 'skin-preview' => 'প্রাকদর্শন',
@@ -1484,7 +1486,9 @@ $1",
 'recentchanges-label-minor' => 'এটি একটি অনুল্লেখিত সম্পাদনা',
 'recentchanges-label-bot' => 'এটি বট দ্বারা সম্পাদিত',
 'recentchanges-label-unpatrolled' => 'এই সম্পাদনাটি এখনও পরীক্ষিত নয়',
-'recentchanges-legend-newpage' => '$1 - নতুন পাতা',
+'recentchanges-label-plusminus' => 'পাতার আকার এই পরিমান বাইট পরিবর্তিত হয়েছে',
+'recentchanges-legend-newpage' => '(আরও দেখুন [[Special:NewPages|নতুন পাতার তালিকা]])',
+'recentchanges-legend-plusminus' => "(''±১২৩'')",
 'rcnote' => "বিগত {{PLURAL:$2|দিনে|'''$2''' দিনে}} সংঘটিত {{PLURAL:$1|'''১'''|'''$1'''}}টি পরিবর্তন নীচে দেখানো হল (যেখানে বর্তমান সময় ও তারিখ $5, $4)।",
 'rcnotefrom' => "'''$2'''-এর পরে সংঘটিত পরিবর্তনগুলো নিচে দেখানো হল ('''$1'''টি)।",
 'rclistfrom' => '$1-এর পর সংঘটিত নতুন পরিবর্তনগুলো দেখাও।',
@@ -3732,6 +3736,7 @@ $4-এ নিশ্চিতকরণ কোডটি মেয়াদোত
 
 # Special:SpecialPages
 'specialpages' => 'বিশেষ পাতাসমূহ',
+'specialpages-note-top' => 'লিজেন্ড',
 'specialpages-note' => '* সাধারণ বিশেষ পাতাসমূহ।
 * <span class="mw-specialpagerestricted">সীমাবদ্ধ বিশেষ পাতা।</span>',
 'specialpages-group-maintenance' => 'রক্ষণাবেক্ষণের কার্যবিবরণীসমূহ',
@@ -3950,9 +3955,9 @@ $4-এ নিশ্চিতকরণ কোডটি মেয়াদোত
 'expandtemplates' => 'টেমপ্লেট সম্প্রসারণ',
 'expand_templates_intro' => 'এই বিশেষ পাতাটি কিছু টেক্সট গ্রহণ করে এবং এর ভেতরের সব টেম্পলেট বারংবার সম্প্রসারিত করে।
 এছাড়াও এটি
-<nowiki>{{</nowiki>#language:...}}-এর মত পার্সার ফাংশন,
-<nowiki>{{</nowiki>CURRENTDAY}}-এর মত ভ্যারিয়েবল &mdash; মোটকথা দ্বিতীয় বন্ধনীর মধ্যে অবস্থিত সবকিছুকেই সম্প্রসারিত করতে পারে।
-à¦\8fà¦\9fি à¦¸à¦\82শà§\8dলিষà§\8dà¦\9f à¦ªà¦¾à¦°à§\8dসার à¦ªà¦°à§\8dযায় à¦¥à§\87à¦\95à§\87 à¦¸à§\8dবয়à¦\82 à¦®à¦¿à¦¡à¦¿à¦¯à¦¼à¦¾à¦\89à¦\87à¦\95িà¦\95à§\87 à¦\95ল à¦\95রà§\87 à¦\8fà¦\87 à¦\95াà¦\9cà¦\9fি à¦\95রà§\87 à¦¥à¦¾à¦\95ে।',
+<code><nowiki>{{</nowiki>#language:...}}</code>-এর মত পার্সার ফাংশন,
+<code><nowiki>{{</nowiki>CURRENTDAY}}</code>-এর মত ভ্যারিয়েবল
+মà§\8bà¦\9fà¦\95থা à¦¦à§\8dবিতà§\80য় à¦¬à¦¨à§\8dধনà§\80র à¦®à¦§à§\8dযà§\87 à¦\85বসà§\8dথিত à¦¸à¦¬à¦\95িà¦\9bà§\81à¦\95à§\87à¦\87 à¦¸à¦®à§\8dপà§\8dরসারিত à¦\95রতà§\87 à¦ªà¦¾à¦°ে।',
 'expand_templates_title' => 'প্রাতিবেশিক শিরোনাম, {{FULLPAGENAME}}, ইত্যাদির জন্য:',
 'expand_templates_input' => 'ইনপুটকৃত লেখা:',
 'expand_templates_output' => 'ফলাফল',
index 93f88c7..5c40e69 100644 (file)
@@ -3828,6 +3828,7 @@ Sañset oc'h bezañ resevet [{{SERVER}}{{SCRIPTPATH}}/COPYING un eilskrid eus ar
 
 # Special:SpecialPages
 'specialpages' => 'Pajennoù dibar',
+'specialpages-note-top' => "Alc'hwez",
 'specialpages-note' => '* Pajennoù dibar boutin.
 * <span class="mw-specialpagerestricted">Pajennoù dibar miret strizh.</span>
 * <span class="mw-specialpagecached">Pajennoù dibar krubuilhet hepken (a c\'hellfe bezañ re gozh).</span>',
index f1272d7..14d5b13 100644 (file)
@@ -1781,6 +1781,7 @@ Vaše adresa v takovém případě není prozrazena.',
 'recentchanges-label-unpatrolled' => 'Tato změna dosud nebyla prověřena',
 'recentchanges-label-plusminus' => 'Velikost stránky se změnila o tolik bajtů',
 'recentchanges-legend-newpage' => '(vizte též [[Special:NewPages|seznam nových stránek]])',
+'recentchanges-legend-plusminus' => "(''±123'')",
 'rcnote' => 'Níže {{plural:$1|je poslední|jsou poslední|je posledních}} <strong>$1</strong> {{plural:$1|změna|změny|změn}} za {{PLURAL:$2|poslední|poslední|posledních}} <strong>$2</strong> {{plural:$2|den|dny|dnů}} před $4, $5.',
 'rcnotefrom' => 'Níže {{PLURAL:$1|je|jsou|je}} nejvýše <b>$1</b> {{PLURAL:$1|změna|změny|změn}} od <b>$2</b>.',
 'rclistfrom' => 'Ukázat nové změny, počínaje od $1',
index 98ed101..e7bdbbc 100644 (file)
@@ -2901,7 +2901,7 @@ Yewna name bınus.',
 'movepage-page-moved' => 'pelê $1i kırışiya pelê $2i.',
 'movepage-page-unmoved' => 'pelê $1i nêkırışiyeno sernameyê $2i.',
 'movepage-max-pages' => 'tewr ziyed $1 {{PLURAL:$1|peli|peli}} kırışiya u hıni ziyedê ıney otomotikmen nêkırışiyeno.',
-'movelogpage' => 'Qeydé berdışi',
+'movelogpage' => 'Qeydê berdışi',
 'movelogpagetext' => 'nameyê liste ya ke cêr de yo, pelê vuriyayeyani mocneno',
 'movesubpage' => '{{PLURAL:$1|Subpage|pelê bınıni}}',
 'movesubpagetext' => '{{PLURAL:$1|pelê bınıni yê|pelê bınıni yê}} no $1 peli cer de yo.',
index f2d370f..46747bc 100644 (file)
@@ -1708,9 +1708,9 @@ See teave on avalik.',
 'recentchanges-summary' => 'Jälgi sellel leheküljel viimaseid muudatusi.',
 'recentchanges-noresult' => 'Selles ajavahemikus pole tehtud neile kriteeriumitele vastavaid muudatusi.',
 'recentchanges-feed-description' => 'Jälgi vikisse tehtud viimaseid muudatusi.',
-'recentchanges-label-newpage' => 'Selle muudatusega alustati uut lehekülge',
-'recentchanges-label-minor' => 'See on pisiparandus',
-'recentchanges-label-bot' => 'Selle muudatuse tegi robot',
+'recentchanges-label-newpage' => 'Uus lehekülg',
+'recentchanges-label-minor' => 'Pisiparandus',
+'recentchanges-label-bot' => 'Roboti tehtud muudatus',
 'recentchanges-label-unpatrolled' => 'Seda muudatust ei ole veel kontrollitud',
 'recentchanges-label-plusminus' => 'Lehekülje suuruse muutus baitides',
 'recentchanges-legend-newpage' => '(vaata ka [[Special:NewPages|uute lehekülgede loendit]])',
index 90a8ce1..c429f79 100644 (file)
@@ -2559,8 +2559,8 @@ Pode mudar o nivel de protección da páxina pero iso non afectará á protecci
 'protect-expiry-indefinite' => 'indefinido',
 'protect-cascade' => 'Protexer as páxinas incluídas nesta (protección en serie)',
 'protect-cantedit' => 'Non pode modificar os niveis de protección desta páxina porque non ten os permisos necesarios para editala.',
-'protect-othertime' => 'Outro período:',
-'protect-othertime-op' => 'outro período',
+'protect-othertime' => 'Outra duración:',
+'protect-othertime-op' => 'outra duración',
 'protect-existing-expiry' => 'Período de caducidade actual: $2 ás $3',
 'protect-otherreason' => 'Outro motivo:',
 'protect-otherreason-op' => 'Outro motivo',
@@ -2723,7 +2723,7 @@ Explique a razón específica do bloqueo (por exemplo, citando as páxinas concr
 'ipbemailban' => 'Impedir que o usuario envíe correos electrónicos',
 'ipbenableautoblock' => 'Bloquear automaticamente o último enderezo IP utilizado por este usuario, e calquera outro enderezo desde o que intente editar',
 'ipbsubmit' => 'Bloquear este usuario',
-'ipbother' => 'Outro período de tempo:',
+'ipbother' => 'Outra duración:',
 'ipboptions' => '2 horas:2 hours,1 día:1 day,3 días:3 days,1 semana:1 week,2 semanas:2 weeks,1 mes:1 month,3 meses:3 months,6 meses:6 months,1 ano:1 year,para sempre:infinite',
 'ipbotheroption' => 'outra',
 'ipbotherreason' => 'Outro motivo:',
index 2ebc00d..af26210 100644 (file)
@@ -12,6 +12,7 @@
  * @author Dekel E
  * @author Drorsnir
  * @author Guycn1
+ * @author Guycn2
  * @author Hoo
  * @author Ijon
  * @author Inkbug
index ce60081..998f87e 100644 (file)
@@ -421,8 +421,8 @@ $1',
 'youhavenewmessages' => 'Tu ha $1 ($2).',
 'youhavenewmessagesfromusers' => 'Tu ha $1 de {{PLURAL:$3|un altere usator|$3 usatores}} ($2).',
 'youhavenewmessagesmanyusers' => 'Tu ha $1 de multe usatores ($2).',
-'newmessageslinkplural' => '{{PLURAL:$1|un nove message|$1 nove messages}}',
-'newmessagesdifflinkplural' => 'ultime {{PLURAL:$1|modification|modificationes}}',
+'newmessageslinkplural' => '{{PLURAL:$1|un nove message|999=nove messages}}',
+'newmessagesdifflinkplural' => 'ultime {{PLURAL:$1|modification|999=modificationes}}',
 'youhavenewmessagesmulti' => 'Tu ha nove messages in $1',
 'editsection' => 'modificar',
 'editold' => 'modificar',
@@ -1607,7 +1607,8 @@ Si tu opta pro dar lo, isto essera usate pro dar te attribution pro tu contribut
 'recentchanges-label-minor' => 'Isto es un modification minor',
 'recentchanges-label-bot' => 'Iste modification ha essite effectuate per un robot',
 'recentchanges-label-unpatrolled' => 'Iste modification non ha ancora essite patruliate',
-'recentchanges-legend-newpage' => '$1 - nove pagina',
+'recentchanges-label-plusminus' => 'Le dimension del pagina ha cambiate de iste numero de bytes',
+'recentchanges-legend-newpage' => '(vide etiam le [[Special:NewPages|lista de nove paginas]])',
 'rcnote' => "Infra es {{PLURAL:$1|'''1''' modification|le ultime '''$1''' modificationes}} in le ultime {{PLURAL:$2|die|'''$2''' dies}}, actualisate le $4 a $5.",
 'rcnotefrom' => 'infra es le modificationes a partir de <b>$2</b> (usque a <b>$1</b>).',
 'rclistfrom' => 'Monstrar nove modificationes a partir de $1',
index 85d5cb5..e3ebda3 100644 (file)
@@ -1695,6 +1695,7 @@ Il tuo indirizzo non viene rivelato quando gli altri utenti ti contattano.',
 'recentchanges-label-unpatrolled' => 'Questa modifica non è stata ancora verificata',
 'recentchanges-label-plusminus' => 'La dimensione della pagina è cambiata di questo numero di byte',
 'recentchanges-legend-newpage' => "(vedi anche [[Special:NewPages|l'elenco delle nuove pagine]])",
+'recentchanges-legend-plusminus' => "(''±123'')",
 'rcnote' => "Di seguito {{PLURAL:$1|è elencata la modifica più recente apportata|sono elencate le '''$1''' modifiche più recenti apportate}} al sito {{PLURAL:$2|nelle ultime 24 ore|negli scorsi '''$2''' giorni}}; i dati sono aggiornati alle $5 del $4.",
 'rcnotefrom' => "Di seguito sono elencate le modifiche apportate a partire da '''$2''' (fino a '''$1''').",
 'rclistfrom' => 'Mostra le modifiche apportate a partire da $1',
index e170038..5023d89 100644 (file)
@@ -637,8 +637,8 @@ $1',
 'youhavenewmessages' => 'Сізде $1 бар ($2).',
 'youhavenewmessagesfromusers' => 'Сіз {{PLURAL:$3|басқа қатысушыдан|$3 қатысушыдан}} $1 алдыңыз ($2).',
 'youhavenewmessagesmanyusers' => 'Сіз бірнеше қатысушыдан $1 алдыңыз ($2).',
-'newmessageslinkplural' => '{{PLURAL:$1|жаңа хабарлама|жаңа хабарламалар}}',
-'newmessagesdifflinkplural' => 'соңғы {{PLURAL:$1|өзгеріс|өзгерістер}}',
+'newmessageslinkplural' => '{{PLURAL:$1|жаңа хабарлама|999=жаңа хабарламалар}}',
+'newmessagesdifflinkplural' => 'соңғы {{PLURAL:$1|өзгеріс|999=өзгерістер}}',
 'youhavenewmessagesmulti' => '$1 дегенде жаңа хабарламалар бар',
 'editsection' => 'өңдеу',
 'editold' => 'өңдеу',
@@ -764,7 +764,7 @@ $2',
 'invalidtitle-knownnamespace' => '"$2" есім кеңістік түрі және  "$3" мәтіні жарамсыз',
 'invalidtitle-unknownnamespace' => 'Нөмері $1 белгісіз есім кеңістік түрі және "$2" мәтіні жарамсыз',
 'exception-nologin' => 'Кірмегенсіз',
-'exception-nologin-text' => 'Бұл бет немесе әрекет бұл уикиге кіріуіңізді міндеттейді.',
+'exception-nologin-text' => 'Бұл әрекетке немесе бетке қатынау үшін [[Special:Userlogin|кіріңіз]].',
 
 # Virus scanner
 'virus-badscanner' => 'Дұрыс емес ішқұрылым. Белгісіз вирус сканері: $1',
@@ -812,9 +812,10 @@ $2',
 'gotaccount' => "Бұған дейін тіркеліп пе едіңіз? '''$1'''.",
 'gotaccountlink' => 'Кіріңіз',
 'userlogin-resetlink' => 'Қатысушы атын не құпия сөзді ұмыттыңыз ба?',
-'userlogin-resetpassword-link' => 'Құпия сөздіңізді ысыру',
+'userlogin-resetpassword-link' => 'Құпия сөздіңізді ұмыттыңыз ба?',
 'helplogin-url' => 'Help:Тіркелу',
 'userlogin-helplink' => '[[{{MediaWiki:helplogin-url}}|Тіркелуге көмек]]',
+'userlogin-createanother' => 'Басқа тіркелгі жасау',
 'createacct-join' => 'Төменге өзіңіз туралы ақпарат енгізіңіз.',
 'createacct-another-join' => 'Төменге жаңа тіркелгі туралы ақпарат енгізіңіз.',
 'createacct-emailrequired' => 'Е-пошта мекен-жайы:',
@@ -1246,8 +1247,8 @@ $3 келтірілген себебі: ''$2''",
 'revdelete-hide-user' => 'Өңдеуші атын (IP мекенжайын) жасыр',
 'revdelete-hide-restricted' => 'Осы тиымдарды әкімшілерге қолдану және бұл тілдесуді құлыптау',
 'revdelete-radio-same' => '(өзгертпе)',
-'revdelete-radio-set' => 'Ð\98Ó\99',
-'revdelete-radio-unset' => 'Ð\96оÒ\9b',
+'revdelete-radio-set' => 'Ð\96аÑ\81Ñ\8bÑ\80Ñ\8bлÒ\93ан',
+'revdelete-radio-unset' => 'Ð\9aÓ©Ñ\80Ñ\81еÑ\82Ñ\96лген',
 'revdelete-suppress' => 'Деректерді баршаға ұқсас әкімшілерден де шеттету',
 'revdelete-unsuppress' => 'Қалпына келтірілген түзетулерден тиымдарды аластау',
 'revdelete-log' => 'Себебі:',
index 9941aa6..58e80f3 100644 (file)
@@ -1831,11 +1831,11 @@ Als u deze opgeeft, kan deze naam gebruikt worden om u erkenning te geven voor u
 'recentchanges-summary' => 'Op deze pagina kunt u de recentste wijzigingen in deze wiki bekijken.',
 'recentchanges-noresult' => 'Er zijn in deze periode geen wijzigingen gemaakt die aan de criteria voldoen.',
 'recentchanges-feed-description' => 'Met deze feed kunt u de recentste wijzigingen in deze wiki bekijken.',
-'recentchanges-label-newpage' => 'Met deze bewerking is een nieuwe pagina aangemaakt',
+'recentchanges-label-newpage' => 'Met deze bewerking is een nieuwe pagina aangemaakt.',
 'recentchanges-label-minor' => 'Dit is een kleine bewerking',
 'recentchanges-label-bot' => 'Deze bewerking is uitgevoerd door een bot',
 'recentchanges-label-unpatrolled' => 'Deze bewerking is nog niet gecontroleerd',
-'recentchanges-legend-newpage' => '$1 - nieuwe pagina',
+'recentchanges-legend-newpage' => "Zie ook de [[Special:NewPages|Lijst met nieuwe pagina's]].",
 'rcnote' => "Hieronder {{PLURAL:$1|staat de laatste bewerking|staan de laatste '''$1''' bewerkingen}} in de laatste {{PLURAL:$2|dag|'''$2''' dagen}}, op $4 om $5.",
 'rcnotefrom' => "Wijzigingen sinds '''$2''' (met een maximum van '''$1''' wijzigingen).",
 'rclistfrom' => 'Wijzigingen bekijken vanaf $1',
index 3387a26..0ab98ac 100644 (file)
@@ -1746,7 +1746,8 @@ Jeśli zdecydujesz się je podać, zostaną użyte, by udokumentować Twoje auto
 'recentchanges-label-minor' => 'To jest drobna zmiana',
 'recentchanges-label-bot' => 'Ta edycja została wykonana przez bota',
 'recentchanges-label-unpatrolled' => 'Ta edycja nie została jeszcze sprawdzona',
-'recentchanges-legend-newpage' => '$1 – nowa strona',
+'recentchanges-label-plusminus' => 'Zmieniony rozmiar strony (liczba bajtów)',
+'recentchanges-legend-newpage' => '(zobacz też [[Special:NewPages|listę nowych stron]])',
 'rcnote' => "Poniżej {{PLURAL:$1|znajduje się '''1''' ostatnia zmiana wykonana|znajdują się ostatnie '''$1''' zmiany wykonane|znajduje się ostatnich '''$1''' zmian wykonanych}} w ciągu {{PLURAL:$2|ostatniego dnia|ostatnich '''$2''' dni}}, licząc od $5 dnia $4.",
 'rcnotefrom' => "Poniżej pokazano zmiany wykonane po '''$2''' (nie więcej niż '''$1''' pozycji).",
 'rclistfrom' => 'Pokaż nowe zmiany od $1',
index 1135d92..4cff02e 100644 (file)
@@ -596,8 +596,8 @@ Consulte a página da [[Special:Version|versão do sistema]].',
 'youhavenewmessages' => 'Tem $1 ($2).',
 'youhavenewmessagesfromusers' => 'Tem $1 de {{PLURAL:$3|outro utilizador|$3 utilizadores}} ($2).',
 'youhavenewmessagesmanyusers' => 'Tem $1 de muitos utilizadores ($2).',
-'newmessageslinkplural' => '{{PLURAL:$1|uma mensagem nova|mensagens novas}}',
-'newmessagesdifflinkplural' => '{{PLURAL:$1|última alteração|últimas alterações}}',
+'newmessageslinkplural' => '{{PLURAL:$1|uma mensagem nova|999=mensagens novas}}',
+'newmessagesdifflinkplural' => '{{PLURAL:$1|última alteração|999=últimas alterações}}',
 'youhavenewmessagesmulti' => 'Tem mensagens novas em $1',
 'editsection' => 'editar',
 'editold' => 'editar',
@@ -726,7 +726,7 @@ O administrador que efetuou o bloqueio deu a seguinte explicação: "$3".',
 'invalidtitle-knownnamespace' => 'Título inválido com o espaço nominal "$2" e texto "$3"',
 'invalidtitle-unknownnamespace' => 'Título inválido com número de espaço nominal $1 desconhecido e texto "$2"',
 'exception-nologin' => 'Não está autenticado',
-'exception-nologin-text' => 'Esta página ou operação requer que esteja autenticado nesta wiki.',
+'exception-nologin-text' => 'Por favor, [[Special:Userlogin|entre]] para poder acessar esta página ou acção.',
 'exception-nologin-text-manual' => 'Por favor  $1  para poder aceder a esta página ou acção.',
 
 # Virus scanner
@@ -774,7 +774,7 @@ Não se esqueça de personalizar as suas [[Special:Preferences|preferências]].'
 'gotaccount' => "Já possui uma conta? '''$1'''.",
 'gotaccountlink' => 'Autentique-se',
 'userlogin-resetlink' => 'Esqueceu-se do seu nome de utilizador ou da palavra-chave?',
-'userlogin-resetpassword-link' => 'Recuperar palavra-chave',
+'userlogin-resetpassword-link' => 'Esqueceu a sua palavra-chave?',
 'helplogin-url' => 'Help:Autenticação',
 'userlogin-helplink' => '[[{{MediaWiki:helplogin-url}}|Ajuda a fazer login]]',
 'userlogin-loggedin' => 'Já está {{GENDER:$1|autenticado|autenticada|autenticado}} com o nome $1.
@@ -848,9 +848,9 @@ Para prevenir abusos, só um email de recuperação de palavra-chave pode ser en
 'mailerror' => 'Erro ao enviar correio: $1',
 'acct_creation_throttle_hit' => 'Visitantes desta wiki com o seu endereço IP criaram $1 {{PLURAL:$1|conta|contas}} no último dia, o que é o máximo permitido neste período de tempo.
 Em resultado, visitantes com este endereço IP não podem criar mais nenhuma conta neste momento.',
-'emailauthenticated' => 'O seu endereço de correio electrónico foi autenticado a $2 às $3.',
-'emailnotauthenticated' => 'O seu endereço de correio electrónico ainda não foi autenticado.
-Não serão enviados correios de nenhuma das seguintes funcionalidades.',
+'emailauthenticated' => 'O seu endereço de correio electrónico foi confirmado a $2, às $3.',
+'emailnotauthenticated' => 'O seu endereço de correio electrónico ainda não foi confirmado.
+Não serão enviados emails de nenhuma das seguintes funcionalidades.',
 'noemailprefs' => 'Especifique um endereço de correio eletrónico nas suas preferências para ativar estas funcionalidades.',
 'emailconfirmlink' => 'Confirme o seu endereço de correio electrónico',
 'invalidemailaddress' => 'O endereço de correio eletrónico não pode ser aceite porque parece ter um formato inválido.
@@ -1299,7 +1299,7 @@ Outros administradores da {{SITENAME}} continuarão a poder aceder ao conteúdo
 'revdelete-suppress-text' => "A supressão '''só''' deverá ser usada nos seguintes casos:
 * Informação potencialmente caluniosa, difamatória ou injuriosa
 * Informação pessoal imprópria
-*: ''endereços de domicílio e números de telefone, números da segurança social, etc''",
+*: ''endereços de domicílio e números de telefone, números de identificação nacional, etc''",
 'revdelete-legend' => 'Definir restrições de visibilidade',
 'revdelete-hide-text' => 'Revisão do texto',
 'revdelete-hide-image' => 'Ocultar conteúdo do ficheiro',
@@ -1308,8 +1308,8 @@ Outros administradores da {{SITENAME}} continuarão a poder aceder ao conteúdo
 'revdelete-hide-user' => 'Nome de utilizador/endereço de IP',
 'revdelete-hide-restricted' => 'Ocultar dados dos administradores e de todos os outros',
 'revdelete-radio-same' => '(manter)',
-'revdelete-radio-set' => 'Visível',
-'revdelete-radio-unset' => 'Escondido',
+'revdelete-radio-set' => 'Escondido',
+'revdelete-radio-unset' => 'Visível',
 'revdelete-suppress' => 'Ocultar dados dos administradores e de todos os outros',
 'revdelete-unsuppress' => 'Remover restrições das revisões restauradas',
 'revdelete-log' => 'Motivo:',
@@ -1468,6 +1468,7 @@ Note, no entanto, que a indexação da {{SITENAME}} neste motor de busca pode es
 'preferences' => 'Preferências',
 'mypreferences' => 'Preferências',
 'prefs-edits' => 'Número de edições:',
+'prefsnologintext2' => 'Por favor, precisa de $1 para definir as suas preferências.',
 'changepassword' => 'Alterar palavra-chave',
 'prefs-skin' => 'Tema',
 'skin-preview' => 'Antever tema',
@@ -1768,7 +1769,7 @@ Se optar por revelá-lo, ele será utilizado para atribuir-lhe crédito pelo seu
 'recentchanges-label-minor' => 'Esta é uma edição menor',
 'recentchanges-label-bot' => 'Esta edição foi feita por um robô',
 'recentchanges-label-unpatrolled' => 'Esta edição ainda não foi patrulhada',
-'recentchanges-legend-newpage' => '$1 - página nova',
+'recentchanges-legend-newpage' => '(ver também a [[Special:NewPages|lista de páginas novas]])',
 'rcnote' => "A seguir {{PLURAL:$1|está listada '''uma''' alteração ocorrida|estão listadas '''$1''' alterações ocorridas}} {{PLURAL:$2|no último dia|nos últimos '''$2''' dias}}, a partir das $5 de $4.",
 'rcnotefrom' => 'Abaixo estão as mudanças desde <b>$2</b> (mostradas até <b>$1</b>).',
 'rclistfrom' => 'Mostrar as novas mudanças a partir das $1',
index a62f50c..755ccbe 100644 (file)
@@ -3604,12 +3604,17 @@ See also:
 'recentchanges-summary' => 'Summary of [[Special:RecentChanges]].',
 'recentchanges-noresult' => 'Used in [[Special:RecentChanges]], [[Special:RecentChangesLinked]], and [[Special:Watchlist]] when there are no changes to be shown.',
 'recentchanges-feed-description' => 'Used in feed of RecentChanges. See example [{{canonicalurl:Special:RecentChanges|feed=atom}} feed].',
-'recentchanges-label-newpage' => 'Tooltip for {{msg-mw|newpageletter}}',
-'recentchanges-label-minor' => 'Tooltip for {{msg-mw|minoreditletter}}',
-'recentchanges-label-bot' => 'Tooltip for {{msg-mw|boteditletter}}',
+'recentchanges-label-newpage' => '# Used as tooltip for {{msg-mw|Newpageletter}}.
+# Also used as legend. Preceded by {{msg-mw|Newpageletter}} and followed by {{msg-mw|Recentchanges-legend-newpage}}.',
+'recentchanges-label-minor' => '# Used as tooltip for {{msg-mw|Minoreditletter}}
+# Also used as legend. Preceded by {{msg-mw|Minoreditletter}}',
+'recentchanges-label-bot' => '# Used as tooltip for {{msg-mw|Boteditletter}}
+# Also used as legend. Preceded by {{msg-mw|Boteditletter}}',
 'recentchanges-label-unpatrolled' => 'Tooltip for {{msg-mw|unpatrolledletter}}',
-'recentchanges-label-plusminus' => 'Legend item for plus/minus',
-'recentchanges-legend-newpage' => 'A link to [[Special:NewPages]]',
+'recentchanges-label-plusminus' => 'Legend item for plus/minus.
+
+Preceded by legend example {{msg-mw|Recentchanges-legend-plusminus}}.',
+'recentchanges-legend-newpage' => 'Used as legend in [[Special:RecentChanges]]. Preceded by {{msg-mw|Recentchanges-label-newpage}}.',
 'recentchanges-legend-plusminus' => 'A plus/minus sign with a number for the legend.',
 'rcnote' => 'Used on [[Special:RecentChanges]].
 
@@ -3643,22 +3648,22 @@ Parameters:
 * $1 - a link to the revision of a specific date and time. The date and the time are the link description (without split of date and time, [[bugzilla:19104|Bug 19104]]).
 
 The corresponding message is {{msg-mw|Rcnotefrom}}.',
-'rcshowhideminor' => "Option text in [[Special:RecentChanges]]. Parameters:
-* $1 is the 'show/hide' command, with the text taken from either {{msg-mw|show}} or {{msg-mw|hide}}.",
-'rcshowhidebots' => "Option text in [[Special:RecentChanges]]. Parameters:
-* $1 is the 'show/hide' command, with the text taken from either {{msg-mw|show}} or {{msg-mw|hide}}.
-{{Identical|$1 bots}}",
+'rcshowhideminor' => 'Option text in [[Special:RecentChanges]]. Parameters:
+* $1 - the "show/hide" command, with the text taken from either {{msg-mw|Show}} or {{msg-mw|Hide}}',
+'rcshowhidebots' => 'Option text in [[Special:RecentChanges]]. Parameters:
+* $1 - the "show/hide" command, with the text taken from either {{msg-mw|Show}} or {{msg-mw|Hide}}
+{{Identical|$1 bots}}',
 'rcshowhideliu' => 'Option text in [[Special:RecentChanges]]. Parameters:
 * $1 - any one of the following messages:
 ** {{msg-mw|Show}}
 ** {{msg-mw|Hide}}',
-'rcshowhideanons' => "Option text in [[Special:RecentChanges]]. Parameters:
-* $1 is the 'show/hide' command, with the text taken from either {{msg-mw|show}} or {{msg-mw|hide}}.
-{{Identical|Anonymous user}}",
-'rcshowhidepatr' => "Option text in [[Special:RecentChanges]]. Parameters:
-* $1 is the 'show/hide' command, with the text taken from either {{msg-mw|show}} or {{msg-mw|hide}}.",
-'rcshowhidemine' => "Option text in [[Special:RecentChanges]]. Parameters:
-* $1 is the 'show/hide' command, with the text taken from either {{msg-mw|show}} or {{msg-mw|hide}}.",
+'rcshowhideanons' => 'Option text in [[Special:RecentChanges]]. Parameters:
+* $1 - the "show/hide" command, with the text taken from either {{msg-mw|Show}} or {{msg-mw|Hide}}
+{{Identical|Anonymous user}}',
+'rcshowhidepatr' => 'Option text in [[Special:RecentChanges]]. Parameters:
+* $1 - the "show/hide" command, with the text taken from either {{msg-mw|Show}} or {{msg-mw|Hide}}',
+'rcshowhidemine' => 'Option text in [[Special:RecentChanges]]. Parameters:
+* $1 - the "show/hide" command, with the text taken from either {{msg-mw|Show}} or {{msg-mw|Hide}}',
 'rclinks' => "Used on [[Special:RecentChanges]].
 * \$1 - a list of different choices with number of pages to be shown.<br />&nbsp;Example: \"''50{{int:pipe-separator}}100{{int:pipe-separator}}250{{int:pipe-separator}}500\".
 * \$2 - a list of clickable links with a number of days for which recent changes are to be displayed.<br />&nbsp;Example: \"''1{{int:pipe-separator}}3{{int:pipe-separator}}7{{int:pipe-separator}}14{{int:pipe-separator}}30''\".
@@ -3849,8 +3854,8 @@ See also:
 'badfilename' => 'Parameters:
 * $1 - filename',
 'filetype-mime-mismatch' => 'Upload error. Parameters:
-* $1 is the extension of the uploaded file
-* $2 is the MIME type of the uploaded file',
+* $1 - the extension of the uploaded file
+* $2 - the MIME type of the uploaded file',
 'filetype-badmime' => 'Parameters:
 * $1 - string representing the MIME type',
 'filetype-bad-ie-mime' => 'Parameters:
@@ -4176,11 +4181,10 @@ See also:
 * {{msg-mw|upload-warning-subj|subject}}
 * {{msg-mw|upload-warning-msg|message}}',
 'upload-warning-msg' => 'Used as warning body which is posted on the user talk page. Parameters:
-* $1 is the URL the file was uploaded from, when using upload-by-URL
-* $2 is the session key for the upload
+* $1 - the URL the file was uploaded from, when using upload-by-URL
+* $2 - the session key for the upload
 See also:
-* {{msg-mw|upload-warning-subj|subject}}
-* {{msg-mw|upload-warning-msg|message}}',
+* {{msg-mw|Upload-warning-subj|subject}}',
 
 'upload-proto-error' => 'See also:
 * {{msg-mw|Upload-proto-error|title}}
index ae90b90..0c49e3c 100644 (file)
@@ -12,6 +12,7 @@
  * @author Geitost
  * @author Helix84
  * @author Kaganer
+ * @author KuboF
  * @author Kusavica
  * @author Liso
  * @author Maros
@@ -1260,8 +1261,8 @@ Iní správcovia {{GRAMMAR:genitív|{{SITENAME}}}} budú stále môcť pristupov
 'revdelete-hide-user' => 'Používateľské meno/IP redaktora',
 'revdelete-hide-restricted' => 'Zatajiť údaje pred všetkými, aj pred správcami',
 'revdelete-radio-same' => '(nezmeniť)',
-'revdelete-radio-set' => 'Viditeľné',
-'revdelete-radio-unset' => 'Skryté',
+'revdelete-radio-set' => 'Skrytý',
+'revdelete-radio-unset' => 'Viditeľný',
 'revdelete-suppress' => 'Skryť údaje pred správcami rovnako ako pred ostatnými',
 'revdelete-unsuppress' => 'Odstrániť obmedzenia obnovených revízií',
 'revdelete-log' => 'Dôvod:',
@@ -1710,7 +1711,7 @@ Softvér používa toto nastavenie na správne oslovenie a označenie vás ostat
 'recentchanges-label-minor' => 'Toto je drobná úprava',
 'recentchanges-label-bot' => 'Túto úpravy vykonal robot',
 'recentchanges-label-unpatrolled' => 'Táto úprava zatiaľ nebola strážená',
-'recentchanges-legend-newpage' => '$1 - nová stránka',
+'recentchanges-legend-newpage' => '(pozri tiež [[Special:NewPages|zoznam nových stránok]])',
 'rcnote' => "Tu {{PLURAL:$1|je posledná úprava|sú posledné '''$1''' úpravy|je posledných '''$1''' úprav}} počas {{PLURAL:$2|posledného dňa|posledných '''$2''' dní}} z $4, $5.",
 'rcnotefrom' => "Nižšie sú zobrazené úpravy od '''$2''' (do '''$1''').",
 'rclistfrom' => 'Zobraziť nové úpravy počnúc od $1',
@@ -3270,7 +3271,7 @@ Jeho spustením môžete kompromitovať svoj systém.",
 'svg-long-desc' => 'SVG súbor, $1 × $2 pixelov, veľkosť súboru: $3',
 'svg-long-desc-animated' => 'Animovaný súbor SVG, nominálne $1 × $2 pixlov, veľkosť súboru: $3',
 'svg-long-error' => 'Neplatný súbor SVG: $1',
-'show-big-image' => 'Obrázok vo vyššom rozlíšení',
+'show-big-image' => 'Pôvodný súbor',
 'show-big-image-preview' => 'Veľkosť tohto náhľadu: $1.',
 'show-big-image-other' => 'Iné {{PLURAL:$2|rozlíšenie|rozlíšenia}}: $1 .',
 'show-big-image-size' => '$1 × $2 pixlov',
index 4b5242a..ae96c21 100644 (file)
@@ -1768,10 +1768,10 @@ $1",
 'action-createtalk' => 'прављење страница за разговор',
 'action-createaccount' => 'отварање овог корисничког налога',
 'action-minoredit' => 'означавање ове измене као мање',
-'action-move' => 'пÑ\80емеÑ\88Ñ\82аÑ\9aе Ð¾Ð²Ðµ Ñ\81Ñ\82Ñ\80аниÑ\86е',
-'action-move-subpages' => 'пÑ\80емеÑ\88Ñ\82аÑ\9aе Ð¾Ð²Ðµ Ñ\81Ñ\82Ñ\80аниÑ\86е Ð¸ Ñ\9aениÑ\85 Ð¿Ð¾Ð´Ñ\81Ñ\82Ñ\80аниÑ\86а',
+'action-move' => 'пÑ\80емеÑ\81Ñ\82и Ð¾Ð²Ñ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83',
+'action-move-subpages' => 'пÑ\80емеÑ\81Ñ\82и Ð¾Ð²Ñ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, ÐºÐ°Ð¾ Ð¸ Ñ\9aене Ð¿Ð¾Ð´Ñ\81Ñ\82Ñ\80аниÑ\86е',
 'action-move-rootuserpages' => 'премештање основних корисничких страница',
-'action-movefile' => 'пÑ\80емеÑ\88Ñ\82аÑ\9aе Ð¾Ð²Ðµ Ð´Ð°Ñ\82оÑ\82еке',
+'action-movefile' => 'пÑ\80емеÑ\81Ñ\82и Ð¾Ð²Ñ\83 Ð´Ð°Ñ\82оÑ\82екÑ\83',
 'action-upload' => 'слање ове датотеке',
 'action-reupload' => 'замењивање постојеће датотеке',
 'action-reupload-shared' => 'постављање ове датотеке на заједничко складиште',
index 150456d..9ace5d3 100644 (file)
@@ -1647,10 +1647,10 @@ Ako izaberete da ga unesete, ono će biti korišćeno za pripisivanje vašeg rad
 'action-createtalk' => 'pravljenje stranica za razgovor',
 'action-createaccount' => 'otvaranje ovog korisničkog naloga',
 'action-minoredit' => 'označavanje ove izmene kao manje',
-'action-move' => 'premeštanje ove stranice',
-'action-move-subpages' => 'premeštanje ove stranice i njenih podstranica',
+'action-move' => 'premesti ovu stranicu',
+'action-move-subpages' => 'premesti ovu stranicu, kao i njene podstranice',
 'action-move-rootuserpages' => 'premeštanje osnovnih korisničkih stranica',
-'action-movefile' => 'premeštanje ove datoteke',
+'action-movefile' => 'premesti ovu datoteku',
 'action-upload' => 'slanje ove datoteke',
 'action-reupload' => 'zamenjivanje postojeće datoteke',
 'action-reupload-shared' => 'postavljanje ove datoteke na zajedničko skladište',
index 21c80f1..d09048f 100644 (file)
@@ -1607,6 +1607,8 @@ Diğer kullanıcılar sizinle bu yolla iletişime geçtiğinde e-posta adresiniz
 'userrights-notallowed' => 'Kullanıcı hakları eklemek veya kaldırmak için izniniz yok.',
 'userrights-changeable-col' => 'Değiştirebildiğiniz gruplar',
 'userrights-unchangeable-col' => 'Değiştirebilmediğiniz gruplar',
+'userrights-conflict' => 'Kullanıcı hakları değişikliklerinde çakışma! Lütfen değişikliklerinizi gözden geçirin ve onaylayın.',
+'userrights-removed-self' => 'Kendi haklarınız başarıyla kaldırıldı. Bu nedenle, artık bu sayfaya erişemeyeceksiniz.',
 
 # Groups
 'group' => 'Grup:',
@@ -1657,6 +1659,7 @@ Diğer kullanıcılar sizinle bu yolla iletişime geçtiğinde e-posta adresiniz
 'right-writeapi' => 'API yaz kullanımı',
 'right-delete' => 'Sayfaları sil',
 'right-bigdelete' => 'Uzun tarihli sayfaları sil',
+'right-deletelogentry' => 'Belirli günlük girdilerini sil ve geri getir',
 'right-deleterevision' => 'Sayfaların belirli revizyonlarını sil ve geri yükle',
 'right-deletedhistory' => 'Silinmiş geçmiş girdilerini gör, ilgili metinleri olmadan',
 'right-deletedtext' => 'Silinmiş metni ve silinmiş revizyonlar arasındaki değişiklikleri gör',
@@ -1672,10 +1675,13 @@ Diğer kullanıcılar sizinle bu yolla iletişime geçtiğinde e-posta adresiniz
 'right-unblockself' => 'Kendi engellemesini kaldır',
 'right-protect' => 'Koruma düzeylerini değiştir ve kademeli korumalı sayfaları düzenle',
 'right-editprotected' => '"{{int:protect-level-sysop}}" olarak korunan sayfalarda değişiklik yap',
+'right-editsemiprotected' => '"{{int:protect-level-autoconfirmed}}" olarak korunan sayfalarda değişiklik yap',
 'right-editinterface' => 'Kullanıcı arayüzünü değiştirmek',
 'right-editusercssjs' => 'Diğer kullanıcıların CSS ve JS dosyalarında değişiklik yap',
 'right-editusercss' => 'Diğer kullanıcıların CSS dosyalarında değişiklik yap',
 'right-edituserjs' => 'Diğer kullanıcıların JS dosyalarında değişiklik yap',
+'right-editmyusercss' => 'Kendi kullanıcı CSS dosyaları düzenle',
+'right-editmyuserjs' => 'Kendi kullanıcı JavaScript dosyalarını düzenle',
 'right-viewmywatchlist' => 'Kendi izleme listeni gör',
 'right-editmywatchlist' => 'Kendi izleme listeni düzenle. Not, bazı eylemler bu yetki olmadan da sayfa ekleyebilir.',
 'right-viewmyprivateinfo' => 'Kendi özel bilgilerini görüntüle (e-posta adresi, gerçek isim vb.)',
@@ -1803,7 +1809,7 @@ Diğer kullanıcılar sizinle bu yolla iletişime geçtiğinde e-posta adresiniz
 'reuploaddesc' => 'Yükleme formuna geri dön.',
 'upload-tryagain' => 'Değiştirilmiş dosya açıklamasını gönder',
 'uploadnologin' => 'Oturum açık değil',
-'uploadnologintext' => 'Dosya yükleyebilmek için [[$1|oturum aç]]manız gerekiyor.',
+'uploadnologintext' => 'Dosya yükleyebilmek için $1manız gerekiyor.',
 'upload_directory_missing' => 'Yükleme dizini ($1) kayıp ve websunucusu tarafından oluşturulamıyor.',
 'upload_directory_read_only' => 'Dosya yükleme dizinine ($1) web sunucusunun yazma izni yok.',
 'uploaderror' => 'Yükleme hatası',
@@ -1947,6 +1953,7 @@ Eğer sorun tekrarlanırsa, bir [[Special:ListUsers/sysop|hizmetli]] ile temasa
 'backend-fail-stream' => '$1 dosyası okunamadı.',
 'backend-fail-backup' => '"$1" dosyası yedeklenemedi.',
 'backend-fail-notexists' => '$1 dosyası mevcut değil.',
+'backend-fail-hashes' => 'Karşılaştırma için dosya sağlamaları alınamadı.',
 'backend-fail-notsame' => 'Aynı olmayan bir dosya "$1" konumunda zaten var.',
 'backend-fail-invalidpath' => '"$1" geçerli bir depolama yolu değil.',
 'backend-fail-delete' => '"$1" dosyası silinemedi.',
@@ -1961,6 +1968,15 @@ Eğer sorun tekrarlanırsa, bir [[Special:ListUsers/sysop|hizmetli]] ile temasa
 'backend-fail-read' => '$1 dosyası okunamadı.',
 'backend-fail-create' => '$1 dosyası yazılamadı.',
 'backend-fail-maxsize' => 'Dosya {{PLURAL:$2|bir bayt|$2 bayt}} daha büyük olduğu için "$1" dosyasına yazılamadı.',
+'backend-fail-readonly' => 'Depolama arkaplan uygulaması "$1" şu anda salt okunur. Verilen gerekçe: "\'\'$2\'\'"',
+'backend-fail-connect' => '"$1" depolama arkaplan uygulamasına bağlanılamıyor.',
+'backend-fail-internal' => '"$1" depolama arkaplan uygulamasında bilinmeyen bir hata oluştu.',
+'backend-fail-contenttype' => '"$1" konumunda saklanan dosyanın içerik türü belirlenemiyor.',
+'backend-fail-usable' => 'Yetersiz izinlerden ya da eksik dizin/konteynerlerden dolayı "$1" dosyası okunup yazılamıyor.',
+
+# File journal errors
+'filejournal-fail-dbconnect' => '"$1" depolama arkaplan uygulaması için günlük veri tabanına erişilemiyor.',
+'filejournal-fail-dbquery' => '"$1" depolama arkaplan uygulaması için günlük veri tabanı güncellenemiyor.',
 
 # Lock manager
 'lockmanager-notlocked' => '"$1" kilidi açılamıyor; kilitli değil.',
@@ -2473,7 +2489,7 @@ Editörün iletişim bilgileri:
 e-posta: $PAGEEDITOR_EMAIL
 viki: $PAGEEDITOR_WIKI
 
-Bahsi geçen sayfayı ziyaret edinceye kadar sayfayla ilgili başka bildirim gönderilmeyecektir. Ayrıca izleme listenizdeki tüm sayfaların bildirim durumlarını sıfırlayabilirsiniz.
+Bahsi geçen sayfayı oturum açarak ziyaret edinceye kadar sayfayla ilgili başka bildirim gönderilmeyecektir. Ayrıca izleme listenizdeki tüm sayfaların bildirim durumlarını sıfırlayabilirsiniz.
 
 {{SITENAME}} bildirim sistemi
 
@@ -2533,6 +2549,7 @@ dikkatle devam edin.',
 'rollback_short' => 'geri al',
 'rollbacklink' => 'geri döndür',
 'rollbacklinkcount' => '$1 {{PLURAL:$1|değişikliği|değişikliği}} geri döndür',
+'rollbacklinkcount-morethan' => '$1 {{PLURAL:$1|değişiklikten|değişiklikten}} fazla geri döndür',
 'rollbackfailed' => 'geri alma işlemi başarısız',
 'cantrollback' => 'Sayfaya son katkıda bulunan kullanıcı, sayfaya katkıda bulunmuş tek kişi olduğu için, değişiklikler geri alınamıyor.',
 'alreadyrolled' => '[[User:$2|$2]] ([[User talk:$2|Talk]]{{int:pipe-separator}}[[Special:Contributions/$2|{{int:contribslink}}]]) tarafından [[:$1]] sayfasında yapılmış son değişiklik geriye alınamıyor;
@@ -2541,7 +2558,7 @@ başka biri sayfada değişiklik yaptı ya da sayfayı geriye aldı.
 Son değişikliği yapan: [[User:$3|$3]] ([[User talk:$3|Talk]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]).',
 'editcomment' => "Değişiklik özeti: \"''\$1''\" idi.",
 'revertpage' => '[[Special:Contributions/$2|$2]] [[User talk:$2|mesaj]] tarafından yapılan değişiklikler geri alınarak, [[User:$1|$1]] tarafından değiştirilmiş önceki sürüm geri getirildi.',
-'revertpage-nouser' => '(kullanıcı adı çıkarılmış) tarafından yapılan değişiklikler [[User:$1|$1]] tarafından yapılan son revizyona geri alındı',
+'revertpage-nouser' => 'Gizli bir kullanıcı tarafından yapılan değişiklikler geri alınarak {{GENDER:$1|[[User:$1|$1]]}} tarafından yapılan son revizyon geri getirildi',
 'rollback-success' => '$1 tarafından yapılan değişiklikler geri alınarak;
 $2 tarafından değiştirilmiş önceki sürüme geri dönüldü.',
 
@@ -2562,7 +2579,9 @@ Lütfen "geri" gidin ve geldiğiniz sayfayı yeniden yükleyin, sonra tekrar den
 'protect-title' => '"$1" için bir koruma seviyesi seçiniz',
 'protect-title-notallowed' => '"$1" için koruma seviyesini görüntüleyin',
 'prot_1movedto2' => '[[$1]] sayfasının yeni adı: [[$2]]',
+'protect-badnamespace-title' => 'Korumaya alınamayan ad alanı',
 'protect-badnamespace-text' => 'Bu ad alanındaki sayfalar korunamaz.',
+'protect-norestrictiontypes-text' => 'Bu sayfa mevcut kısıtlama türü bulunmadığı için korumaya alınamıyor.',
 'protect-norestrictiontypes-title' => 'Korumaya alınamayan sayfa',
 'protect-legend' => 'Korumayı onayla',
 'protectcomment' => 'Sebep:',
@@ -2626,7 +2645,8 @@ Bu sayfanın koruma seviyesini değiştirebilirsiniz; ancak bu kademeli korumaya
 'undeletepagetext' => 'Aşağıdaki {{PLURAL:$1|sayfa|$1 sayfa}} silinmiştir ama hala arşivdedir ve geri getirilebilir.
 Arşiv düzenli olarak temizlenebilir.',
 'undelete-fieldset-title' => 'Revizyonları geri yükle',
-'undeleteextrahelp' => "Sayfalarla birlikte geçmişi geri getirmek için onay kutularına dokunmadan '''Geri getir!''' tuşuna tıklayın. Sayfanın geçmişini ayrı ayrı getirmek için geri getirmek istediğiniz değişikliklerin onay kutularını seçip '''Geri getir!''' tuşuna tıklayın. Seçilen onay kutularını ve neden alanını sıfırlamak için '''Vazgeç''' tuşuna tıklayın.",
+'undeleteextrahelp' => "Sayfanın tüm geçmişini geri getirmek için onay kutularını boş bırakarak '''''{{int:undeletebtn}}''''' tuşuna tıklayın.
+Sayfanın geçmişini ayrı ayrı getirmek için geri getirmek istediğiniz değişikliklerin onay kutularını seçip '''''{{int:undeletebtn}}''''' tuşuna tıklayın.",
 'undeleterevisions' => '$1 {{PLURAL:$1|revizyon|revizyon}} arşivlendi',
 'undeletehistory' => 'Eğer sayfayı geri getirirseniz, tüm revizyonlar geçmişe geri getirilecektir.
 Silindikten sonra aynı isimle yeni bir sayfa oluşturulmuşsa, geri gelen revizyonlar varolan sayfanın geçmişinde görünecektir.',
@@ -2996,8 +3016,12 @@ Genel MediaWiki yerelleştirmesine katkıda bulunmak isterseniz, lütfen [https:
 'thumbnail-more' => 'Büyüt',
 'filemissing' => 'Dosya bulunmadı',
 'thumbnail_error' => 'Küçük resim oluşturmada hata: $1',
+'thumbnail_error_remote' => '$1 için hata mesajı:
+$2',
 'djvu_page_error' => 'DjVu sayfası kapsamdışı',
 'djvu_no_xml' => 'DjVu dosyası için XML alınamıyor',
+'thumbnail-temp-create' => 'Geçici küçük resim dosyası oluşturulamıyor',
+'thumbnail-dest-create' => 'Küçük resim hedefe kaydedilemiyor',
 'thumbnail_invalid_params' => 'Geçersiz küçük resim parametreleri',
 'thumbnail_dest_directory' => 'Hedef dizini oluşturulamıyor',
 'thumbnail_image-type' => 'Görüntü türü desteklenmiyor',
@@ -3046,6 +3070,14 @@ Geçici dosya kayıp.',
 'import-upload' => 'XML bilgileri yükle',
 'import-token-mismatch' => 'Oturum verisi kaybı. Lütfen yeniden deneyin.',
 'import-invalid-interwiki' => 'Belirtilen vikiden içe aktarım yapılamaz.',
+'import-error-edit' => '"$1" sayfası içe aktarılamadı çünkü sayfayı değiştirmeye yetkiniz yok.',
+'import-error-create' => '"$1" sayfası içe aktarılamadı çünkü sayfayı oluşturmaya yetkiniz yok.',
+'import-error-interwiki' => '"$1" sayfası içe aktarılamadı çünkü sayfanın adı dış bağlantı için ayrılmış (vikilerarası).',
+'import-error-special' => '"$1" sayfası içe aktarılamadı çünkü sayfalara izin vermeyen özel bir ad alanına ait.',
+'import-error-invalid' => '"$1" sayfası içe aktarılamadı çünkü sayfa adı geçersiz.',
+'import-options-wrong' => 'Yanlış {{PLURAL:$2|seçenek|seçenek}}: <nowiki>$1</nowiki>',
+'import-rootpage-invalid' => 'Verilen kök sayfa geçersiz bir başlık.',
+'import-rootpage-nosubpage' => 'Kök sayfanın "$1" ad alanı alt sayfalara izin vermiyor.',
 
 # Import log
 'importlogpage' => 'Aktarım günlüğü',
@@ -3058,6 +3090,10 @@ Geçici dosya kayıp.',
 # JavaScriptTest
 'javascripttest' => 'JavaScript denemesi',
 'javascripttest-title' => '$1 testleri çalışıyor',
+'javascripttest-pagetext-noframework' => 'Bu sayfa JavaScript testleri çalıştırmak için ayrılmıştır.',
+'javascripttest-pagetext-unknownframework' => 'Bilinmeyen test çerçevesi "$1".',
+'javascripttest-pagetext-frameworks' => 'Lütfen aşağıdaki test çerçevelerinden birini seçin: $1',
+'javascripttest-pagetext-skins' => 'Testleri koşmak için bir tema seçin:',
 'javascripttest-qunit-intro' => 'mediawiki.org üzerinden [$1 deneme belgelerine] bakınız.',
 'javascripttest-qunit-heading' => 'MediaWiki JavaScript QUnit deneme paketi',
 
@@ -3159,11 +3195,13 @@ Geçici dosya kayıp.',
 'spambot_username' => 'Medyaviki spam temizleme',
 'spam_reverting' => '$1 ile bağlantı içermeyen son sürüme geri dönülüyor',
 'spam_blanking' => 'Tüm revizyonlar $1 sayfasına bağlantı içeriyor, boşaltılıyor',
+'spam_deleting' => 'Tüm revizyonlar $1 sayfasına bağlantı içeriyor, siliniyor',
 'simpleantispam-label' => "Anti-spam denetimi.
-Bunu '''doldurmayın'''!",
+Bunu '''doldurMAyın'''!",
 
 # Info page
 'pageinfo-title' => 'Bilgi için "$1"',
+'pageinfo-not-current' => 'Üzgünüz, eski sürümler için bu bilgileri sağlamamız mümkün değildir.',
 'pageinfo-header-basic' => 'Temel bilgiler',
 'pageinfo-header-edits' => 'Düzenleme geçmişi',
 'pageinfo-header-restrictions' => 'Sayfa koruması',
@@ -3178,9 +3216,11 @@ Bunu '''doldurmayın'''!",
 'pageinfo-robot-noindex' => 'İzin verilmedi',
 'pageinfo-views' => 'Görüntülenme sayısı',
 'pageinfo-watchers' => 'Sayfanın izleyici sayısı',
+'pageinfo-few-watchers' => '$1 {{PLURAL:$1|izleyiciden|izleyiciden}} az',
 'pageinfo-redirects-name' => 'Bu sayfaya yönlendirme sayısı',
 'pageinfo-redirects-value' => '$1',
 'pageinfo-subpages-name' => 'Bu sayfanın alt sayfaları',
+'pageinfo-subpages-value' => '$1 ($2 {{PLURAL:$2|yönlendirme|yönlendirme}}; $3 {{PLURAL:$3|yönlendirme olmayan|non-yönlendirme olmayan}})',
 'pageinfo-firstuser' => 'Sayfa oluşturucu',
 'pageinfo-firsttime' => 'Sayfa oluşturulma tarihi',
 'pageinfo-lastuser' => 'En son düzenleyici',
@@ -3217,6 +3257,8 @@ Bunu '''doldurmayın'''!",
 'markedaspatrollederror' => 'Kontrol edilmedi',
 'markedaspatrollederrortext' => 'Gözlenmiş olarak işaretlemek için bir revizyon belirtmelisiniz.',
 'markedaspatrollederror-noautopatrol' => 'Kendi değişikliklerinizi kontrol edilmiş olarak işaretleyemezsiniz.',
+'markedaspatrollednotify' => '$1 için bu değişiklik kontrol edildi olarak işaretlendi.',
+'markedaspatrollederrornotify' => 'Kontrol edildi olarak işaretleme başarısız oldu.',
 
 # Patrol log
 'patrol-log-page' => 'Kontrol kaydı',
@@ -3260,6 +3302,8 @@ Bunu çalıştırmak, sisteminizi tehlikeye atabilir.",
 'file-info-png-looped' => 'döngüye girdi',
 'file-info-png-repeat' => '$1 {{PLURAL:$1|defa|defa}} oynatıldı',
 'file-info-png-frames' => '$1 {{PLURAL:$1|frame|frames}}',
+'file-no-thumb-animation' => "'''Not: Teknik sınırlamalar nedeniyle, bu dosyanın küçük resimlerinde animasyon yoktur.'''",
+'file-no-thumb-animation-gif' => "'''Not: Teknik sınırlamalar nedeniyle, bu gibi yüksek çözünürlüklü GIF resimlerinin küçük resimlerinde animasyon yoktur.'''",
 
 # Special:NewFiles
 'newimages' => 'Yeni dosya galerisi',
@@ -3449,13 +3493,18 @@ Diğerleri varsayılan olarak gizlenecektir.
 'exif-gpsdifferential' => 'GPS differential correction',
 'exif-jpegfilecomment' => 'JPEG dosyası yorumu',
 'exif-keywords' => 'Anahtar kelimeler',
+'exif-worldregioncreated' => 'Resmin çekildiği dünya bölgesi',
 'exif-countrycreated' => 'Resmin alındığı ülke',
+'exif-countrycodecreated' => 'Resmin çekildiği ülke kodu',
+'exif-provinceorstatecreated' => 'Resmin çekildiği eyalet ya da il',
 'exif-citycreated' => 'Resmin alındığı şehir',
+'exif-sublocationcreated' => 'Resmin çekildiği şehrin alt bölgesi',
 'exif-worldregiondest' => 'Gösterilen bölge',
 'exif-countrydest' => 'Gösterilen ülke',
 'exif-countrycodedest' => 'Gösterilen ülke kodu',
 'exif-provinceorstatedest' => 'Gösterilen il ya da devlet/eyalet',
 'exif-citydest' => 'Gösterilen Şehir',
+'exif-sublocationdest' => 'Şehrin alt bölgesi gösteriliyor',
 'exif-objectname' => 'Kısa başlık',
 'exif-specialinstructions' => 'Özel talimatlar',
 'exif-headline' => 'Başlık',
@@ -3465,27 +3514,35 @@ Diğerleri varsayılan olarak gizlenecektir.
 'exif-urgency' => 'Aciliyet',
 'exif-fixtureidentifier' => 'Fikstür adı',
 'exif-locationdest' => 'Yerin konumu',
+'exif-locationdestcode' => 'Konumun kodu tanımlandı',
+'exif-objectcycle' => 'Ortamın planlandığı günün saati',
 'exif-contact' => 'İletişim bilgileri',
 'exif-writer' => 'Yazar',
 'exif-languagecode' => 'Dil',
 'exif-iimversion' => 'IIM sürümü',
 'exif-iimcategory' => 'Kategori',
+'exif-iimsupplementalcategory' => 'Tamamlayıcı kategoriler',
 'exif-datetimeexpires' => 'Bu tarihten sonra kullanmayın:',
 'exif-datetimereleased' => 'Tarihinde yayınlandı',
+'exif-originaltransmissionref' => 'Orijinal iletim konum kodu',
 'exif-identifier' => 'Tanımlayıcı',
 'exif-lens' => 'Kullanılan objektif',
 'exif-serialnumber' => 'Kameranın seri numarası',
 'exif-cameraownername' => 'Kameranın sahibi',
 'exif-label' => 'Etiket',
+'exif-datetimemetadata' => 'Üstveri son değişim tarihi',
 'exif-nickname' => 'Görüntünün resmî olmayan adı',
 'exif-rating' => 'Oylama (5 üzerinden)',
+'exif-rightscertificate' => 'Hak yönetimi sertifikası',
 'exif-copyrighted' => 'Telif hakkı durumu',
 'exif-copyrightowner' => 'Telif hakkı sahibi',
 'exif-usageterms' => 'Kullanım şartları',
 'exif-webstatement' => 'Çevrimiçi telif hakkı bildirimi',
+'exif-originaldocumentid' => 'Özgün belgenin benzersiz kimliği',
 'exif-licenseurl' => 'Telif hakkı lisansı için URL',
 'exif-morepermissionsurl' => 'Alternatif lisans bilgileri',
 'exif-attributionurl' => 'Bu çalışmayı yeniden kullanırken lütfen bağlantı verin',
+'exif-preferredattributionname' => 'Bu çalışmayı yeniden kullanırken, lütfen atıf verin',
 'exif-pngfilecomment' => 'PNG dosyası yorumu',
 'exif-disclaimer' => 'Sorumluluk reddi',
 'exif-contentwarning' => 'İçerik uyarısı',
@@ -3501,6 +3558,8 @@ Diğerleri varsayılan olarak gizlenecektir.
 
 # Exif attributes
 'exif-compression-1' => 'Sıkıştırılmamış',
+'exif-compression-3' => 'CCITT Grup 3 faks kodlaması',
+'exif-compression-4' => 'CCITT Grup 4 faks kodlaması',
 'exif-compression-6' => 'JPEG',
 
 'exif-copyrighted-true' => 'Telif hakkı',
@@ -3784,13 +3843,13 @@ iptal etmek için aşağıdaki bağlantıyı takip edin:
 $5
 
 Bu onay kodu $4 tarihine kadar geçerli olacak.',
-'confirmemail_body_set' => 'Birisi $1 IP adresiyle {{SITENAME}} sitesinde "$2" kullanıcı hesabının e-posta adresi olarak bu e-posta adresini belirtti.
+'confirmemail_body_set' => 'Birisi, muhtemelen siz, $1 IP adresiyle {{SITENAME}} sitesinde "$2" kullanıcı hesabının e-posta adresi olarak bu e-posta adresini belirtti.
 
-Eğer bu işlemi yapan sizseniz ve {{SITENAME}} sitesindeki e-posta işlevlerini tekrar aktif etmek istiyorsanız alttaki bağlantıyı tarayıcınızda açmanız gerekiyor:
+Bu hesabın gerçekten size ait olduğunu onaylamak ve {{SITENAME}} sitesindeki e-posta işlevlerini aktif etmek için alttaki bağlantıyı tarayıcınızda açmanız gerekiyor:
 
 $3
 
-Eğer bu işlemi yapan siz değilseniz ve böyle bir üyeliğiniz yoksa e-posta onay işlemini iptal etmek için alttaki bağlantıyı tarayıcınızda açmanız gerekiyor:
+Eğer bu hesap size ait değilse, e-posta adresi onayını iptal etmek için alttaki bağlantıyı takip edin:
 
 $5
 
@@ -3920,6 +3979,7 @@ Ayrıca [[Special:EditWatchlist|standart düzenleme sayfasını]] da kullanabili
 'version-license' => 'Lisans',
 'version-poweredby-credits' => "Bu wiki '''[https://www.mediawiki.org/ MediaWiki]''' programı kullanılarak oluşturulmuştur, telif © 2001-$1 $2.",
 'version-poweredby-others' => 'diğerleri',
+'version-poweredby-translators' => 'translatewiki.net çevirmenleri',
 'version-license-info' => "MediaWiki özgür bir yazılımdır; MediaWiki'yi, Özgür Yazılım Vakfı tarafından yayımlanmış olan GNU Genel Kamu Lisansının 2. veya (seçeceğiniz) daha sonraki bir sürümünün koşulları altında yeniden dağıtabilir ve/veya değiştirebilirsiniz.
 
 MediaWiki yazılımı faydalı olacağı ümidiyle dağıtılmaktadır; ancak kastedilen SATILABİLİRLİK veya BELİRLİ BİR AMACA UYGUNLUK garantisi hariç HİÇBİR GARANTİSİ YOKTUR. Daha fazla ayrıntı için GNU Genel Kamu Lisansı'na bakınız.
@@ -3937,8 +3997,10 @@ Bu programla birlikte [{{SERVER}}{{SCRIPTPATH}}/COPYING GNU Genel Kamu Lisansın
 'redirect-legend' => 'Bir dosya veya sayfaya yönlendirme',
 'redirect-summary' => "Bu özel sayfa sizi bir dosya (dosya adı verilen), bir sayfa (bir revizyon ID'si verilen) veya bir kullanıcı sayfasının (sayısal kullanıcı kimliği verilen) adresine yönlendirir. Kullanım: [[{{#Special:Redirect}}/file/Example.jpg]], [[{{#Special:Redirect}}/revision/328429]], ya da  [[{{#Special:Redirect}}/user/101]].",
 'redirect-submit' => 'Git',
+'redirect-lookup' => 'Ara:',
 'redirect-value' => 'Değer:',
 'redirect-user' => 'Kullanıcı kimliği',
+'redirect-revision' => 'Sayfa revizyonu',
 'redirect-file' => 'Dosya adı',
 'redirect-not-exists' => 'Değer bulunamadı',
 
@@ -3989,6 +4051,7 @@ Bu programla birlikte [{{SERVER}}{{SCRIPTPATH}}/COPYING GNU Genel Kamu Lisansın
 'tags' => 'Geçerli değişiklik etiketleri',
 'tag-filter' => '[[Special:Tags|Etiket]] süzgeci:',
 'tag-filter-submit' => 'Süzgeç',
+'tag-list-wrapper' => '([[Special:Tags|{{PLURAL:$1|Etiket|Etiketler}}]]: $2)',
 'tags-title' => 'Etiketler',
 'tags-intro' => 'Bu sayfa, yazılımın bir değişikliği işaretleyebileceği etiketleri ve bunların anlamlarını listeler.',
 'tags-tag' => 'Etiket adı',
@@ -4018,6 +4081,7 @@ Bu programla birlikte [{{SERVER}}{{SCRIPTPATH}}/COPYING GNU Genel Kamu Lisansın
 'dberr-problems' => 'Üzgünüz! Bu site teknik zorluklar yaşıyor.',
 'dberr-again' => 'Bir kaç dakika bekleyip tekrar yüklemeyi deneyin.',
 'dberr-info' => '(Veritabanı sunucusuyla irtibat kurulamıyor: $1)',
+'dberr-info-hidden' => '(Veritabanı sunucusuna bağlantı kurulamıyor)',
 'dberr-usegoogle' => 'Bu zaman zarfında Google ile aramayı deneyebilirsiniz.',
 'dberr-outofdate' => 'İçeriğimizin onların dizinlerinde güncel olmayabileceğini dikkate alın.',
 'dberr-cachederror' => 'Aşağıdaki istenen sayfanın önbellekteki bir kopyasıdır, ve güncel olmayabilir.',
@@ -4035,6 +4099,7 @@ Bu programla birlikte [{{SERVER}}{{SCRIPTPATH}}/COPYING GNU Genel Kamu Lisansın
 'htmlform-selectorother-other' => 'Diğer',
 'htmlform-no' => 'Hayır',
 'htmlform-yes' => 'Evet',
+'htmlform-chosen-placeholder' => 'Bir seçenek seçin',
 
 # SQLite database support
 'sqlite-has-fts' => '$1 tam-metin arama desteği ile',
@@ -4043,10 +4108,10 @@ Bu programla birlikte [{{SERVER}}{{SCRIPTPATH}}/COPYING GNU Genel Kamu Lisansın
 # New logging system
 'logentry-delete-delete' => '$1 $3 sayfasını {{GENDER:$2|sildi}}',
 'logentry-delete-restore' => '$1 $3 sayfasını {{GENDER:$2|geri getirdi}}',
-'logentry-delete-revision' => '$1 $3: $4 sayfasında {{PLURAL:$5|bir sürümün|$5 sürümün}} görünürlüğünü değiştirdi',
-'logentry-delete-revision-legacy' => '$1 $3 sayfasındaki sürümlerin görünürlüğünü değiştirdi',
-'logentry-suppress-revision' => '$1 $3: $4 sayfasında {{PLURAL:$5|bir sürümün|$5 sürümün}} görünürlüğünü gizlice değiştirdi',
-'logentry-suppress-revision-legacy' => '$1 $3 sayfasındaki sürümlerin görünürlüğünü değiştirdi',
+'logentry-delete-revision' => '$1, $3 sayfasında {{PLURAL:$5|bir sürümün|$5 sürümün}} görünürlüğünü {{GENDER:$2|değiştirdi}}: $4',
+'logentry-delete-revision-legacy' => '$1 $3 sayfasındaki sürümlerin görünürlüğünü {{GENDER:$2|değiştirdi}}',
+'logentry-suppress-revision' => '$1, $3 sayfasında {{PLURAL:$5|bir sürümün|$5 sürümün}} görünürlüğünü gizlice {{GENDER:$2|değiştirdi}}: $4',
+'logentry-suppress-revision-legacy' => '$1, $3 sayfasındaki sürümlerin görünürlüğünü {{GENDER:$2|değiştirdi}}',
 'revdelete-content-hid' => 'Gizli içerik',
 'revdelete-summary-hid' => 'değişiklik özeti gizlenmiş',
 'revdelete-uname-hid' => 'kullanıcı adı gizli',
@@ -4055,10 +4120,10 @@ Bu programla birlikte [{{SERVER}}{{SCRIPTPATH}}/COPYING GNU Genel Kamu Lisansın
 'revdelete-uname-unhid' => 'kullanıcı adı gösterildi',
 'revdelete-restricted' => 'hizmetliler için uygulanmış kısıtlamalar',
 'revdelete-unrestricted' => 'hizmetliler için kaldırılmış kısıtlamalar',
-'logentry-move-move' => '$1 $3 sayfasını $4 sayfasına taşıdı',
-'logentry-move-move-noredirect' => '$1 $3 sayfasını $4 sayfasına yönlendirme olmaksızın taşıdı',
-'logentry-move-move_redir' => '$1 $3 sayfasını $4 sayfasına yönlendirme üzerinden taşıdı',
-'logentry-patrol-patrol-auto' => '$1 $3 sayfasını $4 sürümü ile kontrol etti',
+'logentry-move-move' => '$1, $3 sayfasını $4 sayfasına {{GENDER:$2|taşıdı}}',
+'logentry-move-move-noredirect' => '$1, $3 sayfasını $4 sayfasına yönlendirme olmaksızın {{GENDER:$2|taşıdı}}',
+'logentry-move-move_redir' => '$1, $3 sayfasını $4 sayfasına yönlendirme üzerinden {{GENDER:$2|taşıdı}}',
+'logentry-patrol-patrol-auto' => '$1, $3 sayfasının $4 sürümümü otomatik olarak {{GENDER:$2|kontrol etti}}',
 'logentry-newusers-newusers' => 'Kullanıcı hesabı $1 {{GENDER:$2|oluşturuldu}}',
 'logentry-newusers-create' => 'Kullanıcı hesabı $1 {{GENDER:$2|oluşturuldu}}',
 'logentry-newusers-create2' => '$3 kullanıcı hesabı $1 tarafından {{GENDER:$2|oluşturuldu}}',
@@ -4094,7 +4159,7 @@ Bu programla birlikte [{{SERVER}}{{SCRIPTPATH}}/COPYING GNU Genel Kamu Lisansın
 'api-error-file-too-large' => 'Gönderdiğiniz dosya çok büyük.',
 'api-error-filename-tooshort' => 'Dosya adı çok kısa.',
 'api-error-filetype-banned' => 'Bu dosya biçimi yasaklanmıştır.',
-'api-error-filetype-banned-type' => '$1 {{PLURAL:$4|izin verilen bir dosya türü değil|izin verilen bir dosya türü değil}}. İzin verilen {{PLURAL:$3|dosya türü|dosya türleri}} $2.',
+'api-error-filetype-banned-type' => '$1 {{PLURAL:$4|izin verilen bir dosya türü değil|izin verilen dosya türleri değil}}. İzin verilen {{PLURAL:$3|dosya türü|dosya türleri}} $2.',
 'api-error-filetype-missing' => 'Dosya uzantısı eksik.',
 'api-error-hookaborted' => 'Yapmaya çalıştığınız değişiklik bir eklenti tarafından iptal edildi.',
 'api-error-http' => 'İç hata: sunucu ile bağlantı kurulamıyor.',
@@ -4110,6 +4175,7 @@ Bu programla birlikte [{{SERVER}}{{SCRIPTPATH}}/COPYING GNU Genel Kamu Lisansın
 'api-error-ok-but-empty' => 'İç hata: Sunucu yanıt vermiyor.',
 'api-error-overwrite' => 'Varolan dosyanın üzerine yazmaya izin verilmiyor.',
 'api-error-stashfailed' => 'İç hata: Sunucu, geçici dosyaları kaybetti.',
+'api-error-publishfailed' => 'İç hata: Sunucu geçici dosyayı yayınlarken başarısız oldu.',
 'api-error-timeout' => 'Sunucu beklenen süre içinde yanıt vermedi.',
 'api-error-unclassified' => 'Bilinmeyen bir hata oluştu.',
 'api-error-unknown-code' => 'Bilinmeyen hata: "$1"',
@@ -4130,16 +4196,21 @@ Bu programla birlikte [{{SERVER}}{{SCRIPTPATH}}/COPYING GNU Genel Kamu Lisansın
 'duration-centuries' => '$1 {{PLURAL:$1|yüzyıl|yüzyıl}}',
 'duration-millennia' => '$1 {{PLURAL:$1|bin yıl|bin yıl}}',
 
+# Image rotation
+'rotate-comment' => 'Resim saat yönünde $1 {{PLURAL:$1|derece|derece}} çevrildi',
+
 # Limit report
 'limitreport-title' => 'Ayrıştırıcı profil verileri:',
 'limitreport-cputime' => 'CPU süresi kullanımı',
 'limitreport-cputime-value' => '$1 {{PLURAL:$1|saniye|saniye}}',
 'limitreport-walltime' => 'Gerçek süre kullanımı',
+'limitreport-walltime-value' => '$1 {{PLURAL:$1|saniye|saniye}}',
 'limitreport-ppvisitednodes' => 'Önişlemci düğümü ziyaret sayısı',
 'limitreport-ppgeneratednodes' => 'Önişlemcinin ürettiği düğüm sayısı',
 'limitreport-postexpandincludesize' => 'Gönderi genişliği boyutu dahil',
-'limitreport-postexpandincludesize-value' => '$1/$2 bayt',
+'limitreport-postexpandincludesize-value' => '$1/$2 {{PLURAL:$2|bayt|bayt}}',
 'limitreport-templateargumentsize' => 'Şablon değişkeni boyutu',
+'limitreport-templateargumentsize-value' => '$1/$2 {{PLURAL:$2|bayt|bayt}}',
 'limitreport-expansiondepth' => 'En yüksek genişleme derinliği',
 'limitreport-expensivefunctioncount' => 'Daha fazla ayrıştırıcı işlev sayısı',
 
index 081ee1a..45a1497 100644 (file)
@@ -98,9 +98,9 @@ $messages = array(
 'tog-usenewrc' => 'Kävutagat paremboitud tantoižed toižetused (pidab otta radho JavaScript)',
 'tog-numberheadings' => 'Nomeruida avtomatižikš pälkirjutesed',
 'tog-showtoolbar' => "Ozutada azegiden üläpanel' redaktiruindan aigan (JavaScript)",
-'tog-editondblclick' => 'Redaktiruida lehtpoled kaksitadud plokul (JavaScript)',
+'tog-editondblclick' => 'Redaktiruida lehtpoled kaksitadud plokul',
 'tog-editsection' => 'Ozutada "Redaktiruida"-kosketuz kaikuččen sekcijan täht',
-'tog-editsectiononrightclick' => 'Redaktiruida sekcijad hiren oiktal plokul pälkirjutesele (JavaScript)',
+'tog-editsectiononrightclick' => 'Redaktiruida sekcijad hiren oiktal plokul pälkirjutesele',
 'tog-showtoc' => 'Ozutada südäiolend (lehtpoled, kudambil om enamba, mi 3 pälkirjutest)',
 'tog-rememberpassword' => 'Muštta minun kävutajan nimi neciš kompjuteras (enintään $1 {{PLURAL:$1|päivä|päivää}})',
 'tog-watchcreations' => 'Ližata kaik minai sätud lehtpoled minun kaclendkirjuteshe',
@@ -193,6 +193,18 @@ $messages = array(
 'oct' => 'reduku',
 'nov' => 'kül’mku',
 'dec' => 'tal’vku',
+'january-date' => '$1. viluku',
+'february-date' => '$1. uhoku',
+'march-date' => "$1. keväz'ku",
+'april-date' => '$1. sulaku',
+'may-date' => '$1. semendku',
+'june-date' => '$1. kezaku',
+'july-date' => '$1. heinku',
+'august-date' => '$1. eloku',
+'september-date' => "$1. sügüz'ku",
+'october-date' => '$1. reduku',
+'november-date' => "$1. kül'mku",
+'december-date' => "$1. tal'vku",
 
 # Categories related messages
 'pagecategories' => '{{PLURAL:$1|Kategorii|Kategorijad}}',
@@ -218,7 +230,7 @@ $messages = array(
 'newwindow' => '(avaidase udes iknas)',
 'cancel' => 'Heitta pätand',
 'moredotdotdot' => 'Edeleze...',
-'mypage' => "Minun lehtpol'",
+'mypage' => "Lehtpol'",
 'mytalk' => 'Lodud',
 'anontalk' => 'Lodud neciš IP-adresas',
 'navigation' => 'Navigacii',
@@ -251,6 +263,7 @@ $messages = array(
 'namespaces' => 'Nimiavaruded',
 'variants' => 'Variantad',
 
+'navigation-heading' => 'Navigacii',
 'errorpagetitle' => 'Petuz',
 'returnto' => 'Pörttas lehtpolele $1.',
 'tagline' => '{{SITENAME}}',
@@ -288,7 +301,7 @@ $messages = array(
 'articlepage' => "Kacu südäimišton lehtpol'",
 'talk' => 'Diskussii',
 'views' => 'Kacundad',
-'toolbox' => 'Azegišt',
+'toolbox' => 'Instrumentad',
 'userpage' => "Kacu kävutajan lehtpol'",
 'projectpage' => "Kacu projektan lehtpol'",
 'imagepage' => "Kacu fajlan lehtpol'",
@@ -397,6 +410,7 @@ Kc. [[Special:SpecialPages|specialižiden lehtpoliden nimikirj]].",
 # General errors
 'error' => 'Petuz',
 'databaseerror' => 'Andmusiden bazan petuz',
+'databaseerror-error' => 'Petuz: $1',
 'laggedslavemode' => "Varutuz: voib olda, lehtpolen versijal ei ole jäl'gmäižid ližadusid.",
 'readonly' => 'Andmusiden baz om luklostadud',
 'enterlockreason' => 'Kirjutagat sü da pandud blokiruindan strok',
@@ -461,10 +475,18 @@ Sü om "\'\'$2\'\'".',
 
 Sab jatkta rad {{SITENAME}}-saital anonimižikš, vai <span class='plainlinks'>[$1 kirjutagatoiš udes]</span> sil-žo vai toižel kävutajan nimel.
 Otkat sil'mnägubale, miše erasid lehtpolid ozutaškatas mugažo, kut i edel teiden lähtendad sistemaspäi. Miše vajehtada niiden nägu, puhtastagat teiden kaclimen keš.",
+'welcomeuser' => 'Tulgat tervhin, $1!',
 'yourname' => 'Kävutajan nimi:',
+'userlogin-yourname' => 'Kävutajannimi',
+'createacct-another-username-ph' => 'Kirjutagat kävutajannimi',
 'yourpassword' => 'Peitsana:',
+'userlogin-yourpassword' => 'Peitsana',
+'createacct-yourpassword-ph' => 'Kirjutagat peitsana',
 'yourpasswordagain' => 'Kirjutagat peitsana udes:',
+'createacct-yourpasswordagain' => 'Peitsanan vahvištoituz',
+'createacct-yourpasswordagain-ph' => 'Kirjutagat peitsana toškerdan',
 'remembermypassword' => 'Panda muštho minun tulendandmused neciš kompjuteras (enintään $1 {{PLURAL:$1|päivä|päivää}})',
+'userlogin-remembermypassword' => 'Jäda sistemha',
 'yourdomainname' => 'Teiden domen:',
 'externaldberror' => 'Ozaižihe petuz autentifikacijan, kudamb tehtihe andmusiden irdbazan turbiš, aigan, vai teile ei ulotu oiktusid toižetada ičetoi irdregistracijad.',
 'login' => 'Kirjutadas sistemha',
@@ -483,6 +505,12 @@ Otkat sil'mnägubale, miše erasid lehtpolid ozutaškatas mugažo, kut i edel te
 'userlogin-resetlink' => 'Unohtid-ik andmused tulendan täht?',
 'createaccountmail' => 'e-počtaiči',
 'createaccountreason' => 'Sü:',
+'createacct-reason' => 'Sü',
+'createacct-reason-ph' => 'Mikš sädad kävutajanprofilid?',
+'createacct-captcha' => 'Varuitomuden kodvind',
+'createacct-imgcaptcha-ph' => 'Kirjutagat tekst pälpäi',
+'createacct-submit' => "Säta kävutajanprofil'",
+'createacct-another-submit' => "Säta toine kävutajanprofil'",
 'badretype' => 'Teil kirjutadud peitsanad ei kožugoi toine toižhe.',
 'userexists' => 'Kirjutadud kävutajan nimi om jo kävutamižes.
 Olgat hüväd, valikat toine kävutajan nimi.',
@@ -574,6 +602,9 @@ Aigaline peitsana: $2',
 'changeemail-submit' => 'Toižetada e-počtan adres',
 'changeemail-cancel' => 'Heitta',
 
+# Special:ResetTokens
+'resettokens-tokens' => 'Tokenad:',
+
 # Edit page toolbar
 'bold_sample' => 'Lihavoitud tekst',
 'bold_tip' => 'Lihavoitud tekst',
@@ -1044,7 +1075,7 @@ Kodvgat HTML-virgad.',
 Pidab tehta se $1 {{PLURAL:$1|simvolaspäi|simvoloišpäi}}.",
 'yourgender' => 'Sugu:',
 'gender-unknown' => 'Ei ole ozutadud',
-'gender-male' => 'Mez’',
+'gender-male' => "Mez'",
 'gender-female' => 'Naine',
 'prefs-help-gender' => 'Opcionaline: kävutadas likutimen erasiš tedotusiš, miše ozutada kävutajan sugu oikti. Nece informacii om avoin.',
 'email' => 'E-počt',
@@ -1059,6 +1090,7 @@ Ku tö kirjutat sen, nece nimi kävutadas, miše ozutada lehtpolen toižetajad.'
 'prefs-dateformat' => 'Datan format',
 'prefs-timeoffset' => 'Aigan sirdand',
 'prefs-advancedediting' => 'Ližaopcijad',
+'prefs-preview' => 'Ezikacund',
 'prefs-advancedrc' => 'Ližaopcijad',
 'prefs-advancedrendering' => 'Ližaopcijad',
 'prefs-advancedsearchoptions' => 'Ližaopcijad',
@@ -1066,6 +1098,7 @@ Ku tö kirjutat sen, nece nimi kävutadas, miše ozutada lehtpolen toižetajad.'
 'prefs-displayrc' => 'Nägun opcijad',
 'prefs-displaysearchoptions' => 'Nägun opcijad',
 'prefs-displaywatchlist' => 'Nägun opcijad',
+'prefs-tokenwatchlist' => 'Token',
 'prefs-diffs' => 'Erod',
 
 # User preference: email validation using jQuery
@@ -1216,6 +1249,7 @@ Ku tö kirjutat sen, nece nimi kävutadas, miše ozutada lehtpolen toižetajad.'
 
 # Recent changes
 'nchanges' => '$1 {{PLURAL:$1|toižetuz|toižetust}}',
+'enhancedrc-history' => 'istorii',
 'recentchanges' => 'Tantoižed toižetused',
 'recentchanges-legend' => 'Tantoižiden toižetusiden järgendused',
 'recentchanges-summary' => 'Necil lehtpolil om tantoižid toižetusid {{SITENAME}}-saital.',
@@ -1410,6 +1444,7 @@ Ku valitas vaiše ühten kävutajan failad, ka ozutadas vaiše necen kävutajan
 'listfiles_size' => 'Suruz’',
 'listfiles_description' => 'Ümbrikirjutand',
 'listfiles_count' => 'Versijad',
+'listfiles-latestversion-yes' => 'Ka',
 
 # File description page
 'file-anchor-link' => 'Fail',
@@ -1489,6 +1524,9 @@ Informacijad sen [$2 andmusiden lehtpolelpäi] om anttud alemba.',
 'randompage' => "Statjaline lehtpol'",
 'randompage-nopages' => '"$1"-{{PLURAL:$2|Nimiavarudes|Nimiavaruziš}} ei ole lehtpolid.',
 
+# Random page in category
+'randomincategory-selectcategory-submit' => 'Mäne',
+
 # Random redirect
 'randomredirect' => 'Statjaline läbikosketuz',
 'randomredirect-nopages' => '"$1"-nimiavaruses ei ole läbikosketusid.',
@@ -1933,7 +1971,7 @@ $1',
 'contributions' => '{{GENDER:$1|Kävutajan}} tond',
 'contributions-title' => '$1-kävutajan tond',
 'mycontris' => 'Minun tond',
-'contribsub2' => '$1-kävutajan ($2) tond',
+'contribsub2' => '{{GENDER:$3|$1}}-kävutajan ($2) tond',
 'uctop' => '(nügüdläine)',
 'month' => 'Ku:',
 'year' => 'Voz’:',
@@ -2336,6 +2374,8 @@ Voib olda, necil lehtpolel om kosketuz irdsaitale, kudamb om mustas nimikirjutes
 'pageinfo-watchers' => 'Lehtpolen kaclijoiden lugu',
 'pageinfo-edits' => 'Redakcijoiden lugumär',
 'pageinfo-authors' => 'Erazvuiččiden avtoroiden lugu',
+'pageinfo-contentpage-yes' => 'Ka',
+'pageinfo-protect-cascading-yes' => 'Ka',
 
 # Skin names
 'skinname-cologneblue' => "Köl'nan sinine",
@@ -2378,7 +2418,7 @@ $1',
 'file-info-size' => '$1 × $2 pikselad, failan suruz: $3, MIME-tip: $4',
 'file-nohires' => 'Ei ole versijad paremban tarkoiktusenke.',
 'svg-long-desc' => 'SVG-fail, nominaližikš $1 × $2 pikselid, failan suruz: $3',
-'show-big-image' => 'Korgedtarkoiktuseline kuvan versii',
+'show-big-image' => 'Originaline fail',
 'show-big-image-preview' => 'Ezikacundan suruz: $1.',
 'show-big-image-size' => '$1 × $2 pikselid',
 'file-info-gif-looped' => 'toštase',
@@ -2736,6 +2776,9 @@ Ku fail redaktiruidihe sändan polhe, erased parametrad voidas erineda nügüdl
 'exif-dc-publisher' => 'Pästai',
 'exif-dc-rights' => 'Oiktused',
 
+'exif-isospeedratings-overflow' => 'Более 65535',
+
+'exif-iimcategory-ace' => "Čomamaht, kul'tur da bobuštused",
 'exif-iimcategory-clj' => 'Ogerantegend da käskuz',
 'exif-iimcategory-dis' => 'Katastrofad da avarijad',
 'exif-iimcategory-fin' => 'Ekonomik da biznes',
@@ -2816,6 +2859,9 @@ Necen vahvištoitandkodan kävutamižen lopstrok om $4.',
 'confirm-watch-button' => 'OK',
 'confirm-unwatch-button' => 'OK',
 
+# Separators for various lists, etc.
+'quotation-marks' => '«$1»',
+
 # Multipage image navigation
 'imgmultipageprev' => "← edeline lehtpol'",
 'imgmultipagenext' => "jäl'ghine lehtpol' →",
@@ -2969,7 +3015,9 @@ Kävutagat normaline ezikacund.',
 'tags-tag' => 'Tegan (virgan) nimi',
 'tags-display-header' => 'Nägu toižetisiden aigkirjoiš',
 'tags-description-header' => "Znamoičendan täuz' ümbrikirjutand",
+'tags-active-header' => 'Aktivine-ik?',
 'tags-hitcount-header' => 'Virgastadud redakcijad',
+'tags-active-yes' => 'Ka',
 'tags-edit' => 'redaktiruida',
 'tags-hitcount' => '$1 {{PLURAL:$1|toižetuz|toižetust}}',
 
@@ -3002,6 +3050,7 @@ Kävutagat normaline ezikacund.',
 'htmlform-submit' => 'Oigeta',
 'htmlform-reset' => 'Tühjitada toižetused',
 'htmlform-selectorother-other' => 'Toine',
+'htmlform-yes' => 'Ka',
 
 # SQLite database support
 'sqlite-has-fts' => " $1 täuz'tekstaižen ecindan tügedamiženke",
index 6510ba5..953e2ae 100644 (file)
@@ -1055,10 +1055,10 @@ $2
 'userpage-userdoesnotexist' => '用户账户“$1”没有注册。请在创建/编辑本页前检查。',
 'userpage-userdoesnotexist-view' => '用户账户“$1”未曾创建。',
 'blocked-notice-logextract' => '这位用户目前已被封禁。以下提供最近的封禁日志以供参考:',
-'clearyourcache' => "'''注意:'''保存之后,你必须清除浏览器缓存才能看到做出的更改。
-* '''火狐(Firefox)/Safari:'''按住“Shift”,同时单击“刷新”,或按“Ctrl-F5”或“Ctrl-R”(Mac为“⌘-R”)
+'clearyourcache' => "'''注意:'''在保存之后,你可能需要清除你的浏览器的缓存以查看更改。
+* '''Firefox/Safari:'''按住“Shift”的同时单击“刷新”,或按“Ctrl-F5”或“Ctrl-R”(Mac为“⌘-R”)
 * '''Google Chrome:'''按“Ctrl-Shift-R”(Mac为“⌘-Shift-R”)
-* '''Internet Explorer:'''按住“Ctrl”同时单击“刷新”,或按“Ctrl-F5”
+* '''Internet Explorer:'''按住“Ctrl”同时单击“刷新”,或按“Ctrl-F5”
 * '''Opera:'''在“工具→首选项”中清除缓存",
 'usercssyoucanpreview' => "'''提示:''' 在保存前请用“{{int:showpreview}}”按钮来测试您新的 CSS 。",
 'userjsyoucanpreview' => "'''提示:''' 在保存前请用“{{int:showpreview}}”按钮来测试您新的 JavaScript 。",
@@ -1519,7 +1519,7 @@ $1",
 'prefs-displaywatchlist' => '显示',
 'prefs-tokenwatchlist' => '密钥',
 'prefs-diffs' => '差异对比',
-'prefs-help-prefershttps' => '此首选项将在您下次登录时生效。',
+'prefs-help-prefershttps' => '该设置将在你下次登录时生效。',
 
 # User preference: email validation using jQuery
 'email-address-validity-valid' => '电子邮件地址有效',
index 964b313..7ca04b4 100644 (file)
@@ -47,7 +47,7 @@ class UpdateCollation extends Maintenance {
                $this->mDescription = <<<TEXT
 This script will find all rows in the categorylinks table whose collation is
 out-of-date (cl_collation != '$wgCategoryCollation') and repopulate cl_sortkey
-using the page title and cl_sortkey_prefix.  If everything's collation is
+using the page title and cl_sortkey_prefix.  If all collations are
 up-to-date, it will do nothing.
 TEXT;
 
@@ -188,15 +188,14 @@ TEXT;
                                                __METHOD__
                                        );
                                }
+                               if ( $row ) {
+                                       $batchConds = array( $this->getBatchCondition( $row, $dbw ) );
+                               }
                        }
                        if ( !$dryRun ) {
                                $dbw->commit( __METHOD__ );
                        }
 
-                       if ( $row ) {
-                               $batchConds = array( $this->getBatchCondition( $row ) );
-                       }
-
                        $count += $res->numRows();
                        $this->output( "$count done.\n" );
 
@@ -219,8 +218,7 @@ TEXT;
         * Return an SQL expression selecting rows which sort above the given row,
         * assuming an ordering of cl_to, cl_type, cl_from
         */
-       function getBatchCondition( $row ) {
-               $dbw = $this->getDB( DB_MASTER );
+       function getBatchCondition( $row, $dbw ) {
                $fields = array( 'cl_to', 'cl_type', 'cl_from' );
                $first = true;
                $cond = false;
index 31714a6..a022c6d 100644 (file)
@@ -158,6 +158,7 @@ return array(
        'jquery.autoEllipsis' => array(
                'scripts' => 'resources/jquery/jquery.autoEllipsis.js',
                'dependencies' => 'jquery.highlightText',
+               'targets' => array( 'desktop', 'mobile' ),
        ),
        'jquery.badge' => array(
                'scripts' => 'resources/jquery/jquery.badge.js',
@@ -171,6 +172,7 @@ return array(
        'jquery.byteLimit' => array(
                'scripts' => 'resources/jquery/jquery.byteLimit.js',
                'dependencies' => 'jquery.byteLength',
+               'targets' => array( 'desktop', 'mobile' ),
        ),
        'jquery.checkboxShiftClick' => array(
                'scripts' => 'resources/jquery/jquery.checkboxShiftClick.js',
@@ -225,6 +227,7 @@ return array(
        'jquery.highlightText' => array(
                'scripts' => 'resources/jquery/jquery.highlightText.js',
                'dependencies' => 'jquery.mwExtension',
+               'targets' => array( 'desktop', 'mobile' ),
        ),
        'jquery.hoverIntent' => array(
                'scripts' => 'resources/jquery/jquery.hoverIntent.js',
@@ -605,6 +608,7 @@ return array(
        'mediawiki.api' => array(
                'scripts' => 'resources/mediawiki.api/mediawiki.api.js',
                'dependencies' => 'mediawiki.util',
+               'targets' => array( 'desktop', 'mobile' ),
        ),
        'mediawiki.api.category' => array(
                'scripts' => 'resources/mediawiki.api/mediawiki.api.category.js',
@@ -710,6 +714,7 @@ return array(
                'dependencies' => array(
                        'mediawiki.page.startup',
                ),
+               'targets' => array( 'desktop', 'mobile' ),
        ),
        'mediawiki.notify' => array(
                'scripts' => 'resources/mediawiki/mediawiki.notify.js',
@@ -736,9 +741,11 @@ return array(
                        'jquery.byteLength',
                        'mediawiki.util',
                ),
+               'targets' => array( 'desktop', 'mobile' ),
        ),
        'mediawiki.Uri' => array(
                'scripts' => 'resources/mediawiki/mediawiki.Uri.js',
+               'targets' => array( 'desktop', 'mobile' ),
        ),
        'mediawiki.user' => array(
                'scripts' => 'resources/mediawiki/mediawiki.user.js',
@@ -748,6 +755,7 @@ return array(
                        'user.options',
                        'user.tokens',
                ),
+               'targets' => array( 'desktop', 'mobile' ),
        ),
        'mediawiki.util' => array(
                'scripts' => 'resources/mediawiki/mediawiki.util.js',
@@ -800,6 +808,7 @@ return array(
        'mediawiki.action.history.diff' => array(
                'styles' => 'resources/mediawiki.action/mediawiki.action.history.diff.css',
                'group' => 'mediawiki.action.history',
+               'targets' => array( 'desktop', 'mobile' ),
        ),
        'mediawiki.action.view.dblClickEdit' => array(
                'scripts' => 'resources/mediawiki.action/mediawiki.action.view.dblClickEdit.js',
index 10d36ff..a931756 100644 (file)
        .buttonColors();
        border-radius: @buttonBorderRadius;
 
-       // Content styling
+       // Ensure that buttons and inputs are nicely aligned when they have differing heights
        vertical-align: middle;
 
+       // Content styling
        text-align: center;
        text-decoration: none;
 
index 4ed7261..1b177ee 100644 (file)
@@ -19,6 +19,9 @@
 
        color: @agoraTextColor;
        padding: 0.35em 0.5em 0.35em 0.5em;
+
+       // Ensure that buttons and inputs are nicely aligned when they have differing heights
+       vertical-align: middle;
 }
 
 .agora-label-styling() {
index 8a8215d..724ca5e 100644 (file)
@@ -1749,38 +1749,6 @@ var mw = ( function ( $, undefined ) {
                                        // Cache hit stats
                                        stats: { hits: 0, misses: 0, expired: 0 },
 
-                                       // Experiment data
-                                       experiment: ( function () {
-                                               var start = ( new Date() ).getTime(), id = 0, seed = 0;
-
-                                               try {
-                                                       id = JSON.parse( localStorage.getItem( 'moduleStorageExperiment' ) );
-                                                       if ( typeof id !== 'number' ) {
-                                                               id = Math.floor( Math.random() * Math.random() * 1e16 );
-                                                               localStorage.setItem( 'moduleStorageExperiment', id );
-                                                       }
-                                                       seed = id % 2000;
-                                               } catch ( e ) {}
-
-                                               return {
-                                                       // Unique identifier for this browser. This allows us to group all
-                                                       // datapoints generated by a particular browser, which in turn allows us
-                                                       // to see how the initial load compares to subsequent page loads.
-                                                       id: id,
-
-                                                       // Group assignment may be 0 (not in experiment), 1 (control group), or 2
-                                                       // (experimental group). Browsers that don't implement all the prerequisite APIs
-                                                       // (JSON and Web Storage) are ineligible. Eligible browsers have a 0.1% chance
-                                                       // of being included in the experiment, in which case they are equally likely to
-                                                       // be assigned to either the experimental or control group.
-                                                       group: seed === 1 ? 1 : ( seed === 2 ? 2 : 0 ),
-
-                                                       // Assess module storage performance by measuring the time between this
-                                                       // reference point and the window load event.
-                                                       start: start
-                                               };
-                                       }() ),
-
                                        /**
                                         * Construct a JSON-serializable object representing the content of the store.
                                         * @return {Object} Module store contents.
@@ -1838,12 +1806,9 @@ var mw = ( function ( $, undefined ) {
                                                        return;
                                                }
 
-                                               if (
-                                                       // We're in debug mode
-                                                       mw.config.get( 'debug' ) ||
-                                                       // Module storage is neither enabled by default, nor enabled for this user's group.
-                                                       !( mw.config.get( 'wgResourceLoaderStorageEnabled' ) || mw.loader.store.experiment.group === 2 )
-                                               ) {
+                                               if ( !mw.config.get( 'wgResourceLoaderStorageEnabled' ) || mw.config.get( 'debug' ) ) {
+                                                       // Disabled by configuration, or because debug mode is set.
+                                                       mw.loader.store.enabled = false;
                                                        return;
                                                }
 
index b954d22..8ed2189 100644 (file)
@@ -3,7 +3,7 @@
 /**
  * @author Adam Shorland
  */
-class StatusTest extends MediaWikiTestCase {
+class StatusTest extends MediaWikiLangTestCase {
 
        public function testCanConstruct() {
                new Status();
diff --git a/tests/phpunit/includes/jobqueue/RefreshLinksPartitionTest.php b/tests/phpunit/includes/jobqueue/RefreshLinksPartitionTest.php
new file mode 100644 (file)
index 0000000..398c5a2
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+
+/**
+ * @group JobQueue
+ * @group medium
+ * @group Database
+ */
+class RefreshLinksPartitionTest extends MediaWikiTestCase {
+       function __construct( $name = null, array $data = array(), $dataName = '' ) {
+               parent::__construct( $name, $data, $dataName );
+
+               $this->tablesUsed[] = 'page';
+               $this->tablesUsed[] = 'revision';
+               $this->tablesUsed[] = 'pagelinks';
+       }
+
+       /**
+        * @dataProvider provider_backlinks
+        */
+       public function testRefreshLinks( $ns, $dbKey, $pages ) {
+               $title = Title::makeTitle( $ns, $dbKey );
+
+               $dbw = wfGetDB( DB_MASTER );
+
+               $rows = array();
+               foreach ( $pages as $page ) {
+                       list( $bns, $bdbkey ) = $page;
+                       $bpage = WikiPage::factory( Title::makeTitle( $bns, $bdbkey ) );
+                       $content = ContentHandler::makeContent( "[[{$title->getPrefixedText()}]]", $bpage->getTitle() );
+                       $bpage->doEditContent( $content, "test" );
+               }
+
+               $title->getBacklinkCache()->clear();
+               $this->assertEquals( 20, $title->getBacklinkCache()->getNumLinks( 'pagelinks' ), 'Correct number of backlinks' );
+
+               $job = new RefreshLinksJob( $title, array( 'recursive' => true, 'table' => 'pagelinks' ) 
+                       + Job::newRootJobParams( "refreshlinks:pagelinks:{$title->getPrefixedText()}" ) );
+               $extraParams = $job->getRootJobParams();
+               $jobs = BacklinkJobUtils::partitionBacklinkJob( $job, 9, 1, array( 'params' => $extraParams ) );
+
+               $this->assertEquals( 10, count( $jobs ), 'Correct number of sub-jobs' );
+               $this->assertEquals( $pages[0], current( $jobs[0]->params['pages'] ), 
+                       'First job is leaf job with proper title' );
+               $this->assertEquals( $pages[8], current( $jobs[8]->params['pages'] ),
+                       'Last leaf job is leaf job with proper title' );
+               $this->assertEquals( true, isset( $jobs[9]->params['recursive'] ),
+                       'Last job is recursive sub-job' );
+               $this->assertEquals( true, $jobs[9]->params['recursive'],
+                       'Last job is recursive sub-job' );
+               $this->assertEquals( true, is_array( $jobs[9]->params['range'] ),
+                       'Last job is recursive sub-job' );
+               $this->assertEquals( $title->getPrefixedText(), $jobs[0]->getTitle()->getPrefixedText(), 
+                       'Base job title retainend in leaf job' );
+               $this->assertEquals( $title->getPrefixedText(), $jobs[9]->getTitle()->getPrefixedText(), 
+                       'Base job title retainend recursive sub-job' );
+               $this->assertEquals( $extraParams['rootJobSignature'], $jobs[0]->params['rootJobSignature'],
+                       'Leaf job has root params' );
+               $this->assertEquals( $extraParams['rootJobSignature'], $jobs[9]->params['rootJobSignature'], 
+                       'Recursive sub-job has root params' );
+
+               $jobs2 = BacklinkJobUtils::partitionBacklinkJob( $jobs[9], 9, 1, array( 'params' => $extraParams ) );
+
+               $this->assertEquals( 10, count( $jobs2 ), 'Correct number of sub-jobs' );
+               $this->assertEquals( $pages[9], current( $jobs2[0]->params['pages'] ), 
+                       'First job is leaf job with proper title' );
+               $this->assertEquals( $pages[17], current( $jobs2[8]->params['pages'] ),
+                       'Last leaf job is leaf job with proper title' );
+               $this->assertEquals( true, isset( $jobs2[9]->params['recursive'] ),
+                       'Last job is recursive sub-job' );
+               $this->assertEquals( true, $jobs2[9]->params['recursive'],
+                       'Last job is recursive sub-job' );
+               $this->assertEquals( true, is_array( $jobs2[9]->params['range'] ),
+                       'Last job is recursive sub-job' );
+               $this->assertEquals( $extraParams['rootJobSignature'], $jobs2[0]->params['rootJobSignature'],
+                       'Leaf job has root params' );
+               $this->assertEquals( $extraParams['rootJobSignature'], $jobs2[9]->params['rootJobSignature'], 
+                       'Recursive sub-job has root params' );
+
+               $jobs3 = BacklinkJobUtils::partitionBacklinkJob( $jobs2[9], 9, 1, array( 'params' => $extraParams ) );
+
+               $this->assertEquals( 2, count( $jobs3 ), 'Correct number of sub-jobs' );
+               $this->assertEquals( $pages[18], current( $jobs3[0]->params['pages'] ), 
+                       'First job is leaf job with proper title' );
+               $this->assertEquals( $extraParams['rootJobSignature'], $jobs3[0]->params['rootJobSignature'],
+                       'Leaf job has root params' );
+               $this->assertEquals( $pages[19], current( $jobs3[1]->params['pages'] ), 
+                       'Last job is leaf job with proper title' );
+               $this->assertEquals( $extraParams['rootJobSignature'], $jobs3[1]->params['rootJobSignature'],
+                       'Last leaf job has root params' );
+       }
+
+       public static function provider_backlinks() {
+               $pages = array();
+               for ( $i=0; $i<20; ++$i ) {
+                       $pages[] = array( 0, "Page-$i" );
+               }
+               return array(
+                       array( 10, 'Bang', $pages )
+               );
+       }
+}
index f1babf5..e958fde 100644 (file)
@@ -1486,13 +1486,14 @@ class LanguageTest extends LanguageClassesTestCase {
 
        public static function provideCommafyData() {
                return array(
-                       array( 1, '1' ),
+                       array( -1, '-1' ),
                        array( 10, '10' ),
                        array( 100, '100' ),
                        array( 1000, '1,000' ),
                        array( 10000, '10,000' ),
                        array( 100000, '100,000' ),
                        array( 1000000, '1,000,000' ),
+                       array( -1.0001, '-1.0001' ),
                        array( 1.0001, '1.0001' ),
                        array( 10.0001, '10.0001' ),
                        array( 100.0001, '100.0001' ),
@@ -1500,6 +1501,8 @@ class LanguageTest extends LanguageClassesTestCase {
                        array( 10000.0001, '10,000.0001' ),
                        array( 100000.0001, '100,000.0001' ),
                        array( 1000000.0001, '1,000,000.0001' ),
+                       array( '200000000000000000000', '200,000,000,000,000,000,000' ),
+                       array( '-200000000000000000000', '-200,000,000,000,000,000,000' ),
                );
        }