them.
* Added new hook ChangePasswordForm to allow adding of additional fields in Special:ChangePassword
* Added new function getDomain to AuthPlugin for getting a user's domain
+* (bug 23427) New magic word {{PAGEID}} which gives the current page ID.
+ Will be null on previewing a page being created.
=== Bug fixes in 1.20 ===
* (bug 30245) Use the correct way to construct a log page title.
* (bug 32604) Some messages needs escaping of wikitext inside username
* (bug 36537) Rename wfArrayToCGI to wfArrayToCgi for consistency with wfCgiToArray.
* (bug 25946) The message on the top of Special:RecentChanges is now displayed
- in user language instead of content language
+ in user language instead of content language.
* (bug 35264) Wrong type used for <ns> in export.xsd
* (bug 24985) Use $wgTmpDirectory as the default temp directory so that people
who don't have access to /tmp can specify an alternative.
-* (bug 27283) SqlBagOStuff breaks PostgreSQL transactions
+* (bug 27283) SqlBagOStuff breaks PostgreSQL transactions.
* (bug 35727) mw.Api ajax() should put token parameter last.
+* (bug 37708) mw.Uri.clone() should make a deep copy.
=== API changes in 1.20 ===
* (bug 34316) Add ability to retrieve maximum upload size from MediaWiki API.
the format parameter.
* (bug 32384) Allow descending order for list=watchlistraw.
* (bug 31883) Limit of bkusers of list=blocks and titles of action=query is not documented in API help.
-* (bug 32492) API now allows editing using pageid
-* (bug 32497) API now allows changing of protection level using pageid
-* (bug 32498) API now allows comparing pages using pageids
-* (bug 30975) API import of pages with invalid characters in this wiki leads to Fatal Error
+* (bug 32492) API now allows editing using pageid.
+* (bug 32497) API now allows changing of protection level using pageid.
+* (bug 32498) API now allows comparing pages using pageids.
+* (bug 30975) API import of pages with invalid characters in this wiki leads to Fatal Error.
* (bug 30488) API now allows listing of backlinks/embeddedin/imageusage per pageid
* (bug 34927) Output media_type for list=filearchive
* (bug 28814) add properties to output of action=parse
'DoubleRedirectJob' => 'includes/job/DoubleRedirectJob.php',
'EmaillingJob' => 'includes/job/EmaillingJob.php',
'EnotifNotifyJob' => 'includes/job/EnotifNotifyJob.php',
- 'Job' => 'includes/job/JobQueue.php',
+ 'Job' => 'includes/job/Job.php',
'RefreshLinksJob' => 'includes/job/RefreshLinksJob.php',
'RefreshLinksJob2' => 'includes/job/RefreshLinksJob.php',
'UploadFromUrlJob' => 'includes/job/UploadFromUrlJob.php',
'numberoffiles',
'numberofedits',
'articlepath',
+ 'pageid',
'sitename',
'server',
'servername',
$user->setOption( 'rememberpassword', 1 );
$user->setCookies( $this->getRequest() );
+ ApiQueryInfo::resetTokenCache();
+
// Run hooks.
// @todo FIXME: Split back and frontend from this hook.
// @todo FIXME: This hook should be placed in the backend
if ( !isset( $moduleParams['token'] ) ) {
$this->dieUsageMsg( array( 'missingparam', 'token' ) );
} else {
- if ( !$this->getUser()->matchEditToken( $moduleParams['token'], $salt ) ) {
+ if ( !$this->getUser()->matchEditToken( $moduleParams['token'], $salt, $this->getContext()->getRequest() ) ) {
$this->dieUsageMsg( 'sessionfailure' );
}
}
return $this->tokenFunctions;
}
+ static $cachedTokens = array();
+
+ public static function resetTokenCache() {
+ ApiQueryInfo::$cachedTokens = array();
+ }
+
public static function getEditToken( $pageid, $title ) {
// We could check for $title->userCan('edit') here,
// but that's too expensive for this purpose
return false;
}
- // The edit token is always the same, let's exploit that
- static $cachedEditToken = null;
- if ( !is_null( $cachedEditToken ) ) {
- return $cachedEditToken;
+ // The token is always the same, let's exploit that
+ if ( !isset( ApiQueryInfo::$cachedTokens[ 'edit' ] ) ) {
+ ApiQueryInfo::$cachedTokens[ 'edit' ] = $wgUser->getEditToken();
}
- $cachedEditToken = $wgUser->getEditToken();
- return $cachedEditToken;
+ return ApiQueryInfo::$cachedTokens[ 'edit' ];
}
public static function getDeleteToken( $pageid, $title ) {
return false;
}
- static $cachedDeleteToken = null;
- if ( !is_null( $cachedDeleteToken ) ) {
- return $cachedDeleteToken;
+ // The token is always the same, let's exploit that
+ if ( !isset( ApiQueryInfo::$cachedTokens[ 'delete' ] ) ) {
+ ApiQueryInfo::$cachedTokens[ 'delete' ] = $wgUser->getEditToken();
}
- $cachedDeleteToken = $wgUser->getEditToken();
- return $cachedDeleteToken;
+ return ApiQueryInfo::$cachedTokens[ 'delete' ];
}
public static function getProtectToken( $pageid, $title ) {
return false;
}
- static $cachedProtectToken = null;
- if ( !is_null( $cachedProtectToken ) ) {
- return $cachedProtectToken;
+ // The token is always the same, let's exploit that
+ if ( !isset( ApiQueryInfo::$cachedTokens[ 'protect' ] ) ) {
+ ApiQueryInfo::$cachedTokens[ 'protect' ] = $wgUser->getEditToken();
}
- $cachedProtectToken = $wgUser->getEditToken();
- return $cachedProtectToken;
+ return ApiQueryInfo::$cachedTokens[ 'protect' ];
}
public static function getMoveToken( $pageid, $title ) {
return false;
}
- static $cachedMoveToken = null;
- if ( !is_null( $cachedMoveToken ) ) {
- return $cachedMoveToken;
+ // The token is always the same, let's exploit that
+ if ( !isset( ApiQueryInfo::$cachedTokens[ 'move' ] ) ) {
+ ApiQueryInfo::$cachedTokens[ 'move' ] = $wgUser->getEditToken();
}
- $cachedMoveToken = $wgUser->getEditToken();
- return $cachedMoveToken;
+ return ApiQueryInfo::$cachedTokens[ 'move' ];
}
public static function getBlockToken( $pageid, $title ) {
return false;
}
- static $cachedBlockToken = null;
- if ( !is_null( $cachedBlockToken ) ) {
- return $cachedBlockToken;
+ // The token is always the same, let's exploit that
+ if ( !isset( ApiQueryInfo::$cachedTokens[ 'block' ] ) ) {
+ ApiQueryInfo::$cachedTokens[ 'block' ] = $wgUser->getEditToken();
}
- $cachedBlockToken = $wgUser->getEditToken();
- return $cachedBlockToken;
+ return ApiQueryInfo::$cachedTokens[ 'block' ];
}
public static function getUnblockToken( $pageid, $title ) {
return false;
}
- static $cachedEmailToken = null;
- if ( !is_null( $cachedEmailToken ) ) {
- return $cachedEmailToken;
+ // The token is always the same, let's exploit that
+ if ( !isset( ApiQueryInfo::$cachedTokens[ 'email' ] ) ) {
+ ApiQueryInfo::$cachedTokens[ 'email' ] = $wgUser->getEditToken();
}
- $cachedEmailToken = $wgUser->getEditToken();
- return $cachedEmailToken;
+ return ApiQueryInfo::$cachedTokens[ 'email' ];
}
public static function getImportToken( $pageid, $title ) {
return false;
}
- static $cachedImportToken = null;
- if ( !is_null( $cachedImportToken ) ) {
- return $cachedImportToken;
+ // The token is always the same, let's exploit that
+ if ( !isset( ApiQueryInfo::$cachedTokens[ 'import' ] ) ) {
+ ApiQueryInfo::$cachedTokens[ 'import' ] = $wgUser->getEditToken();
}
- $cachedImportToken = $wgUser->getEditToken();
- return $cachedImportToken;
+ return ApiQueryInfo::$cachedTokens[ 'import' ];
}
public static function getWatchToken( $pageid, $title ) {
return false;
}
- static $cachedWatchToken = null;
- if ( !is_null( $cachedWatchToken ) ) {
- return $cachedWatchToken;
+ // The token is always the same, let's exploit that
+ if ( !isset( ApiQueryInfo::$cachedTokens[ 'watch' ] ) ) {
+ ApiQueryInfo::$cachedTokens[ 'watch' ] = $wgUser->getEditToken( 'watch' );
}
- $cachedWatchToken = $wgUser->getEditToken( 'watch' );
- return $cachedWatchToken;
+ return ApiQueryInfo::$cachedTokens[ 'watch' ];
}
public static function getOptionsToken( $pageid, $title ) {
return false;
}
- static $cachedOptionsToken = null;
- if ( !is_null( $cachedOptionsToken ) ) {
- return $cachedOptionsToken;
+ // The token is always the same, let's exploit that
+ if ( !isset( ApiQueryInfo::$cachedTokens[ 'options' ] ) ) {
+ ApiQueryInfo::$cachedTokens[ 'options' ] = $wgUser->getEditToken();
}
- $cachedOptionsToken = $wgUser->getEditToken();
- return $cachedOptionsToken;
+ return ApiQueryInfo::$cachedTokens[ 'options' ];
}
public function execute() {
{
$destTable = $this->tableName( $destTable );
- if( is_array( $insertOptions ) ) {
- $insertOptions = implode( ' ', $insertOptions ); // FIXME: This is unused
+ if( !is_array( $insertOptions ) ) {
+ $insertOptions = array( $insertOptions );
}
+
+ /*
+ * If IGNORE is set, we use savepoints to emulate mysql's behavior
+ * Ignore LOW PRIORITY option, since it is MySQL-specific
+ */
+ $savepoint = null;
+ if ( in_array( 'IGNORE', $insertOptions ) ) {
+ $savepoint = new SavepointPostgres( $this, 'mw' );
+ $olde = error_reporting( 0 );
+ $numrowsinserted = 0;
+ $savepoint->savepoint();
+ }
+
if( !is_array( $selectOptions ) ) {
$selectOptions = array( $selectOptions );
}
$srcTable = $this->tableName( $srcTable );
}
- // If IGNORE is set, we use savepoints to emulate mysql's behavior
- $savepoint = null;
- if ( in_array( 'IGNORE', $options ) ) {
- $savepoint = new SavepointPostgres( $this, 'mw' );
- $olde = error_reporting( 0 );
- $numrowsinserted = 0;
- $savepoint->savepoint();
- }
-
$sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
" SELECT $startOpts " . implode( ',', $varMap ) .
" FROM $srcTable $useIndex";
--- /dev/null
+<?php
+/**
+ * Job queue base code.
+ *
+ * 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
+ * @defgroup JobQueue JobQueue
+ */
+
+/**
+ * Class to both describe a background job and handle jobs.
+ *
+ * @ingroup JobQueue
+ */
+abstract class Job {
+
+ /**
+ * @var Title
+ */
+ var $title;
+
+ var $command,
+ $params,
+ $id,
+ $removeDuplicates,
+ $error;
+
+ /*-------------------------------------------------------------------------
+ * Abstract functions
+ *------------------------------------------------------------------------*/
+
+ /**
+ * Run the job
+ * @return boolean success
+ */
+ abstract function run();
+
+ /*-------------------------------------------------------------------------
+ * Static functions
+ *------------------------------------------------------------------------*/
+
+ /**
+ * Pop a job of a certain type. This tries less hard than pop() to
+ * actually find a job; it may be adversely affected by concurrent job
+ * runners.
+ *
+ * @param $type string
+ *
+ * @return Job
+ */
+ static function pop_type( $type ) {
+ wfProfilein( __METHOD__ );
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ $dbw->begin( __METHOD__ );
+
+ $row = $dbw->selectRow(
+ 'job',
+ '*',
+ array( 'job_cmd' => $type ),
+ __METHOD__,
+ array( 'LIMIT' => 1, 'FOR UPDATE' )
+ );
+
+ if ( $row === false ) {
+ $dbw->commit( __METHOD__ );
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ /* Ensure we "own" this row */
+ $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
+ $affected = $dbw->affectedRows();
+ $dbw->commit( __METHOD__ );
+
+ if ( $affected == 0 ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ wfIncrStats( 'job-pop' );
+ $namespace = $row->job_namespace;
+ $dbkey = $row->job_title;
+ $title = Title::makeTitleSafe( $namespace, $dbkey );
+ $job = Job::factory( $row->job_cmd, $title, Job::extractBlob( $row->job_params ),
+ $row->job_id );
+
+ $job->removeDuplicates();
+
+ wfProfileOut( __METHOD__ );
+ return $job;
+ }
+
+ /**
+ * Pop a job off the front of the queue
+ *
+ * @param $offset Integer: Number of jobs to skip
+ * @return Job or false if there's no jobs
+ */
+ static function pop( $offset = 0 ) {
+ wfProfileIn( __METHOD__ );
+
+ $dbr = wfGetDB( DB_SLAVE );
+
+ /* Get a job from the slave, start with an offset,
+ scan full set afterwards, avoid hitting purged rows
+
+ NB: If random fetch previously was used, offset
+ will always be ahead of few entries
+ */
+
+ $conditions = self::defaultQueueConditions();
+
+ $offset = intval( $offset );
+ $options = array( 'ORDER BY' => 'job_id', 'USE INDEX' => 'PRIMARY' );
+
+ $row = $dbr->selectRow( 'job', '*',
+ array_merge( $conditions, array( "job_id >= $offset" ) ),
+ __METHOD__,
+ $options
+ );
+
+ // Refetching without offset is needed as some of job IDs could have had delayed commits
+ // and have lower IDs than jobs already executed, blame concurrency :)
+ //
+ if ( $row === false ) {
+ if ( $offset != 0 ) {
+ $row = $dbr->selectRow( 'job', '*', $conditions, __METHOD__, $options );
+ }
+
+ if ( $row === false ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ }
+
+ // Try to delete it from the master
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
+ $affected = $dbw->affectedRows();
+ $dbw->commit( __METHOD__ );
+
+ if ( !$affected ) {
+ // Failed, someone else beat us to it
+ // Try getting a random row
+ $row = $dbw->selectRow( 'job', array( 'MIN(job_id) as minjob',
+ 'MAX(job_id) as maxjob' ), '1=1', __METHOD__ );
+ if ( $row === false || is_null( $row->minjob ) || is_null( $row->maxjob ) ) {
+ // No jobs to get
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ // Get the random row
+ $row = $dbw->selectRow( 'job', '*',
+ 'job_id >= ' . mt_rand( $row->minjob, $row->maxjob ), __METHOD__ );
+ if ( $row === false ) {
+ // Random job gone before we got the chance to select it
+ // Give up
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ // Delete the random row
+ $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
+ $affected = $dbw->affectedRows();
+ $dbw->commit( __METHOD__ );
+
+ if ( !$affected ) {
+ // Random job gone before we exclusively deleted it
+ // Give up
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+ }
+
+ // If execution got to here, there's a row in $row that has been deleted from the database
+ // by this thread. Hence the concurrent pop was successful.
+ wfIncrStats( 'job-pop' );
+ $namespace = $row->job_namespace;
+ $dbkey = $row->job_title;
+ $title = Title::makeTitleSafe( $namespace, $dbkey );
+
+ if ( is_null( $title ) ) {
+ wfProfileOut( __METHOD__ );
+ return false;
+ }
+
+ $job = Job::factory( $row->job_cmd, $title, Job::extractBlob( $row->job_params ), $row->job_id );
+
+ // Remove any duplicates it may have later in the queue
+ $job->removeDuplicates();
+
+ wfProfileOut( __METHOD__ );
+ return $job;
+ }
+
+ /**
+ * Create the appropriate object to handle a specific job
+ *
+ * @param $command String: Job command
+ * @param $title Title: Associated title
+ * @param $params Array: Job parameters
+ * @param $id Int: Job identifier
+ * @return Job
+ */
+ static function factory( $command, Title $title, $params = false, $id = 0 ) {
+ global $wgJobClasses;
+ if( isset( $wgJobClasses[$command] ) ) {
+ $class = $wgJobClasses[$command];
+ return new $class( $title, $params, $id );
+ }
+ throw new MWException( "Invalid job command `{$command}`" );
+ }
+
+ /**
+ * @param $params
+ * @return string
+ */
+ static function makeBlob( $params ) {
+ if ( $params !== false ) {
+ return serialize( $params );
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * @param $blob
+ * @return bool|mixed
+ */
+ static function extractBlob( $blob ) {
+ if ( (string)$blob !== '' ) {
+ return unserialize( $blob );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Batch-insert a group of jobs into the queue.
+ * This will be wrapped in a transaction with a forced commit.
+ *
+ * This may add duplicate at insert time, but they will be
+ * removed later on, when the first one is popped.
+ *
+ * @param $jobs array of Job objects
+ */
+ static function batchInsert( $jobs ) {
+ if ( !count( $jobs ) ) {
+ return;
+ }
+ $dbw = wfGetDB( DB_MASTER );
+ $rows = array();
+
+ /**
+ * @var $job Job
+ */
+ foreach ( $jobs as $job ) {
+ $rows[] = $job->insertFields();
+ if ( count( $rows ) >= 50 ) {
+ # Do a small transaction to avoid slave lag
+ $dbw->begin( __METHOD__ );
+ $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
+ $dbw->commit( __METHOD__ );
+ $rows = array();
+ }
+ }
+ if ( $rows ) { // last chunk
+ $dbw->begin( __METHOD__ );
+ $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
+ $dbw->commit( __METHOD__ );
+ }
+ wfIncrStats( 'job-insert', count( $jobs ) );
+ }
+
+ /**
+ * Insert a group of jobs into the queue.
+ *
+ * Same as batchInsert() but does not commit and can thus
+ * be rolled-back as part of a larger transaction. However,
+ * large batches of jobs can cause slave lag.
+ *
+ * @param $jobs array of Job objects
+ */
+ static function safeBatchInsert( $jobs ) {
+ if ( !count( $jobs ) ) {
+ return;
+ }
+ $dbw = wfGetDB( DB_MASTER );
+ $rows = array();
+ foreach ( $jobs as $job ) {
+ $rows[] = $job->insertFields();
+ if ( count( $rows ) >= 500 ) {
+ $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
+ $rows = array();
+ }
+ }
+ if ( $rows ) { // last chunk
+ $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
+ }
+ wfIncrStats( 'job-insert', count( $jobs ) );
+ }
+
+
+ /**
+ * SQL conditions to apply on most JobQueue queries
+ *
+ * Whenever we exclude jobs types from the default queue, we want to make
+ * sure that queries to the job queue actually ignore them.
+ *
+ * @return array SQL conditions suitable for Database:: methods
+ */
+ static function defaultQueueConditions( ) {
+ global $wgJobTypesExcludedFromDefaultQueue;
+ $conditions = array();
+ if ( count( $wgJobTypesExcludedFromDefaultQueue ) > 0 ) {
+ $dbr = wfGetDB( DB_SLAVE );
+ foreach ( $wgJobTypesExcludedFromDefaultQueue as $cmdType ) {
+ $conditions[] = "job_cmd != " . $dbr->addQuotes( $cmdType );
+ }
+ }
+ return $conditions;
+ }
+
+ /*-------------------------------------------------------------------------
+ * Non-static functions
+ *------------------------------------------------------------------------*/
+
+ /**
+ * @param $command
+ * @param $title
+ * @param $params array
+ * @param int $id
+ */
+ function __construct( $command, $title, $params = false, $id = 0 ) {
+ $this->command = $command;
+ $this->title = $title;
+ $this->params = $params;
+ $this->id = $id;
+
+ // A bit of premature generalisation
+ // Oh well, the whole class is premature generalisation really
+ $this->removeDuplicates = true;
+ }
+
+ /**
+ * Insert a single job into the queue.
+ * @return bool true on success
+ */
+ function insert() {
+ $fields = $this->insertFields();
+
+ $dbw = wfGetDB( DB_MASTER );
+
+ if ( $this->removeDuplicates ) {
+ $res = $dbw->select( 'job', array( '1' ), $fields, __METHOD__ );
+ if ( $dbw->numRows( $res ) ) {
+ return true;
+ }
+ }
+ wfIncrStats( 'job-insert' );
+ return $dbw->insert( 'job', $fields, __METHOD__ );
+ }
+
+ /**
+ * @return array
+ */
+ protected function insertFields() {
+ $dbw = wfGetDB( DB_MASTER );
+ return array(
+ 'job_id' => $dbw->nextSequenceValue( 'job_job_id_seq' ),
+ 'job_cmd' => $this->command,
+ 'job_namespace' => $this->title->getNamespace(),
+ 'job_title' => $this->title->getDBkey(),
+ 'job_timestamp' => $dbw->timestamp(),
+ 'job_params' => Job::makeBlob( $this->params )
+ );
+ }
+
+ /**
+ * Remove jobs in the job queue which are duplicates of this job.
+ * This is deadlock-prone and so starts its own transaction.
+ */
+ function removeDuplicates() {
+ if ( !$this->removeDuplicates ) {
+ return;
+ }
+
+ $fields = $this->insertFields();
+ unset( $fields['job_id'] );
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->begin( __METHOD__ );
+ $dbw->delete( 'job', $fields, __METHOD__ );
+ $affected = $dbw->affectedRows();
+ $dbw->commit( __METHOD__ );
+ if ( $affected ) {
+ wfIncrStats( 'job-dup-delete', $affected );
+ }
+ }
+
+ /**
+ * @return string
+ */
+ function toString() {
+ $paramString = '';
+ if ( $this->params ) {
+ foreach ( $this->params as $key => $value ) {
+ if ( $paramString != '' ) {
+ $paramString .= ' ';
+ }
+ $paramString .= "$key=$value";
+ }
+ }
+
+ if ( is_object( $this->title ) ) {
+ $s = "{$this->command} " . $this->title->getPrefixedDBkey();
+ if ( $paramString !== '' ) {
+ $s .= ' ' . $paramString;
+ }
+ return $s;
+ } else {
+ return "{$this->command} $paramString";
+ }
+ }
+
+ protected function setLastError( $error ) {
+ $this->error = $error;
+ }
+
+ function getLastError() {
+ return $this->error;
+ }
+}
+++ /dev/null
-<?php
-/**
- * Job queue base code.
- *
- * 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
- * @defgroup JobQueue JobQueue
- */
-
-/**
- * Class to both describe a background job and handle jobs.
- *
- * @ingroup JobQueue
- */
-abstract class Job {
-
- /**
- * @var Title
- */
- var $title;
-
- var $command,
- $params,
- $id,
- $removeDuplicates,
- $error;
-
- /*-------------------------------------------------------------------------
- * Abstract functions
- *------------------------------------------------------------------------*/
-
- /**
- * Run the job
- * @return boolean success
- */
- abstract function run();
-
- /*-------------------------------------------------------------------------
- * Static functions
- *------------------------------------------------------------------------*/
-
- /**
- * Pop a job of a certain type. This tries less hard than pop() to
- * actually find a job; it may be adversely affected by concurrent job
- * runners.
- *
- * @param $type string
- *
- * @return Job
- */
- static function pop_type( $type ) {
- wfProfilein( __METHOD__ );
-
- $dbw = wfGetDB( DB_MASTER );
-
- $dbw->begin( __METHOD__ );
-
- $row = $dbw->selectRow(
- 'job',
- '*',
- array( 'job_cmd' => $type ),
- __METHOD__,
- array( 'LIMIT' => 1, 'FOR UPDATE' )
- );
-
- if ( $row === false ) {
- $dbw->commit( __METHOD__ );
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- /* Ensure we "own" this row */
- $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
- $affected = $dbw->affectedRows();
- $dbw->commit( __METHOD__ );
-
- if ( $affected == 0 ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- wfIncrStats( 'job-pop' );
- $namespace = $row->job_namespace;
- $dbkey = $row->job_title;
- $title = Title::makeTitleSafe( $namespace, $dbkey );
- $job = Job::factory( $row->job_cmd, $title, Job::extractBlob( $row->job_params ),
- $row->job_id );
-
- $job->removeDuplicates();
-
- wfProfileOut( __METHOD__ );
- return $job;
- }
-
- /**
- * Pop a job off the front of the queue
- *
- * @param $offset Integer: Number of jobs to skip
- * @return Job or false if there's no jobs
- */
- static function pop( $offset = 0 ) {
- wfProfileIn( __METHOD__ );
-
- $dbr = wfGetDB( DB_SLAVE );
-
- /* Get a job from the slave, start with an offset,
- scan full set afterwards, avoid hitting purged rows
-
- NB: If random fetch previously was used, offset
- will always be ahead of few entries
- */
-
- $conditions = self::defaultQueueConditions();
-
- $offset = intval( $offset );
- $options = array( 'ORDER BY' => 'job_id', 'USE INDEX' => 'PRIMARY' );
-
- $row = $dbr->selectRow( 'job', '*',
- array_merge( $conditions, array( "job_id >= $offset" ) ),
- __METHOD__,
- $options
- );
-
- // Refetching without offset is needed as some of job IDs could have had delayed commits
- // and have lower IDs than jobs already executed, blame concurrency :)
- //
- if ( $row === false ) {
- if ( $offset != 0 ) {
- $row = $dbr->selectRow( 'job', '*', $conditions, __METHOD__, $options );
- }
-
- if ( $row === false ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
- }
-
- // Try to delete it from the master
- $dbw = wfGetDB( DB_MASTER );
- $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
- $affected = $dbw->affectedRows();
- $dbw->commit( __METHOD__ );
-
- if ( !$affected ) {
- // Failed, someone else beat us to it
- // Try getting a random row
- $row = $dbw->selectRow( 'job', array( 'MIN(job_id) as minjob',
- 'MAX(job_id) as maxjob' ), '1=1', __METHOD__ );
- if ( $row === false || is_null( $row->minjob ) || is_null( $row->maxjob ) ) {
- // No jobs to get
- wfProfileOut( __METHOD__ );
- return false;
- }
- // Get the random row
- $row = $dbw->selectRow( 'job', '*',
- 'job_id >= ' . mt_rand( $row->minjob, $row->maxjob ), __METHOD__ );
- if ( $row === false ) {
- // Random job gone before we got the chance to select it
- // Give up
- wfProfileOut( __METHOD__ );
- return false;
- }
- // Delete the random row
- $dbw->delete( 'job', array( 'job_id' => $row->job_id ), __METHOD__ );
- $affected = $dbw->affectedRows();
- $dbw->commit( __METHOD__ );
-
- if ( !$affected ) {
- // Random job gone before we exclusively deleted it
- // Give up
- wfProfileOut( __METHOD__ );
- return false;
- }
- }
-
- // If execution got to here, there's a row in $row that has been deleted from the database
- // by this thread. Hence the concurrent pop was successful.
- wfIncrStats( 'job-pop' );
- $namespace = $row->job_namespace;
- $dbkey = $row->job_title;
- $title = Title::makeTitleSafe( $namespace, $dbkey );
-
- if ( is_null( $title ) ) {
- wfProfileOut( __METHOD__ );
- return false;
- }
-
- $job = Job::factory( $row->job_cmd, $title, Job::extractBlob( $row->job_params ), $row->job_id );
-
- // Remove any duplicates it may have later in the queue
- $job->removeDuplicates();
-
- wfProfileOut( __METHOD__ );
- return $job;
- }
-
- /**
- * Create the appropriate object to handle a specific job
- *
- * @param $command String: Job command
- * @param $title Title: Associated title
- * @param $params Array: Job parameters
- * @param $id Int: Job identifier
- * @return Job
- */
- static function factory( $command, Title $title, $params = false, $id = 0 ) {
- global $wgJobClasses;
- if( isset( $wgJobClasses[$command] ) ) {
- $class = $wgJobClasses[$command];
- return new $class( $title, $params, $id );
- }
- throw new MWException( "Invalid job command `{$command}`" );
- }
-
- /**
- * @param $params
- * @return string
- */
- static function makeBlob( $params ) {
- if ( $params !== false ) {
- return serialize( $params );
- } else {
- return '';
- }
- }
-
- /**
- * @param $blob
- * @return bool|mixed
- */
- static function extractBlob( $blob ) {
- if ( (string)$blob !== '' ) {
- return unserialize( $blob );
- } else {
- return false;
- }
- }
-
- /**
- * Batch-insert a group of jobs into the queue.
- * This will be wrapped in a transaction with a forced commit.
- *
- * This may add duplicate at insert time, but they will be
- * removed later on, when the first one is popped.
- *
- * @param $jobs array of Job objects
- */
- static function batchInsert( $jobs ) {
- if ( !count( $jobs ) ) {
- return;
- }
- $dbw = wfGetDB( DB_MASTER );
- $rows = array();
-
- /**
- * @var $job Job
- */
- foreach ( $jobs as $job ) {
- $rows[] = $job->insertFields();
- if ( count( $rows ) >= 50 ) {
- # Do a small transaction to avoid slave lag
- $dbw->begin( __METHOD__ );
- $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
- $dbw->commit( __METHOD__ );
- $rows = array();
- }
- }
- if ( $rows ) { // last chunk
- $dbw->begin( __METHOD__ );
- $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
- $dbw->commit( __METHOD__ );
- }
- wfIncrStats( 'job-insert', count( $jobs ) );
- }
-
- /**
- * Insert a group of jobs into the queue.
- *
- * Same as batchInsert() but does not commit and can thus
- * be rolled-back as part of a larger transaction. However,
- * large batches of jobs can cause slave lag.
- *
- * @param $jobs array of Job objects
- */
- static function safeBatchInsert( $jobs ) {
- if ( !count( $jobs ) ) {
- return;
- }
- $dbw = wfGetDB( DB_MASTER );
- $rows = array();
- foreach ( $jobs as $job ) {
- $rows[] = $job->insertFields();
- if ( count( $rows ) >= 500 ) {
- $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
- $rows = array();
- }
- }
- if ( $rows ) { // last chunk
- $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' );
- }
- wfIncrStats( 'job-insert', count( $jobs ) );
- }
-
-
- /**
- * SQL conditions to apply on most JobQueue queries
- *
- * Whenever we exclude jobs types from the default queue, we want to make
- * sure that queries to the job queue actually ignore them.
- *
- * @return array SQL conditions suitable for Database:: methods
- */
- static function defaultQueueConditions( ) {
- global $wgJobTypesExcludedFromDefaultQueue;
- $conditions = array();
- if ( count( $wgJobTypesExcludedFromDefaultQueue ) > 0 ) {
- $dbr = wfGetDB( DB_SLAVE );
- foreach ( $wgJobTypesExcludedFromDefaultQueue as $cmdType ) {
- $conditions[] = "job_cmd != " . $dbr->addQuotes( $cmdType );
- }
- }
- return $conditions;
- }
-
- /*-------------------------------------------------------------------------
- * Non-static functions
- *------------------------------------------------------------------------*/
-
- /**
- * @param $command
- * @param $title
- * @param $params array
- * @param int $id
- */
- function __construct( $command, $title, $params = false, $id = 0 ) {
- $this->command = $command;
- $this->title = $title;
- $this->params = $params;
- $this->id = $id;
-
- // A bit of premature generalisation
- // Oh well, the whole class is premature generalisation really
- $this->removeDuplicates = true;
- }
-
- /**
- * Insert a single job into the queue.
- * @return bool true on success
- */
- function insert() {
- $fields = $this->insertFields();
-
- $dbw = wfGetDB( DB_MASTER );
-
- if ( $this->removeDuplicates ) {
- $res = $dbw->select( 'job', array( '1' ), $fields, __METHOD__ );
- if ( $dbw->numRows( $res ) ) {
- return true;
- }
- }
- wfIncrStats( 'job-insert' );
- return $dbw->insert( 'job', $fields, __METHOD__ );
- }
-
- /**
- * @return array
- */
- protected function insertFields() {
- $dbw = wfGetDB( DB_MASTER );
- return array(
- 'job_id' => $dbw->nextSequenceValue( 'job_job_id_seq' ),
- 'job_cmd' => $this->command,
- 'job_namespace' => $this->title->getNamespace(),
- 'job_title' => $this->title->getDBkey(),
- 'job_timestamp' => $dbw->timestamp(),
- 'job_params' => Job::makeBlob( $this->params )
- );
- }
-
- /**
- * Remove jobs in the job queue which are duplicates of this job.
- * This is deadlock-prone and so starts its own transaction.
- */
- function removeDuplicates() {
- if ( !$this->removeDuplicates ) {
- return;
- }
-
- $fields = $this->insertFields();
- unset( $fields['job_id'] );
- $dbw = wfGetDB( DB_MASTER );
- $dbw->begin( __METHOD__ );
- $dbw->delete( 'job', $fields, __METHOD__ );
- $affected = $dbw->affectedRows();
- $dbw->commit( __METHOD__ );
- if ( $affected ) {
- wfIncrStats( 'job-dup-delete', $affected );
- }
- }
-
- /**
- * @return string
- */
- function toString() {
- $paramString = '';
- if ( $this->params ) {
- foreach ( $this->params as $key => $value ) {
- if ( $paramString != '' ) {
- $paramString .= ' ';
- }
- $paramString .= "$key=$value";
- }
- }
-
- if ( is_object( $this->title ) ) {
- $s = "{$this->command} " . $this->title->getPrefixedDBkey();
- if ( $paramString !== '' ) {
- $s .= ' ' . $paramString;
- }
- return $s;
- } else {
- return "{$this->command} $paramString";
- }
- }
-
- protected function setLastError( $error ) {
- $this->error = $error;
- }
-
- function getLastError() {
- return $this->error;
- }
-}
$offset = $match[0][1] + strlen( $match[0][0] ) + $lengthIncrease;
continue;
}
+
+ // Guard against double slashes, because "some/remote/../foo.png"
+ // resolves to "some/remote/foo.png" on (some?) clients (bug 27052).
+ if ( substr( $remote, -1 ) == '/' ) {
+ $remote = substr( $remote, 0, -1 );
+ }
+
// Shortcuts
$embed = $match['embed'][0];
$pre = $match['pre'][0];
$query = $match['query'][0];
$url = "{$remote}/{$match['file'][0]}";
$file = "{$local}/{$match['file'][0]}";
- // bug 27052 - Guard against double slashes, because foo//../bar
- // apparently resolves to foo/bar on (some?) clients
- $url = preg_replace( '#([^:])//+#', '\1/', $url );
+
$replacement = false;
+
if ( $local !== false && file_exists( $file ) ) {
// Add version parameter as a time-stamp in ISO 8601 format,
// using Z for the timezone, meaning GMT
$subjPage = $this->mTitle->getSubjectPage();
$value = wfEscapeWikiText( $subjPage->getPrefixedUrl() );
break;
+ case 'pageid': // requested in bug 23427
+ $pageid = $this->getTitle()->getArticleId();
+ if( $pageid == 0 ) {
+ # 0 means the page doesn't exist in the database,
+ # which means the user is previewing a new page.
+ # The vary-revision flag must be set, because the magic word
+ # will have a different value once the page is saved.
+ $this->mOutput->setFlag( 'vary-revision' );
+ wfDebug( __METHOD__ . ": {{PAGEID}} used in a new page, setting vary-revision...\n" );
+ }
+ $value = $pageid ? $pageid : null;
+ break;
case 'revisionid':
# Let the edit saving system know we should parse the page
# *after* a revision ID has been assigned.
'bcc' => 'بلوچی مکرانی', # Southern Balochi
'bcl' => 'Bikol Central', # Bikol: Central Bicolano language
'be' => 'беларуская', # Belarusian normative
- 'be-tarask' => "\xE2\x80\xAAÐ\91еларуская (тарашкевіца)\xE2\x80\xAC", # Belarusian in Taraskievica orthography
- 'be-x-old' => "\xE2\x80\xAAÐ\91еларуская (тарашкевіца)\xE2\x80\xAC", # Belarusian in Taraskievica orthography; compat link
+ 'be-tarask' => "\xE2\x80\xAAбеларуская (тарашкевіца)\xE2\x80\xAC", # Belarusian in Taraskievica orthography
+ 'be-x-old' => "\xE2\x80\xAAбеларуская (тарашкевіца)\xE2\x80\xAC", # Belarusian in Taraskievica orthography; compat link
'bg' => 'български', # Bulgarian
'bh' => 'भोजपुरी', # Bihari macro language. Falls back to Bhojpuri (bho). The name actually says "Bhojpuri".
'bho' => 'भोजपुरी', # Bhojpuri
'crh-latn' => "\xE2\x80\xAAQırımtatarca (Latin)\xE2\x80\xAC", # Crimean Tatar (Latin)
'crh-cyrl' => "\xE2\x80\xAAКъырымтатарджа (Кирилл)\xE2\x80\xAC", # Crimean Tatar (Cyrillic)
'cs' => 'česky', # Czech
- 'csb' => 'Kaszëbsczi', # Cassubian
- 'cu' => 'Словѣ́ньскъ / ⰔⰎⰑⰂⰡⰐⰠⰔⰍⰟ', # Old Church Slavonic (ancient language)
+ 'csb' => 'kaszëbsczi', # Cassubian
+ 'cu' => 'словѣ́ньскъ / ⰔⰎⰑⰂⰡⰐⰠⰔⰍⰟ', # Old Church Slavonic (ancient language)
'cv' => 'Чӑвашла', # Chuvash
'cy' => 'Cymraeg', # Welsh
'da' => 'dansk', # Danish
'de-ch' => 'Schweizer Hochdeutsch', # Swiss Standard German
'de-formal' => "\xE2\x80\xAADeutsch (Sie-Form)\xE2\x80\xAC", # German - formal address ("Sie")
'diq' => 'Zazaki', # Zazaki
- 'dsb' => 'Dolnoserbski', # Lower Sorbian
+ 'dsb' => 'dolnoserbski', # Lower Sorbian
'dtp' => 'Dusun Bundu-liwan', # Central Dusun
'dv' => 'ދިވެހިބަސް', # Dhivehi
'dz' => 'ཇོང་ཁ', # Dzongkha (Bhutan)
'hil' => 'Ilonggo', # Hiligaynon
'ho' => 'Hiri Motu', # Hiri Motu
'hr' => 'hrvatski', # Croatian
- 'hsb' => 'Hornjoserbsce', # Upper Sorbian
+ 'hsb' => 'hornjoserbsce', # Upper Sorbian
'ht' => 'Kreyòl ayisyen', # Haitian Creole French
'hu' => 'magyar', # Hungarian
'hy' => 'Հայերեն', # Armenian
'roa-rup' => 'Armãneashce', # Aromanian (deprecated code, 'rup' exists in ISO 693-3)
'roa-tara' => 'tarandíne', # Tarantino
'ru' => 'русский', # Russian
- 'rue' => 'Русиньскый', # Rusyn
+ 'rue' => 'русиньскый', # Rusyn
'rup' => 'Armãneashce', # Aromanian
'ruq' => 'Vlăheşte', # Megleno-Romanian (multiple scripts - defaults to Latin)
'ruq-cyrl' => 'Влахесте', # Megleno-Romanian (Cyrillic script)
'se' => 'sámegiella', # Northern Sami
'sei' => 'Cmique Itom', # Seri
'sg' => 'Sängö', # Sango/Sangho
- 'sgs' => 'Žemaitėška', # Samogitian
+ 'sgs' => 'žemaitėška', # Samogitian
'sh' => 'srpskohrvatski / српскохрватски', # Serbocroatian
'shi' => 'Tašlḥiyt/ⵜⴰⵛⵍⵃⵉⵜ', # Tachelhit (multiple scripts - defaults to Latin)
'shi-tfng' => 'ⵜⴰⵛⵍⵃⵉⵜ', # Tachelhit (Tifinagh script)
'su' => 'Basa Sunda', # Sundanese
'sv' => 'svenska', # Swedish
'sw' => 'Kiswahili', # Swahili
- 'szl' => 'Å\9alůnski', # Silesian
+ 'szl' => 'Å\9blůnski', # Silesian
'ta' => 'தமிழ்', # Tamil
'tcy' => 'ತುಳು', # Tulu
'te' => 'తెలుగు', # Telugu
'qbbrowse' => 'ܡܦܐܬ',
'qbedit' => 'ܫܚܠܦ',
'qbpageoptions' => 'ܗܕܐ ܦܐܬܐ',
-'qbmyoptions' => 'Ü\95Ì\88ܦܐ ܕܝܠܝ',
+'qbmyoptions' => 'ܦÜ\90ܬܬÌ\88ܐ ܕܝܠܝ',
'qbspecialpages' => 'ܦܐܬܬ̈ܐ ܕ̈ܝܠܢܝܬܐ',
'faq' => 'ܫܘܐܠ̈ܐ ܬܢܝ̈ܐ',
'faqpage' => 'Project:ܫܘܐܠ̈ܐ ܬܢܝ̈ܐ',
'jumpto' => 'ܫܘܪ ܠ:',
'jumptonavigation' => 'ܐܠܦܪܘܬܐ',
'jumptosearch' => 'ܒܨܝܐ',
-'view-pool-error' => 'ܬÜ\98Ü\9dÜ\9aܐ، ܬܫܡܫܬ̈ܐ ܐܢܘܢ ܠܐ̈ܝܐ ܗܫܐܝܬ
-Ü£Ü\93Ü\9d Ü¡Ü¦Ü Ü\9aÜ¢Ì\88Ü\90 ܢܣÜ\9dÜ¢ Ü Ü\9aÜ\99Ü\9dÜ\90 Ü\95Ü\97Ü¢Ü\90 Ü\95ܦܐ
-ܦÜ\9dÜ£Ü\90 Ü¡Ü¢Ü\9f Ü£Ü\9fÜ\9d Ü©Ü Ü\9dÜ Ü¡Ü¢ Ü©Ü\95Ü¡ Ü\95ܬܢܣÜ\90 Ü Ü¡Ü\9bÜ\9dÜ\90 Ü Ü\97Ü¢Ü\90 Ü\95ܦܐ ܙܒܢܬܐ ܐܚܪܬܐ.
+'view-pool-error' => 'Ü«Ü\98Ü\92Ü©Ü¢ܐ، ܬܫܡܫܬ̈ܐ ܐܢܘܢ ܠܐ̈ܝܐ ܗܫܐܝܬ
+Ü£Ü\93Ü\9d Ü¡Ü¦Ü Ü\9aÜ¢Ì\88Ü\90 ܢܣÜ\9dÜ¢ Ü Ü\9aÜ\99Ü\9dÜ\90 Ü\95ܦÜ\90ܬÜ\90 Ü\97Ü\95ܐ
+ܦÜ\9dÜ£Ü\90 Ü¡Ü¢Ü\9f Ü£Ü\9fÜ\9d Ü©Ü Ü\9dÜ Ü¡Ü¢ Ü©Ü\95Ü¡ Ü\95ܬܢܣÜ\90 Ü Ü¡Ü\9bÜ\9dÜ\90 Ü Ü¦Ü\90ܬÜ\90 Ü\97Ü\95ܐ ܙܒܢܬܐ ܐܚܪܬܐ.
$1',
'pool-timeout' => 'ܫܠܡ ܥܕܢܐ ܣܒܪܬܐ ܠܚܠܩܐ',
'actionthrottled' => 'ܠܐ ܡܬܡܨܝܢܬܐ ܐܝܬܝܗܝ ܠܡܥܒܕ ܝܬܝܪ ܡܢ ܗܢܐ ܥܒܕܐ',
'viewsourcetext' => 'ܡܨܐ ܐܢܬ ܕܢܚܙܐ ܘܢܣܚܐ ܠܡܒܘ̈ܥܐ ܕܗܕܐ ܦܐܬܐ:',
'protectedinterface' => 'ܗܕܐ ܦܐܬܐ ܡܘܬܪܐ ܟܬܝܒܬܐ ܕܦܐܬܐ ܠܚܘܪܙܐ, ܘܐܝܬܝܗܝ ܢܛܪܬܐ ܠܡܘܢܥ ܚܘܒܠܐ.',
-'editinginterface' => "'''Ü\99Ü\98Ü\97ܪÜ\90:''' Ü\90ܢܬ Ü«Ü\9aÜ Ü¦Ü¬ Ü\95ܦÜ\90 Ü\95Ü¡Ü¬Ü¦Ü Ü\9a ܒܚܙܝܐ ܟܬܝܒܝܐ ܕܚܘܪܙܐ.
-Ü\9fÜ Ü«Ü\98Ü\9aÜ Ü¦Ü\90 Ü\92Ü\97Ü¢Ü\90 Ü\95ܦÜ\90 Ü¢Ü\97Ü\98Ü\90 Ü Ü\97 Ü¡Ü¥Ü\92Ü\95Ü¢Ü\98ܬÜ\90 Ü¥Ü Ü\90Ü£Ü\9fÜ¡Ü\90 Ü\95Ü\95ܦܐ ܕܡܦܠܚܢܐ ܕܡܦܠܚܢ̈ܐ ܐܚܪ̈ܢܐ.
+'editinginterface' => "'''Ü\99Ü\98Ü\97ܪÜ\90:''' Ü\90ܢܬ Ü«Ü\9aÜ Ü¦Ü¬ ܦÜ\90ܬÜ\90 Ü\95Ü¡Ü¬Ü¦Ü Ü\9aܬ ܒܚܙܝܐ ܟܬܝܒܝܐ ܕܚܘܪܙܐ.
+Ü\9fÜ Ü«Ü\98Ü\9aÜ Ü¦Ü\90 Ü\92ܦÜ\90ܬÜ\90 Ü\97Ü\95Ü\90 Ü¢Ü\97Ü\98Ü\90 Ü Ü\97 Ü¡Ü¥Ü\92Ü\95Ü¢Ü\98ܬÜ\90 Ü¥Ü Ü\90Ü£Ü\9fÜ¡Ü\90 Ü\95ܦÜ\90ܬܐ ܕܡܦܠܚܢܐ ܕܡܦܠܚܢ̈ܐ ܐܚܪ̈ܢܐ.
ܠܬܘܪ̈ܓܡܐ، ܐܦܠܚ ܬܪܡܝܬܐ ܕܬܘܪܓܡܐ ܕܡܝܕܝܐܘܝܩܝ [//translatewiki.net/wiki/Main_Page?setlang=ar translatewiki.net].",
'sqlhidden' => '(ܒܘܬܬܐ SQL ܛܫܝܐ)',
'namespaceprotected' => "ܠܝܬ ܠܟ ܦܣܣܐ ܠܫܚܠܦܬܐ ܕܦܐܬܬ̈ܐ ܒܚܩܠܐ ܕ'''$1'''.",
# Login and logout pages
'logouttext' => "'''ܗܫܐ ܦܠܛܠܟ ܡܢ ܚܘܫܒܢܟ.'''
-ܡܨÜ\90 Ü\90ܢܬ Ü\95Ü\90ܬÜ\9aÜ«Ü\9a {{SITENAME}} ܐܝܟ ܡܦܠܚܢܐ ܠܐ ܝܕܝܥܐ ܐܘ ܡܨܐ ܐܢܬ ܕ[[Special:UserLogin|ܬܥܘܠ]] ܒܚܘܫܒܢܐ ܥܝܢܗ ܐܘ ܐܝܟ ܡܦܠܚܢܐ ܐܚܪܢܐ.
+ܡܨÜ\90 Ü\90ܢܬ Ü\90Ü¦Ü Ü\9aܬ {{SITENAME}} ܐܝܟ ܡܦܠܚܢܐ ܠܐ ܝܕܝܥܐ ܐܘ ܡܨܐ ܐܢܬ ܕ[[Special:UserLogin|ܬܥܘܠ]] ܒܚܘܫܒܢܐ ܥܝܢܗ ܐܘ ܐܝܟ ܡܦܠܚܢܐ ܐܚܪܢܐ.
-Ü\9aÜ\95 Ü\9fÜ¡Ü\90 Ü¡Ü¢ Ü\95Ì\88ܦÜ\90 ܡܬÜ\9aÜ\99Ü\9dÜ¢ Ü\90Ü\9dÜ\9f Ü\95Ü\97Ü\98 Ü\90ܢܬ Ü¥Ü Ü\9dÜ Ü\90 Ü¥Ü\95Ü¡Ü\90 Ü\95Ü\90ܣܦܩܬ Ü Ü\95Ì\88ܦÜ\90 Ü Ü\92Ü\9dÜ\9f̈ܐ ܕܡܦܐܬܢܐ ܕܝܠܟ",
+Ü\9aÜ\95 Ü\9fÜ¡Ü\90 Ü¡Ü¢ ܦÜ\90ܬܬÌ\88Ü\90 ܡܬÜ\9aÜ\99Ü\9dÜ¢ Ü\90Ü\9dÜ\9f Ü\95Ü\97Ü\98 Ü\90ܢܬ Ü¥Ü Ü\9dÜ Ü\90 Ü¥Ü\95Ü¡Ü\90 Ü\95Ü\90ܣܦܩܬ Ü Ü¦Ü\90ܬܬÌ\88Ü\90 Ü Ü\92Ü\9dÜ\9fܬ̈ܐ ܕܡܦܐܬܢܐ ܕܝܠܟ",
'welcomecreation' => '== ܒܫܝܢܐ, $1! ==
ܐܬܒܪܝ ܚܘܫܒܢܟ.
ܠܐ ܢܫܐ ܐܢܬ ܠܫܚܠܦܬܐ ܕ[[Special:Preferences|ܨܒܝܢܝܘܬ̈ܐ ܒ {{SITENAME}}]].',
'subject' => 'ܡܠܘܐܐ/ܡܘܢܥܐ ܪܫܝܐ:',
'minoredit' => 'ܗܢܐ ܗܘ ܫܘܚܠܦܐ ܙܥܘܪܐ',
'watchthis' => 'ܪܗܝ ܦܐܬܐ ܗܕܐ',
-'savearticle' => 'Ü Ü\92Ü\98Ü\9f Ü\95ܦܐ',
+'savearticle' => 'Ü Ü\92Ü\98Ü\9f ܦÜ\90ܬܐ',
'preview' => 'ܚܝܪܐ ܩܕܡܝܐ',
'showpreview' => 'ܚܘܝ ܚܝܪܐ ܩܕܡܝܐ',
'showlivepreview' => 'ܚܝܪܐ ܩܕܡܝܐ ܚܝܐ',
'loginreqlink' => 'ܥܘܠ',
'accmailtitle' => 'ܡܠܬܐ ܕܥܠܠܐ ܫܕܪܬ',
'newarticle' => '(ܚܕܬܐ)',
-'newarticletext' => "Ü\90ܬܬ Ü\92ܬܪ Ü\90ܣܪÜ\90 Ü\95Ü\95ܦÜ\90 Ü\95Ü Ü\90 Ü\90ܬܬܣÜ\9dÜ¡ ܥܕܡܫ.
-Ü Ü£Ü\98Ü\9dÜ¡Ü\90 Ü\95Ü\95ܦÜ\90 Ü\97Ü¢Ü\90, ܫܪÜ\9d Ü Ü\9fܬÜ\92ܬÜ\90 Ü\92ܣܢÜ\95Ü\98Ü©Ü\90 Ü Ü¬Ü\9aܬ (Ü\9aÜ\99Ü\9d [[{{MediaWiki:Helppage}}|Ü\95ܦܐ ܕܥܘܕܪܢܐ]] ܠܐܚܪܢܐ ܝܕ̈ܥܬܐ).
+'newarticletext' => "Ü\90ܬܬ Ü\92ܬܪ Ü\90ܣܪÜ\90 Ü\95ܦÜ\90ܬÜ\90 Ü\95Ü Ü\90 Ü\90ܬܬܣÜ\9dܡܬ ܥܕܡܫ.
+Ü Ü£Ü\98Ü\9dÜ¡Ü\90 Ü\95ܦÜ\90ܬÜ\90 Ü\97Ü\95Ü\90, ܫܪÜ\9d Ü Ü\9fܬÜ\92ܬÜ\90 Ü\92ܣܢÜ\95Ü\98Ü©Ü\90 Ü Ü¬Ü\9aܬ (Ü\9aÜ\99Ü\9d [[{{MediaWiki:Helppage}}|ܦÜ\90ܬܐ ܕܥܘܕܪܢܐ]] ܠܐܚܪܢܐ ܝܕ̈ܥܬܐ).
ܐܢ ܐܬܬ ܠܗܪܟܐ ܦܘܕܐܝܬ, ܕܘܫ ܠܦܪܡܝܬܐ ܕ '''ܠܒܣܬܪ back''' ܒܡܦܐܬܢܐ ܕܝܠܟ.",
'updated' => '(ܐܬܚܕܬ)',
'note' => "'''ܡܥܝܪܢܘܬܐ:'''",
'editingcomment' => 'ܫܚܠܦܬܐ ܕ $1 (ܡܢܬܐ ܚܕܬܐ)',
'yourtext' => 'ܟܬܒܬܐ ܕܝܠܟ',
'storedversion' => 'ܬܢܝܬ̈ܐ ܐܣܝܢ̈ܐ',
-'editingold' => "'''Ü\99Ü\98Ü\97ܪÜ\90: Ü«Ü\9aÜ Ü¦ Ü\90ܢܬ ܬܢÜ\9dܬÜ\90 ܥܬÜ\9dܩܬÜ\90 Ü\95Ü\95ܦÜ\90 Ü\97Ü¢ܐ.'''
-Ü\90Ü¢ Ü Ü\92Ü\9f Ü\90ܢܬ Ü\95ܦÜ\90 Ü\97Ü¢ܐ, ܟܠ ܫܘ̈ܚܠܦܐ ܕܐܬܥܒܕܘ ܒܬܪ ܗܕܐ ܬܢܝܬܐ ܢܬܛܠܩܘܢ.",
+'editingold' => "'''Ü\99Ü\98Ü\97ܪÜ\90: Ü«Ü\9aÜ Ü¦ Ü\90ܢܬ ܬܢÜ\9dܬÜ\90 ܥܬÜ\9dܩܬÜ\90 Ü\95ܦÜ\90ܬÜ\90 Ü\97Ü\95ܐ.'''
+Ü\90Ü¢ Ü Ü\92Ü\9f Ü\90ܢܬ ܦÜ\90ܬÜ\90 Ü\97Ü\95ܐ, ܟܠ ܫܘ̈ܚܠܦܐ ܕܐܬܥܒܕܘ ܒܬܪ ܗܕܐ ܬܢܝܬܐ ܢܬܛܠܩܘܢ.",
'yourdiff' => 'ܦܪ̈ܝܫܘܝܬܐ',
'templatesused' => '{{PLURAL:$1|ܩܠܒܐ|ܩܠܒ̈ܐ}} ܒܦܐܬܐ ܗܕܐ:',
'template-protected' => '(ܢܛܝܪܐ)',
'history-title' => '"$1": ܬܫܥܝܬܐ ܕܬܢܝܬܐ',
'difference-title' => '«$1»: ܦܘܪܫܐ ܒܝܢܝ ܬܢܝܬ̈ܐ',
'difference-title-multipage' => '«$1» ܘ«$2»: ܦܘܪܫܐ ܒܝܢܝ ܬܢܝܬ̈ܐ',
-'difference-multipage' => '(ܦÜ\98ܪܫÜ\90 Ü\92Ü\9dÜ¢Ü\9d Ü\95Ì\88ܦܐ)',
+'difference-multipage' => '(ܦÜ\98ܪܫÜ\90 Ü\92Ü\9dÜ¢Ü\9d ܦÜ\90ܬܬÌ\88ܐ)',
'lineno' => 'ܣܪܛܐ $1:',
-'compareselectedversions' => 'ܦÜ\9aÜ\98Ü¡ Ü\92Ü\9dܬ ܬܪܝܢ ܬܢܝܬ̈ܐ ܓܒܝܬ̈ܐ',
+'compareselectedversions' => 'ܦÜ\9aÜ\98Ü¡ Ü\92Ü\9dÜ¢Ü\9d ܬܪܝܢ ܬܢܝܬ̈ܐ ܓܒܝܬ̈ܐ',
'showhideselectedversions' => 'ܚܘܝ/ܛܫܝ ܬܢܝܬ̈ܐ ܓܒܝܬ̈ܐ',
'editundo' => 'ܠܐ ܬܥܒܕ',
'diff-multi' => '({{PLURAL:$1|ܚܕܐ ܬܢܝܬܐ ܡܨܥܝܬܐ|$1 ܬܢܝܬ̈ܐ ܡܨܥܝܬ̈ܐ}} ܒܝܕ {{PLURAL:$2|ܚܕ ܡܦܠܚܢܐ ܠܐ ܓܠܝܚܬܐ|$2 ܡܦܠܚܢ̈ܐ ܠܐ ܓܠܝܚܬ̈ܐ}})',
'right-createaccount' => 'ܒܪܝ ܚܘܫܒܢ̈ܐ ܕܡܦܠܚܢܐ ܚܕܬܐ',
'right-minoredit' => 'ܫܘܕܥ ܥܠ ܫܘܚܠܦ̈ܐ ܐܝܟ ܙܥܘܪܐ',
'right-move' => 'ܫܢܝ ܦܐܬܬ̈ܐ',
-'right-move-subpages' => 'Ü«Ü¢Ü\9d Ü\95Ì\88ܦÜ\90 ܥܡ Ü\95Ì\88ܦܐ ܦܪ̈ܥܝܐ ܕܝܠܗܘܢ',
+'right-move-subpages' => 'Ü«Ü¢Ü\9d ܦÜ\90ܬܬÌ\88Ü\90 ܥܡ ܦÜ\90ܬܬÌ\88ܐ ܦܪ̈ܥܝܐ ܕܝܠܗܘܢ',
'right-movefile' => 'ܫܢܝ ܠܦܦ̈ܐ',
'right-upload' => 'ܐܣܩ ܠܦܦ̈ܐ',
'right-delete' => 'ܫܘܦ ܦܐܬܬ̈ܐ',
'recentchanges' => 'ܫܘܚܠܦ̈ܐ ܚܕ̈ܬܐ',
'recentchanges-legend' => 'ܓܒܝܬ̈ܐ ܕܫܘܚܠܦ̈ܐ ܚܕ̈ܬܐ',
'recentchanges-summary' => 'ܥܩܒ ܫܘܚܠܦ̈ܐ ܚܕܬ ܡܢ ܟܠ ܕܘܝܩܝ ܒܦܐܬܐ ܗܕܐ.',
-'recentchanges-label-newpage' => 'Ü«Ü\98Ü\9aÜ Ü¦Ü\90 Ü\97Ü¢Ü\90 Ü\90ܬܬܣÜ\9dÜ¡ Ü\95ܦܐ ܚܕܬܐ',
+'recentchanges-label-newpage' => 'Ü«Ü\98Ü\9aÜ Ü¦Ü\90 Ü\97Ü¢Ü\90 Ü\90ܬܬܣÜ\9dÜ¡ ܦÜ\90ܬܐ ܚܕܬܐ',
'recentchanges-label-minor' => 'ܗܢܘ ܫܘܚܠܦܐ ܙܥܘܪܐ',
'recentchanges-label-bot' => 'ܒܘܬ (bot) ܥܒܕ ܗܢܐ ܫܘܚܠܦܐ',
'rclistfrom' => 'ܚܘܝ ܫܘܚܠܦ̈ܐ ܚܕ̈ܬܐ ܡܢ $1',
'brokenredirects-delete' => 'ܫܘܦ',
'withoutinterwiki' => 'ܦܐܬܬ̈ܐ ܕܠܐ ܐܣܘܪ̈ܐ ܕܠܫܢ̈ܐ ܐܚܪ̈ܢܐ',
-'withoutinterwiki-summary' => 'Ü\95Ì\88ܦܐ ܗܠܝܢ ܠܐ ܡܛܝܢ ܠܨ̈ܚܚܐ ܕܠܫܢ̈ܐ ܐܚܪ̈ܢܐ.',
+'withoutinterwiki-summary' => 'ܦÜ\90ܬܬÌ\88ܐ ܗܠܝܢ ܠܐ ܡܛܝܢ ܠܨ̈ܚܚܐ ܕܠܫܢ̈ܐ ܐܚܪ̈ܢܐ.',
'withoutinterwiki-legend' => 'ܫܪܘܝܐ',
'withoutinterwiki-submit' => 'ܚܘܝ',
# Undelete
'undelete' => 'ܚܙܝ ܦܐܬܬ̈ܐ ܫܝܦܬ̈ܐ',
-'undeletepage' => 'Ü\9aÜ\99Ü\9d Ü\98Ü\90ܦܢÜ\9d Ü\95Ì\88ܦÜ\90 Ü«Ü\9dܦ̈ܐ',
+'undeletepage' => 'Ü\9aÜ\99Ü\9d Ü\98Ü\90ܦܢÜ\9d ܦÜ\90ܬܬÌ\88Ü\90 Ü«Ü\9dܦܬ̈ܐ',
'viewdeletedpage' => 'ܚܙܝ ܦܐܬܬ̈ܐ ܫܝܦܬ̈ܐ',
'undelete-fieldset-title' => 'ܐܦܢܝ ܬܢܝܬ̈ܐ',
'undelete-revision' => 'ܫܦ ܬܢܝܬܐ ܕ $1 (ܒܣܝܩܘܡ $4, ܒ $5) ܒܝܕ $3:',
'movepage-moved' => '\'\'\'"$1" ܐܫܬܢܝܬ ܠ "$2"\'\'\'',
'movepage-moved-redirect' => 'ܨܘܝܒܐ ܐܬܒܪܝ',
'movedto' => 'ܐܬܫܢܝ ܠ',
-'move-subpages' => 'Ü«Ü¢Ü\9d Ü\95Ì\88ܦÜ\90 ܦܪÌ\88Ü¥Ü\9dܐ (ܥܕܡܐ ܠ $1)',
-'move-talk-subpages' => 'Ü«Ü¢Ü\9d Ü\95Ì\88ܦÜ\90 ܦܪÌ\88Ü¥Ü\9dÜ\90 Ü\95Ü\95ܦܐ ܕܕܘܪܫܐ (ܥܕܡܐ ܠ $1)',
+'move-subpages' => 'Ü«Ü¢Ü\9d ܦÜ\90ܬܬÌ\88Ü\90 ܦܪÌ\88Ü¥Ü\9dܬÌ\88ܐ (ܥܕܡܐ ܠ $1)',
+'move-talk-subpages' => 'Ü«Ü¢Ü\9d ܦÜ\90ܬܬÌ\88Ü\90 ܦܪÌ\88Ü¥Ü\9dܬÌ\88Ü\90 Ü\95ܦÜ\90ܬܐ ܕܕܘܪܫܐ (ܥܕܡܐ ܠ $1)',
'movelogpage' => 'ܣܓܠܐ ܕܫܘܢܝܐ',
'movereason' => 'ܥܠܬܐ:',
'revertmove' => 'ܐܦܢܝ',
# Export
'export' => 'ܐܦܩ ܦܐܬܬ̈ܐ',
-'exportall' => 'Ü\90ܦܩ Ü\9fÜ Ü\95Ì\88ܦܐ',
+'exportall' => 'Ü\90ܦܩ Ü\9fÜ Ü¦Ü\90ܬܬÌ\88ܐ',
'export-submit' => 'ܐܦܩ',
'export-addcattext' => 'ܐܘܣܦ ܦܐܬܬ̈ܐ ܡܢ ܣܕܪܐ:',
'export-addcat' => 'ܐܘܣܦ',
'table_pager_prev' => 'ܦܐܬܐ ܩܕܝܡܬܐ',
'table_pager_first' => 'ܦܐܬܐ ܩܕܡܝܬܐ',
'table_pager_last' => 'ܦܐܬܐ ܐܚܪܝܬܐ',
-'table_pager_limit_label' => 'ܡܕܡ ܠܟܠ ܕܦܐ:',
+'table_pager_limit_label' => 'ܡܕ̈ܡܐ ܠܟܠ ܦܐܬܐ:',
'table_pager_limit_submit' => 'ܙܠ',
'table_pager_empty' => 'ܠܝܬ ܦܠܛ̈ܐ',
'tags-hitcount' => '$1 {{PLURAL:$1|ܫܘܚܠܦܐ|ܫܘܚܠܦ̈ܐ}}',
# Special:ComparePages
-'compare-page1' => 'Ü\95ܦܐ 1',
-'compare-page2' => 'Ü\95ܦܐ 2',
+'compare-page1' => 'ܦÜ\90ܬܐ 1',
+'compare-page2' => 'ܦÜ\90ܬܐ 2',
'compare-rev1' => 'ܬܢܝܬܐ 1',
'compare-rev2' => 'ܬܢܝܬܐ 2',
'compare-submit' => 'ܦܚܘܡ',
'htmlform-selectorother-other' => 'ܐܚܪܢܐ',
# New logging system
-'logentry-delete-delete' => '$1 ܫܦ Ü\95ܦܐ ܕ $3',
-'logentry-move-move' => '$1 Ü«Ü¢Ü\90 Ü\95ܦܐ ܕ $3 ܠ $4',
-'logentry-move-move-noredirect' => '$1 Ü«Ü¢Ü\90 Ü\95ܦÜ\90 Ü\95 $3 Ü $4 Ü\95Ü Ü\90 Ü«Ü\92Ü©Ü\90 Ü\95Ü\95ܦܐ ܕܨܘܝܒܐ',
-'logentry-move-move_redir' => '$1 Ü«Ü¢Ü\90 Ü\95ܦÜ\90 Ü\95 $3 Ü $4 Ü\95Ü\90Ü\9dܬÜ\98Ü\97Ü\9d Ü\97Ü\98Ü\90 Ü\95ܦܐ ܕܨܘܝܒܐ',
-'logentry-move-move_redir-noredirect' => '$1 Ü«Ü¢Ü\90 Ü\95ܦÜ\90 Ü\95 $3 Ü $4 Ü\95Ü\90Ü\9dܬÜ\98Ü\97Ü\9d Ü\97Ü\98Ü\90 Ü\95ܦÜ\90 Ü\95ܨÜ\98Ü\9dÜ\92Ü\90 Ü\98Ü\95Ü Ü\90 Ü«Ü\92Ü©Ü\90 Ü\95Ü\95ܦܐ ܕܨܘܝܒܐ',
+'logentry-delete-delete' => '$1 ܫܦ ܦÜ\90ܬܐ ܕ $3',
+'logentry-move-move' => '$1 Ü«Ü¢Ü\90 ܦÜ\90ܬܐ ܕ $3 ܠ $4',
+'logentry-move-move-noredirect' => '$1 Ü«Ü¢Ü\90 ܦÜ\90ܬÜ\90 Ü\95 $3 Ü $4 Ü\95Ü Ü\90 Ü«Ü\92Ü©Ü\90 ܦÜ\90ܬܐ ܕܨܘܝܒܐ',
+'logentry-move-move_redir' => '$1 Ü«Ü¢Ü\90 ܦÜ\90ܬÜ\90 Ü\95 $3 Ü $4 Ü\95Ü\90Ü\9dܬÜ\98Ü\97Ü\9d ܦÜ\90ܬܐ ܕܨܘܝܒܐ',
+'logentry-move-move_redir-noredirect' => '$1 Ü«Ü¢Ü\90 ܦÜ\90ܬÜ\90 Ü\95 $3 Ü $4 Ü\95Ü\90Ü\9dܬÜ\98Ü\97Ü\9d ܦÜ\90ܬÜ\90 Ü\95ܨÜ\98Ü\9dÜ\92Ü\90 Ü\98Ü\95Ü Ü\90 Ü«Ü\92Ü©Ü\90 ܦÜ\90ܬܐ ܕܨܘܝܒܐ',
'logentry-newusers-newusers' => '$1 ܒܪܐ ܚܘܫܒܢܐ ܕܡܦܠܚܢܐ',
'logentry-newusers-create' => '$1 ܒܪܐ ܚܘܫܒܢܐ ܕܡܦܠܚܢܐ',
'logentry-newusers-create2' => '$1 ܒܪܐ ܚܘܫܒܢܐ ܕܡܦܠܚܢܐ $3',
'tog-watchlisthidepatrolled' => 'Kontrollierte Änderungen in der Beobachtungsliste ausblenden',
'tog-nolangconversion' => 'Konvertierung von Sprachvarianten deaktivieren',
'tog-ccmeonemails' => 'Schicke mir Kopien der E-Mails, die ich anderen Benutzern sende',
-'tog-diffonly' => 'Zeige beim Versionsvergleich nur die Unterschiede und nicht die vollständige Seite',
+'tog-diffonly' => 'Beim Versionsvergleich nur die Unterschiede und nicht die vollständige Seite anzeigen',
'tog-showhiddencats' => 'Anzeige versteckter Kategorien',
'tog-noconvertlink' => 'Konvertierung des Titels deaktivieren',
'tog-norollbackdiff' => 'Unterschied nach dem Zurücksetzen unterdrücken',
'nse' => array( 0, 'NSE:' ),
'localurl' => array( 0, 'LOCALURL:' ),
'localurle' => array( 0, 'LOCALURLE:' ),
- 'articlepath' => array( 0, 'ARTICLEPATH' ),
+ 'articlepath' => array( 0, 'ARTICLEPATH' ),
+ 'pageid' => array( 0, 'PAGEID' ),
'server' => array( 0, 'SERVER' ),
'servername' => array( 0, 'SERVERNAME' ),
'scriptpath' => array( 0, 'SCRIPTPATH' ),
'tog-hidepatrolled' => 'Hide patrolled edits in recent changes',
'tog-newpageshidepatrolled' => 'Hide patrolled pages from new page list',
'tog-extendwatchlist' => 'Expand watchlist to show all changes, not just the most recent',
-'tog-usenewrc' => 'Use enhanced recent changes (requires JavaScript)',
+'tog-usenewrc' => 'Group changes by page in recent changes and watchlist (requires JavaScript)',
'tog-numberheadings' => 'Auto-number headings',
'tog-showtoolbar' => 'Show edit toolbar (requires JavaScript)',
'tog-editondblclick' => 'Edit pages on double click (requires JavaScript)',
'parser-template-loop-warning' => 'حلقه در الگو پیدا شد: [[$1]]',
'parser-template-recursion-depth-warning' => 'محدودیت عمق بازگشت الگو رد شد ($1)',
'language-converter-depth-warning' => 'محدودیت عمق مبدل زبانی رد شد ($1)',
+'node-count-exceeded-category' => 'صفحههایی که از حداکثر تعداد گره تجاوز کردهاند',
+'node-count-exceeded-warning' => 'صفحه از حداکثر تعداد گره تجاوز کرد',
+'expansion-depth-exceeded-category' => 'صفحههایی که از حداکثر عمق بسط دادن تجاوز کردهاند',
+'expansion-depth-exceeded-warning' => 'صفحه حداکثر عمق بسط دادن تجاوز کرد',
+'parser-unstrip-loop-warning' => 'حلقه در دستور unstrip پیدا شد',
+'parser-unstrip-recursion-limit' => 'از حداکثر ارجاع در دستور unstrip تجاوز شد ($1)',
# "Undo" feature
'undo-success' => 'این ویرایش را میتوان خنثی کرد.
میتوانید با انتخاب نوع سیاهه، نام کاربری (حساس به کوچکی و بزرگی حروف) و صفحههای تغییریافته (حساس به بزرگی و کوچکی حروف)، نمایش را محدودتر سازید.',
'logempty' => 'مورد منطبق با منظور شما در سیاهه یافت نشد.',
'log-title-wildcard' => 'صفحههایی را جستجو کن که عنوانشان با این عبارت آغاز میشود',
+'showhideselectedlogentries' => 'نمایش/نهفتن موارد انتخابی در سیاهه',
# Special:AllPages
'allpages' => 'همهٔ صفحهها',
'spambot_username' => 'هرزهتمیزکارِ مدیاویکی',
'spam_reverting' => 'واگردانی به آخرین نسخهای که پیوندی به $1 ندارد.',
'spam_blanking' => 'تمام نسخهها حاوی پیوند به $1 بود، در حال خالی کردن',
+'spam_deleting' => 'تمام نسخهها حاوی پیوند به $1 بود، در حال حذف',
# Info page
'pageinfo-title' => 'اطلاعات در مورد «$1»',
'duration-centuries' => '$1 قرن',
'duration-millennia' => '{{PLURAL:$1|هزار سال |$1 هزار سال}}',
+# Unknown messages
+'lockmanager-fail-svr-acquire' => 'امکان گرفتن قفلهای سرور $1 وجود ندارد.',
);
'lockmanager-fail-acquirelock' => 'לא הייתה אפשרות לקבל נעילה עבור "$1".',
'lockmanager-fail-openlock' => 'לא הייתה אפשרות לפתוח את קובץ הנעילה עבור "$1".',
'lockmanager-fail-releaselock' => 'לא הייתה אפשרות לשחרר את הנעילה עבור "$1".',
-'lockmanager-fail-db-bucket' => 'לא הייתה אפשרות לקבל מספיק מסדי נתונים של נעילות בדלי $1',
-'lockmanager-fail-db-release' => 'לא הייתה אפשרות לשחרר נעילות על מסד הנתונים $1',
-'lockmanager-fail-svr-release' => 'לא הייתה אפשרות לשחרר נעילות על השרת $1',
+'lockmanager-fail-db-bucket' => 'לא הייתה אפשרות לקבל מספיק מסדי נתונים של נעילות בדלי $1.',
+'lockmanager-fail-db-release' => 'לא הייתה אפשרות לשחרר נעילות על מסד הנתונים $1.',
+'lockmanager-fail-svr-release' => 'לא הייתה אפשרות לשחרר נעילות על השרת $1.',
# ZipDirectoryReader
'zip-file-open-error' => 'אירעה שגיאה במהלך פתיחת הקובץ לבדיקות ZIP.',
'duration-centuries' => '{{PLURAL:$1|מאה שנה|$1 מאות שנים|מאתיים שנה}}',
'duration-millennia' => '{{PLURAL:$1|אלף שנה|$1 אלפי שנים|אלפיים שנה}}',
+# Unknown messages
+'lockmanager-fail-svr-acquire' => 'לא הייתה אפשרות לבצע נעילות על השרת $1.',
);
'badtitle' => '잘못된 제목',
'badtitletext' => '문서 제목이 잘못되었거나 비어있습니다.',
'perfcached' => '다음 자료는 캐시된 것이므로 현재 상황을 반영하지 않을 수 있습니다. 캐시에 최대 {{PLURAL:$1|결과 $1개}}가 있습니다.',
-'perfcachedts' => '다음 자료는 캐시된 것으로, $1에 마지막으로 갱신되었습니다. 캐시에 최대 {{PLURAL:$4|$4개의 결과}}가 있습니다.',
+'perfcachedts' => '다음 자료는 캐시된 것으로, $1에 마지막으로 갱신되었습니다. 캐시에 최대 {{PLURAL:$4|결과 $4개}}가 있습니다.',
'querypage-no-updates' => '이 문서의 갱신이 현재 비활성화되어 있습니다. 자료가 잠시 갱신되지 않을 것입니다.',
'wrong_wfQuery_params' => 'wfQuery()에서 잘못된 매개변수 발생<br />함수: $1<br />쿼리: $2',
'viewsource' => '내용 보기',
'userlogout' => '로그아웃',
'notloggedin' => '로그인하지 않음',
'nologin' => '계정이 없나요? $1.',
-'nologinlink' => '계정을 만들 수 있습니다',
+'nologinlink' => '계정 만들기',
'createaccount' => '계정 만들기',
'gotaccount' => "계정이 이미 있다면, '''$1'''.",
-'gotaccountlink' => '로그인하세요',
+'gotaccountlink' => '로그인',
'userlogin-resetlink' => '계정 이름이나 비밀번호를 잊으셨나요?',
'createaccountmail' => '이메일로 보내기',
'createaccountreason' => '이유:',
'mergehistory-go' => '합칠 수 있는 편집 보기',
'mergehistory-submit' => '문서 역사 합치기',
'mergehistory-empty' => '합칠 수 있는 판이 없습니다.',
-'mergehistory-success' => '[[:$1]] 문서의 $3개의 판이 [[:$2]]에 성공적으로 합쳐졌습니다.',
+'mergehistory-success' => '[[:$1]] 문서의 판 $3개가 [[:$2]]에 성공적으로 합쳐졌습니다.',
'mergehistory-fail' => '문서 역사 합치기 명령을 수행할 수 없습니다. 문서와 시간 변수를 다시 확인하십시오.',
'mergehistory-no-source' => '원본인 $1 문서가 존재하지 않습니다.',
'mergehistory-no-destination' => '대상인 $1 문서가 존재하지 않습니다.',
'backend-fail-connect' => "'$1' 저장 백엔드에 접속하지 못했습니다.",
'backend-fail-internal' => '"$1" 저장 백엔드에 알 수 없는 오류가 발생했습니다.',
'backend-fail-contenttype' => '"$1"에 저장하기 위한 파일의 내용 유형을 판별하지 못했습니다.',
-'backend-fail-batchsize' => '저장 백엔드에서 $1개의 파일 {{PLURAL:$1|작업}}이 쌓여 있습니다; 한계는 $2개입니다.',
+'backend-fail-batchsize' => '저장 백엔드에서 파일 {{PLURAL:$1|작업}} $1개가 쌓여 있습니다; 한계는 $2개입니다.',
'backend-fail-usable' => '파일 저장 권한이 없거나 저장 위치가 빠졌기 때문에 $1 파일을 저장할 수 없습니다.',
# File journal errors
'filehist-comment' => '내용',
'filehist-missing' => '파일을 찾을 수 없음',
'imagelinks' => '이 파일을 사용하는 문서',
-'linkstoimage' => '다음 $1ê°\9cì\9d\98 문ì\84\9cê°\80 ì\9d´ í\8c\8cì\9d¼ì\9d\84 ì\82¬ì\9a©í\95\98ê³ ì\9e\88ì\8aµë\8b\88ë\8b¤:',
+'linkstoimage' => '다음 문ì\84\9c $1ê°\9cê°\80 ì\9d´ í\8c\8cì\9d¼ì\9d\84 ì\82¬ì\9a©í\95\98ê³ ì\9e\88ì\8aµë\8b\88ë\8b¤:',
'linkstoimage-more' => '$1개 이상의 문서가 이 파일을 가리키고 있습니다.
다음 목록은 이 파일을 가리키는 처음 $1개 문서만 보여주고 있습니다.
이 파일을 가리키는 모든 문서를 보려면 [[Special:WhatLinksHere/$2|여기]]를 참고해 주십시오.',
'exblank' => '빈 문서',
'delete-confirm' => '‘$1’ 삭제',
'delete-legend' => '삭제',
-'historywarning' => "'''주의''': 삭제하려는 문서에 약 $1개의 과거 편집 내역이 있습니다:",
+'historywarning' => "'''주의:''' 삭제하려는 문서에 과거 편집 내역 약 $1개가 있습니다:",
'confirmdeletetext' => '문서와 문서 역사를 삭제하려고 합니다. 삭제하려는 문서가 맞는지, 이 문서를 삭제하는 것이 [[{{MediaWiki:Policy-url}}|정책]]에 맞는 행동인지를 확인해 주세요.',
'actioncomplete' => '명령 완료',
'actionfailed' => '명령 실패',
'movelogpage' => '이동 기록',
'movelogpagetext' => '아래는 옮겨진 문서의 목록입니다.',
'movesubpage' => '{{PLURAL:$1}}하위 문서',
-'movesubpagetext' => '이 문서에는 다음 $1ê°\9cì\9d\98 í\95\98ì\9c\84 문ì\84\9cê°\80 ì\9e\88ì\8aµë\8b\88ë\8b¤.',
+'movesubpagetext' => '이 문서에는 다음 í\95\98ì\9c\84 문ì\84\9c $1ê°\9cê°\80 ì\9e\88ì\8aµë\8b\88ë\8b¤.',
'movenosubpage' => '이 문서에는 하위 문서가 존재하지 않습니다.',
'movereason' => '이유:',
'revertmove' => '되돌리기',
'spamprotectionmatch' => '문제가 되는 부분은 다음과 같습니다: $1',
'spambot_username' => 'MediaWiki 스팸 제거',
'spam_reverting' => '$1을 포함하지 않는 최신 버전으로 되돌림',
-'spam_blanking' => '모든 버전에 $1 링크를 포함하고 있어 문서를 비움',
-'spam_deleting' => '모든 버전에 $1 링크를 포함하고 있어 문서를 삭제함',
+'spam_blanking' => '모든 버전에 $1 링크를 포함하고 있어 차단함',
+'spam_deleting' => '모든 버전에 $1 링크를 포함하고 있어 삭제함',
# Info page
'pageinfo-title' => '"$1" 문서에 대한 정보',
# New logging system
'logentry-delete-delete' => '$1 사용자가 $3 문서를 삭제하였습니다.',
'logentry-delete-restore' => '$1 사용자가 $3 문서를 복구하였습니다.',
-'logentry-delete-event' => '$1 사용자가 $3의 $5개의 기록에 대해 표시 설정을 바꾸었습니다: $4',
+'logentry-delete-event' => '$1 사용자가 $3의 기록 $5개에 대해 표시 설정을 바꾸었습니다: $4',
'logentry-delete-revision' => '$1 사용자가 $3 문서의 {{PLURAL:$5|$5}}개 편집의 설정을 변경하였습니다: $4',
'logentry-delete-event-legacy' => '$1 사용자가 $3 문서 기록의 표시 설정을 변경하였습니다.',
'logentry-delete-revision-legacy' => '$1 사용자가 $3 문서 편집의 표시 설정을 변경하였습니다.',
'confirmemail_noemail' => '[[Special:Preferences|താങ്കളുടെ ക്രമീകരണങ്ങളുടെ കൂടെ]] സാധുവായൊരു ഇ-മെയിൽ വിലാസം സജ്ജീകരിച്ചിട്ടില്ല.',
'confirmemail_text' => '{{SITENAME}} സംരംഭത്തിൽ ഇ-മെയിൽ സൗകര്യം ഉപയോഗിക്കണമെങ്കിൽ താങ്കൾ താങ്കളുടെ ഇ-മെയിൽ വിലാസത്തിന്റെ സാധുത തെളിയിച്ചിരിക്കണം. താങ്കളുടെ ഇ-മെയിൽ വിലാസത്തിലേക്ക് സ്ഥിരീകരണ മെയിൽ അയക്കുവാൻ താഴെയുള്ള ബട്ടൺ അമർത്തുക. താങ്കൾക്ക് അയക്കുന്ന ഇ-മെയിലിൽ ഒരു സ്ഥിരീകരണ കോഡ് ഉണ്ട്. ആ കോഡിൽ അമർത്തിയാൽ താങ്കളുടെ വിലാസത്തിന്റെ സാധുത തെളിയിക്കപ്പെടും.',
'confirmemail_pending' => 'താങ്കളുടെ അംഗത്വം ഈ അടുത്ത് ഉണ്ടാക്കിയതാണെങ്കിൽ, ഒരു സ്ഥിരീകരണ കോഡ് താങ്കൾക്ക് ഇ-മെയിൽ ചെയ്തിട്ടുണ്ട്. പുതിയ സ്ഥിരീകരണ കോഡ് ആവശ്യപ്പെടാൻ ശ്രമിക്കുന്നതിനു മുൻപ് ആദ്യത്തെ സ്ഥിരീകരണ കോഡിനായി കുറച്ച് സമയം കാത്തിരിക്കൂ.',
-'confirmemail_send' => 'സ്ഥിരീകരണ കോഡ് (confirmation code) മെയിൽ ചെയ്യുക',
+'confirmemail_send' => 'സ്ഥിരീകരണ കോഡ് ഇമെയിലിൽ അയയ്ക്കുക',
'confirmemail_sent' => 'സ്ഥിരീകരണ ഇ-മെയിൽ അയച്ചിരിക്കുന്നു.',
'confirmemail_oncreate' => 'ഒരു സ്ഥിരീകരണ കോഡ് താങ്കളുടെ ഇ-മെയിൽ വിലാസത്തിലേക്ക് അയച്ചിട്ടുണ്ട്.
ലോഗിൻ ചെയ്യുന്നതിനു ഈ കോഡ് ആവശ്യമില്ല. പക്ഷെ വിക്കിയിൽ ഇ-മെയിലുമായി ബന്ധപ്പെട്ട സേവനങ്ങൾ ഉപയോഗിക്കുന്നതിനു മുൻപ് പ്രസ്തുത കോഡ് ഉപയോഗിച്ച് ഇ-മെയിൽ സ്ഥിരീകരിച്ചിരിക്കണം.',
'duration-centuries' => '$1 abad',
'duration-millennia' => '$1 alaf',
+# Unknown messages
+'lockmanager-fail-svr-acquire' => 'Selak-selak tidak dapat diperoleh di pelayan $1.',
);
'logentry-move-move-noredirect' => '$1 flytte sida $3 til $4 utan å lata etter ei omdirigering',
'logentry-move-move_redir' => '$1 flytte sida $3 til $4 over ei omdirigering',
'logentry-move-move_redir-noredirect' => '$1 flytte sida $3 til $4 over ei omdirigering utan å lata etter ei omdirigering',
+'logentry-patrol-patrol' => '$1 merkte versjon $4 av sida $3 som patruljert',
+'logentry-patrol-patrol-auto' => '$1 merkte automatisk versjon $4 av sida $3 som patruljert',
'logentry-newusers-newusers' => '$1 oppretta ein brukarkonto',
'logentry-newusers-create' => '$1 oppretta ein brukarkonto',
'logentry-newusers-create2' => '$1 oppretta brukarkontoen $3',
Date dle revision e stranòm dj'editor a resteran piàjit sù 'cò lor.
Tute j'amportassion antra wiki diferente a resto marcà ant ël [[Special:Log/import|Registr dj'amportassion]].",
'import-interwiki-source' => 'Wiki e pàgina sorgiss:',
-'import-interwiki-history' => 'Còpia tute le version stòriche dë sta pàgina-sì',
-'import-interwiki-templates' => 'Ansëriss tùit jë stamp',
+'import-interwiki-history' => 'Copié tute le revision ëd la stòria ëd costa pàgina',
+'import-interwiki-templates' => 'Anserì tùit jë stamp',
'import-interwiki-submit' => 'Amporté',
'import-interwiki-namespace' => 'Spassi nominal ëd destinassion:',
-'import-upload-filename' => 'Nòm dël file:',
+'import-upload-filename' => "Nòm ëd l'archivi:",
'import-comment' => 'Oget:',
'importtext' => "Për piasì, che as espòrta l'archivi da 'nt la sorgiss wiki ën dovrand l'[[Special:Export|utiss d'esportassion]].
Che as lo salva ansima a sò ordinator e peui che a lo caria ambelessì.",
'tog-hidepatrolled' => 'Option in Recent changes tab of [[Special:Preferences]] (if [[mw:Manual:$wgUseRCPatrol|$wgUseRCPatrol]] is enabled). {{Gender}}',
'tog-newpageshidepatrolled' => 'Toggle in [[Special:Preferences]], section "Recent changes" (if [[mw:Manual:$wgUseRCPatrol|$wgUseRCPatrol]] is enabled). {{Gender}}',
'tog-extendwatchlist' => "[[Special:Preferences]], tab 'Watchlist'. Offers user to show all applicable changes in watchlist (by default only the last change to a page on the watchlist is shown). {{Gender}}",
-'tog-usenewrc' => "[[Special:Preferences]], tab 'Recent changes'. Offers user to use alternative reprsentation of [[Special:RecentChanges]]. {{Gender}}",
+'tog-usenewrc' => "[[Special:Preferences]], tab 'Recent changes'. Offers user to use alternative reprsentation of [[Special:RecentChanges]] and watchlist. {{Gender}}",
'tog-numberheadings' => "[[Special:Preferences]], tab 'Misc'. Offers numbered headings on content pages to user. {{Gender}}",
'tog-showtoolbar' => "[[Special:Preferences]], tab 'Edit'. Offers user to show edit toolbar in page edit screen. {{Gender}}
Vă rugăm să alegeți un alt nume.',
'loginerror' => 'Eroare de autentificare',
'createaccounterror' => 'Nu pot crea contul: $1',
-'nocookiesnew' => 'Contul a fost creat, dar dvs. nu sunteți autentificat(ă). {{SITENAME}} folosește cookie-uri pentru a reține utilizatorii autentificați. Browser-ul dvs. are modulele cookie dezactivate (disabled). Vă rugăm să le activați și să vă reautentificați folosind noul nume de utilizator și noua parolă.',
+'nocookiesnew' => 'Contul a fost creat, dar nu sunteți autentificat{{GENDER:||ă|}}. {{SITENAME}} folosește module cookie pentru a reține utilizatorii autentificați. Navigatorul dumneavoastră are aceste module cookie dezactivate. Vă rugăm să le activați și să vă reautentificați folosind noul nume de utilizator și noua parolă.',
'nocookieslogin' => '{{SITENAME}} folosește module cookie pentru a autentifica utilizatorii. Browser-ul dvs. are cookie-urile dezactivate. Vă rugăm să le activați și să incercați din nou.',
'nocookiesfornew' => 'Contul de utilizator nu a fost creat, deoarece nu am putut confirma sursa.
Asigurați-vă că aveți cookie-urile activate, reîncărcați pagina și încercați din nou.',
'vector-view-view' => 'చదువు',
'vector-view-viewsource' => 'మూలాన్ని చూడండి',
'actions' => 'పనులు',
-'namespaces' => 'à°¨à±\87à°\82à°¸à±\8dà°ªà±\87à°¸ులు',
+'namespaces' => 'à°ªà±\87à°°à±\81బరులు',
'variants' => 'రకరకాలు',
'errorpagetitle' => 'పొరపాటు',
'returnto' => 'తిరిగి $1కి.',
'tagline' => '{{SITENAME}} నుండి',
-'help' => 'సాయమà±\81',
+'help' => 'సహాయà°\82',
'search' => 'వెతుకు',
'searchbutton' => 'వెతుకు',
'go' => 'వెళ్లు',
'searcharticle' => 'వెళ్లు',
'history' => 'పేజీ చరిత్ర',
-'history_short' => 'à°®à±\86లన',
+'history_short' => 'à°\9aà°°à°¿à°¤à±\8dà°°',
'updatedmarker' => 'నేను కిందటిసారి వచ్చిన తరువాత జరిగిన మార్పులు',
'printableversion' => 'అచ్చుతీయదగ్గ కూర్పు',
'permalink' => 'శాశ్వత లంకె',
'mainpage' => 'మొదటి పేజీ',
'mainpage-description' => 'తలపుట',
'policy-url' => 'Project:విధానం',
-'portal' => 'à°®à°\82ది పందిరి',
+'portal' => 'సమà±\81దాయ పందిరి',
'portal-url' => 'Project:సముదాయ పందిరి',
'privacy' => 'గోప్యతా విధానం',
-'privacypage' => 'Project:మరà±\81à°\97à±\81 à°¤à±\80à°°à±\81',
+'privacypage' => 'Project:à°\97à±\8bà°ªà±\8dయతా విధానà°\82',
'badaccess' => 'అనుమతి లోపం',
'badaccess-group0' => 'మీరు చేయతలపెట్టిన పనికి మీకు హక్కులు లేవు.',
'actionthrottledtext' => 'స్పామును తగ్గించటానికి తీసుకున్న నిర్ణయాల వల్ల, మీరు ఈ కార్యాన్ని అతి తక్కువ సమయంలో బోలెడన్ని సార్లు చేయకుండా అడ్డుకుంటున్నాము. కొన్ని నిమిషాలు ఆగి మరలా ప్రయత్నించండి.',
'protectedpagetext' => 'ఈ పేజీని మార్చకుండా ఉండేందుకు సంరక్షించారు.',
'viewsourcetext' => 'మీరీ పేజీ సోర్సును చూడవచ్చు, కాపీ చేసుకోవచ్చు:',
-'viewyourtext' => 'ఈ పుటకు "మీ మార్పుల"యొక్క వేరును మీరు చూడవచ్చు లేదా అచ్చుదింపవచ్చు:',
+'viewyourtext' => "ఈ పేజీకి '''మీ మార్పుల''' యొక్క మూలాన్ని చూడవచ్చు లేదా కాపీచేసుకోవచ్చు:",
'protectedinterface' => 'సాఫ్టువేరు ఇంటరుఫేసుకు చెందిన టెక్స్టును ఈ పేజీ అందిస్తుంది. దుశ్చర్యల నివారణ కోసమై దీన్ని లాకు చేసాం.',
'editinginterface' => "'''హెచ్చరిక''': సాఫ్టువేరుకు ఇంటరుఫేసు టెక్స్టును అందించే పేజీని మీరు సరిదిద్దుతున్నారు.
ఈ పేజీలో చేసే మార్పుల వల్ల ఇతర వాడుకరులకు ఇంటరుఫేసు కనబడే విధానంలో తేడావస్తుంది.
'securelogin-stick-https' => 'ప్రవేశం తర్వాత కూడా HTTPSకి అనుసంధానమై ఉండు',
'yourdomainname' => 'మీ డోమైను',
'externaldberror' => 'డేటాబేసు అధీకరణలో పొరపాటు జరిగింది లేదా మీ బయటి ఖాతాని తాజాకరించడానికి మీకు అనుమతి లేదు.',
-'login' => 'à°ªà±\8dà°°à°µà±\87శిà°\82à°\9aండి',
-'nav-login-createaccount' => 'à°²à±\8bనిà°\95à°¿ à°°à°\82à°¡à°¿/ పదà±\8dà°¦à±\81à°¨à±\81 à°\95à°²à±\8dà°ªించుకోండి',
+'login' => 'à°²à±\8bనిà°\95à°¿ à°°ండి',
+'nav-login-createaccount' => 'à°²à±\8bనిà°\95à°¿ à°ªà±\8dà°°à°µà±\87శిà°\82à°\9aà°\82à°¡à°¿ / à°\96ాతాని à°¸à±\83à°·à±\8dà°\9fించుకోండి',
'loginprompt' => '{{SITENAME}}లోకి ప్రవేశించాలంటే మీ విహారిణిలో కూకీలు చేతనమై ఉండాలి.',
'userlogin' => 'ప్రవేశించండి / ఖాతాను సృష్టించుకోండి',
'userloginnocreate' => 'ప్రవేశించండి',
'nosuchsectiontext' => 'మీరు లేని విభాగాన్ని మార్చడానికి ప్రయత్నించారు.
మీరు పేజీని చూస్తూన్నప్పుడు దాన్ని ఎవరైనా తరలించి లేదా తొలగించి ఉండవచ్చు.',
'loginreqtitle' => 'ప్రవేశము తప్పనిసరి',
-'loginreqlink' => 'à°ªà±\8dà°°à°µà±\87శిà°\82à°\9aి',
+'loginreqlink' => 'à°²à±\8bనిà°\95à°¿ à°°à°\82à°¡ి',
'loginreqpagetext' => 'ఇతర పుటలను చూడడానికి మీరు $1 ఉండాలి.',
'accmailtitle' => 'సంకేతపదం పంపించబడింది.',
'accmailtext' => "[[User talk:$1|$1]] కొరకు ఒక యాదృచ్చిక సంకేతపదాన్ని $2కి పంపించాం.
# Recent changes linked
'recentchangeslinked' => 'సంబంధిత మార్పులు',
'recentchangeslinked-feed' => 'సంబంధిత మార్పులు',
-'recentchangeslinked-toolbox' => 'à°ªà±\8aà°¤à±\8dà°¤à±\81గల మార్పులు',
+'recentchangeslinked-toolbox' => 'à°ªà±\8aà°\82తనగల మార్పులు',
'recentchangeslinked-title' => '$1 కు సంబంధించిన మార్పులు',
'recentchangeslinked-noresult' => 'మీరిచ్చిన కాలంలో ఇక్కడికి లింకు ఉన్న పేజీలలో ఎటువంటు మార్పులూ జరగలేదు.',
'recentchangeslinked-summary' => "దీనికి లింకై ఉన్న పేజీల్లో జరిగిన చివరి మార్పులు ఇక్కడ చూడవచ్చు. మీ వీక్షణ జాబితాలో ఉన్న పేజీలు '''బొద్దు'''గా ఉంటాయి.",
'recentchangeslinked-to' => 'ఇచ్చిన పేజీకి లింకయివున్న పేజీలలో జరిగిన మార్పులను చూపించు',
# Upload
-'upload' => 'దసà±\8dà°¤à±\8dà°°à°®à±\81 à°\8eà°\95à±\8dà°\95à°¿à°\82à°\9aు',
+'upload' => 'దసà±\8dà°¤à±\8dà°°à°ªà±\81 à°\8eà°\95à±\8dà°\95à°¿à°\82à°ªు',
'uploadbtn' => 'దస్త్రాన్ని ఎక్కించు',
'reuploaddesc' => 'మళ్ళీ అప్లోడు ఫారంకు వెళ్ళు.',
'upload-tryagain' => 'మార్చిన ఫైలు వివరణని దాఖలుచేయండి',
# Special:AllPages
'allpages' => 'అన్ని పేజీలు',
-'alphaindexline' => '$1 నుండి $2',
+'alphaindexline' => '$1 నుండి $2 వరకు',
'nextpage' => 'తరువాతి పేజీ ($1)',
'prevpage' => 'మునుపటి పేజీ ($1)',
'allpagesfrom' => 'ఇక్కడ మొదలు పెట్టి పేజీలు చూపించు:',
'namespace' => 'పేరుబరి:',
'invert' => 'ఎంపికను తిరగవెయ్యి',
'namespace_association' => 'సంబంధిత పేరుబరి',
-'blanknamespace' => '(తల)',
+'blanknamespace' => '(à°®à±\8aà°¦à°\9fà°¿)',
# Contributions
'contributions' => 'వాడుకరి రచనలు',
'fileduplicatesearch-noresults' => '"$1" అనే పేరుగల దస్త్రమేమీ కనబడలేదు.',
# Special:SpecialPages
-'specialpages' => 'à°µà±\87à°°à±\88à°¨ à°ªà±\81à°\9fలు',
+'specialpages' => 'à°ªà±\8dà°°à°¤à±\8dà°¯à±\87à°\95 à°ªà±\87à°\9cà±\80లు',
'specialpages-note' => '----
* మామూలు ప్రత్యేక పుటలు.
* <strong class="mw-specialpagerestricted">నియంత్రిత ప్రత్యేక పుటలు.</strong>
<?php
-/** Uzbek (Oʻzbek)
+/** Uzbek (Oʻzbekcha)
*
* See MessagesQqq.php for message documentation incl. usage of parameters
* To improve a translation please visit http://translatewiki.net
$linkTrail = '/^([a-zʻʼ“»]+)(.*)$/sDu';
$messages = array(
+# User preference toggles
+'tog-oldsig' => 'Mavjud imzo:',
+'tog-fancysig' => 'Imzoni wikimatn sifatida qara (avtomatik ishoratsiz)',
+
'underline-always' => 'Har doim',
'underline-never' => 'Hech qachon',
# Recent changes
'recentchanges' => 'Yangi o‘zgartirishlar',
'recentchanges-summary' => "Bu sahifada siz oxirgi o'zgartirishlarni ko'rishingiz mumkin.",
+'recentchanges-label-newpage' => 'Bu tahrir yangi sahifani yaratdi',
+'recentchanges-label-minor' => 'Bu kichik tahrir',
+'recentchanges-label-bot' => 'Bu tahrirni bot bajardi',
+'recentchanges-label-unpatrolled' => 'Bu tahrir hali tekshirilmadi',
'rcnote' => "Quyida $5, $4ga koʻra oxirgi {{PLURAL:$2|kun|'''$2''' kun}} davomida sodir boʻlgan {{PLURAL:$1|'''1''' oʻzgartirish|'''$1''' oʻzgartirishlar}} koʻrsatilgan.",
'rclistfrom' => "$1dan boshlab yangi o'zgartirishlarni ko'rsat.",
'rcshowhideminor' => 'Kichik tahrirlarni $1',
'duration-centuries' => '$1 thế kỷ',
'duration-millennia' => '$1 thiên niên kỷ',
+# Unknown messages
+'lockmanager-fail-svr-acquire' => 'Không thể lấy các chìa khóa trên máy chủ $1.',
);
'restorelink' => '{{PLURAL:$1|on candjmint disfacé|$1 candjmints disfacés}}',
'feedlinks' => 'Sindicåcion:',
'feed-invalid' => 'Sôre di sindicåcion nén valide.',
+'site-atom-feed' => 'Floûs Atom di $1',
+'page-atom-feed' => 'Floûs Atom di «$1»',
'red-link-title' => '$1 (nén co ataké)',
'sort-descending' => 'Discrexhant relijhaedje',
'sort-ascending' => 'Acrexhant relijhaedje',
Li båze di dnêyes a rtourné l' aroke «$3: $4».",
'laggedslavemode' => "Asteme: I s' pout ki l' pådje n' åye nén co les dierins candjmints.",
'readonly' => 'Li båze di dnêyes est aclawêye',
+'missing-article' => "Li båze di dnêyes n' a nén trové l' tecse d' ene pådje k' åreut dvou esse trovêye, lomêye «$1» $2.
+
+Çoula arive cwand on shût on loyén po ene diferince k' est houte ou viè l' istwere d' ene pådej disfacêye.
+
+Si c' est nén çoula, motoit k' vos avoz trové on bug dins l' programe.
+Adon dijhoz l' a on [[Special:ListUsers/sysop|manaedjeu]], sins rovyî d' mete li hårdêye (URL) ki mostere l' aroke.",
'missingarticle-rev' => '(l° del modêye: $1)',
+'missingarticle-diff' => '(dif: $1, $2)',
'internalerror' => 'Divintrinne aroke',
'internalerror_info' => 'Divintrinne aroke: $1',
'filecopyerror' => "Dji n' a savou copyî l' fitchî «$1» viè «$2».",
'createaccount' => 'Ahiver on novea conte',
'gotaccount' => "Vos avoz ddja on conte so ç' wiki ci? '''$1'''.",
'gotaccountlink' => 'Elodjîz vs',
+'userlogin-resetlink' => "Avoz rovyî vos detays d' elodjaedje?",
'createaccountmail' => 'pa emile',
'createaccountreason' => 'Råjhon:',
'badretype' => 'Vos avoz dné deus screts diferins.',
'templatesusedsection' => '{{PLURAL:$1|Modele eployî|Modeles eployîs}} e cisse seccion ci:',
'template-protected' => '(protedjî)',
'template-semiprotected' => '(dimey-protedjî)',
+'hiddencategories' => "Cisse pådje ci est mimbe {{PLURAL:$1|d' ene categoreye catcheye|di $1 categoreyes catcheyes}}:",
'nocreatetitle' => 'Ahivaedje di pådjes limité',
'nocreatetext' => "{{SITENAME}} a limité l' possibilité d' ahiver des novelès pådjes.
Vos ploz rivni en erî eyet candjî ene pådje k' egzistêye dedja, oudonbén, [[Special:UserLogin|vos elodjî ou ahiver on conte d' uzeu]].",
'edit-no-change' => "Vosse sicrijhaedje n' a nén passé, paski rén n' a stî candjî al modêye di dvant.",
'edit-already-exists' => "Li novele pâdje n' a savou esse ahivêye, ca cisse pâdje la egzistêye dedja.",
+# Parser/template warnings
+'post-expand-template-inclusion-warning' => "'''Asteme:''' I gn a trop di modeles dins cisse pådje ci.
+Sacwants di zels ni seront nén eployîs.",
+'post-expand-template-inclusion-category' => "Pådjes ki l' inclusion d' modeles est foû limite",
+
# Account creation failure
'cantcreateaccounttitle' => "Vos n' ploz nén ahiver-st on conte.",
'revdelete-radio-unset' => 'Neni',
'revdelete-log' => 'Råjhon:',
'revdelete-submit' => 'Apliker {{PLURAL:$1|al modêye tchoezeye|åzès modêyes tchoezeyes}}',
+'revdel-restore' => "candjî l' veyåvisté",
+'revdel-restore-deleted' => 'disfacêyès modêyes',
+'revdel-restore-visible' => 'veyåvès modêyes',
'deletedhist' => 'Istwere disfacêye',
# History merging
'mergehistory-no-source' => "Li pådje sourdant $1 n' egzistêye nén.",
'mergehistory-reason' => 'Råjhon:',
+# Merge log
+'revertmerge' => 'Dispårti',
+
# Diffs
'history-title' => 'Istwere des candjmints po «$1»',
'lineno' => 'Roye $1:',
# Recent changes
'nchanges' => '$1 {{PLURAL:$1|candjmint|candjmints}}',
'recentchanges' => 'Dierins candjmints',
+'recentchanges-legend' => 'Tchuzes po les dierins candjmints',
'recentchanges-summary' => "Shuvoz chal les dierins candjmints k' i gn a yeu dsu {{SITENAME}}.",
+'recentchanges-label-newpage' => "Ci candjmint la est èn ahivaedje d' ene nouve pådje",
+'recentchanges-label-minor' => "Ci n' est k' on ptit candjmint",
+'recentchanges-label-bot' => 'Ci candjmint la a stî fwait pa on robot',
+'recentchanges-label-unpatrolled' => "Ci candjmint la n' a nén co stî patrouyî",
'rcnote' => "Chal pa dzo {{PLURAL:$1|li dierin candjmint|les '''$1''' dierins candjmints}} {{PLURAL:$2|do dierin djoû|des '''$2''' dierins djoûs}}, disk' å $4 a $5.",
'rcnotefrom' => "Chal pa dzo les candjmints dispoy li '''$2''' (disk' a '''$1''' di mostrés).",
'rclistfrom' => "Mostrer les candjmints k' i gn a yeu a pårti do $1",
'hide' => 'catch.',
'show' => 'håy.',
'minoreditletter' => 'm',
+'newpageletter' => 'N',
+'boteditletter' => 'b',
'number_of_watching_users_pageview' => '[shuvou pa $1 {{PLURAL:$1|uzeu|uzeus}}]',
'rc_categories' => 'Limiter åzès categoreyes (separer avou des «|»)',
'rc_categories_any' => 'Totes',
'rc-change-size-new' => "$1 {{PLURAL:$1|octet|octets}} après l' candjmint",
'newsectionsummary' => '/* $1 */ novele seccion',
+'rc-enhanced-expand' => 'Mostrer les detays (i fåt JavaScript)',
+'rc-enhanced-hide' => 'Catchî les detays',
# Recent changes linked
'recentchangeslinked' => 'Candjmints aloyîs',
'recentchangeslinked-feed' => 'Candjmints aloyîs',
'recentchangeslinked-toolbox' => 'Candjmints aloyîs',
+'recentchangeslinked-title' => 'Candjmints aloyîs a «$1»',
+'recentchangeslinked-summary' => "Çouchal c' est ene djivêye des candjmints k' ont stî fwaits dierinnmint a des pådjes aloyeyes a pårti d' ene pådje dinêye (ou mimbes d' ene categoreye dinêye).
+Les pådjes ki [[Special:Watchlist|vos shuvoz]] sont-st e '''cråssès letes'''.",
+'recentchangeslinked-page' => 'No del pådje:',
+'recentchangeslinked-to' => "Mostere les candjmints des pådjes avou on loyén viè l' pådje dinêye purade k' å rviè",
# Upload
'upload' => 'Eberweter on fitchî',
# Book sources
'booksources' => 'Sourdants po les lives',
+'booksources-search-legend' => 'Cweri des sourdants po des lives',
+'booksources-go' => 'I va',
# Special:Log
'specialloguserlabel' => 'Fwait pa:',
'newuserlogpage' => 'Djournå des noveas uzeus',
'newuserlogpagetext' => "Chal pa dzo c' est ene djivêye des uzeus novelmint eredjîstrés.",
+# Special:ListGroupRights
+'listgrouprights-members' => '(djivêye des mimbes)',
+
# E-mail user
'mailnologin' => "Nole adresse d' evoyeu",
'mailnologintext' => "Po-z evoyî èn emile a èn ôte uzeu i vs fåt esse [[Special:UserLogin|elodjî]] eyet aveur ene adresse emile d' evoyeu ki soeye valide dins vos [[Special:Preferences|preferinces]].",
# Watchlist
'watchlist' => 'Pådjes shuvowes',
'mywatchlist' => 'Pådjes shuvowes',
+'watchlistfor2' => 'Pa $1 ($2)',
'nowatchlist' => 'Vosse djivêye des pådjes a shuve est vude.',
'watchlistanontext' => 'I vs fåt $1 po vey ou candjî les cayets di vosse djivêye des shuvous.',
'watchnologin' => "Vos n' estoz nén elodjî",
'iteminvalidname' => "Åk n' a nén stî avou «$1», li no n' est nén valide...",
'wlnote' => "Chal pa dzo {{PLURAL:$1|li dierin candjmint|les '''$1''' dierins candjmints}} {{PLURAL:$2|del dierinne eure|des '''$2''' dierinnès eures}}, disk' å $3 a $4.",
'wlshowlast' => 'Mostrer les dierin(nè)s $1 eures, $2 djoûs ou $3',
+'watchlist-options' => 'Tchuzes del djivêye des shuvous',
'enotif_mailer' => 'Notifiaedje pa emile di {{SITENAME}}',
'enotif_reset' => 'Mårker totes les pådjes come vizitêyes',
ni pout esse veyou ki des manaedjeus.",
'undeletebtn' => 'Rapexhî',
'undeletelink' => 'vey/rapexhî',
+'undeleteviewlink' => 'vey',
'undeletereset' => 'Netyî',
'undeletecomment' => 'Råjhon:',
'undeletedrevisions' => '{{PLURAL:$1|1 modêye|$1 modêyes}} di rapexheyes',
'linkshere' => "Les pådjes ki shuvèt ont des loyéns viè '''[[:$1]]''':",
'nolinkshere' => "Nole pådje avou des loyéns viè '''[[:$1]]'''.",
'isredirect' => 'pådje di redjiblaedje',
+'isimage' => "loyén viè l' fitchî",
'whatlinkshere-prev' => '{{PLURAL:$1|di dvant|$1 di dvant}}',
'whatlinkshere-next' => '{{PLURAL:$1|shuvant|$1 shuvants}}',
'whatlinkshere-links' => '← loyaedjes',
'expiringblock' => "disk' å $1 a $2",
'blocklink' => 'bloker',
'unblocklink' => 'disbloker',
+'change-blocklink' => "candjî l' blocaedje",
'contribslink' => 'contribouwaedjes',
'autoblocker' => "Bloké otomaticmint paski vos eployîz li minme adresse IP ki «[[User:$1|$1]]». Råjhon do blocaedje «'''$2'''».",
'blocklogpage' => 'Djournå des blocaedjes',
'tooltip-rollback' => "Li loyén «{{int:rollbacklink}}» permete di disfé d' on seu clitch tos les candjmints(s) fwaits sol pådje på dierin uzeu.",
'tooltip-undo' => "Li loyén «{{int:editundo}}» permete di disfé li candjmint et drouve li boesse di candjmint e môde prévoeyaedje.
Dj' ô bén, ça permete di disfé l' candjmint et d' mete on messaedje dins l' boesse di rascourti.",
+'tooltip-summary' => 'Dinez on ptit rascourti',
# Stylesheets
'common.css' => '/* li côde CSS metou chal serè eployî pa totes les peas et tos les uzeus */',
'thumbsize' => 'Grandeu po les imådjetes (thumb):',
'widthheightpage' => '$1 × $2, $3 pådje{{PLURAL:$3||s}}',
'file-info-size' => '$1 × $2 picsels, groxheur do fitchî: $3, del sôre "MIME": $4',
+'file-nohires' => "I n' a nén di pus grande finté.",
+'svg-long-desc' => 'Fitchî SVG, finté di $1 × $2 picsels, grandeu: $3',
'show-big-image' => "Imådje a si grandeur d' oridjinne",
# Special:NewFiles
'days' => '$1 djoû{{PLURAL:$1||s}}',
'ago' => 'i gn a $1',
+# Bad image list
+'bad_image_list' => "Li fôrmat est l' shuvant:
+
+Seulmint les intrêyes di djivêye (dj' ô bén, les royes ki cmincèt avou ene sitoele «*») sont acontêyes.
+Li prumî loén d' ene roye doet esse on loyén viè on fitchî k' on vout bloker si eployaedje.
+Les ôtes loyéns dnés sol minme roye sont veyous come des foû-rîle, dj' ô bén les pådjes wice ki l' fitchî pout esse eployî",
+
# Metadata
'metadata' => 'Meta-dnêyes',
'metadata-help' => "Ci fitchî chal a des infôrmåcions di rawete, motoit bén radjoutêyes pa l' aparey foto limerike ou l' sicanrece eployeye po fé l' imådje.
# Watchlist editor
'watchlistedit-raw-titles' => 'Tites:',
+# Watchlist editing tools
+'watchlisttools-edit' => "Vey et candjî l' djivêye des shuvous",
+
# Special:Version
'version' => 'Modêye des programes',
'version-extensions' => "Rawetes d' astalêyes",
'duration-centuries' => '$1世紀',
'duration-millennia' => '$1千年',
+# Unknown messages
+'lockmanager-fail-svr-acquire' => '無法取得伺服器$1上的鎖。',
);
if ( typeof uri === 'string' ) {
this.parse( uri, options );
} else if ( typeof uri === 'object' ) {
- var uriObj = this;
- $.each( properties, function ( i, property ) {
- uriObj[property] = uri[property];
- } );
- if ( this.query === undefined ) {
+ // Copy data over from existing URI object
+ for ( var prop in uri ) {
+ // Only copy direct properties, not inherited ones
+ if ( uri.hasOwnProperty( prop ) ) {
+ // Deep copy object properties
+ if ( $.isArray( uri[prop] ) || $.isPlainObject( uri[prop] ) ) {
+ this[prop] = $.extend( true, {}, uri[prop] );
+ } else {
+ this[prop] = uri[prop];
+ }
+ }
+ }
+ if ( !this.query ) {
this.query = {};
}
}
/**
* Log a message to window.console, if possible. Useful to force logging of some
- * errors that are otherwise hard to detect, even if mw.log is not available. (I.e.,
- * this logs also if not in debug mode.)
+ * errors that are otherwise hard to detect (I.e., this logs also in production mode).
+ * Gets console references in each invocation, so that delayed debugging tools work
+ * fine. No need for optimization here, which would only result in losing logs.
*
- * @param msg String text for the log entry
- * @param e Error [optional] to also log.
+ * @param msg String text for the log entry.
+ * @param e Error [optional] to also log.
*/
function log( msg, e ) {
- if ( window.console && typeof window.console.log === 'function' ) {
+ var console = window.console;
+ if ( console && console.log ) {
console.log( msg );
+ // console.error triggers the proper handling of exception objects in
+ // consoles that support it. Fallback to passing as plain object to log().
if ( e ) {
- console.log( e );
+ (console.error || console.log).call( console, e );
}
}
}
'''This year''''s election ''should'' beat '''last year''''s.
''Tom'''s car is bigger than ''Susan'''s.
+
+Plain ''italic'''s plain
!! result
<p><i><b>Bold italic text </b>with bold deactivated<b> in between.</b></i>
</p><p><b><i>Bold italic text </i>with italic deactivated<i> in between.</i></b>
</p><p>Normal text.
</p><p><b>This year'</b>s election <i>should</i> beat <b>last year'</b>s.
</p><p><i>Tom<b>s car is bigger than </b></i><b>Susan</b>s.
+</p><p>Plain <i>italic'</i>s plain
</p>
!! end
* @dataProvider dataPreSaveTransform
*/
public function testPreSaveTransform( $text, $expected ) {
+ $this->hideDeprecated( 'WikiPage::preSaveTransform' );
$user = new User();
$user->setName("127.0.0.1");
'action' => 'block',
'user' => 'UTApiBlockee',
'reason' => 'Some reason',
- 'token' => $pageinfo['blocktoken'] ), $data, false, self::$users['sysop']->user );
+ 'token' => $pageinfo['blocktoken'] ), null, false, self::$users['sysop']->user );
$block = Block::newFromTarget('UTApiBlockee');
}
- protected function doApiRequest( $params, $session = null, $appendModule = false, $user = null ) {
+ protected function doApiRequest( Array $params, Array $session = null, $appendModule = false, User $user = null ) {
+ global $wgRequest, $wgUser;
+
if ( is_null( $session ) ) {
- $session = array();
+ # re-use existing global session by default
+ $session = $wgRequest->getSessionArray();
+ }
+
+ # set up global environment
+ if ( $user ) {
+ $wgUser = $user;
}
- $context = $this->apiContext->newTestContext( $params, $session, $user );
+ $wgRequest = new FauxRequest( $params, true, $session );
+ RequestContext::getMain()->setRequest( $wgRequest );
+
+ # set up local environment
+ $context = $this->apiContext->newTestContext( $wgRequest, $wgUser );
+
$module = new ApiMain( $context, true );
+
+ # run it!
$module->execute();
+ # construct result
$results = array(
$module->getResultData(),
$context->getRequest(),
* Add an edit token to the API request
* This is cheating a bit -- we grab a token in the correct format and then add it to the pseudo-session and to the
* request, without actually requesting a "real" edit token
- * @param $params: key-value API params
- * @param $session: session array
- * @param $user String|null A User object for the context
+ * @param $params Array: key-value API params
+ * @param $session Array: session array
+ * @param $user User|null A User object for the context
*/
- protected function doApiRequestWithToken( $params, $session, $user = null ) {
+ protected function doApiRequestWithToken( Array $params, Array $session, User $user = null ) {
if ( $session['wsToken'] ) {
// add edit token to fake session
$session['wsEditToken'] = $session['wsToken'];
'lgtoken' => $token,
'lgname' => self::$users['sysop']->username,
'lgpassword' => self::$users['sysop']->password
- ), $data );
+ ), $data[2] );
return $data;
}
- protected function getTokenList( $user ) {
+ protected function getTokenList( $user, $session = null ) {
$data = $this->doApiRequest( array(
'action' => 'query',
'titles' => 'Main Page',
'intoken' => 'edit|delete|protect|move|block|unblock',
- 'prop' => 'info' ), false, $user->user );
+ 'prop' => 'info' ), $session, false, $user->user );
return $data;
}
}
/**
* Returns a DerivativeContext with the request variables in place
*
- * @param $params Array key-value API params
- * @param $session Array session data
+ * @param $request WebRequest request object including parameters and session
* @param $user User or null
* @return DerivativeContext
*/
- public function newTestContext( $params, $session, $user = null ) {
+ public function newTestContext( WebRequest $request, User $user = null ) {
$context = new DerivativeContext( $this );
- $context->setRequest( new FauxRequest( $params, true, $session ) );
+ $context->setRequest( $request );
if ( $user !== null ) {
$context->setUser( $user );
}
--- /dev/null
+<?php
+
+class ParserLesserFunctionsTest extends MediaWikiLangTestCase {
+
+ public function dataPreSaveTransform() {
+ return array(
+ array( 'hello this is ~~~',
+ "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]",
+ ),
+ array( 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+ 'hello \'\'this\'\' is <nowiki>~~~</nowiki>',
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider dataPreSaveTransform
+ */
+ public function testPreSaveTransform( $text, $expected ) {
+ global $wgParser;
+
+ $title = Title::newFromText( str_replace( '::', '__', __METHOD__ ) );
+ $user = new User();
+ $user->setName( "127.0.0.1" );
+ $popts = ParserOptions::newFromUser( $user );
+ $text = $wgParser->preSaveTransform( $text, $title, $user, $popts );
+
+ $this->assertEquals( $expected, $text );
+ }
+
+ // TODO: Add tests for cleanSig() / cleanSigInSig(), getSection(), replaceSection(), getPreloadText()
+}
+
}
}
- protected function doApiRequest( $params, $unused = null, $appendModule = false, $user = null ) {
+ protected function doApiRequest( Array $params, Array $unused = null, $appendModule = false, User $user = null ) {
$sessionId = session_id();
session_write_close();
test( 'Cloning', function () {
var original, clone;
- expect( 5 );
+ expect( 6 );
- original = new mw.Uri( 'http://en.wiki.local/w/api.php?action=query&foo=bar' );
+ original = new mw.Uri( 'http://foo.example.org/index.php?one=1&two=2' );
clone = original.clone();
deepEqual( clone, original, 'clone has equivalent properties' );
equal( original.toString(), clone.toString(), 'toString matches original' );
- notStrictEqual( clone, original, 'clone is not the same when compared by reference' );
+ notStrictEqual( clone, original, 'clone is a different object when compared by reference' );
- clone.host = 'fr.wiki.local';
+ clone.host = 'bar.example.org';
notEqual( original.host, clone.host, 'manipulating clone did not effect original' );
- notEqual( original.toString(), clone.toString(), 'toString no longer matches original' );
+ notEqual( original.toString(), clone.toString(), 'Stringified url no longer matches original' );
+
+ clone.query.three = 3;
+
+ deepEqual(
+ original.query,
+ { 'one': '1', 'two': '2' },
+ 'Properties is deep cloned (bug 37708)'
+ );
} );
test( 'Constructing mw.Uri from plain object', function () {