$wgResourceLoaderMinifierMaxLineLength, because there was little value in
making the behavior configurable. The default values (`false` for the former,
1000 for the latter) are now hard-coded.
+* $wgDebugDumpSqlLength was removed (deprecated in 1.24).
+* $wgDebugDBTransactions was removed (deprecated in 1.20).
=== New features in 1.27 ===
* $wgDataCenterId and $wgDataCenterRoles where added, which will serve as
basic configuration settings needed for multi-datacenter setups.
$wgDataCenterUpdateStickTTL was also added.
+* Added a new hook, 'UserMailerTransformContent', to transform the contents
+ of an email. This is similar to the EmailUser hook but applies to all mail
+ sent via UserMailer.
+* Added a new hook, 'UserMailerTransformMessage', to transform the contents
+ of an emai after MIME encoding.
+* Added a new hook, 'UserMailerSplitTo', to control which users have to be
+ emailed separately (ie. there is a single address in the To: field) so
+ user-specific changes to the email can be applied safely.
+* $wgCdnMaxageLagged was added, which limits the CDN cache TTL
+ when any load balancer uses a DB that is lagged beyond the 'max lag'
+ setting in the relevant section of $wgLBFactoryConf.
==== External libraries ====
'DBLockManager' => __DIR__ . '/includes/filebackend/lockmanager/DBLockManager.php',
'DBMasterPos' => __DIR__ . '/includes/db/DatabaseUtility.php',
'DBQueryError' => __DIR__ . '/includes/db/DatabaseError.php',
+ 'DBReadOnlyError' => __DIR__ . '/includes/db/DatabaseError.php',
'DBSiteStore' => __DIR__ . '/includes/site/DBSiteStore.php',
'DBUnexpectedError' => __DIR__ . '/includes/db/DatabaseError.php',
'DataUpdate' => __DIR__ . '/includes/deferred/DataUpdate.php',
+ 'Database' => __DIR__ . '/includes/db/Database.php',
'DatabaseBase' => __DIR__ . '/includes/db/Database.php',
'DatabaseInstaller' => __DIR__ . '/includes/installer/DatabaseInstaller.php',
'DatabaseLag' => __DIR__ . '/maintenance/lag.php',
'MemcachedBagOStuff' => __DIR__ . '/includes/objectcache/MemcachedBagOStuff.php',
'MemcachedPeclBagOStuff' => __DIR__ . '/includes/objectcache/MemcachedPeclBagOStuff.php',
'MemcachedPhpBagOStuff' => __DIR__ . '/includes/objectcache/MemcachedPhpBagOStuff.php',
+ 'MemoizedCallable' => __DIR__ . '/includes/libs/MemoizedCallable.php',
'MemoryFileBackend' => __DIR__ . '/includes/filebackend/MemoryFileBackend.php',
'MergeHistoryPager' => __DIR__ . '/includes/specials/SpecialMergeHistory.php',
'MergeLogFormatter' => __DIR__ . '/includes/logging/MergeLogFormatter.php',
'PostgresBlob' => __DIR__ . '/includes/db/DatabasePostgres.php',
'PostgresField' => __DIR__ . '/includes/db/DatabasePostgres.php',
'PostgresInstaller' => __DIR__ . '/includes/installer/PostgresInstaller.php',
- 'PostgresTransactionState' => __DIR__ . '/includes/db/DatabasePostgres.php',
'PostgresUpdater' => __DIR__ . '/includes/installer/PostgresUpdater.php',
'Preferences' => __DIR__ . '/includes/Preferences.php',
'PreferencesForm' => __DIR__ . '/includes/Preferences.php',
'WebInstallerWelcome' => __DIR__ . '/includes/installer/WebInstallerPage.php',
'WebPHandler' => __DIR__ . '/includes/media/WebP.php',
'WebRequest' => __DIR__ . '/includes/WebRequest.php',
- 'WebRequestUpload' => __DIR__ . '/includes/WebRequest.php',
+ 'WebRequestUpload' => __DIR__ . '/includes/WebRequestUpload.php',
'WebResponse' => __DIR__ . '/includes/WebResponse.php',
'WikiCategoryPage' => __DIR__ . '/includes/page/WikiCategoryPage.php',
'WikiDiff3' => __DIR__ . '/includes/diff/WikiDiff3.php',
on the search results page. Useful for including a feedback link.
$specialSearch: SpecialSearch object ($this)
$output: $wgOut
+$term: Search term specified by the user
'SpecialSearchSetupEngine': Allows passing custom data to search engine.
$search: SpecialSearch special page object
$to: Array of MailAddress objects for the recipients
&$returnPath: The return address string
+'UserMailerSplitTo': Called in UserMailer::send() to give extensions a chance
+to split up an email with multiple the To: field into separate emails.
+$to: array of MailAddress objects; unset the ones which should be mailed separately
+
+'UserMailerTransformContent': Called in UserMailer::send() to change email contents.
+Extensions can block sending the email by returning false and setting $error.
+$to: array of MailAdresses of the targets
+$from: MailAddress of the sender
+&$body: email body, either a string (for plaintext emails) or an array with 'text' and 'html' keys
+&$error: should be set to an error message string
+
+'UserMailerTransformMessage': Called in UserMailer::send() to change email after it has gone through
+the MIME transform. Extensions can block sending the email by returning false and setting $error.
+$to: array of MailAdresses of the targets
+$from: MailAddress of the sender
+&$subject: email subject (not MIME encoded)
+&$headers: email headers (except To: and Subject:) as an array of header name => value pairs
+&$body: email body (in MIME format) as a string
+&$error: should be set to an error message string
+
'UserRemoveGroup': Called when removing a group; return false to override stock
group removal.
$user: the user object that is to have a group removed
* Insert a block into the block table. Will fail if there is a conflicting
* block (same name and options) already in the database.
*
- * @param DatabaseBase $dbw If you have one available
+ * @param IDatabase $dbw If you have one available
* @return bool|array False on failure, assoc array on success:
* ('id' => block ID, 'autoIds' => array of autoblock IDs)
*/
/**
* Get an array suitable for passing to $dbw->insert() or $dbw->update()
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @return array
*/
protected function getDatabaseArray( $db = null ) {
/** @var string "AND" or "OR" */
protected $mode;
- /** @var DatabaseBase Read-DB slave */
+ /** @var IDatabase Read-DB slave */
protected $dbr;
/**
$wgInternalServer = false;
/**
- * Cache timeout for the squid, will be sent as s-maxage (without ESI) or
- * Surrogate-Control (with ESI). Without ESI, you should strip out s-maxage in
- * the Squid config.
+ * Cache TTL for the CDN sent as s-maxage (without ESI) or
+ * Surrogate-Control (with ESI). Without ESI, you should strip
+ * out s-maxage in the Squid config.
*
-* 18000 seconds = 5 hours, more cache hits with 2678400 = 31 days.
+ * 18000 seconds = 5 hours, more cache hits with 2678400 = 31 days.
*/
$wgSquidMaxage = 18000;
+/**
+ * Cache timeout for the CDN when DB slave lag is high
+ * @see $wgSquidMaxage
+ * @since 1.27
+ */
+$wgCdnMaxageLagged = 30;
+
/**
* Default maximum age for raw CSS/JS accesses
*
*/
$wgDebugComments = false;
-/**
- * Extensive database transaction state debugging
- *
- * @since 1.20
- */
-$wgDebugDBTransactions = false;
-
/**
* Write SQL queries to the debug log.
*
*/
$wgDebugDumpSql = false;
-/**
- * Trim logged SQL queries to this many bytes. Set 0/false/null to do no
- * trimming.
- * @since 1.24
- */
-$wgDebugDumpSqlLength = 500;
-
/**
* Performance expectations for DB usage
*
* make additional queries to pull source data while the
* main query is still running.
*
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param int|array $history One of WikiExporter::FULL, WikiExporter::CURRENT,
* WikiExporter::RANGE or WikiExporter::STABLE, or an associative array:
* - offset: non-inclusive offset at which to start the query
}
/**
- * Make an array to be used for calls to DatabaseBase::buildLike(), which
+ * Make an array to be used for calls to Database::buildLike(), which
* will match the specified string. There are several kinds of filter entry:
* *.domain.com - Produces http://com.domain.%, matches domain.com
* and www.domain.com
*
* @param string $filterEntry Domainparts
* @param string $protocol Protocol (default http://)
- * @return array Array to be passed to DatabaseBase::buildLike() or false on error
+ * @return array Array to be passed to Database::buildLike() or false on error
*/
public static function makeLikeArray( $filterEntry, $protocol = 'http://' ) {
$db = wfGetDB( DB_SLAVE );
$mValidNamespaces[] = $ns;
}
}
+ // T109137: sort numerically
+ sort( $mValidNamespaces, SORT_NUMERIC );
}
return $mValidNamespaces;
*/
public function run() {
try {
- $this->checkMaxLag();
try {
$this->main();
} catch ( ErrorPageError $e ) {
$expires = time() + $this->config->get( 'DataCenterUpdateStickTTL' );
$request->response()->setCookie( 'UseDC', 'master', $expires );
}
+
+ // Avoid letting a few seconds of slave lag cause a month of stale data
+ if ( $factory->laggedSlaveUsed() ) {
+ $maxAge = $this->config->get( 'CdnMaxageLagged' );
+ $this->context->getOutput()->lowerCdnMaxage( $maxAge );
+ wfDebugLog( 'replication', "Lagged DB used; CDN cache TTL limited to $maxAge seconds" );
+ }
}
/**
}
}
- /**
- * Checks if the request should abort due to a lagged server,
- * for given maxlag parameter.
- * @return bool
- */
- private function checkMaxLag() {
- $maxLag = $this->context->getRequest()->getVal( 'maxlag' );
- if ( !is_null( $maxLag ) ) {
- list( $host, $lag ) = wfGetLB()->getMaxLag();
- if ( $lag > $maxLag ) {
- $resp = $this->context->getRequest()->response();
- $resp->statusHeader( 503 );
- $resp->header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) );
- $resp->header( 'X-Database-Lag: ' . intval( $lag ) );
- $resp->header( 'Content-Type: text/plain' );
- if ( $this->config->get( 'ShowHostnames' ) ) {
- echo "Waiting for $host: $lag seconds lagged\n";
- } else {
- echo "Waiting for a database server: $lag seconds lagged\n";
- }
-
- exit;
- }
- }
- return true;
- }
-
private function main() {
global $wgTitle, $wgTrxProfilerLimits;
/** @var int Cache stuff. Looks like mEnableClientCache */
protected $mSquidMaxage = 0;
+ /** @var in Upper limit on mSquidMaxage */
+ protected $mCdnMaxageLimit = INF;
/**
* @var bool Controls if anti-clickjacking / frame-breaking headers will
* @param int $maxage Maximum cache time on the Squid, in seconds.
*/
public function setSquidMaxage( $maxage ) {
- $this->mSquidMaxage = $maxage;
+ $this->mSquidMaxage = min( $maxage, $this->mCdnMaxageLimit );
+ }
+
+ /**
+ * Lower the value of the "s-maxage" part of the "Cache-control" HTTP header
+ *
+ * @param int $maxage Maximum cache time on the Squid, in seconds
+ * @since 1.27
+ */
+ public function lowerCdnMaxage( $maxage ) {
+ $this->mCdnMaxageLimit = $this->min( $maxage, $this->mCdnMaxageLimit );
+ $this->setSquidMaxage( $this->mSquidMaxage );
}
/**
* Load a page revision from a given revision ID number.
* Returns null if no such revision can be found.
*
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param int $id
* @return Revision|null
*/
* that's attached to a given page. If not attached
* to that page, will return null.
*
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param int $pageid
* @param int $id
* @return Revision|null
* that's attached to a given page. If not attached
* to that page, will return null.
*
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param Title $title
* @param int $id
* @return Revision|null
* WARNING: Timestamps may in some circumstances not be unique,
* so this isn't the best key to use.
*
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param Title $title
* @param string $timestamp
* @return Revision|null
* Given a set of conditions, fetch a revision from
* the given database connection.
*
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param array $conditions
* @param int $flags (optional)
* @return Revision|null
* which will return matching database rows with the
* fields necessary to build Revision objects.
*
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param array $conditions
* @param int $flags (optional)
* @return ResultWrapper
/**
* Do a batched query to get the parent revision lengths
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param array $revIds
* @return array
*/
* Get previous revision Id for this page_id
* This is used to populate rev_parent_id on save
*
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @return int
*/
private function getPreviousRevisionId( $db ) {
* Insert a new revision into the database, returning the new revision ID
* number on success and dies horribly on failure.
*
- * @param DatabaseBase $dbw (master connection)
+ * @param IDatabase $dbw (master connection)
* @throws MWException
* @return int
*/
* Such revisions can for instance identify page rename
* operations and other such meta-modifications.
*
- * @param DatabaseBase $dbw
+ * @param IDatabase $dbw
* @param int $pageId ID number of the page to read from
* @param string $summary Revision's summary
* @param bool $minor Whether the revision should be considered as minor
/**
* Get count of revisions per page...not very efficient
*
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param int $id Page id
* @return int
*/
/**
* Get count of revisions per page...not very efficient
*
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param Title $title
* @return int
*/
* @since 1.20
* @deprecated since 1.24
*
- * @param DatabaseBase|int $db The Database to perform the check on. May be given as a
+ * @param IDatabase|int $db The Database to perform the check on. May be given as a
* Database object or a database identifier usable with wfGetDB.
* @param int $pageId The ID of the page in question
* @param int $userId The ID of the user in question
/**
* Do the DB query to iterate through the objects.
- * @param DatabaseBase $db DatabaseBase object to use for the query
+ * @param IDatabase $db DB object to use for the query
*/
abstract public function doQuery( $db );
}
/**
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @return mixed
*/
public function doQuery( $db ) {
}
/**
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @return bool|ResultWrapper
*/
static function doLoad( $db ) {
/**
* Constructor
- * @param bool|DatabaseBase $database
- * - Boolean: whether to use the master DB
- * - DatabaseBase: database connection to use
+ * @param bool|IDatabase $database
+ * - boolean: Whether to use the master DB
+ * - IDatabase: Database connection to use
*/
public function __construct( $database = false ) {
- if ( $database instanceof DatabaseBase ) {
+ if ( $database instanceof IDatabase ) {
$this->db = $database;
} else {
$this->db = wfGetDB( $database ? DB_MASTER : DB_SLAVE );
* Do all updates and commit them. More or less a replacement
* for the original initStats, but without output.
*
- * @param DatabaseBase|bool $database
- * - Boolean: whether to use the master DB
- * - DatabaseBase: database connection to use
+ * @param IDatabase|bool $database
+ * - boolean: Whether to use the master DB
+ * - IDatabase: Database connection to use
* @param array $options Array of options, may contain the following values
- * - activeUsers Boolean: whether to update the number of active users (default: false)
+ * - activeUsers boolean: Whether to update the number of active users (default: false)
*/
public static function doAllAndCommit( $database, array $options = array() ) {
$options += array( 'update' => false, 'activeUsers' => false );
*
* @note: this handles RawMessage poorly
*
- * @param string $type
+ * @param string|bool $type
* @return array
*/
protected function getStatusArray( $type = false ) {
/**
* Updates page_touched for this page; called from LinksUpdate.php
*
- * @param integer $purgeTime TS_MW timestamp [optional]
+ * @param string $purgeTime [optional] TS_MW timestamp
* @return bool True if the update succeeded
*/
public function invalidateCache( $purgeTime = null ) {
* on the number of links. Typically called on create and delete.
*/
public function touchLinks() {
- $u = new HTMLCacheUpdate( $this, 'pagelinks' );
- $u->doUpdate();
-
+ DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'pagelinks' ) );
if ( $this->getNamespace() == NS_CATEGORY ) {
- $u = new HTMLCacheUpdate( $this, 'categorylinks' );
- $u->doUpdate();
+ DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this, 'categorylinks' ) );
}
}
/**
* Get the last touched timestamp
*
- * @param DatabaseBase $db Optional db
+ * @param IDatabase $db Optional db
* @return string Last-touched timestamp
*/
public function getTouched( $db = null ) {
$data['mVersion'] = self::VERSION;
$key = wfMemcKey( 'user', 'id', $this->mId );
- $opts = array( 'since' => wfGetDB( DB_SLAVE )->trxTimestamp() );
+ $opts = Database::getCacheSetOptions( wfGetDB( DB_SLAVE ) );
ObjectCache::getMainWANInstance()->set( $key, $data, 3600, $opts );
}
*
* @see newFromId()
* @see newFromName()
- * @param DatabaseBase $db Db connection
+ * @param IDatabase $db Db connection
* @param string $database Database name
* @param string $name User name
* @param int $id User ID
*
* @param string $database
* @param bool $ignoreInvalidDB If true, don't check if $database is in $wgLocalDatabases
- * @return DatabaseBase|null If invalid selection
+ * @return IDatabase|null If invalid selection
*/
public static function getDB( $database, $ignoreInvalidDB = false ) {
global $wgDBname;
}
}
-/**
- * Object to access the $_FILES array
- */
-class WebRequestUpload {
- protected $request;
- protected $doesExist;
- protected $fileInfo;
-
- /**
- * Constructor. Should only be called by WebRequest
- *
- * @param WebRequest $request The associated request
- * @param string $key Key in $_FILES array (name of form field)
- */
- public function __construct( $request, $key ) {
- $this->request = $request;
- $this->doesExist = isset( $_FILES[$key] );
- if ( $this->doesExist ) {
- $this->fileInfo = $_FILES[$key];
- }
- }
-
- /**
- * Return whether a file with this name was uploaded.
- *
- * @return bool
- */
- public function exists() {
- return $this->doesExist;
- }
-
- /**
- * Return the original filename of the uploaded file
- *
- * @return string|null Filename or null if non-existent
- */
- public function getName() {
- if ( !$this->exists() ) {
- return null;
- }
-
- global $wgContLang;
- $name = $this->fileInfo['name'];
-
- # Safari sends filenames in HTML-encoded Unicode form D...
- # Horrid and evil! Let's try to make some kind of sense of it.
- $name = Sanitizer::decodeCharReferences( $name );
- $name = $wgContLang->normalize( $name );
- wfDebug( __METHOD__ . ": {$this->fileInfo['name']} normalized to '$name'\n" );
- return $name;
- }
-
- /**
- * Return the file size of the uploaded file
- *
- * @return int File size or zero if non-existent
- */
- public function getSize() {
- if ( !$this->exists() ) {
- return 0;
- }
-
- return $this->fileInfo['size'];
- }
-
- /**
- * Return the path to the temporary file
- *
- * @return string|null Path or null if non-existent
- */
- public function getTempName() {
- if ( !$this->exists() ) {
- return null;
- }
-
- return $this->fileInfo['tmp_name'];
- }
-
- /**
- * Return the upload error. See link for explanation
- * http://www.php.net/manual/en/features.file-upload.errors.php
- *
- * @return int One of the UPLOAD_ constants, 0 if non-existent
- */
- public function getError() {
- if ( !$this->exists() ) {
- return 0; # UPLOAD_ERR_OK
- }
-
- return $this->fileInfo['error'];
- }
-
- /**
- * Returns whether this upload failed because of overflow of a maximum set
- * in php.ini
- *
- * @return bool
- */
- public function isIniSizeOverflow() {
- if ( $this->getError() == UPLOAD_ERR_INI_SIZE ) {
- # PHP indicated that upload_max_filesize is exceeded
- return true;
- }
-
- $contentLength = $this->request->getHeader( 'CONTENT_LENGTH' );
- if ( $contentLength > wfShorthandToInteger( ini_get( 'post_max_size' ) ) ) {
- # post_max_size is exceeded
- return true;
- }
-
- return false;
- }
-}
-
/**
* WebRequest clone which takes values from a provided array.
*
--- /dev/null
+<?php
+/**
+ * Object to access the $_FILES array
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Object to access the $_FILES array
+ *
+ * @ingroup HTTP
+ */
+class WebRequestUpload {
+ protected $request;
+ protected $doesExist;
+ protected $fileInfo;
+
+ /**
+ * Constructor. Should only be called by WebRequest
+ *
+ * @param WebRequest $request The associated request
+ * @param string $key Key in $_FILES array (name of form field)
+ */
+ public function __construct( $request, $key ) {
+ $this->request = $request;
+ $this->doesExist = isset( $_FILES[$key] );
+ if ( $this->doesExist ) {
+ $this->fileInfo = $_FILES[$key];
+ }
+ }
+
+ /**
+ * Return whether a file with this name was uploaded.
+ *
+ * @return bool
+ */
+ public function exists() {
+ return $this->doesExist;
+ }
+
+ /**
+ * Return the original filename of the uploaded file
+ *
+ * @return string|null Filename or null if non-existent
+ */
+ public function getName() {
+ if ( !$this->exists() ) {
+ return null;
+ }
+
+ global $wgContLang;
+ $name = $this->fileInfo['name'];
+
+ # Safari sends filenames in HTML-encoded Unicode form D...
+ # Horrid and evil! Let's try to make some kind of sense of it.
+ $name = Sanitizer::decodeCharReferences( $name );
+ $name = $wgContLang->normalize( $name );
+ wfDebug( __METHOD__ . ": {$this->fileInfo['name']} normalized to '$name'\n" );
+ return $name;
+ }
+
+ /**
+ * Return the file size of the uploaded file
+ *
+ * @return int File size or zero if non-existent
+ */
+ public function getSize() {
+ if ( !$this->exists() ) {
+ return 0;
+ }
+
+ return $this->fileInfo['size'];
+ }
+
+ /**
+ * Return the path to the temporary file
+ *
+ * @return string|null Path or null if non-existent
+ */
+ public function getTempName() {
+ if ( !$this->exists() ) {
+ return null;
+ }
+
+ return $this->fileInfo['tmp_name'];
+ }
+
+ /**
+ * Return the upload error. See link for explanation
+ * http://www.php.net/manual/en/features.file-upload.errors.php
+ *
+ * @return int One of the UPLOAD_ constants, 0 if non-existent
+ */
+ public function getError() {
+ if ( !$this->exists() ) {
+ return 0; # UPLOAD_ERR_OK
+ }
+
+ return $this->fileInfo['error'];
+ }
+
+ /**
+ * Returns whether this upload failed because of overflow of a maximum set
+ * in php.ini
+ *
+ * @return bool
+ */
+ public function isIniSizeOverflow() {
+ if ( $this->getError() == UPLOAD_ERR_INI_SIZE ) {
+ # PHP indicated that upload_max_filesize is exceeded
+ return true;
+ }
+
+ $contentLength = $this->request->getHeader( 'CONTENT_LENGTH' );
+ if ( $contentLength > wfShorthandToInteger( ini_get( 'post_max_size' ) ) ) {
+ # post_max_size is exceeded
+ return true;
+ }
+
+ return false;
+ }
+}
$content .= $this->msg( 'pageinfo-footer' )->parse();
}
- // Page credits
- /*if ( $this->page->exists() ) {
- $content .= Html::rawElement( 'div', array( 'id' => 'mw-credits' ), $this->getContributors() );
- }*/
-
return $content;
}
* @return string The table with the row added
*/
protected function addRow( $table, $name, $value, $id ) {
- return $table . Html::rawElement( 'tr', $id === null ? array() : array( 'id' => 'mw-' . $id ),
- Html::rawElement( 'td', array( 'style' => 'vertical-align: top;' ), $name ) .
- Html::rawElement( 'td', array(), $value )
- );
+ return $table .
+ Html::rawElement(
+ 'tr',
+ $id === null ? array() : array( 'id' => 'mw-' . $id ),
+ Html::rawElement( 'td', array( 'style' => 'vertical-align: top;' ), $name ) .
+ Html::rawElement( 'td', array(), $value )
+ );
}
/**
$policy = $this->page->getRobotPolicy( 'view', $pOutput );
$pageInfo['header-basic'][] = array(
// Messages: pageinfo-robot-index, pageinfo-robot-noindex
- $this->msg( 'pageinfo-robot-policy' ), $this->msg( "pageinfo-robot-${policy['index']}" )
+ $this->msg( 'pageinfo-robot-policy' ),
+ $this->msg( "pageinfo-robot-${policy['index']}" )
);
$unwatchedPageThreshold = $config->get( 'UnwatchedPageThreshold' );
// Subpages of this page, if subpages are enabled for the current NS
if ( MWNamespace::hasSubpages( $title->getNamespace() ) ) {
- $prefixIndex = SpecialPage::getTitleFor( 'Prefixindex', $title->getPrefixedText() . '/' );
+ $prefixIndex = SpecialPage::getTitleFor(
+ 'Prefixindex', $title->getPrefixedText() . '/' );
$pageInfo['header-basic'][] = array(
Linker::link( $prefixIndex, $this->msg( 'pageinfo-subpages-name' )->escaped() ),
$this->msg( 'pageinfo-subpages-value' )
$sources = $title->getCascadeProtectionSources(); // Array deferencing is in PHP 5.4 :(
foreach ( $sources[0] as $sourceTitle ) {
- $cascadingFrom .= Html::rawElement( 'li', array(), Linker::linkKnown( $sourceTitle ) );
+ $cascadingFrom .= Html::rawElement(
+ 'li', array(), Linker::linkKnown( $sourceTitle ) );
}
$cascadingFrom = Html::rawElement( 'ul', array(), $cascadingFrom );
$this->msg( 'pageinfo-lasttime' ),
Linker::linkKnown(
$title,
- htmlspecialchars( $lang->userTimeAndDate( $this->page->getTimestamp(), $user ) ),
+ htmlspecialchars(
+ $lang->userTimeAndDate( $this->page->getTimestamp(), $user )
+ ),
array(),
array( 'oldid' => $this->page->getLatest() )
)
// Recent number of edits (within past 30 days)
$pageInfo['header-edits'][] = array(
- $this->msg( 'pageinfo-recent-edits', $lang->formatDuration( $config->get( 'RCMaxAge' ) ) ),
+ $this->msg( 'pageinfo-recent-edits',
+ $lang->formatDuration( $config->get( 'RCMaxAge' ) ) ),
$lang->formatNum( $pageCounts['recent_edits'] )
);
// Recent number of distinct authors
$pageInfo['header-edits'][] = array(
- $this->msg( 'pageinfo-recent-authors' ), $lang->formatNum( $pageCounts['recent_authors'] )
+ $this->msg( 'pageinfo-recent-authors' ),
+ $lang->formatNum( $pageCounts['recent_authors'] )
);
// Array of MagicWord objects
$title = $page->getTitle();
$id = $title->getArticleID();
+ $dbr = wfGetDB( DB_SLAVE );
$dbrWatchlist = wfGetDB( DB_SLAVE, 'watchlist' );
+
+ $setOpts += Database::getCacheSetOptions( $dbr, $dbrWatchlist );
+
$result = array();
// Number of page watchers
array(
'wl_namespace' => $title->getNamespace(),
'wl_title' => $title->getDBkey(),
- 'wl_notificationtimestamp >= ' . $dbrWatchlist->addQuotes( $threshold ) .
- ' OR wl_notificationtimestamp IS NULL'
+ 'wl_notificationtimestamp >= ' .
+ $dbrWatchlist->addQuotes( $threshold ) .
+ ' OR wl_notificationtimestamp IS NULL'
),
$fname
);
$result['visitingWatchers'] = $visitingWatchers;
}
- $dbr = wfGetDB( DB_SLAVE );
// Total number of edits
$edits = (int)$dbr->selectField(
'revision',
$fname
);
- $setOpts = array( 'since' => $dbr->trxTimestamp() );
-
return $result;
},
86400 * 7
# "ThisSite user(s) A, B and C"
if ( count( $user_names ) ) {
- $user = $this->msg( 'siteusers' )->rawParams( $lang->listToText( $user_names ) )->params(
- count( $user_names ) )->escaped();
+ $user = $this->msg( 'siteusers' )
+ ->rawParams( $lang->listToText( $user_names ) )
+ ->params( count( $user_names ) )->escaped();
} else {
$user = false;
}
if ( count( $anon_ips ) ) {
- $anon = $this->msg( 'anonusers' )->rawParams( $lang->listToText( $anon_ips ) )->params(
- count( $anon_ips ) )->escaped();
+ $anon = $this->msg( 'anonusers' )
+ ->rawParams( $lang->listToText( $anon_ips ) )
+ ->params( count( $anon_ips ) )->escaped();
} else {
$anon = false;
}
/**
* @deprecated since 1.25, always returns empty string
- * @param DatabaseBase|bool $db
+ * @param IDatabase|bool $db
* @return string
*/
public function getModuleProfileName( $db = false ) {
* Note that the query result must include the columns returned by
* $this->getPageTableFields().
*
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param ResultWrapper $queryResult Query result object
*/
public function populateFromQueryResult( $db, $queryResult ) {
"apihelp-main-param-servedby": "Inclúa o nome do servidor que servía a solicitude nos resultados.",
"apihelp-main-param-curtimestamp": "Incluir a marca de tempo actual no resultado.",
"apihelp-main-param-origin": "Cando se accede á API usando unha petición AJAX entre-dominios (CORS), inicializar o parámetro co dominio orixe. Isto debe incluírse en calquera petición pre-flight, e polo tanto debe ser parte da petición URI (non do corpo POST). Debe coincidir exactamente cunha das orixes na cabeceira <code>Origin</code>, polo que ten que ser fixado a algo como <kbd>https://en.wikipedia.org</kbd> ou <kbd>https://meta.wikimedia.org</kbd>. Se este parámetro non coincide coa cabeceira <code>Origin</code>, devolverase unha resposta 403. Se este parámetro coincide coa cabeceira <code>Origin</code> e a orixe está na lista branca, porase unha cabeceira <code>Access-Control-Allow-Origin</code>.",
- "apihelp-main-param-uselang": "Linga a usar para a tradución de mensaxes. Pode consultarse unha lista de códigos en <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> con <kbd>siprop=languages</kbd>, ou especificando <kbd>user</kbd> coa preferencia de lingua do usuario actual, ou especificando <kbd>content</kbd> para usar a lingua do contido desta wiki.",
+ "apihelp-main-param-uselang": "Linga a usar para a tradución de mensaxes. <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo]]</kbd> con <kbd>siprop=languages</kbd> devolve unha lista de códigos de lingua, ou especificando <kbd>user</kbd> coa preferencia de lingua do usuario actual, ou especificando <kbd>content</kbd> para usar a lingua do contido desta wiki.",
"apihelp-block-description": "Bloquear un usuario.",
"apihelp-block-param-user": "Nome de usuario, dirección ou rango de IPs que quere bloquear.",
"apihelp-block-param-expiry": "Tempo de caducidade. Pode ser relativo (p. ex.<kbd>5 meses</kbd> ou <kbd>2 semanas</kbd>) ou absoluto (p. ex. 2014-09-18T12:34:56Z</kbd>). Se se pon kbd>infinite</kbd>, <kbd>indefinite</kbd>, ou <kbd>never</kbd>, o bloqueo nunca caducará.",
"apihelp-parse-param-pst": "Fai unha transformación antes de gardar a entrada antes de analizala. Válida unicamente para usar con texto.",
"apihelp-parse-param-onlypst": "Facer unha transformación antes de gardar (PST) a entrada, pero sen analizala. Devolve o mesmo wikitexto, despois de que a PST foi aplicada. Só válida cando se usa con <var>$1text</var>.",
"apihelp-parse-param-effectivelanglinks": "Inclúe ligazóns de idioma proporcionadas polas extensións (para usar con <kbd>$1prop=langlinks</kbd>).",
- "apihelp-parse-param-section": "Recuperar unicamente o contido deste número de sección ou cando <kbd>new</kbd> xera unha nova sección.\n\nA sección <kbd>new</kbd> só é atendida cando se especifica <var>text</var>.",
+ "apihelp-parse-param-section": "Analizar unicamente o contido deste número de sección.\n\nCando <kbd>nova</kbd>, analiza <var>$1text</var> e <var>$1sectiontitle</var> como se fose a engadir unha nova sección da páxina.\n\n<kbd>novo</kbd> só se permite cando especifica <var>text</var>.",
"apihelp-parse-param-sectiontitle": "Novo título de sección cando <var>section</var> é <kbd>new</kbd>.\n\nA diferenza da edición de páxinas, non se oculta no <var>summary</var> cando se omite ou está baleiro.",
"apihelp-parse-param-disablelimitreport": "Omitir o informe de límite (\"Informe de límite NewPP\") da saída do analizador.",
"apihelp-parse-param-disablepp": "Use <var>$1disablelimitreport</var> no seu lugar.",
"apihelp-query+pagepropnames-description": "Listar os nomes de todas as propiedades de páxina usados na wiki.",
"apihelp-query+pagepropnames-param-limit": "Máximo número de nomes a retornar.",
"apihelp-query+pagepropnames-example-simple": "Obter os dez primeiros nomes de propiedade.",
- "apihelp-query+pageprops-description": "Obter varias propiedades definidas no contido da páxina.",
- "apihelp-query+pageprops-param-prop": "Listar só esas propiedades. Útil para verificar se unha páxina concreta usa unha propiedade de páxina determinada.",
+ "apihelp-query+pageprops-description": "Obter varias propiedades de páxina definidas no contido da páxina.",
+ "apihelp-query+pageprops-param-prop": "Listar só estas propiedades de páxina (<kbd>[[Special:ApiHelp/query+pagepropnames|action=query&list=pagepropnames]]</kbd> devolve os nomes das propiedades de páxina usados). Útil para verificar se as páxinas usan unha determinada propiedade de páxina.",
"apihelp-query+pageprops-example-simple": "Obter as propiedades para as páxinas <kbd>Main Page</kbd> e <kbd>MediaWiki</kbd>",
"apihelp-query+pageswithprop-description": "Mostrar a lista de páxinas que empregan unha propiedade determinada.",
- "apihelp-query+pageswithprop-param-propname": "Propiedade de páxina pola que enumerar as páxinas.",
+ "apihelp-query+pageswithprop-param-propname": "Propiedade de páxina para a que enumerar as páxinas (<kbd>[[Special:ApiHelp/query+pagepropnames|action=query&list=pagepropnames]]</kbd> devolve os nomes das propiedades de páxina en uso).",
"apihelp-query+pageswithprop-param-prop": "Que información incluír:",
"apihelp-query+pageswithprop-paramvalue-prop-ids": "Engade o ID da páxina.",
"apihelp-query+pageswithprop-paramvalue-prop-title": "Engade o título e o ID do espazo de nomes da páxina.",
- "apihelp-query+pageswithprop-paramvalue-prop-value": "Engade o valor da propiedade da páxina.",
+ "apihelp-query+pageswithprop-paramvalue-prop-value": "Engade o valor da propiedade de páxina.",
"apihelp-query+pageswithprop-param-limit": "Máximo número de páxinas a retornar.",
"apihelp-query+pageswithprop-param-dir": "En que dirección ordenar.",
"apihelp-query+pageswithprop-example-simple": "Lista as dez primeiras páxinas que usan <code>{{DISPLAYTITLE:}}</code>.",
"apihelp-setnotificationtimestamp-example-page": "Restaurar o estado de notificación para a <kbd>Páxina Principal</kbd>.",
"apihelp-setnotificationtimestamp-example-pagetimestamp": "Fixar o selo de tempo de notificación para a <kbd>Main page</kbd> de forma que todas as edicións dende o 1 se xaneiro de 2012 queden sen revisar.",
"apihelp-setnotificationtimestamp-example-allpages": "Restaurar o estado de notificación para as páxinas no espazo de nomes de <kbd>{{ns:user}}</kbd>.",
+ "apihelp-stashedit-param-title": "Título da páxina que se está a editar.",
+ "apihelp-stashedit-param-section": "Número de selección. O <kbd>0</kbd> é para a sección superior, <kbd>novo</kbd> para unha sección nova.",
+ "apihelp-stashedit-param-sectiontitle": "Título para unha nova sección.",
"apihelp-stashedit-param-text": "Contido da páxina.",
"apihelp-stashedit-param-contentmodel": "Modelo de contido para o novo contido.",
"apihelp-stashedit-param-contentformat": "Formato de serialización de contido utilizado para o texto de entrada.",
"apihelp-query+allcategories-param-from": "列挙を開始するカテゴリ。",
"apihelp-query+allcategories-param-to": "列挙を終了するカテゴリ。",
"apihelp-query+allcategories-param-prefix": "この値で始まるタイトルのカテゴリを検索します。",
+ "apihelp-query+allcategories-param-dir": "並べ替えの方向。",
"apihelp-query+allcategories-param-limit": "返すカテゴリの数。",
+ "apihelp-query+allcategories-param-prop": "取得するプロパティ:",
"apihelp-query+allcategories-example-generator": "<kbd>List</kbd> で始まるカテゴリページに関する情報を取得する。",
"apihelp-query+alldeletedrevisions-paraminfo-nonuseronly": "<var>$3user</var> と同時に使用できません。",
"apihelp-query+alldeletedrevisions-param-start": "列挙の始点となるタイムスタンプ。",
"apihelp-query+allfileusages-param-from": "列挙を開始するファイルのタイトル。",
"apihelp-query+allfileusages-param-to": "列挙を終了するファイルのタイトル。",
"apihelp-query+allfileusages-param-prefix": "この値で始まるすべてのファイルのタイトルを検索する。",
+ "apihelp-query+allimages-param-sort": "並べ替えに使用するプロパティ。",
"apihelp-query+allimages-param-from": "列挙の始点となる画像タイトル。$1sort=name を指定した場合のみ使用できます。",
"apihelp-query+allimages-param-to": "列挙の終点となる画像タイトル。$1sort=name を指定した場合のみ使用できます。",
"apihelp-query+allimages-param-start": "列挙の始点となるタイムスタンプ。$1sort=timestamp を指定した場合のみ使用できます。",
"apihelp-query+alllinks-param-to": "列挙を終了するリンクのページ名。",
"apihelp-query+alllinks-param-prefix": "この値で始まるすべてのリンクされたページを検索する。",
"apihelp-query+alllinks-example-B": "<kbd>B</kbd> で始まるリンクされたページ (存在しないページも含む)を、リンク元のページIDとともに表示する。",
+ "apihelp-query+allmessages-param-prop": "取得するプロパティ:",
"apihelp-query+allmessages-param-args": "メッセージ中に展開される引数。",
"apihelp-query+allmessages-param-filter": "この文字列を含んだ名前のメッセージのみを返す。",
"apihelp-query+allmessages-param-customised": "変更された状態のメッセージのみを返す。",
"apihelp-query+allpages-param-to": "列挙を終了するページ名。",
"apihelp-query+allpages-param-prefix": "この値で始まるすべてのページ名を検索します。",
"apihelp-query+allpages-param-prtype": "保護されているページに絞り込む。",
+ "apihelp-query+allpages-param-prlevel": "保護レベルで絞り込む ($1type= パラメーターと同時に使用しなければなりません)。",
"apihelp-query+allpages-param-limit": "返すページの総数。",
"apihelp-query+allpages-example-B": "<kbd>B</kbd> で始まるページの一覧を表示する。",
"apihelp-query+allpages-example-generator": "<kbd>T</kbd> で始まる4つのページに関する情報を表示する。",
"apihelp-query+allusers-param-from": "列挙を開始する利用者名。",
"apihelp-query+allusers-param-to": "列挙を終了する利用者名。",
"apihelp-query+allusers-param-prefix": "この値で始まるすべての利用者を検索する。",
+ "apihelp-query+allusers-param-dir": "並べ替えの方向。",
"apihelp-query+allusers-param-group": "このグループに所属する利用者のみを結果に含める。",
"apihelp-query+allusers-param-excludegroup": "このグループに所属する利用者を結果から除外する。",
"apihelp-query+allusers-param-limit": "返す利用者名の総数。",
"apihelp-query+blocks-param-ids": "一覧表示するブロックIDのリスト (任意)。",
"apihelp-query+blocks-param-users": "検索対象の利用者のリスト (任意)。",
"apihelp-query+blocks-param-limit": "一覧表示するブロックの最大数。",
+ "apihelp-query+blocks-param-prop": "取得するプロパティ:",
"apihelp-query+blocks-example-simple": "ブロックを一覧表示する。",
"apihelp-query+blocks-example-users": "利用者<kbd>Alice</kbd> および <kbd>Bob</kbd> のブロックを一覧表示する。",
"apihelp-query+categories-param-limit": "返すカテゴリの数。",
"apihelp-query+categorymembers-param-title": "一覧表示するカテゴリ (必須)。<kbd>{{ns:category}}:</kbd> 接頭辞を含まなければなりません。<var>$1pageid</var> とは同時に使用できません。",
"apihelp-query+categorymembers-param-pageid": "一覧表示するカテゴリのページID. <var>$1title</var> とは同時に使用できません。",
"apihelp-query+categorymembers-param-limit": "返すページの最大数。",
+ "apihelp-query+categorymembers-param-sort": "並べ替えに使用するプロパティ。",
"apihelp-query+categorymembers-param-start": "列挙の始点となるタイムスタンプ。<kbd>$1sort=timestamp</kbd>を指定した場合のみ使用できます。",
"apihelp-query+categorymembers-param-end": "列挙の終点となるタイムスタンプ。<kbd>$1sort=timestamp</kbd>を指定した場合のみ使用できます。",
"apihelp-query+categorymembers-param-startsortkeyprefix": "列挙の始点となるソートキーの接頭辞。<kbd>$1sort=sortkey</kbd>を指定した場合のみ使用できます。<var>$1starthexsortkey</var>をオーバーライドします。",
"apihelp-query+exturlusage-description": "与えられたURLを含むページを一覧表示します。",
"apihelp-query+exturlusage-example-simple": "<kbd>http://www.mediawiki.org</kbd> にリンクしているページを一覧表示する。",
"apihelp-query+filearchive-example-simple": "削除されたファイルの一覧を表示する。",
+ "apihelp-query+fileusage-param-prop": "取得するプロパティ:",
"apihelp-query+fileusage-example-simple": "[[:File:Example.jpg]] を使用しているページの一覧を取得する。",
"apihelp-query+fileusage-example-generator": "[[:File:Example.jpg]] を使用しているページの情報を取得する。",
+ "apihelp-query+imageinfo-param-prop": "取得するファイル情報:",
"apihelp-query+imageinfo-param-start": "一覧表示の始点となるタイムスタンプ。",
"apihelp-query+imageinfo-param-end": "一覧表示の終点となるタイムスタンプ。",
"apihelp-query+images-description": "与えられたページに含まれるすべてのファイルを返します。",
"apihelp-query+imageusage-example-simple": "[[:File:Albert Einstein Head.jpg]] を使用しているページを表示する。",
"apihelp-query+imageusage-example-generator": "[[:File:Albert Einstein Head.jpg]] を使用しているページに関する情報を取得する。",
"apihelp-query+info-description": "ページの基本的な情報を取得します。",
+ "apihelp-query+info-param-prop": "追加で取得するプロパティ:",
"apihelp-query+info-paramvalue-prop-protection": "それぞれのページの保護レベルを一覧表示する。",
"apihelp-query+info-example-simple": "<kbd>Main Page</kbd> に関する情報を取得する。",
+ "apihelp-query+iwbacklinks-param-prop": "取得するプロパティ:",
"apihelp-query+iwbacklinks-example-simple": "[[wikibooks:Test]] へリンクしているページを取得する。",
"apihelp-query+iwbacklinks-example-generator": "[[wikibooks:Test]] へリンクしているページの情報を取得する。",
"apihelp-query+iwlinks-param-limit": "返すウィキ間リンクの数。",
"apihelp-query+langbacklinks-param-lang": "言語間リンクの言語。",
"apihelp-query+langbacklinks-param-title": "検索する言語間リンク。$1lang と同時に使用しなければなりません。",
"apihelp-query+langbacklinks-param-limit": "返すページの総数。",
+ "apihelp-query+langbacklinks-param-prop": "取得するプロパティ:",
"apihelp-query+langbacklinks-example-simple": "[[:fr:Test]] へリンクしているページを取得する。",
"apihelp-query+langbacklinks-example-generator": "[[:fr:Test]] へリンクしているページの情報を取得する。",
"apihelp-query+langlinks-param-limit": "返す言語間リンクの数。",
"apihelp-query+links-example-simple": "<kbd>Main Page</kbd> からのリンクを取得する。",
"apihelp-query+links-example-generator": "<kbd>Main Page</kbd> からリンクされているページに関する情報を取得する。",
"apihelp-query+links-example-namespaces": "<kbd>Main Page</kbd> からの {{ns:user}} および {{ns:template}} 名前空間へのリンクを取得する。",
+ "apihelp-query+linkshere-param-prop": "取得するプロパティ:",
"apihelp-query+linkshere-example-simple": "[[Main Page]] にリンクしているページの一覧を取得する。",
"apihelp-query+linkshere-example-generator": "<kbd>[[Main Page]]<kbd> にリンクしているページの情報を取得する。",
+ "apihelp-query+logevents-param-prop": "取得するプロパティ:",
"apihelp-query+logevents-param-start": "列挙の始点となるタイムスタンプ。",
"apihelp-query+logevents-param-end": "列挙の終点となるタイムスタンプ。",
"apihelp-query+logevents-param-user": "与えられた利用者による記録項目に絞り込む。",
"apihelp-query+protectedtitles-param-limit": "返すページの総数。",
"apihelp-query+protectedtitles-param-start": "一覧表示の始点となる保護タイムスタンプ。",
"apihelp-query+protectedtitles-param-end": "一覧表示の終点となる保護タイムスタンプ。",
+ "apihelp-query+protectedtitles-param-prop": "取得するプロパティ:",
"apihelp-query+protectedtitles-example-simple": "保護されているページを一覧表示する。",
"apihelp-query+protectedtitles-example-generator": "標準名前空間にある保護されたページへのリンクを検索する。",
"apihelp-query+querypage-param-page": "特別ページの名前です。これは大文字小文字を区別することに注意。",
"apihelp-query+recentchanges-param-toponly": "最新の版である変更のみを一覧表示する。",
"apihelp-query+recentchanges-example-simple": "最近の更新を一覧表示する。",
"apihelp-query+redirects-description": "ページへのすべての転送を返します。",
+ "apihelp-query+redirects-param-prop": "取得するプロパティ:",
"apihelp-query+redirects-param-limit": "返す転送の数。",
"apihelp-query+redirects-example-simple": "[[Main Page]] への転送の一覧を取得する。",
"apihelp-query+redirects-example-generator": "[[Main Page]] へのすべての転送ページに関する情報を取得する。",
"apihelp-query+search-param-search": "この値を含むページ名または本文を検索します。Wikiの検索バックエンド実装に応じて、あなたは特別な検索機能を呼び出すための文字列を検索することができます。",
"apihelp-query+search-param-namespace": "この名前空間内のみを検索します。",
"apihelp-query+search-param-what": "実行する検索の種類です。",
+ "apihelp-query+search-param-prop": "返すプロパティ:",
"apihelp-query+search-param-limit": "返すページの総数です。",
"apihelp-query+search-example-simple": "<kbd>meaning</kbd> を検索する。",
"apihelp-query+search-example-generator": "<kbd>meaning</kbd> の検索で返されたページのページ情報を取得する。",
"apihelp-query+tags-description": "変更タグを一覧表示します。",
"apihelp-query+tags-param-limit": "一覧表示するタグの最大数。",
+ "apihelp-query+tags-param-prop": "取得するプロパティ:",
"apihelp-query+tags-example-simple": "利用可能なタグを一覧表示する。",
"apihelp-query+templates-description": "与えられたページでトランスクルードされているすべてのページを返します。",
"apihelp-query+templates-param-namespace": "この名前空間のテンプレートのみ表示する。",
"apihelp-query+tokens-example-simple": "csrfトークンを取得する (既定)。",
"apihelp-query+tokens-example-types": "ウォッチトークンおよび巡回トークンを取得する。",
"apihelp-query+transcludedin-description": "与えられたページをトランスクルードしているすべてのページを検索します。",
+ "apihelp-query+transcludedin-param-prop": "取得するプロパティ:",
"apihelp-query+transcludedin-example-simple": "<kbd>Main Page</kbd> をトランスクルードしているページの一覧を取得する。",
"apihelp-query+transcludedin-example-generator": "<kbd>Main Page</kbd> をトランスクルードしているページに関する情報を取得する。",
"apihelp-query+usercontribs-description": "利用者によるすべての編集を取得します。",
"apihelp-query+watchlist-param-namespace": "この名前空間の変更のみに絞り込む。",
"apihelp-query+watchlist-param-user": "この利用者による変更のみを一覧表示する。",
"apihelp-query+watchlist-param-excludeuser": "この利用者による変更を一覧表示しない。",
+ "apihelp-query+watchlist-param-prop": "追加で取得するプロパティ:",
"apihelp-query+watchlistraw-description": "現在の利用者のウォッチリストにあるすべてのページを取得します。",
"apihelp-query+watchlistraw-param-namespace": "この名前空間に含まれるページのみを一覧表示します。",
+ "apihelp-query+watchlistraw-param-prop": "追加で取得するプロパティ:",
"apihelp-revisiondelete-description": "版の削除および復元を行います。",
"apihelp-revisiondelete-param-reason": "削除または復元の理由。",
"apihelp-revisiondelete-example-revision": "<kbd>Main Page</kbd> の版 <kbd>12345</kbd> の本文を隠す。",
"apihelp-query+pageprops-example-simple": "Holl de Eijeschaffte för di Sigge „<kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">Main Page</kbd>“ un „<kbd lang=\"en\" xml:lang=\"en\" dir=\"ltr\">MediaWiki</kbd>“.",
"apihelp-query+pageswithprop-description": "Donn alle Sigge met bechtemmpte Sigge_Eijeschaff opleßte.",
"apihelp-query+pageswithprop-paramvalue-prop-ids": "Deiht de Kännong vun de Sigge derbei.",
+ "apihelp-query+pageswithprop-paramvalue-prop-value": "Deiht der Wäät för de Eijeschaff vun dä Sigg derbei.",
"apihelp-query+pageswithprop-param-limit": "De jrüüßte Zahl Sigge för ußzejävve.",
"apihelp-query+pageswithprop-param-dir": "En wälsche Reihjefollsch opleßte.",
"apihelp-query+pageswithprop-example-generator": "Holl zohsäzlejje Aanjahbe övver de eezde zehn Sigge, woh <code>__NOTOC__</code> dren vörkütt.",
"apihelp-parse-example-text": "Wikitext parseren.",
"apihelp-parse-example-summary": "Een samenvatting parseren.",
"apihelp-protect-example-protect": "Een pagina beveiligen",
+ "apihelp-stashedit-param-text": "Pagina-inhoud.",
"api-help-flag-readrights": "Voor deze module zijn leesrechten nodig.",
"api-help-flag-writerights": "Voor deze module zijn schrijfrechten nodig.",
"api-help-parameters": "{{PLURAL:$1|Parameter|Parameters}}:",
"apihelp-query+alltransclusions-param-namespace": "Przestrzeń nazw do emulacji.",
"apihelp-query+allusers-param-witheditsonly": "Tylko użytkownicy, którzy edytowali.",
"apihelp-query+backlinks-param-namespace": "Przestrzeń nazw do emulacji.",
+ "apihelp-query+backlinks-example-simple": "Pokazuj linki do <kbd>Main page</kbd>.",
"apihelp-query+blocks-param-ids": "Lista zablokowanych ID do wylistowania (opcjonalne).",
"apihelp-query+blocks-param-users": "Lista użytkowników do wyszukania (opcjonalne).",
"apihelp-query+blocks-param-limit": "Maksymalna liczba blokad do wylistowania.",
/**
* Set the Database object to use
*
- * @param DatabaseBase $db
+ * @param IDatabase $db
*/
public function setDB( $db ) {
$this->db = $db;
* Construct a WHERE clause which will match all the given titles.
*
* @param string $prefix The appropriate table's field name prefix ('page', 'pl', etc)
- * @param DatabaseBase $db DatabaseBase object to use
+ * @param IDatabase $db DatabaseBase object to use
* @return string|bool String with SQL where clause fragment, or false if no items.
*/
public function constructSet( $prefix, $db ) {
public static function listExtensionActivatedTags() {
return ObjectCache::getMainWANInstance()->getWithSetCallback(
wfMemcKey( 'active-tags' ),
- function() {
+ function ( $oldValue, &$ttl, array &$setOpts ) {
+ $setOpts += Database::getCacheSetOptions( wfGetDB( DB_SLAVE ) );
+
// Ask extensions which tags they consider active
$extensionActive = array();
Hooks::run( 'ChangeTagsListActive', array( &$extensionActive ) );
return ObjectCache::getMainWANInstance()->getWithSetCallback(
wfMemcKey( 'valid-tags-db' ),
- function() use ( $fname ) {
+ function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
$dbr = wfGetDB( DB_SLAVE );
- $tags = $dbr->selectFieldValues(
- 'valid_tag', 'vt_tag', array(), $fname );
+
+ $setOpts += Database::getCacheSetOptions( $dbr );
+
+ $tags = $dbr->selectFieldValues( 'valid_tag', 'vt_tag', array(), $fname );
return array_filter( array_unique( $tags ) );
},
public static function listExtensionDefinedTags() {
return ObjectCache::getMainWANInstance()->getWithSetCallback(
wfMemcKey( 'valid-tags-hook' ),
- function() {
+ function ( $oldValue, &$ttl, array &$setOpts ) {
+ $setOpts += Database::getCacheSetOptions( wfGetDB( DB_SLAVE ) );
+
$tags = array();
Hooks::run( 'ListDefinedTags', array( &$tags ) );
return array_filter( array_unique( $tags ) );
$fname = __METHOD__;
$cachedStats = ObjectCache::getMainWANInstance()->getWithSetCallback(
wfMemcKey( 'change-tag-statistics' ),
- function() use ( $fname ) {
- $out = array();
-
+ function ( $oldValue, &$ttl, array &$setOpts ) use ( $fname ) {
$dbr = wfGetDB( DB_SLAVE, 'vslow' );
+
+ $setOpts += Database::getCacheSetOptions( $dbr );
+
$res = $dbr->select(
'change_tag',
array( 'ct_tag', 'hitcount' => 'count(*)' ),
array( 'GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC' )
);
+ $out = array();
foreach ( $res as $row ) {
$out[$row->ct_tag] = $row->hitcount;
}
}
/**
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @return mixed
*/
public function doQuery( $db ) {
}
/**
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @return mixed
*/
public function doQuery( $db ) {
return $this->__call( __FUNCTION__, func_get_args() );
}
+ public function getSessionLagStatus() {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
public function maxListLen() {
return $this->__call( __FUNCTION__, func_get_args() );
}
protected $mServer, $mUser, $mPassword, $mDBname;
+ /** @var BagOStuff APC cache */
+ protected $srvCache;
+
/** @var resource Database connection */
protected $mConn = null;
protected $mOpened = false;
*/
private $mTrxTimestamp = null;
+ /** @var float Lag estimate at the time of BEGIN */
+ private $mTrxSlaveLag = null;
+
/**
* Remembers the function name given for starting the most recent transaction via begin().
* Used to provide additional context for error reporting.
* - DBO_PERSISTENT: use persistant database connection
*/
public function setFlag( $flag ) {
- global $wgDebugDBTransactions;
$this->mFlags |= $flag;
- if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) {
- wfDebug( "Implicit transactions are now enabled.\n" );
- }
}
/**
* - DBO_PERSISTENT: use persistant database connection
*/
public function clearFlag( $flag ) {
- global $wgDebugDBTransactions;
$this->mFlags &= ~$flag;
- if ( ( $flag & DBO_TRX ) && $wgDebugDBTransactions ) {
- wfDebug( "Implicit transactions are now disabled.\n" );
- }
}
/**
* @param array $params Parameters passed from DatabaseBase::factory()
*/
function __construct( array $params ) {
- global $wgDBprefix, $wgDBmwschema, $wgCommandLineMode, $wgDebugDBTransactions;
+ global $wgDBprefix, $wgDBmwschema, $wgCommandLineMode;
+
+ $this->mTrxAtomicLevels = new SplStack;
+ $this->srvCache = ObjectCache::newAccelerator( 'hash' );
$server = $params['host'];
$user = $params['user'];
if ( $this->mFlags & DBO_DEFAULT ) {
if ( $wgCommandLineMode ) {
$this->mFlags &= ~DBO_TRX;
- if ( $wgDebugDBTransactions ) {
- wfDebug( "Implicit transaction open disabled.\n" );
- }
} else {
$this->mFlags |= DBO_TRX;
- if ( $wgDebugDBTransactions ) {
- wfDebug( "Implicit transaction open enabled.\n" );
- }
}
}
* for a successful read query, or false on failure if $tempIgnore set
*/
public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
- global $wgUser, $wgDebugDBTransactions, $wgDebugDumpSqlLength;
+ global $wgUser;
$this->mLastQuery = $sql;
$isWriteQuery = $this->isWriteQuery( $sql );
if ( $isWriteQuery ) {
- if ( !$this->mDoneWrites ) {
- wfDebug( __METHOD__ . ': Writes done: ' .
- DatabaseBase::generalizeSQL( $sql ) . "\n" );
+ $reason = $this->getLBInfo( 'readOnlyReason' );
+ if ( is_string( $reason ) ) {
+ throw new DBReadOnlyError( $this, "Database is read-only: $reason" );
}
# Set a flag indicating that writes have been done
$this->mDoneWrites = microtime( true );
$commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 );
if ( !$this->mTrxLevel && $this->getFlag( DBO_TRX ) && $this->isTransactableQuery( $sql ) ) {
- if ( $wgDebugDBTransactions ) {
- wfDebug( "Implicit transaction start.\n" );
- }
$this->begin( __METHOD__ . " ($fname)" );
$this->mTrxAutomatic = true;
}
}
if ( $this->debug() ) {
- static $cnt = 0;
-
- $cnt++;
- $sqlx = $wgDebugDumpSqlLength ? substr( $commentedSql, 0, $wgDebugDumpSqlLength )
- : $commentedSql;
- $sqlx = strtr( $sqlx, "\t\n", ' ' );
-
- $master = $isMaster ? 'master' : 'slave';
- wfDebug( "Query {$this->mDBname} ($cnt) ($master): $sqlx\n" );
+ wfDebugLog( 'queries', sprintf( "%s: %s", $this->mDBname, $sql ) );
}
$queryId = MWDebug::query( $sql, $fname, $isMaster );
*
* @return string
*/
- static function generalizeSQL( $sql ) {
+ protected static function generalizeSQL( $sql ) {
# This does the same as the regexp below would do, but in such a way
# as to avoid crashing php on some large strings.
# $sql = preg_replace( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql );
* @throws DBError
*/
final public function begin( $fname = __METHOD__ ) {
- global $wgDebugDBTransactions;
-
if ( $this->mTrxLevel ) { // implicit commit
if ( $this->mTrxAtomicLevels ) {
// If the current transaction was an automatic atomic one, then we definitely have
) )
);
} else {
- // if the transaction was automatic and has done write operations,
- // log it if $wgDebugDBTransactions is enabled.
- if ( $this->mTrxDoneWrites && $wgDebugDBTransactions ) {
+ // if the transaction was automatic and has done write operations
+ if ( $this->mTrxDoneWrites ) {
wfDebug( "$fname: Automatic transaction with writes in progress" .
" (from {$this->mTrxFname}), performing implicit commit!\n"
);
$this->mTrxPreCommitCallbacks = array();
$this->mTrxShortId = wfRandomString( 12 );
$this->mTrxWriteDuration = 0.0;
+ // First SELECT after BEGIN will establish the snapshot in REPEATABLE-READ.
+ // Get an estimate of the slave lag before then, treating estimate staleness
+ // as lag itself just to be safe
+ $status = $this->getApproximateLagStatus();
+ $this->mTrxSlaveLag = $status['lag'] + ( microtime( true ) - $status['since'] );
}
/**
return true;
}
+ /**
+ * Get the slave lag when the current transaction started
+ * or a general lag estimate if not transaction is active
+ *
+ * This is useful when transactions might use snapshot isolation
+ * (e.g. REPEATABLE-READ in innodb), so the "real" lag of that data
+ * is this lag plus transaction duration. If they don't, it is still
+ * safe to be pessimistic. In AUTO-COMMIT mode, this still gives an
+ * indication of the staleness of subsequent reads.
+ *
+ * @return array ('lag': seconds, 'since': UNIX timestamp of BEGIN)
+ * @since 1.27
+ */
+ public function getSessionLagStatus() {
+ return $this->getTransactionLagStatus() ?: $this->getApproximateLagStatus();
+ }
+
+ /**
+ * Get the slave lag when the current transaction started
+ *
+ * This is useful when transactions might use snapshot isolation
+ * (e.g. REPEATABLE-READ in innodb), so the "real" lag of that data
+ * is this lag plus transaction duration. If they don't, it is still
+ * safe to be pessimistic. This returns null if there is no transaction.
+ *
+ * @return array|null ('lag': seconds, 'since': UNIX timestamp of BEGIN)
+ * @since 1.27
+ */
+ public function getTransactionLagStatus() {
+ return $this->mTrxLevel
+ ? array( 'lag' => $this->mTrxSlaveLag, 'since' => $this->trxTimestamp() )
+ : null;
+ }
+
+ /**
+ * Get a slave lag estimate for this server
+ *
+ * @return array ('lag': seconds, 'since': UNIX timestamp of estimate)
+ * @since 1.27
+ */
+ public function getApproximateLagStatus() {
+ return array(
+ 'lag' => $this->getLBInfo( 'slave' ) ? $this->getLag() : 0,
+ 'since' => microtime( true )
+ );
+ }
+
+ /**
+ * Merge the result of getSessionLagStatus() for several DBs
+ * using the most pessimistic values to estimate the lag of
+ * any data derived from them in combination
+ *
+ * This is information is useful for caching modules
+ *
+ * @see WANObjectCache::set()
+ * @see WANObjectCache::getWithSetCallback()
+ *
+ * @param IDatabase $db1
+ * @param IDatabase ...
+ * @return array ('lag': highest lag, 'since': lowest estimate UNIX timestamp)
+ * @since 1.27
+ */
+ public static function getCacheSetOptions( IDatabase $db1 ) {
+ $res = array( 'lag' => 0, 'since' => INF );
+ foreach ( func_get_args() as $db ) {
+ /** @var IDatabase $db */
+ $status = $db->getSessionLagStatus();
+ $res['lag'] = max( $res['lag'], $status['lag'] );
+ $res['since'] = min( $res['since'], $status['since'] );
+ }
+
+ return $res;
+ }
+
/**
* Get slave lag. Currently supported only by MySQL.
*
}
}
}
+
+/**
+ * @since 1.27
+ */
+abstract class Database extends DatabaseBase {
+ // B/C until nothing type hints for DatabaseBase
+ // @TODO: finish renaming DatabaseBase => Database
+}
*/
class DBUnexpectedError extends DBError {
}
+
+/**
+ * @ingroup Database
+ */
+class DBReadOnlyError extends DBError {
+}
/**
* @ingroup Database
*/
-class DatabaseMssql extends DatabaseBase {
+class DatabaseMssql extends Database {
protected $mInsertId = null;
protected $mLastResult = null;
protected $mAffectedRows = null;
* @since 1.22
* @see Database
*/
-abstract class DatabaseMysqlBase extends DatabaseBase {
+abstract class DatabaseMysqlBase extends Database {
/** @var MysqlMasterPos */
protected $lastKnownSlavePos;
/** @var string Method to detect slave lag */
protected $lagDetectionMethod;
- /** @var BagOStuff APC cache */
- protected $srvCache;
-
/** @var string|null */
private $serverVersion = null;
$this->lagDetectionMethod = isset( $params['lagDetectionMethod'] )
? $params['lagDetectionMethod']
: 'Seconds_Behind_Master';
-
- $this->srvCache = ObjectCache::newAccelerator( 'hash' );
}
/**
return $sQuoted;
}
+ /**
+ * @param string $s
+ * @return mixed
+ */
+ abstract protected function mysqlRealEscapeString( $s );
+
/**
* MySQL uses `backticks` for identifier quoting instead of the sql standard "double quotes".
*
return false;
}
+ public function getApproximateLagStatus() {
+ if ( $this->lagDetectionMethod === 'pt-heartbeat' ) {
+ // Disable caching since this is fast enough and we don't wan't
+ // to be *too* pessimistic by having both the cache TTL and the
+ // pt-heartbeat interval count as lag in getSessionLagStatus()
+ return parent::getApproximateLagStatus();
+ }
+
+ $key = wfGlobalCacheKey( 'mysql-lag', $this->getServer() );
+ $approxLag = $this->srvCache->get( $key );
+ if ( !$approxLag ) {
+ $approxLag = parent::getApproximateLagStatus();
+ $this->srvCache->set( $key, $approxLag, 1 );
+ }
+
+ return $approxLag;
+ }
+
/**
* Wait for the slave to catch up to a given master position.
* @todo Return values for this and base class are rubbish
* @return string
*/
function tableName() {
- return $this->tableName;
+ return $this->tablename;
}
/**
/**
* @ingroup Database
*/
-class DatabaseOracle extends DatabaseBase {
+class DatabaseOracle extends Database {
/** @var resource */
protected $mLastResult = null;
}
}
-/**
- * Used to debug transaction processing
- * Only used if $wgDebugDBTransactions is true
- *
- * @since 1.19
- * @ingroup Database
- */
-class PostgresTransactionState {
- private static $WATCHED = array(
- array(
- "desc" => "%s: Connection state changed from %s -> %s\n",
- "states" => array(
- PGSQL_CONNECTION_OK => "OK",
- PGSQL_CONNECTION_BAD => "BAD"
- )
- ),
- array(
- "desc" => "%s: Transaction state changed from %s -> %s\n",
- "states" => array(
- PGSQL_TRANSACTION_IDLE => "IDLE",
- PGSQL_TRANSACTION_ACTIVE => "ACTIVE",
- PGSQL_TRANSACTION_INTRANS => "TRANS",
- PGSQL_TRANSACTION_INERROR => "ERROR",
- PGSQL_TRANSACTION_UNKNOWN => "UNKNOWN"
- )
- )
- );
-
- /** @var array */
- private $mNewState;
-
- /** @var array */
- private $mCurrentState;
-
- public function __construct( $conn ) {
- $this->mConn = $conn;
- $this->update();
- $this->mCurrentState = $this->mNewState;
- }
-
- public function update() {
- $this->mNewState = array(
- pg_connection_status( $this->mConn ),
- pg_transaction_status( $this->mConn )
- );
- }
-
- public function check() {
- global $wgDebugDBTransactions;
- $this->update();
- if ( $wgDebugDBTransactions ) {
- if ( $this->mCurrentState !== $this->mNewState ) {
- $old = reset( $this->mCurrentState );
- $new = reset( $this->mNewState );
- foreach ( self::$WATCHED as $watched ) {
- if ( $old !== $new ) {
- $this->log_changed( $old, $new, $watched );
- }
- $old = next( $this->mCurrentState );
- $new = next( $this->mNewState );
- }
- }
- }
- $this->mCurrentState = $this->mNewState;
- }
-
- protected function describe_changed( $status, $desc_table ) {
- if ( isset( $desc_table[$status] ) ) {
- return $desc_table[$status];
- } else {
- return "STATUS " . $status;
- }
- }
-
- protected function log_changed( $old, $new, $watched ) {
- wfDebug( sprintf( $watched["desc"],
- $this->mConn,
- $this->describe_changed( $old, $watched["states"] ),
- $this->describe_changed( $new, $watched["states"] )
- ) );
- }
-}
-
/**
* Manage savepoints within a transaction
* @ingroup Database
}
protected function query( $keyword, $msg_ok, $msg_failed ) {
- global $wgDebugDBTransactions;
if ( $this->dbw->doQuery( $keyword . " " . $this->id ) !== false ) {
- if ( $wgDebugDBTransactions ) {
- wfDebug( sprintf( $msg_ok, $this->id ) );
- }
} else {
wfDebug( sprintf( $msg_failed, $this->id ) );
}
/**
* @ingroup Database
*/
-class DatabasePostgres extends DatabaseBase {
+class DatabasePostgres extends Database {
/** @var resource */
protected $mLastResult = null;
/** @var string Connect string to open a PostgreSQL connection */
private $connectString;
- /** @var PostgresTransactionState */
- private $mTransactionState;
-
/** @var string */
private $mCoreSchema;
}
$this->mOpened = true;
- $this->mTransactionState = new PostgresTransactionState( $this->mConn );
global $wgCommandLineMode;
# If called from the command-line (e.g. importDump), only show errors
if ( function_exists( 'mb_convert_encoding' ) ) {
$sql = mb_convert_encoding( $sql, 'UTF-8' );
}
- $this->mTransactionState->check();
if ( pg_send_query( $this->mConn, $sql ) === false ) {
throw new DBUnexpectedError( $this, "Unable to post new query to PostgreSQL\n" );
}
$this->mLastResult = pg_get_result( $this->mConn );
- $this->mTransactionState->check();
$this->mAffectedRows = null;
if ( pg_result_error( $this->mLastResult ) ) {
return false;
/**
* @ingroup Database
*/
-class DatabaseSqlite extends DatabaseBase {
+class DatabaseSqlite extends Database {
/** @var bool Whether full text is enabled */
private static $fulltextEnabled = null;
return $endArray;
}
+
+ /**
+ * @return string
+ */
+ public function __toString() {
+ return 'SQLite ' . (string)$this->mConn->getAttribute( PDO::ATTR_SERVER_VERSION );
+ }
+
} // end DatabaseSqlite class
/**
*/
public function getLag();
+ /**
+ * Get the slave lag when the current transaction started
+ * or a general lag estimate if not transaction is active
+ *
+ * This is useful when transactions might use snapshot isolation
+ * (e.g. REPEATABLE-READ in innodb), so the "real" lag of that data
+ * is this lag plus transaction duration. If they don't, it is still
+ * safe to be pessimistic. In AUTO-COMMIT mode, this still gives an
+ * indication of the staleness of subsequent reads.
+ *
+ * @return array ('lag': seconds, 'since': UNIX timestamp of BEGIN)
+ * @since 1.27
+ */
+ public function getSessionLagStatus();
+
/**
* Return the maximum number of items allowed in a list, or 0 for unlimited.
*
$this->forEachLB( function ( LoadBalancer $lb ) use ( &$ret ) {
$ret = $ret || $lb->hasMasterChanges();
} );
+
+ return $ret;
+ }
+
+ /**
+ * Detemine if any lagged slave connection was used
+ * @since 1.27
+ * @return bool
+ */
+ public function laggedSlaveUsed() {
+ $ret = false;
+ $this->forEachLB( function ( LoadBalancer $lb ) use ( &$ret ) {
+ $ret = $ret || $lb->laggedSlaveUsed();
+ } );
+
return $ret;
}
$this->$key = $conf[$key];
}
}
-
- // Check for read-only mode
- $section = $this->getSectionForWiki();
- if ( !empty( $this->readOnlyBySection[$section] ) ) {
- global $wgReadOnly;
- $wgReadOnly = $this->readOnlyBySection[$section];
- }
}
/**
$trxProf->recordConnection( $host, $dbname, $masterOnly );
}
+ # Make master connections read only if in lagged slave mode
+ if ( $masterOnly && $this->getServerCount() > 1 && $this->getLaggedSlaveMode() ) {
+ $conn->setLBInfo( 'readOnlyReason',
+ 'The database has been automatically locked ' .
+ 'while the slave database servers catch up to the master'
+ );
+ }
+
return $conn;
}
}
/**
+ * @note This method will trigger a DB connection if not yet done
* @return bool Whether the generic connection for reads is highly "lagged"
*/
public function getLaggedSlaveMode() {
return $this->mLaggedSlaveMode;
}
+ /**
+ * @note This method will never cause a new DB connection
+ * @return bool Whether any generic connection used for reads was highly "lagged"
+ * @since 1.27
+ */
+ public function laggedSlaveUsed() {
+ return $this->mLaggedSlaveMode;
+ }
+
/**
* Disables/enables lag checks
* @param null|bool $mode
}
/**
- * @param DatabaseBase $dbw
+ * @param IDatabase $dbw
* @return bool|mixed
*/
public static function cacheUpdate( $dbw ) {
* Get a slave database connection for the specified cluster
*
* @param string $cluster Cluster name
- * @return DatabaseBase
+ * @return IDatabase
*/
function getSlave( $cluster ) {
global $wgDefaultExternalStore;
* Get a master database connection for the specified cluster
*
* @param string $cluster Cluster name
- * @return DatabaseBase
+ * @return IDatabase
*/
function getMaster( $cluster ) {
$wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
/**
* Get the 'blobs' table name for this database
*
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @return string Table name ('blobs' by default)
*/
function getTable( $db ) {
* Helper function for self::batchFetchBlobs for merging master/slave results
* @param array &$ret Current self::batchFetchBlobs return value
* @param array &$ids Map from blob_id to requested itemIDs
- * @param mixed $res DB result from DatabaseBase::select
+ * @param mixed $res DB result from Database::select
*/
private function mergeBatchResult( array &$ret, array &$ids, $res ) {
foreach ( $res as $row ) {
protected function initFromGlobals() {
global $wgLocalFileRepo, $wgForeignFileRepos, $wgFileBackends;
+ // Register explicitly defined backends
+ $this->register( $wgFileBackends, wfConfiguredReadOnlyReason() );
+
$autoBackends = array();
// Automatically create b/c backends for file repos...
$repos = array_merge( $wgForeignFileRepos, array( $wgLocalFileRepo ) );
);
}
- $backends = array_merge( $autoBackends, $wgFileBackends );
-
- // Apply $wgReadOnly to all backends if not already read-only
- foreach ( $backends as &$backend ) {
- $backend['readOnly'] = !empty( $backend['readOnly'] )
- ? $backend['readOnly']
- : wfConfiguredReadOnlyReason();
- }
-
- $this->register( $backends );
+ // Register implicitly defined backends
+ $this->register( $autoBackends, wfConfiguredReadOnlyReason() );
}
/**
* Register an array of file backend configurations
*
* @param array $configs
+ * @param string|null $readOnlyReason
* @throws FileBackendException
*/
- protected function register( array $configs ) {
+ protected function register( array $configs, $readOnlyReason = null ) {
foreach ( $configs as $config ) {
if ( !isset( $config['name'] ) ) {
throw new FileBackendException( "Cannot register a backend with no name." );
}
$class = $config['class'];
+ $config['readOnly'] = !empty( $config['readOnly'] )
+ ? $config['readOnly']
+ : $readOnlyReason;
+
unset( $config['class'] ); // backend won't need this
$this->backends[$name] = array(
'class' => $class,
* @since 1.20
*/
class DBFileJournal extends FileJournal {
- /** @var DatabaseBase */
+ /** @var IDatabase */
protected $dbw;
protected $wiki = false; // string; wiki DB name
/**
* Get a master connection to the logging DB
*
- * @return DatabaseBase
+ * @return IDatabase
* @throws DBError
*/
protected function getMasterDB() {
* Get (or reuse) a connection to a lock DB
*
* @param string $lockDb
- * @return DatabaseBase
+ * @return IDatabase
* @throws DBError
*/
protected function getConnection( $lockDb ) {
* Do additional initialization for new lock DB connection
*
* @param string $lockDb
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @throws DBError
*/
- protected function initConnection( $lockDb, DatabaseBase $db ) {
+ protected function initConnection( $lockDb, IDatabase $db ) {
}
/**
/**
* @param string $lockDb
- * @param DatabaseBase $db
+ * @param IDatabase $db
*/
- protected function initConnection( $lockDb, DatabaseBase $db ) {
+ protected function initConnection( $lockDb, IDatabase $db ) {
# Let this transaction see lock rows from other transactions
$db->query( "SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;" );
}
}
/**
- * @return DatabaseBase
+ * @return IDatabase
*/
function getMasterDB() {
if ( !isset( $this->dbConn ) ) {
}
/**
- * @return DatabaseBase
+ * @return IDatabase
*/
function getSlaveDB() {
return $this->getMasterDB();
}
/**
- * @return DatabaseBase
+ * @return IDatabase
*/
function getMasterDB() {
return wfGetDB( DB_MASTER, array(), $this->wiki );
}
/**
- * @return DatabaseBase
+ * @return IDatabase
*/
function getSlaveDB() {
return wfGetDB( DB_SLAVE, array(), $this->wiki );
function ( $oldValue, &$ttl, array &$setOpts ) use ( $that, $title ) {
$dbr = $that->getSlaveDB(); // possibly remote DB
- $setOpts = array( 'since' => $dbr->trxTimestamp() );
+ $setOpts += Database::getCacheSetOptions( $dbr );
if ( $title instanceof Title ) {
$row = $dbr->selectRow(
// Purge cache of all pages using this file
$title = $this->getTitle();
if ( $title ) {
- $update = new HTMLCacheUpdate( $title, 'imagelinks' );
- $update->doUpdate();
+ DeferredUpdates::addUpdate( new HTMLCacheUpdate( $title, 'imagelinks' ) );
}
}
* @param string $end Only revisions newer than $end will be returned
* @param bool $inc Include the endpoints of the time range
*
- * @return array
+ * @return File[]
*/
function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
return array();
// Cache presence for 1 week and negatives for 1 day
$ttl = $this->fileExists ? 86400 * 7 : 86400;
- $opts = array( 'since' => wfGetDB( DB_SLAVE )->trxTimestamp() );
+ $opts = Database::getCacheSetOptions( $this->repo->getSlaveDB() );
ObjectCache::getMainWANInstance()->set( $key, $cacheVal, $ttl, $opts );
}
}
/**
- * @param DatabaseBase $dbr
+ * @param IDatabase $dbr
* @param string $fname
* @return array|bool
*/
* Delete cached transformed files for the current version only.
* @param array $options
*/
- function purgeThumbnails( $options = array() ) {
+ public function purgeThumbnails( $options = array() ) {
global $wgUseSquid;
// Delete thumbnails
* @param int $start Optional: Timestamp, start from
* @param int $end Optional: Timestamp, end at
* @param bool $inc
- * @return array
+ * @return OldLocalFile[]
*/
function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
$dbr = $this->repo->getSlaveDB();
$user
);
- $dbw->begin( __METHOD__ ); // XXX; doEdit() uses a transaction
// Now that the page exists, make an RC entry.
+ // This relies on the resetArticleID() call in WikiPage::insertOn(),
+ // which is triggered on $descTitle by doEditContent() above.
$logEntry->publish( $logId );
if ( isset( $status->value['revision'] ) ) {
$dbw->update( 'logging',
__METHOD__
);
}
- $dbw->commit( __METHOD__ ); // commit before anything bad can happen
}
- if ( $reupload ) {
- # Delete old thumbnails
- $this->purgeThumbnails();
-
- # Remove the old file from the squid cache
- SquidUpdate::purge( array( $this->getURL() ) );
- }
-
- # Hooks, hooks, the magic of hooks...
- Hooks::run( 'FileUpload', array( $this, $reupload, $descTitle->exists() ) );
+ # Do some cache purges after final commit so that:
+ # a) Changes are more likely to be seen post-purge
+ # b) They won't cause rollback of the log publish/update above
+ $that = $this;
+ $dbw->onTransactionIdle( function () use ( $that, $reupload, $descTitle ) {
+ # Run hook for other updates (typically more cache purging)
+ Hooks::run( 'FileUpload', array( $that, $reupload, $descTitle->exists() ) );
+
+ if ( $reupload ) {
+ # Delete old thumbnails
+ $that->purgeThumbnails();
+ # Remove the old file from the squid cache
+ SquidUpdate::purge( array( $that->getURL() ) );
+ } else {
+ # Update backlink pages pointing to this title if created
+ LinksUpdate::queueRecursiveJobsForTable( $that->getTitle(), 'imagelinks' );
+ }
+ } );
# Invalidate cache for all pages using this file
- $update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
- $update->doUpdate();
- if ( !$reupload ) {
- LinksUpdate::queueRecursiveJobsForTable( $this->getTitle(), 'imagelinks' );
- }
+ DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' ) );
return true;
}
* @param User $user User who did this upload
* @return bool
*/
- function recordOldUpload( $srcPath, $archiveName, $timestamp, $comment, $user ) {
+ protected function recordOldUpload( $srcPath, $archiveName, $timestamp, $comment, $user ) {
$dbw = $this->repo->getMasterDB();
- $dbw->begin( __METHOD__ );
$dstPath = $this->repo->getZonePath( 'public' ) . '/' . $this->getRel();
$props = $this->repo->getFileProps( $dstPath );
), __METHOD__
);
- $dbw->commit( __METHOD__ );
-
return true;
}
"config-xcache": "[http://xcache.lighttpd.net/ XCache] نصب شو",
"config-apc": "[http://www.php.net/apc APC] نصب شو",
"config-wincache": "[http://www.iis.net/download/WinCacheForPhp WinCache] نصب شو",
+ "config-diff3-bad": "جي ان يو ډيف3 و نه موندل شو.",
+ "config-using-server": "د پالنگر نوم \"<nowiki>$1</nowiki>\" کارېږي.",
+ "config-using-uri": "د پالنگر URL \"<nowiki>$1$2</nowiki>\" کارېږي.",
"config-db-type": "د توکبنسټ ډول:",
"config-db-host": "د توکبنسټ کوربه:",
"config-db-host-oracle": "د توکبنسټ TNS:",
"config-db-wiki-settings": "دا ويکي پېژندل",
"config-db-name": "د توکبنسټ نوم:",
+ "config-db-name-oracle": "د اومتوکبنسټ طرحه:",
"config-db-username": "د توکبنسټ کارن-نوم:",
"config-db-password": "د توکبنسټ پټنوم:",
"config-charset-mysql5-binary": "مای اس کيو ال 4.1/5.0 دوييز",
"config-profile": "د کارن رښتو پېژنليک:",
"config-profile-wiki": "پرانيستې ويکي",
"config-profile-private": "شخصي ويکي",
+ "config-license": "منښتليک او د خپرولو رښته:",
+ "config-license-none": "بې پايڅوړه منښتليک",
"config-license-pd": "ټولگړی شپول",
"config-email-settings": "د برېښليک امستنې",
"config-email-user": "کارن تر کارن برېښليک چارنول",
+ "config-upload-enable": "دوتنې پورته کېدنې چارنول",
+ "config-logo": "د نښې يو آر ال:",
"config-extensions": "شاتاړي",
"config-skins": "پوښۍ",
"config-skins-use-as-default": "همدا پوښۍ په تلواليزه توگه کارول",
"config-email-watchlist-help": "若使用者在個人偏好開啟了此功能,允許使用者收到與其監視清單有關的通知。",
"config-email-auth": "開啟電子郵件身份認證",
"config-email-auth-help": "若開啟此選項,使用者不論設定或者更改電子郵件地址,都必須透過收信的方式確認沒有問題。\n只有驗證過的電子郵件地址可以收到來自其他使用者或修改通知的信件。\n公開的 Wiki 會 <strong>建議</strong> 設定此選項,以防使用者濫用電子郵件功能。",
- "config-email-sender": "電子郵件回覆位址:",
+ "config-email-sender": "電子郵件回覆地址:",
"config-email-sender-help": "請輸入要用來做為外寄郵件的電子郵件回覆地址。\n該郵件地址會收到被拒收的信件。\n許多郵件伺服器會要求使用有效的網域名稱。",
"config-upload-settings": "圖片和檔案上傳",
"config-upload-enable": "開啟檔案上傳",
function ( $oldValue, &$ttl, array &$setOpts ) use ( $prefix ) {
$dbr = wfGetDB( DB_SLAVE );
+ $setOpts += Database::getCacheSetOptions( $dbr );
+
$row = $dbr->selectRow(
'interwiki',
Interwiki::selectFields(),
__METHOD__
);
- $setOpts = array( 'since' => $dbr->trxTimestamp() );
-
return $row ? (array)$row : '!NONEXISTENT';
},
$wgInterwikiExpiry
// Re-ping all masters with transactions. This throws DBError if some
// connection died while waiting on locks/slaves, triggering a rollback.
wfGetLBFactory()->forEachLB( function( LoadBalancer $lb ) use ( $fname ) {
- $lb->forEachOpenConnection( function( DatabaseBase $conn ) use ( $fname ) {
+ $lb->forEachOpenConnection( function( IDatabase $conn ) use ( $fname ) {
if ( $conn->writesOrCallbacksPending() ) {
$conn->query( "SELECT 1", $fname );
}
}
public function run() {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$scope = RequestContext::importScopedSession( $this->params['session'] );
$context = RequestContext::getMain();
$user = $context->getUser();
$upload->continueChunks(
$this->params['filename'],
$this->params['filekey'],
- $context->getRequest()
+ new WebRequestUpload( $context->getRequest(), 'null' )
);
// Combine all of the chunks into a local file and upload that to a new stash file
'status' => Status::newFatal( 'api-error-stashfailed' )
)
);
- $this->setLastError( get_class( $e ) . ": " . $e->getText() );
+ $this->setLastError( get_class( $e ) . ": " . $e->getMessage() );
// To be extra robust.
MWExceptionHandler::rollbackMasterChangesAndLog( $e );
}
public function run() {
+ /** @noinspection PhpUnusedLocalVariableInspection */
$scope = RequestContext::importScopedSession( $this->params['session'] );
$context = RequestContext::getMain();
$user = $context->getUser();
'status' => Status::newFatal( 'api-error-publishfailed' )
)
);
- $this->setLastError( get_class( $e ) . ": " . $e->getText() );
+ $this->setLastError( get_class( $e ) . ": " . $e->getMessage() );
// To prevent potential database referential integrity issues.
// See bug 32551.
MWExceptionHandler::rollbackMasterChangesAndLog( $e );
--- /dev/null
+<?php
+/**
+ * APC-backed function memoization
+ *
+ * This class provides memoization for pure functions. A function is pure
+ * if its result value depends on nothing other than its input parameters
+ * and if invoking it does not cause any side-effects.
+ *
+ * The first invocation of the memoized callable with a particular set of
+ * arguments will be delegated to the underlying callable. Repeat invocations
+ * with the same input parameters will be served from APC.
+ *
+ * @par Example:
+ * @code
+ * $memoizedStrrev = new MemoizedCallable( 'range' );
+ * $memoizedStrrev->invoke( 5, 8 ); // result: array( 5, 6, 7, 8 )
+ * $memoizedStrrev->invokeArgs( array( 5, 8 ) ); // same
+ * MemoizedCallable::call( 'range', array( 5, 8 ) ); // same
+ * @endcode
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @author Ori Livneh
+ * @since 1.27
+ */
+class MemoizedCallable {
+
+ /** @var callable */
+ private $callable;
+
+ /** @var string Unique name of callable; used for cache keys. */
+ private $callableName;
+
+ /**
+ * Constructor.
+ *
+ * @throws InvalidArgumentException if $callable is not a callable.
+ * @param callable $callable Function or method to memoize.
+ * @param int $ttl TTL in seconds. Defaults to 3600 (1hr). Capped at 86400 (24h).
+ */
+ public function __construct( $callable, $ttl = 3600 ) {
+ if ( !is_callable( $callable, false, $this->callableName ) ) {
+ throw new InvalidArgumentException(
+ 'Argument 1 passed to MemoizedCallable::__construct() must ' .
+ 'be an instance of callable; ' . gettype( $callable ) . ' given'
+ );
+ }
+
+ if ( $this->callableName === 'Closure::__invoke' ) {
+ // Differentiate anonymous functions from one another
+ $this->callableName .= uniqid();
+ }
+
+ $this->callable = $callable;
+ $this->ttl = min( max( $ttl, 1 ), 86400 );
+ }
+
+ /**
+ * Fetch the result of a previous invocation from APC.
+ *
+ * @param string $key
+ * @param bool &$success
+ */
+ protected function fetchResult( $key, &$success ) {
+ $success = false;
+ if ( function_exists( 'apc_fetch' ) ) {
+ return apc_fetch( $key, $success );
+ }
+ return false;
+ }
+
+ /**
+ * Store the result of an invocation in APC.
+ *
+ * @param string $key
+ * @param mixed $result
+ */
+ protected function storeResult( $key, $result ) {
+ if ( function_exists( 'apc_store' ) ) {
+ apc_store( $key, $result, $this->ttl );
+ }
+ }
+
+ /**
+ * Invoke the memoized function or method.
+ *
+ * @throws InvalidArgumentException If parameters list contains non-scalar items.
+ * @param array $args Parameters for memoized function or method.
+ * @return mixed The memoized callable's return value.
+ */
+ public function invokeArgs( Array $args = array() ) {
+ foreach ( $args as $arg ) {
+ if ( $arg !== null && !is_scalar( $arg ) ) {
+ throw new InvalidArgumentException(
+ 'MemoizedCallable::invoke() called with non-scalar ' .
+ 'argument'
+ );
+ }
+ }
+
+ $hash = md5( serialize( $args ) );
+ $key = __CLASS__ . ':' . $this->callableName . ':' . $hash;
+ $success = false;
+ $result = $this->fetchResult( $key, $success );
+ if ( !$success ) {
+ $result = call_user_func_array( $this->callable, $args );
+ $this->storeResult( $key, $result );
+ }
+
+ return $result;
+ }
+
+ /**
+ * Invoke the memoized function or method.
+ *
+ * Like MemoizedCallable::invokeArgs(), but variadic.
+ *
+ * @param mixed ...$params Parameters for memoized function or method.
+ * @return mixed The memoized callable's return value.
+ */
+ public function invoke() {
+ return $this->invokeArgs( func_get_args() );
+ }
+
+ /**
+ * Shortcut method for creating a MemoizedCallable and invoking it
+ * with the specified arguments.
+ *
+ * @param callable $callable
+ * @param array $args
+ * @param int $ttl
+ */
+ public static function call( $callable, Array $args = array(), $ttl = 3600 ) {
+ $instance = new self( $callable, $ttl );
+ return $instance->invokeArgs( $args );
+ }
+}
protected $maxConnsPerHost = 50;
/** @var string|null proxy */
protected $proxy;
+ /** @var string */
+ protected $userAgent = 'wikimedia/multi-http-client v1.0';
/**
* @param array $options
* - proxy : HTTP proxy to use
* - usePipelining : whether to use HTTP pipelining if possible (for all hosts)
* - maxConnsPerHost : maximum number of concurrent connections (per host)
+ * - userAgent : The User-Agent header value to send
* @throws Exception
*/
public function __construct( array $options ) {
}
}
static $opts = array(
- 'connTimeout', 'reqTimeout', 'usePipelining', 'maxConnsPerHost', 'proxy'
+ 'connTimeout', 'reqTimeout', 'usePipelining', 'maxConnsPerHost', 'proxy', 'userAgent'
);
foreach ( $opts as $key ) {
if ( isset( $options[$key] ) ) {
$req['headers']['content-length'] = 0;
}
+ if ( !isset( $req['headers']['user-agent'] ) ) {
+ $req['headers']['user-agent'] = $this->userAgent;
+ }
+
$headers = array();
foreach ( $req['headers'] as $name => $value ) {
if ( strpos( $name, ': ' ) ) {
* Values in the arguments collection which are Closure instances will be
* expanded by invoking them with no arguments before passing the
* resulting value on to the constructor/callable. This can be used to
- * pass DatabaseBase instances or other live objects to the
+ * pass IDatabase instances or other live objects to the
* constructor/callable. This behavior can be suppressed by adding
* closure_expansion => false to the specification.
*
**/
const KEY_SUFFIX = ':1';
- public function get( $key, &$casToken = null, $flags = 0 ) {
+ protected function doGet( $key, $flags = 0 ) {
$val = apc_fetch( $key . self::KEY_SUFFIX );
- $casToken = $val;
-
return $val;
}
/** Bitfield constants for get()/getMulti() */
const READ_LATEST = 1; // use latest data for replicated stores
+ const READ_VERIFIED = 2; // promise that caller can tell when keys are stale
public function __construct( array $params = array() ) {
if ( isset( $params['logger'] ) ) {
}
/**
- * Get an item with the given key. Returns false if it does not exist.
+ * Get an item with the given key, regenerating and setting it if not found
+ *
+ * If the callback returns false, then nothing is stored.
+ *
* @param string $key
- * @param mixed $casToken [optional]
- * @param integer $flags Bitfield; supports READ_LATEST [optional]
- * @return mixed Returns false on failure
+ * @param int $ttl Time-to-live (seconds)
+ * @param callable $callback Callback that derives the new value
+ * @return mixed The cached value if found or the result of $callback otherwise
+ * @since 1.27
+ */
+ final public function getWithSetCallback( $key, $ttl, $callback ) {
+ $value = $this->get( $key );
+
+ if ( $value === false ) {
+ if ( !is_callable( $callback ) ) {
+ throw new InvalidArgumentException( "Invalid cache miss callback provided." );
+ }
+ $value = call_user_func( $callback );
+ if ( $value !== false ) {
+ $this->set( $key, $value, $ttl );
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * Get an item with the given key
+ *
+ * If the key includes a determistic input hash (e.g. the key can only have
+ * the correct value) or complete staleness checks are handled by the caller
+ * (e.g. nothing relies on the TTL), then the READ_VERIFIED flag should be set.
+ * This lets tiered backends know they can safely upgrade a cached value to
+ * higher tiers using standard TTLs.
+ *
+ * @param string $key
+ * @param integer $flags Bitfield of BagOStuff::READ_* constants [optional]
+ * @param integer $oldFlags [unused]
+ * @return mixed Returns false on failure and if the item does not exist
*/
- abstract public function get( $key, &$casToken = null, $flags = 0 );
+ public function get( $key, $flags = 0, $oldFlags = null ) {
+ // B/C for ( $key, &$casToken = null, $flags = 0 )
+ $flags = is_int( $oldFlags ) ? $oldFlags : $flags;
+
+ return $this->doGet( $key, $flags );
+ }
+
+ /**
+ * @param string $key
+ * @param integer $flags Bitfield of BagOStuff::READ_* constants [optional]
+ * @return mixed Returns false on failure and if the item does not exist
+ */
+ abstract protected function doGet( $key, $flags = 0 );
/**
- * Set an item.
+ * @note: This method is only needed if cas() is not is used for merge()
+ *
+ * @param string $key
+ * @param mixed $casToken
+ * @param integer $flags Bitfield of BagOStuff::READ_* constants [optional]
+ * @return mixed Returns false on failure and if the item does not exist
+ */
+ protected function getWithToken( $key, &$casToken, $flags = 0 ) {
+ throw new Exception( __METHOD__ . ' not implemented.' );
+ }
+
+ /**
+ * Set an item
+ *
* @param string $key
* @param mixed $value
* @param int $exptime Either an interval in seconds or a unix timestamp for expiry
abstract public function set( $key, $value, $exptime = 0 );
/**
- * Delete an item.
+ * Delete an item
+ *
* @param string $key
* @return bool True if the item was deleted or not found, false on failure
*/
do {
$this->clearLastError();
$casToken = null; // passed by reference
- $currentValue = $this->get( $key, $casToken );
+ $currentValue = $this->getWithToken( $key, $casToken, BagOStuff::READ_LATEST );
if ( $this->getLastError() ) {
return false; // don't spam retries (retry only on races)
}
}
$this->clearLastError();
- $currentValue = $this->get( $key );
+ $currentValue = $this->get( $key, BagOStuff::READ_LATEST );
if ( $this->getLastError() ) {
$success = false;
} else {
* @ingroup Cache
*/
class EmptyBagOStuff extends BagOStuff {
- public function get( $key, &$casToken = null, $flags = 0 ) {
+ protected function doGet( $key, $flags = 0 ) {
return false;
}
return true;
}
- public function get( $key, &$casToken = null, $flags = 0 ) {
+ protected function doGet( $key, $flags = 0 ) {
if ( !isset( $this->bag[$key] ) ) {
return false;
}
return false;
}
- $casToken = $this->bag[$key][0];
-
return $this->bag[$key][0];
}
$this->readStore->setDebug( $debug );
}
- public function get( $key, &$casToken = null, $flags = 0 ) {
+ protected function doGet( $key, $flags = 0 ) {
return ( $flags & self::READ_LATEST )
- ? $this->writeStore->get( $key, $casToken, $flags )
- : $this->readStore->get( $key, $casToken, $flags );
+ ? $this->writeStore->get( $key, $flags )
+ : $this->readStore->get( $key, $flags );
}
public function getMulti( array $keys, $flags = 0 ) {
protected $lastRelayError = self::ERR_NONE;
/** Max time expected to pass between delete() and DB commit finishing */
- const MAX_COMMIT_DELAY = 1;
- /** Max expected replication lag for a reasonable storage setup */
- const MAX_REPLICA_LAG = 7;
+ const MAX_COMMIT_DELAY = 3;
+ /** Max replication lag before applying TTL_LAGGED to set() */
+ const MAX_REPLICA_LAG = 5;
/** Max time since snapshot transaction start to avoid no-op of set() */
- const MAX_SNAPSHOT_LAG = 6;
+ const MAX_SNAPSHOT_LAG = 5;
/** Seconds to tombstone keys on delete() */
- const HOLDOFF_TTL = 14; // MAX_COMMIT_DELAY + MAX_REPLICA_LAG + MAX_SNAPSHOT_LAG
+ const HOLDOFF_TTL = 14; // MAX_COMMIT_DELAY + MAX_REPLICA_LAG + MAX_SNAPSHOT_LAG + 1
/** Seconds to keep dependency purge keys around */
const CHECK_KEY_TTL = 31536000; // 1 year
const TTL_UNCACHEABLE = -1;
/** Idiom for getWithSetCallback() callbacks to 'lockTSE' logic */
const TSE_NONE = -1;
+ /** Max TTL to store keys when a data sourced is lagged */
+ const TTL_LAGGED = 30;
/** Cache format version number */
const VERSION = 1;
* - d) T1 reads the row and calls set() due to a cache miss
* - e) Stale value is stuck in cache
*
+ * Setting 'lag' helps avoids keys getting stuck in long-term stale states.
+ *
* Example usage:
* @code
* $dbr = wfGetDB( DB_SLAVE );
+ * $setOpts = Database::getCacheSetOptions( $dbr );
* // Fetch the row from the DB
* $row = $dbr->selectRow( ... );
* $key = wfMemcKey( 'building', $buildingId );
- * // Give the age of the transaction snapshot the data came from
- * $opts = array( 'since' => $dbr->trxTimestamp() );
- * $cache->set( $key, $row, 86400, $opts );
+ * $cache->set( $key, $row, 86400, $setOpts );
* @endcode
*
* @param string $key Cache key
* @param mixed $value
* @param integer $ttl Seconds to live [0=forever]
* @param array $opts Options map:
+ * - lag : Seconds of slave lag. Typically, this is either the slave lag
+ * before the data was read or, if applicable, the slave lag before
+ * the snapshot-isolated transaction the data was read from started.
+ * [Default: 0 seconds]
* - since : UNIX timestamp of the data in $value. Typically, this is either
* the current time the data was read or (if applicable) the time when
* the snapshot-isolated transaction the data was read from started.
final public function set( $key, $value, $ttl = 0, array $opts = array() ) {
$lockTSE = isset( $opts['lockTSE'] ) ? $opts['lockTSE'] : self::TSE_NONE;
$age = isset( $opts['since'] ) ? max( 0, microtime( true ) - $opts['since'] ) : 0;
+ $lag = isset( $opts['lag'] ) ? $opts['lag'] : 0;
+
+ if ( $lag > self::MAX_REPLICA_LAG ) {
+ // Too much lag detected; lower TTL so it converges faster
+ $ttl = $ttl ? min( $ttl, self::TTL_LAGGED ) : self::TTL_LAGGED;
+ }
if ( $age > self::MAX_SNAPSHOT_LAG ) {
if ( $lockTSE >= 0 ) {
* can be set dynamically by altering $ttl in the callback (by reference).
* The $setOpts array can be altered and is given to set() when called;
* it is recommended to set the 'since' field to avoid race conditions.
+ * Setting 'lag' helps avoids keys getting stuck in long-term stale states.
*
* Usually, callbacks ignore the current value, but it can be used
* to maintain "most recent X" values that come from time or sequence
* // Key to store the cached value under
* wfMemcKey( 'cat-attributes', $catId ),
* // Function that derives the new key value
- * function( $oldValue, &$ttl, array &$setOpts ) {
- * // Fetch row from the DB
+ * function ( $oldValue, &$ttl, array &$setOpts ) {
* $dbr = wfGetDB( DB_SLAVE );
- * $row = $dbr->selectRow( ... );
- *
- * // Set age of the transaction snapshot the data came from
- * $setOpts = array( 'since' => $dbr->trxTimestamp() );
+ * // Account for any snapshot/slave lag
+ * $setOpts += Database::getCacheSetOptions( $dbr );
*
- * return $row;
+ * return $dbr->selectRow( ... );
* },
* // Time-to-live (seconds)
* 60
* // Key to store the cached value under
* wfMemcKey( 'site-cat-config' ),
* // Function that derives the new key value
- * function( $oldValue, &$ttl, array &$setOpts ) {
- * // Fetch row from the DB
+ * function ( $oldValue, &$ttl, array &$setOpts ) {
* $dbr = wfGetDB( DB_SLAVE );
- * $config = CatConfig::newFromRow( $dbr->selectRow( ... ) );
+ * // Account for any snapshot/slave lag
+ * $setOpts += Database::getCacheSetOptions( $dbr );
*
- * // Set age of the transaction snapshot the data came from
- * $setOpts = array( 'since' => $dbr->trxTimestamp() );
- *
- * return $config;
+ * return CatConfig::newFromRow( $dbr->selectRow( ... ) );
* },
* // Time-to-live (seconds)
* 86400,
* // Key to store the cached value under
* wfMemcKey( 'cat-state', $cat->getId() ),
* // Function that derives the new key value
- * function( $oldValue, &$ttl, array &$setOpts ) {
+ * function ( $oldValue, &$ttl, array &$setOpts ) {
* // Determine new value from the DB
* $dbr = wfGetDB( DB_SLAVE );
- * $state = CatState::newFromResults( $dbr->select( ... ) );
- *
- * // Set age of the transaction snapshot the data came from
- * $setOpts = array( 'since' => $dbr->trxTimestamp() );
+ * // Account for any snapshot/slave lag
+ * $setOpts += Database::getCacheSetOptions( $dbr );
*
- * return $state;
+ * return CatState::newFromResults( $dbr->select( ... ) );
* },
* // Time-to-live (seconds)
* 900,
* // Key to store the cached value under
* wfMemcKey( 'cat-last-actions', 100 ),
* // Function that derives the new key value
- * function( $oldValue, &$ttl, array &$setOpts ) {
+ * function ( $oldValue, &$ttl, array &$setOpts ) {
* $dbr = wfGetDB( DB_SLAVE );
+ * // Account for any snapshot/slave lag
+ * $setOpts += Database::getCacheSetOptions( $dbr );
+ *
* // Start off with the last cached list
* $list = $oldValue ?: array();
* // Fetch the last 100 relevant rows in descending order;
* // only fetch rows newer than $list[0] to reduce scanning
* $rows = iterator_to_array( $dbr->select( ... ) );
* // Merge them and get the new "last 100" rows
- * $list = array_slice( array_merge( $new, $list ), 0, 100 );
- *
- * // Set age of the transaction snapshot the data came from
- * $setOpts = array( 'since' => $dbr->trxTimestamp() );
- *
- * return $list;
+ * return array_slice( array_merge( $new, $list ), 0, 100 );
* },
* // Time-to-live (seconds)
* 10,
* @ingroup Cache
*/
class WinCacheBagOStuff extends BagOStuff {
- public function get( $key, &$casToken = null, $flags = 0 ) {
+ protected function doGet( $key, $flags = 0 ) {
+ $casToken = null;
+
+ return $this->getWithToken( $key, $casToken, $flags );
+ }
+
+ protected function getWithToken( $key, &$casToken, $flags = 0 ) {
$val = wincache_ucache_get( $key );
$casToken = $val;
* @ingroup Cache
*/
class XCacheBagOStuff extends BagOStuff {
- public function get( $key, &$casToken = null, $flags = 0 ) {
+ protected function doGet( $key, $flags = 0 ) {
$val = xcache_get( $key );
if ( is_string( $val ) ) {
/**
* SQL clause to skip forbidden log types for this user
*
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param string $audience Public/user
* @param User $user User to check, or null to use $wgUser
* @return string|bool String on success, false on failure.
* @return Status
*/
public static function send( $to, $from, $subject, $body, $options = array() ) {
- global $wgSMTP, $wgEnotifMaxRecips, $wgAdditionalMailParams, $wgAllowHTMLEmail;
+ global $wgAllowHTMLEmail;
$contentType = 'text/plain; charset=UTF-8';
- $headers = array();
- if ( is_array( $options ) ) {
- $replyto = isset( $options['replyTo'] ) ? $options['replyTo'] : null;
- $contentType = isset( $options['contentType'] ) ? $options['contentType'] : $contentType;
- $headers = isset( $options['headers'] ) ? $options['headers'] : $headers;
- } else {
+ if ( !is_array( $options ) ) {
// Old calling style
wfDeprecated( __METHOD__ . ' with $replyto as 5th parameter', '1.26' );
- $replyto = $options;
+ $options = array( 'replyTo' => $options );
if ( func_num_args() === 6 ) {
- $contentType = func_get_arg( 5 );
+ $options['contentType'] = func_get_arg( 5 );
}
}
- $mime = null;
if ( !is_array( $to ) ) {
$to = array( $to );
}
return Status::newFatal( 'user-mail-no-addy' );
}
+ // give a chance to UserMailerTransformContents subscribers who need to deal with each
+ // target differently to split up the address list
+ if ( count( $to ) > 1 ) {
+ $oldTo = $to;
+ Hooks::run( 'UserMailerSplitTo', array( &$to ) );
+ if ( $oldTo != $to ) {
+ $splitTo = array_diff( $oldTo, $to );
+ $to = array_diff( $oldTo, $splitTo ); // ignore new addresses added in the hook
+ // first send to non-split address list, then to split addresses one by one
+ $status = Status::newGood();
+ if ( $to ) {
+ $status->merge( UserMailer::sendInternal(
+ $to, $from, $subject, $body, $options ) );
+ }
+ foreach ( $splitTo as $newTo ) {
+ $status->merge( UserMailer::sendInternal(
+ array( $newTo ), $from, $subject, $body, $options ) );
+ }
+ return $status;
+ }
+ }
+
+ return UserMailer::sendInternal( $to, $from, $subject, $body, $options );
+ }
+
+ /**
+ * Helper function fo UserMailer::send() which does the actual sending. It expects a $to
+ * list which the UserMailerSplitTo hook would not split further.
+ * @param MailAddress[] $to Array of recipients' email addresses
+ * @param MailAddress $from Sender's email
+ * @param string $subject Email's subject.
+ * @param string $body Email's text or Array of two strings to be the text and html bodies
+ * @param array $options:
+ * 'replyTo' MailAddress
+ * 'contentType' string default 'text/plain; charset=UTF-8'
+ * 'headers' array Extra headers to set
+ *
+ * @throws MWException
+ * @throws Exception
+ * @return Status
+ */
+ protected static function sendInternal(
+ array $to,
+ MailAddress $from,
+ $subject,
+ $body,
+ $options = array()
+ ) {
+ global $wgSMTP, $wgEnotifMaxRecips, $wgAdditionalMailParams;
+ $mime = null;
+
+ $replyto = isset( $options['replyTo'] ) ? $options['replyTo'] : null;
+ $contentType = isset( $options['contentType'] ) ?
+ $options['contentType'] : 'text/plain; charset=UTF-8';
+ $headers = isset( $options['headers'] ) ? $options['headers'] : array();
+
+ // Allow transformation of content, such as encrypting/signing
+ $error = false;
+ if ( !Hooks::run( 'UserMailerTransformContent', array( $to, $from, &$body, &$error ) ) ) {
+ if ( $error ) {
+ return Status::newFatal( 'php-mail-error', $error );
+ } else {
+ return Status::newFatal( 'php-mail-error-unknown' );
+ }
+ }
+
/**
* Forge email headers
* -------------------
$headers['Content-transfer-encoding'] = '8bit';
}
+ // allow transformation of MIME-encoded message
+ if ( !Hooks::run( 'UserMailerTransformMessage',
+ array( $to, $from, &$subject, &$headers, &$body, &$error ) )
+ ) {
+ if ( $error ) {
+ return Status::newFatal( 'php-mail-error', $error );
+ } else {
+ return Status::newFatal( 'php-mail-error-unknown' );
+ }
+ }
+
$ret = Hooks::run( 'AlternateUserMailer', array( $headers, $to, $from, $subject, $body ) );
if ( $ret === false ) {
// the hook implementation will return false to skip regular mail sending
return $params;
}
- public function get( $key, &$casToken = null, $flags = 0 ) {
+ protected function doGet( $key, $flags = 0 ) {
+ $casToken = null;
+
+ return $this->getWithToken( $key, $casToken, $flags );
+ }
+
+ protected function getWithToken( $key, &$casToken, $flags = 0 ) {
return $this->client->get( $this->encodeKey( $key ), $casToken );
}
$this->client->addServers( $servers );
}
- public function get( $key, &$casToken = null, $flags = 0 ) {
+ protected function getWithToken( $key, &$casToken, $flags = 0 ) {
$this->debugLog( "get($key)" );
$result = $this->client->get( $this->encodeKey( $key ), null, $casToken );
$result = $this->checkResult( $key, $result );
/** @var bool Use async secondary writes */
protected $asyncWrites = false;
+ /** Idiom for "write to all backends" */
+ const ALL = INF;
+
+ const UPGRADE_TTL = 3600; // TTL when a key is copied to a higher cache tier
+
/**
* $params include:
* - caches: This should have a numbered array of cache parameter
* @param bool $debug
*/
public function setDebug( $debug ) {
- $this->doWrite( 'setDebug', $debug );
+ $this->doWrite( self::ALL, 'setDebug', $debug );
}
- public function get( $key, &$casToken = null, $flags = 0 ) {
+ protected function doGet( $key, $flags = 0 ) {
+ $misses = 0; // number backends checked
+ $value = false;
foreach ( $this->caches as $cache ) {
- $value = $cache->get( $key, $casToken, $flags );
+ $value = $cache->get( $key, $flags );
if ( $value !== false ) {
- return $value;
+ break;
}
+ ++$misses;
+ }
+
+ if ( $value !== false
+ && $misses > 0
+ && ( $flags & self::READ_VERIFIED ) == self::READ_VERIFIED
+ ) {
+ $this->doWrite( $misses, 'set', $key, $value, self::UPGRADE_TTL );
}
- return false;
+
+ return $value;
}
/**
* @return bool
*/
public function set( $key, $value, $exptime = 0 ) {
- return $this->doWrite( 'set', $key, $value, $exptime );
+ return $this->doWrite( self::ALL, 'set', $key, $value, $exptime );
}
/**
* @return bool
*/
public function delete( $key ) {
- return $this->doWrite( 'delete', $key );
+ return $this->doWrite( self::ALL, 'delete', $key );
}
/**
* @return bool
*/
public function add( $key, $value, $exptime = 0 ) {
- return $this->doWrite( 'add', $key, $value, $exptime );
+ return $this->doWrite( self::ALL, 'add', $key, $value, $exptime );
}
/**
* @return bool|null
*/
public function incr( $key, $value = 1 ) {
- return $this->doWrite( 'incr', $key, $value );
+ return $this->doWrite( self::ALL, 'incr', $key, $value );
}
/**
* @return bool
*/
public function decr( $key, $value = 1 ) {
- return $this->doWrite( 'decr', $key, $value );
+ return $this->doWrite( self::ALL, 'decr', $key, $value );
}
/**
* @return bool Success
*/
public function merge( $key, $callback, $exptime = 0, $attempts = 10 ) {
- return $this->doWrite( 'merge', $key, $callback, $exptime );
+ return $this->doWrite( self::ALL, 'merge', $key, $callback, $exptime );
}
public function getLastError() {
}
/**
+ * Apply a write method to the first $count backing caches
+ *
+ * @param integer $count
* @param string $method
+ * @param mixed ...
* @return bool
*/
- protected function doWrite( $method /*, ... */ ) {
+ protected function doWrite( $count, $method /*, ... */ ) {
$ret = true;
- $args = func_get_args();
- array_shift( $args );
+ $args = array_slice( func_get_args(), 2 );
foreach ( $this->caches as $i => $cache ) {
+ if ( $i >= $count ) {
+ break; // ignore the lower tiers
+ }
+
if ( $i == 0 || !$this->asyncWrites ) {
// First store or in sync mode: write now and get result
if ( !call_user_func_array( array( $cache, $method ), $args ) ) {
}
}
- public function get( $key, &$casToken = null, $flags = 0 ) {
+ protected function doGet( $key, $flags = 0 ) {
list( $server, $conn ) = $this->getConnection( $key );
if ( !$conn ) {
return false;
}
try {
$value = $conn->get( $key );
- $casToken = $value;
$result = $this->unserialize( $value );
} catch ( RedisException $e ) {
$result = false;
* Get a connection to the specified database
*
* @param int $serverIndex
- * @return DatabaseBase
+ * @return IDatabase
* @throws MWException
*/
protected function getDB( $serverIndex ) {
- global $wgDebugDBTransactions;
-
if ( !isset( $this->conns[$serverIndex] ) ) {
if ( $serverIndex >= $this->numServers ) {
throw new MWException( __METHOD__ . ": Invalid server index \"$serverIndex\"" );
# If server connection info was given, use that
if ( $this->serverInfos ) {
- if ( $wgDebugDBTransactions ) {
- $this->logger->debug( "Using provided serverInfo for SqlBagOStuff" );
- }
$info = $this->serverInfos[$serverIndex];
$type = isset( $info['type'] ) ? $info['type'] : 'mysql';
$host = isset( $info['host'] ) ? $info['host'] : '[unknown]';
$db = wfGetDB( $index );
}
}
- if ( $wgDebugDBTransactions ) {
- $this->logger->debug( sprintf( "Connection %s will be used for SqlBagOStuff", $db ) );
- }
+ $this->logger->debug( sprintf( "Connection %s will be used for SqlBagOStuff", $db ) );
$this->conns[$serverIndex] = $db;
}
}
}
- public function get( $key, &$casToken = null, $flags = 0 ) {
+ protected function doGet( $key, $flags = 0 ) {
+ $casToken = null;
+
+ return $this->getWithToken( $key, $casToken, $flags );
+ }
+
+ protected function getWithToken( $key, &$casToken, $flags = 0 ) {
$values = $this->getMulti( array( $key ) );
if ( array_key_exists( $key, $values ) ) {
$casToken = $values[$key];
}
/**
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @param string $exptime
* @return bool
*/
}
/**
- * @param DatabaseBase $db
+ * @param IDatabase $db
* @return string
*/
protected function getMaxDateTime( $db ) {
$this->loadFile();
if ( $this->mFile->exists() ) {
wfDebug( 'ImagePage::doPurge purging ' . $this->mFile->getName() . "\n" );
- $update = new HTMLCacheUpdate( $this->mTitle, 'imagelinks' );
- $update->doUpdate();
+ DeferredUpdates::addUpdate( new HTMLCacheUpdate( $this->mTitle, 'imagelinks' ) );
$this->mFile->upgradeRow();
$this->mFile->purgeCache( array( 'forThumbRefresh' => true ) );
} else {
/**
* Fetch a page record with the given conditions
- * @param DatabaseBase $dbr
+ * @param IDatabase $dbr
* @param array $conditions
* @param array $options
* @return object|bool Database result resource, or false on failure
* Fetch a page record matching the Title object's namespace and title
* using a sanitized title string
*
- * @param DatabaseBase $dbr
+ * @param IDatabase $dbr
* @param Title $title
* @param array $options
* @return object|bool Database result resource, or false on failure
/**
* Fetch a page record matching the requested ID
*
- * @param DatabaseBase $dbr
+ * @param IDatabase $dbr
* @param int $id
* @param array $options
* @return object|bool Database result resource, or false on failure
* or else the record will be left in a funky state.
* Best if all done inside a transaction.
*
- * @param DatabaseBase $dbw
+ * @param IDatabase $dbw
* @return int|bool The newly created page_id key; false if the title already existed
*/
public function insertOn( $dbw ) {
/**
* Update the page record to point to a newly saved revision.
*
- * @param DatabaseBase $dbw
+ * @param IDatabase $dbw
* @param Revision $revision For ID number, and text used to set
* length and redirect status fields
* @param int $lastRevision If given, will not overwrite the page field
/**
* Add row to the redirect table if this is a redirect, remove otherwise.
*
- * @param DatabaseBase $dbw
+ * @param IDatabase $dbw
* @param Title $redirectTitle Title object pointing to the redirect target,
* or NULL if this is not a redirect
* @param null|bool $lastRevIsRedirect If given, will optimize adding and
*
* @deprecated since 1.24, use updateRevisionOn instead
*
- * @param DatabaseBase $dbw
+ * @param IDatabase $dbw
* @param Revision $revision
* @return bool
*/
// Images
if ( $title->getNamespace() == NS_FILE ) {
- $update = new HTMLCacheUpdate( $title, 'imagelinks' );
- $update->doUpdate();
+ DeferredUpdates::addUpdate( new HTMLCacheUpdate( $title, 'imagelinks' ) );
}
// User talk pages
/**
* Get the Database object in use
*
- * @return DatabaseBase
+ * @return IDatabase
*/
public function getDatabase() {
return $this->mDb;
}
// Determine the options which affect this article
- $optionsKey = $this->mMemc->get( $this->getOptionsKey( $article ) );
+ $casToken = null;
+ $optionsKey = $this->mMemc->get(
+ $this->getOptionsKey( $article ), $casToken, BagOStuff::READ_VERIFIED );
if ( $optionsKey instanceof CacheTime ) {
if ( !$useOutdated && $optionsKey->expired( $article->getTouched() ) ) {
wfIncrStats( "pcache.miss.expired" );
return false;
}
- $value = $this->mMemc->get( $parserOutputKey );
+ $casToken = null;
+ $value = $this->mMemc->get( $parserOutputKey, $casToken, BagOStuff::READ_VERIFIED );
if ( !$value ) {
wfDebug( "ParserOutput cache miss.\n" );
wfIncrStats( "pcache.miss.absent" );
* @return bool Whether $moduleName is a valid module name
*/
public static function isValidModuleName( $moduleName ) {
- return !preg_match( '/[|,!]/', $moduleName ) && strlen( $moduleName ) <= 255;
+ return strcspn( $moduleName, '!,|', 0, 255 ) === strlen( $moduleName );
}
/**
$this->missingLocalFileRefs[] = $file;
}
}
- return CSSMin::remap(
- $style, $localDir, $remoteDir, true
- );
+ return MemoizedCallable::call( 'CSSMin::remap',
+ array( $style, $localDir, $remoteDir, true ) );
}
/**
*/
class SearchDatabase extends SearchEngine {
/**
- * @var DatabaseBase Slave database for reading from for results
+ * @var IDatabase Slave database for reading from for results
*/
protected $db;
/**
* Constructor
- * @param DatabaseBase $db The database to search from
+ * @param IDatabase $db The database to search from
*/
- public function __construct( DatabaseBase $db = null ) {
+ public function __construct( IDatabase $db = null ) {
if ( $db ) {
$this->db = $db;
} else {
}
/**
- * @return Title|null Title object (pagename+fragment) for the section, null if none or not supported
+ * @return Title|null Title object (pagename+fragment) for the section,
+ * null if none or not supported
*/
function getSectionTitle() {
return null;
*/
protected $sites = null;
- /**
- * @var ORMTable
- */
- protected $sitesTable;
-
/**
* @since 1.25
- *
- * @param ORMTable|null $sitesTable
+ * @param null $sitesTable Unused since 1.27
*/
- public function __construct( ORMTable $sitesTable = null ) {
- if ( $sitesTable === null ) {
- $sitesTable = $this->newSitesTable();
+ public function __construct( $sitesTable = null ) {
+ if ( $sitesTable !== null ) {
+ throw new InvalidArgumentException(
+ __METHOD__ . ': $sitesTable parameter must be null'
+ );
}
-
- $this->sitesTable = $sitesTable;
}
/**
return $this->sites;
}
- /**
- * Returns a new Site object constructed from the provided ORMRow.
- *
- * @since 1.25
- *
- * @param ORMRow $siteRow
- *
- * @return Site
- */
- protected function siteFromRow( ORMRow $siteRow ) {
-
- $site = Site::newForType( $siteRow->getField( 'type', Site::TYPE_UNKNOWN ) );
-
- $site->setGlobalId( $siteRow->getField( 'global_key' ) );
-
- $site->setInternalId( $siteRow->getField( 'id' ) );
-
- if ( $siteRow->hasField( 'forward' ) ) {
- $site->setForward( $siteRow->getField( 'forward' ) );
- }
-
- if ( $siteRow->hasField( 'group' ) ) {
- $site->setGroup( $siteRow->getField( 'group' ) );
- }
-
- if ( $siteRow->hasField( 'language' ) ) {
- $site->setLanguageCode( $siteRow->getField( 'language' ) === ''
- ? null
- : $siteRow->getField( 'language' )
- );
- }
-
- if ( $siteRow->hasField( 'source' ) ) {
- $site->setSource( $siteRow->getField( 'source' ) );
- }
-
- if ( $siteRow->hasField( 'data' ) ) {
- $site->setExtraData( $siteRow->getField( 'data' ) );
- }
-
- if ( $siteRow->hasField( 'config' ) ) {
- $site->setExtraConfig( $siteRow->getField( 'config' ) );
- }
-
- return $site;
- }
-
- /**
- * Get a new ORMRow from a Site object
- *
- * @since 1.25
- *
- * @param Site $site
- *
- * @return ORMRow
- */
- protected function getRowFromSite( Site $site ) {
- $fields = array(
- // Site data
- 'global_key' => $site->getGlobalId(), // TODO: check not null
- 'type' => $site->getType(),
- 'group' => $site->getGroup(),
- 'source' => $site->getSource(),
- 'language' => $site->getLanguageCode() === null ? '' : $site->getLanguageCode(),
- 'protocol' => $site->getProtocol(),
- 'domain' => strrev( $site->getDomain() ) . '.',
- 'data' => $site->getExtraData(),
-
- // Site config
- 'forward' => $site->shouldForward(),
- 'config' => $site->getExtraConfig(),
- );
-
- if ( $site->getInternalId() !== null ) {
- $fields['id'] = $site->getInternalId();
- }
-
- return new ORMRow( $this->sitesTable, $fields );
- }
-
/**
* Fetches the site from the database and loads them into the sites field.
*
protected function loadSites() {
$this->sites = new SiteList();
- $siteRows = $this->sitesTable->select( null, array(), array(
- 'ORDER BY' => 'site_global_key'
- ) );
+ $dbr = wfGetDB( DB_SLAVE );
- foreach ( $siteRows as $siteRow ) {
- $this->sites[] = $this->siteFromRow( $siteRow );
+ $res = $dbr->select(
+ 'sites',
+ array(
+ 'site_id',
+ 'site_global_key',
+ 'site_type',
+ 'site_group',
+ 'site_source',
+ 'site_language',
+ 'site_protocol',
+ 'site_domain',
+ 'site_data',
+ 'site_forward',
+ 'site_config',
+ ),
+ '',
+ __METHOD__,
+ array( 'ORDER BY' => 'site_global_key' )
+ );
+
+ foreach ( $res as $row ) {
+ $site = Site::newForType( $row->site_type );
+ $site->setGlobalId( $row->site_global_key );
+ $site->setInternalId( (int)$row->site_id );
+ $site->setForward( (bool)$row->site_forward );
+ $site->setGroup( $row->site_group );
+ $site->setLanguageCode( $row->site_language === ''
+ ? null
+ : $row->site_language
+ );
+ $site->setSource( $row->site_source );
+ $site->setExtraData( unserialize( $row->site_data ) );
+ $site->setExtraConfig( unserialize( $row->site_config ) );
+ $this->sites[] = $site;
}
// Batch load the local site identifiers.
- $ids = wfGetDB( $this->sitesTable->getReadDb() )->select(
+ $ids = $dbr->select(
'site_identifiers',
array(
'si_site',
return true;
}
- $dbw = $this->sitesTable->getWriteDbConnection();
+ $dbw = wfGetDB( DB_MASTER );
$dbw->startAtomic( __METHOD__ );
$internalIds[] = $site->getInternalId();
}
- $siteRow = $this->getRowFromSite( $site );
- $success = $siteRow->save( __METHOD__ ) && $success;
+ $fields = array(
+ // Site data
+ 'site_global_key' => $site->getGlobalId(), // TODO: check not null
+ 'site_type' => $site->getType(),
+ 'site_group' => $site->getGroup(),
+ 'site_source' => $site->getSource(),
+ 'site_language' => $site->getLanguageCode() === null ? '' : $site->getLanguageCode(),
+ 'site_protocol' => $site->getProtocol(),
+ 'site_domain' => strrev( $site->getDomain() ) . '.',
+ 'site_data' => serialize( $site->getExtraData() ),
+
+ // Site config
+ 'site_forward' => $site->shouldForward() ? 1 : 0,
+ 'site_config' => serialize( $site->getExtraConfig() ),
+ );
+
+ $rowId = $site->getInternalId();
+ if ( $rowId !== null ) {
+ $success = $dbw->update(
+ 'sites', $fields, array( 'site_id' => $rowId ), __METHOD__
+ ) && $success;
+ } else {
+ $rowId = $dbw->nextSequenceValue( 'sites_site_id_seq' );
+ $fields['site_id'] = $rowId;
+ $success = $dbw->insert( 'sites', $fields, __METHOD__ ) && $success;
+ $rowId = $dbw->insertId();
+ }
foreach ( $site->getLocalIds() as $idType => $ids ) {
foreach ( $ids as $id ) {
- $localIds[] = array( $siteRow->getId(), $idType, $id );
+ $localIds[] = array( $rowId, $idType, $id );
}
}
}
* @return bool Success
*/
public function clear() {
- $dbw = $this->sitesTable->getWriteDbConnection();
+ $dbw = wfGetDB( DB_MASTER );
$dbw->startAtomic( __METHOD__ );
$ok = $dbw->delete( 'sites', '*', __METHOD__ );
return $ok;
}
- /**
- * @since 1.25
- *
- * @return ORMTable
- */
- protected function newSitesTable() {
- return new ORMTable(
- 'sites',
- array(
- 'id' => 'id',
-
- // Site data
- 'global_key' => 'str',
- 'type' => 'str',
- 'group' => 'str',
- 'source' => 'str',
- 'language' => 'str',
- 'protocol' => 'str',
- 'domain' => 'str',
- 'data' => 'array',
-
- // Site config
- 'forward' => 'bool',
- 'config' => 'array',
- ),
- array(
- 'type' => Site::TYPE_UNKNOWN,
- 'group' => Site::GROUP_NONE,
- 'source' => Site::SOURCE_LOCAL,
- 'data' => array(),
-
- 'forward' => false,
- 'config' => array(),
- 'language' => '',
- ),
- 'ORMRow',
- 'site_'
- );
- }
-
}
* @since 1.21
* @deprecated 1.25 Construct a SiteStore instance directly instead.
*
- * @param ORMTable|null $sitesTable
+ * @param null $sitesTable Unused
* @param BagOStuff|null $cache
*
* @return SiteStore
*/
- public static function newInstance( ORMTable $sitesTable = null, BagOStuff $cache = null ) {
+ public static function newInstance( $sitesTable = null, BagOStuff $cache = null ) {
+ if ( $sitesTable !== null ) {
+ throw new InvalidArgumentException(
+ __METHOD__ . ': $sitesTable parameter is unused and must be null'
+ );
+ }
+
if ( $cache === null ) {
$cache = wfGetCache( wfIsHHVM() ? CACHE_ACCEL : CACHE_ANYTHING );
}
* Failures here must throw subclasses of ErrorPageError.
* @param User $user
* @throws UserBlockedError
- * @return bool True
*/
protected function checkExecutePermissions( User $user ) {
$this->checkPermissions();
if ( $this->requiresWrite() ) {
$this->checkReadOnly();
}
-
- return true;
}
/**
throw new ErrorPageError( 'internalerror', 'resetpass_forbidden' );
}
- return parent::checkExecutePermissions( $user );
+ parent::checkExecutePermissions( $user );
}
protected function getFormFields() {
$out->addHtml( "</div>" );
- Hooks::run( 'SpecialSearchResultsAppend', array( $this, $out ) );
+ Hooks::run( 'SpecialSearchResultsAppend', array( $this, $out, $term ) );
}
}
protected function showHistory() {
+ $this->checkReadOnly();
+
$out = $this->getOutput();
if ( $this->mAllowed ) {
$out->addModules( 'mediawiki.special.undelete' );
throw new ErrorPageError( 'undelete-error', 'filedelete-maintenance' );
}
- if ( wfReadOnly() ) {
- throw new ReadOnlyError;
- }
+ $this->checkReadOnly();
$out = $this->getOutput();
$archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
class BatchRowIterator implements RecursiveIterator {
/**
- * @var DatabaseBase $db The database to read from
+ * @var IDatabase $db The database to read from
*/
protected $db;
/**
* @var array $fetchColumns List of column names to select from the
- * table suitable for use with DatabaseBase::select()
+ * table suitable for use with IDatabase::select()
*/
protected $fetchColumns;
private $key;
/**
- * @param DatabaseBase $db The database to read from
+ * @param IDatabase $db The database to read from
* @param string $table The name of the table to read from
* @param string|array $primaryKey The name or names of the primary key columns
* @param integer $batchSize The number of rows to fetch per iteration
* @throws MWException
*/
- public function __construct( DatabaseBase $db, $table, $primaryKey, $batchSize ) {
+ public function __construct( IDatabase $db, $table, $primaryKey, $batchSize ) {
if ( $batchSize < 1 ) {
throw new MWException( 'Batch size must be at least 1 row.' );
}
/**
* @param array $condition Query conditions suitable for use with
- * DatabaseBase::select
+ * IDatabase::select
*/
public function addConditions( array $conditions ) {
$this->conditions = array_merge( $this->conditions, $conditions );
/**
* @param array $condition Query join conditions suitable for use
- * with DatabaseBase::select
+ * with IDatabase::select
*/
public function addJoinConditions( array $conditions ) {
$this->joinConditions = array_merge( $this->joinConditions, $conditions );
/**
* @param array $columns List of column names to select from the
- * table suitable for use with DatabaseBase::select()
+ * table suitable for use with IDatabase::select()
*/
public function setFetchColumns( array $columns ) {
// If it's not the all column selector merge in the primary keys we need
*/
class BatchRowWriter {
/**
- * @var DatabaseBase $db The database to write to
+ * @var IDatabase $db The database to write to
*/
protected $db;
protected $clusterName;
/**
- * @param DatabaseBase $db The database to write to
+ * @param IDatabase $db The database to write to
* @param string $table The name of the table to update
* @param string|bool $clusterName A cluster name valid for use with LBFactory
*/
- public function __construct( DatabaseBase $db, $table, $clusterName = false ) {
+ public function __construct( IDatabase $db, $table, $clusterName = false ) {
$this->db = $db;
$this->table = $table;
$this->clusterName = $clusterName;
"searchbutton": "Mita",
"go": "Jak u",
"searcharticle": "Jak u",
- "history": "Riwayat laman",
+ "history": "Riwayat miëng",
"history_short": "Riwayat",
"updatedmarker": "geuubah yôh seunaweue keuneulheueh lôn phôn kön",
"printableversion": "Seunalén rakam",
"site-atom-feed": "Umpeuën Atôm $1",
"page-rss-feed": "Umpeuën RSS \"$1\"",
"page-atom-feed": "Umpeuën Atom \"$1\"",
- "red-link-title": "$1 (laman hana)",
+ "red-link-title": "$1 (miëng hana)",
"sort-descending": "Peuurôt tren",
"sort-ascending": "Peuurôt ék",
- "nstab-main": "Laman",
+ "nstab-main": "Miëng",
"nstab-user": "Ureuëng ngui",
"nstab-media": "Laman media",
- "nstab-special": "Laman kusuih",
+ "nstab-special": "Miëng kusuih",
"nstab-project": "Laman buët",
"nstab-image": "Beureukaih",
"nstab-mediawiki": "Peusan",
"nstab-template": "Seunaleuëk",
"nstab-help": "Beunantu",
"nstab-category": "Kawan",
+ "mainpage-nstab": "Ôn keuë",
"nosuchaction": "Hana buët nyan",
"nosuchactiontext": "Buët nyang geulakèë lé URL nyan hana sah. Droeneuh kadang salah neukeutik URL, atawa neuseutöt saboh neuhubông nyang hana beutôi. Hai nyoë kadang jeuët keu lageuëm saboh bug bak alat leumiëk nyang geungui lé {{SITENAME}}.",
"nosuchspecialpage": "Hana laman kusuih lagèë nyan",
"summary": "Éhtisa:",
"subject": "Bhah/nan:",
"minoredit": "Nyoë lôn andam bacut",
- "watchthis": "Kalön laman nyoë",
- "savearticle": "Keubah laman",
+ "watchthis": "Kalön miëng nyoë",
+ "savearticle": "Keubah miëng",
"preview": "Eu dilèë",
"showpreview": "Peuleumah hasé",
"showdiff": "Peuleumah neuubah",
"currentrev": "Geunantoë jinoë",
"currentrev-asof": "Geunantoë barô bak $1",
"revisionasof": "Geunantoë tiëp $1",
- "revision-info": "Geunantoë tiëp $1; $2",
+ "revision-info": "Revisi per $1 lé {{GENDER:$6|$2}}$7",
"previousrevision": "← Geunantoë awai",
"nextrevision": "Geunantoë lheuëh nyan→",
"currentrevisionlink": "Geunantoë jinoë",
"viewprevnext": "Eu ($1 {{int:pipe-separator}} $2)($3)",
"searchmenu-exists": "'''Na laman ngön nan \"[[:$1]]\" bak wiki nyoe.'''",
"searchmenu-new": "<strong>Peugöt laman \"[[:$1]]\" bak wiki nyoë!</strong> {{PLURAL:$2|0=|Eu cit laman nyang geurumpok nibak meunita droëneuh.|Eu cit hasé mita nyang geurumpok.}}",
- "searchprofile-articles": "Laman asoë",
+ "searchprofile-articles": "Miëng asoë",
"searchprofile-images": "Multimedia",
"searchprofile-everything": "Ban dum",
"searchprofile-advanced": "Tingkat lanjut",
"searchprofile-articles-tooltip": "Mita bak $1",
"searchprofile-images-tooltip": "Mita beureukaih",
- "searchprofile-everything-tooltip": "Mita ban dum laman asoë (rôh ôn marit)",
+ "searchprofile-everything-tooltip": "Mita ban dum miëng asoë (rôh ôn marit)",
"searchprofile-advanced-tooltip": "Mita bak ruweuëng nan meupat-pat",
"search-result-size": "$1 ({{PLURAL:$2|1 narit|$2 narit}})",
"search-result-category-size": "{{PLURAL:$1|1 anggeeta|$1 anggeeta}} ({{PLURAL:$2|1 aneuk kawan|$2 aneuk kawan}}, {{PLURAL:$3|1 beureukaih|$3 beureukaih}})",
"recentchanges-summary": "Di yup nyoë nakeuh neuubah barô nyang na bak Wikipèdia nyoë.\nHareutoë: (bida) = neuubah, (riwayat) = riwayat teumuléh, '''B''' = laman barô, '''u''' = neuandam ubeut, '''b''' = neuandam bot, (± ''bit'') = jeumeulah asoë meutamah/meukureuëng, → = neuandam bideuëng, ← = mohtasa otomatis.\n----",
"recentchanges-noresult": "Hana neuubah lam lheuëng watèë nyoë nyang paih ngön syarat",
"recentchanges-feed-description": "Seutöt neuubah barô lam wiki bak umpeuën nyoë.",
- "recentchanges-label-newpage": "Neuandam nyoë jipeugöt laman barô",
+ "recentchanges-label-newpage": "Neuandam nyoë jipeugöt miëng barô",
"recentchanges-label-minor": "Nyoe neuandam ubeut",
"recentchanges-label-bot": "Neuandam nyoe geupubuet le bot",
"recentchanges-label-unpatrolled": "Neuandam nyoe goh lom geukalon",
"recentchangeslinked-toolbox": "Neuubah teukaw'èt",
"recentchangeslinked-title": "Neuubah nyang meukaw'èt ngön $1",
"recentchangeslinked-summary": "Nyoë nakeuh dapeuta neuubah nyang geupeugèt ban-ban nyoë keu on-on nyang meuhubông nibak ôn ka kusuih (atawa keu anggèëta kawan kusuih).\nÔn-ôn bak [[Special:Watchlist|keunalon droeneuh]] geucitak '''teubay'''.",
- "recentchangeslinked-page": "Nan laman:",
+ "recentchangeslinked-page": "Nan miëng:",
"recentchangeslinked-to": "Peuleumah neuubah nibak laman-laman nyang mupawôt ngön laman nyang geubri",
"upload": "Peutamöng beureukaih",
"uploadbtn": "Peutamong beureukaih",
"filehist-filesize": "Rayek beureukah",
"filehist-comment": "Seuneu'ôt",
"imagelinks": "Seuneungui beureukaih",
- "linkstoimage": "{{PLURAL:$1|laman}} di yup nyoë mupawôt u beureukaih nyoë:",
+ "linkstoimage": "{{PLURAL:$1|miëng}} di yup nyoë mupawôt u beureukaih nyoë:",
"nolinkstoimage": "Hana laman nyang na meupawôt u beureukaih nyoë.",
"sharedupload": "Beureukah nyoë dari $1 ngön kadang geunguy lé buët-buët la’én.",
"sharedupload-desc-here": "Beureukaih nyoe nejih nibak $1 ngon kadang geunguy le proyek-proyek la'en.\nTeuneurang bak [$2 on teuneurangjih] geupeuleumah di yup nyoe.",
"mimesearch": "Mita MIME",
"listredirects": "Dapeuta peuninah",
"unusedtemplates": "Templat nyang hana geungui",
- "randompage": "Laman baranggari",
+ "randompage": "Miëng baranggari",
"randomredirect": "Peuninah saban sakri",
"statistics": "Keunira",
"doubleredirects": "Peuninah ganda",
"protectedpages": "Laman nyang geulindông",
"listusers": "Dapeuta ureuëng ngui",
"usercreated": "{{GENDER:$3|Geupeugot}} bak $1 poh $2",
- "newpages": "Laman barô",
+ "newpages": "Miëng barô",
"newpages-username": "Ureuëng ngui:",
"ancientpages": "Laman paléng awai",
"move": "Pupinah",
"sp-contributions-submit": "Mita",
"whatlinkshere": "Peunawôt balék",
"whatlinkshere-title": "Laman nyang mupawôt u $1",
- "whatlinkshere-page": "Laman:",
+ "whatlinkshere-page": "Miëng:",
"linkshere": "Laman-laman nyoë meupawôt u '''[[:$1]]''':",
"nolinkshere": "Hana halaman nyang teukaw'et u '''[[:$1]]'''.",
"isredirect": "laman peuninah",
"block-log-flags-nocreate": "pumeugöt akun geupumaté",
"movepagetext": "Formulir di yup nyoë geunguy keu jak ubah nan saboh ôn ngön jak peupinah ban dum data riwayat u nan barô. Nan nyang trép euntreuk jeuët keu ôn peupinah u nan nyang barô. Hubông u nan trép hana meu’ubah. Neupeupaseuti keu neupréksa peuninah ôn nyang reulöh atawa meuganda lheuëh neupinah. Droëneuh nyang mat tanggông jaweuëb keu neupeupaseuti meunyo hubông laju teusambông u ôn nyang patôt.\n\nBeu neuingat that meunyo ôn '''h’an''' jan geupeupinah meunyo ka na ôn nyang geunguy nan barô, keucuali meunyo ôn nyan soh atawa nakeuh ôn peuninah ngön hana riwayat andam. Nyoë areutijih Droëneuh jeuët neu’ubah nan ôn keulayi lagèë söt meunyo Droëneuh neupeugöt seunalah, ngön Droëneuh h‘an jeuët neutimpa ôn nyang ka na.\n'''INGAT'''\nNyoë jeuët geupeuakébat neu’ubah nyang h’an neuduga ngön kreuëh ngön bacah keu ôn nyang meuceuhu. Neupeupaseuti Droëneuh meuphôm akébat nibak buët nyoë sigohlom neulanjut.",
"movepagetalktext": "Ôn peugah haba nyang na hubôngan euntreuk teupinah keudroë '''keucuali meunyo:'''\n\n*Saboh ôn peugah haba nyang hana soh ka na di yup nan barô, atawa\n*Droëneuh hana neubôh tanda cunténg bak kutak di yup nyoë\n\nLam masalah nyoë, meunyo neuhawa, Droëneuh jeuët neupeupinah atawa neupeugabông ôn keudroë.",
- "movearticle": "Peupinah laman:",
"newtitle": "U nan barô:",
"move-watch": "Kalön laman nyoë",
"movepagebtn": "Peupinah laman",
"movelogpage": "Log pinah",
"movereason": "Choë:",
"revertmove": "peuriwang",
- "export": "Peuteubiët laman",
+ "export": "Peuteubiët miëng",
"allmessages": "Peusan sistem",
"allmessagesname": "Nan",
"allmessagesdefault": "Naseukah pukok",
"tooltip-pt-login": "Droëneuh geupadan keu tamong log, bah pih nyan hana geupeuwajéb.",
"tooltip-pt-logout": "Teubiët",
"tooltip-pt-createaccount": "Droëneuh geupadan keu neupeugöt saboh akun ngön neutamöng; bah pih nyan hana wajéb",
- "tooltip-ca-talk": "Marit laman asoë",
- "tooltip-ca-edit": "Droëneuh jeuët neuandam laman nyoë. Neungui tumbôi eu dilèë sigoh neukeubah.",
+ "tooltip-ca-talk": "Marit miëng asoë",
+ "tooltip-ca-edit": "Andam miëng nyoë",
"tooltip-ca-addsection": "Puphôn beunagi barô",
"tooltip-ca-viewsource": "Laman nyoë geulindông.\nDroëneuh jeuët neu’eu nèjih mantöng.",
- "tooltip-ca-history": "Geunantoë awai nibak laman nyoë",
+ "tooltip-ca-history": "Geunantoë awai nibak miëng nyoë",
"tooltip-ca-protect": "Peulindông laman nyoë",
"tooltip-ca-delete": "Sampôh laman nyoë",
"tooltip-ca-move": "Pupinah laman nyoë",
- "tooltip-ca-watch": "Tamah laman nyoë u dapeuta kalön droëneuh",
+ "tooltip-ca-watch": "Tamah miëng nyoë u dapeuta kalön droëneuh",
"tooltip-ca-unwatch": "Sampôh laman nyoë nibak dapeuta kalön droëneuh",
"tooltip-search": "Mita {{SITENAME}}",
- "tooltip-search-go": "Mita saboh laman ngon nan nyang peureuséh lagèë nyoë meunyo na",
- "tooltip-search-fulltext": "Mita laman nyang na asoë lagèë nyoë",
+ "tooltip-search-go": "Mita saboh miëng ngon nan nyang peureuséh lagèë nyoë meunyo na",
+ "tooltip-search-fulltext": "Mita miëng nyang na asoë lagèë nyoë",
"tooltip-p-logo": "Saweuë ôn keuë",
"tooltip-n-mainpage": "Saweuë ôn keuë",
"tooltip-n-mainpage-description": "Saweuë ôn keuë",
"tooltip-n-portal": "Bhaih buët, peuë nyang jeuët neupubuët, pat keu mita sipeuë hai",
"tooltip-n-currentevents": "Mita haba barô",
"tooltip-n-recentchanges": "Dapeuta neuubah barô lam wiki.",
- "tooltip-n-randompage": "Peudeuih laman baranggari",
+ "tooltip-n-randompage": "Peudeuih miëng baranggari",
"tooltip-n-help": "Bak mita bantu.",
- "tooltip-t-whatlinkshere": "Dapeuta ban dum laman wiki nyang mupawôt keunoë",
+ "tooltip-t-whatlinkshere": "Dapeuta ban dum miëng wiki nyang mupawôt keunoë",
"tooltip-t-recentchangeslinked": "Neuubah barô lam laman nyang meupawôt nibak laman nyoë",
"tooltip-feed-rss": "Umpeuën RSS keu laman nyoë",
- "tooltip-feed-atom": "Umpeuën Atom keu laman nyoë",
+ "tooltip-feed-atom": "Umpeuën Atom keu miëng nyoë",
"tooltip-t-contributions": "Dapeuta beuneuri ureuëng ngui nyoë",
"tooltip-t-emailuser": "Peu'ét surat-e keu ureuëng ngui nyoë",
"tooltip-t-upload": "Peutamong beureukaih",
- "tooltip-t-specialpages": "Dapeuta ban dum laman kusuih",
- "tooltip-t-print": "Seunalén rakam laman nyoë",
- "tooltip-t-permalink": "Peunawôt teutap keu geunantoë laman nyoë",
- "tooltip-ca-nstab-main": "Eu laman asoë",
- "tooltip-ca-nstab-user": "Eu laman ureuëng ngui",
- "tooltip-ca-nstab-special": "Nyoë nakeuh laman kusuih nyang h’an jeuët geuandam.",
+ "tooltip-t-specialpages": "Dapeuta ban dum miëng kusuih",
+ "tooltip-t-print": "Seunalén rakam miëng nyoë",
+ "tooltip-t-permalink": "Peunawôt teutap keu geunantoë miëng nyoë",
+ "tooltip-ca-nstab-main": "Eu miëng asoë",
+ "tooltip-ca-nstab-user": "Eu miëng ureuëng ngui",
+ "tooltip-ca-nstab-special": "Nyoë nakeuh miëng kusuih, ngön h’an jeuët geuandam.",
"tooltip-ca-nstab-project": "Eu laman buët",
- "tooltip-ca-nstab-image": "Eu laman beureukaih",
+ "tooltip-ca-nstab-image": "Eu miëng beureukaih",
"tooltip-ca-nstab-template": "Eu seunaleuëk",
"tooltip-ca-nstab-help": "Eu laman beunantu",
- "tooltip-ca-nstab-category": "Eu laman kawan",
+ "tooltip-ca-nstab-category": "Eu miëng kawan",
"tooltip-minoredit": "Bôh tanda keu nyoë sibagoë andam bacut",
"tooltip-save": "Keubah neuubah Droëneuh",
"tooltip-preview": "Peuleumah neuubah Droëneuh, neungui nyoë sigohlom neukeubah!",
"tooltip-summary": "Pasoë éhtisa paneuk",
"interlanguage-link-title": "$1 – $2",
"simpleantispam-label": "Paréksa anti-spam.\n<strong>BÈK</strong> neupasoë!",
- "pageinfo-toolboxlink": "Teuneurang laman",
+ "pageinfo-toolboxlink": "Teuneurang miëng",
"previousdiff": "← Bida awai",
"nextdiff": "Geunantoë lheuëh nyan →",
"file-info-size": "$1 × $2 piksel, rayek beureukaih: $3, MIME jeunèh: $4",
"exif-yresolution": "Rèsolusi buju",
"exif-software": "Software geungui",
"exif-exifversion": "Versi Exif",
+ "exif-colorspace": "Ruweuëng wareuna",
"exif-datetimedigitized": "Uroë buleuën ngön watèë digital",
"exif-orientation-1": "Biasa",
"namespacesall": "ban dum",
"duplicate-defaultsort": "'''Ingat:''' Gunci meuurot pukok \"$2\" jipeuhiro gunci meuurot pukok \"$1\" sigohlomjih.",
"version": "Curak",
"fileduplicatesearch-submit": "Mita",
- "specialpages": "Laman kusuih",
+ "specialpages": "Miëng kusuih",
"specialpages-note": "* Laman kusuih biasa.\n* <span class=\"mw-specialpagerestricted\">Laman kusuih geutheun.</span>",
"specialpages-group-maintenance": "Beuneuri thèë plara",
"specialpages-group-other": "La'én-la'én",
"viewsource-title": "$1 लेली स्रोत देखऽ",
"actionthrottled": "कार्य समाप्त करलऽ गेलै",
"protectedpagetext": "इ पन्ना संपादन आरू अन्य कार्यो स॑ सुरक्षित रखलऽ गेलऽ छै.",
+ "viewsourcetext": "आपनै इ पन्ना केरौ स्त्रोत क देखै आरू कापी करै सकै छियै.",
"protectedinterface": "इ पन्ना इ विकी के सॉफ़्टवेयर क॑ इंटरफ़ेस पाठ दै छै, आरू एखरऽ गलत प्रयोग स॑ बचाबै लेली सुरक्षित करी देलऽ गेलऽ छै.\nसब्भे विकि लेली अनुवाद जोड़ै या बदलै लेली कृपया मीडियाविकि केरऽ क्षेत्रीयकरण प्रकल्प [//translatewiki.net/ translatewiki.net] केर प्रयोग करऽ.",
+ "editinginterface": "<strong>चेतावनी:</strong> आपनै एगो ऐसनौ पन्ना क बदली रहलौ छियै जे सॉफ़्टवेयर केरौ इंटरफ़ेस पाठ प्रदान करै छै।\nइ पन्ना क॑ बदलला स अन्य सदस्यो केरौ प्रदर्शित इंटरफ़ेस केरौ शक्लोसूरत म बदलाव ऐतै ।",
"yourname": "सदस्यनाम:",
"userlogin-yourname": "यूजरनाम",
"userlogin-yourname-ph": "अपनऽ सदस्यनाम लिखऽ",
"viewsource": "Ver fonte",
"viewsource-title": "Ver la fonte de $1",
"actionthrottled": "Aición llendada",
- "actionthrottledtext": "Como midida anti-spam, nun se pue repetir esta aición munches vegaes en pocu tiempu, y trespasasti esi llímite.\nPor favor vuelvi intentalo nunos minutos.",
+ "actionthrottledtext": "Como midida escontra abusos, nun se pué repetir esta aición munches vegaes en pocu tiempu, y trespasasti esa llende.\nVuelve a intentalo n'unos minutos.",
"protectedpagetext": "Esta páxina ta candada pa torgar ediciones y otres aiciones.",
"viewsourcetext": "Pues ver y copiar la fonte d'esta páxina.",
"viewyourtext": "Pues ver y copiar la fonte de <strong>les tos ediciones</strong> d'esta páxina.",
"passwordreset-emailtext-ip": "Dalguién (seique vusté, dende la direición IP $1)solicitó'l reaniciu de la so contraseña de {{SITENAME}} ($4).\n{{PLURAL:$3|La cuenta d'usuariu siguiente ta asociada|Les cuentes d'usuariu siguientes tán asociaes}}\na esta direición de corréu electrónicu:\n\n$2\n\n{{PLURAL:$3|Esta contraseña provisional caduca|Estes contraseñes provisionales caduquen}} {{PLURAL:$5|nun día|en $5 díes}}.\nTendría d'aniciar sesión y escoyer una contraseña nueva agora. Si esta solicitú la fizo otra persona,\no si recordó la clave orixinal y yá nun quier camudala, pue escaecer esti mensaxe y siguir\nusando la contraseña antigua.",
"passwordreset-emailtext-user": "L'usuariu $1 de {{SITENAME}} solicitó un reaniciu de la so contraseña de {{SITENAME}} ($4). {{PLURAL:$3|La cuenta d'usuariu siguiente ta asociada|Les cuentes d'usuariu siguientes tán asociaes}} con esta direición de corréu electrónicu:\n\n$2\n\n{{PLURAL:$3|Esta contraseña provisional caduca|Estes contraseñes provisionales caduquen}} {{PLURAL:$5|nun día|en $5 díes}}.\nTendría d'aniciar sesión y escoyer una contraseña nueva agora. Si esta solicitú la fizo otra persona, o si recordó la clave orixinal y yá nun quier camudala, pue escaecer esti mensaxe y siguir usando la contraseña antigua.",
"passwordreset-emailelement": "Nome d'usuariu: \n$1\n\nContraseña temporal: \n$2",
- "passwordreset-emailsent": "Unvióse un corréu electrónicu pa reaniciar la contraseña.",
+ "passwordreset-emailsent": "Si esta ye una direición de corréu electrónicu rexistrada pa la to cuenta, unviaráse un corréu pa reaniciar la contraseña.",
"passwordreset-emailsent-capture": "Unvióse un corréu electrónicu pa reaniciar la contraseña, que s'amuesa abaxo.",
"passwordreset-emailerror-capture": "Unvióse un corréu electrónicu pa reaniciar la contraseña, que s'amuesa abaxo, pero falló l'unviu {{GENDER:$2|al usuariu|a la usuaria}}: $1",
"changeemail": "Camudar o desaniciar la dirección de corréu electrónicu",
"upload-options": "Opciones de xubía",
"watchthisupload": "Vixilar esti ficheru",
"filewasdeleted": "Yá se xubió y se desanició depués un ficheru con esti nome.\nHabríes comprobar el $1 enantes de volver a xubilu.",
+ "filename-thumb-name": "Esto paez un títulu de miniatura. Por favor, nun xubas miniatures a la mesma wiki de la que salieron. D'otra manera, igua'l nome del ficheru pa que tenga más significáu, y que nun tenga'l prefixu de miniatura.",
"filename-bad-prefix": "El nome del ficheru que tas xubiendo entama con '''\"$1\"''', que ye un nome non descriptivu que de vezu conseñen automáticamente les cámares dixitales.\nPor favor escueyi un nome más descriptivu pal to ficheru.",
"filename-prefix-blacklist": " #<!-- dexa esta llinia exactamente como ta --> <pre>\n# La sintaxis ye la siguiente:\n# * Lo que va del caráuter \"#\" al fin de llinia ye un comentariu\n# * Toa llinia non-balera ye un prefixu pa los nomes de ficheru típicos que ponen les cámares dixitales\nCIMG # Casio\nDSC_ # Nikon\nDSCF # Fuji\nDSCN # Nikon\nDUW # dellos teléfonos móviles\nIMG # xenéricu\nJD # Jenoptik\nMGP # Pentax\nPICT # misc.\n #</pre> <!-- dexa esta llinia exactamente como ta -->",
"upload-success-subj": "Xubida correuta",
"changeemail-no-info": "Для непасрэднага доступу да гэтай старонкі Вам неабходна ўвайсьці ў сыстэму.",
"changeemail-oldemail": "Цяперашні адрас электроннай пошты:",
"changeemail-newemail": "Новы адрас электроннай пошты:",
+ "changeemail-newemail-help": "Поле трэба пакінуць пустым, калі вы хочаце выдаліць свой адрас электроннай пошты. Пасьля выдаленьня вы ня зможаце ануляваць забыты пароль і ня будзеце атрымліваць лісты электроннай пошты з гэтай вікі.",
"changeemail-none": "(няма)",
"changeemail-password": "Ваш пароль у {{GRAMMAR:месны|{{SITENAME}}}}:",
"changeemail-submit": "Зьмяніць адрас электроннай пошты",
"textmatches": "Супадзеньні ў тэкстах старонак",
"notextmatches": "Супадзеньні ў тэкстах старонак ня знойдзеныя",
"prevn": "{{PLURAL:$1|1=папярэдняя|папярэднія}} $1",
- "nextn": "{{PLURAL:$1|наступная|наступныя|наступныя}} $1",
+ "nextn": "{{PLURAL:$1|1=наступная|наступныя}} $1",
"prev-page": "папярэдняя старонка",
"next-page": "наступная старонка",
"prevn-title": "{{PLURAL:$1|Папярэдні $1 вынік|Папярэднія $1 вынікі|Папярэднія $1 вынікаў}}",
"group-bot-member": "робат",
"group-sysop-member": "{{GENDER:$1|адміністратар|адміністратарка}}",
"group-bureaucrat-member": "{{GENDER:$1|бюракрат|бюракратка}}",
- "group-suppress-member": "{{GENDER:$1|рэвізор|рэвізорка}}",
+ "group-suppress-member": "{{GENDER:$1|падаўляльнік|падаўляльніца}} вэрсіяў",
"grouppage-user": "{{ns:project}}:Удзельнікі",
"grouppage-autoconfirmed": "{{ns:project}}:Аўтаматычна пацьверджаныя ўдзельнікі",
"grouppage-bot": "{{ns:project}}:Робаты",
"grouppage-sysop": "{{ns:project}}:Адміністрацыя",
"grouppage-bureaucrat": "{{ns:project}}:Бюракраты",
- "grouppage-suppress": "{{ns:project}}:Ð Ñ\8dвÑ\96зоÑ\80Ñ\8b",
+ "grouppage-suppress": "{{ns:project}}:Ð\9fадаÑ\9eлÑ\8fлÑ\8cнÑ\96кÑ\96_вÑ\8dÑ\80Ñ\81Ñ\96Ñ\8fÑ\9e",
"right-read": "прагляд старонак",
"right-edit": "рэдагаваньне старонак",
"right-createpage": "стварэньне старонак (акрамя старонак абмеркаваньняў)",
"nopagetext": "Пазначанай мэтавай старонкі не існуе.",
"pager-newer-n": "$1 {{PLURAL:$1|навейшая|навейшыя|навейшых}}",
"pager-older-n": "$1 {{PLURAL:$1|старэйшая|старэйшыя|старэйшых}}",
- "suppress": "Ð Ñ\8dвÑ\96заваÑ\86Ñ\8c",
+ "suppress": "Ð\9fадавÑ\96Ñ\86Ñ\8c вÑ\8dÑ\80Ñ\81Ñ\96Ñ\8e",
"querypage-disabled": "Гэта спэцыяльная старонка адключаная для падвышэньня прадукцыйнасьці",
"apihelp": "Даведка API",
"apihelp-no-such-module": "Модуль «$1» ня знойдзены.",
"filerevert-legend": "بیئرگردینتین فایلی",
"filerevert-intro": "شما بی حالی بیئرگردینتینا '''[[Media:$1|$1]]''' بی [$4 نخسه تاریخ $2 سائت $3] هستیت.",
"filerevert-comment": "دلیل:",
- "filerevert-defaultcomment": "بÛ\8cئرگردÛ\8cÙ\86تÛ\8cÙ\86 بئ $1 ئÛ\8c Ù\86خسÙ\87 ئا سائت $2 ($3)",
+ "filerevert-defaultcomment": "بÛ\8eرگردÛ\8cÙ\86تÛ\8cÙ\86 بÙ\90Ù\87 Ù\86خسÙ\87 $2Ø\8c سائت $1 ($3)",
"filerevert-submit": "بیئرگشت",
"filerevert-success": "<strong>[[Media:$1|$1]]</strong> بی [$4 نخسه ئی بی تاریخی $2 سائت $3] ئا بیئرگشتینته بوت.",
"filerevert-badversion": "قدیمیتیرین نخسه شه ای فایلا موجود نه اینت.",
"undeletepage": "Mostra i restaura pàgines esborrades",
"undeletepagetitle": "'''A continuació teniu revisions eliminades de [[:$1]]'''.",
"viewdeletedpage": "Visualitza les pàgines eliminades",
- "undeletepagetext": "{{PLURAL:|S'ha eliminat la pàgina següent, però encara és a l'arxiu i pot ser restaurada|S'han eliminat les $1 pàgines següents, però encara són a l'arxiu i poden ser restaurades}}.\nL'arxiu pot ser netejat periòdicament.",
+ "undeletepagetext": "{{PLURAL:$1|S'ha eliminat la pàgina següent, però encara és a l'arxiu i pot ser restaurada|S'han eliminat les $1 pàgines següents, però encara són a l'arxiu i poden ser restaurades}}.\nL'arxiu pot ser netejat periòdicament.",
"undelete-fieldset-title": "Restaura revisions",
"undeleteextrahelp": "Per a restaurar l'historial sencer de la pàgina, deixeu totes les caselles sense seleccionar i feu clic a '''''{{int:undeletebtn}}'''''.\nPer a realitzar una restauració selectiva, marqueu les caselles que corresponguin a les revisions que voleu recuperar, i feu clic a '''''{{int:undeletebtn}}'''''.",
"undeleterevisions": "{{PLURAL:$1|Una revisió suprimida|$1 revisions suprimides}}",
"nstab-template": "обраꙁьць",
"nstab-help": "страница помощи",
"nstab-category": "катигорїꙗ",
+ "mainpage-nstab": "главьна страница",
"nosuchspecialpage": "си нарочнꙑ страницѧ нѣстъ",
"error": "блаꙁна",
"internalerror": "вънѫтрѣнꙗ блаꙁна",
"block-log-flags-anononly": "тъкъмо анѡнѷмьнꙑ польꙃєватєлє",
"move-page": "прѣимєнованиѥ ⁖ $1 ⁖",
"move-page-legend": "страницѧ прѣимєнованиѥ",
- "movearticle": "страница :",
"newtitle": "ново имѧ :",
"move-watch": "си страницѧ блюдєниѥ",
"movepagebtn": "прѣимєнованиѥ",
"nstab-template": "Şablon",
"nstab-help": "Pela peşti",
"nstab-category": "Kategoriye",
+ "mainpage-nstab": "Pela seri",
"nosuchaction": "Fealiyeto wınasi çıniyo",
"nosuchactiontext": "URL ra kar qebul nêbı.\nŞıma belka URL şaş nuşt, ya zi gıreyi şaş ra ameyi.\nKeyepelê {{SITENAME}} eşkeno xeta eşkera bıkero.",
"nosuchspecialpage": "Pela xasa wınasiye çıniya",
"tooltip-pt-login": "Mayê şıma ronıştış akerdışi rê dawet keme; labelê ronıştış mecburi niyo",
"tooltip-pt-logout": "Bıveciye",
"tooltip-ca-talk": "Zerrekê pele sero werênayış",
- "tooltip-ca-edit": "Tı şenay na pele bıvurnê. Kerem ke, qeydkerdış ra ver gocega verqayti bıgurene.",
+ "tooltip-ca-edit": "Ena pele bıvurne",
"tooltip-ca-addsection": "Zu bınnusteya newi ak",
"tooltip-ca-viewsource": "Ena pele kılit biya.\nŞıma şenê çımeyê aye bıvênê",
"tooltip-ca-history": "Versiyonê verênê ena pele",
"api-error-badaccess-groups": "Ena wiki de dosya barkerdışi rê mısade nêdeyêno.",
"api-error-badtoken": "Xetaya zerreki: Antışo xırabın.",
"api-error-copyuploaddisabled": "URL barkerdış ena waster dı qefılyayo.",
- "api-error-duplicate": "Ena {{PLURAL:$1|ze ke zeq|biya zey dosya da}} zeq wesiqa biya wendeyê.",
+ "api-error-duplicate": "Pele de xora be nê zerreki ra {{PLURAL:$1|dosyaya bine esta|dosyeyê bini estê}}.",
"api-error-duplicate-archive": "Ena {{PLURAL:$1|vurneyaya zey na dosya|zerrey cı zey dosya}} aseno,feqet {{PLURAL:$1|ena dosya|tewr veri}} besterneyaya.",
"api-error-empty-file": "Dosyaya ke şıma rışta venga.",
"api-error-emptypage": "Newi, pelaya veng vıraştışi rê mısade nêdeyêno.",
"confirmdeletetext": "Vas a esborral una páhina/imahin i el su estorial de horma permanenti.\nPol favol, confirma que realmenti quieis hazel esu, qu'entiendis las consecuencias, i que lo hazis dalcuerdu cola\n[[{{MediaWiki:Policy-url}}]].",
"actioncomplete": "Ación acabihá",
"deletedtext": "S'á esborrau \"$1\" corretamenti.\nConsurta $2 pa vel los úrtimus esborraus.",
- "dellogpage": "Rustrihu d´esborrau",
+ "dellogpage": "Rustrihu d'esborrau",
"dellogpagetext": "Embahu se muestra una lista colos úrtimus esborraus.",
"deletionlog": "rustrihu d´esborrau",
"reverted": "Revertiu a la úrtima revisión",
"broken-file-category": "Pages avec des liens de fichiers brisés",
"about": "À propos",
"article": "Page de contenu",
- "newwindow": "(ouvre une nouvelle fenêtre)",
+ "newwindow": "(ouvre dans une nouvelle fenêtre)",
"cancel": "Annuler",
"moredotdotdot": "Plus...",
"morenotlisted": "Cette liste n’est pas complète.",
"move-page-legend": "Renommer une page",
"movepagetext": "Utilisez le formulaire ci-dessous pour renommer une page, en déplaçant tout son historique vers le nouveau nom. L'ancien titre deviendra une page de redirection vers le nouveau titre. Vous pouvez mettre à jour automatiquement les redirections actuelles qui pointent vers le titre original. Si vous choisissez de ne pas le faire, assurez-vous de vérifier toute [[Special:DoubleRedirects|double redirection]] ou [[Special:BrokenRedirects|redirection cassée]]. Vous avez la responsabilité de vous assurer que les liens continuent de pointer vers leur destination supposée.\n\nNotez que la page ne sera '''pas''' renommée s'il existe déjà une page avec le nouveau titre, sauf si cette dernière est une simple redirection avec un historique de modifications vierge. Ceci permet de renommer une page vers sa position d'origine si le déplacement s'avère erroné.\n\n'''Attention !'''\nCeci peut provoquer un changement radical et imprévu pour une page souvent consultée ; assurez-vous d'en avoir compris les conséquences avant de continuer.",
"movepagetext-noredirectfixer": "Utilisez le formulaire ci-dessous pour renommer une page, en déplaçant tout son historique vers le nouveau nom.\nL'ancien titre deviendra une page de redirection vers le nouveau titre.\nVérifiez bien les [[Special:DoubleRedirects|doubles redirections]] ou les [[Special:BrokenRedirects|redirections cassées]].\nVous avez la responsabilité de vous assurer que les liens continuent de pointer vers leur destination supposée.\n\nNotez que la page ne sera '''pas''' déplacée s'il existe déjà une page avec le nouveau titre, sauf si cette dernière a un historique de modifications vierge et est soit vide, soit une simple redirection. Ceci permet de renommer une page vers sa position d'origine si le déplacement s'avère erroné, et il est impossible d'écraser une page existante.\n\n'''Attention !'''\nCeci peut provoquer un changement radical et imprévu pour une page souvent consultée ; assurez-vous d'en avoir compris les conséquences avant de continuer.",
- "movepagetalktext": "Si vous cochez cette case, la page de discussion associée sera automatiquement renommée, à moins qu’une page de discussion non vide existe déjà sous le nouveau nom.\n\nDans ce cas, vous devrez renommer ou fusionner cette page de discussion manuellement si vous le désirez.",
+ "movepagetalktext": "Si vous cochez cette case, la page de discussion associée sera automatiquement renommée, à moins qu’une page de discussion non vide existe déjà sous ce nouveau nom.\n\nDans ce cas, vous devrez renommer ou fusionner cette page de discussion manuellement si vous le désirez.",
"moveuserpage-warning": "'''Attention :''' Vous êtes sur le point de renommer une page d’utilisateur. Veuillez noter que seule la page sera renommée et que l’utilisateur '''ne''' sera '''pas''' renommé.",
"movecategorypage-warning": "<strong>Avertissement :</strong> Vous êtes sur le point de renommer une page de catégorie. Veuillez noter que seule la catégorie sera renommée et <em>qu’aucune</em> des pages de l’ancienne catégorie ne sera transférée dans la nouvelle.",
"movenologintext": "Pour pouvoir renommer une page, vous devez être [[Special:UserLogin|identifié{{GENDER:||e}}]] avec un compte utilisateur enregistré et d'ancienneté suffisante.",
"viewsource": "Ver o código fonte",
"viewsource-title": "Ver o código fonte de \"$1\"",
"actionthrottled": "Acción limitada",
- "actionthrottledtext": "Como unha medida de loita contra o ''spam'', limítase a realización desta acción a un número determinado de veces nun curto espazo de tempo, e vostede superou este límite.\nInténteo de novo nuns minutos.",
+ "actionthrottledtext": "Como medida contra os abusos, a acción que está realizando está limitada a un número determinado de veces nun periodo curto de tempo, e superou ese límite.\nInténteo de novo nuns minutos.",
"protectedpagetext": "Esta páxina foi protexida para evitar a edición e outras accións.",
"viewsourcetext": "Pode ver e copiar o código fonte desta páxina.",
"viewyourtext": "Pode ver e copiar o código fonte <strong>das súas edicións</strong> nesta páxina.",
"createacct-captcha": "Comprobación de seguridade",
"createacct-imgcaptcha-ph": "Insira o texto que ve enriba",
"createacct-submit": "Crear a conta",
- "createacct-another-submit": "Crear outra conta",
+ "createacct-another-submit": "Crear conta",
"createacct-benefit-heading": "Xente coma vostede elabora {{SITENAME}}.",
"createacct-benefit-body1": "{{PLURAL:$1|edición|edicións}}",
"createacct-benefit-body2": "{{PLURAL:$1|páxina|páxinas}}",
"passwordreset-emailtext-ip": "Alguén (probablemente vostede, desde o enderezo IP $1) solicitou o restablecemento do seu\ncontrasinal de {{SITENAME}} ($4). {{PLURAL:$3|A seguinte conta de usuario está asociada|As seguintes contas de usuarios están asociadas}}\na este enderezo de correo electrónico:\n\n$2\n\n{{PLURAL:$3|Este contrasinal temporal caducará|Estes contrasinais temporais caducarán}} {{PLURAL:$5|nun día|en $5 días}}.\nDebería acceder ao sistema e elixir un novo contrasinal agora. Se outra persoa fixo esta\nsolicitude ou se lembrou o seu contrasinal orixinal e xa non o quere cambiar,\nignore esta mensaxe e continúe empregando o seu contrasinal vello.",
"passwordreset-emailtext-user": "O usuario $1 solicitou o restablecemento do contrasinal de {{SITENAME}}\n($4). {{PLURAL:$3|A seguinte conta de usuario está asociada|As seguintes contas de usuarios están asociadas}}\na este enderezo de correo electrónico:\n\n$2\n\n{{PLURAL:$3|Este contrasinal temporal caducará|Estes contrasinais temporais caducarán}} {{PLURAL:$5|nun día|en $5 días}}.\nDebería acceder ao sistema e elixir un novo contrasinal agora. Se outra persoa fixo esta\nsolicitude ou se lembrou o seu contrasinal orixinal e xa non o quere cambiar,\nignore esta mensaxe e continúe empregando o seu contrasinal vello.",
"passwordreset-emailelement": "Nome de usuario: \n$1\n\nContrasinal temporal: \n$2",
- "passwordreset-emailsent": "Enviouse o correo electrónico de restablecemento do contrasinal.",
+ "passwordreset-emailsent": "Se esta é unha dirección de correo electrónico rexistrada para a túa conta, entón enviarase un correo electrónico para o restablecemento do teu contrasinal.",
"passwordreset-emailsent-capture": "Enviouse un correo electrónico de restablecemento do contrasinal, mostrado a continuación.",
"passwordreset-emailerror-capture": "Xerouse un correo electrónico de restablecemento do contrasinal, mostrado a continuación, pero o envío {{GENDER:$2|ao usuario|á usuaria}} fallou: $1",
- "changeemail": "Cambiar o enderezo de correo electrónico",
- "changeemail-text": "Encha este formulario para cambiar o seu enderezo de correo electrónico. Terá que escribir o seu contrasinal para confirmar este cambio.",
+ "changeemail": "Cambiar ou eliminar o enderezo de correo electrónico",
+ "changeemail-text": "Encha este formulario para cambiar o seu enderezo de correo electrónico. Terá que escribir o seu contrasinal para confirmar este cambio. Se vostede quere eliminar a asociación da dirección de correo electrónico da súa conta, deixe en branco a nova dirección de correo electrónico cando envíe o formulario.",
"changeemail-no-info": "Debe rexistrarse para acceder directamente a esta páxina.",
"changeemail-oldemail": "Enderezo de correo electrónico actual:",
"changeemail-newemail": "Novo enderezo de correo electrónico:",
"permissionserrorstext-withaction": "Non ten os permisos necesarios para $2, {{PLURAL:$1|pola seguinte razón|polas seguintes razóns}}:",
"recreate-moveddeleted-warn": "'''Atención: Vai volver crear unha páxina que xa foi eliminada anteriormente.'''\n\nDebería considerar se é apropiado continuar a editar esta páxina.\nVelaquí están o rexistro de borrados e mais o de traslados desta páxina, por se quere consultalos:",
"moveddeleted-notice": "Esta páxina foi borrada.\nA continuación pódese ver o rexistro de borrados e traslados desta páxina, por se quere consultalos.",
+ "moveddeleted-notice-recent": "Sentímolo, esta página foi borrada recentemente (dentro das últimas 24 horas).\nO rexistro de borrado e traslado da páxina amósanse abaixo como referencia.",
"log-fulllog": "Ver o rexistro completo",
"edit-hook-aborted": "A edición foi abortada polo asociador.\nEste non deu ningunha explicación.",
"edit-gone-missing": "Non se pode actualizar a páxina.\nSemella que foi borrada.",
"mergehistory-go": "Mostrar as edicións que se poden fusionar",
"mergehistory-submit": "Fusionar as revisións",
"mergehistory-empty": "Non hai revisións que se poidan fusionar.",
- "mergehistory-done": "{{PLURAL:$3|Unha revisión|$3 revisións}} de \"$1\" {{PLURAL:$3|fusionouse|fusionáronse}} sen problemas con \"[[:$2]]\".",
+ "mergehistory-done": "$3 {{PLURAL:$3|revisión|revisións}} de $1 {{PLURAL:$3|fusionouse|fusionáronse}} sen problemas con [[:$2]].",
"mergehistory-fail": "Non se puido fusionar o historial; comprobe outra vez os parámetros de páxina e data.",
"mergehistory-fail-toobig": "Non se puido fusionar o historial, xa que supón trasladar máis revisións que o límite de $1 {{PLURAL:$1|revisión|revisións}}.",
"mergehistory-no-source": "Non existe a páxina de orixe \"$1\".",
"prefs-watchlist-token": "Pase para a lista de vixilancia:",
"prefs-misc": "Preferencias varias",
"prefs-resetpass": "Cambiar o contrasinal",
- "prefs-changeemail": "Cambiar o enderezo de correo electrónico",
+ "prefs-changeemail": "Cambiar ou eliminar o enderezo de correo electrónico",
"prefs-setemail": "Establecer un enderezo de correo electrónico",
"prefs-email": "Opcións de correo electrónico",
"prefs-rendering": "Aparencia",
"group-bot": "Bots",
"group-sysop": "Administradores",
"group-bureaucrat": "Burócratas",
- "group-suppress": "Supervisores",
+ "group-suppress": "Supresores",
"group-all": "(todos)",
"group-user-member": "{{GENDER:$1|usuario|usuaria}}",
"group-autoconfirmed-member": "{{GENDER:$1|usuario autoconfirmado|usuaria autoconfirmada}}",
"group-bot-member": "{{GENDER:$1|bot}}",
"group-sysop-member": "{{GENDER:$1|administrador|administradora}}",
"group-bureaucrat-member": "{{GENDER:$1|burócrata}}",
- "group-suppress-member": "{{GENDER:$1|supervisor|supervisora}}",
+ "group-suppress-member": "{{GENDER:$1|supresor|supresora}}",
"grouppage-user": "{{ns:project}}:Usuarios",
"grouppage-autoconfirmed": "{{ns:project}}:Usuarios autoconfirmados",
"grouppage-bot": "{{ns:project}}:Bots",
"grouppage-sysop": "{{ns:project}}:Administradores",
"grouppage-bureaucrat": "{{ns:project}}:Burócratas",
- "grouppage-suppress": "{{ns:project}}:Supervisores",
+ "grouppage-suppress": "{{ns:project}}:Supresores",
"right-read": "Ler páxinas",
"right-edit": "Editar páxinas",
"right-createpage": "Crear páxinas (que non son de conversa)",
"recentchanges-page-added-to-category-bundled": "\"[[:$1]]\" e {{PLURAL:$2|unha páxina|$2 páxinas}} engadíronse á categoría",
"recentchanges-page-removed-from-category": "\"[[:$1]]\" eliminouse da categoría",
"recentchanges-page-removed-from-category-bundled": "\"[[:$1]]\" e {{PLURAL:$2|unha páxina|$2 páxinas}} elimináronse da categoría",
+ "autochange-username": "Cambio automático de MediaWiki",
"upload": "Subir un ficheiro",
"uploadbtn": "Subir un ficheiro",
"reuploaddesc": "Cancelar a carga e volver ao formulario de carga",
"upload-options": "Opcións de carga",
"watchthisupload": "Vixiar este ficheiro",
"filewasdeleted": "Un ficheiro con ese nome foi cargado con anterioridade e a continuación borrado.\nDebe comprobar o $1 antes de proceder a cargalo outra vez.",
+ "filename-thumb-name": "Semella que este título é dunha miniatura. Non cargue miniaturas no wiki do que as sacou. Se non é así, corrixa o nome do ficheiro para que sexa máis significativo e non teña o prefixo das miniaturas.",
"filename-bad-prefix": "O nome do ficheiro que está cargando comeza con '''\"$1\"''', que é un típico nome non descritivo asignado automaticamente polas cámaras dixitais.\nPor favor, escolla un nome máis descritivo para o seu ficheiro.",
"filename-prefix-blacklist": " #<!-- Deixe esta liña tal e como está --> <pre>\n# A sintaxe é a seguinte:\n# * Todo o que vaia despois dun carácter \"#\" ata o final da liña é un comentario\n# * Toda liña que non estea en branco é un prefixo para os nomes típicos dos ficheiros asignados automaticamente polas cámaras dixitais\nCIMG # Casio\nDSC_ # Nikon\nDSCF # Fuji\nDSCN # Nikon\nDUW # algúns teléfonos móbiles\nIMG # xenérico\nJD # Jenoptik\nMGP # Pentax\nPICT # varios\n #</pre> <!-- Deixe esta liña tal e como está -->",
"upload-success-subj": "A carga realizouse correctamente",
"upload-form-label-infoform-description": "Descrición",
"upload-form-label-usage-title": "Uso",
"upload-form-label-usage-filename": "Nome do ficheiro",
+ "foreign-structured-upload-form-label-own-work": "Isto é o meu propio traballo",
+ "foreign-structured-upload-form-label-infoform-categories": "Categorías",
+ "foreign-structured-upload-form-label-infoform-date": "Data",
"backend-fail-stream": "Non se puido transmitir o ficheiro \"$1\".",
"backend-fail-backup": "Non se puido facer unha copia de seguridade do ficheiro \"$1\".",
"backend-fail-notexists": "O ficheiro \"$1\" non existe.",
"nopagetext": "A páxina que especificou non existe.",
"pager-newer-n": "{{PLURAL:$1|unha posterior|$1 posteriores}}",
"pager-older-n": "{{PLURAL:$1|unha anterior|$1 anteriores}}",
- "suppress": "Supervisor",
+ "suppress": "Supresor",
"querypage-disabled": "Esta páxina especial está desactivada por razóns de rendemento.",
"apihelp": "Axuda coa API",
"apihelp-no-such-module": "Non se atopou o módulo \"$1\".",
"emailccsubject": "Copia da súa mensaxe para $1: $2",
"emailsent": "Mensaxe enviada",
"emailsenttext": "A súa mensaxe de correo electrónico foi enviada.",
- "emailuserfooter": "Este correo electrónico foi enviado por $1 a $2 mediante a función \"{{int:emailuser}}\" en {{SITENAME}}.",
+ "emailuserfooter": "Este correo electrónico foi {{GENDER:$1|enviado}} por $1 a {{GENDER:$2|$2}} mediante a función \"{{int:emailuser}}\" en {{SITENAME}}.",
"usermessage-summary": "Mensaxe deixada polo sistema.",
"usermessage-editor": "Editor das mensaxes do sistema",
"watchlist": "Lista de vixilancia",
"deletepage": "Borrar a páxina",
"confirm": "Confirmar",
"excontent": "o contido era: \"$1\"",
- "excontentauthor": "o contido era: \"$1\" (e o único editor foi [[Special:Contributions/$2|$2]])",
+ "excontentauthor": "o contido era: \"$1\", e o único editor foi \"[[Special:Contributions/$2|$2]]\" ([[User talk:$2|conversa]])",
"exbeforeblank": "o contido antes do baleirado era: \"$1\"",
"delete-confirm": "Borrar \"$1\"",
"delete-legend": "Borrar",
"move-page-legend": "Mover unha páxina",
"movepagetext": "Ao usar o formulario inferior vai cambiar o nome da páxina, movendo todo o seu historial ao novo nome.\nO título vello vaise converter nunha páxina de redirección ao novo título.\nPode actualizar automaticamente as redireccións que van dar ao título orixinal.\nSe escolle non facelo, asegúrese de verificar que non hai redireccións [[Special:DoubleRedirects|dobres]] ou [[Special:BrokenRedirects|crebadas]].\nVostede é responsable de asegurarse de que as ligazóns continúan a apuntar cara a onde se supón que deberían.\n\nTeña en conta que a páxina '''non''' será trasladada se xa existe unha páxina co novo título, a menos que esta última sexa unha redirección e non teña historial de edicións.\nIsto significa que pode volver renomear unha páxina ao seu nome antigo se comete un erro, e que non pode sobrescribir unha páxina que xa existe.\n\n'''Atención!'''\nEste cambio nunha páxina popular pode ser drástico e inesperado;\npor favor, asegúrese de que entende as consecuencias disto antes de proseguir.",
"movepagetext-noredirectfixer": "Ao usar o formulario inferior vai cambiar o nome da páxina, movendo todo o seu historial ao novo nome.\nO título vello vaise converter nunha páxina de redirección ao novo título.\nAsegúrese de verificar que non hai redireccións [[Special:DoubleRedirects|dobres]] ou [[Special:BrokenRedirects|crebadas]].\nVostede é responsable de asegurarse de que as ligazóns continúan a apuntar cara a onde se supón que deberían.\n\nTeña en conta que a páxina '''non''' será trasladada se xa existe unha páxina co novo título, a menos que esta última sexa unha redirección e non teña historial de edicións.\nIsto significa que pode volver renomear unha páxina ao seu nome antigo se comete un erro, e que non pode sobrescribir unha páxina que xa existe.\n\n'''Atención!'''\nEste cambio nunha páxina popular pode ser drástico e inesperado;\npor favor, asegúrese de que entende as consecuencias disto antes de proseguir.",
- "movepagetalktext": "A páxina de conversa asociada, se existe, será automaticamente movida con esta '''agás que''':\n*Estea a mover a páxina empregando espazos de nomes,\n*Xa exista unha páxina de conversa con ese nome, ou\n*Desactive a opción de abaixo.\n\nNestes casos, terá que mover ou mesturar a páxina manualmente se o desexa.",
+ "movepagetalktext": "Se marca esta caixa, a páxina de conversa asociada trasladarase automáticamente ó título novo a menos que xa exista unha páxina de conversa non baleira alí.\n\nNeste caso, deberá trasladar ou fusionar manualmente a páxina se así o quere.",
"moveuserpage-warning": "'''Aviso:''' Está a piques de mover unha páxina de usuario. Por favor, teña en conta que só se trasladará a páxina e que o usuario '''non''' será renomeado.",
"movecategorypage-warning": "'''Aviso:''' Está a piques de mover unha páxina de categoría. Por favor, teña en conta que só se trasladará a páxina e que as páxinas presentes na categoría vella '''non''' serán recategorizadas na categoría nova.",
"movenologintext": "Debe ser un usuario rexistrado e [[Special:UserLogin|acceder ao sistema]] para mover unha páxina.",
"cant-move-to-user-page": "Non ten os permisos necesarios para mover unha páxina a unha páxina de usuario (agás a unha subpáxina).",
"cant-move-category-page": "Non ten os permisos necesarios para mover páxinas de categoría.",
"cant-move-to-category-page": "Non ten os permisos necesarios para mover unha páxina a unha páxina de categoría.",
- "newtitle": "Ao novo título:",
+ "newtitle": "Novo título:",
"move-watch": "Vixiar esta páxina",
"movepagebtn": "Mover a páxina",
"pagemovedsub": "O movemento foi un éxito",
"logentry-newusers-byemail": "$1 {{GENDER:$2|creou}} a conta de usuario $3; o contrasinal enviouse por correo electrónico",
"logentry-newusers-autocreate": "A conta de {{GENDER:$2|usuario|usuaria}} $1 creouse automaticamente",
"logentry-protect-move_prot": "$1 {{GENDER:$2|trasladou}} a protección de \"$4\" a \"$3\"",
+ "logentry-protect-unprotect": "$1 {{GENDER:$2|eliminou}} a protección de $3",
+ "logentry-protect-protect": "$1 {{GENDER:$2|protexeu}} a $3 $4",
+ "logentry-protect-protect-cascade": "$1 {{GENDER:$2|protexeu}} a $3 $4 [en cascada]",
+ "logentry-protect-modify": "$1 {{GENDER:$2|cambiou}} o nivel de protección de $3 $4",
+ "logentry-protect-modify-cascade": "$1 {{GENDER:$2|cambiou}} o nivel de protección de $3 $4 [en cascada]",
"logentry-rights-rights": "$1 {{GENDER:$2|cambiou}} o grupo ao que pertence $3 de $4 a $5",
"logentry-rights-rights-legacy": "$1 {{GENDER:$2|cambiou}} o grupo ao que pertence $3",
"logentry-rights-autopromote": "$1 foi {{GENDER:$2|promovido|promovida}} automaticamente de $4 a $5",
"exif-bitspersample": "ביטים לרכיב",
"exif-compression": "תבנית דחיסה",
"exif-photometricinterpretation": "הרכב פיקסלים",
- "exif-orientation": "×\9b×\99×\95×\95× ×\99×\95ת",
+ "exif-orientation": "×\9b×\99×\95×\95×\9f ×\9eצ×\9c×\9e×\94",
"exif-samplesperpixel": "מספר רכיבים",
"exif-planarconfiguration": "סידור מידע",
"exif-ycbcrsubsampling": "הפחתת יחס Y ל־C",
"permissionserrorstext-withaction": "Anda tidak memiliki hak akses untuk $2, karena {{PLURAL:$1|alasan|alasan}} berikut:",
"recreate-moveddeleted-warn": "'''Peringatan: Anda membuat ulang suatu halaman yang sudah pernah dihapus.'''\n\nHarap pertimbangkan apakah layak untuk melanjutkan suntingan Anda.\nBerikut adalah log penghapusan dan pemindahan dari halaman ini:",
"moveddeleted-notice": "Halaman ini telah dihapus.\nSebagai referensi, berikut adalah log penghapusan dan pemindahan halaman ini.",
+ "moveddeleted-notice-recent": "Maaf, halaman ini telah dihapus (dalam 24 jam ini). Sebagai referensi, berikut adalah log penghapusan atau pemindahan halaman ini.",
"log-fulllog": "Lihat seluruh log",
"edit-hook-aborted": "Suntingan dibatalkan oleh kait parser\ntanpa ada keterangan.",
"edit-gone-missing": "Tidak dapat memperbaharui halaman.\nHalaman kemungkinan telah dihapus.",
"Aquatech",
"Statix64",
"CassiodoroVicinetti",
- "Bultro"
+ "Bultro",
+ "Oggioniale"
]
},
"tog-underline": "Sottolinea i collegamenti:",
"viewsource": "Visualizza wikitesto",
"viewsource-title": "Visualizza wikitesto di $1",
"actionthrottled": "Azione ritardata",
- "actionthrottledtext": "Come misura di sicurezza contro lo spam, l'esecuzione di alcune azioni è limitata a un numero massimo di volte in un determinato periodo di tempo, limite che in questo caso è stato superato. Si prega di riprovare tra qualche minuto.",
+ "actionthrottledtext": "Come misura di sicurezza contro lo spam, l'esecuzione di alcune azioni è limitata a un numero massimo di volte in un determinato periodo di tempo, limite che tu hai superato. Si prega di riprovare tra qualche minuto.",
"protectedpagetext": "Questa pagina è stata protetta per impedirne la modifica o altre operazioni.",
"viewsourcetext": "È possibile visualizzare e copiare il codice sorgente di questa pagina.",
"viewyourtext": "È possibile visualizzare e copiare il codice sorgente delle <strong>tue modifiche</strong> a questa pagina.",
"upload-options": "Opzioni di caricamento",
"watchthisupload": "Aggiungi agli osservati speciali",
"filewasdeleted": "Un file con questo nome è stato già caricato e cancellato in passato. Verificare il log delle $1 prima di caricarlo di nuovo.",
+ "filename-thumb-name": "Questo sembra essere il titolo di una miniatura. Non caricare le miniature sulla stessa wiki. Oppure, modifica il nome del file in modo che sia più significativo e non abbia il prefisso della miniatura.",
"filename-bad-prefix": "Il nome del file che si sta caricando inizia con '''\"$1\"''', che è un nome generico simile a quelli assegnati automaticamente dalle fotocamere digitali. Si prega di scegliere un nome più descrittivo per il file.",
"filename-prefix-blacklist": " #<!-- lascia questa riga esattamente com'è --> <pre>\n# La sintassi è la seguente:\n# * Tutto ciò che segue il carattere \"#\" sino alla fine della riga è un commento\n# * Ogni riga non vuota è un prefisso per nomi di file tipici assegnati automaticamente da fotocamere digitali\nCIMG # Casio\nDSC_ # Nikon\nDSCF # Fuji\nDSCN # Nikon\nDUW # alcuni telefonini\nIMG # generic\nJD # Jenoptik\nMGP # Pentax\nPICT # misc.\n #</pre> <!-- lascia questa riga esattamente com'è -->",
"upload-success-subj": "Caricamento completato",
"move-page-legend": "ページの移動",
"movepagetext": "下のフォームを使用すると、ページ名を変更でき、そのページの履歴も変更先に移動できます。\n移動元のページは移動先への転送ページになります。\n移動元のページへの転送ページを自動的に修正できます。\n[[Special:DoubleRedirects|二重転送]]や[[Special:BrokenRedirects|迷子のリダイレクト]]を確認する必要があります。\nリンクを正しく維持するのは移動した人の責任です。\n\n移動先のページが既に存在する場合は、その移動先が転送ページであり、かつ過去の版を持たない場合以外は移動<strong>できません</strong>。\nつまり、間違えてページ名を変更した場合には元に戻せます。また移動によって既存のページを上書きしてしまうことはありません。\n\n<strong>注意!</strong>\nよく閲覧されるページや、他の多くのページからリンクされているページを移動すると予期しない結果が起こるかもしれません。\nページの移動に伴う影響をよく考えてから踏み切るようにしてください。",
"movepagetext-noredirectfixer": "下のフォームを使用すると、ページ名を変更でき、そのページの履歴も変更先に移動できます。\n移動元のページは移動先への転送ページになります。\n自動的な修正を選択しない場合は、[[Special:DoubleRedirects|二重転送]]や[[Special:BrokenRedirects|迷子のリダイレクト]]を確認する必要があります。\nつながるべき場所にリンクがつながるよう維持するのは移動した人の責任です。\n\n移動先が既に存在する場合は、そのページが転送ページであり、かつ過去の版を持たない場合を除いて移動<strong>できません</strong>。\nつまり、間違えてページ名を変更した場合には元に戻せます。また移動によって既存のページを上書きしてしまうことはありません。\n\n<strong>警告!</strong>\n多く閲覧されるページや多くリンクされているページを移動すると、予期しない大きな変化が起こるかもしれないことにご注意ください。\nページの移動に伴う影響をよく考えてから移動してください。",
- "movepagetalktext": "ã\81\93ã\81\93ã\81«ã\83\81ã\82§ã\83\83ã\82¯ã\82\92ä»\98ã\81\91ã\82\8bã\81¨ã\80\81é\96¢é\80£ä»\98ã\81\91ã\82\89ã\82\8cã\81\9fã\83\88ã\83¼ã\82¯ã\83\9aã\83¼ã\82¸ã\82\82ä¸\80ç·\92ã\81«ã\80\81è\87ªå\8b\95ç\9a\84ã\81«æ\96°ã\81\97ã\81\84ã\82¿ã\82¤ã\83\88ã\83«ã\81«ç§»å\8b\95ã\81\95ã\82\8cã\81¾ã\81\99ã\80\82ã\81\9fã\81 ã\81\97ã\80\81移å\8b\95å\85\88ã\81«ã\80\81空ではないトークページが既に存在する場合を除きます。\n\nこの場合、手動でトークページを移動または統合する必要があります。",
+ "movepagetalktext": "ã\81\93ã\81\93ã\81«ã\83\81ã\82§ã\83\83ã\82¯ã\82\92ä»\98ã\81\91ã\82\8bã\81¨ã\80\81é\96¢é\80£ä»\98ã\81\91ã\82\89ã\82\8cã\81\9fã\83\88ã\83¼ã\82¯ã\83\9aã\83¼ã\82¸ã\82\82ä¸\80ç·\92ã\81«ã\80\81è\87ªå\8b\95ç\9a\84ã\81«æ\96°ã\81\97ã\81\84ã\83\9aã\83¼ã\82¸å\90\8dã\81«ç§»å\8b\95ã\81\95ã\82\8cã\81¾ã\81\99ã\80\82ã\81\9fã\81 ã\81\97ã\80\81移å\8b\95å\85\88ã\81«空ではないトークページが既に存在する場合を除きます。\n\nこの場合、手動でトークページを移動または統合する必要があります。",
"moveuserpage-warning": "<strong>警告:</strong> 利用者ページを移動しようとしています。この操作ではページのみが移動され、利用者名は<em>変更されない</em>点に注意してください。",
"movecategorypage-warning": "<strong>警告:</strong> カテゴリのページを移動させようとしています。カテゴリのページのみが移動するため、元のカテゴリに属していたどのページも新しいカテゴリには移動 <em>しない</em> ことにご注意ください。",
"movenologintext": "ページを移動するためには、登録利用者でありかつ、[[Special:UserLogin|ログイン]]している必要があります。",
"tooltip-rollback": "Mbalèkaké suntingan-suntingan ing kaca iki menyang kontributor pungkasan nganggo sak klik.",
"tooltip-undo": "Mbalèkaké révisi iki lan mbukak kothak panyuntingan jroning mode pratayang. Wènèhi kasempatan kanggo ngisi alesan ing kothak ringkesan.",
"tooltip-preferences-save": "Simpen préperensi",
- "tooltip-summary": "Lebkaké ringkesan cedhèk",
+ "tooltip-summary": "Lebokna ringkesan cendhèk",
"anonymous": "{{PLURAL:$1|Panganggo|panganggo}} anon ing {{SITENAME}}.",
"siteuser": "Panganggo {{SITENAME}} $1",
"anonuser": "Panganggo anonim {{SITENAME}} $1",
"undo-failure": "Dat kunnt mer nit zeröck nämme, dä Afschnedd wood enzwesche ald widder beärbeidt.",
"undo-norev": "Do kam_mer nix zeröck nämme. Di väsjohn jidd_et nit, udder se es verschtoche udder fottjeschmeße woode.",
"undo-nochange": "Di Änderong schingk ald retuur jemaat woode ze sin.",
- "undo-summary": "De Änderong $1 fum [[Special:Contributions/$2|$2]] ([[User talk:$2|Klaaf]]) zeröck jenomme.",
+ "undo-summary": "Di Änderong $1 wood {{GENDER:$2|vum|vum|vumm Metmaacher|vun dä|vum}} [[Special:Contributions/$2|$2]] ([[User talk:$2|Klaaf]]) zeröck jenomme.",
"undo-summary-username-hidden": "Nemm di Väsjohn $1 vun enem verschtoche Metmaacher widder retuhr.",
"cantcreateaccounttitle": "Kann keine Zojang enrichte",
"cantcreateaccount-text": "Dä [[User:$3|$3]] hät verbodde, dat mer sich vun dä IP-Adress '''$1''' uß als ene neue Metmaacher aanmelde könne soll.\n\nAls Jrund för et Sperre es enjedraare: ''$2''",
"recentchangeslinked-summary": "Heh di {{int:nstab-special}} hädd en Leß met Änderonge aan Sigge, di vun dä aanjejovve Sigg uß verlengk sin.\nBei Saachjroppe sen et de Sigge en dä Saachjropp.\nSigge uß Dinge [[Special:Watchlist|Opaßleß]] sin en '''Fättschreff''' jeschrevve.",
"recentchangeslinked-page": "Dä Sigg ier Övverschreff:",
"recentchangeslinked-to": "Zeisch de Änderonge aan dä Sigge, woh Lengks op di aanjejovve Sigg drop sin",
+ "autochange-username": "Automattesche Ännderong aam MediaWiki",
"upload": "Daate huhlade",
"uploadbtn": "Huhlade!",
"reuploaddesc": "Zeröck noh de Sigg zem Huhlade.",
"cant-move-to-user-page": "Do häs nit dat Rääsch, en Sigg tirkäk op en Metmaacher-Sigg ömzenänne, Do kanns se ävver op en Ungersigg dofun ömnenne.",
"cant-move-category-page": "Do häß nit dat Rääsch, Saachjroppesigge ömzebenänne.",
"cant-move-to-category-page": "Do häß nit dat Rääsch, en Sigg obb en Saachjroppesigg ömzebenänne.",
- "newtitle": "op dä neue Nahme",
+ "newtitle": "Dä neuje Nahme:",
"move-watch": "Op di Sigg heh oppaßße",
"movepagebtn": "Ömnenne",
"pagemovedsub": "Dat Ömnenne hät jeflupp",
"group-bot-member": "{{GENDER:$1|Bot}}",
"group-sysop-member": "{{GENDER:$1|Administrateur|Administratrice}}",
"group-bureaucrat-member": "{{GENDER:$1|Bürokrat|Bürokratin}}",
- "group-suppress-member": "{{GENDER:$1|Iwwersiicht}}",
+ "group-suppress-member": "{{GENDER:$1|Ënnerdrécker}}",
"grouppage-user": "{{ns:project}}:Benotzer",
"grouppage-autoconfirmed": "{{ns:project}}:Registréiert Benotzer",
"grouppage-bot": "{{ns:project}}:Botten",
"upload-form-label-infoform-description": "Beschreiwung",
"upload-form-label-usage-title": "Benotzung",
"upload-form-label-usage-filename": "Numm vum Fichier",
+ "foreign-structured-upload-form-label-own-work": "Dëst ass mäin eegent Wierk",
"foreign-structured-upload-form-label-infoform-categories": "Kategorien",
"foreign-structured-upload-form-label-infoform-date": "Datum",
"backend-fail-stream": "De Fichier $1 konnt net iwwerdroe ginn.",
"recentchangeslinked-summary": "Tai paskutinių keitimų, atliktų puslapiuose, į kuriuos yra nuoroda iš nurodyto puslapio (arba į nurodytos kategorijos narius), sąrašas.\nPuslapiai iš jūsų [[Special:Watchlist|stebimųjų sąrašo]] yra '''paryškinti'''.",
"recentchangeslinked-page": "Puslapio pavadinimas:",
"recentchangeslinked-to": "Rodyti su duotuoju puslapiu susijusių puslapių pakeitimus",
+ "recentchanges-page-added-to-category": "[[:$1]] pridėta prie kategorijos",
+ "recentchanges-page-removed-from-category": "[[:$1]] pašalinta iš kategorijos",
+ "autochange-username": "MediaWiki automatinis pakeitimas",
"upload": "Įkelti rinkmeną",
"uploadbtn": "Įkelti rinkmeną",
"reuploaddesc": "Atšaukti įkėlimą ir grįžti į įkėlimo formą.",
"nstab-template": "साचा",
"nstab-help": "साहाय्य पान",
"nstab-category": "वर्ग",
+ "mainpage-nstab": "मुख्य पान",
"nosuchaction": "अशी कृती अस्तित्वात नाही",
"nosuchactiontext": "URL ने नमूद केलेली कृती चुकीची आहे.\nतुम्ही कदाचित URL चुकीची दिली असेल, किंवा चुकीच्या दुव्यावर टिचकी दिली असेल.\nकदाचित, ही कृती {{SITENAME}} वापरत असलेल्या सॉफ्टवेअर मधील गणकदोष \nसुद्धा दर्शवीत असेल.",
"nosuchspecialpage": "असे कोणतेही विशेष पृष्ठ अस्तित्वात नाही",
"viewsource": "स्रोत पहा",
"viewsource-title": "$1 चा उगम बघा",
"actionthrottled": "कृती नियामक(थ्रॉटल) केली",
- "actionthrottledtext": "à¤\86à¤\82तरà¤\9cाल-à¤\9aिà¤\96लणà¥\80 विरà¥\8bधà¥\80 à¤\89पायाà¤\9aà¥\8dया दà¥\83षà¥\8dà¤\9fà¥\80नà¥\87(à¤\85à¤\81à¤\9fà¥\80 सà¥\8dपà¥\85म मà¥\87à¤\9dर ), हà¥\80 à¤\95à¥\83तà¥\80 थà¥\8bडà¥\8dया à¤\95ालावधà¥\80त à¤\85सà¤\82à¤\96à¥\8dयवà¥\87ळा à¤\95रणà¥\8dयापासà¥\82न, तà¥\81मà¥\8dहाला पà¥\8dरतिबà¤\82धित à¤\95रणà¥\8dयात à¤\86लà¥\87 à¤\86हà¥\87, à¤\86णि à¤\86पण या मरà¥\8dयादà¥\87à¤\9aà¥\87 à¤\89लà¥\8dलà¤\82à¤\98न à¤\95à¥\87लà¥\87 à¤\86हà¥\87. à¤\95à¥\83पया थà¥\8bडà¥\8dया वà¥\87ळानà¥\87 पुन्हा प्रयत्न करा.",
+ "actionthrottledtext": "à¤\85पशबà¥\8dद-विरà¥\8bधà¥\80 à¤\89पायाà¤\9aà¥\8dया दà¥\83षà¥\8dà¤\9fà¥\80नà¥\87(à¤\85à¤\81à¤\9fà¥\80-à¤\85बà¥\8dयà¥\81à¤\9c मà¥\87à¤\9dर ),हà¥\80 à¤\95à¥\83तà¥\80 थà¥\8bडà¥\8dया à¤\95ालावधà¥\80त à¤\85सà¤\82à¤\96à¥\8dयवà¥\87ळा à¤\95रणà¥\8dयापासà¥\82न, तà¥\81मà¥\8dहाला पà¥\8dरतिबà¤\82धित à¤\95रणà¥\8dयात à¤\86लà¥\87 à¤\86हà¥\87, à¤\86णि à¤\86पण या मरà¥\8dयादà¥\87à¤\9aà¥\87 à¤\89लà¥\8dलà¤\82à¤\98न à¤\95à¥\87लà¥\87 à¤\86हà¥\87. à¤\95à¥\83पया à¤\95ाहà¥\80 मिनà¥\80à¤\9fाà¤\82नà¤\82तर पुन्हा प्रयत्न करा.",
"protectedpagetext": "हे पान बदल अथवा इतर कृती होऊ नयेत म्हणून सुरक्षित केले आहे.",
"viewsourcetext": "तुम्ही या पानाचा स्रोत पाहू शकता व प्रत करू शकता:",
"viewyourtext": "तुम्ही या पानाची, '''तुमची संपादने''' पाहू शकता व त्याची प्रत करू शकता:",
"createacct-captcha": "सुरक्षा तपासणी",
"createacct-imgcaptcha-ph": "वर दिसत असलेला मजकूर येथे टाका",
"createacct-submit": "आपले खाते निर्माण करा",
- "createacct-another-submit": "दà¥\81सरà¥\87 नवà¥\80न à¤\96ातà¥\87 तयार à¤\95रा",
+ "createacct-another-submit": "खाते तयार करा",
"createacct-benefit-heading": "{{SITENAME}} हे आपल्यासारख्याच लोकांनी बनविलेले आहे.",
"createacct-benefit-body1": "{{PLURAL:$1|edit|संपादने}}",
"createacct-benefit-body2": "{{PLURAL:$1|लेख}}",
"passwordreset-emailtext-ip": "कुणीतरी (कदाचित तुम्ही, अंकपत्ता $1 वरुन) {{SITENAME}}($4) करिता नविन 'परवलीचा शब्द' पुनर्स्थापनेबद्दल विनंती केली आहे.\nखालील{{PLURAL:$3|सदस्यखाते}}या विपत्रपत्त्याशी निगडीत आहे: \n\"$2\"\n{{PLURAL:$3|हा तात्पुरता परवलीचा शब्द|हे तात्पुरते परवलीचे शब्द}}{{PLURAL:$5|एक दिवस|$5 दिवसात}} मुदतबाह्य होतील.आता आपण लॉग-ईन करून नविन परवलीचा शब्द निवडा.जर ईतर कोणी ही विनंती केली असेल,किंवा जर आपणास परवलीच शब्द आठवला असेल तर,व जर आपण तो बदलु इच्छित नसाल तर आपण हा संदेश टाळा व आपला जुना परवलीचा शब्द वापरणे सुरू ठेवा.",
"passwordreset-emailtext-user": " {{SITENAME}}वरील सदस्य $1ने {{SITENAME}}($4) करिता नविन 'परवलीचा शब्द' पुनर्स्थापनेबद्दल विनंती केली आहे.\nखालील{{PLURAL:$3|सदस्यखाते}}या विपत्रपत्त्याशी निगडीत आहे: \n\n\"$2\"\n\n{{PLURAL:$3|हा तात्पुरता परवलीचा शब्द|हे तात्पुरते परवलीचे शब्द}}{{PLURAL:$5|एक दिवस|$5 दिवसात}} मुदतबाह्य होतील.आता आपण लॉग-ईन करून नविन परवलीचा शब्द निवडा.जर ईतर कोणी ही विनंती केली असेल,किंवा जर आपणास परवलीच शब्द आठवला असेल तर,व, जर आपण तो बदलु इच्छित नसाल तर आपण हा संदेश टाळा व आपला जुना परवलीचा शब्द वापरणे सुरू ठेवा.",
"passwordreset-emailelement": "सदस्यनाव: \n$1\n\nअस्थायी परवलीचा शब्द: \n$2",
- "passwordreset-emailsent": "'परवलीचा शब्द' पुनर्स्थापनेबाबत एक विपत्र पाठवण्यात आले आहे.",
+ "passwordreset-emailsent": "जर हा आपल्या खात्याचा नोंदणिकृत विपत्रपत्ता असेल तर, परवलीच्या शब्दाच्या पुनर्स्थापनेबाबत एक विपत्र पाठवण्यात येईल.",
"passwordreset-emailsent-capture": "'परवलीचा शब्द' पुनर्स्थापनेबाबत एक विपत्र पाठवण्यात आले आहे जे खाली दर्शविण्यात आले आहे.",
"passwordreset-emailerror-capture": "'परवलीचा शब्द' पुनर्स्थापनेबाबत एक विपत्र निर्माण करण्यात आले, जे खाली दर्शविण्यात आले आहे.परंतु,{{GENDER:$2|सदस्य}}ला पाठविणे असफल झाले: $1",
- "changeemail": "à¤\88-मà¥\87ल पतà¥\8dता बदला",
- "changeemail-text": "आपला ई-मेल पत्त बदलण्यासाठी हे आवेदनपत्र भरा. या बदलाची पुष्टी करण्यासाठी तुम्हाला तुमचा परवलीचा शब्द टाकावा लागेल.",
+ "changeemail": "विपतà¥\8dरपतà¥\8dता बदला à¤\95िà¤\82वा हà¤\9fवा",
+ "changeemail-text": "आपला ई-मेल पत्त बदलण्यासाठी हे आवेदनपत्र भरा. या बदलाची पुष्टी करण्यासाठी तुम्हाला तुमचा परवलीचा शब्द टाकावा लागेल.जर आपणास आपल्या खात्याशी असलेली विपत्रपत्त्याची संलग्नता हटवायची असेल तर,हे आवेदन सादर करतेवेळी नविन विपत्रपत्त्याचा रकाना रिकामा ठेवा.",
"changeemail-no-info": "हे पान थेट बघण्यासठी तुम्हाला सनोंद-प्रवेशित असावे लागेल.",
"changeemail-oldemail": "सध्याचा ईमेल पत्ता :",
"changeemail-newemail": "नवा ईमेल पत्ता:",
"prefs-watchlist-token": "निरीक्षणसूचीचा बिल्ला:",
"prefs-misc": "किरकोळ",
"prefs-resetpass": "परवलीचा शब्द बदला.",
- "prefs-changeemail": "विपत्रपत्ता बदला",
+ "prefs-changeemail": "विपत्रपत्ता बदला किंवा हटवा",
"prefs-setemail": "तुमचा ई-मेल पत्ता लिहा.",
"prefs-email": "विपत्र पर्याय",
"prefs-rendering": "देखावा",
"upload-too-many-redirects": "या आंतरजालपत्त्यात खूप पुनर्निर्देशने आहेत",
"upload-http-error": "एक एचटीटीपी चूक उद्भवली: $1",
"upload-copy-upload-invalid-domain": "संक्रमित केलेली महिती अधिक्षेत्रात उपलब्ध नाही.",
+ "foreign-structured-upload-form-label-own-work": "हे माझे स्वत:चे काम आहे",
"backend-fail-stream": "$1 या संचिकेचा स्त्रोत शोधता आला नाही.",
"backend-fail-backup": "$1 या संचिकेची आधारप्रत बनविता आली नाही.",
"backend-fail-notexists": "$1 ही संचिका अस्तित्वात नाही.",
"deletepage": "पान वगळा",
"confirm": "निश्चीत",
"excontent": "मजकूर होता: '$1'",
- "excontentauthor": "मजकूर होता: '$1' (आणि फक्त '[[Special:Contributions/$2|$2]]' यांचे योगदान होते.)",
+ "excontentauthor": "मजकूर होता: \"$1\" आणि फक्त [[Special:Contributions/$2|$2]] ([[User talk:$2|चर्चा]])यांचेच योगदान होते.",
"exbeforeblank": "वगळण्यापूर्वीचा मजकूर पुढीलप्रमाणे: '$1'",
"delete-confirm": "\"$1\" वगळा",
"delete-legend": "वगळा",
"movenotallowedfile": "तुम्हाला संचिका स्थानांतरीत करण्याची परवानगी नाही.",
"cant-move-user-page": "तुम्हाला सदस्यपान स्थानांतरीत करण्याची परवानगी नाही.(उपपानाव्यतिरिक्त)",
"cant-move-to-user-page": "तुम्हाला एखाद्या पानास सदस्य पानांवर (सदस्य उप-पाने सोडून) घेऊन जाण्यास परवानगी नाही.",
- "newtitle": "नवीन शीर्षकाकडे:",
+ "newtitle": "नवीन शीर्षक:",
"move-watch": "स्रोत पान व लक्ष पानांवर निगराणी ठेवा",
"movepagebtn": "स्थानांतरण करा",
"pagemovedsub": "स्थानांतरण यशस्वी",
"editfont-monospace": "Cencoyāhualiztli machiyōtlahtōliztli",
"editfont-sansserif": "Sans-serif machiyōtlahtōliztli",
"editfont-serif": "Serif machiyōtlahtōliztli",
- "sunday": "ic cemilhuitl",
- "monday": "ic ōmilhuitl",
- "tuesday": "ic ēyilhuitl",
- "wednesday": "ic nāhuilhuitl",
- "thursday": "ic mācuīlilhuitl",
- "friday": "ic chicuacemilhuitl",
- "saturday": "ic chicōmilhuitl",
- "sun": "cemilhui",
- "mon": "ōmilhui",
- "tue": "ēyilhui",
- "wed": "nāhuilhui",
- "thu": "mācuīlilhui",
- "fri": "chicuacemilhui",
- "sat": "chicōmilhui",
- "january": "Àtemòstli",
- "february": "Iskalli",
- "march": "Àtlakàwalo",
- "april": "Tlàkaxipèwalistli",
- "may_long": "Tosostli",
- "june": "Toxkatl",
- "july": "Tèkòilwitl",
- "august": "Tlaxòchimàko",
- "september": "Xokowetzi",
- "october": "Teòtlêko",
- "november": "Tepèilwitl",
- "december": "Pànketzalistli",
+ "sunday": "Īccemilhuitl",
+ "monday": "Īcōmilhuitl",
+ "tuesday": "Īcyēyilhuitl",
+ "wednesday": "Īcnāhuilhuitl",
+ "thursday": "Īcmācuīlilhuitl",
+ "friday": "Īcchicuacemilhuitl",
+ "saturday": "Īcchicōmilhuitl",
+ "sun": "1 ilhui",
+ "mon": "2 ilhui",
+ "tue": "3 ilhui",
+ "wed": "4 ilhui",
+ "thu": "5 ilhui",
+ "fri": "6 ilhui",
+ "sat": "7 ilhui",
+ "january": "Īccēmētztli",
+ "february": "Īcōmēmētztli",
+ "march": "Īcyēyimētztli",
+ "april": "Īcnāuhtetlmētztli",
+ "may_long": "Īcmācuīllimētztli",
+ "june": "Īcchicuacemmētztli",
+ "july": "Īcchicōmemētztli",
+ "august": "Īcchicuēyimētztli",
+ "september": "Īcchiucnāhuimētztli",
+ "october": "Īcmahtlāctlimētztli",
+ "november": "Īcmahtlāctlioncēmētztli",
+ "december": "Īcmahtlāctliomōmemētztli",
"january-gen": "Īccēmētztli",
"february-gen": "Īcōmemētztli",
"march-gen": "Īcyēyimētztli",
"july-gen": "Īcchicōmemētztli",
"august-gen": "Īcchicuēyimētztli",
"september-gen": "Īcchiucnāhuimētztli",
- "october-gen": "Īcmahtlactetlmētztli",
+ "october-gen": "Īcmahtlāctetlmētztli",
"november-gen": "Īcmahtlāctetloncēmētztli",
- "december-gen": "ic mahtlāctetl omōme mētztli",
- "jan": "ic cē",
- "feb": "ic ōme",
- "mar": "ic ēyi",
- "apr": "ic nāuh",
- "may": "ic mācuīl",
- "jun": "ic chicuacē",
- "jul": "ic chicōme",
- "aug": "ic chicuēyi",
- "sep": "ic chiucnāuh",
- "oct": "ic mahtlāc",
- "nov": "ic mahtlāctli oncē",
- "dec": "ic mahtlāctli omōme",
+ "december-gen": "Īcmahtlāctetlomōmemētztli",
+ "jan": "1 Mētz",
+ "feb": "2 Mētz",
+ "mar": "3 Mētz",
+ "apr": "4 Mētz",
+ "may": "5 Mētz",
+ "jun": "6 Mētz",
+ "jul": "7 Mētz",
+ "aug": "8 Mētz",
+ "sep": "9 Mētz",
+ "oct": "10 Mētz",
+ "nov": "11 Mētz",
+ "dec": "12 Mētz",
"january-date": "Īccēmētztli $1",
"february-date": "Īcōmemētztli $1",
"march-date": "Īquēyimētztli $1",
"category-file-count": "{{PLURAL:$2|Inìn tlaìxmatkàtlàlilòtl san kipia|Inìn tlaìxmatkàtlalilòtl kimpia {{PLURAL:$1|inìn èwalli|inîkë $1 èwaltìn}}, ìwikpa $2.}}",
"category-file-count-limited": "{{PLURAL:$1|Inìn tlâkuilòlèwalli kä|Inîkë $1 tlâkuilòlèwaltìn katêkë}} ìpan inìn tlaìxmatkàtlàlilòtl.",
"listingcontinuesabbrev": "niman",
- "about": "Ītechpa",
+ "about": "Ītechcopa",
"article": "Tlâkuilòpilli",
"newwindow": "(Motlapoāz cē yancuīc tlanexillōtl)",
- "cancel": "Ticcuepāz",
+ "cancel": "Ticcāhuaz",
"moredotdotdot": "Huehca ōmpa...",
"mypage": "Noāmauh",
- "mytalk": "Notēixnāmiquiliz",
+ "mytalk": "Nozānīl",
"anontalk": "Inīn IP ītēixnāmiquiliz",
"navigation": "Nènemòwalistli",
"and": " īhuān",
- "qbfind": "Tlatēmōz",
- "qbbrowse": "Titlatēmōz",
- "qbedit": "Ticpatlāz",
- "qbpageoptions": "Inīn zāzanilli",
- "qbmyoptions": "Nozāzanil",
+ "qbfind": "Ticahciz",
+ "qbbrowse": "Titlatepotztocaz",
+ "qbedit": "Ticpatlaz",
+ "qbpageoptions": "Inīn tlaīxtli",
+ "qbmyoptions": "Notlaīx",
"faq": "Zan īc tētlatlanīliztli",
"faqpage": "Project:FAQ",
"actions": "Āyiliztli",
- "namespaces": "Tòkâyeyàntìn",
+ "namespaces": "Tōcātlacāuhtli",
"errorpagetitle": "Aiuhcāyōtl",
"returnto": "Timocuepāz īhuīc $1.",
"tagline": "Īhuīcpa {{SITENAME}}",
"help": "Tēpalēhuiliztli",
- "search": "Tlatēmōz",
- "searchbutton": "Tlatēmōz",
- "go": "Yāuh",
- "searcharticle": "Yāuh",
- "history": "tlahcuilōlloh",
- "history_short": "Tlahcuilōlloh",
+ "search": "Titlatēmōz",
+ "searchbutton": "Tictēmōz",
+ "go": "Tiyāz",
+ "searcharticle": "Tiyāz",
+ "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": "Mà mỏta",
+ "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 zāzanilli",
- "create-this-page": "Ticchīhuaz inīn zāzanilli",
+ "editthispage": "Ticpatlaz inīn tlaīxtli",
+ "create-this-page": "Ticchīhuaz inīn tlaīxtli",
"delete": "Ticpolōz",
- "deletethispage": "Ticpolōz inīn zāzanilli",
+ "deletethispage": "Ticpolōz inīn tlaīxtli",
+ "undeletethispage": "Ticmāquīxtīz 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": "Ticquīxtīz",
+ "protect": "Ticpiyaz",
"protect_change": "ticpatlaz",
- "protectthispage": "Ticquīxtiāz inīn zāzanilli",
- "unprotect": "Ticpatlaz in tlaquīxtīliztli",
- "unprotectthispage": "Ticpatlaz inīn āmatl ītlaquīxtīliz",
- "newpage": "Yancuīc zāzanilli",
+ "protectthispage": "Ticpiyaz inīn tlaīxtli",
+ "unprotect": "Ticpatlaz in tlapiyaliztli",
+ "unprotectthispage": "Ticpatlaz inīn tlaīxtli ītlapiyaliz",
+ "newpage": "Yancuic tlaīxtli",
"talkpage": "Tictlahtōz inīn zāzaniltechcopa",
- "talkpagelinktext": "Tèìxnàmikilistli",
+ "talkpagelinktext": "Zānīlli",
"specialpage": "Nònkuâkìskàtlaìxtlapalli",
"personaltools": "In tlein nitēquitiltilia",
- "articlepage": "Xiquittaz in tlahcuilōlli",
+ "articlepage": "Tiquittaz in tlahcuilōlli",
"talk": "tēixnāmiquiliztli",
"views": "Tlachiyaliztli",
"toolbox": "Tlachihchīhualōni",
"badaccess": "Tlahuelītiliztechcopa ahcuallōtl",
"badaccess-group0": "Tehhuātl ahmo tiquichīhua inōn tiquiēlēhuia.",
"badaccess-groups": "Inōn tiquiēlēhuia zan quichīhuah tlatequitiltilīlli {{PLURAL:$2|oncān}}: $1.",
- "ok": "Nopan iti",
+ "ok": "Cualli",
"retrievedfrom": "Ōquīzqui ītech \"$1\"",
"youhavenewmessages": "Tiquimpiya $1 ($2).",
"youhavenewmessagesmulti": "Tiquimpiya yancuīc tlahcuilōlli īpan $1",
"editsection": "ticpatlaz",
"editold": "ticpatlaz",
- "viewsourceold": "xiquitta tlahtōlcaquiliztilōni",
+ "viewsourceold": "tiquittaz mēyalli",
"editlink": "ticpatlaz",
- "viewsourcelink": "tiquittaz tlahtōlcaquiliztilōni",
+ "viewsourcelink": "tiquittaz mēyalli",
"editsectionhint": "Ticpatlacah: $1",
"toc": "Inīn tlahcuilōlco",
- "showtoc": "xiquitta",
+ "showtoc": "ticnēxtīz",
"hidetoc": "tictlātīz",
"collapsible-collapse": "Motlàtìs",
"collapsible-expand": "Monèxtìs",
"nstab-media": "Mēdiatl",
"nstab-special": "Nònkuâkìskàtlaìxtlapalli",
"nstab-project": "Ìtlaìxtlapal in tlayẻkàntekitl",
- "nstab-image": "Īxiptli",
+ "nstab-image": "Ihcuilōlli",
"nstab-mediawiki": "Tlahcuilōltzintli",
"nstab-template": "Nemachiòtl",
"nstab-help": "Tèpalèwilistli",
"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 tlahtōlcaquiliztilōni",
+ "viewsource-title": "Tiquittaz $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'''.",
"logout": "Tiquīzaz",
"userlogout": "Tiquīzaz",
"notloggedin": "Ahmō ōtimocalac",
- "nologin": "¿Ahmō ticpiya cuentah? '''$1'''.",
+ "userlogin-noaccount": "Cuix ahmō titlapōhualeh?",
+ "nologin": "Cuix ahmō titlapōhualeh? $1.",
"nologinlink": "Ticchīhuaz cē cuentah",
"createaccount": "Ticchīhuaz cuentah",
"gotaccount": "¿Ye ticpiya cē tlapōhualli? '''$1'''.",
"powersearch-toggleall": "Mochi",
"powersearch-togglenone": "Ahtlein",
"search-external": "Tlatēmotiliztli calāmpa",
- "preferences": "Tlaēlēhuiliztli",
+ "preferences": "Panitlatlālīlli",
"mypreferences": "Notlaēlēhuiliz",
"prefs-edits": "Tlapatlaliztli tlapōhualli:",
"prefs-skin": "Ēhuatl",
"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-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",
"sp-contributions-newbies-title": "Yancuīc tlatequitiltilīlli ītlahcuilōl",
"sp-contributions-blocklog": "Tlatzacuiliztli tlahcuilōlloh",
"sp-contributions-uploads": "tlahcuilōlquetzaliztli",
- "sp-contributions-talk": "tēixnāmiquiliztli",
+ "sp-contributions-talk": "zānīlli",
"sp-contributions-search": "Tiquintlatēmōz tlapatlaliztli",
"sp-contributions-username": "IP nozo tlatequitiltilīlli ītōcā:",
"sp-contributions-submit": "Tlatēmōz",
"import-upload": "Tiquinquetzāz XML tlahcuilōlli",
"importlogpage": "Tiquincōhuāz tlahcuilōlloh",
"tooltip-pt-userpage": "Notlatequitiltilīlzāzanil",
- "tooltip-pt-mytalk": "Notēixnāmiquiliz",
- "tooltip-pt-preferences": "Notlaēlēhuiliz",
+ "tooltip-pt-mytalk": "Mozānīl",
+ "tooltip-pt-preferences": "Mopanitlatlālīl",
"tooltip-pt-watchlist": "Zāzaniltin tiquintlachiya ic tlapatlaliztli",
"tooltip-pt-mycontris": "Notlahcuilōl",
"tooltip-pt-login": "Tihuelīti timocalaqui, tēl ahmo tihuīquilia.",
"viewsource": "Vere sorgente",
"viewsource-title": "Vere surgente 'e $1",
"actionthrottled": "Azione ritardata",
- "actionthrottledtext": "Comme misura anti-spam, site lemmetato 'a ffà st'azione troppe vote dint'a nu curto spazio 'e tiempo, e mò stu lèmmeto è stato superato.\nPe' piacere pruvate n'ata vota dint'a cocche minuto.",
+ "actionthrottledtext": "Comme misura anti-abuse, site lemmetato 'a ffà st'azione troppe vote dint'a nu curto spazio 'e tiempo, e mò stu lèmmeto è stato superato.\nPe' piacere pruvate n'ata vota dint'a cocche minuto.",
"protectedpagetext": "Sta paggena s'è prutetta pe' ne bloccà 'a mudifeca o n'ata azione.",
"viewsourcetext": "Putite vedé e copià 'o codece surgiva 'e sta paggena.",
"viewyourtext": "Putite vedé e copià 'o codice surgiva d' 'e <strong>cagnamiénte vuoste</strong> a sta paggena.",
"passwordreset-emailtext-ip": "Coccherun (può darse ca sì tu, cu n'indirizzo IP $1) ha addimannato na mmasciata c' 'a password nova pe' putè trasì a {{SITENAME}} ($4). {{PLURAL:$3|L'utente associato|L'utente associate}} a st'indirizze e-mail songo:\n\n$2\n\n{{PLURAL:$3|Sta password temporanea ammaturarrà|Sti password temporanee ammaturarranno}} aropp'a {{PLURAL:$5|nu juorno|$5 ghiuorne}}.\nHè 'a trasì e scegliere na password nova mò. \n\nSi nun sì stato tu a fà sta richiesta, o te sì scurdat' 'a password origginale e nun 'a buò cagnà cchiù, lassa perde sta mmasciata e usa 'a password viecchia.",
"passwordreset-emailtext-user": "L'utente $1 di {{SITENAME}} ha addimannato na mmasciata c' 'a password nova pe' putè trasì a {{SITENAME}} ($4). {{PLURAL:$3|L'utente associato|L'utente associate}} a st'indirizze e-mail songo:\n\n$2\n\n{{PLURAL:$3|Sta password temporanea ammaturarrà|Sti password temporanee ammaturarranno}} aropp'a {{PLURAL:$5|nu juorno|$5 ghiuorne}}.\nHè 'a trasì e scegliere na password nova mò. \n\nSi nun sì stato tu a fà sta richiesta, o te sì scurdat' 'a password origginale e nun 'a buò cagnà cchiù, lassa perde sta mmasciata e usa 'a password viecchia.",
"passwordreset-emailelement": "Nomme utente: \n$1\n\nPassword temporanea: \n$2",
- "passwordreset-emailsent": "Na mmasciata e-mail pe' riabbià 'a password è stata mannata.",
+ "passwordreset-emailsent": "Si chesto fosse nu cunto riggistrato e-mail, allora buò dicere ca se mannarrà na mmasciata e-mail pe' riabbià 'a password.",
"passwordreset-emailsent-capture": "Na mmasciata e-mail pe' riabbià 'a password è stata mannata, chista mmasciata 'a putite vedé ccà abbascio.",
"passwordreset-emailerror-capture": "Na mmasciata e-mail pe' riabbià 'a password è stata mannata, 'a putite vedé ccà abbascio, ma aita sapé ca nun s'è mannata a {{GENDER:$2|l'utente}} pecché c'è stato cocch'errore: $1",
"changeemail": "Cagna o lèva l'indirizzo e-mail",
"upload-options": "Opziune 'e carreca",
"watchthisupload": "Tiene d'uocchio chistu file",
"filewasdeleted": "Nu file ca se chiamave cumm'a chillo c'avete primma carrecato e pò è stato scancellato.\nVedite 'e cuntrullà 'o $1 apprimma ca cuntinuate c' 'a carreca.",
+ "filename-thumb-name": "Chesto pare nu titolo 'e miniatura. Pe' piacere nun carrecate miniature 'a stessa wiki. 'E n'atu modo, cagnate pe' piacere 'o nomme d' 'o file, facenno chesto cchiù significativo e senza prefisso 'e miniatura.",
"filename-bad-prefix": "'O nomme d' 'o file ca state a carrecà accummencia pe' ''\"$1\"''', ca nurmalmente è 'o nomme c'assegnasse na machina fotografeca automatecamente ed è nu nomme nun descrittivo.\nPe' piacere scigliete n'atu nomme ca fosse cchiù descrittivo.",
"upload-success-subj": "Carreca ngarrata",
"upload-success-msg": "'A carreca tuja d' 'o [$2] è asciuta bona. Mò è a disposizione ccà: [[:{{ns:file}}:$1]]",
"recentchangeslinked-summary": "Deze speciale pagina geeft de laatste bewerkingen weer op pagina's waarheen verwezen wordt vanaf een opgegeven pagina of op pagina's in een opgegeven categorie.\nPagina's die op [[Special:Watchlist|uw volglijst]] staan worden '''vet''' weergegeven.",
"recentchangeslinked-page": "Paginanaam:",
"recentchangeslinked-to": "Wijzigingen aan pagina's met koppelingen naar deze pagina bekijken",
+ "recentchanges-page-added-to-category": "[[:$1]] aan categorie toegevoegd",
"upload": "Bestand uploaden",
"uploadbtn": "Bestand uploaden",
"reuploaddesc": "Upload annuleren en terugkeren naar het uploadformulier",
"htmlform-cloner-create": "Meer toevoegen",
"htmlform-cloner-delete": "Verwijderen",
"htmlform-cloner-required": "Ten minste één waarde is vereist.",
+ "htmlform-title-not-exists": "[[:$1]] bestaat niet.",
+ "htmlform-user-not-exists": "<strong>$1</strong> bestaat niet.",
+ "htmlform-user-not-valid": "<strong>$1</strong> is geen geldige gebruikersnaam.",
"sqlite-has-fts": "Versie $1 met ondersteuning voor \"full-text\" zoeken",
"sqlite-no-fts": "Versie $1 zonder ondersteuning voor \"full-text\" zoeken",
"logentry-delete-delete": "$1 {{GENDER:$2|heeft}} de pagina $3 verwijderd",
"tog-oldsig": "Nygöine allekirjutus:",
"tog-uselivepreview": "Käytä välittömiä ezikaččeluu",
"tog-forceeditsummary": "Huomavuta minuu, gu en olle kirjutannuh yhtehveduo",
+ "tog-watchlisthideown": "Peittiä minun korjavukset valvonduluvettelospäi",
"tog-watchlisthideliu": "Peitä kirjutannuhuoloin käyttäjien kohendukset valvondulistalpäi",
"tog-watchlisthideanons": "Peitä registriiruičemattomien käyttäjien kohendukset valvondulistalpäi",
"tog-watchlisthidepatrolled": "Peitä muutoksientarkistajien hyväksytyt kohendukset valvondulistalpäi",
"underline-always": "Ainos",
"underline-never": "Nikonzu",
"editfont-style": "Edituičendualovehen kirjainstiil'u:",
+ "editfont-default": "Livaimen piäazetus",
"editfont-sansserif": "Sans-serif -fontu",
"editfont-serif": "Serif-fontu",
"sunday": "Pyhäpäivy",
"createacct-email-ph": "Kirjuta sinun sähköpoštuadressu",
"createacct-another-email-ph": "Kirjuta sinun sähköpoštuadressu",
"createacct-realname": "Tovelline nimi (omatahtoine tiedo)",
+ "createaccountreason": "Syy:",
+ "createacct-reason": "Syy",
"createacct-reason-ph": "Mindäh olet luadimas tostu käyttäitilii",
"createacct-captcha": "Turvallizusvarmistus",
"createacct-imgcaptcha-ph": "Kirjuta ylähän olii tekstu",
"createacct-benefit-body2": "{{PLURAL:$1|sivu|sivuu}}",
"createacct-benefit-body3": "{{PLURAL:$1|Jälgimäime käyttäi|Jälgimäzet käyttäjät}}",
"badretype": "Sinun kirjutetut peittosanat ei oldu yhtenjyttymät.",
+ "usernameinprogress": "Tunnuksen luadimine tälle käyttäinimele on nygöi kesken. Ole hyvä da vuota.",
+ "userexists": "Syötetty käyttäjän nimi on jo käytös. Ole hyvä, keksi toine nimi.",
"loginerror": "Kirjuttuanduhaireh",
"createacct-error": "Haireh käyttäitunnuksen luadimizes",
"createaccounterror": "Ei voinuh luadie tilii: $1",
+ "loginsuccesstitle": "Olet kirjutannuhes",
"loginsuccess": "<strong>Olet kirjutannuhes sivule {{SITENAME}} käyttäitunnuksel \"$1\".</strong>",
"nosuchusershort": "Ei ole käyttäjiä nimel \"$1\". Tarkista kirjutitgo oigein.",
"nouserspecified": "Käyttäinimi pidäy.",
"passwordremindertitle": "Uuzi väliaigaine peittosana {{SITENAME}}-sivuh niškoi",
"passwordremindertext": "Kentah IP-adressispäi $1 kyzyi työndämäh uuttu peittosanua saitale {{SITENAME}} ($4). Väliaigaine peittosana käyttäjäle $2 on nygöi $3. Kirjuttai da vaihta peittosana. Väliaigaine peittosana vahnenou {{PLURAL:$5|yhten päivän|$5 päivän}} jälles.\n\nOllou kentah toine työndänyh tämän pakičuksen, libo ku ollet mustanuh sinun peittosanan da et tahto vaihtua sidä, voit jättiä tämän viestin huomivottah da jatkua vahnan peittosanan käyttyö.",
"mailerror": "Haireh työndäjes sähköpoštua: $1",
+ "emaildisabled": "Tämä verkosivusto ei voi työndiä sähköpoštuviestilöi.",
"accountcreated": "Tili luajittu",
+ "createaccount-title": "{{SITENAME}}: käyttäitunnuksen luadimine",
"loginlanguagelabel": "Kieli: $1",
"pt-login": "Kirjuttai",
"pt-login-button": "Kirjuttai",
"pt-userlogout": "Kirjuttai ullos",
"user-mail-no-addy": "Opit työndiä sähköpoštan sähköpoštuadressittah.",
"changepassword": "Vaihta peittosana",
+ "resetpass_announce": "Suaja sizähkirjuamine loppuh, sinul pidäy keksie uuzi peittosana.",
"resetpass_header": "Vaihta tilin peittosana",
"oldpassword": "Vahnu peittosana:",
"newpassword": "Uuzi peittosana:",
+ "retypenew": "Kirjuta peittosana uvvessah:",
+ "resetpass_submit": "Azeta peittosana da kirjuttai sistiemah:",
"changepassword-success": "Sinun peittosana on vaihtettu!",
"resetpass_forbidden": "Ei voi vaihtua peittosanua",
"resetpass-no-info": "Et voi nähtä tädä sivuu kuni et ole kirjutannuhes.",
"passwordreset-emailtext-ip": "Ktoś (prawdopodobnie Ty, spod adresu IP $1) poprosił o zresetowanie twojego hasła w {{GRAMMAR:MS.lp{{SITENAME}}}} ($4). Z tym adresem e‐mailowym powiązane {{PLURAL:$3|jest konto użytkownika|są następujące konta użytkowników:}}\n\n$2\n\n{{PLURAL:$3|Tymczasowego hasła|Tymczasowych haseł}} można użyć w ciągu {{PLURAL:$5|jednego dnia|$5 dni}}.\nPowinieneś zalogować się i zmienić hasło na nowe. Jeśli to ktoś inny poprosił o wysłanie przypomnienia lub jeśli pamiętasz aktualne hasło i nie chcesz go zmieniać wystarczy, że zignorujesz tę wiadomość i będziesz nadal korzystać ze swojego starego hasła.",
"passwordreset-emailtext-user": "Użytkownik $1 poprosił o zresetowanie twojego hasła w {{GRAMMAR:MS.lp{{SITENAME}}}} ($4). Z tym adresem e‐mailowym powiązane {{PLURAL:$3|jest konto użytkownika|są następujące konta użytkowników:}}\n\n$2\n\n{{PLURAL:$3|Tymczasowego hasła|Tymczasowych haseł}} można użyć w ciągu {{PLURAL:$5|jednego dnia|$5 dni}}.\nPowinieneś zalogować się i zmienić hasło na nowe. Jeśli to ktoś inny poprosił o wysłanie przypomnienia lub jeśli pamiętasz aktualne hasło i nie chcesz go zmieniać wystarczy, że zignorujesz tę wiadomość i będziesz nadal korzystać ze swojego starego hasła.",
"passwordreset-emailelement": "Nazwa użytkownika: \n$1\n\nTymczasowe hasło: \n$2",
- "passwordreset-emailsent": "E‐mail resetowania hasła został wysłany.",
+ "passwordreset-emailsent": "Jeśli adres e‐mail przypisany do Twojego konta został zarejestrowany, zostanie wysłany e-mail do odzyskiwania hasła.",
"passwordreset-emailsent-capture": "Wyświetlony poniżej e‐mail pozwalający na zresetowanie hasła został wysłany.",
"passwordreset-emailerror-capture": "Poniżej wyświetlony e‐mail pozwalający na zresetowanie hasła został wygenerowany, ale nie udało się wysłać go do {{GENDER:$2|użytkownika|użytkowniczki}}: $1",
"changeemail": "Zmiana lub usunięcie adresu e‐mail",
"createacct-captcha": "امنيتي تدبير",
"createacct-imgcaptcha-ph": "پورته ښکاره شوی متن دلته وټاپئ",
"createacct-submit": "گڼون مو جوړ کړئ",
- "createacct-another-submit": "بل گڼون جوړول",
+ "createacct-another-submit": "گڼون جوړول",
"createacct-benefit-heading": "{{SITENAME}} ستاسې په شان خلکو لخوا جوړ شوی.",
"createacct-benefit-body1": "{{PLURAL:$1|سمون|سمونونه}}",
"createacct-benefit-body2": "{{PLURAL:$1|مخ|مخونه}}",
"group-bot": "روباټونه",
"group-sysop": "پازوالان",
"group-bureaucrat": "بيوروکراټان",
- "group-suppress": "Ú\85ارونکي",
+ "group-suppress": "Ú\81Ù¾Ù\88ونکي",
"group-all": "(ټول)",
"group-user-member": "{{GENDER:$1|کارن}}",
"group-autoconfirmed-member": "{{GENDER:$1|تاييد شوی کارن}}",
"group-bot-member": "{{GENDER:$1|روباټ}}",
"group-sysop-member": "{{GENDER:$1|پازوال}}",
"group-bureaucrat-member": "{{GENDER:$1|بيوروکراټ}}",
- "group-suppress-member": "{{GENDER:$1|Ú\85ارÙ\86}}",
+ "group-suppress-member": "{{GENDER:$1|Ú\81Ù¾Ù\88Ù\88Ù\86Ú©Û\8c}}",
"grouppage-user": "{{ns:project}}:کارنان",
"grouppage-autoconfirmed": "{{ns:project}}:تاييد شوي کارنان",
"grouppage-bot": "{{ns:project}}:روباټان",
"grouppage-sysop": "{{ns:project}}:پازوالان",
"grouppage-bureaucrat": "{{ns:project}}:بيوروکراټان",
- "grouppage-suppress": "{{ns:project}}:Ú\85ارÙ\86",
+ "grouppage-suppress": "{{ns:project}}:Ú\81Ù¾Ù\84",
"right-read": "مخونه لوستل",
"right-edit": "مخونه سمول",
"right-createpage": "مخونه جوړول (هغه چې د خبرو اترو مخونه نه دي)",
"nopagetext": "کوم مخ مو چې وښوده هغه نشته.",
"pager-newer-n": "{{PLURAL:$1|نوی 1|نوي $1}}",
"pager-older-n": "{{PLURAL:$1|زوړ 1|زاړه $1}}",
- "suppress": "Ú\85ارÙ\86",
+ "suppress": "Ú\81Ù¾Ù\84",
"apihelp": "API لارښود",
"apihelp-no-such-module": "د \"$1\" ماډيول و نه موندل شو.",
"booksources": "د کتاب سرچينې",
"tooltip-recreate": "Recreează",
"tooltip-upload": "Pornește încărcarea",
"tooltip-rollback": "„Revenire” anulează modificarea(ările) de pe această pagină a(le) ultimului contribuitor printr-o singură apăsare",
- "tooltip-undo": "„Anulează” șterge această modificare și deschide formularul de modificare în modulul de previzualizare.\nPermite adăugarea unui motiv în descrierea modificărilor.",
+ "tooltip-undo": "„Anulează” revine asupra acestei modificări către versiunea anterioară și deschide formularul de modificare în modul de previzualizare.\nPermite adăugarea unui motiv în descrierea modificărilor.",
"tooltip-preferences-save": "Salvează preferințele",
"tooltip-summary": "Descrieți pe scurt modificarea",
"interlanguage-link-title": "$1 – $2",
"viewsource": "Vide 'u sorgende",
"viewsource-title": "Vide 'a sorgende pe $1",
"actionthrottled": "Azione inderrotte",
- "actionthrottledtext": "Cumme 'na mesure andi-spam, tu è state limitete da fà st'azione troppe vote jndr'à 'nu timbe piccinne e tu è subranete stu limite.\nPe piacere prueve cchiù tarde.",
+ "actionthrottledtext": "Cumme 'na mesure andi-abbuse, tu è state limitate da fà st'azione troppe vote jndr'à 'nu tiembe curte e tu è subranate stu limite.\nPe piacere pruéve cchiù tarde.",
"protectedpagetext": "Sta pàgene ha state prutette pe no fa fà cangiaminde o otre aziune a uecchje.",
"viewsourcetext": "Tu puè 'ndrucà e cupià 'a sorgente de sta pàgene.",
"viewyourtext": "Tu puè 'ndrucà e copià 'a sorgende de <strong>le cangiaminde tune</strong> a sta pàgene.",
"passwordreset-emailtext-ip": "Quacchedune (pò essere tu, da 'u 'ndirizze IP $1) ha richieste 'na mail pe arrecurdarse de le dettaglie d'u cunde sue pe {{SITENAME}} ($4). {{PLURAL:$3|'U cunde utende seguende jè|le cunde utinde seguende sonde}} associate cu st'indirizze e-mail:\n\n$2\n\n{{PLURAL:$3|Sta passuord temboranèe scade|Ste passuord temboranèe scadene}} 'mbrà {{PLURAL:$5|'nu sciurne|$5 sciurne}}.\nTu avissa trasè e scacchià 'na passuord nova. Ce quacchedun'otre ha fatte sta richieste, o ce tu t'è arrecurdate 'a passuord origgenale toje, e non g'a vuè ccu cange cchiù, tu puè ignorà stu messagge e condinuà ausanne 'a passuord vecchie.",
"passwordreset-emailtext-user": "L'utende $1 sus a {{SITENAME}} ave richieste 'na mail pe arrecurdarse le dettaglie d'u cunde sue pe {{SITENAME}}\n($4). {{PLURAL:$3|'U cunde utende seguende jè|le cunde utinde seguende sonde}} associate cu st'indirizze e-mail:\n\n$2\n\n{{PLURAL:$3|Sta passuord temboranèe scade|Ste passuord temboranèe scadene}} 'mbrà {{PLURAL:$5|'nu sciurne|$5 sciurne}}.\nTu avissa trasè e scacchià 'na passuord nova. Ce quacchedun'otre ha fatte sta richieste, o ce tu t'è arrecurdate 'a passuord origgenale toje, e non g'a vuè ccu cange cchiù, tu puè ignorà stu messagge e condinuà ausanne 'a passuord vecchie.",
"passwordreset-emailelement": "Nome utende: \n$1\n\nPassuord temboranèe: \n$2",
- "passwordreset-emailsent": "'N'e-mail pe arrecurdarte ha state mannate.",
+ "passwordreset-emailsent": "Ce quiste jè 'n'e-mail pu cunde tune, allore 'na password azzerate ha state mannate addà.",
"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 'u 'ndirizze e-mail",
"recentchanges-page-added-to-category-bundled": "[[:$1]] и {{PLURAL:$2|одна страница|$2 страниц}} добавлены в категорию",
"recentchanges-page-removed-from-category": "[[:$1]] убрана из категории",
"recentchanges-page-removed-from-category-bundled": "[[:$1]] и {{PLURAL:$2|одна страница|$2 страниц}} убраны из категории",
+ "autochange-username": "Автоматическое изменение MediaWiki",
"upload": "Загрузить файл",
"uploadbtn": "Загрузить файл",
"reuploaddesc": "Вернуться к форме загрузки",
"watchlistanontext": "Пожалуйста, войдите, чтобы просмотреть или отредактировать элементы в списке наблюдения.",
"watchnologin": "Нужно представиться системе",
"addwatch": "Добавить в список наблюдения",
- "addedwatchtext": "Статья «[[:$1]]» и её страница обсуждения были добавлены в ваш [[Special:Watchlist|список наблюдения]].",
+ "addedwatchtext": "Страница «[[:$1]]» вместе с её обсуждением были добавлены в ваш [[Special:Watchlist|список наблюдения]].",
"addedwatchtext-short": "Страница «$1» была добавлена в ваш список наблюдения.",
"removewatch": "Удалить из списка наблюдения",
"removedwatchtext": "Статья «[[:$1]]» и её страница обсуждения были удалены из вашего [[Special:Watchlist|списка наблюдения]].",
"exbeforeblank": "содержимое до очистки: «$1»",
"delete-confirm": "$1 — удаление",
"delete-legend": "Удаление",
- "historywarning": "<strong>Ð\92нимание:</strong> У Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\8b, коÑ\82оÑ\80Ñ\83Ñ\8e вÑ\8b Ñ\81обиÑ\80аеÑ\82еÑ\81Ñ\8c Ñ\83далиÑ\82Ñ\8c, еÑ\81Ñ\82Ñ\8c иÑ\81Ñ\82оÑ\80иÑ\8f пÑ\80авок, Ñ\81одеÑ\80жаÑ\89аÑ\8f $1 {{PLURAL:$1|веÑ\80Ñ\81иÑ\8e|версий}}:",
+ "historywarning": "<strong>Ð\92нимание:</strong> Ð\92Ñ\8b Ñ\81обиÑ\80аеÑ\82еÑ\81Ñ\8c Ñ\83далиÑ\82Ñ\8c Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, Ñ\83 коÑ\82оÑ\80ой еÑ\81Ñ\82Ñ\8c иÑ\81Ñ\82оÑ\80иÑ\8f пÑ\80авок, Ñ\81одеÑ\80жаÑ\89аÑ\8f $1 {{PLURAL:$1|веÑ\80Ñ\81иÑ\8e|веÑ\80Ñ\81ии|версий}}:",
"confirmdeletetext": "Вы запросили полное удаление страницы (или изображения) и всей её истории изменений. Пожалуйста, подтвердите, что вы действительно желаете это сделать, понимаете последствия своих действий, и делаете это в соответствии [[{{MediaWiki:Policy-url}}|с правилами]].",
"actioncomplete": "Действие выполнено",
"actionfailed": "Действие не выполнено",
"undeletepagetext": "{{PLURAL:$1|Следующая $1 страница была удалена|Следующие $1 страниц были удалены|Следующие $1 страницы были удалены|1=Следующая страница была удалена}}, однако {{PLURAL:$1|1=она всё ещё находится в архиве и поэтому может быть восстановлена|они всё ещё находятся в архиве и поэтому могут быть восстановлены}}.\nАрхив может периодически очищаться.",
"undelete-fieldset-title": "Восстановить версии",
"undeleteextrahelp": "Для полного восстановления истории страницы оставьте все отметки пустыми и нажмите '''«{{int:undeletebtn}}»'''.\nДля частичного восстановления отметьте те версии страницы, которые нужно восстановить, и нажмите '''«{{int:undeletebtn}}»'''.",
- "undeleterevisions": "$1 {{PLURAL:$1|версия|версий|версии}} {{PLURAL:$1|удалена|удалены}}",
+ "undeleterevisions": "$1 {{PLURAL:$1|удалённая версия|удалённые версии|удалённых версий}}",
"undeletehistory": "При восстановлении страницы восстанавливается и её история правок.\nЕсли после удаления была создана новая страница с тем же названием, то восстановленные версии появятся в истории правок перед новыми версиями.",
"undeleterevdel": "Восстановление не будет произведено, если оно приведёт к частичному удалению последней версии страницы или файла.\nВ подобном случае вы должны снять отметку или показать последние удалённые версии.",
"undeletehistorynoadmin": "Статья была удалена. Причина удаления и список участников, редактировавших статью до её удаления, показаны ниже. Текст удалённой статьи могут просмотреть только администраторы.",
"cant-move-to-user-page": "У вас нет прав переименовывать страницу в страницу участника (можно переименовать в подстраницу).",
"cant-move-category-page": "У вас нет разрешения переименовывать страницы категорий.",
"cant-move-to-category-page": "У вас нет разрешения переименовывать страницы в страницу категории.",
- "newtitle": "Новое название",
+ "newtitle": "Новое название:",
"move-watch": "Включить эту страницу в список наблюдения",
"movepagebtn": "Переименовать страницу",
"pagemovedsub": "Страница переименована",
"viewsource": "Izvorno besedilo",
"viewsource-title": "Ogled vira $1",
"actionthrottled": "Dejanje zaustavljeno",
- "actionthrottledtext": "Kot ukrep proti smetju, je število izvajanj tega dejanja v časovnem obdobju omejeno, in vi ste ta limit presegli.\nProsimo, poskusite znova čez nekaj minut.",
+ "actionthrottledtext": "Kot ukrep proti zlorabam je število izvajanj tega dejanja v časovnem obdobju omejeno, in vi ste ta limit presegli.\nProsimo, poskusite znova čez nekaj minut.",
"protectedpagetext": "Ta stran je bila zaklenjena za preprečitev urejanja ali drugih dejanj.",
"viewsourcetext": "Vsebino te strani si lahko ogledate in kopirate.",
"viewyourtext": "Lahko si ogledate in kopirate vsebino <strong>vaših urejanj</strong> te strani.",
"nocookiesfornew": "该用户账户未被创建,我们不能确认它的来源。请确保你已启用Cookie,刷新本页后再试。",
"noname": "你没有指定有效的用户名。",
"loginsuccesstitle": "登录成功",
- "loginsuccess": "'''“$1”,欢迎登录{{SITENAME}}。'''",
+ "loginsuccess": "<strong>您现在已经以\"$1\"的身份登录了{{SITENAME}}。</strong>",
"nosuchuser": "没有名为“$1”的用户。用户名区分大小写。请检查你的拼写或[[Special:UserLogin/signup|创建新账户]]。",
"nosuchusershort": "没有名为“$1”的用户。请检查你的拼写。",
"nouserspecified": "你必须指定用户名。",
"undo-summary": "撤销[[Special:Contributions/$2|$2]]([[User talk:$2|讨论]])的版本$1",
"undo-summary-username-hidden": "取消由一匿名用户所作的版本$1",
"cantcreateaccounttitle": "无法创建账户",
- "cantcreateaccount-text": "从该IP地址('''$1''')创建账户已被[[User:$3|$3]]禁止。\n\n$3的理由是''$2''",
+ "cantcreateaccount-text": "从该IP地址(<strong>$1</strong>)创建账户已被[[User:$3|$3]]禁止。\n\n$3的理由是<em>$2</em>",
"cantcreateaccount-range-text": "从该IP地址段'''$1'''的账户创建已被[[User:$3|$3]]禁止,而这也包括了您的IP地址('''$4''')。\n\n$3给出的原因是 $2。",
"viewpagelogs": "查看该页面的日志",
"nohistory": "本页面没有编辑历史记录。",
"shared-repo-from": "来自$1",
"shared-repo": "一个共享文件库",
"shared-repo-name-wikimediacommons": "维基共享资源",
- "filepage.css": "/* 放置于此的CSS会包含在文件描述页上,并包含在其他客户端wiki上 */",
+ "filepage.css": "/* 这里放置的CSS会包含在文件描述页上,并包含在其他客户端wiki上 */",
"upload-disallowed-here": "您不可以覆盖此文件。",
"filerevert": "恢复$1",
"filerevert-legend": "恢复文件",
"tooltip-summary": "请输入简短的摘要",
"interlanguage-link-title": "$1 – $2",
"interlanguage-link-title-nonlang": "$1——$2",
- "common.css": "/* 放置于这里的CSS将应用于所有皮肤 */",
- "print.css": "/* 放置于这里的CSS将影响打印输出 */",
- "noscript.css": "/* 放置于这里的CSS将影响停用JavaScript的用户 */",
+ "common.css": "/* 这里放置的CSS将应用于所有皮肤 */",
+ "print.css": "/* 这里放置的CSS将影响打印输出 */",
+ "noscript.css": "/* 这里放置的CSS将影响停用JavaScript的用户 */",
"group-autoconfirmed.css": "/* 这里放置的CSS将只影响自动确认用户 */",
"group-user.css": "/* 这里放置的CSS将只影响注册用户 */",
"group-bot.css": "/* 这里放置的CSS将只影响机器人 */",
"nocookiesfornew": "這個使用者的帳號未建立,我們不能確認它的來源。\n請確認您已開啟 Cookie,重新載入後再試。",
"noname": "您輸入的使用者名稱無效。",
"loginsuccesstitle": "登入成功",
- "loginsuccess": "<strong>{{GENDER:|你|妳|你}}正使用 \"$1\" 的身份登入 {{SITENAME}}。</strong>",
+ "loginsuccess": "<strong>{{GENDER:|您|妳|你}}現在已經以 \"$1\" 的身分登入了 {{SITENAME}}。</strong>",
"nosuchuser": "查無使用者 \"$1\"。\n使用者名稱有大小寫區分,\n請檢查您拼寫是否正確,或者 [[Special:UserLogin/signup|建立新帳號]]。",
"nosuchusershort": "查無使用者 \"$1\",\n請檢查您拼寫是否正確。",
"nouserspecified": "您必須指定一個使用者名稱。",
"undo-summary": "取消由 [[Special:Contributions/$2|$2]] ([[User talk:$2|對話]]) 所作出的修訂 $1",
"undo-summary-username-hidden": "還原隱藏使用者的修訂 $1",
"cantcreateaccounttitle": "無法建立帳號",
- "cantcreateaccount-text": "來自這個 IP 位址 (<strong>$1</strong>) 建立的帳號已經被 [[User:$3|$3]] 封鎖。\n\n $3 封鎖的原因是 <em>$2</em>",
+ "cantcreateaccount-text": "自這個 IP 位址(<strong>$1</strong>)建立帳號已經被 [[User:$3|$3]] 封鎖。\n\n$3 封鎖的原因是 <em>$2</em>",
"cantcreateaccount-range-text": "來自 IP 位址範圍 '''$1''',包含您的 IP 位址 ('''$4''') 所建立的帳號已經被 [[User:$3|$3]] 封鎖。\n\n$3 封鎖的原因是 ''$2''",
"viewpagelogs": "檢視此頁面的日誌",
"nohistory": "此頁沒有任何的修訂記錄。",
"upload-form-label-infoform-description": "描述",
"upload-form-label-usage-title": "用法",
"upload-form-label-usage-filename": "檔案名稱",
+ "foreign-structured-upload-form-label-infoform-categories": "分類",
"backend-fail-stream": "無法傳輸檔案 \"$1\"。",
"backend-fail-backup": "無法備份檔案 \"$1\"。",
"backend-fail-notexists": "檔案 $1 不存在。",
"ipb-blocklist": "檢視目前的封鎖",
"ipb-blocklist-contribs": "{{GENDER:$1|$1}} 的貢獻",
"unblockip": "解除封鎖使用者",
- "unblockiptext": "填寫以下單據以取消先前封鎖的 IP 位址或使用者名稱。",
- "ipusubmit": "移除這個封鎖",
+ "unblockiptext": "使用以下表單以還原之前被封鎖的 IP 位址或使用者名稱的寫入存取。",
+ "ipusubmit": "移除此封鎖",
"unblocked": "[[User:$1|$1]] 的封鎖已經解除。",
"unblocked-range": "已解除封鎖 $1。",
"unblocked-id": "已經移除 $1 的封鎖。",
"import-interwiki-text": "請選擇一個 Wiki 與頁面標題以進行匯入。\n會同時記錄修訂日期和編輯者的名稱。\n所有的從跨 Wiki 匯入操作都會被記錄在 [[Special:Log/import|匯入日誌]]。",
"import-interwiki-sourcewiki": "來源 Wiki:",
"import-interwiki-sourcepage": "來源頁面:",
- "import-interwiki-history": "複製此頁的所有歷史修訂",
+ "import-interwiki-history": "複製此頁面的所有歷史修訂",
"import-interwiki-templates": "包含所有模板",
"import-interwiki-submit": "匯入",
"import-mapping-default": "匯入至預設位置",
"javascripttest-pagetext-skins": "選擇執行測試的外觀:",
"javascripttest-qunit-intro": "請參考 mediawiki.org 的 [$1 測試說明文件]。",
"tooltip-pt-userpage": "您的使用者頁面",
- "tooltip-pt-anonuserpage": "您正使用以下身份編輯該 IP 位址的使用者頁面 :",
+ "tooltip-pt-anonuserpage": "您正在作為以下身分編輯此 IP 位址的使用者頁面",
"tooltip-pt-mytalk": "您的對話頁面",
"tooltip-pt-anontalk": "有關來自此 IP 位址編輯的討論",
"tooltip-pt-preferences": "您的偏好設定",
"tooltip-n-help": "尋求協助的地方",
"tooltip-t-whatlinkshere": "列出所有連結此頁面的頁面",
"tooltip-t-recentchangeslinked": "此頁面連結到其他頁面的近期變更",
- "tooltip-feed-rss": "訂閱此頁面的 RSS feed",
- "tooltip-feed-atom": "訂閱此頁面的 Atom feed",
+ "tooltip-feed-rss": "此頁面的 RSS 摘要",
+ "tooltip-feed-atom": "此頁面的 Atom 摘要",
"tooltip-t-contributions": "此使用者的貢獻清單",
"tooltip-t-emailuser": "傳送電子郵件聯絡這位使用者",
"tooltip-t-info": "更多關於此頁面的資訊",
"tooltip-save": "儲存您的變更",
"tooltip-preview": "請在儲存前預覽您的變更!",
"tooltip-diff": "顯示您對內容所做的變更",
- "tooltip-compareselectedversions": "檢è¦\96æ¤é \81兩個已選擇的修訂間的差異",
+ "tooltip-compareselectedversions": "æ\9f¥é\96±æ¤é \81é\9d¢兩個已選擇的修訂間的差異",
"tooltip-watch": "加入此頁面至您的監視清單",
"tooltip-watchlistedit-normal-submit": "移除標題",
"tooltip-watchlistedit-raw-submit": "更新監視清單",
$batch = array();
$lastName = '';
do {
- $res = $dbw->select( 'image', array( 'img_name', 'img_sha1' ),
+ $res = $dbw->select( 'image',
+ array( 'img_name', 'img_sha1' ),
array_merge( array( 'img_name > ' . $dbw->addQuotes( $lastName ) ), $conds ),
__METHOD__,
array( 'LIMIT' => $this->mBatchSize, 'ORDER BY' => 'img_name' )
foreach ( $res as $row ) {
$lastName = $row->img_name;
- $sha1 = $row->img_sha1;
+ /** @var LocalFile $file */
+ $file = $repo->newFile( $row->img_name );
+ // Check in case SHA1 rows are not populated for some files
+ $sha1 = strlen( $row->img_sha1 ) ? $row->img_sha1 : $file->getSha1();
+
if ( !strlen( $sha1 ) ) {
- $this->error( "Image SHA-1 not set for {$row->img_name}." );
+ $this->error( "Image SHA-1 not known for {$row->img_name}." );
} else {
- $file = $repo->newFile( $row->img_name );
-
if ( $oldLayout === 'sha1' ) {
$spath = "{$origBase}/{$sha1[0]}/{$sha1[1]}/{$sha1[2]}/{$sha1}";
} else {
$dpath = $file->getPath();
}
- $status = $be->prepare( array( 'dir' => dirname( $dpath ) ) );
+ $status = $be->prepare( array(
+ 'dir' => dirname( $dpath ), 'bypassReadOnly' => 1 ) );
if ( !$status->isOK() ) {
$this->error( print_r( $status->getErrorsArray(), true ) );
}
$dpath = $ofile->getPath();
}
- $status = $be->prepare( array( 'dir' => dirname( $dpath ) ) );
+ $status = $be->prepare( array(
+ 'dir' => dirname( $dpath ), 'bypassReadOnly' => 1 ) );
if ( !$status->isOK() ) {
$this->error( print_r( $status->getErrorsArray(), true ) );
}
'/' . $repo->getDeletedHashPath( $sha1Key ) . $sha1Key;
}
- $status = $be->prepare( array( 'dir' => dirname( $dpath ) ) );
+ $status = $be->prepare( array(
+ 'dir' => dirname( $dpath ), 'bypassReadOnly' => 1 ) );
if ( !$status->isOK() ) {
$this->error( print_r( $status->getErrorsArray(), true ) );
}
$this->output( "\"{$op['img']}\" (dest: {$op['dst']})\n" );
}
- $status = $be->doOperations( $ops );
+ $status = $be->doOperations( $ops, array( 'bypassReadOnly' => 1 ) );
if ( !$status->isOK() ) {
$this->output( print_r( $status->getErrorsArray(), true ) );
}
<ruleset name="MediaWiki">
<rule ref="vendor/mediawiki/mediawiki-codesniffer/MediaWiki">
<!-- Disable failing rules -->
- <exclude name="Generic.Files.LineLength"/>
- <exclude name="PSR2.Methods.MethodDeclaration.Underscore"/>
<exclude name="Squiz.Classes.ValidClassName.NotCamelCaps"/>
<exclude name="MediaWiki.WhiteSpace.SpaceBeforeSingleLineComment.EmptyComment"/>
</rule>
<rule ref="Generic.Files.LineLength">
<exclude-pattern>*/languages/messages/Messages*.php</exclude-pattern>
</rule>
+ <rule ref="PSR2.Methods.MethodDeclaration.Underscore">
+ <exclude-pattern>*/includes/StubObject.php</exclude-pattern>
+ </rule>
<exclude-pattern>node_modules</exclude-pattern>
<exclude-pattern>vendor</exclude-pattern>
<exclude-pattern>extensions</exclude-pattern>
'dependencies' => array(
'mediawiki.ForeignStructuredUpload',
'mediawiki.Upload.BookletLayout',
- 'mediawiki.widgets',
+ 'mediawiki.widgets.CategorySelector',
'mediawiki.widgets.DateInputWidget',
'mediawiki.jqueryMsg',
),
'styles' => array(
// @todo: Remove mediawiki.page.gallery when cache has cleared
'resources/src/mediawiki/page/gallery-print.css' => array( 'media' => 'print' ),
- // @todo: Remove mediawiki.action.view.filepage.print.css when cache has cleared
- 'resources/src/mediawiki.action/mediawiki.action.view.filepage.print.css' =>
- array( 'media' => 'print' ),
'resources/src/mediawiki.legacy/commonPrint.css' => array( 'media' => 'print' )
),
),
'styles' => array(
// @todo: Remove when mediawiki.page.gallery in cached html.
'resources/src/mediawiki/page/gallery.css',
- // @todo: Remove mediawiki.action.view.filepage.css
- // and mediawiki.legacy/images/checker.png when cache has cleared
- 'resources/src/mediawiki.action/mediawiki.action.view.filepage.css',
'resources/src/mediawiki.legacy/shared.css' => array( 'media' => 'screen' )
),
),
'resources/src/mediawiki.widgets/mw.widgets.ComplexTitleInputWidget.js',
'resources/src/mediawiki.widgets/mw.widgets.TitleOptionWidget.js',
'resources/src/mediawiki.widgets/mw.widgets.UserInputWidget.js',
- 'resources/src/mediawiki.widgets/mw.widgets.CategoryCapsuleItemWidget.js',
- 'resources/src/mediawiki.widgets/mw.widgets.CategorySelector.js',
),
'skinStyles' => array(
'default' => array(
'jquery.byteLimit',
// TitleOptionWidget
'jquery.autoEllipsis',
+ // FIXME: Kept for bc
+ 'mediawiki.widgets.CategorySelector',
),
'messages' => array(
// NamespaceInputWidget
),
'targets' => array( 'desktop', 'mobile' ),
),
+ 'mediawiki.widgets.CategorySelector' => array(
+ 'scripts' => array(
+ 'resources/src/mediawiki.widgets/mw.widgets.CategoryCapsuleItemWidget.js',
+ 'resources/src/mediawiki.widgets/mw.widgets.CategorySelector.js',
+ ),
+ 'dependencies' => array(
+ 'oojs-ui',
+ 'mediawiki.api',
+ ),
+ 'targets' => array( 'desktop', 'mobile' ),
+ ),
/* es5-shim */
'es5-shim' => array(
* Wrapper around jQuery append that converts all non-objects to TextNode so append will not
* convert what it detects as an htmlString to an element.
*
+ * If our own htmlEmitter jQuery object is given, its children will be unwrapped and appended to
+ * new parent.
+ *
* Object elements of children (jQuery, HTMLElement, TextNode, etc.) will be left as is.
*
* @private
if ( typeof children[ i ] !== 'object' ) {
children[ i ] = document.createTextNode( children[ i ] );
}
+ if ( children[ i ] instanceof jQuery && children[ i ].hasClass( 'mediaWiki_htmlEmitter' ) ) {
+ children[ i ] = children[ i ].contents();
+ }
}
return $parent.append( children );
.replace( /&/g, '&' );
}
+ /**
+ * Turn input into a string.
+ *
+ * @private
+ * @param {string|jQuery} input
+ * @return {string} Textual value of input
+ */
+ function textify( input ) {
+ if ( input instanceof jQuery ) {
+ input = input.text();
+ }
+ return String( input );
+ }
+
/**
* Given parser options, return a function that parses a key and replacements, returning jQuery object
*
return function () {
var $target = this.empty();
- // TODO: Simply appendWithoutParsing( $target, failableParserFn( arguments ).contents() )
- // or Simply appendWithoutParsing( $target, failableParserFn( arguments ) )
- $.each( failableParserFn( arguments ).contents(), function ( i, node ) {
- appendWithoutParsing( $target, node );
- } );
+ appendWithoutParsing( $target, failableParserFn( arguments ) );
return $target;
};
};
escapedOrLiteralWithoutSpace, escapedOrLiteralWithoutBar, escapedOrRegularLiteral,
whitespace, dollar, digits, htmlDoubleQuoteAttributeValue, htmlSingleQuoteAttributeValue,
htmlAttributeEquals, openHtmlStartTag, optionalForwardSlash, openHtmlEndTag, closeHtmlTag,
- openExtlink, closeExtlink, wikilinkPage, wikilinkContents, openWikilink, closeWikilink, templateName, pipe, colon,
+ openExtlink, closeExtlink, wikilinkContents, openWikilink, closeWikilink, templateName, pipe, colon,
templateContents, openTemplate, closeTemplate,
nonWhitespaceExpression, paramExpression, expression, curlyBraceTransformExpression, result,
settings = this.settings,
return result === null ? null : result.join( '' );
}
- // Used for wikilink page names. Like literalWithoutBar, but
- // without allowing escapes.
- function unescapedLiteralWithoutBar() {
- var result = nOrMore( 1, regularLiteralWithoutBar )();
- return result === null ? null : result.join( '' );
- }
-
function literal() {
var result = nOrMore( 1, escapedOrRegularLiteral )();
return result === null ? null : result.join( '' );
closeExtlink = makeStringParser( ']' );
// this extlink MUST have inner contents, e.g. [foo] not allowed; [foo bar] [foo <i>bar</i>], etc. are allowed
function extlink() {
- var result, parsedResult;
+ var result, parsedResult, target;
result = null;
parsedResult = sequence( [
openExtlink,
- nonWhitespaceExpression,
+ nOrMore( 1, nonWhitespaceExpression ),
whitespace,
nOrMore( 1, expression ),
closeExtlink
] );
if ( parsedResult !== null ) {
- result = [ 'EXTLINK', parsedResult[ 1 ] ];
- // TODO (mattflaschen, 2013-03-22): Clean this up if possible.
- // It's avoiding CONCAT for single nodes, so they at least doesn't get the htmlEmitter span.
- if ( parsedResult[ 3 ].length === 1 ) {
- result.push( parsedResult[ 3 ][ 0 ] );
- } else {
- result.push( [ 'CONCAT' ].concat( parsedResult[ 3 ] ) );
- }
+ // When the entire link target is a single parameter, we can't use CONCAT, as we allow
+ // passing fancy parameters (like a whole jQuery object or a function) to use for the
+ // link. Check only if it's a single match, since we can either do CONCAT or not for
+ // singles with the same effect.
+ target = parsedResult[ 1 ].length === 1 ?
+ parsedResult[ 1 ][ 0 ] :
+ [ 'CONCAT' ].concat( parsedResult[ 1 ] );
+ result = [
+ 'EXTLINK',
+ target,
+ [ 'CONCAT' ].concat( parsedResult[ 3 ] )
+ ];
}
return result;
}
- // this is the same as the above extlink, except that the url is being passed on as a parameter
- function extLinkParam() {
- var result = sequence( [
- openExtlink,
- dollar,
- digits,
- whitespace,
- expression,
- closeExtlink
- ] );
- if ( result === null ) {
- return null;
- }
- return [ 'EXTLINKPARAM', parseInt( result[ 2 ], 10 ) - 1, result[ 4 ] ];
- }
openWikilink = makeStringParser( '[[' );
closeWikilink = makeStringParser( ']]' );
pipe = makeStringParser( '|' );
return result === null ? null : result[ 1 ];
}
- wikilinkPage = choice( [
- unescapedLiteralWithoutBar,
- template
- ] );
-
function pipedWikilink() {
var result = sequence( [
- wikilinkPage,
+ nOrMore( 1, paramExpression ),
pipe,
nOrMore( 1, expression )
] );
- return result === null ? null : [ result[ 0 ], [ 'CONCAT' ].concat( result[ 2 ] ) ];
+ return result === null ? null : [
+ [ 'CONCAT' ].concat( result[ 0 ] ),
+ [ 'CONCAT' ].concat( result[ 2 ] )
+ ];
+ }
+
+ function unpipedWikilink() {
+ var result = sequence( [
+ nOrMore( 1, paramExpression )
+ ] );
+ return result === null ? null : [
+ [ 'CONCAT' ].concat( result[ 0 ] )
+ ];
}
wikilinkContents = choice( [
pipedWikilink,
- wikilinkPage // unpiped link
+ unpipedWikilink
] );
function wikilink() {
nonWhitespaceExpression = choice( [
template,
wikilink,
- extLinkParam,
extlink,
replacement,
literalWithoutSpace
paramExpression = choice( [
template,
wikilink,
- extLinkParam,
extlink,
replacement,
literalWithoutBar
expression = choice( [
template,
wikilink,
- extLinkParam,
extlink,
replacement,
html,
concat: function ( nodes ) {
var $span = $( '<span>' ).addClass( 'mediaWiki_htmlEmitter' );
$.each( nodes, function ( i, node ) {
- if ( node instanceof jQuery && node.hasClass( 'mediaWiki_htmlEmitter' ) ) {
- $.each( node.contents(), function ( j, childNode ) {
- appendWithoutParsing( $span, childNode );
- } );
- } else {
- // Let jQuery append nodes, arrays of nodes and jQuery objects
- // other things (strings, numbers, ..) are appended as text nodes (not as HTML strings)
- appendWithoutParsing( $span, node );
- }
+ // Let jQuery append nodes, arrays of nodes and jQuery objects
+ // other things (strings, numbers, ..) are appended as text nodes (not as HTML strings)
+ appendWithoutParsing( $span, node );
} );
return $span;
},
* @param {string[]} nodes
*/
wikilink: function ( nodes ) {
- var page, anchor, url;
+ var page, anchor, url, $el;
- page = nodes[ 0 ];
+ page = textify( nodes[ 0 ] );
url = mw.util.getUrl( page );
if ( nodes.length === 1 ) {
anchor = nodes[ 1 ];
}
- return $( '<a>' ).attr( {
+ $el = $( '<a>' ).attr( {
title: page,
href: url
- } )
- // FIXME This means that you can't have anything with formatting inside a wikilink.
- .text( anchor.jquery ? anchor.text() : anchor );
+ } );
+ return appendWithoutParsing( $el, anchor );
},
/**
},
/**
- * Transform parsed structure into external link
- * If the href is a jQuery object, treat it as "enclosing" the link text.
+ * Transform parsed structure into external link.
*
- * - ... function, treat it as the click handler.
- * - ... string, treat it as a URI.
+ * The "href" can be:
+ * - a jQuery object, treat it as "enclosing" the link text.
+ * - a function, treat it as the click handler.
+ * - a string, or our htmlEmitter jQuery object, treat it as a URI after stringifying.
*
* TODO: throw an error if nodes.length > 2 ?
*
var $el,
arg = nodes[ 0 ],
contents = nodes[ 1 ];
- if ( arg instanceof jQuery ) {
+ if ( arg instanceof jQuery && !arg.hasClass( 'mediaWiki_htmlEmitter' ) ) {
$el = arg;
} else {
$el = $( '<a>' );
} )
.click( arg );
} else {
- $el.attr( 'href', arg.toString() );
+ $el.attr( 'href', textify( arg ) );
}
}
return appendWithoutParsing( $el, contents );
},
- /**
- * This is basically use a combination of replace + external link (link with parameter
- * as url), but we don't want to run the regular replace here-on: inserting a
- * url as href-attribute of a link will automatically escape it already, so
- * we don't want replace to (manually) escape it as well.
- *
- * TODO: throw error if nodes.length > 1 ?
- *
- * @param {Array} nodes List of one element, integer, n >= 0
- * @param {Array} replacements List of at least n strings
- * @return {string} replacement
- */
- extlinkparam: function ( nodes, replacements ) {
- var replacement,
- index = parseInt( nodes[ 0 ], 10 );
- if ( index < replacements.length ) {
- replacement = replacements[ index ];
- } else {
- replacement = '$' + ( index + 1 );
- }
- return this.extlink( [ replacement, nodes[ 1 ] ] );
- },
-
/**
* Transform parsed structure into pluralization
* n.b. The first node may be a non-integer (for instance, a string representing an Arabic number).
for ( formIndex = 0; formIndex < forms.length; formIndex++ ) {
form = forms[ formIndex ];
- if ( form.jquery && form.hasClass( 'mediaWiki_htmlEmitter' ) ) {
+ if ( form instanceof jQuery && form.hasClass( 'mediaWiki_htmlEmitter' ) ) {
// This is a nested node, may be an explicit plural form like 5=[$2 linktext]
firstChild = form.contents().get( 0 );
if ( firstChild && firstChild.nodeType === Node.TEXT_NODE ) {
// Caching is somewhat problematic, because we do need different message functions for different maps, so
// we'd have to cache the parser as a member of this.map, which sounds a bit ugly.
// Do not use mw.jqueryMsg unless required
- if ( this.format === 'plain' || !/\{\{|[\[<>]/.test( this.map.get( this.key ) ) ) {
+ if ( this.format === 'plain' || !/\{\{|[\[<>&]/.test( this.map.get( this.key ) ) ) {
// Fall back to mw.msg's simple parser
return oldParser.apply( this );
}
$this->assertRegExp( '/<li class="[\w\s-]*mw-tag-newbie[\w\s-]*">/', $line );
}
+ public function testRecentChangesLine_numberOfWatchingUsers() {
+ $oldChangesList = $this->getOldChangesList();
+
+ $recentChange = $this->getEditChange();
+ $recentChange->numberofWatchingusers = 100;
+
+ $line = $oldChangesList->recentChangesLine( $recentChange, false, 1 );
+ $this->assertRegExp( "/(number_of_watching_users_RCview: 100)/", $line );
+ }
+
+ public function testRecentChangesLine_watchlistCssClass() {
+ $oldChangesList = $this->getOldChangesList();
+ $oldChangesList->setWatchlistDivs( true );
+
+ $recentChange = $this->getEditChange();
+ $line = $oldChangesList->recentChangesLine( $recentChange, false, 1 );
+ $this->assertRegExp( "/watchlist-0-Cat/", $line );
+ }
+
private function getNewBotEditChange() {
$user = $this->getTestUser();
protected function mysqlPing() {
}
+ protected function mysqlRealEscapeString( $s ) {
+
+ }
+
// From interface DatabaseType
function insertId() {
}
$this->assertTrue( $db->close(), "closing database" );
}
+
+ public function testToString() {
+ $db = DatabaseSqlite::newStandaloneInstance( ':memory:' );
+
+ $toString = (string)$db;
+
+ $this->assertContains( 'SQLite ', $toString );
+ }
}
--- /dev/null
+<?php
+/**
+ * A MemoizedCallable subclass that stores function return values
+ * in an instance property rather than APC.
+ */
+class ArrayBackedMemoizedCallable extends MemoizedCallable {
+ public $cache = array();
+
+ protected function fetchResult( $key, &$success ) {
+ if ( array_key_exists( $key, $this->cache ) ) {
+ $success = true;
+ return $this->cache[$key];
+ }
+ $success = false;
+ return false;
+ }
+
+ protected function storeResult( $key, $result ) {
+ $this->cache[$key] = $result;
+ }
+}
+
+
+/**
+ * PHP Unit tests for MemoizedCallable class.
+ * @covers MemoizedCallable
+ */
+class MemoizedCallableTest extends PHPUnit_Framework_TestCase {
+
+ /**
+ * The memoized callable should relate inputs to outputs in the same
+ * way as the original underlying callable.
+ */
+ public function testReturnValuePassedThrough() {
+ $mock = $this->getMock( 'stdClass', array( 'reverse' ) );
+ $mock->expects( $this->any() )
+ ->method( 'reverse' )
+ ->will( $this->returnCallback( 'strrev' ) );
+
+ $memoized = new MemoizedCallable( array( $mock, 'reverse' ) );
+ $this->assertEquals( 'flow', $memoized->invoke( 'wolf' ) );
+ }
+
+ /**
+ * Consecutive calls to the memoized callable with the same arguments
+ * should result in just one invocation of the underlying callable.
+ *
+ * @requires function apc_store
+ */
+ public function testCallableMemoized() {
+ $observer = $this->getMock( 'stdClass', array( 'computeSomething' ) );
+ $observer->expects( $this->once() )
+ ->method( 'computeSomething' )
+ ->will( $this->returnValue( 'ok' ) );
+
+ $memoized = new ArrayBackedMemoizedCallable( array( $observer, 'computeSomething' ) );
+
+ // First invocation -- delegates to $observer->computeSomething()
+ $this->assertEquals( 'ok', $memoized->invoke() );
+
+ // Second invocation -- returns memoized result
+ $this->assertEquals( 'ok', $memoized->invoke() );
+ }
+
+ /**
+ * @covers MemoizedCallable::invoke
+ */
+ public function testInvokeVariadic() {
+ $memoized = new MemoizedCallable( 'sprintf' );
+ $this->assertEquals(
+ $memoized->invokeArgs( array( 'this is %s', 'correct' ) ),
+ $memoized->invoke( 'this is %s', 'correct' )
+ );
+ }
+
+ /**
+ * @covers MemoizedCallable::call
+ */
+ public function testShortcutMethod() {
+ $this->assertEquals(
+ 'this is correct',
+ MemoizedCallable::call( 'sprintf', array( 'this is %s', 'correct' ) )
+ );
+ }
+
+ /**
+ * Outlier TTL values should be coerced to range 1 - 86400.
+ */
+ public function testTTLMaxMin() {
+ $memoized = new MemoizedCallable( 'abs', 100000 );
+ $this->assertEquals( 86400, $this->readAttribute( $memoized, 'ttl' ) );
+
+ $memoized = new MemoizedCallable( 'abs', -10 );
+ $this->assertEquals( 1, $this->readAttribute( $memoized, 'ttl' ) );
+ }
+
+ /**
+ * Closure names should be distinct.
+ */
+ public function testMemoizedClosure() {
+ $a = new MemoizedCallable( function () {
+ return 'a';
+ } );
+
+ $b = new MemoizedCallable( function () {
+ return 'b';
+ } );
+
+ $this->assertEquals( $a->invokeArgs(), 'a' );
+ $this->assertEquals( $b->invokeArgs(), 'b' );
+
+ $this->assertNotEquals(
+ $this->readAttribute( $a, 'callableName' ),
+ $this->readAttribute( $b, 'callableName' )
+ );
+ }
+
+ /**
+ * @expectedExceptionMessage non-scalar argument
+ * @expectedException InvalidArgumentException
+ */
+ public function testNonScalarArguments() {
+ $memoized = new MemoizedCallable( 'gettype' );
+ $memoized->invoke( new stdClass() );
+ }
+
+ /**
+ * @expectedExceptionMessage must be an instance of callable
+ * @expectedException InvalidArgumentException
+ */
+ public function testNotCallable() {
+ $memoized = new MemoizedCallable( 14 );
+ }
+}
$this->assertEquals( $this->cache->get( $key ), $value );
}
+ /**
+ * @covers BagOStuff::getWithSetCallback
+ */
+ public function testGetWithSetCallback() {
+ $key = wfMemcKey( 'test' );
+ $value = $this->cache->getWithSetCallback(
+ $key,
+ 30,
+ function () {
+ return 'hello kitty';
+ }
+ );
+
+ $this->assertEquals( 'hello kitty', $value );
+ $this->assertEquals( $value, $this->cache->get( $key ) );
+ }
+
/**
* @covers BagOStuff::incr
*/
protected function mockDb() {
// Cant mock from DatabaseType or DatabaseBase, they dont
// have the full gamut of methods
+ // FIXME: the constructor normally sets mAtomicLevels and mSrvCache
$databaseMysql = $this->getMockBuilder( 'DatabaseMysql' )
->disableOriginalConstructor()
->getMock();
$databaseMysql->expects( $this->any() )
->method( 'isOpen' )
->will( $this->returnValue( true ) );
+ $databaseMysql->expects( $this->any() )
+ ->method( 'getApproximateLagStatus' )
+ ->will( $this->returnValue( array( 'lag' => 0, 'since' => 0 ) ) );
return $databaseMysql;
}
}
process( tasks, QUnit.start );
} );
- QUnit.test( 'Links', 7, function ( assert ) {
- var expectedDisambiguationsText,
+ QUnit.test( 'Links', 11, function ( assert ) {
+ var testCases,
+ expectedDisambiguationsText,
expectedMultipleBars,
expectedSpecialCharacters;
expectedListUsersSitename,
'Piped wikilink with parser function in the text'
);
+
+ testCases = [
+ [
+ 'extlink-html-full',
+ 'asd [http://example.org <strong>Example</strong>] asd',
+ 'asd <a href="http://example.org"><strong>Example</strong></a> asd'
+ ],
+ [
+ 'extlink-html-partial',
+ 'asd [http://example.org foo <strong>Example</strong> bar] asd',
+ 'asd <a href="http://example.org">foo <strong>Example</strong> bar</a> asd'
+ ],
+ [
+ 'wikilink-html-full',
+ 'asd [[Example|<strong>Example</strong>]] asd',
+ 'asd <a title="Example" href="/wiki/Example"><strong>Example</strong></a> asd'
+ ],
+ [
+ 'wikilink-html-partial',
+ 'asd [[Example|foo <strong>Example</strong> bar]] asd',
+ 'asd <a title="Example" href="/wiki/Example">foo <strong>Example</strong> bar</a> asd'
+ ]
+ ];
+
+ $.each( testCases, function () {
+ var
+ key = this[ 0 ],
+ input = this[ 1 ],
+ output = this[ 2 ];
+ mw.messages.set( key, input );
+ assert.htmlEqual(
+ formatParse( key ),
+ output,
+ 'HTML in links: ' + key
+ );
+ } );
+ } );
+
+ QUnit.test( 'Replacements in links', 14, function ( assert ) {
+ var testCases = [
+ [
+ 'extlink-param-href-full',
+ 'asd [$1 Example] asd',
+ 'asd <a href="http://example.com">Example</a> asd'
+ ],
+ [
+ 'extlink-param-href-partial',
+ 'asd [$1/example Example] asd',
+ 'asd <a href="http://example.com/example">Example</a> asd'
+ ],
+ [
+ 'extlink-param-text-full',
+ 'asd [http://example.org $2] asd',
+ 'asd <a href="http://example.org">Text</a> asd'
+ ],
+ [
+ 'extlink-param-text-partial',
+ 'asd [http://example.org Example $2] asd',
+ 'asd <a href="http://example.org">Example Text</a> asd'
+ ],
+ [
+ 'extlink-param-both-full',
+ 'asd [$1 $2] asd',
+ 'asd <a href="http://example.com">Text</a> asd'
+ ],
+ [
+ 'extlink-param-both-partial',
+ 'asd [$1/example Example $2] asd',
+ 'asd <a href="http://example.com/example">Example Text</a> asd'
+ ],
+ [
+ 'wikilink-param-href-full',
+ 'asd [[$1|Example]] asd',
+ 'asd <a title="Example" href="/wiki/Example">Example</a> asd'
+ ],
+ [
+ 'wikilink-param-href-partial',
+ 'asd [[$1/Test|Example]] asd',
+ 'asd <a title="Example/Test" href="/wiki/Example/Test">Example</a> asd'
+ ],
+ [
+ 'wikilink-param-text-full',
+ 'asd [[Example|$2]] asd',
+ 'asd <a title="Example" href="/wiki/Example">Text</a> asd'
+ ],
+ [
+ 'wikilink-param-text-partial',
+ 'asd [[Example|Example $2]] asd',
+ 'asd <a title="Example" href="/wiki/Example">Example Text</a> asd'
+ ],
+ [
+ 'wikilink-param-both-full',
+ 'asd [[$1|$2]] asd',
+ 'asd <a title="Example" href="/wiki/Example">Text</a> asd'
+ ],
+ [
+ 'wikilink-param-both-partial',
+ 'asd [[$1/Test|Example $2]] asd',
+ 'asd <a title="Example/Test" href="/wiki/Example/Test">Example Text</a> asd'
+ ],
+ [
+ 'wikilink-param-unpiped-full',
+ 'asd [[$1]] asd',
+ 'asd <a title="Example" href="/wiki/Example">Example</a> asd'
+ ],
+ [
+ 'wikilink-param-unpiped-partial',
+ 'asd [[$1/Test]] asd',
+ 'asd <a title="Example/Test" href="/wiki/Example/Test">Example/Test</a> asd'
+ ]
+ ];
+
+ $.each( testCases, function () {
+ var
+ key = this[ 0 ],
+ input = this[ 1 ],
+ output = this[ 2 ],
+ paramHref = key.slice( 0, 8 ) === 'wikilink' ? 'Example' : 'http://example.com',
+ paramText = 'Text';
+ mw.messages.set( key, input );
+ assert.htmlEqual(
+ formatParse( key, paramHref, paramText ),
+ output,
+ 'Replacements in links: ' + key
+ );
+ } );
} );
// Tests that {{-transformation vs. general parsing are done as requested
} );
// HTML in wikitext
- QUnit.test( 'HTML', 26, function ( assert ) {
+ QUnit.test( 'HTML', 32, function ( assert ) {
mw.messages.set( 'jquerymsg-italics-msg', '<i>Very</i> important' );
assertBothModes( assert, [ 'jquerymsg-italics-msg' ], mw.messages.get( 'jquerymsg-italics-msg' ), 'Simple italics unchanged' );
'Mismatched HTML start and end tag treated as text'
);
- // TODO (mattflaschen, 2013-03-18): It's not a security issue, but there's no real
- // reason the htmlEmitter span needs to be here. It's an artifact of how emitting works.
mw.messages.set( 'jquerymsg-script-and-external-link', '<script>alert( "jquerymsg-script-and-external-link test" );</script> [http://example.com <i>Foo</i> bar]' );
assert.htmlEqual(
formatParse( 'jquerymsg-script-and-external-link' ),
- '<script>alert( "jquerymsg-script-and-external-link test" );</script> <a href="http://example.com"><span class="mediaWiki_htmlEmitter"><i>Foo</i> bar</span></a>',
+ '<script>alert( "jquerymsg-script-and-external-link test" );</script> <a href="http://example.com"><i>Foo</i> bar</a>',
'HTML tags in external links not interfering with escaping of other tags'
);
mw.messages.set( 'jquerymsg-link-script', '[http://example.com <script>alert( "jquerymsg-link-script test" );</script>]' );
assert.htmlEqual(
formatParse( 'jquerymsg-link-script' ),
- '<a href="http://example.com"><span class="mediaWiki_htmlEmitter"><script>alert( "jquerymsg-link-script test" );</script></span></a>',
+ '<a href="http://example.com"><script>alert( "jquerymsg-link-script test" );</script></a>',
'Non-whitelisted HTML tag in external link anchor treated as text'
);
mw.messages.set( 'jquerymsg-wikitext-contents-script', '<i><script>Script inside</script></i>' );
assert.htmlEqual(
formatParse( 'jquerymsg-wikitext-contents-script' ),
- '<i><span class="mediaWiki_htmlEmitter"><script>Script inside</script></span></i>',
+ '<i><script>Script inside</script></i>',
'Contents of valid tag are treated as wikitext, so invalid HTML element is treated as text'
);
'Foo<tag/>bar',
'Self-closing tags don\'t cause a parse error'
);
+
+ mw.messages.set( 'jquerymsg-entities1', 'A&B' );
+ mw.messages.set( 'jquerymsg-entities2', 'A>B' );
+ mw.messages.set( 'jquerymsg-entities3', 'A→B' );
+ assert.htmlEqual(
+ formatParse( 'jquerymsg-entities1' ),
+ 'A&B',
+ 'Lone "&" is escaped in text'
+ );
+ assert.htmlEqual(
+ formatParse( 'jquerymsg-entities2' ),
+ 'A&gt;B',
+ '">" entity is double-escaped in text' // (WHY?)
+ );
+ assert.htmlEqual(
+ formatParse( 'jquerymsg-entities3' ),
+ 'A&rarr;B',
+ '"→" entity is double-escaped in text'
+ );
+
+ mw.messages.set( 'jquerymsg-entities-attr1', '<i title="A&B"></i>' );
+ mw.messages.set( 'jquerymsg-entities-attr2', '<i title="A>B"></i>' );
+ mw.messages.set( 'jquerymsg-entities-attr3', '<i title="A→B"></i>' );
+ assert.htmlEqual(
+ formatParse( 'jquerymsg-entities-attr1' ),
+ '<i title="A&B"></i>',
+ 'Lone "&" is escaped in attribute'
+ );
+ assert.htmlEqual(
+ formatParse( 'jquerymsg-entities-attr2' ),
+ '<i title="A>B"></i>',
+ '">" entity is not double-escaped in attribute' // (WHY?)
+ );
+ assert.htmlEqual(
+ formatParse( 'jquerymsg-entities-attr3' ),
+ '<i title="A&rarr;B"></i>',
+ '"→" entity is double-escaped in attribute'
+ );
} );
QUnit.test( 'Behavior in case of invalid wikitext', 3, function ( assert ) {