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,
** 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
$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
*/
'gender' => 'unknown',
'hideminor' => 0,
'hidepatrolled' => 0,
+ 'hidecategorization' => 1,
'imagesize' => 2,
'math' => 1,
'minordefault' => 0,
'watchlisthideminor' => 0,
'watchlisthideown' => 0,
'watchlisthidepatrolled' => 0,
+ 'watchlisthidecategorization' => 1,
'watchmoves' => 0,
'watchrollback' => 0,
'wllimit' => 250,
'udp' => 'UDPRCFeedEngine',
);
+/**
+ * Treat category membership changes as a RecentChange
+ * @since 1.27
+ */
+$wgRCWatchCategoryMembership = false;
+
/**
* Use RC Patrolling to check for vandalism
*/
*/
$wgAjaxExportList = array();
-/**
- * Enable watching/unwatching pages using AJAX.
- * Requires $wgUseAjax to be true too.
- */
-$wgAjaxWatch = true;
-
/**
* Enable AJAX check for file overwrite, pre-upload
*/
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();
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' );
$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)
$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' );
}
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;
}
/**
'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',
'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',
* @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();
'hideliu' => false,
'hidepatrolled' => false,
'hidemyself' => false,
+ 'hidecategorization' => false,
'tagfilter' => array(
ApiBase::PARAM_TYPE => 'string',
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(
)
),
'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'
"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.",
"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。",
"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}}",
"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ı).",
"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。",
return array();
}
- $config = $resourceLoader->getConfig();
$retval = array();
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'msg_resource',
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;
/**
* @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;
* @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
*/
*/
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'];
$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> ';
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;
+ }
+
}
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;
/** @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 ) {
}
# 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'];
}
# 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> ';
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 );
}
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
// Regular entries
} else {
$unpatrolled = $this->showAsUnpatrolled( $rc );
-
$this->insertDiffHist( $html, $rc, $unpatrolled );
# M, N, b and ! (minor, new, bot and unpatrolled)
$html .= $this->recentChangesFlags(
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 );
$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>";
$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']
+ );
+ }
}
}
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
*/
$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
// 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
* restore the initial value
*/
public function setBigSelects( $value = true );
+
+ /**
+ * @return bool Whether this DB is read-only
+ * @since 1.27
+ */
+ public function isReadOnly();
}
/** @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'];
+ }
}
/**
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;
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
);
}
$template = $this->templateOverridesByCluster[$cluster] + $template;
}
- return $this->newLoadBalancer( $template, $this->externalLoads[$cluster], array() );
+ return $this->newLoadBalancer(
+ $template,
+ $this->externalLoads[$cluster],
+ array(),
+ $this->readOnlyReason
+ );
}
/**
* @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;
}
/**
return new LoadBalancer( array(
'servers' => $servers,
- 'loadMonitor' => $this->loadMonitorClass
+ 'loadMonitor' => $this->loadMonitorClass,
+ 'readOnlyReason' => $this->readOnlyReason
) );
}
return new LoadBalancer( array(
'servers' => $wgExternalServers[$cluster],
- 'loadMonitor' => $this->loadMonitorClass
+ 'loadMonitor' => $this->loadMonitorClass,
+ 'readOnlyReason' => $this->readOnlyReason
) );
}
public function __construct( array $conf ) {
parent::__construct( $conf );
+ $conf['readOnlyReason'] = $this->readOnlyReason;
$this->lb = new LoadBalancerSingle( $conf );
}
*/
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'] );
+ }
}
/**
/** @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;
/**
* @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 ) {
'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 {
/**
* @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 ) {
$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 );
if ( $i > 0 ) {
if ( !$this->doWait( $i ) ) {
$this->mServers[$i]['slave pos'] = $this->getAnyOpenConnection( $i )->getSlavePos();
- $this->mLaggedSlaveMode = true;
+ $this->laggedSlaveMode = true;
}
}
}
$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;
* @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;
}
/**
* @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;
}
/**
/** @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.
*/
}
protected function doIncrementalUpdate() {
+ global $wgRCWatchCategoryMembership;
# Page links
$existing = $this->getExistingLinks();
$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();
}
+ 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
*
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
'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()
)
"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.",
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." );
return false;
}
- JobQueue::incrStats( 'job-ack', $this->type );
+ JobQueue::incrStats( 'acks', $this->type );
} catch ( RedisException $e ) {
$this->throwRedisException( $conn, $e );
}
} 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(
}
$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 );
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;
/**
* $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
*/
}
}
- $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 )
+ );
}
/**
}
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 ) {
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();
}
} 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" );
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 \""
$updates = $content->getSecondaryDataUpdates(
$this->getTitle(), null, $recursive, $editInfo->output );
foreach ( $updates as $update ) {
+ if ( $update instanceof LinksUpdate ) {
+ $update->setRevision( $revision );
+ }
DeferredUpdates::addUpdate( $update );
}
}
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 ) );
}
}
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();
}
* @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();
// 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()
) {
* @return FormOptions
*/
public function getDefaultOptions() {
+ $config = $this->getConfig();
$opts = new FormOptions();
$opts->add( 'hideminor', false );
$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 );
$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'] !== '' ) {
}
$config = $this->getConfig();
- if ( ( $config->get( 'UseTidy' ) && $options->getTidy() ) || $config->get( 'AlwaysUseTidy' ) ) {
+ if ( $config->get( 'UseTidy' ) && $options->getTidy() ) {
$tmp = MWTidy::tidy( $tmp );
}
public function getDefaultOptions() {
$opts = parent::getDefaultOptions();
$user = $this->getUser();
+ $config = $this->getConfig();
$opts->add( 'days', $user->getIntOption( 'rcdays' ) );
$opts->add( 'limit', $user->getIntOption( 'rclimit' ) );
$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', '' );
if ( 'hidemyself' === $bit ) {
$opts['hidemyself'] = true;
}
+ if ( 'hidecategorization' === $bit ) {
+ $opts['hidecategorization'] = true;
+ }
if ( is_numeric( $bit ) ) {
$opts['limit'] = $bit;
$lang = $this->getLanguage();
$user = $this->getUser();
+ $config = $this->getConfig();
if ( $options['from'] ) {
$note .= $this->msg( 'rcnotefrom' )
->numParams( $options['limit'] )
}
# 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 );
'hidemyself' => 'rcshowhidemine'
);
+ if ( $config->get( 'RCWatchCategoryMembership' ) ) {
+ $filters['hidecategorization'] = 'rcshowhidecategorization';
+ }
+
$showhide = array( 'show', 'hide' );
foreach ( $this->getCustomFilters() as $key => $params ) {
// 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'.
$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;
'hidemyself' => 'rcshowhidemine',
'hidepatrolled' => 'rcshowhidepatr'
);
+
+ if ( $this->getConfig()->get( 'RCWatchCategoryMembership' ) ) {
+ $filters['hidecategorization'] = 'rcshowhidecategorization';
+ }
+
foreach ( $this->getCustomFilters() as $key => $params ) {
$filters[$key] = $params['msg'];
}
"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 не існуе.",
"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",
"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.",
"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}})",
"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": "데이터베이스를 잠그거나 잠금 해제",
"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.",
"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": " ī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:",
"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.",
"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.",
"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 /> 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 /> 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 /> 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}}]",
"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",
"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>.",
"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>.",
"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.",
aiprop
airtel
aisort
-ajaxwatch
al
alefsym
algo
}
}
wfWaitForSlaves();
- // XXX: Don't let deferred jobs array get absurdly large (bug 24375)
- DeferredUpdates::doUpdates( 'commit' );
}
function progress( $string ) {
*/
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( '?' );
}
};
$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
*/
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
*/
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 ) );
+ }
+
}
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,
'externallinks',
'imagelinks',
'templatelinks',
- 'iwlinks'
+ 'iwlinks',
+ 'recentchanges',
)
);
}
'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 ) {
) );
}
+ 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
*/
$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
+ );
+ }
}
$this->cache2 = new HashBagOStuff();
$this->cache = new MultiWriteBagOStuff( array(
'caches' => array( $this->cache1, $this->cache2 ),
- 'replication' => 'async'
+ 'replication' => 'async',
+ 'asyncHandler' => 'DeferredUpdates::addCallableUpdate'
) );
}
*/
class SpecialRecentchangesTest extends MediaWikiTestCase {
+ protected function setUp() {
+ parent::setUp();
+ $this->setMwGlobals( 'wgRCWatchCategoryMembership', true );
+ }
+
/**
* @var SpecialRecentChanges
*/
$this->assertConditions(
array( # expected
'rc_bot' => 0,
- 0 => "rc_namespace = '0'",
+ 0 => "rc_type != '6'",
+ 1 => "rc_namespace = '0'",
),
array(
'namespace' => NS_MAIN,
$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,
$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,
$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,