Merge "Sync up with Parsoid parserTests."
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Wed, 21 Oct 2015 12:55:34 +0000 (12:55 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Wed, 21 Oct 2015 12:55:34 +0000 (12:55 +0000)
63 files changed:
RELEASE-NOTES-1.27
includes/DefaultSettings.php
includes/EditPage.php
includes/GlobalFunctions.php
includes/Preferences.php
includes/User.php
includes/api/ApiFeedRecentChanges.php
includes/api/ApiQueryRecentChanges.php
includes/api/ApiQueryWatchlist.php
includes/api/i18n/en.json
includes/api/i18n/ja.json
includes/api/i18n/qqq.json
includes/api/i18n/tr.json
includes/api/i18n/zh-hans.json
includes/cache/MessageBlobStore.php
includes/changes/CategoryMembershipChange.php
includes/changes/ChangesList.php
includes/changes/EnhancedChangesList.php
includes/changes/OldChangesList.php
includes/changes/RCCacheEntryFactory.php
includes/changes/RecentChange.php
includes/db/DBConnRef.php
includes/db/Database.php
includes/db/IDatabase.php
includes/db/loadbalancer/LBFactory.php
includes/db/loadbalancer/LBFactoryMulti.php
includes/db/loadbalancer/LBFactorySimple.php
includes/db/loadbalancer/LBFactorySingle.php
includes/db/loadbalancer/LoadBalancer.php
includes/deferred/LinksUpdate.php
includes/installer/i18n/ast.json
includes/jobqueue/JobQueueRedis.php
includes/jobqueue/jobs/RefreshLinksJob.php
includes/objectcache/MultiWriteBagOStuff.php
includes/objectcache/ObjectCache.php
includes/page/WikiPage.php
includes/resourceloader/ResourceLoaderWikiModule.php
includes/skins/Skin.php
includes/specialpage/ChangesListSpecialPage.php
includes/specials/SpecialExpandTemplates.php
includes/specials/SpecialRecentchanges.php
includes/specials/SpecialWatchlist.php
languages/i18n/be-tarask.json
languages/i18n/en.json
languages/i18n/it.json
languages/i18n/ko.json
languages/i18n/lb.json
languages/i18n/nah.json
languages/i18n/nb.json
languages/i18n/pt.json
languages/i18n/qqq.json
languages/i18n/roa-tara.json
languages/i18n/sr-ec.json
languages/i18n/sr-el.json
languages/i18n/tr.json
maintenance/dictionary/mediawiki.dic
maintenance/importDump.php
resources/src/mediawiki/mediawiki.Upload.js
tests/phpunit/includes/changes/EnhancedChangesListTest.php
tests/phpunit/includes/changes/TestRecentChangesHelper.php
tests/phpunit/includes/deferred/LinksUpdateTest.php
tests/phpunit/includes/objectcache/MultiWriteBagOStuffTest.php
tests/phpunit/includes/specials/SpecialRecentchangesTest.php

index 2c64b2d..7d4ac3d 100644 (file)
@@ -9,7 +9,7 @@ MediaWiki 1.27 is an alpha-quality branch and is not recommended for use in
 production.
 
 === Configuration changes in 1.27 ===
-* Removed $wgUseLinkNamespaceDBFields
+* $wgUseLinkNamespaceDBFields was removed.
 * Deprecated $wgResourceLoaderMinifierStatementsOnOwnLine and
   $wgResourceLoaderMinifierMaxLineLength, because there was little value in
   making the behavior configurable. The default values (`false` for the former,
@@ -44,6 +44,7 @@ production.
 ** A new constructor, User::newSystemUser(), has been added to simplify the
    creation of passwordless "system" users for logged actions.
 * $wgMaxSquidPurgeTitles was removed.
+* $wgAjaxWatch was removed. This is now enabled by default.
 
 === New features in 1.27 ===
 * $wgDataCenterId and $wgDataCenterRoles where added, which will serve as
index 7b196cb..71fe83d 100644 (file)
@@ -3365,10 +3365,11 @@ $wgEdititis = false;
 $wgSend404Code = true;
 
 /**
- * The $wgShowRollbackEditCount variable is used to show how many edits will be
- * rollback. The numeric value of the variable are the limit up to are counted.
- * If the value is false or 0, the edits are not counted. Disabling this will
- * furthermore prevent MediaWiki from hiding some useless rollback links.
+ * The $wgShowRollbackEditCount variable is used to show how many edits can be rolled back.
+ * The numeric value of the variable controls how many edits MediaWiki will look back to
+ * determine whether a rollback is allowed (by checking that they are all from the same author).
+ * If the value is false or 0, the edits are not counted. Disabling this will prevent MediaWiki
+ * from hiding some useless rollback links.
  *
  * @since 1.20
  */
@@ -4542,6 +4543,7 @@ $wgDefaultUserOptions = array(
        'gender' => 'unknown',
        'hideminor' => 0,
        'hidepatrolled' => 0,
+       'hidecategorization' => 1,
        'imagesize' => 2,
        'math' => 1,
        'minordefault' => 0,
@@ -4573,6 +4575,7 @@ $wgDefaultUserOptions = array(
        'watchlisthideminor' => 0,
        'watchlisthideown' => 0,
        'watchlisthidepatrolled' => 0,
+       'watchlisthidecategorization' => 1,
        'watchmoves' => 0,
        'watchrollback' => 0,
        'wllimit' => 250,
@@ -6178,6 +6181,12 @@ $wgRCEngines = array(
        'udp' => 'UDPRCFeedEngine',
 );
 
+/**
+ * Treat category membership changes as a RecentChange
+ * @since 1.27
+ */
+$wgRCWatchCategoryMembership = false;
+
 /**
  * Use RC Patrolling to check for vandalism
  */
@@ -7341,12 +7350,6 @@ $wgUseAjax = true;
  */
 $wgAjaxExportList = array();
 
-/**
- * Enable watching/unwatching pages using AJAX.
- * Requires $wgUseAjax to be true too.
- */
-$wgAjaxWatch = true;
-
 /**
  * Enable AJAX check for file overwrite, pre-upload
  */
index 8571cd7..81f35f9 100644 (file)
@@ -537,6 +537,20 @@ class EditPage {
                        return;
                }
 
+               $revision = $this->mArticle->getRevisionFetched();
+               // Disallow editing revisions with content models different from the current one
+               if ( $revision && $revision->getContentModel() !== $this->contentModel ) {
+                       $this->displayViewSourcePage(
+                               $this->getContentObject(),
+                               wfMessage(
+                                       'contentmodelediterror',
+                                       $revision->getContentModel(),
+                                       $this->contentModel
+                               )->plain()
+                       );
+                       return;
+               }
+
                $this->isConflict = false;
                // css / js subpages of user pages get a special treatment
                $this->isCssJsSubpage = $this->mTitle->isCssJsSubpage();
@@ -647,6 +661,20 @@ class EditPage {
                        throw new PermissionsError( $action, $permErrors );
                }
 
+               $this->displayViewSourcePage(
+                       $content,
+                       $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' )
+               );
+       }
+
+       /**
+        * Display a read-only View Source page
+        * @param Content $content content object
+        * @param string $errorMessage additional wikitext error message to display
+        */
+       protected function displayViewSourcePage( Content $content, $errorMessage = '' ) {
+               global $wgOut;
+
                Hooks::run( 'EditPage::showReadOnlyForm:initial', array( $this, &$wgOut ) );
 
                $wgOut->setRobotPolicy( 'noindex,nofollow' );
@@ -658,8 +686,10 @@ class EditPage {
                $wgOut->addHTML( $this->editFormPageTop );
                $wgOut->addHTML( $this->editFormTextTop );
 
-               $wgOut->addWikiText( $wgOut->formatPermissionsErrorMessage( $permErrors, 'edit' ) );
-               $wgOut->addHTML( "<hr />\n" );
+               if ( $errorMessage !== '' ) {
+                       $wgOut->addWikiText( $errorMessage );
+                       $wgOut->addHTML( "<hr />\n" );
+               }
 
                # If the user made changes, preserve them when showing the markup
                # (This happens when a user is blocked during edit, for instance)
@@ -667,7 +697,13 @@ class EditPage {
                        $text = $this->textbox1;
                        $wgOut->addWikiMsg( 'viewyourtext' );
                } else {
-                       $text = $this->toEditText( $content );
+                       try {
+                               $text = $this->toEditText( $content );
+                       } catch ( MWException $e ) {
+                               # Serialize using the default format if the content model is not supported
+                               # (e.g. for an old revision with a different model)
+                               $text = $content->serialize();
+                       }
                        $wgOut->addWikiMsg( 'viewsourcetext' );
                }
 
index e73c75c..cda3154 100644 (file)
@@ -1355,24 +1355,14 @@ function wfReadOnlyReason() {
                return $readOnly;
        }
 
-       static $autoReadOnly = null;
-       if ( $autoReadOnly === null ) {
+       static $lbReadOnly = null;
+       if ( $lbReadOnly === null ) {
                // Callers use this method to be aware that data presented to a user
                // may be very stale and thus allowing submissions can be problematic.
-               try {
-                       if ( wfGetLB()->getLaggedSlaveMode() ) {
-                               $autoReadOnly = 'The database has been automatically locked ' .
-                                       'while the slave database servers catch up to the master';
-                       } else {
-                               $autoReadOnly = false;
-                       }
-               } catch ( DBConnectionError $e ) {
-                       $autoReadOnly = 'The database has been automatically locked ' .
-                               'until the slave database servers become available';
-               }
+               $lbReadOnly = wfGetLB()->getReadOnlyReason();
        }
 
-       return $autoReadOnly;
+       return $lbReadOnly;
 }
 
 /**
index b3ee207..0f8dcc3 100644 (file)
@@ -892,6 +892,14 @@ class Preferences {
                        'section' => 'rc/advancedrc',
                );
 
+               if ( $config->get( 'RCWatchCategoryMembership' ) ) {
+                       $defaultPreferences['hidecategorization'] = array(
+                               'type' => 'toggle',
+                               'label-message' => 'tog-hidecategorization',
+                               'section' => 'rc/advancedrc',
+                       );
+               }
+
                if ( $user->useRCPatrol() ) {
                        $defaultPreferences['hidepatrolled'] = array(
                                'type' => 'toggle',
@@ -999,6 +1007,14 @@ class Preferences {
                        'label-message' => 'tog-watchlisthideliu',
                );
 
+               if ( $config->get( 'RCWatchCategoryMembership' ) ) {
+                       $defaultPreferences['watchlisthidecategorization'] = array(
+                               'type' => 'toggle',
+                               'section' => 'watchlist/advancedwatchlist',
+                               'label-message' => 'tog-watchlisthidecategorization',
+                       );
+               }
+
                if ( $user->useRCPatrol() ) {
                        $defaultPreferences['watchlisthidepatrolled'] = array(
                                'type' => 'toggle',
index 0dfdfc4..6e52a1d 100644 (file)
@@ -2478,6 +2478,11 @@ class User implements IDBAccessObject {
         * @param bool $throttle If true, reset the throttle timestamp to the present
         */
        public function setNewpassword( $str, $throttle = true ) {
+               $id = $this->getId();
+               if ( $id == 0 ) {
+                       throw new LogicException( 'Cannot set new password for a user that is not in the database.' );
+               }
+
                $dbw = wfGetDB( DB_MASTER );
 
                $passwordFactory = new PasswordFactory();
index d24112c..5adde87 100644 (file)
@@ -155,6 +155,7 @@ class ApiFeedRecentChanges extends ApiBase {
                        'hideliu' => false,
                        'hidepatrolled' => false,
                        'hidemyself' => false,
+                       'hidecategorization' => false,
 
                        'tagfilter' => array(
                                ApiBase::PARAM_TYPE => 'string',
index ed0a2a7..0a11f4b 100644 (file)
@@ -678,14 +678,9 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
                                ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2
                        ),
                        'type' => array(
-                               ApiBase::PARAM_DFLT => 'edit|new|log',
+                               ApiBase::PARAM_DFLT => 'edit|new|log|categorize',
                                ApiBase::PARAM_ISMULTI => true,
-                               ApiBase::PARAM_TYPE => array(
-                                       'edit',
-                                       'external',
-                                       'new',
-                                       'log'
-                               )
+                               ApiBase::PARAM_TYPE => RecentChange::getChangeTypes()
                        ),
                        'toponly' => false,
                        'continue' => array(
index 648d259..75fc33e 100644 (file)
@@ -483,14 +483,10 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase {
                                )
                        ),
                        'type' => array(
-                               ApiBase::PARAM_DFLT => 'edit|new|log',
+                               ApiBase::PARAM_DFLT => 'edit|new|log|categorize',
                                ApiBase::PARAM_ISMULTI => true,
-                               ApiBase::PARAM_TYPE => array(
-                                       'edit',
-                                       'external',
-                                       'new',
-                                       'log',
-                               )
+                               ApiBase::PARAM_HELP_MSG_PER_VALUE => array(),
+                               ApiBase::PARAM_TYPE => RecentChange::getChangeTypes()
                        ),
                        'owner' => array(
                                ApiBase::PARAM_TYPE => 'user'
index b53289a..589e5fd 100644 (file)
        "apihelp-feedrecentchanges-param-hideliu": "Hide changes made by registered users.",
        "apihelp-feedrecentchanges-param-hidepatrolled": "Hide patrolled changes.",
        "apihelp-feedrecentchanges-param-hidemyself": "Hide changes made by the current user.",
+       "apihelp-feedrecentchanges-param-hidecategorization": "Hide category membership changes.",
        "apihelp-feedrecentchanges-param-tagfilter": "Filter by tag.",
        "apihelp-feedrecentchanges-param-target": "Show only changes on pages linked from this page.",
        "apihelp-feedrecentchanges-param-showlinkedto": "Show changes on pages linked to the selected page instead.",
        "apihelp-query+watchlist-paramvalue-prop-notificationtimestamp": "Adds timestamp of when the user was last notified about the edit.",
        "apihelp-query+watchlist-paramvalue-prop-loginfo": "Adds log information where appropriate.",
        "apihelp-query+watchlist-param-show": "Show only items that meet these criteria. For example, to see only minor edits done by logged-in users, set $1show=minor|!anon.",
-       "apihelp-query+watchlist-param-type": "Which types of changes to show:\n;edit:Regular page edits.\n;external:External changes.\n;new:Page creations.\n;log:Log entries.",
+       "apihelp-query+watchlist-param-type": "Which types of changes to show:",
+       "apihelp-query+watchlist-paramvalue-type-edit": "Regular page edits.",
+       "apihelp-query+watchlist-paramvalue-type-external": "External changes.",
+       "apihelp-query+watchlist-paramvalue-type-new": "Page creations.",
+       "apihelp-query+watchlist-paramvalue-type-log": "Log entries.",
+       "apihelp-query+watchlist-paramvalue-type-categorize": "Category membership changes.",
        "apihelp-query+watchlist-param-owner": "Used along with $1token to access a different user's watchlist.",
        "apihelp-query+watchlist-param-token": "A security token (available in the user's [[Special:Preferences#mw-prefsection-watchlist|preferences]]) to allow access to another user's watchlist.",
        "apihelp-query+watchlist-example-simple": "List the top revision for recently changed pages on the current user's watchlist.",
index 0230ae0..c091e6d 100644 (file)
@@ -40,6 +40,7 @@
        "apihelp-checktoken-param-token": "調べるトークン。",
        "apihelp-checktoken-example-simple": "<kbd>csrf</kbd> トークンの妥当性を調べる。",
        "apihelp-clearhasmsg-description": "現在の利用者の <code>hasmsg</code> フラグを消去します。",
+       "apihelp-clearhasmsg-example-1": "現在の利用者の <code>hasmsg</code> フラグを消去する。",
        "apihelp-compare-description": "2つの版間の差分を取得します。\n\n\"from\" と \"to\" の両方の版番号、ページ名、もしくはページIDを渡す必要があります。",
        "apihelp-compare-param-fromtitle": "比較する1つ目のページ名。",
        "apihelp-compare-param-fromid": "比較する1つ目のページID。",
index 125a155..ea4251c 100644 (file)
        "apihelp-feedrecentchanges-param-hideliu": "{{doc-apihelp-param|feedrecentchanges|hideliu}}",
        "apihelp-feedrecentchanges-param-hidepatrolled": "{{doc-apihelp-param|feedrecentchanges|hidepatrolled}}",
        "apihelp-feedrecentchanges-param-hidemyself": "{{doc-apihelp-param|feedrecentchanges|hidemyself}}",
+       "apihelp-feedrecentchanges-param-hidecategorization": "{{doc-apihelp-param|feedrecentchanges|hidecategorization}}",
        "apihelp-feedrecentchanges-param-tagfilter": "{{doc-apihelp-param|feedrecentchanges|tagfilter}}",
        "apihelp-feedrecentchanges-param-target": "{{doc-apihelp-param|feedrecentchanges|target}}",
        "apihelp-feedrecentchanges-param-showlinkedto": "{{doc-apihelp-param|feedrecentchanges|showlinkedto}}",
        "apihelp-query+watchlist-paramvalue-prop-loginfo": "{{doc-apihelp-paramvalue|query+watchlist|prop|loginfo}}",
        "apihelp-query+watchlist-param-show": "{{doc-apihelp-param|query+watchlist|show}}",
        "apihelp-query+watchlist-param-type": "{{doc-apihelp-param|query+watchlist|type}}",
+       "apihelp-query+watchlist-paramvalue-type-edit": "{{doc-apihelp-paramvalue|query+watchlist|type|edit}}",
+       "apihelp-query+watchlist-paramvalue-type-external": "{{doc-apihelp-paramvalue|query+watchlist|type|external}}",
+       "apihelp-query+watchlist-paramvalue-type-new": "{{doc-apihelp-paramvalue|query+watchlist|type|new}}",
+       "apihelp-query+watchlist-paramvalue-type-log": "{{doc-apihelp-paramvalue|query+watchlist|type|log}}",
+       "apihelp-query+watchlist-paramvalue-type-categorize": "{{doc-apihelp-paramvalue|query+watchlist|type|categorize}}",
        "apihelp-query+watchlist-param-owner": "{{doc-apihelp-param|query+watchlist|owner}}",
        "apihelp-query+watchlist-param-token": "{{doc-apihelp-param|query+watchlist|token}}",
        "apihelp-query+watchlist-example-simple": "{{doc-apihelp-example|query+watchlist}}",
index 4b61085..997c6ac 100644 (file)
@@ -3,9 +3,12 @@
                "authors": [
                        "Sayginer",
                        "Sadrettin",
-                       "Uğurkent"
+                       "Uğurkent",
+                       "Gorizon"
                ]
        },
+       "apihelp-block-description": "Kullanıcıyı engelle",
+       "apihelp-block-param-reason": "Engelleme sebebi",
        "apihelp-createaccount-param-name": "Kullanıcı adı.",
        "apihelp-createaccount-param-password": "Parola (ignored if <var>$1mailpassword</var> is set).",
        "apihelp-createaccount-param-email": "Kullanıcının e-posta adresi (isteğe bağlı).",
index c1fd649..058a51b 100644 (file)
@@ -75,6 +75,7 @@
        "apihelp-delete-param-title": "你所希望删除的页面的标题。不能与<var>$1pageid</var>一起使用。",
        "apihelp-delete-param-pageid": "要删除的页面的页面 ID。不能与<var>$1title</var>一起使用。",
        "apihelp-delete-param-reason": "删除原因。如果未设置,将使用一个自动生成的原因。",
+       "apihelp-delete-param-tags": "要在删除日志中应用到实体的更改标签。",
        "apihelp-delete-param-watch": "将该页面加入当前用户的监视列表。",
        "apihelp-delete-param-watchlist": "无条件地将页面加入至当前用户的监视列表或将其移除,使用设置或不更改监视。",
        "apihelp-delete-param-unwatch": "将该页面从当前用户的监视列表删除。",
        "apihelp-query+siteinfo-paramvalue-prop-usergroups": "返回用户组及其相关权限。",
        "apihelp-query+siteinfo-paramvalue-prop-libraries": "返回wiki上安装的库。",
        "apihelp-query+siteinfo-paramvalue-prop-extensions": "返回wiki上安装的扩展。",
-       "apihelp-query+siteinfo-paramvalue-prop-fileextensions": "返回允许上传的文件扩展名列表。",
+       "apihelp-query+siteinfo-paramvalue-prop-fileextensions": "返回允许上传的文件扩展名(文件类型)列表。",
        "apihelp-query+siteinfo-paramvalue-prop-rightsinfo": "如果可用,返回wiki的版权信息。",
        "apihelp-query+siteinfo-paramvalue-prop-restrictions": "返回可用的编辑限制(保护)类型信息。",
        "apihelp-query+siteinfo-paramvalue-prop-languages": "返回MediaWiki支持的语言列表(可选择使用<var>$1inlanguagecode</var>本地化)。",
        "apihelp-stashedit-param-sectiontitle": "新段落的标题。",
        "apihelp-stashedit-param-text": "页面内容。",
        "apihelp-stashedit-param-contentmodel": "新内容的内容模型。",
+       "apihelp-stashedit-param-baserevid": "基础修订的修订ID。",
        "apihelp-tag-description": "从个别修订或日志记录中添加或移除更改标签。",
        "apihelp-tag-param-rcid": "要添加或移除标签的一个或更多的最近更改ID。",
        "apihelp-tag-param-revid": "要添加或移除标签的一个或更多的修订ID。",
index 63d8c7e..b7c70c1 100644 (file)
@@ -374,7 +374,6 @@ class MessageBlobStore {
                        return array();
                }
 
-               $config = $resourceLoader->getConfig();
                $retval = array();
                $dbr = wfGetDB( DB_SLAVE );
                $res = $dbr->select( 'msg_resource',
@@ -390,13 +389,10 @@ class MessageBlobStore {
                                throw new MWException( __METHOD__ . ' passed an invalid module name' );
                        }
 
-                       // Update the module's blobs if the set of messages changed or if the blob is
-                       // older than the CacheEpoch setting
-                       $keys = array_keys( FormatJson::decode( $row->mr_blob, true ) );
-                       $values = array_values( array_unique( $module->getMessages() ) );
-                       if ( $keys !== $values
-                               || wfTimestamp( TS_MW, $row->mr_timestamp ) <= $config->get( 'CacheEpoch' )
-                       ) {
+                       // Update the module's blob if the list of messages changed
+                       $blobKeys = array_keys( FormatJson::decode( $row->mr_blob, true ) );
+                       $moduleMsgs = array_values( array_unique( $module->getMessages() ) );
+                       if ( $blobKeys !== $moduleMsgs ) {
                                $retval[$row->mr_resource] = $this->updateModule( $row->mr_resource, $module, $lang );
                        } else {
                                $retval[$row->mr_resource] = $row->mr_blob;
index 9e73ebe..b4086f9 100644 (file)
@@ -47,7 +47,8 @@ class CategoryMembershipChange {
 
        /**
         * @var int
-        * Number of pages this WikiPage is embedded by; set by CategoryMembershipChange::setRecursive()
+        * Number of pages this WikiPage is embedded by
+        * Set by CategoryMembershipChange::checkTemplateLinks()
         */
        private $numTemplateLinks = 0;
 
@@ -239,7 +240,7 @@ class CategoryMembershipChange {
         * @param int $type may be CategoryMembershipChange::CATEGORY_ADDITION
         * or CategoryMembershipChange::CATEGORY_REMOVAL
         * @param array $params
-        * - prefixedUrl: result of Title::->getPrefixedURL()
+        * - prefixedText: result of Title::->getPrefixedText()
         *
         * @return string
         */
index 01f10d8..9ac6c32 100644 (file)
@@ -320,7 +320,11 @@ class ChangesList extends ContextSource {
         */
        public function insertDiffHist( &$s, &$rc, $unpatrolled ) {
                # Diff link
-               if ( $rc->mAttribs['rc_type'] == RC_NEW || $rc->mAttribs['rc_type'] == RC_LOG ) {
+               if (
+                       $rc->mAttribs['rc_type'] == RC_NEW ||
+                       $rc->mAttribs['rc_type'] == RC_LOG ||
+                       $rc->mAttribs['rc_type'] == RC_CATEGORIZE
+               ) {
                        $diffLink = $this->message['diff'];
                } elseif ( !self::userCan( $rc, Revision::DELETED_TEXT, $this->getUser() ) ) {
                        $diffLink = $this->message['diff'];
@@ -338,17 +342,22 @@ class ChangesList extends ContextSource {
                                $query
                        );
                }
-               $diffhist = $diffLink . $this->message['pipe-separator'];
-               # History link
-               $diffhist .= Linker::linkKnown(
-                       $rc->getTitle(),
-                       $this->message['hist'],
-                       array(),
-                       array(
-                               'curid' => $rc->mAttribs['rc_cur_id'],
-                               'action' => 'history'
-                       )
-               );
+               if ( $rc->mAttribs['rc_type'] == RC_CATEGORIZE ) {
+                       $diffhist = $diffLink . $this->message['pipe-separator'] . $this->message['hist'];
+               } else {
+                       $diffhist = $diffLink . $this->message['pipe-separator'];
+                       # History link
+                       $diffhist .= Linker::linkKnown(
+                               $rc->getTitle(),
+                               $this->message['hist'],
+                               array(),
+                               array(
+                                       'curid' => $rc->mAttribs['rc_cur_id'],
+                                       'action' => 'history'
+                               )
+                       );
+               }
+
                // @todo FIXME: Hard coded ". .". Is there a message for this? Should there be?
                $s .= $this->msg( 'parentheses' )->rawParams( $diffhist )->escaped() .
                        ' <span class="mw-changeslist-separator">. .</span> ';
@@ -645,4 +654,19 @@ class ChangesList extends ContextSource {
 
                return false;
        }
+
+       /**
+        * Determines whether a revision is linked to this change; this may not be the case
+        * when the categorization wasn't done by an edit but a conditional parser function
+        *
+        * @since 1.27
+        *
+        * @param RecentChange|RCCacheEntry $rcObj
+        * @return bool
+        */
+       protected function isCategorizationWithoutRevision( $rcObj ) {
+               return intval( $rcObj->getAttribute( 'rc_type' ) ) === RC_CATEGORIZE
+                       && intval( $rcObj->getAttribute( 'rc_this_oldid' ) ) === 0;
+       }
+
 }
index f10307d..b59437e 100644 (file)
@@ -406,6 +406,8 @@ class EnhancedChangesList extends ChangesList {
 
                if ( $rcObj->mAttribs['rc_type'] == RC_LOG ) {
                        $data['logEntry'] = $this->insertLogEntry( $rcObj );
+               } elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) {
+                       $data['comment'] = $this->insertComment( $rcObj );
                } else {
                        # User links
                        $data['userLink'] = $rcObj->userlink;
@@ -498,7 +500,7 @@ class EnhancedChangesList extends ChangesList {
                /** @var $block0 RecentChange */
                $block0 = $block[0];
                $last = $block[count( $block ) - 1];
-               if ( !$allLogs ) {
+               if ( !$allLogs && $rcObj->mAttribs['rc_type'] != RC_CATEGORIZE ) {
                        if ( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT, $this->getUser() ) ) {
                                $links['total-changes'] = $nchanges[$n];
                        } elseif ( $isnew ) {
@@ -530,7 +532,7 @@ class EnhancedChangesList extends ChangesList {
                }
 
                # History
-               if ( $allLogs ) {
+               if ( $allLogs || $rcObj->mAttribs['rc_type'] == RC_CATEGORIZE ) {
                        // don't show history link for logs
                } elseif ( $namehidden || !$block0->getTitle()->exists() ) {
                        $links['history'] = $this->message['enhancedrc-history'];
@@ -606,15 +608,9 @@ class EnhancedChangesList extends ChangesList {
                }
 
                # Diff and hist links
-               if ( $type != RC_LOG ) {
+               if ( $type  == RC_LOG && $type != RC_CATEGORIZE ) {
                        $query['action'] = 'history';
-                       $data['historyLink'] = ' ' . $this->msg( 'parentheses' )
-                               ->rawParams( $rcObj->difflink . $this->message['pipe-separator'] . Linker::linkKnown(
-                                       $rcObj->getTitle(),
-                                       $this->message['hist'],
-                                       array(),
-                                       $query
-                               ) )->escaped();
+                       $data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query );
                }
                $data['separatorAfterLinks'] = ' <span class="mw-changeslist-separator">. .</span> ';
 
@@ -629,10 +625,15 @@ class EnhancedChangesList extends ChangesList {
 
                if ( $type == RC_LOG ) {
                        $data['logEntry'] = $this->insertLogEntry( $rcObj );
+               } elseif ( $this->isCategorizationWithoutRevision( $rcObj ) ) {
+                       $data['comment'] = $this->insertComment( $rcObj );
                } else {
                        $data['userLink'] = $rcObj->userlink;
                        $data['userTalkLink'] = $rcObj->usertalklink;
                        $data['comment'] = $this->insertComment( $rcObj );
+                       if ( $type == RC_CATEGORIZE ) {
+                               $data['historyLink'] = $this->getDiffHistLinks( $rcObj, $query );
+                       }
                        $data['rollback'] = $this->getRollback( $rcObj );
                }
 
@@ -673,6 +674,33 @@ class EnhancedChangesList extends ChangesList {
                return $line;
        }
 
+       /**
+        * Returns value to be used in 'historyLink' element of $data param in
+        * EnhancedChangesListModifyBlockLineData hook.
+        *
+        * @since 1.27
+        *
+        * @param RCCacheEntry $rc
+        * @param array $query array of key/value pairs to append as a query string
+        * @return string HTML
+        */
+       public function getDiffHistLinks( RCCacheEntry $rc, array $query ) {
+               $pageTitle = $rc->getTitle();
+               if ( $rc->getAttribute( 'rc_type' ) == RC_CATEGORIZE ) {
+                       // For categorizations we must swap the category title with the page title!
+                       $pageTitle = Title::newFromID( $rc->getAttribute( 'rc_cur_id' ) );
+               }
+
+               $retVal = ' ' . $this->msg( 'parentheses' )
+                               ->rawParams( $rc->difflink . $this->message['pipe-separator'] . Linker::linkKnown(
+                                               $pageTitle,
+                                               $this->message['hist'],
+                                               array(),
+                                               $query
+                                       ) )->escaped();
+               return $retVal;
+       }
+
        /**
         * If enhanced RC is in use, this function takes the previously cached
         * RC lines, arranges them, and outputs the HTML
index 4ce564d..31b355d 100644 (file)
@@ -87,7 +87,6 @@ class OldChangesList extends ChangesList {
                // Regular entries
                } else {
                        $unpatrolled = $this->showAsUnpatrolled( $rc );
-
                        $this->insertDiffHist( $html, $rc, $unpatrolled );
                        # M, N, b and ! (minor, new, bot and unpatrolled)
                        $html .= $this->recentChangesFlags(
@@ -113,6 +112,8 @@ class OldChangesList extends ChangesList {
 
                if ( $rc->mAttribs['rc_type'] == RC_LOG ) {
                        $html .= $this->insertLogEntry( $rc );
+               } elseif ( $this->isCategorizationWithoutRevision( $rc ) ) {
+                       $html .= $this->insertComment( $rc );
                } else {
                        # User tool links
                        $this->insertUserRelatedLinks( $html, $rc );
index c3fe183..f31125d 100644 (file)
@@ -209,6 +209,15 @@ class RCCacheEntryFactory {
                        $diffLink = $diffMessage;
                } elseif ( in_array( $cacheEntry->mAttribs['rc_type'], $logTypes ) ) {
                        $diffLink = $diffMessage;
+               } elseif ( $cacheEntry->getAttribute( 'rc_type' ) == RC_CATEGORIZE ) {
+                       $rcCurId = $cacheEntry->getAttribute( 'rc_cur_id' );
+                       $pageTitle = Title::newFromID( $rcCurId );
+                       if ( $pageTitle === null ) {
+                               wfDebugLog( 'RCCacheEntryFactory', 'Could not get Title for rc_cur_id: ' . $rcCurId );
+                               return $diffMessage;
+                       }
+                       $diffUrl = htmlspecialchars( $pageTitle->getLinkURL( $queryParams ) );
+                       $diffLink = "<a href=\"$diffUrl\" tabindex=\"$counter\">$diffMessage</a>";
                } else {
                        $diffUrl = htmlspecialchars( $cacheEntry->getTitle()->getLinkURL( $queryParams ) );
                        $diffLink = "<a href=\"$diffUrl\" tabindex=\"$counter\">$diffMessage</a>";
index 3599f50..9025736 100644 (file)
@@ -324,15 +324,21 @@ class RecentChange {
                        $editor = $this->getPerformer();
                        $title = $this->getTitle();
 
-                       if ( Hooks::run( 'AbortEmailNotification', array( $editor, $title, $this ) ) ) {
-                               # @todo FIXME: This would be better as an extension hook
-                               $enotif = new EmailNotification();
-                               $enotif->notifyOnPageChange( $editor, $title,
-                                       $this->mAttribs['rc_timestamp'],
-                                       $this->mAttribs['rc_comment'],
-                                       $this->mAttribs['rc_minor'],
-                                       $this->mAttribs['rc_last_oldid'],
-                                       $this->mExtra['pageStatus'] );
+                       // Never send an RC notification email about categorization changes
+                       if ( $this->mAttribs['rc_type'] != RC_CATEGORIZE ) {
+                               if ( Hooks::run( 'AbortEmailNotification', array( $editor, $title, $this ) ) ) {
+                                       # @todo FIXME: This would be better as an extension hook
+                                       $enotif = new EmailNotification();
+                                       $enotif->notifyOnPageChange(
+                                               $editor,
+                                               $title,
+                                               $this->mAttribs['rc_timestamp'],
+                                               $this->mAttribs['rc_comment'],
+                                               $this->mAttribs['rc_minor'],
+                                               $this->mAttribs['rc_last_oldid'],
+                                               $this->mExtra['pageStatus']
+                                       );
+                               }
                        }
                }
 
index 5a8fe92..53e9a50 100644 (file)
@@ -513,6 +513,10 @@ class DBConnRef implements IDatabase {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
+       public function isReadOnly() {
+               return $this->__call( __FUNCTION__, func_get_args() );
+       }
+
        /**
         * Clean up the connection when out of scope
         */
index 1da85d7..2f135a4 100644 (file)
@@ -927,8 +927,8 @@ abstract class DatabaseBase implements IDatabase {
 
                $isWriteQuery = $this->isWriteQuery( $sql );
                if ( $isWriteQuery ) {
-                       $reason = $this->getLBInfo( 'readOnlyReason' );
-                       if ( is_string( $reason ) ) {
+                       $reason = $this->getReadOnlyReason();
+                       if ( $reason !== false ) {
                                throw new DBReadOnlyError( $this, "Database is read-only: $reason" );
                        }
                        # Set a flag indicating that writes have been done
@@ -4286,6 +4286,19 @@ abstract class DatabaseBase implements IDatabase {
                // no-op
        }
 
+       public function isReadOnly() {
+               return ( $this->getReadOnlyReason() !== false );
+       }
+
+       /**
+        * @return string|bool Reason this DB is read-only or false if it is not
+        */
+       protected function getReadOnlyReason() {
+               $reason = $this->getLBInfo( 'readOnlyReason' );
+
+               return is_string( $reason ) ? $reason : false;
+       }
+
        /**
         * @since 1.19
         * @return string
index ba3c1dd..d110583 100644 (file)
@@ -1511,4 +1511,10 @@ interface IDatabase {
         *   restore the initial value
         */
        public function setBigSelects( $value = true );
+
+       /**
+        * @return bool Whether this DB is read-only
+        * @since 1.27
+        */
+       public function isReadOnly();
 }
index bad04f9..e7b7627 100644 (file)
@@ -29,11 +29,17 @@ abstract class LBFactory {
        /** @var LBFactory */
        private static $instance;
 
+       /** @var string|bool Reason all LBs are read-only or false if not */
+       protected $readOnlyReason = false;
+
        /**
         * Construct a factory based on a configuration array (typically from $wgLBFactoryConf)
         * @param array $conf
         */
        public function __construct( array $conf ) {
+               if ( isset( $conf['readOnlyReason'] ) && is_string( $conf['readOnlyReason'] ) ) {
+                       $this->readOnlyReason = $conf['readOnlyReason'];
+               }
        }
 
        /**
@@ -55,8 +61,11 @@ abstract class LBFactory {
 
                if ( is_null( self::$instance ) ) {
                        $class = self::getLBFactoryClass( $wgLBFactoryConf );
-
-                       self::$instance = new $class( $wgLBFactoryConf );
+                       $config = $wgLBFactoryConf;
+                       if ( !isset( $config['readOnlyReason'] ) ) {
+                               $config['readOnlyReason'] = wfConfiguredReadOnlyReason();
+                       }
+                       self::$instance = new $class( $config );
                }
 
                return self::$instance;
index 2655659..089dfd3 100644 (file)
@@ -209,19 +209,27 @@ class LBFactoryMulti extends LBFactory {
        public function newMainLB( $wiki = false ) {
                list( $dbName, ) = $this->getDBNameAndPrefix( $wiki );
                $section = $this->getSectionForWiki( $wiki );
-               $groupLoads = array();
                if ( isset( $this->groupLoadsByDB[$dbName] ) ) {
                        $groupLoads = $this->groupLoadsByDB[$dbName];
+               } else {
+                       $groupLoads = array();
                }
 
                if ( isset( $this->groupLoadsBySection[$section] ) ) {
                        $groupLoads = array_merge_recursive( $groupLoads, $this->groupLoadsBySection[$section] );
                }
 
+               $readOnlyReason = $this->readOnlyReason;
+               // Use the LB-specific read-only reason if everything isn't already read-only
+               if ( $readOnlyReason === false && isset( $this->readOnlyBySection[$section] ) ) {
+                       $readOnlyReason = $this->readOnlyBySection[$section];
+               }
+
                return $this->newLoadBalancer(
                        $this->serverTemplate,
                        $this->sectionLoads[$section],
-                       $groupLoads
+                       $groupLoads,
+                       $readOnlyReason
                );
        }
 
@@ -259,7 +267,12 @@ class LBFactoryMulti extends LBFactory {
                        $template = $this->templateOverridesByCluster[$cluster] + $template;
                }
 
-               return $this->newLoadBalancer( $template, $this->externalLoads[$cluster], array() );
+               return $this->newLoadBalancer(
+                       $template,
+                       $this->externalLoads[$cluster],
+                       array(),
+                       $this->readOnlyReason
+               );
        }
 
        /**
@@ -283,16 +296,15 @@ class LBFactoryMulti extends LBFactory {
         * @param array $template
         * @param array $loads
         * @param array $groupLoads
+        * @param string|bool $readOnlyReason
         * @return LoadBalancer
         */
-       private function newLoadBalancer( $template, $loads, $groupLoads ) {
-               $servers = $this->makeServerArray( $template, $loads, $groupLoads );
-               $lb = new LoadBalancer( array(
-                       'servers' => $servers,
-                       'loadMonitor' => $this->loadMonitorClass
+       private function newLoadBalancer( $template, $loads, $groupLoads, $readOnlyReason ) {
+               return new LoadBalancer( array(
+                       'servers' => $this->makeServerArray( $template, $loads, $groupLoads ),
+                       'loadMonitor' => $this->loadMonitorClass,
+                       'readOnlyReason' => $readOnlyReason
                ) );
-
-               return $lb;
        }
 
        /**
index e328727..353c47a 100644 (file)
@@ -89,7 +89,8 @@ class LBFactorySimple extends LBFactory {
 
                return new LoadBalancer( array(
                        'servers' => $servers,
-                       'loadMonitor' => $this->loadMonitorClass
+                       'loadMonitor' => $this->loadMonitorClass,
+                       'readOnlyReason' => $this->readOnlyReason
                ) );
        }
 
@@ -121,7 +122,8 @@ class LBFactorySimple extends LBFactory {
 
                return new LoadBalancer( array(
                        'servers' => $wgExternalServers[$cluster],
-                       'loadMonitor' => $this->loadMonitorClass
+                       'loadMonitor' => $this->loadMonitorClass,
+                       'readOnlyReason' => $this->readOnlyReason
                ) );
        }
 
index 32bce6c..5a6cfa7 100644 (file)
@@ -35,6 +35,7 @@ class LBFactorySingle extends LBFactory {
        public function __construct( array $conf ) {
                parent::__construct( $conf );
 
+               $conf['readOnlyReason'] = $this->readOnlyReason;
                $this->lb = new LoadBalancerSingle( $conf );
        }
 
@@ -93,12 +94,21 @@ class LoadBalancerSingle extends LoadBalancer {
         */
        public function __construct( array $params ) {
                $this->db = $params['connection'];
-               parent::__construct( array( 'servers' => array( array(
-                       'type' => $this->db->getType(),
-                       'host' => $this->db->getServer(),
-                       'dbname' => $this->db->getDBname(),
-                       'load' => 1,
-               ) ) ) );
+
+               parent::__construct( array(
+                       'servers' => array(
+                               array(
+                                       'type' => $this->db->getType(),
+                                       'host' => $this->db->getServer(),
+                                       'dbname' => $this->db->getDBname(),
+                                       'load' => 1,
+                               )
+                       )
+               ) );
+
+               if ( isset( $params['readOnlyReason'] ) ) {
+                       $this->db->setLBInfo( 'readOnlyReason', $params['readOnlyReason'] );
+               }
        }
 
        /**
index eda374a..bc9465b 100644 (file)
@@ -55,9 +55,13 @@ class LoadBalancer {
        /** @var bool|DBMasterPos False if not set */
        private $mWaitForPos;
        /** @var bool Whether the generic reader fell back to a lagged slave */
-       private $mLaggedSlaveMode;
+       private $laggedSlaveMode = false;
+       /** @var bool Whether the generic reader fell back to a lagged slave */
+       private $slavesDownMode = false;
        /** @var string The last DB selection or connection error */
        private $mLastError = 'Unknown error';
+       /** @var string|bool Reason the LB is read-only or false if not */
+       private $readOnlyReason = false;
        /** @var integer Total connections opened */
        private $connsOpened = 0;
 
@@ -68,8 +72,9 @@ class LoadBalancer {
 
        /**
         * @param array $params Array with keys:
-        *   servers           Required. Array of server info structures.
-        *   loadMonitor       Name of a class used to fetch server lag and load.
+        *  - servers : Required. Array of server info structures.
+        *  - loadMonitor : Name of a class used to fetch server lag and load.
+        *  - readOnlyReason : Reason the master DB is read-only if so [optional]
         * @throws MWException
         */
        public function __construct( array $params ) {
@@ -87,10 +92,13 @@ class LoadBalancer {
                        'foreignFree' => array() );
                $this->mLoads = array();
                $this->mWaitForPos = false;
-               $this->mLaggedSlaveMode = false;
                $this->mErrorConnection = false;
                $this->mAllowLagged = false;
 
+               if ( isset( $params['readOnlyReason'] ) && is_string( $params['readOnlyReason'] ) ) {
+                       $this->readOnlyReason = $params['readOnlyReason'];
+               }
+
                if ( isset( $params['loadMonitor'] ) ) {
                        $this->mLoadMonitorClass = $params['loadMonitor'];
                } else {
@@ -154,7 +162,7 @@ class LoadBalancer {
        /**
         * @param array $loads
         * @param bool|string $wiki Wiki to get non-lagged for
-        * @param float $maxLag Restrict the maximum allowed lag to this many seconds
+        * @param int $maxLag Restrict the maximum allowed lag to this many seconds
         * @return bool|int|string
         */
        private function getRandomNonLagged( array $loads, $wiki = false, $maxLag = self::MAX_LAG ) {
@@ -329,7 +337,7 @@ class LoadBalancer {
                                $this->mReadIndex = $i;
                                # Record if the generic reader index is in "lagged slave" mode
                                if ( $laggedSlaveMode ) {
-                                       $this->mLaggedSlaveMode = true;
+                                       $this->laggedSlaveMode = true;
                                }
                        }
                        $serverName = $this->getServerName( $i );
@@ -352,7 +360,7 @@ class LoadBalancer {
                if ( $i > 0 ) {
                        if ( !$this->doWait( $i ) ) {
                                $this->mServers[$i]['slave pos'] = $this->getAnyOpenConnection( $i )->getSlavePos();
-                               $this->mLaggedSlaveMode = true;
+                               $this->laggedSlaveMode = true;
                        }
                }
        }
@@ -548,12 +556,9 @@ class LoadBalancer {
                        $trxProf->recordConnection( $host, $dbname, $masterOnly );
                }
 
-               # Make master connections read only if in lagged slave mode
-               if ( $masterOnly && $this->getServerCount() > 1 && $this->getLaggedSlaveMode( $wiki ) ) {
-                       $conn->setLBInfo( 'readOnlyReason',
-                               'The database has been automatically locked ' .
-                               'while the slave database servers catch up to the master'
-                       );
+               if ( $masterOnly ) {
+                       # Make master-requested DB handles inherit any read-only mode setting
+                       $conn->setLBInfo( 'readOnlyReason', $this->getReadOnlyReason( $wiki ) );
                }
 
                return $conn;
@@ -1147,10 +1152,19 @@ class LoadBalancer {
         * @return bool Whether the generic connection for reads is highly "lagged"
         */
        public function getLaggedSlaveMode( $wiki = false ) {
-               # Get a generic reader connection
-               $this->getConnection( DB_SLAVE, false, $wiki );
+               // No-op if there is only one DB (also avoids recursion)
+               if ( !$this->laggedSlaveMode && $this->getServerCount() > 1 ) {
+                       try {
+                               // See if laggedSlaveMode gets set
+                               $this->getConnection( DB_SLAVE, false, $wiki );
+                       } catch ( DBConnectionError $e ) {
+                               // Avoid expensive re-connect attempts and failures
+                               $this->slavesDownMode = true;
+                               $this->laggedSlaveMode = true;
+                       }
+               }
 
-               return $this->mLaggedSlaveMode;
+               return $this->laggedSlaveMode;
        }
 
        /**
@@ -1159,7 +1173,29 @@ class LoadBalancer {
         * @since 1.27
         */
        public function laggedSlaveUsed() {
-               return $this->mLaggedSlaveMode;
+               return $this->laggedSlaveMode;
+       }
+
+       /**
+        * @note This method may trigger a DB connection if not yet done
+        * @param string|bool $wiki Wiki ID, or false for the current wiki
+        * @return string|bool Reason the master is read-only or false if it is not
+        * @since 1.27
+        */
+       public function getReadOnlyReason( $wiki = false ) {
+               if ( $this->readOnlyReason !== false ) {
+                       return $this->readOnlyReason;
+               } elseif ( $this->getLaggedSlaveMode( $wiki ) ) {
+                       if ( $this->slavesDownMode ) {
+                               return 'The database has been automatically locked ' .
+                                       'until the slave database servers become available';
+                       } else {
+                               return 'The database has been automatically locked ' .
+                                       'while the slave database servers catch up to the master.';
+                       }
+               }
+
+               return false;
        }
 
        /**
index d996870..b96fa46 100644 (file)
@@ -61,6 +61,12 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
        /** @var bool Whether to queue jobs for recursive updates */
        public $mRecursive;
 
+       /** @var bool Whether this job was triggered by a recursive update job */
+       private $mTriggeredRecursive;
+
+       /** @var Revision Revision for which this update has been triggered */
+       private $mRevision;
+
        /**
         * @var null|array Added links if calculated.
         */
@@ -147,6 +153,7 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
        }
 
        protected function doIncrementalUpdate() {
+               global $wgRCWatchCategoryMembership;
 
                # Page links
                $existing = $this->getExistingLinks();
@@ -199,6 +206,14 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
                $this->invalidateCategories( $categoryUpdates );
                $this->updateCategoryCounts( $categoryInserts, $categoryDeletes );
 
+               # Category membership changes
+               if (
+                       $wgRCWatchCategoryMembership &&
+                       !$this->mTriggeredRecursive && ( $categoryInserts || $categoryDeletes )
+               ) {
+                       $this->triggerCategoryChanges( $categoryInserts, $categoryDeletes );
+               }
+
                # Page properties
                $existing = $this->getExistingProperties();
 
@@ -222,6 +237,24 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
 
        }
 
+       private function triggerCategoryChanges( $categoryInserts, $categoryDeletes ) {
+               $catMembChange = new CategoryMembershipChange( $this->mTitle, $this->mRevision );
+
+               if ( $this->mRecursive ) {
+                       $catMembChange->checkTemplateLinks();
+               }
+
+               foreach ( $categoryInserts as $categoryName => $value ) {
+                       $categoryTitle = Title::newFromText( $categoryName, NS_CATEGORY );
+                       $catMembChange->triggerCategoryAddedNotification( $categoryTitle );
+               }
+
+               foreach ( $categoryDeletes as $categoryName => $value ) {
+                       $categoryTitle = Title::newFromText( $categoryName, NS_CATEGORY );
+                       $catMembChange->triggerCategoryRemovedNotification( $categoryTitle );
+               }
+       }
+
        /**
         * Queue recursive jobs for this page
         *
@@ -863,6 +896,26 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
                return $this->mImages;
        }
 
+       /**
+        * Set this object as being triggered by a recursive LinksUpdate
+        *
+        * @since 1.27
+        */
+       public function setTriggeredRecursive() {
+               $this->mTriggeredRecursive = true;
+       }
+
+       /**
+        * Set the revision corresponding to this LinksUpdate
+        *
+        * @since 1.27
+        *
+        * @param Revision $revision
+        */
+       public function setRevision( Revision $revision ) {
+               $this->mRevision = $revision;
+       }
+
        /**
         * Invalidate any necessary link lists related to page property changes
         * @param array $changed
@@ -940,7 +993,11 @@ class LinksUpdate extends SqlDataUpdate implements EnqueueableDataUpdate {
                        'wiki' => $this->mDb->getWikiID(),
                        'job'  => new JobSpecification(
                                'refreshLinks',
-                               array( 'prioritize' => true ),
+                               array(
+                                       'prioritize' => true,
+                                       // Reuse the parser cache if it was saved
+                                       'rootJobTimestamp' => $this->mParserOutput->getCacheTime()
+                               ),
                                array( 'removeDuplicates' => true ),
                                $this->getTitle()
                        )
index 852982f..21a4fa3 100644 (file)
        "config-outdated-sqlite": "'''Avisu:''' tien SQLite $1, que ye inferior a la versión mínima necesaria $2. SQLite nun tará disponible.",
        "config-no-fts3": "'''Avisu:''' SQLite ta compiláu ensin el [//sqlite.org/fts3.html módulu FTS3]; les funciones de gueta nun tarán disponibles nesti sistema.",
        "config-register-globals-error": "<strong>Error: la opción de PHP <code>[http://php.net/register_globals register_globals]</code> ta activada.\nTien de desactivase pa siguir cola instalación.</strong>\nVisita [https://www.mediawiki.org/wiki/register_globals https://www.mediawiki.org/wiki/register_globals] p'alcontrar ayuda tocante a cómo facelo.",
+       "config-magic-quotes-gpc": "<strong>Fatal: ¡[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-gpc magic_quotes_gpc] ta activáu!</strong>\nEsta opción causa la imprevisible corrupción de la entrada de datos.\nNun puedes instalar o utilizar MediaWiki nun siendo que esta opción tea desactivada.",
+       "config-magic-quotes-runtime": "<strong>Fatal: ¡[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-runtime magic_quotes_runtime] ta activáu!</strong>\nEsta opción causa la imprevisible corrupción de la entrada de datos.\nNun puedes instalar o utilizar MediaWiki nun siendo que esta opción tea desactivada.",
+       "config-magic-quotes-sybase": "<strong>Fatal: ¡[http://www.php.net/manual/en/ref.info.php#ini.magic-quotes-sybase magic_quotes_sybase] ta activáu!</strong>\nEsta opción causa la imprevisible corrupción de la entrada de datos.\nNun puedes instalar o utilizar MediaWiki nun siendo que esta opción tea desactivada.",
+       "config-mbstring": "<strong>Fatal: ¡[@http://www.php.net/manual/en/ref.mbstring.php#mbstring.overload mbstring.func_overload] ta activáu!</strong>\nEsta opción causa errores y pué corromper los datos de mou imprevisible.\nNun puedes instalar o utilizar MediaWiki nun siendo que esta opción tea desactivada.",
+       "config-safe-mode": "<strong>Atención:</strong> el [http://www.php.net/features.safe-mode mou seguru] de PHP ta activáu.\nPuede causar problemes, especialmente si uses la carga de ficheros ya l'encontu pa <code>math</code>.",
+       "config-xml-bad": "Falta'l módulu XML de PHP.\nMediaWiki rique funciones d'esti módulu y nun va funcionar con esta configuración.\nSeique precises instalar el paquete RPM llamáu php-xml.",
+       "config-pcre-old": "<strong>Fatal:</strong> Ríquese PCRE $1 o posterior.\nEl binariu de PHP ta enllazáu con PCRE $2.\n[https://www.mediawiki.org/wiki/Manual:Errors_and_symptoms/PCRE Más información].",
        "config-diff3-bad": "Nun s'alcontró GNU diff3.",
        "config-git": "Alcontróse'l software de control de versiones Git: <code>$1</code>.",
        "config-git-bad": "Nun s'alcontró el software de control de versiones Git.",
index 29c8068..002fe0d 100644 (file)
@@ -301,7 +301,7 @@ LUA;
                                        break; // no jobs; nothing to do
                                }
 
-                               JobQueue::incrStats( 'job-pop', $this->type );
+                               JobQueue::incrStats( 'pops', $this->type );
                                $item = $this->unserialize( $blob );
                                if ( $item === false ) {
                                        wfDebugLog( 'JobQueueRedis', "Could not unserialize {$this->type} job." );
@@ -393,7 +393,7 @@ LUA;
                                return false;
                        }
 
-                       JobQueue::incrStats( 'job-ack', $this->type );
+                       JobQueue::incrStats( 'acks', $this->type );
                } catch ( RedisException $e ) {
                        $this->throwRedisException( $conn, $e );
                }
index 4ba1d4c..7093e14 100644 (file)
@@ -83,6 +83,7 @@ class RefreshLinksJob extends Job {
                        } else {
                                $extraParams['masterPos'] = false;
                        }
+                       $extraParams['triggeredRecursive'] = true;
                        // 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(
@@ -197,6 +198,12 @@ class RefreshLinksJob extends Job {
                }
 
                $updates = $content->getSecondaryDataUpdates( $title, null, false, $parserOutput );
+               foreach ( $updates as $key => $update ) {
+                       if ( $update instanceof LinksUpdate && isset( $this->params['triggeredRecursive'] ) ) {
+                               $update->setTriggeredRecursive();
+                       }
+               }
+
                DataUpdate::runUpdates( $updates );
 
                InfoAction::invalidateCache( $title );
index a9304c1..98380d9 100644 (file)
@@ -33,6 +33,8 @@ class MultiWriteBagOStuff extends BagOStuff {
        protected $caches;
        /** @var bool Use async secondary writes */
        protected $asyncWrites = false;
+       /** @var callback|null */
+       protected $asyncHandler;
 
        /** Idiom for "write to all backends" */
        const ALL = INF;
@@ -41,22 +43,23 @@ class MultiWriteBagOStuff extends BagOStuff {
 
        /**
         * $params include:
-        *   - caches:      A numbered array of either ObjectFactory::getObjectFromSpec
-        *                  arrays yeilding BagOStuff objects or direct BagOStuff objects.
-        *                  If using the former, the 'args' field *must* be set.
-        *                  The first cache is the primary one, being the first to
-        *                  be read in the fallback chain. Writes happen to all stores
-        *                  in the order they are defined. However, lock()/unlock() calls
-        *                  only use the primary store.
-        *   - replication: Either 'sync' or 'async'. This controls whether writes to
-        *                  secondary stores are deferred when possible. Async writes
-        *                  require the HHVM register_postsend_function() function.
-        *                  Async writes can increase the chance of some race conditions
-        *                  or cause keys to expire seconds later than expected. It is
-        *                  safe to use for modules when cached values: are immutable,
-        *                  invalidation uses logical TTLs, invalidation uses etag/timestamp
-        *                  validation against the DB, or merge() is used to handle races.
-        *
+        *   - caches: A numbered array of either ObjectFactory::getObjectFromSpec
+        *      arrays yeilding BagOStuff objects or direct BagOStuff objects.
+        *      If using the former, the 'args' field *must* be set.
+        *      The first cache is the primary one, being the first to
+        *      be read in the fallback chain. Writes happen to all stores
+        *      in the order they are defined. However, lock()/unlock() calls
+        *      only use the primary store.
+        *   - replication: Either 'sync' or 'async'. This controls whether writes
+        *      to secondary stores are deferred when possible. Async writes
+        *      require setting 'asyncCallback'. HHVM register_postsend_function() function.
+        *      Async writes can increase the chance of some race conditions
+        *      or cause keys to expire seconds later than expected. It is
+        *      safe to use for modules when cached values: are immutable,
+        *      invalidation uses logical TTLs, invalidation uses etag/timestamp
+        *      validation against the DB, or merge() is used to handle races.
+        *   - asyncHandler: callable that takes a callback and runs it after the
+        *      current web request ends. In CLI mode, it should run it immediately.
         * @param array $params
         * @throws InvalidArgumentException
         */
@@ -85,7 +88,14 @@ class MultiWriteBagOStuff extends BagOStuff {
                        }
                }
 
-               $this->asyncWrites = isset( $params['replication'] ) && $params['replication'] === 'async';
+               $this->asyncHandler = isset( $params['asyncHandler'] )
+                       ? $params['asyncHandler']
+                       : null;
+               $this->asyncWrites = (
+                       isset( $params['replication'] ) &&
+                       $params['replication'] === 'async' &&
+                       is_callable( $this->asyncHandler )
+               );
        }
 
        /**
@@ -96,6 +106,13 @@ class MultiWriteBagOStuff extends BagOStuff {
        }
 
        protected function doGet( $key, $flags = 0 ) {
+               if ( ( $flags & self::READ_LATEST ) == self::READ_LATEST ) {
+                       // If the latest write was a delete(), we do NOT want to fallback
+                       // to the other tiers and possibly see the old value. Also, this
+                       // is used by mergeViaLock(), which only needs to hit the primary.
+                       return $this->caches[0]->get( $key, $flags );
+               }
+
                $misses = 0; // number backends checked
                $value = false;
                foreach ( $this->caches as $cache ) {
@@ -182,17 +199,6 @@ class MultiWriteBagOStuff extends BagOStuff {
                return $this->caches[0]->unlock( $key );
        }
 
-       /**
-        * @param string $key
-        * @param callable $callback Callback method to be executed
-        * @param int $exptime Either an interval in seconds or a unix timestamp for expiry
-        * @param int $attempts The amount of times to attempt a merge in case of failure
-        * @return bool Success
-        */
-       public function merge( $key, $callback, $exptime = 0, $attempts = 10 ) {
-               return $this->doWrite( self::ALL, 'merge', $key, $callback, $exptime );
-       }
-
        public function getLastError() {
                return $this->caches[0]->getLastError();
        }
@@ -226,7 +232,8 @@ class MultiWriteBagOStuff extends BagOStuff {
                        } else {
                                // Secondary write in async mode: do not block this HTTP request
                                $logger = $this->logger;
-                               DeferredUpdates::addCallableUpdate(
+                               call_user_func(
+                                       $this->asyncHandler,
                                        function () use ( $cache, $method, $args, $logger ) {
                                                if ( !call_user_func_array( array( $cache, $method ), $args ) ) {
                                                        $logger->warning( "Async $method op failed" );
index 3665c11..151bb06 100644 (file)
@@ -178,6 +178,9 @@ class ObjectCache {
                        return call_user_func( $params['factory'], $params );
                } elseif ( isset( $params['class'] ) ) {
                        $class = $params['class'];
+                       if ( $class === 'MultiWriteBagOStuff' && !isset( $params['asyncHandler'] ) ) {
+                               $params['asyncHandler'] = 'DeferredUpdates::addCallableUpdate';
+                       }
                        return new $class( $params );
                } else {
                        throw new MWException( "The definition of cache type \""
index 29038c7..cdaab1a 100644 (file)
@@ -2182,6 +2182,9 @@ class WikiPage implements Page, IDBAccessObject {
                        $updates = $content->getSecondaryDataUpdates(
                                $this->getTitle(), null, $recursive, $editInfo->output );
                        foreach ( $updates as $update ) {
+                               if ( $update instanceof LinksUpdate ) {
+                                       $update->setRevision( $revision );
+                               }
                                DeferredUpdates::addUpdate( $update );
                        }
                }
@@ -3489,11 +3492,7 @@ class WikiPage implements Page, IDBAccessObject {
                if ( $this->getLinksTimestamp() < $this->getTouched() ) {
                        $params['isOpportunistic'] = true;
                        $params['rootJobTimestamp'] = $parserOutput->getCacheTime();
-
-                       JobQueueGroup::singleton()->lazyPush( EnqueueJob::newFromLocalJobs(
-                               new JobSpecification( 'refreshLinks', $params,
-                                       array( 'removeDuplicates' => true ), $this->mTitle )
-                       ) );
+                       JobQueueGroup::singleton()->lazyPush( new RefreshLinksJob( $this->mTitle, $params ) );
                }
        }
 
index 0023de2..156ff4e 100644 (file)
@@ -214,7 +214,8 @@ class ResourceLoaderWikiModule extends ResourceLoaderModule {
                        if ( $this->getFlip( $context ) ) {
                                $style = CSSJanus::transform( $style, true, false );
                        }
-                       $style = CSSMin::remap( $style, false, $this->getConfig()->get( 'ScriptPath' ), true );
+                       $style = MemoizedCallable::call( 'CSSMin::remap',
+                               array( $style, false, $this->getConfig()->get( 'ScriptPath' ), true ) );
                        if ( !isset( $styles[$media] ) ) {
                                $styles[$media] = array();
                        }
index 4e6201c..12ebb54 100644 (file)
@@ -180,7 +180,7 @@ abstract class Skin extends ContextSource {
         * @return array Array of modules with helper keys for easy overriding
         */
        public function getDefaultModules() {
-               global $wgUseAjax, $wgAjaxWatch, $wgEnableAPI, $wgEnableWriteAPI;
+               global $wgUseAjax, $wgEnableAPI, $wgEnableWriteAPI;
 
                $out = $this->getOutput();
                $user = $out->getUser();
@@ -201,7 +201,7 @@ abstract class Skin extends ContextSource {
 
                // Add various resources if required
                if ( $wgUseAjax && $wgEnableAPI ) {
-                       if ( $wgEnableWriteAPI && $wgAjaxWatch && $user->isLoggedIn()
+                       if ( $wgEnableWriteAPI && $user->isLoggedIn()
                                && $user->isAllowedAll( 'writeapi', 'viewmywatchlist', 'editmywatchlist' )
                                && $this->getRelevantTitle()->canExist()
                        ) {
index ccdf3fa..74842aa 100644 (file)
@@ -136,6 +136,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
         * @return FormOptions
         */
        public function getDefaultOptions() {
+               $config = $this->getConfig();
                $opts = new FormOptions();
 
                $opts->add( 'hideminor', false );
@@ -145,6 +146,10 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                $opts->add( 'hidepatrolled', false );
                $opts->add( 'hidemyself', false );
 
+               if ( $config->get( 'RCWatchCategoryMembership' ) ) {
+                       $opts->add( 'hidecategorization', false );
+               }
+
                $opts->add( 'namespace', '', FormOptions::INTNULL );
                $opts->add( 'invert', false );
                $opts->add( 'associated', false );
@@ -249,6 +254,11 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                                $conds[] = 'rc_user_text != ' . $dbr->addQuotes( $user->getName() );
                        }
                }
+               if ( $this->getConfig()->get( 'RCWatchCategoryMembership' )
+                       && $opts['hidecategorization'] === true
+               ) {
+                       $conds[] = 'rc_type != ' . $dbr->addQuotes( RC_CATEGORIZE );
+               }
 
                // Namespace filtering
                if ( $opts['namespace'] !== '' ) {
index b7582e6..06eb276 100644 (file)
@@ -114,7 +114,7 @@ class SpecialExpandTemplates extends SpecialPage {
                        }
 
                        $config = $this->getConfig();
-                       if ( ( $config->get( 'UseTidy' ) && $options->getTidy() ) || $config->get( 'AlwaysUseTidy' ) ) {
+                       if ( $config->get( 'UseTidy' ) && $options->getTidy() ) {
                                $tmp = MWTidy::tidy( $tmp );
                        }
 
index 96d512c..da84a9e 100644 (file)
@@ -72,6 +72,7 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
        public function getDefaultOptions() {
                $opts = parent::getDefaultOptions();
                $user = $this->getUser();
+               $config = $this->getConfig();
 
                $opts->add( 'days', $user->getIntOption( 'rcdays' ) );
                $opts->add( 'limit', $user->getIntOption( 'rclimit' ) );
@@ -84,6 +85,10 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                $opts->add( 'hidepatrolled', $user->getBoolOption( 'hidepatrolled' ) );
                $opts->add( 'hidemyself', false );
 
+               if ( $config->get( 'RCWatchCategoryMembership' ) ) {
+                       $opts->add( 'hidecategorization', $user->getBoolOption( 'hidecategorization' ) );
+               }
+
                $opts->add( 'categories', '' );
                $opts->add( 'categories_any', false );
                $opts->add( 'tagfilter', '' );
@@ -138,6 +143,9 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                        if ( 'hidemyself' === $bit ) {
                                $opts['hidemyself'] = true;
                        }
+                       if ( 'hidecategorization' === $bit ) {
+                               $opts['hidecategorization'] = true;
+                       }
 
                        if ( is_numeric( $bit ) ) {
                                $opts['limit'] = $bit;
@@ -677,6 +685,7 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
 
                $lang = $this->getLanguage();
                $user = $this->getUser();
+               $config = $this->getConfig();
                if ( $options['from'] ) {
                        $note .= $this->msg( 'rcnotefrom' )
                                ->numParams( $options['limit'] )
@@ -690,12 +699,12 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                }
 
                # Sort data for display and make sure it's unique after we've added user data.
-               $linkLimits = $this->getConfig()->get( 'RCLinkLimits' );
+               $linkLimits = $config->get( 'RCLinkLimits' );
                $linkLimits[] = $options['limit'];
                sort( $linkLimits );
                $linkLimits = array_unique( $linkLimits );
 
-               $linkDays = $this->getConfig()->get( 'RCLinkDays' );
+               $linkDays = $config->get( 'RCLinkDays' );
                $linkDays[] = $options['days'];
                sort( $linkDays );
                $linkDays = array_unique( $linkDays );
@@ -726,6 +735,10 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                        'hidemyself' => 'rcshowhidemine'
                );
 
+               if ( $config->get( 'RCWatchCategoryMembership' ) ) {
+                       $filters['hidecategorization'] = 'rcshowhidecategorization';
+               }
+
                $showhide = array( 'show', 'hide' );
 
                foreach ( $this->getCustomFilters() as $key => $params ) {
@@ -741,7 +754,8 @@ class SpecialRecentChanges extends ChangesListSpecialPage {
                        // The following messages are used here:
                        // rcshowhideminor-show, rcshowhideminor-hide, rcshowhidebots-show, rcshowhidebots-hide,
                        // rcshowhideanons-show, rcshowhideanons-hide, rcshowhideliu-show, rcshowhideliu-hide,
-                       // rcshowhidepatr-show, rcshowhidepatr-hide, rcshowhidemine-show, rcshowhidemine-hide.
+                       // rcshowhidepatr-show, rcshowhidepatr-hide, rcshowhidemine-show, rcshowhidemine-hide,
+                       // rcshowhidecategorization-show, rcshowhidecategorization-hide.
                        $linkMessage = $this->msg( $msg . '-' . $showhide[1 - $options[$key]] );
                        // Extensions can define additional filters, but don't need to define the corresponding
                        // messages. If they don't exist, just fall back to 'show' and 'hide'.
index 20f5776..962e0c3 100644 (file)
@@ -111,6 +111,10 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                $opts->add( 'hidepatrolled', $user->getBoolOption( 'watchlisthidepatrolled' ) );
                $opts->add( 'hidemyself', $user->getBoolOption( 'watchlisthideown' ) );
 
+               if ( $this->getConfig()->get( 'RCWatchCategoryMembership' ) ) {
+                       $opts->add( 'hidecategorization', $user->getBoolOption( 'watchlisthidecategorization' ) );
+               }
+
                $opts->add( 'extended', $user->getBoolOption( 'extendwatchlist' ) );
 
                return $opts;
@@ -425,6 +429,11 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                        'hidemyself' => 'rcshowhidemine',
                        'hidepatrolled' => 'rcshowhidepatr'
                );
+
+               if ( $this->getConfig()->get( 'RCWatchCategoryMembership' ) ) {
+                       $filters['hidecategorization'] = 'rcshowhidecategorization';
+               }
+
                foreach ( $this->getCustomFilters() as $key => $params ) {
                        $filters[$key] = $params['msg'];
                }
index b7fb78e..0cbe8b8 100644 (file)
        "foreign-structured-upload-form-label-infoform-categories": "Катэгорыі",
        "foreign-structured-upload-form-label-infoform-date": "Дата",
        "foreign-structured-upload-form-label-own-work-message-local": "Я пацьвярджаю, што загружаю гэты файл згодна з правіламі і ліцэнзійнай палітыкай {{GRAMMAR:родны|{{SITENAME}}}}.",
+       "foreign-structured-upload-form-label-not-own-work-message-local": "Калі вы ня можаце загрузіць файл у адпаведнасьці з правіламі {{GRAMMAR:родны|{{SITENAME}}}}, калі ласка, закрыйце гэтае акно і паспрабуйце іншы мэтад.",
        "backend-fail-stream": "Немагчыма накіраваць файл $1.",
        "backend-fail-backup": "Немагчыма зрабіць рэзэрвовую копію файла $1.",
        "backend-fail-notexists": "Файл $1 не існуе.",
index 8f4ef96..3e545d2 100644 (file)
@@ -7,6 +7,7 @@
        "tog-hideminor": "Hide minor edits from recent changes",
        "tog-hidepatrolled": "Hide patrolled edits from recent changes",
        "tog-newpageshidepatrolled": "Hide patrolled pages from new page list",
+       "tog-hidecategorization": "Hide categorization of pages",
        "tog-extendwatchlist": "Expand watchlist to show all changes, not just the most recent",
        "tog-usenewrc": "Group changes by page in recent changes and watchlist",
        "tog-numberheadings": "Auto-number headings",
@@ -36,6 +37,7 @@
        "tog-watchlisthideliu": "Hide edits by logged in users from the watchlist",
        "tog-watchlisthideanons": "Hide edits by anonymous users from the watchlist",
        "tog-watchlisthidepatrolled": "Hide patrolled edits from the watchlist",
+       "tog-watchlisthidecategorization": "Hide categorization of pages",
        "tog-ccmeonemails": "Send me copies of emails I send to other users",
        "tog-diffonly": "Do not show page content below diffs",
        "tog-showhiddencats": "Show hidden categories",
        "permissionserrors": "Permission error",
        "permissionserrorstext": "You do not have permission to do that, for the following {{PLURAL:$1|reason|reasons}}:",
        "permissionserrorstext-withaction": "You do not have permission to $2, for the following {{PLURAL:$1|reason|reasons}}:",
+       "contentmodelediterror": "You cannot edit this revision because its content model is <code>$1</code>, and the current content model of the page is <code>$2</code>.",
        "recreate-moveddeleted-warn": "<strong>Warning: You are recreating a page that was previously deleted.</strong>\n\nYou should consider whether it is appropriate to continue editing this page.\nThe deletion and move log for this page are provided here for convenience:",
        "moveddeleted-notice": "This page has been deleted.\nThe deletion and move log for the page are provided below for reference.",
        "moveddeleted-notice-recent": "Sorry, this page was recently deleted (within the last 24 hours).\nThe deletion and move log for the page are provided below for reference.",
        "rcshowhidemine": "$1 my edits",
        "rcshowhidemine-show": "Show",
        "rcshowhidemine-hide": "Hide",
+       "rcshowhidecategorization": "$1 page categorization",
+       "rcshowhidecategorization-show": "Show",
+       "rcshowhidecategorization-hide": "Hide",
        "rclinks": "Show last $1 changes in last $2 days<br />$3",
        "diff": "diff",
        "hist": "hist",
        "spam_blanking": "All revisions contained links to $1, blanking",
        "spam_deleting": "All revisions contained links to $1, deleting",
        "simpleantispam-label": "Anti-spam check.\nDo <strong>not</strong> fill this in!",
+       "autochange-username": "MediaWiki automatic change",
        "pageinfo-header": "-",
        "pageinfo-title": "Information for \"$1\"",
        "pageinfo-not-current": "Sorry, it's impossible to provide this information for old revisions.",
index 541534a..38e47ed 100644 (file)
        "changecontentmodel-success-text": "Il tipo di contenuto di [[:$1]] è stato modificato.",
        "changecontentmodel-cannot-convert": "Il contenuto di [[:$1]] non può essere convertito in tipo $2.",
        "changecontentmodel-nodirectediting": "Il modello di contenuto $1 non supporta la modifica diretta",
-       "log-name-contentmodel": "Registro delle modifiche del modello contenuti",
+       "log-name-contentmodel": "Modifiche del modello contenuti",
        "log-description-contentmodel": "Eventi relativi al modello di contenuto di una pagina",
        "logentry-contentmodel-change": "$1 {{GENDER:$2|ha modificato}} il modello di contenuto della pagina $3 da \"$4\" a \"$5\"",
        "logentry-contentmodel-change-revertlink": "ripristina",
        "logentry-upload-upload": "$1 {{GENDER:$2|ha caricato}} $3",
        "logentry-upload-overwrite": "$1 {{GENDER:$2|ha caricato}} una nuova versione di $3.",
        "logentry-upload-revert": "$1 {{GENDER:$2|ha caricato}} $3",
-       "log-name-managetags": "Registro gestione tag",
+       "log-name-managetags": "Gestione etichette",
        "log-description-managetags": "Questa pagina elenca le azioni di gestione relative alle [[Special:Tags|etichette]]. Il registro contiene solo le azioni effettuate manualmente da un amministratore; le etichette potrebbero essere create o cancellate dal programma wiki senza che ciò venga registrato qui.",
        "logentry-managetags-create": "$1 {{GENERE:$2|ha creato}} il tag \"$4\"",
        "logentry-managetags-delete": "$1 {{GENDER:$2|ha rimosso}} l'etichetta \"$4\" (da $5 {{PLURAL:$5|versione o voce di registro|versioni o voci di registro}})",
index 60bb039..a043097 100644 (file)
        "right-autopatrol": "자신의 편집을 자동으로 점검된 판으로 표시",
        "right-patrolmarks": "최근 바뀜에서 점검 표시를 보기",
        "right-unwatchedpages": "주시되지 않은 문서 목록 보기",
-       "right-mergehistory": "문ì\84\9cì\9d\98 ì\97­ì\82¬ë¥¼ í\95©ì¹¨",
+       "right-mergehistory": "문ì\84\9cì\9d\98 ì\97­ì\82¬ë¥¼ í\95©ì¹\98기",
        "right-userrights": "사용자의 모든 권한 조정",
        "right-userrights-interwiki": "다른 위키의 사용자 권한을 조정",
        "right-siteadmin": "데이터베이스를 잠그거나 잠금 해제",
index 2ae00bc..1d50603 100644 (file)
        "badsig": "D'Syntax vun Ärer Ënnerschrëft ass net korrekt; iwwerpréift w.e.g. den HTML Code.",
        "badsiglength": "Är Ënnerschrëft ass ze laang.\nSi muss manner wéi $1 {{PLURAL:$1|Zeechen|Zeechen}} hunn.",
        "yourgender": "Wéi wëllt Dir beschriwwe ginn?",
-       "gender-unknown": "Wann Dir ernimmt gëtt da benotzt D'Software do wou et méiglech ass geschlechtsneutral Wierder",
+       "gender-unknown": "Wann Dir ernimmt gëtt da benotzt d'Software do wou et méiglech ass geschlechtsneutral Wierder",
        "gender-male": "Hien ännert Wikisäiten",
        "gender-female": "Si ännert Wikisäiten",
        "prefs-help-gender": "Fakultativ:\nD'Software benotzt seng Wäerter fir Iech unzeschwätzen a fir vun Iech vis-a-vis vun Aneren grammatesch ''Gender-korrekt'' ze schwätzen. \n\nDës Informatioun ass ëffentlech.",
index 1db008b..271130f 100644 (file)
        "about": "Ītechcopa",
        "article": "Tlâkuilòpilli",
        "newwindow": "(Motlapoāz cē yancuīc tlanexillōtl)",
-       "cancel": "Ticcāhuaz",
+       "cancel": "Xiccāhua",
        "moredotdotdot": "Huehca ōmpa...",
        "mypage": "Noāmauh",
        "mytalk": "Nozānīl",
        "anontalk": "Inīn IP ītēixnāmiquiliz",
        "navigation": "Nēnemōhualiztli",
        "and": "&#32;īhuān",
-       "qbfind": "Ticahciz",
-       "qbbrowse": "Titlatepotztocaz",
-       "qbedit": "Ticpatlaz",
+       "qbfind": "Xicahci",
+       "qbbrowse": "Xitlatepotztoca",
+       "qbedit": "Xicpatla",
        "qbpageoptions": "Inīn tlaīxtli",
        "qbmyoptions": "Notlaīx",
        "faq": "Zan īc tētlatlanīliztli",
        "variants": "Nepāpan",
        "navigation-heading": "Nemiliztlahtōlpōhualāmatl",
        "errorpagetitle": "Aiuhcāyōtl",
-       "returnto": "Timocuepaz īhuīc $1.",
+       "returnto": "Ximocuepa īhuīc $1.",
        "tagline": "Īhuīcpa {{SITENAME}}",
        "help": "Tēpalēhuiliztli",
-       "search": "Titlatēmōz",
-       "searchbutton": "Tictēmōz",
-       "go": "Tiyāz",
-       "searcharticle": "Tiyāz",
+       "search": "Xitlatēmo",
+       "searchbutton": "Xictēmo",
+       "go": "Xiyauh",
+       "searcharticle": "Xiyauh",
        "history": "Tlaīxtli ītlahtōllo",
        "history_short": "Tlahtōllōtl",
        "updatedmarker": "ōmoyancuīx īhuīcpa xōcoyōc notlahpololiz",
        "printableversion": "Tepoztlahcuilōlli",
        "permalink": "Mochipa tzonhuiliztli",
-       "print": "Tictepoztlahcuilōz",
-       "view": "Tiquittaz",
-       "view-foreign": "Īpan tiquittaz in $1",
-       "edit": "Ticpatlaz",
-       "edit-local": "Ticpatlaz nicān tlahtōlli",
-       "create": "Ticchīhuaz",
-       "create-local": "Ticahxiltīz nicān tlahtōlli",
-       "editthispage": "Ticpatlaz inīn tlaīxtli",
-       "create-this-page": "Ticchīhuaz inīn tlaīxtli",
-       "delete": "Ticpolōz",
-       "deletethispage": "Ticpolōz inīn tlaīxtli",
-       "undeletethispage": "Ticmāquīxtīz inīn tlaīxtli",
+       "print": "Xictepoztlahcuilo",
+       "view": "Xiquitta",
+       "view-foreign": "Īpan xiquitta in $1",
+       "edit": "Xicpatla",
+       "edit-local": "Xicpatla nicān tlahtōlli",
+       "create": "Xicchīhua",
+       "create-local": "Xicahxilti nicān tlahtōlli",
+       "editthispage": "Xicpatla inīn tlaīxtli",
+       "create-this-page": "Xicchīhua inīn tlaīxtli",
+       "delete": "Xicpolo",
+       "deletethispage": "Xicpolo inīn tlaīxtli",
+       "undeletethispage": "Xicmāquīxti inīn tlaīxtli",
        "undelete_short": "Ahticpolōz {{PLURAL:$1|cē tlapatlaliztli|$1 tlapatlaliztli}}",
        "viewdeleted_short": "Mà mỏta {{PLURAL:$1|se tlatlaìxpôpolòlli tlayèktlàlilistli|$1 tlatlaìxpôpolòltin tlayèktlàlilistin}}",
-       "protect": "Ticpiyaz",
-       "protect_change": "ticpatlaz",
-       "protectthispage": "Ticpiyaz inīn tlaīxtli",
-       "unprotect": "Ticpatlaz in tlapiyaliztli",
-       "unprotectthispage": "Ticpatlaz inīn tlaīxtli ītlapiyaliz",
+       "protect": "Xicpiya",
+       "protect_change": "xicpatla",
+       "protectthispage": "Xicpiya inīn tlaīxtli",
+       "unprotect": "Xicpatla in tlapiyaliztli",
+       "unprotectthispage": "Xicpatla inīn tlaīxtli ītlapiyaliz",
        "newpage": "Yancuic tlaīxtli",
-       "talkpage": "Tictlahtōz inīn zāzaniltechcopa",
+       "talkpage": "Xictlahto inīn tlaīxtli ītechcopa",
        "talkpagelinktext": "Zānīlli",
        "specialpage": "Nònkuâkìskàtlaìxtlapalli",
        "personaltools": "In tlein nitēquitiltilia",
-       "articlepage": "Tiquittaz in tlahcuilōlli",
+       "articlepage": "Xiquitta in tlamantlaīxtli",
        "talk": "Zānīlli",
        "views": "Tlachiyaliztli",
        "toolbox": "Tequitīhuani",
        "userpage": "Xiquitta tlatequitiltilīlli zāzanilli",
        "projectpage": "Xiquitta tlachīhualiztli zāzanilli",
-       "imagepage": "Tiquittaz in zāzanilli īāma",
+       "imagepage": "Xiquitta in zāzanilli īāma",
        "mediawikipage": "Xiquitta tlahcuilōltzin zāzanilli",
-       "templatepage": "Tiquittāz nemachiyōtīlli zāzanilli",
+       "templatepage": "Xiquitta neīxcuītīllaīxtli",
        "viewhelppage": "Xiquitta tēpalēhuiliztli zāzanilli",
        "categorypage": "Mà mỏta in tlaìxmatkàtlàlilòtlaìxtlapalli",
        "viewtalkpage": "Xiquitta tēixnāmiquiliztli zāzanilli",
        "retrievedfrom": "Ōquīzqui ītech  \"$1\"",
        "youhavenewmessages": "Tiquimpiya $1 ($2).",
        "youhavenewmessagesmulti": "Tiquimpiya yancuīc tlahcuilōlli īpan $1",
-       "editsection": "ticpatlaz",
-       "editold": "ticpatlaz",
-       "viewsourceold": "tiquittaz mēyalli",
-       "editlink": "ticpatlaz",
-       "viewsourcelink": "tiquittaz mēyalli",
-       "editsectionhint": "Ticpatlaz in: $1",
+       "editsection": "xicpatla",
+       "editold": "xicpatla",
+       "viewsourceold": "xiquitta mēyalli",
+       "editlink": "xicpatla",
+       "viewsourcelink": "xiquitta mēyalli",
+       "editsectionhint": "Xicpatla in: $1",
        "toc": "Inīn tlahcuilōlco",
-       "showtoc": "ticnēxtīz",
-       "hidetoc": "tictlātīz",
+       "showtoc": "xicnēxti",
+       "hidetoc": "xictlāti",
        "collapsible-collapse": "Motlàtìs",
        "collapsible-expand": "Monèxtìs",
        "confirmable-yes": "Quēmah",
        "cannotdelete": "Ahmō ōhuelītic mopoloa in zāzanilli \"$1\".\nHueli tlein āquin ōquipolo achtopa.",
        "badtitle": "Ahcualli tōcāitl",
        "badtitletext": "Zāzanilli ticnequi in ītōca cah ahcualli, ahtlein quipiya nozo ahcualtzonhuiliztli interwiki tōcāhuicpa.\nHueliz quimpiya tlahtōl tlein ahmo mohuelītih motequitiltia tōcāpan.",
-       "viewsource": "Tiquittaz mēyalli",
-       "viewsource-title": "Tiquittaz $1 īmēyal",
+       "viewsource": "Xiquitta mēyalli",
+       "viewsource-title": "Xiquitta in $1 īmēyal",
        "actionthrottled": "Tlachīhualiztli ōmotzacuili",
        "viewsourcetext": "Tihuelīti tiquitta auh ticcopīna inīn zāzanilli ītlahtōlcaquiliztilōni:",
        "namespaceprotected": "Ahmo tiquihuelīti tiquimpatla zāzaniltin īpan '''$1'''.",
        "titleprotected": "Inīn zāzanilli ōmoquīxti ic tlachīhualiztli ic [[User:$1|$1]].\nŌquihto: ''$2''",
        "exception-nologin": "Ahmō timocalac",
        "virus-unknownscanner": "ahmatic antivirus:",
-       "welcomeuser": "Ximopanōltih, $1!",
-       "yourname": "Motlatequitiltilīltōca:",
+       "welcomeuser": "Ximopanōlti, $1!",
+       "yourname": "Tequihuihcātōcāitl:",
        "userlogin-yourname": "Tequihuihcātōcāitl",
        "yourpassword": "Motlahtōlichtacāyo",
        "yourpasswordagain": "Motlahtōlichtacāyo occeppa",
        "nav-login-createaccount": "Ximocalaqui / ximomachiyōmaca",
        "userlogin": "Ximomachiyōmaca/Ximocalaqui",
        "userloginnocreate": "Ximocalaqui",
-       "logout": "Tiquīzaz",
-       "userlogout": "Tiquīzaz",
+       "logout": "Xiquīza",
+       "userlogout": "Xiquīza",
        "notloggedin": "Ahmō ōtimocalac",
        "userlogin-noaccount": "Cuix ahmō titlapōhualeh?",
        "nologin": "Cuix ahmō titlapōhualeh? $1.",
-       "nologinlink": "Ticchīhuaz cē cuentah",
-       "createaccount": "Ticchīhuaz cuentah",
+       "nologinlink": "Xicchīhua cē tlapōhualli",
+       "createaccount": "Xicchīhua tlapōhualli",
        "gotaccount": "¿Ye ticpiya cē tlapōhualli? '''$1'''.",
        "gotaccountlink": "Ximocalaqui",
        "createaccountmail": "Ticnemītīz ahmo cemihcac zāzoichtacātlahtōlli nō in tiquēhualtīz in maltzinteyōtl monetitlanizyeyān",
        "createaccountreason": "Tleīpampa:",
        "createacct-reason": "Tleīpampa",
-       "createacct-submit": "Ticchīhuaz in motlapōhual",
+       "createacct-submit": "Xicchīhua in motlapōhual",
        "badretype": "Ahneneuhqui motlahtōlichtacāyo.",
        "userexists": "In tlatequitiltilīltōcāitl in ōquipehpen ye ia.\nTimitztlātlauhtiah xicpehpena occē.",
        "loginerror": "Ahcuallōtl tlacalaquiliztechcopa",
        "accountcreated": "Tlapōhualli ōmochīuh",
        "accountcreatedtext": "In ītlatequitiltilīllapōhual in [[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|talk]]) ōquiyōcoyalo.",
        "createaccount-title": "Cuentah ītlachīhualiz ic {{SITENAME}}",
-       "loginlanguagelabel": "Tlâtòlli: $1",
+       "loginlanguagelabel": "Tlahtōlli: $1",
        "pt-login": "Xicalaqui",
        "pt-createaccount": "Xicchīhua motlapōhual",
-       "changepassword": "Ticpatlāz motlahtōlichtacāyo",
-       "resetpass_header": "Xicpatlāz motlahtōlichtacāyo",
+       "changepassword": "Xicpatla motlahtōlichtacāyo",
+       "resetpass_header": "Xicpatla motlahtōlichtacāyo",
        "oldpassword": "Huēhueh motlahtōlichtacayo:",
        "newpassword": "Yancuīc motlahtōlichtacayo:",
        "retypenew": "Occeppa xiquihcuiloa yancuīc motlahtōlichtacayo:",
        "changepassword-success": "Mochtacātlahtōl cualli ōtlapatlalo.",
        "resetpass_forbidden": "Tlahtōlichtacayōtl ahmo mohuelītih mopatlah",
        "resetpass-submit-loggedin": "Ticpatlāz motlahtōlichtacāyo",
-       "resetpass-submit-cancel": "Ticcuepāz",
-       "passwordreset-username": "Tlatequitiltilīltōcāitl:",
+       "resetpass-submit-cancel": "Xiccāhua",
+       "passwordreset-username": "Tequihuihcātōcāitl:",
        "bold_sample": "Tlīltic tlahcuilōlli",
        "bold_tip": "Tlīltic tlahcuilōlli",
        "italic_sample": "Cōliuhqui tlahcuilōliztli",
        "summary": "Mopatlaliz:",
        "subject": "Tōcāitl/Āmoxmachiyōtl:",
        "minoredit": "Inīn tlapatlaliztli tepitōn",
-       "watchthis": "Tictlachiyāz inīn zāzanilli",
-       "savearticle": "Ticpiyaz tlaīxtli",
+       "watchthis": "Xicpiya inīn tlaīxtli",
+       "savearticle": "Xicpiya tlaīxtli",
        "preview": "Xiquitta achtochīhualiztli",
        "showpreview": "Xiquitta achtochīhualiztli",
-       "showdiff": "Tiquinttāz tlapatlaliztli",
+       "showdiff": "Xicnēxti tlapatlaliztli",
        "missingcommenttext": "Timitztlātlauhtiah xitlanitlahcuiloa.",
        "summary-preview": "Tlahcuilōltōn achtochīhualiztli:",
        "blockedtitle": "Ōmotzacuili tlatequitiltilīlli",
        "loginreqpagetext": "Tihuīquilia $1 ic tiquintta occequīntīn zāzaniltin.",
        "accmailtitle": "Tlahtōlichtacāyōtl ōmoihuah.",
        "accmailtext": "Ōquiyōcox zāzochtacātlahtōlli in [[User talk:$1|$1]] auh ōmoquitītlan īhuīc $2. Tihueliti ticpatlaz īpan ''[[Special:ChangePassword|Ticpatlaz in ]]'' in ōticalaco achtopa.",
-       "newarticle": "(Yancuīc)",
+       "newarticle": "(Yancuic)",
        "newarticletext": "Ōtictocac cētiliztli cē zāzanilhuīc oc ahmo ia. Intlā quiēlēhuia quichīhua, xitlahcuiloa niman (nō xiquitta [$1 tēpalēhuiliztli zāzanilli] huehca ōmpa tlapatlaliztli). Intlā ahmo, yāuh achtopa zāzanilli.",
        "noarticletext": "In āxcān, ahmō onca tlahcuilōlli inīn zāzanilpan.\nTihuelīti [[Special:Search/{{PAGENAME}}|tictēmoa inīn zāzaniltōcācopa]] occequīntīn zāzanilpan,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} machiyōmacalpan], ahnozo [{{fullurl:{{FULLPAGENAME}}|action=edit}} ticpatla inīn zāzanilli]</span>.",
        "userpage-userdoesnotexist": "Ahmo ia cuentah \"<nowiki>$1</nowiki>\" ītōca. Timitztlātlauhtiah xitēchquinōtza intlā ticchīhuāz intlā nozo ticpatlāz inīn zāzanilli.",
        "updated": "(Ōmoyancuīli)",
        "note": "'''Tlahtōlcaquiliztilōni:'''",
        "previewnote": "'''Xiquilnamiqui tein inīn zan tlaachtopaittaliztli.'''\n¡Motlapatlaliz ayamo ōquinpix!",
-       "editing": "Ticpatlacah $1",
+       "editing": "Ticpatla $1",
        "creating": "Ticchīhua $1",
        "editingsection": "Ticpatlacah $1 (tlahtōltzintli)",
        "editingcomment": "Ticpatlacah $1 (tlahtōltzintli)",
        "last": "xōcoyōc",
        "page_first": "achto",
        "page_last": "xōcoyōc",
-       "history-fieldset-title": "Tictēmōz īpan tlahcuilōlloh",
+       "history-fieldset-title": "Xitlatēmo īpan tlahtōllōtl",
        "history-show-deleted": "Zan tlapolōlli",
        "histfirst": "in achto",
        "histlast": "in tlatzaucticah",
        "history-feed-item-nocomment": "$1 īpan $2",
        "history-feed-empty": "In zāzanilli tiquiēlēhuia ahmo ia.\nHueliz ōmopolo huiqui nozo ōmozacac.\n[[Special:Search|Xitēmoa huiquipan]] yancuīc huēyi zāzaniltin.",
        "rev-delundel": "tiquittāz/tictlātīz",
-       "rev-showdeleted": "ticnēxtīz",
+       "rev-showdeleted": "xicnēxti",
        "revisiondelete": "Tiquimpolōz/ahtiquimpolōz tlachiyaliztli",
        "revdelete-show-file-submit": "Quēmah",
        "revdelete-hide-text": "In tlahtlachiyaliztli ītlahcuilōl",
        "revdelete-radio-unset": "Ittalōni",
        "revdelete-log": "Tleīpampa:",
        "revdel-restore": "Ticpatlāz tlattaliztli",
-       "pagehist": "Zāzanilli tlahcuilōlloh",
+       "pagehist": "Tlaīxtli ītlahtōllo",
        "deletedhist": "Ōtlapolo tlahcuilōlloh",
        "revdelete-edit-reasonlist": "Tiquimpatlāz īxtlamatiliztli tlapoloaliztechcopa",
-       "mergehistory-from": "Zāzanilhuīcpa:",
+       "mergehistory-from": "Mēyallaīxtli:",
        "mergehistory-into": "Zāzanilhuīc:",
        "mergehistory-no-source": "Zāzanilhuīcpa $1 ahmo ia.",
        "mergehistory-no-destination": "Zāzanilhuīc $1 ahmo ia.",
        "skin-preview": "Xiquitta quemeh yez",
        "datedefault": "Ayāc tlanequiliztli",
        "prefs-labs": "Ìntlâtlamảtilis in tlayêyẻkòyàntìn",
-       "prefs-personal": "Motlācatlanōnōtzaliz",
-       "prefs-rc": "Yancuīc tlapatlaliztli",
+       "prefs-personal": "Tequihuihcātlapōhualli",
+       "prefs-rc": "Yancuic tlapatlaliztli",
        "prefs-watchlist": "Tlachiyaliztli",
        "prefs-watchlist-days": "Tōnaltin tiquinttāz tlachiyalizpan:",
        "prefs-watchlist-edits": "Tlapatlaliztli tiquintta tlachiyalizpan:",
        "prefs-misc": "Zāzo",
        "prefs-resetpass": "Ticpatlāz motlahtōlichtacāyo",
-       "saveprefs": "Ticpiyāz",
+       "saveprefs": "Xicpiya",
        "prefs-editing": "Tlapatlaliztli",
        "rows": "Pāntli:",
        "searchresultshead": "Tlatēmoliztli",
        "timezoneregion-indian": "Índico Ilhuicaātl",
        "timezoneregion-pacific": "Pacífico Ilhuicaātl",
        "prefs-searchoptions": "Titlatēmōz",
-       "prefs-namespaces": "Tōcātzin",
+       "prefs-namespaces": "Tōcātlacāuhtli",
        "default": "ic default",
-       "prefs-files": "Tlahcuilōlli",
+       "prefs-files": "Ihcuilōlli",
        "youremail": "Maltzinteyōtl netitlanizyeyāntli:",
-       "username": "{{GENDER:$1|Tlatequitiltilīltōcāitl}}:",
+       "username": "{{GENDER:$1|Tequihuihcātōcāitl}}:",
        "prefs-memberingroups": "{{GENDER:$2|Tlacotōncayōtl}} in {{PLURAL:$1|tēolōlolli|tēolōloltin}}",
        "yourrealname": "Melāhuac motōcā:",
-       "yourlanguage": "Tlâtòlli:",
+       "yourlanguage": "Tlahtōlli:",
        "yournick": "Motōcātlaliz:",
        "badsiglength": "Motōcātlaliz cah ocachi huēyac.\nAhmo quihuīquilia quimpiya achi $1 {{PLURAL:$1|machiyōtlahtōliztli}}.",
        "gender-male": "Oquichtli",
        "userrights-reason": "Īxtlamatiliztli:",
        "userrights-no-interwiki": "Ahmo tihuelīti ticpatla tlatequitiltilīlli huelītiliztli occequīntīn huiquipan.",
        "group": "Olōlli:",
-       "group-user": "Tlatequitiltilīlli",
+       "group-user": "Tequihuihqueh",
        "group-bot": "Tepoztlācah",
        "group-sysop": "Tlahcuilōlpixqueh",
        "group-all": "(mochīntīn)",
        "right-suppressredirect": "Ahmo ticchīhuāz tlacuepaliztli huēhueh tōcāhuīc ihcuāc ticzacāz cē zāzanilli",
        "right-upload": "Tiquinquetzāz tlahcuilōlli",
        "right-upload_by_url": "Ticquetzāz cē tlahcuilōlli īhuīcpa URL",
-       "right-delete": "Tiquimpolōz zāzaniltin",
+       "right-delete": "Xicpolo tlaīxtli",
        "right-bigdelete": "Tiquimpolōz zāzaniltin īca huēiyac tlahcuilōlloh",
        "right-browsearchive": "Tlatēmōz zāzaniltin ōmopoloh",
        "right-undelete": "Ahticpolōz cē zāzanilli",
        "right-block": "Tiquintzacuilīz occequīntīn tlatequitiltilīlli",
        "right-blockemail": "Titēquīxtīz tlatequitiltilīlli ic tēch-e-mailīz",
        "right-hideuser": "Ticquīxtīz cē tlatequitiltilīltōcāitl, āuh ichtac",
-       "right-editmyoptions": "Ticpatlaz mopanitlatlālīl",
+       "right-editmyoptions": "Xicpatla in mopanitlatlālīl",
        "right-import": "Ticcōhuāz zāzaniltin occequīntīn huiquihuīcpa",
        "right-importupload": "Tiquincōhuāz zāzaniltin tlahcuilōlquetzalizhuīcpa",
        "right-patrolmarks": "Tiquinttāz tlapiyalizmachiyōtl īpan yancuīc tlapatlaliztli",
        "right-userrights-interwiki": "Tiquimpatlāz tlatequitiltilīlli huelītiliztli occequīntīn huiquipan",
        "newuserlogpage": "Tequihuihcāchīhualiztlapōhualāmatl",
        "rightslog": "Tlatequitiltilīlli huelītiliztli tlahcuilōlloh",
-       "action-read": "ticpōhuāz inīn zāzanilli",
-       "action-edit": "ticpatlāz inīn zāzanilli",
-       "action-createpage": "tiquinchīhuāz zāzaniltin",
+       "action-read": "xāmapōhua inīn tlaīxtli",
+       "action-edit": "xicpatla inīn tlaīxtli",
+       "action-createpage": "xicchīhua tlaīxtli",
        "action-createtalk": "tiquinchīhuāz tēixnāmiquiliztli zāzaniltin",
        "action-createaccount": "ticchīhuaz inīn tlatequitiltilīlli īcuentah",
        "action-move": "ticpatlāz inīn zāzanilli",
        "rcshowhidebots-hide": "Tiquihyānaz",
        "rcshowhideliu": "$1 tēmachiyōmacalli tlatequitiltilīltin",
        "rcshowhideanons": "$1 ahtōcā tlatequitiltilīlli",
-       "rcshowhideanons-show": "Ticnēxtīz",
+       "rcshowhideanons-show": "Xicnēxti",
        "rcshowhidepatr": "$1 tlapatlaliztli mochiyahua",
        "rcshowhidemine": "$1 notlahcuilōl",
-       "rcshowhidemine-show": "Ticnēxtīz",
+       "rcshowhidemine-show": "Xicnēxti",
        "rclinks": "Xiquintta xōcoyōc $1 tlapatlaliztli xōcoyōc $2 tōnalpan.<br />$3",
        "diff": "ahneneuh",
        "hist": "tlahtōl",
        "hide": "Tiquintlātīz",
-       "show": "Tiquinttāz",
+       "show": "Xicnēxti",
        "minoreditletter": "p",
        "newpageletter": "Y",
        "boteditletter": "T",
        "filedesc": "Tlahcuilōltōn",
        "fileuploadsummary": "Tlahcuilōltōn:",
        "filestatus": "Copyright:",
-       "filesource": "Īhuīcpa:",
+       "filesource": "Mēyalli:",
        "minlength1": "Tlahcuilōltōcāitl quihuīlquilia huehca ōmpa cē tlahtōl.",
        "badfilename": "Īxiptli ītōcā ōmopatlac īhuīc \"$1\".",
        "filetype-unwanted-type": "'''\".$1\"''' ahmo moēlēhuia quemeh tlahcuilōlli iuhcāyōtl.\nTlahcuilōlli iuhcāyōtl {{PLURAL:$3|moēlēhuia cah|moēlēhuiah cateh}} $2.",
        "uploaddisabled": "Ahmo mohuelīti tlahcuilōlquetzā",
        "uploaddisabledtext": "Ahmo huelīti moquetzazqueh tlahcuilōlli.",
        "upload-source": "Mēyalihcuilōlli",
-       "sourcefilename": "Tōcāhuīcpa:",
+       "sourcefilename": "Mēyalihcuilōltōcāitl:",
        "sourceurl": "Mēyal-URL:",
        "destfilename": "Tōcāhuīc:",
        "watchthisupload": "Tictlachiyāz inīn zāzanilli",
        "upload-form-label-usage-filename": "Ihcuilōlli ītōcā",
        "upload_source_file": " (cē tlahcuilōlli mochīuhpōhualhuazco)",
        "listfiles_search_for": "Tlatēmōz mēdiatl tōcācopa:",
-       "imgfile": "īxiptli",
+       "imgfile": "ihcuilōlli",
        "listfiles": "Mochīntīn īxiptli",
        "listfiles_name": "Tōcāitl",
-       "listfiles_user": "Tlatequitiltilīlli",
+       "listfiles_user": "Tequihuihqui",
        "listfiles_size": "Octacayōtl (bytes)",
        "listfiles_count": "Cuepaliztli",
        "listfiles-latestversion-yes": "Quēmah",
        "file-anchor-link": "Ihcuilōlli",
        "filehist": "Ihcuilōlli ītlahtōllo",
        "filehist-deleteall": "tiquimpolōz mochīntīn",
-       "filehist-deleteone": "ticpolōz",
+       "filehist-deleteone": "xicpolo",
        "filehist-revert": "tlacuepāz",
        "filehist-current": "āxcān",
        "filehist-datetime": "Tlapōhualpan/Cāhuitl",
        "filehist-thumb": "Īxiptlahtōn",
-       "filehist-user": "Tlatequitiltilīlli",
+       "filehist-user": "Tequihuihqui",
        "filehist-dimensions": "Octacayōtl",
        "filehist-comment": "TlahtōIcaquiliztīlōni",
        "imagelinks": "Ihcuilōlli ītequiuh",
        "filedelete": "Ticpolōz $1",
        "filedelete-legend": "Ticpolōz tlahcuilōlli",
        "filedelete-comment": "Īxtlamatiliztli:",
-       "filedelete-submit": "Ticpolōz",
+       "filedelete-submit": "Xicpolo",
        "filedelete-success": "Ōmopolo '''$1'''.",
        "filedelete-nofile": "'''$1''' ahmo ia.",
        "filedelete-otherreason": "Occē īxtlamatiliztli:",
index 550c1f0..bcf8572 100644 (file)
        "passwordreset-emailerror-capture": "En passordtilbakestillingsepost ble laget, men det lyktes ikke å sende denne til {{GENDER:$2|brukeren}}: $1",
        "changeemail": "Endre eller fjerne epostadresse",
        "changeemail-header": "Endre kontoens e-postadresse",
+       "changeemail-passwordrequired": "Du må skrive inn passordet ditt for å bekrefte denne endringen.",
        "changeemail-no-info": "Du må være innlogget for å få direkte tilgang til denne siden.",
        "changeemail-oldemail": "Nåværende e-postadresse:",
        "changeemail-newemail": "Ny e-postadresse:",
        "missingsummary": "'''Påminnelse:''' Du har ikke lagt inn en redigeringsforklaring.\nVelger du ''Lagre siden'' en gang til blir endringene lagret uten forklaring.",
        "selfredirect": "<strong>Advarsel:</strong> Du omdirigerer denne siden til seg selv. Du kan ha oppgitt feil mål for omdirigeringen, eller kanskje du redigerer feil side. Om du klikker «{{int:savearticle}}» igjen vil omdirigeringen bli opprettet uansett.",
        "missingcommenttext": "Vennligst legg inn en kommentar under.",
-       "missingcommentheader": "'''Påminnelse:''' Du har ikke angitt et emne/overskrift for denne kommentaren.\nOm du trykker «{{int:savearticle}}» igjen vil redigeringen din bli lagret uten forklaring.",
+       "missingcommentheader": "<strong>Påminnelse:</strong> Du har ikke angitt et emne/overskrift for denne kommentaren.\nOm du trykker «{{int:savearticle}}» igjen vil redigeringen din bli lagret uten forklaring.",
        "summary-preview": "Forhåndsvisning av redigeringsforklaring:",
        "subject-preview": "Forhåndsvisning av emne/overskrift:",
        "previewerrortext": "En feil oppsto mens dine endringer skulle forhåndsvises.",
index 3b43602..ee79c10 100644 (file)
        "passwordreset-emailsent-capture": "Foi enviado um correio eletrónico para recuperação da palavra-passe, que é mostrado abaixo.",
        "passwordreset-emailerror-capture": "Foi gerado um correio eletrónico para redefinição da palavra-passe, mostrado abaixo, mas o seu envio para {{GENDER:$2|o utilizador|a utilizadora}} falhou: $1",
        "changeemail": "Alterar ou remover o endereço de correio eletrónico",
-       "changeemail-header": "Alterar o endereço de correio eletrónico da conta",
+       "changeemail-header": "Completa este formulário para alterar o seu endereço de correio electrónico. Se quer eliminar a associação de qualquer endereço de correio electrónico com a sua conta, deixa em branco o novo endereço de correio electrónico ao enviar o formulário.",
        "changeemail-passwordrequired": "Necessita de introduzir a sua palavra-passe para confirmar esta alteração.",
        "changeemail-no-info": "Precisa de iniciar sessão para aceder diretamente a esta página.",
        "changeemail-oldemail": "Correio eletrónico atual:",
        "recentchangeslinked-summary": "Esta é uma lista de mudanças recentes a todas as páginas para as quais a página fornecida contém ligações (ou de todas as que pertencem à categoria fornecida).\nAs suas [[Special:Watchlist|páginas vigiadas]] aparecem a '''negrito'''.",
        "recentchangeslinked-page": "Nome da página:",
        "recentchangeslinked-to": "Inversamente, mostrar mudanças às páginas que contêm ligações para esta",
+       "recentchanges-page-added-to-category": "[[:$1]] foi adicionada à categoria",
+       "recentchanges-page-added-to-category-bundled": "[[:$1]] e {{PLURAL:$2|uma página|$2 páginas}} foram adicionadas à categoria",
+       "recentchanges-page-removed-from-category": "[[:$1]] foi removida da categoria",
+       "recentchanges-page-removed-from-category-bundled": "[[:$1]] e {{PLURAL:$2|uma página|$2 páginas}} foram removidas da categoria",
        "upload": "Carregar ficheiro",
        "uploadbtn": "Carregar ficheiro",
        "reuploaddesc": "Cancelar o envio e voltar ao formulário de carregamento",
        "nopagetext": "A página de destino que especificou não existe.",
        "pager-newer-n": "{{PLURAL:$1|posterior|$1 posteriores}}",
        "pager-older-n": "{{PLURAL:$1|1 anterior|$1 anteriores}}",
-       "suppress": "Supervisor",
+       "suppress": "Suprimir",
        "querypage-disabled": "Esta página especial está desativada para não prejudicar o desempenho.",
        "apihelp": "Ajuda API",
        "apihelp-no-such-module": "Módulo \"$1\" não encontrado.",
index 921f9a3..4bd1055 100644 (file)
        "tog-hideminor": "[[Special:Preferences]], tab 'Recent changes'. Offers user to hide minor edits in recent changes or not. {{Gender}}\n\n{{Related|Preferences-watchlistrc-toggle}}",
        "tog-hidepatrolled": "Option in Recent changes tab of [[Special:Preferences]] (if [[mw:Manual:$wgUseRCPatrol|$wgUseRCPatrol]] is enabled). {{Gender}}\n\n{{Related|Preferences-watchlistrc-toggle}}",
        "tog-newpageshidepatrolled": "Toggle in [[Special:Preferences]], section \"Recent changes\" (if [[mw:Manual:$wgUseRCPatrol|$wgUseRCPatrol]] is enabled). {{Gender}}",
+       "tog-hidecategorization": "Option in \"Recent changes\" tab of [[Special:Preferences]]. Offers user to hide/show categorization of pages. Appears next to messages such as {{msg-mw|tog-hideminor}}.",
        "tog-extendwatchlist": "[[Special:Preferences]], tab 'Watchlist'. Offers user to show all applicable changes in watchlist (by default only the last change to a page on the watchlist is shown). {{Gender}}",
        "tog-usenewrc": "{{Gender}}\nUsed as label for the checkbox in [[Special:Preferences]], tab \"Recent changes\".\n\nOffers user to use alternative representation of [[Special:RecentChanges]] and watchlist.",
        "tog-numberheadings": "[[Special:Preferences]], tab 'Misc'. Offers numbered headings on content pages to user. {{Gender}}",
        "tog-watchlisthideliu": "Option in tab 'Watchlist' of [[Special:Preferences]]. {{Gender}}\n\n{{Related|Preferences-watchlistrc-toggle}}",
        "tog-watchlisthideanons": "Option in tab 'Watchlist' of [[Special:Preferences]]. {{Gender}}\n\n{{Related|Preferences-watchlistrc-toggle}}",
        "tog-watchlisthidepatrolled": "Option in Watchlist tab of [[Special:Preferences]]. {{Gender}}\n\n{{Related|Preferences-watchlistrc-toggle}}",
+       "tog-watchlisthidecategorization": "Option in Watchlist tab of [[Special:Preferences]]. Offers user to hide/show categorization of pages. Appears next to checkboxes with labels such as {{msg-mw|tog-watchlisthideminor}}.",
        "tog-ccmeonemails": "Option in [[Special:Preferences]] > {{int:prefs-personal}} > {{int:email}}. {{Gender}}",
        "tog-diffonly": "Toggle option used in [[Special:Preferences]]. {{Gender}}",
        "tog-showhiddencats": "Toggle option used in [[Special:Preferences]]. {{Gender}}",
        "permissionserrors": "Used as title of error message.\n\nSee also:\n* {{msg-mw|loginreqtitle}}\n{{Identical|Permission error}}",
        "permissionserrorstext": "This message is \"without action\" version of {{msg-mw|Permissionserrorstext-withaction}}.\n\nParameters:\n* $1 - the number of reasons that were found why ''the action'' cannot be performed",
        "permissionserrorstext-withaction": "This message is \"with action\" version of {{msg-mw|Permissionserrorstext}}.\n\nParameters:\n* $1 - the number of reasons that were found why the action cannot be performed\n* $2 - one of the action-* messages (for example {{msg-mw|action-edit}}) or other such messages tagged with {{tl|doc-action}} in their documentation\n\nPlease report at [[Support]] if you are unable to properly translate this message. Also see [[phab:T16246]] (now closed) for background.",
+       "contentmodelediterror": "Error message shown when trying to edit an old revision with a content model different from that of the current revision\n* $1 - content model of the old revision\n* $2 - content model of the current revision",
        "recreate-moveddeleted-warn": "Warning shown when creating a page which has already been deleted. See for example [[Test]].",
        "moveddeleted-notice": "Shown on top of a deleted page in normal view modus ([{{canonicalurl:Test}} example]).",
        "moveddeleted-notice-recent": "Shown on top of a recently deleted page in normal view modus ([{{canonicalurl:Test}} example]).",
        "rcshowhidemine": "Option text in [[Special:RecentChanges]]. Parameters:\n* $1 - the \"show/hide\" command, with the text taken from either {{msg-mw|rcshowhidemine-show}} or {{msg-mw|rcshowhidemine-hide}}",
        "rcshowhidemine-show": "{{doc-actionlink}}\nOption text in [[Special:RecentChanges]] in conjunction with {{msg-mw|rcshowhidemine}}.\n\nSee also:\n* {{msg-mw|rcshowhidemine-hide}}\n{{Identical|show}}",
        "rcshowhidemine-hide": "{{doc-actionlink}}\nOption text in [[Special:RecentChanges]] in conjunction with {{msg-mw|rcshowhidemine}}.\n\nSee also:\n* {{msg-mw|rcshowhidemine-show}}\n{{Identical|hide}}",
+       "rcshowhidecategorization": "Option text in [[Special:RecentChanges]]. Parameters:\n* $1 - the \"show/hide\" command, with the text taken from either {{msg-mw|rcshowhidecategorization-show}} or {{msg-mw|rcshowhidecategorization-hide}}",
+       "rcshowhidecategorization-show": "{{doc-actionlink}}\nOption text in [[Special:RecentChanges]] in conjunction with {{msg-mw|rcshowhidecategorization}}.\n\nSee also:\n* {{msg-mw|rcshowhidecategorization-hide}}\n{{Identical|show}}",
+       "rcshowhidecategorization-hide": "{{doc-actionlink}}\nOption text in [[Special:RecentChanges]] in conjunction with {{msg-mw|rcshowhidecategorization}}.\n\nSee also:\n* {{msg-mw|rcshowhidecategorization-show}}\n{{Identical|hide}}",
        "rclinks": "Used on [[Special:RecentChanges]].\n* $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\".\n* $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''\".\n* $3 - a block of text that consists of other messages.<br />&nbsp;Example: \"''Hide minor edits{{int:pipe-separator}}Show bots{{int:pipe-separator}}Hide anonymous users{{int:pipe-separator}}Hide logged-in users{{int:pipe-separator}}Hide patrolled edits{{int:pipe-separator}}Hide my edits''\"\nList elements are separated by {{msg-mw|Pipe-separator}} each. Each list element is, or contains, a link.",
        "diff": "Short form of \"differences\". Used on [[Special:RecentChanges]], [[Special:Watchlist]], ...\n{{Identical|Diff}}",
        "hist": "Short form of \"history\". Used on [[Special:RecentChanges]], [[Special:Watchlist]], ...",
        "spam_blanking": "Edit summary for spam cleanup script.\n\nUsed when a page is blanked (made to have no content, but still exist) because the script could not find an appropriate revision to set the page to.\n\nParameters:\n* $1 - a spammed domain name",
        "spam_deleting": "Edit summary for spam cleanup script.\n\nUsed when a page is deleted because all revisions contained a particular link.\n\nParameters:\n* $1 - a spammed domain name",
        "simpleantispam-label": "Used as label for the input box in \"Edit\" page.\n\nThe label and the input box are always hidden.",
+       "autochange-username": "Used as bot / unknown username.",
        "pageinfo-header": "{{ignored}}Custom text for the top of the info page (action=info).",
        "pageinfo-title": "Page title for action=info. Parameters:\n* $1 is the page name",
        "pageinfo-not-current": "Error message displayed when information for an old revision is requested. Example: [{{fullurl:Project:News|oldid=4266597&action=info}}]",
index ef53640..f5c9600 100644 (file)
        "passwordreset-emailsent-capture": "'Na e-mail pe azzeramende d'a passuord ha state mannate, ca jè fatte vedè aqquà sotte.",
        "passwordreset-emailerror-capture": "'Na e-mail de azzeramende d'a passuord ha state generate, ca jè fatte vedè aqquà sotte, ma 'u 'nvie a {{GENDER:$2|l'utende}} ha fallite: $1",
        "changeemail": "Cange o live 'u 'ndirizze e-mail",
-       "changeemail-header": "Cange 'u 'ndirizze e-mail d'u cunde",
+       "changeemail-header": "Comblete stu module pe cangià 'u 'ndirizze email. Ce tu vuè ccu live l'associazione cu ogne indirizze email da 'u cunde tune, lasse 'u 'ndirizze email vacande quanne conferme 'u module.",
        "changeemail-no-info": "Tu a essere collegate pe accedere a sta pàgene direttamende.",
        "changeemail-oldemail": "Indirizze e-mail de mò:",
        "changeemail-newemail": "Indirizze e-mail nuève:",
        "logentry-newusers-create2": "'U cunde utende $3 ha state {{GENDER:$2|ccrejate}} da $1",
        "logentry-newusers-byemail": "'U cunde utende $3 ha state {{GENDER:$2|ccrejate}} da $1 e 'a passuord ha state mannate pe e-mail",
        "logentry-newusers-autocreate": "'U cunde utende $1 ha state {{GENDER:$2|ccrejate}} automaticamende",
+       "logentry-protect-move_prot": "$1 {{GENDER:$2|ave spustate}} le 'mbostaziune de protezzione da $4 a $3",
+       "logentry-protect-unprotect": "$1 {{GENDER:$2|ave luate}} 'a protezzione da $3",
+       "logentry-protect-protect": "$1 {{GENDER:$2|ptuette}} $3 $4",
+       "logentry-protect-protect-cascade": "$1 {{GENDER:$2|prutette}} $3 $4 [a cascate]",
+       "logentry-protect-modify": "$1 {{GENDER:$2|ave cangiate}} 'u levélle de protezzione pe $3 $4",
+       "logentry-protect-modify-cascade": "$1 {{GENDER:$2|ave cangiate}} 'u levélle de protezzione pe $3 $4 [a cascate]",
        "logentry-rights-rights": "$1 membre d'u gruppe {{GENDER:$2|cangiate}} pe $3 da $4 a $5",
        "logentry-rights-rights-legacy": "$1 ave {{GENDER:$2|cangiate}} 'u membre d'u gruppe pe $3",
        "logentry-rights-autopromote": "$1 ha state {{GENDER:$2|promosse}} automaticamende da $4 a $5",
index 96f593a..8700ce0 100644 (file)
        "listgrouprights-namespaceprotection-header": "Ограничења именских простора",
        "listgrouprights-namespaceprotection-namespace": "Именски простор",
        "listgrouprights-namespaceprotection-restrictedto": "Права потребна за уређивање",
-       "trackingcategories": "Ð\9aаÑ\82егоÑ\80иÑ\98е Ð·Ð° Ð¿Ñ\80аÑ\9bеÑ\9aе",
+       "trackingcategories": "Ð\9cедиÑ\98авики ÐºÐ°Ñ\82егоÑ\80иÑ\98е",
        "trackingcategories-name": "Име поруке",
        "trackingcategories-desc": "Које странице се налазе у категорији",
        "noindex-category-desc": "Странице које у себи имају магичну реч <code><nowiki>__NOINDEX__</nowiki></code>.",
index 359b359..02a124e 100644 (file)
        "listgrouprights-namespaceprotection-header": "Ograničenja imenskih prostora",
        "listgrouprights-namespaceprotection-namespace": "Imenski prostor",
        "listgrouprights-namespaceprotection-restrictedto": "Prava potrebna za uređivanje",
-       "trackingcategories": "Kategorije za praćenje",
+       "trackingcategories": "Medijaviki kategorije",
        "trackingcategories-name": "Ime poruke",
        "trackingcategories-desc": "Koje stranice se nalaze u kategoriji",
        "noindex-category-desc": "Stranice koje u sebi imaju magičnu reč <code><nowiki>__NOINDEX__</nowiki></code>.",
index f64e531..f6c90e1 100644 (file)
        "api-error-badaccess-groups": "Bu wiki için dosya yüklemenize izin verilmiyor.",
        "api-error-badtoken": "İç hata: Bozuk anahtar.",
        "api-error-copyuploaddisabled": "URL ile yükleme bu sunucuda devre dışı bırakılmıştır.",
-       "api-error-duplicate": "Sitede zaten aynı içerikte başka {{PLURAL:$1|bir dosya|dosyalar}} var.",
+       "api-error-duplicate": "Sitede zaten aynı içerikte başka {{PLURAL:$1|bir dosya|dosyalar}} mevcut.",
        "api-error-duplicate-archive": "Sitede zaten aynı içerikte başka {{PLURAL:$1|bir dosya|dosyalar}} vardı, ama {{PLURAL:$1|silindi|silindiler}}.",
        "api-error-empty-file": "Gönderdiğiniz dosya boş.",
        "api-error-emptypage": "Yeni, boş bir sayfa oluşturmaya izin verilmez.",
index 8e4e531..2322909 100644 (file)
@@ -433,7 +433,6 @@ aiff
 aiprop
 airtel
 aisort
-ajaxwatch
 al
 alefsym
 algo
index ea8c84b..bf59495 100644 (file)
@@ -235,8 +235,6 @@ TEXT;
                        }
                }
                wfWaitForSlaves();
-               // XXX: Don't let deferred jobs array get absurdly large (bug 24375)
-               DeferredUpdates::doUpdates( 'commit' );
        }
 
        function progress( $string ) {
index 1432912..4f8789d 100644 (file)
         */
        UP.setFilenameFromFile = function () {
                var file = this.getFile();
+               if ( !file ) {
+                       return;
+               }
                if ( file.nodeType && file.nodeType === Node.ELEMENT_NODE ) {
                        // File input element, use getBasename to cut out the path
                        this.setFilename( this.getBasename( file.value ) );
-               } else if ( file.name && file.lastModified ) {
+               } else if ( file.name ) {
                        // HTML5 FileAPI File object, but use getBasename to be safe
                        this.setFilename( this.getBasename( file.name ) );
+               } else {
+                       // If we ever implement uploading files from clipboard, they might not have a name
+                       this.setFilename( '?' );
                }
        };
 
index a14a50d..01e221f 100644 (file)
@@ -74,6 +74,20 @@ class EnhancedChangesListTest extends MediaWikiLangTestCase {
                $this->assertEquals( '', $html );
        }
 
+       public function testCategorizationLineFormatting() {
+               $html = $this->createCategorizationLine(
+                       $this->getCategorizationChange( '20150629191735', 0, 0 )
+               );
+               $this->assertNotContains( '(diff | hist)', strip_tags( $html ) );
+       }
+
+       public function testCategorizationLineFormattingWithRevision() {
+               $html = $this->createCategorizationLine(
+                       $this->getCategorizationChange( '20150629191735', 1025, 1024 )
+               );
+               $this->assertContains( '(diff | hist)', strip_tags( $html ) );
+       }
+
        /**
         * @todo more tests for actual formatting, this is more of a smoke test
         */
@@ -115,6 +129,24 @@ class EnhancedChangesListTest extends MediaWikiLangTestCase {
                return $recentChange;
        }
 
+       /**
+        * @return RecentChange
+        */
+       private function getCategorizationChange( $timestamp, $thisId, $lastId ) {
+               $wikiPage = new WikiPage( Title::newFromText( 'Testpage' ) );
+               $wikiPage->doEditContent( new WikitextContent( 'Some random text' ), 'page created' );
+
+               $wikiPage = new WikiPage( Title::newFromText( 'Category:Foo' ) );
+               $wikiPage->doEditContent( new WikitextContent( 'Some random text' ), 'category page created' );
+
+               $user = $this->getTestUser();
+               $recentChange = $this->testRecentChangesHelper->makeCategorizationRecentChange(
+                       $user, 'Category:Foo', $wikiPage->getId(), $thisId, $lastId, $timestamp
+               );
+
+               return $recentChange;
+       }
+
        /**
         * @return User
         */
@@ -128,4 +160,15 @@ class EnhancedChangesListTest extends MediaWikiLangTestCase {
                return $user;
        }
 
+       private function createCategorizationLine( $recentChange ) {
+               $enhancedChangesList = $this->newEnhancedChangesList();
+               $cacheEntry = $this->testRecentChangesHelper->getCacheEntry( $recentChange );
+
+               $reflection = new \ReflectionClass( get_class( $enhancedChangesList ) );
+               $method = $reflection->getMethod( 'recentChangesBlockLine' );
+               $method->setAccessible( true );
+
+               return $method->invokeArgs( $enhancedChangesList, array( $cacheEntry ) );
+       }
+
 }
index fe5bdd2..10d4c6e 100644 (file)
@@ -97,6 +97,36 @@ class TestRecentChangesHelper {
                return $change;
        }
 
+       public function getCacheEntry( $recentChange ) {
+               $rcCacheFactory = new RCCacheEntryFactory(
+                       new RequestContext(),
+                       array( 'diff' => 'diff', 'cur' => 'cur', 'last' => 'last' )
+               );
+               return $rcCacheFactory->newFromRecentChange( $recentChange, false );
+       }
+
+       public function makeCategorizationRecentChange(
+               User $user, $titleText, $curid, $thisid, $lastid, $timestamp
+       ) {
+
+               $attribs = array_merge(
+                       $this->getDefaultAttributes( $titleText, $timestamp ),
+                       array(
+                               'rc_type' => RC_CATEGORIZE,
+                               'rc_user' => $user->getId(),
+                               'rc_user_text' => $user->getName(),
+                               'rc_this_oldid' => $thisid,
+                               'rc_last_oldid' => $lastid,
+                               'rc_cur_id' => $curid,
+                               'rc_comment' => '[[:Testpage]] added to category',
+                               'rc_old_len' => 0,
+                               'rc_new_len' => 0,
+                       )
+               );
+
+               return $this->makeRecentChange( $attribs, 0, 0 );
+       }
+
        private function getDefaultAttributes( $titleText, $timestamp ) {
                return array(
                        'rc_id' => 545,
index bbd196d..25ee5ec 100644 (file)
@@ -19,7 +19,8 @@ class LinksUpdateTest extends MediaWikiTestCase {
                                'externallinks',
                                'imagelinks',
                                'templatelinks',
-                               'iwlinks'
+                               'iwlinks',
+                               'recentchanges',
                        )
                );
        }
@@ -39,6 +40,13 @@ class LinksUpdateTest extends MediaWikiTestCase {
                                'iw_wikiid' => 'linksupdatetest',
                        )
                );
+               $this->setMwGlobals( 'wgRCWatchCategoryMembership', true );
+       }
+
+       public function addDBData() {
+               $this->insertPage( 'Testing' );
+               $this->insertPage( 'Some_other_page' );
+               $this->insertPage( 'Template:TestingTemplate' );
        }
 
        protected function makeTitleAndParserOutput( $name, $id ) {
@@ -133,6 +141,61 @@ class LinksUpdateTest extends MediaWikiTestCase {
                ) );
        }
 
+       public function testOnAddingAndRemovingCategory_recentChangesRowIsAdded() {
+               $this->setMwGlobals( 'wgCategoryCollation', 'uppercase' );
+
+               $title = Title::newFromText( 'Testing' );
+               $wikiPage = new WikiPage( $title );
+               $wikiPage->doEditContent( new WikitextContent( '[[Category:Foo]]' ), 'added category' );
+
+               $this->assertRecentChangeByCategorization(
+                       $title,
+                       $wikiPage->getParserOutput( new ParserOptions() ),
+                       Title::newFromText( 'Category:Foo' ),
+                       array( array( 'Foo', '[[:Testing]] added to category' ) )
+               );
+
+               $wikiPage->doEditContent( new WikitextContent( '[[Category:Bar]]' ), 'added category' );
+               $this->assertRecentChangeByCategorization(
+                       $title,
+                       $wikiPage->getParserOutput( new ParserOptions() ),
+                       Title::newFromText( 'Category:Foo' ),
+                       array(
+                               array( 'Foo', '[[:Testing]] added to category' ),
+                               array( 'Foo', '[[:Testing]] removed from category' ),
+                       )
+               );
+
+               $this->assertRecentChangeByCategorization(
+                       $title,
+                       $wikiPage->getParserOutput( new ParserOptions() ),
+                       Title::newFromText( 'Category:Bar' ),
+                       array(
+                               array( 'Bar', '[[:Testing]] added to category' ),
+                       )
+               );
+       }
+
+       public function testOnAddingAndRemovingCategoryToTemplates_embeddingPagesAreIgnored() {
+               $this->setMwGlobals( 'wgCategoryCollation', 'uppercase' );
+
+               $templateTitle = Title::newFromText( 'Template:TestingTemplate' );
+               $templatePage = new WikiPage( $templateTitle );
+
+               $wikiPage = new WikiPage( Title::newFromText( 'Testing' ) );
+               $wikiPage->doEditContent( new WikitextContent( '{{TestingTemplate}}' ), 'added template' );
+               $otherWikiPage = new WikiPage( Title::newFromText( 'Some_other_page' ) );
+               $otherWikiPage->doEditContent( new WikitextContent( '{{TestingTemplate}}' ), 'added template' );
+               $templatePage->doEditContent( new WikitextContent( '[[Category:Foo]]' ), 'added category' );
+
+               $this->assertRecentChangeByCategorization(
+                       $templateTitle,
+                       $templatePage->getParserOutput( new ParserOptions() ),
+                       Title::newFromText( 'Foo' ),
+                       array( array( 'Foo', '[[:Template:TestingTemplate]] and 2 pages added to category' ) )
+               );
+       }
+
        /**
         * @covers ParserOutput::addInterwikiLink
         */
@@ -263,4 +326,26 @@ class LinksUpdateTest extends MediaWikiTestCase {
                $this->assertSelect( $table, $fields, $condition, $expectedRows );
                return $update;
        }
+
+       protected function assertRecentChangeByCategorization(
+               Title $pageTitle, ParserOutput $parserOutput, Title $categoryTitle, $expectedRows
+       ) {
+               $update = new LinksUpdate( $pageTitle, $parserOutput );
+               $revision = Revision::newFromTitle( $pageTitle );
+               $update->setRevision( $revision );
+               $update->beginTransaction();
+               $update->doUpdate();
+               $update->commitTransaction();
+
+               $this->assertSelect(
+                       'recentchanges',
+                       'rc_title, rc_comment',
+                       array(
+                               'rc_type' => RC_CATEGORIZE,
+                               'rc_namespace' => NS_CATEGORY,
+                               'rc_title' => $categoryTitle->getDBkey()
+                       ),
+                       $expectedRows
+               );
+       }
 }
index 2b66181..ac52e12 100644 (file)
@@ -18,7 +18,8 @@ class MultiWriteBagOStuffTest extends MediaWikiTestCase {
                $this->cache2 = new HashBagOStuff();
                $this->cache = new MultiWriteBagOStuff( array(
                        'caches' => array( $this->cache1, $this->cache2 ),
-                       'replication' => 'async'
+                       'replication' => 'async',
+                       'asyncHandler' => 'DeferredUpdates::addCallableUpdate'
                ) );
        }
 
index 0a6336f..384b000 100644 (file)
  */
 class SpecialRecentchangesTest extends MediaWikiTestCase {
 
+       protected function setUp() {
+               parent::setUp();
+               $this->setMwGlobals( 'wgRCWatchCategoryMembership', true );
+       }
+
        /**
         * @var SpecialRecentChanges
         */
@@ -50,7 +55,8 @@ class SpecialRecentchangesTest extends MediaWikiTestCase {
                $this->assertConditions(
                        array( # expected
                                'rc_bot' => 0,
-                               0 => "rc_namespace = '0'",
+                               0 => "rc_type != '6'",
+                               1 => "rc_namespace = '0'",
                        ),
                        array(
                                'namespace' => NS_MAIN,
@@ -63,7 +69,8 @@ class SpecialRecentchangesTest extends MediaWikiTestCase {
                $this->assertConditions(
                        array( # expected
                                'rc_bot' => 0,
-                               0 => sprintf( "rc_namespace != '%s'", NS_MAIN ),
+                               0 => "rc_type != '6'",
+                               1 => sprintf( "rc_namespace != '%s'", NS_MAIN ),
                        ),
                        array(
                                'namespace' => NS_MAIN,
@@ -81,7 +88,8 @@ class SpecialRecentchangesTest extends MediaWikiTestCase {
                $this->assertConditions(
                        array( # expected
                                'rc_bot' => 0,
-                               0 => sprintf( "(rc_namespace = '%s' OR rc_namespace = '%s')", $ns1, $ns2 ),
+                               0 => "rc_type != '6'",
+                               1 => sprintf( "(rc_namespace = '%s' OR rc_namespace = '%s')", $ns1, $ns2 ),
                        ),
                        array(
                                'namespace' => $ns1,
@@ -99,7 +107,8 @@ class SpecialRecentchangesTest extends MediaWikiTestCase {
                $this->assertConditions(
                        array( # expected
                                'rc_bot' => 0,
-                               0 => sprintf( "(rc_namespace != '%s' AND rc_namespace != '%s')", $ns1, $ns2 ),
+                               0 => "rc_type != '6'",
+                               1 => sprintf( "(rc_namespace != '%s' AND rc_namespace != '%s')", $ns1, $ns2 ),
                        ),
                        array(
                                'namespace' => $ns1,