* Added $wgExtensionEntryPointListFiles for use in mergeMessageFileList.php.
* Added a hook, APIQuerySiteInfoStatisticsInfo, to allow extensions to modify
the output of the API query meta=siteinfo&siprop=statistics
+* Primary keys have been added to both the archive table and the externallinks
+ tables.
+* Added $wgEnableParserLimitReporting to control whether the NewPP limit report is
+ output in a HTML comment.
+* The 'UnwatchArticle' and 'WatchArticle' hooks now support a Status object
+ instead of just a boolean return value to abort the hook.
=== Bug fixes in 1.22 ===
* Disable Special:PasswordReset when $wgEnableEmail is false. Previously one
database is created.
* (bug 47191) Fixed "Column 'si_title' cannot be part of FULLTEXT index"
MySQL error when installing using the binary character set option.
+* (bug 45288) Support mysqli PHP extension
=== API changes in 1.22 ===
* (bug 25553) The JSON output formatter now leaves forward slashes unescaped
user blocks.
* (bug 48201) action=parse&text=foo now assumes wikitext if no title is given,
rather than using the content model of the page "API".
-* action=watch may now return errors.
+* action=watch no longer silently ignores hook abort.
* (bug 50785) action=purge with forcelinkupdate=1 no longer queues refreshLinks
jobs in the job queue for link table updates of pages that use the given page
as a template. Instead, forcerecursivelinkupdate=1 is introduced and should
to the Vector skin in core.
* SpecialRecentChanges::addRecentChangesJS() function has been renamed
to addModules() and made protected.
+* Methods WatchAction::doWatch and WatchAction::doUnwatch now return a Status
+ object instead of a boolean.
== Compatibility ==
'DatabaseMssql' => 'includes/db/DatabaseMssql.php',
'DatabaseMysql' => 'includes/db/DatabaseMysql.php',
'DatabaseMysqlBase' => 'includes/db/DatabaseMysqlBase.php',
+ 'DatabaseMysqli' => 'includes/db/DatabaseMysqli.php',
'DatabaseOracle' => 'includes/db/DatabaseOracle.php',
'DatabasePostgres' => 'includes/db/DatabasePostgres.php',
'DatabaseSqlite' => 'includes/db/DatabaseSqlite.php',
*/
$wgParserOutputHooks = array();
+/**
+ * Whether to include the NewPP limit report as a HTML comment
+ */
+$wgEnableParserLimitReporting = true;
+
/**
* List of valid skin names.
* The key should be the name in all lower case, the value should be a properly
* @since 1.22
*/
class HashRing {
+ /** @var Array (location => weight) */
+ protected $sourceMap = array();
/** @var Array (location => (start, end)) */
protected $ring = array();
if ( !count( $map ) ) {
throw new MWException( "Ring is empty or all weights are zero." );
}
+ $this->sourceMap = $map;
// Sort the locations based on the hash of their names
$hashes = array();
foreach ( $map as $location => $weight ) {
}
return $locations;
}
+
+ /**
+ * Get the map of locations to weight (ignores 0-weight items)
+ *
+ * @return array
+ */
+ public function getLocationWeights() {
+ return $this->sourceMap;
+ }
+
+ /**
+ * Get a new hash ring with a location removed from the ring
+ *
+ * @param string $location
+ * @return HashRing|bool Returns false if no non-zero weighted spots are left
+ */
+ public function newWithoutLocation( $location ) {
+ $map = $this->sourceMap;
+ unset( $map[$location] );
+ if ( count( $map ) ) {
+ return new self( $map );
+ }
+ return false;
+ }
}
return $this;
}
+ /**
+ * Add parameters that are durations of time and will be passed through
+ * Language::formatDuration before substitution
+ * @since 1.22
+ * @param Varargs: numeric parameters (or single argument that is array of numeric parameters)
+ * @return Message: $this
+ */
+ public function durationParams( /*...*/ ) {
+ $params = func_get_args();
+ if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+ $params = $params[0];
+ }
+ foreach ( $params as $param ) {
+ $this->parameters[] = self::durationParam( $param );
+ }
+ return $this;
+ }
+
+ /**
+ * Add parameters that are expiration times and will be passed through
+ * Language::formatExpiry before substitution
+ * @since 1.22
+ * @param Varargs: numeric parameters (or single argument that is array of numeric parameters)
+ * @return Message: $this
+ */
+ public function expiryParams( /*...*/ ) {
+ $params = func_get_args();
+ if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+ $params = $params[0];
+ }
+ foreach ( $params as $param ) {
+ $this->parameters[] = self::expiryParam( $param );
+ }
+ return $this;
+ }
+
+ /**
+ * Add parameters that are time periods and will be passed through
+ * Language::formatTimePeriod before substitution
+ * @since 1.22
+ * @param Varargs: numeric parameters (or single argument that is array of numeric parameters)
+ * @return Message: $this
+ */
+ public function timeperiodParams( /*...*/ ) {
+ $params = func_get_args();
+ if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+ $params = $params[0];
+ }
+ foreach ( $params as $param ) {
+ $this->parameters[] = self::timeperiodParam( $param );
+ }
+ return $this;
+ }
+
+ /**
+ * Add parameters that are file sizes and will be passed through
+ * Language::formatSize before substitution
+ * @since 1.22
+ * @param Varargs: numeric parameters (or single argument that is array of numeric parameters)
+ * @return Message: $this
+ */
+ public function sizeParams( /*...*/ ) {
+ $params = func_get_args();
+ if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+ $params = $params[0];
+ }
+ foreach ( $params as $param ) {
+ $this->parameters[] = self::sizeParam( $param );
+ }
+ return $this;
+ }
+
+ /**
+ * Add parameters that are bitrates and will be passed through
+ * Language::formatBitrate before substitution
+ * @since 1.22
+ * @param Varargs: numeric parameters (or single argument that is array of numeric parameters)
+ * @return Message: $this
+ */
+ public function bitrateParams( /*...*/ ) {
+ $params = func_get_args();
+ if ( isset( $params[0] ) && is_array( $params[0] ) ) {
+ $params = $params[0];
+ }
+ foreach ( $params as $param ) {
+ $this->parameters[] = self::bitrateParam( $param );
+ }
+ return $this;
+ }
+
/**
* Set the language and the title from a context object
* @since 1.19
return array( 'num' => $value );
}
+ /**
+ * @since 1.22
+ * @param $value
+ * @return array
+ */
+ public static function durationParam( $value ) {
+ return array( 'duration' => $value );
+ }
+
+ /**
+ * @since 1.22
+ * @param $value
+ * @return array
+ */
+ public static function expiryParam( $value ) {
+ return array( 'expiry' => $value );
+ }
+
+ /**
+ * @since 1.22
+ * @param $value
+ * @return array
+ */
+ public static function timeperiodParam( $value ) {
+ return array( 'period' => $value );
+ }
+
+ /**
+ * @since 1.22
+ * @param $value
+ * @return array
+ */
+ public static function sizeParam( $value ) {
+ return array( 'size' => $value );
+ }
+
+ /**
+ * @since 1.22
+ * @param $value
+ * @return array
+ */
+ public static function bitrateParam( $value ) {
+ return array( 'bitrate' => $value );
+ }
+
/**
* Substitutes any parameters into the message text.
* @since 1.17
* @return Tuple(type, value)
*/
protected function extractParam( $param ) {
- if ( is_array( $param ) && isset( $param['raw'] ) ) {
- return array( 'after', $param['raw'] );
- } elseif ( is_array( $param ) && isset( $param['num'] ) ) {
- // Replace number params always in before step for now.
- // No support for combined raw and num params
- return array( 'before', $this->language->formatNum( $param['num'] ) );
- } elseif ( !is_array( $param ) ) {
- return array( 'before', $param );
+ if ( is_array( $param ) ){
+ if ( isset( $param['raw'] ) ) {
+ return array( 'after', $param['raw'] );
+ } elseif ( isset( $param['num'] ) ) {
+ // Replace number params always in before step for now.
+ // No support for combined raw and num params
+ return array( 'before', $this->language->formatNum( $param['num'] ) );
+ } elseif ( isset( $param['duration'] ) ) {
+ return array( 'before', $this->language->formatDuration( $param['duration'] ) );
+ } elseif ( isset( $param['expiry'] ) ) {
+ return array( 'before', $this->language->formatExpiry( $param['expiry'] ) );
+ } elseif ( isset( $param['period'] ) ) {
+ return array( 'before', $this->language->formatTimePeriod( $param['period'] ) );
+ } elseif ( isset( $param['size'] ) ) {
+ return array( 'before', $this->language->formatSize( $param['size'] ) );
+ } elseif ( isset( $param['bitrate'] ) ) {
+ return array( 'before', $this->language->formatBitrate( $param['bitrate'] ) );
+ } else {
+ trigger_error(
+ "Invalid message parameter: " . htmlspecialchars( serialize( $param ) ),
+ E_USER_WARNING
+ );
+ return array( 'before', '[INVALID]' );
+ }
} else {
- trigger_error(
- "Invalid message parameter: " . htmlspecialchars( serialize( $param ) ),
- E_USER_WARNING
- );
- return array( 'before', '[INVALID]' );
+ return array( 'before', $param );
}
}
* @return ParserOptions
*/
public function makeParserOptions( $context ) {
- global $wgContLang;
+ global $wgContLang, $wgEnableParserLimitReporting;
if ( $context instanceof IContextSource ) {
$options = ParserOptions::newFromContext( $context );
throw new MWException( "Bad context for parser options: $context" );
}
- $options->enableLimitReport(); // show inclusion/loop reports
+ $options->enableLimitReport( $wgEnableParserLimitReporting ); // show inclusion/loop reports
$options->setTidy( true ); // fix bad HTML
return $options;
/**
* Constructor.
+ *
+ * FIXME: It is possible to construct a Database object with no associated
+ * connection object, by specifying no parameters to __construct(). This
+ * feature is deprecated and should be removed.
+ *
+ * FIXME: The long list of formal parameters here is not really appropriate
+ * for MySQL, and not at all appropriate for any other DBMS. It should be
+ * replaced by named parameters as in DatabaseBase::factory().
+ *
+ * DatabaseBase subclasses should not be constructed directly in external
+ * code. DatabaseBase::factory() should be used instead.
+ *
* @param string $server database server host
* @param string $user database user name
* @param string $password database user password
/**
* Given a DB type, construct the name of the appropriate child class of
* DatabaseBase. This is designed to replace all of the manual stuff like:
- * $class = 'Database' . ucfirst( strtolower( $type ) );
+ * $class = 'Database' . ucfirst( strtolower( $dbType ) );
* as well as validate against the canonical list of DB types we have
*
* This factory function is mostly useful for when you need to connect to a
*
* @param string $dbType A possible DB type
* @param array $p An array of options to pass to the constructor.
- * Valid options are: host, user, password, dbname, flags, tablePrefix
+ * Valid options are: host, user, password, dbname, flags, tablePrefix, driver
* @return DatabaseBase subclass or null
*/
final public static function factory( $dbType, $p = array() ) {
$canonicalDBTypes = array(
- 'mysql', 'postgres', 'sqlite', 'oracle', 'mssql'
+ 'mysql' => array( 'mysqli', 'mysql' ),
+ 'postgres' => array(),
+ 'sqlite' => array(),
+ 'oracle' => array(),
+ 'mssql' => array(),
);
+
+ $driver = false;
$dbType = strtolower( $dbType );
- $class = 'Database' . ucfirst( $dbType );
+ if ( isset( $canonicalDBTypes[$dbType] ) && $canonicalDBTypes[$dbType] ) {
+ $possibleDrivers = $canonicalDBTypes[$dbType];
+ if ( !empty( $p['driver'] ) ) {
+ if ( in_array( $p['driver'], $possibleDrivers ) ) {
+ $driver = $p['driver'];
+ } else {
+ throw new MWException( __METHOD__ .
+ " cannot construct Database with type '$dbType' and driver '{$p['driver']}'" );
+ }
+ } else {
+ foreach ( $possibleDrivers as $posDriver ) {
+ if ( extension_loaded( $posDriver ) ) {
+ $driver = $posDriver;
+ break;
+ }
+ }
+ }
+ } else {
+ $driver = $dbType;
+ }
+ if ( $driver === false ) {
+ throw new MWException( __METHOD__ .
+ " no viable database extension found for type '$dbType'" );
+ }
- if ( in_array( $dbType, $canonicalDBTypes ) || ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) ) {
+ $class = 'Database' . ucfirst( $driver );
+ if ( class_exists( $class ) && is_subclass_of( $class, 'DatabaseBase' ) ) {
return new $class(
isset( $p['host'] ) ? $p['host'] : false,
isset( $p['user'] ) ? $p['user'] : false,
*/
class MySQLField implements Field {
private $name, $tablename, $default, $max_length, $nullable,
- $is_pk, $is_unique, $is_multiple, $is_key, $type;
+ $is_pk, $is_unique, $is_multiple, $is_key, $type, $binary;
function __construct( $info ) {
$this->name = $info->name;
$this->is_multiple = $info->multiple_key;
$this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
$this->type = $info->type;
+ $this->binary = isset( $info->binary ) ? $info->binary : false;
}
/**
function isMultipleKey() {
return $this->is_multiple;
}
+
+ function isBinary() {
+ return $this->binary;
+ }
}
class MySQLMasterPos implements DBMasterPos {
--- /dev/null
+<?php
+/**
+ * This is the MySQLi database abstraction layer.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Database
+ */
+
+/**
+ * Database abstraction object for PHP extension mysqli.
+ *
+ * @ingroup Database
+ * @since 1.22
+ * @see Database
+ */
+class DatabaseMysqli extends DatabaseMysqlBase {
+
+ /**
+ * @param $sql string
+ * @return resource
+ */
+ protected function doQuery( $sql ) {
+ if ( $this->bufferResults() ) {
+ $ret = $this->mConn->query( $sql );
+ } else {
+ $ret = $this->mConn->query( $sql, MYSQLI_USE_RESULT );
+ }
+ return $ret;
+ }
+
+ protected function mysqlConnect( $realServer ) {
+ # Fail now
+ # Otherwise we get a suppressed fatal error, which is very hard to track down
+ if ( !function_exists( 'mysqli_init' ) ) {
+ throw new DBConnectionError( $this, "MySQLi functions missing,"
+ . " have you compiled PHP with the --with-mysqli option?\n" );
+ }
+
+ $connFlags = 0;
+ if ( $this->mFlags & DBO_SSL ) {
+ $connFlags |= MYSQLI_CLIENT_SSL;
+ }
+ if ( $this->mFlags & DBO_COMPRESS ) {
+ $connFlags |= MYSQLI_CLIENT_COMPRESS;
+ }
+ if ( $this->mFlags & DBO_PERSISTENT ) {
+ $realServer = 'p:' . $realServer;
+ }
+
+ $mysqli = mysqli_init();
+ $numAttempts = 2;
+
+ for ( $i = 0; $i < $numAttempts; $i++ ) {
+ if ( $i > 1 ) {
+ usleep( 1000 );
+ }
+ if ( $mysqli->real_connect( $realServer, $this->mUser,
+ $this->mPassword, $this->mDBname, null, null, $connFlags ) )
+ {
+ return $mysqli;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @return bool
+ */
+ protected function closeConnection() {
+ return $this->mConn->close();
+ }
+
+ /**
+ * @return int
+ */
+ function insertId() {
+ return $this->mConn->insert_id;
+ }
+
+ /**
+ * @return int
+ */
+ function lastErrno() {
+ if ( $this->mConn ) {
+ return $this->mConn->errno;
+ } else {
+ return mysqli_connect_errno();
+ }
+ }
+
+ /**
+ * @return int
+ */
+ function affectedRows() {
+ return $this->mConn->affected_rows;
+ }
+
+ /**
+ * @param $db
+ * @return bool
+ */
+ function selectDB( $db ) {
+ $this->mDBname = $db;
+ return $this->mConn->select_db( $db );
+ }
+
+ /**
+ * @return string
+ */
+ function getServerVersion() {
+ return $this->mConn->server_info;
+ }
+
+ protected function mysqlFreeResult( $res ) {
+ $res->free_result();
+ return true;
+ }
+
+ protected function mysqlFetchObject( $res ) {
+ $object = $res->fetch_object();
+ if ( $object === null ) {
+ return false;
+ }
+ return $object;
+ }
+
+ protected function mysqlFetchArray( $res ) {
+ $array = $res->fetch_array();
+ if ( $array === null ) {
+ return false;
+ }
+ return $array;
+ }
+
+ protected function mysqlNumRows( $res ) {
+ return $res->num_rows;
+ }
+
+ protected function mysqlNumFields( $res ) {
+ return $res->field_count;
+ }
+
+ protected function mysqlFetchField( $res, $n ) {
+ $field = $res->fetch_field_direct( $n );
+ $field->not_null = $field->flags & MYSQLI_NOT_NULL_FLAG;
+ $field->primary_key = $field->flags & MYSQLI_PRI_KEY_FLAG;
+ $field->unique_key = $field->flags & MYSQLI_UNIQUE_KEY_FLAG;
+ $field->multiple_key = $field->flags & MYSQLI_MULTIPLE_KEY_FLAG;
+ $field->binary = $field->flags & MYSQLI_BINARY_FLAG;
+ return $field;
+ }
+
+ protected function mysqlFieldName( $res, $n ) {
+ $field = $res->fetch_field_direct( $n );
+ return $field->name;
+ }
+
+ protected function mysqlDataSeek( $res, $row ) {
+ return $res->data_seek( $row );
+ }
+
+ protected function mysqlError( $conn = null ) {
+ if ($conn === null) {
+ return mysqli_connect_error();
+ } else {
+ return $conn->error;
+ }
+ }
+
+ protected function mysqlRealEscapeString( $s ) {
+ return $this->mConn->real_escape_string( $s );
+ }
+
+ protected function mysqlPing() {
+ return $this->mConn->ping();
+ }
+
+}
$this->mName = $dbName;
parent::__construct( $server, $user, $password, $dbName, $flags );
// parent doesn't open when $user is false, but we can work with $dbName
- if ( $dbName ) {
+ if ( $dbName && !$this->isOpen() ) {
global $wgSharedDB;
if ( $this->open( $server, $user, $password, $dbName ) && $wgSharedDB ) {
$this->attachDatabase( $wgSharedDB );
function open( $server, $user, $pass, $dbName ) {
global $wgSQLiteDataDir;
+ $this->close();
$fileName = self::generateFileName( $wgSQLiteDataDir, $dbName );
if ( !is_readable( $fileName ) ) {
$this->mConn = false;
if ( $this->mTrxLevel == 1 ) {
$this->commit( __METHOD__ );
}
- $this->mConn->beginTransaction();
+ try {
+ $this->mConn->beginTransaction();
+ } catch ( PDOException $e ) {
+ throw new DBUnexpectedError( $this, 'Error in BEGIN query: ' . $e->getMessage() );
+ }
$this->mTrxLevel = 1;
}
if ( $this->mTrxLevel == 0 ) {
return;
}
- $this->mConn->commit();
+ try {
+ $this->mConn->commit();
+ } catch ( PDOException $e ) {
+ throw new DBUnexpectedError( $this, 'Error in COMMIT query: ' . $e->getMessage() );
+ }
$this->mTrxLevel = 0;
}
* Once the return value goes out scope, the locks will be released and
* the status updated. Unlock fatals will not change the status "OK" value.
*
- * @param array $paths Storage paths
- * @param integer $type LockManager::LOCK_* constant
+ * @see ScopedLock::factory()
+ *
+ * @param array $paths List of storage paths or map of lock types to path lists
+ * @param integer|string $type LockManager::LOCK_* constant or "mixed"
* @param Status $status Status to update on lock/unlock
* @return ScopedLock|null Returns null on failure
*/
$mbe = $this->backends[$this->masterIndex]; // convenience
- // Get the paths to lock from the master backend
- $realOps = $this->substOpBatchPaths( $ops, $mbe );
- $paths = $mbe->getPathsToLockForOpsInternal( $mbe->getOperationsInternal( $realOps ) );
- // Get the paths under the proxy backend's name
- $paths['sh'] = $this->unsubstPaths( $paths['sh'] );
- $paths['ex'] = $this->unsubstPaths( $paths['ex'] );
// Try to lock those files for the scope of this function...
if ( empty( $opts['nonLocking'] ) ) {
// Try to lock those files for the scope of this function...
- $scopeLockS = $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status );
- $scopeLockE = $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status );
+ $scopeLock = $this->getScopedLocksForOps( $ops, $status );
if ( !$status->isOK() ) {
return $status; // abort
}
}
}
// Actually attempt the operation batch on the master backend...
+ $realOps = $this->substOpBatchPaths( $ops, $mbe );
$masterStatus = $mbe->doOperations( $realOps, $opts );
$status->merge( $masterStatus );
// Propagate the operations to the clone backends if there were no unexpected errors
}
public function getScopedLocksForOps( array $ops, Status $status ) {
- $fileOps = $this->backends[$this->masterIndex]->getOperationsInternal( $ops );
+ $realOps = $this->substOpBatchPaths( $ops, $this->backends[$this->masterIndex] );
+ $fileOps = $this->backends[$this->masterIndex]->getOperationsInternal( $realOps );
// Get the paths to lock from the master backend
$paths = $this->backends[$this->masterIndex]->getPathsToLockForOpsInternal( $fileOps );
// Get the paths under the proxy backend's name
- $paths['sh'] = $this->unsubstPaths( $paths['sh'] );
- $paths['ex'] = $this->unsubstPaths( $paths['ex'] );
- return array(
- $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status ),
- $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status )
+ $pbPaths = array(
+ LockManager::LOCK_UW => $this->unsubstPaths( $paths[LockManager::LOCK_UW] ),
+ LockManager::LOCK_EX => $this->unsubstPaths( $paths[LockManager::LOCK_EX] )
);
+ // Actually acquire the locks
+ return array( $this->getScopedFileLocks( $pbPaths, 'mixed', $status ) );
}
}
/**
* Get a list of storage paths to lock for a list of operations
- * Returns an array with 'sh' (shared) and 'ex' (exclusive) keys,
- * each corresponding to a list of storage paths to be locked.
- * All returned paths are normalized.
+ * Returns an array with LockManager::LOCK_UW (shared locks) and
+ * LockManager::LOCK_EX (exclusive locks) keys, each corresponding
+ * to a list of storage paths to be locked. All returned paths are
+ * normalized.
*
* @param array $performOps List of FileOp objects
- * @return Array ('sh' => list of paths, 'ex' => list of paths)
+ * @return Array (LockManager::LOCK_UW => path list, LockManager::LOCK_EX => path list)
*/
final public function getPathsToLockForOpsInternal( array $performOps ) {
// Build up a list of files to lock...
// Get a shared lock on the parent directory of each path changed
$paths['sh'] = array_merge( $paths['sh'], array_map( 'dirname', $paths['ex'] ) );
- return $paths;
+ return array(
+ LockManager::LOCK_UW => $paths['sh'],
+ LockManager::LOCK_EX => $paths['ex']
+ );
}
public function getScopedLocksForOps( array $ops, Status $status ) {
$paths = $this->getPathsToLockForOpsInternal( $this->getOperationsInternal( $ops ) );
- return array(
- $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status ),
- $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status )
- );
+ return array( $this->getScopedFileLocks( $paths, 'mixed', $status ) );
}
final protected function doOperationsInternal( array $ops, array $opts ) {
// Build up a list of files to lock...
$paths = $this->getPathsToLockForOpsInternal( $performOps );
// Try to lock those files for the scope of this function...
- $scopeLockS = $this->getScopedFileLocks( $paths['sh'], LockManager::LOCK_UW, $status );
- $scopeLockE = $this->getScopedFileLocks( $paths['ex'], LockManager::LOCK_EX, $status );
+ $scopeLock = $this->getScopedFileLocks( $paths, 'mixed', $status );
if ( !$status->isOK() ) {
return $status; // abort
}
wfDebugLog( 'imagemove', "Finished moving {$this->name}" );
- $this->purgeEverything();
- foreach ( $archiveNames as $archiveName ) {
- $this->purgeOldThumbnails( $archiveName );
- }
+ // Purge the source and target files...
+ $oldTitleFile = wfLocalFile( $this->title );
+ $newTitleFile = wfLocalFile( $target );
+ // Hack: the lock()/unlock() pair is nested in a transaction so the locking is not
+ // tied to BEGIN/COMMIT. To avoid slow purges in the transaction, move them outside.
+ $this->getRepo()->getMasterDB()->onTransactionIdle(
+ function() use ( $oldTitleFile, $newTitleFile, $archiveNames ) {
+ $oldTitleFile->purgeEverything();
+ foreach ( $archiveNames as $archiveName ) {
+ $oldTitleFile->purgeOldThumbnails( $archiveName );
+ }
+ $newTitleFile->purgeEverything();
+ }
+ );
+
if ( $status->isOK() ) {
// Now switch the object
$this->title = $target;
// Force regeneration of the name and hashpath
unset( $this->name );
unset( $this->hashPath );
- // Purge the new image
- $this->purgeEverything();
}
return $status;
* @return Bool
*/
public function isCompiled() {
- return self::checkExtension( 'mysql' );
+ return self::checkExtension( 'mysql' ) || self::checkExtension( 'mysqli' );
}
/**
public function openConnection() {
$status = Status::newGood();
try {
- $db = new DatabaseMysql(
- $this->getVar( 'wgDBserver' ),
- $this->getVar( '_InstallUser' ),
- $this->getVar( '_InstallPassword' ),
- false,
- 0,
- $this->getVar( 'wgDBprefix' )
- );
+ $db = DatabaseBase::factory( 'mysql', array(
+ 'host' => $this->getVar( 'wgDBserver' ),
+ 'user' => $this->getVar( '_InstallUser' ),
+ 'password' => $this->getVar( '_InstallPassword' ),
+ 'dbname' => false,
+ 'flags' => 0,
+ 'tablePrefix' => $this->getVar( 'wgDBprefix' ) ) );
$status->value = $db;
} catch ( DBConnectionError $e ) {
$status->fatal( 'config-connection-error', $e->getMessage() );
if ( !$create ) {
// Test the web account
try {
- new DatabaseMysql(
- $this->getVar( 'wgDBserver' ),
- $this->getVar( 'wgDBuser' ),
- $this->getVar( 'wgDBpassword' ),
- false,
- 0,
- $this->getVar( 'wgDBprefix' )
- );
+ $db = DatabaseBase::factory( 'mysql', array(
+ 'host' => $this->getVar( 'wgDBserver' ),
+ 'user' => $this->getVar( 'wgDBuser' ),
+ 'password' => $this->getVar( 'wgDBpassword' ),
+ 'dbname' => false,
+ 'flags' => 0,
+ 'tablePrefix' => $this->getVar( 'wgDBprefix' ) ) );
} catch ( DBConnectionError $e ) {
return Status::newFatal( 'config-connection-error', $e->getMessage() );
}
if ( $this->getVar( '_CreateDBAccount' ) ) {
// Before we blindly try to create a user that already has access,
try { // first attempt to connect to the database
- new DatabaseMysql(
- $server,
- $dbUser,
- $password,
- false,
- 0,
- $this->getVar( 'wgDBprefix' )
- );
+ $db = DatabaseBase::factory( 'mysql', array(
+ 'host' => $server,
+ 'user' => $dbUser,
+ 'password' => $password,
+ 'dbname' => false,
+ 'flags' => 0,
+ 'tablePrefix' => $this->getVar( 'wgDBprefix' ) ) );
$grantableNames[] = $this->buildFullUserName( $dbUser, $server );
$tryToCreate = false;
} catch ( DBConnectionError $e ) {
// 1.15
array( 'doUniquePlTlIl' ),
array( 'addTable', 'change_tag', 'patch-change_tag.sql' ),
- /* array( 'addTable', 'tag_summary', 'patch-change_tag.sql' ), */
- /* array( 'addTable', 'valid_tag', 'patch-change_tag.sql' ), */
+ array( 'addTable', 'tag_summary', 'patch-tag_summary.sql' ),
+ array( 'addTable', 'valid_tag', 'patch-valid_tag.sql' ),
// 1.16
array( 'addTable', 'user_properties', 'patch-user_properties.sql' ),
// 1.22
array( 'doIwlinksIndexNonUnique' ),
array( 'addIndex', 'iwlinks', 'iwl_prefix_from_title', 'patch-iwlinks-from-title-index.sql' ),
+ array( 'addField', 'archive', 'ar_id', 'patch-archive-ar_id.sql' ),
+ array( 'addField', 'externallinks', 'el_id', 'patch-externallinks-el_id.sql' ),
);
}
return true;
}
- $tableName = $this->db->tableName( $table );
- $res = $this->db->query( "SELECT $field FROM $tableName LIMIT 0", __METHOD__ );
- $flags = explode( ' ', mysql_field_flags( $res->result, 0 ) );
-
- if ( in_array( 'binary', $flags ) ) {
+ $fieldInfo = $this->db->fieldInfo( $table, $field );
+ if ( $fieldInfo->isBinary() ) {
$this->output( "...$table table has correct $field encoding.\n" );
} else {
$this->applyPatch( $patchFile, false, "Fixing $field encoding on $table table" );
array( 'addField', 'revision', 'rev_content_format', 'patch-revision-rev_content_format.sql' ),
array( 'addField', 'revision', 'rev_content_model', 'patch-revision-rev_content_model.sql' ),
array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ),
- array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ),
+ array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ),
+ array( 'addField', 'archive', 'ar_id', 'patch-archive-ar_id.sql' ),
+ array( 'addField', 'externallinks', 'el_id', 'patch-externallinks-el_id.sql' ),
array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
array( 'dropField', 'site_stats', 'ss_admins', 'patch-ss_admins.sql' ),
array( 'dropField', 'recentchanges', 'rc_moved_to_title', 'patch-rc_moved.sql' ),
array( 'addPgField', 'archive', 'ar_content_model', 'TEXT' ),
array( 'addPgField', 'archive', 'ar_content_format', 'TEXT' ),
array( 'addPgField', 'categorylinks', 'cl_sortkey_prefix', "TEXT NOT NULL DEFAULT ''"),
- array( 'addPgField', 'categorylinks', 'cl_collation', "TEXT NOT NULL DEFAULT 0"),
- array( 'addPgField', 'categorylinks', 'cl_type', "TEXT NOT NULL DEFAULT 'page'"),
+ array( 'addPgField', 'categorylinks', 'cl_collation', "TEXT NOT NULL DEFAULT 0" ),
+ array( 'addPgField', 'categorylinks', 'cl_type', "TEXT NOT NULL DEFAULT 'page'" ),
array( 'addPgField', 'image', 'img_sha1', "TEXT NOT NULL DEFAULT ''" ),
array( 'addPgField', 'ipblocks', 'ipb_allow_usertalk', 'SMALLINT NOT NULL DEFAULT 0' ),
array( 'addPgField', 'ipblocks', 'ipb_anon_only', 'SMALLINT NOT NULL DEFAULT 0' ),
array( 'addPgField', 'job', 'job_token', "TEXT NOT NULL DEFAULT ''" ),
array( 'addPgField', 'job', 'job_token_timestamp', "TIMESTAMPTZ" ),
array( 'addPgField', 'job', 'job_sha1', "TEXT NOT NULL DEFAULT ''" ),
+ array( 'addPgField', 'archive', 'ar_id', "INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('archive_ar_id_seq')" ),
+ array( 'addPgField', 'externallinks', 'el_id', "INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('externallinks_el_id_seq')" ),
+
# type changes
array( 'changeField', 'archive', 'ar_deleted', 'smallint', '' ),
// 1.15
array( 'addTable', 'change_tag', 'patch-change_tag.sql' ),
- array( 'addTable', 'tag_summary', 'patch-change_tag.sql' ),
- array( 'addTable', 'valid_tag', 'patch-change_tag.sql' ),
+ array( 'addTable', 'tag_summary', 'patch-tag_summary.sql' ),
+ array( 'addTable', 'valid_tag', 'patch-valid_tag.sql' ),
// 1.16
array( 'addTable', 'user_properties', 'patch-user_properties.sql' ),
array( 'addField', 'archive', 'ar_content_format', 'patch-archive-ar_content_format.sql' ),
array( 'addField', 'archive', 'ar_content_model', 'patch-archive-ar_content_model.sql' ),
array( 'addField', 'page', 'page_content_model', 'patch-page-page_content_model.sql' ),
-
array( 'dropField', 'site_stats', 'ss_admins', 'patch-drop-ss_admins.sql' ),
array( 'dropField', 'recentchanges', 'rc_moved_to_title', 'patch-rc_moved.sql' ),
array( 'addTable', 'sites', 'patch-sites.sql' ),
array( 'addIndex', 'page_props', 'pp_propname_page', 'patch-page_props-propname-page-index.sql' ),
array( 'addIndex', 'image', 'img_media_mime', 'patch-img_media_mime-index.sql' ),
array( 'addIndex', 'iwlinks', 'iwl_prefix_from_title', 'patch-iwlinks-from-title-index.sql' ),
+ array( 'addField', 'archive', 'ar_id', 'patch-archive-ar_id.sql' ),
+ array( 'addField', 'externallinks', 'el_id', 'patch-externallinks-el_id.sql' ),
);
}
protected $dupCache;
const QOS_ATOMIC = 1; // integer; "all-or-nothing" job insertions
- const QoS_Atomic = 1; // integer; "all-or-nothing" job insertions (b/c)
const ROOTJOB_TTL = 2419200; // integer; seconds to remember root jobs (28 days)
*
* If used for performance, then $wgMainCacheType should be set to memcached/redis.
* Note that "fifo" cannot be used for the ordering, since the data is distributed.
- * One can still use "timestamp" instead, as in "roughly timestamp ordered".
+ * One can still use "timestamp" instead, as in "roughly timestamp ordered". Also,
+ * queue classes used by this should ignore down servers (with TTL) to avoid slowness.
*
* @ingroup JobQueue
* @since 1.22
*/
class JobQueueFederated extends JobQueue {
- /** @var Array (wiki ID => section name) */
- protected $sectionsByWiki = array();
- /** @var Array (section name => (partition name => weight)) */
- protected $partitionsBySection = array();
- /** @var Array (section name => config array) */
- protected $configByPartition = array();
- /** @var Array (partition names => integer) */
- protected $partitionsNoPush = array();
-
- /** @var HashRing */
- protected $partitionRing;
- /** @var Array (partition name => JobQueue) */
+ /** @var Array (partition name => weight) reverse sorted by weight */
+ protected $partitionMap = array();
+ /** @var Array (partition name => JobQueue) reverse sorted by weight */
protected $partitionQueues = array();
+ /** @var HashRing */
+ protected $partitionPushRing;
/** @var BagOStuff */
protected $cache;
*/
protected function __construct( array $params ) {
parent::__construct( $params );
- $this->sectionsByWiki = isset( $params['sectionsByWiki'] )
- ? $params['sectionsByWiki']
- : array(); // all in "default" section
- $this->partitionsBySection = $params['partitionsBySection'];
- $this->configByPartition = $params['configByPartition'];
+ $section = isset( $params['sectionsByWiki'][$this->wiki] )
+ ? $params['sectionsByWiki'][$this->wiki]
+ : 'default';
+ if ( !isset( $params['partitionsBySection'][$section] ) ) {
+ throw new MWException( "No configuration for section '$section'." );
+ }
+ // Get the full partition map
+ $this->partitionMap = $params['partitionsBySection'][$section];
+ arsort( $this->partitionMap, SORT_NUMERIC );
+ // Get the partitions jobs can actually be pushed to
+ $partitionPushMap = $this->partitionMap;
if ( isset( $params['partitionsNoPush'] ) ) {
- $this->partitionsNoPush = array_flip( $params['partitionsNoPush'] );
+ foreach ( $params['partitionsNoPush'] as $partition ) {
+ unset( $partitionPushMap[$partition] );
+ }
}
+ // Get the config to pass to merge into each partition queue config
$baseConfig = $params;
foreach ( array( 'class', 'sectionsByWiki',
'partitionsBySection', 'configByPartition', 'partitionsNoPush' ) as $o )
{
unset( $baseConfig[$o] );
}
- foreach ( $this->getPartitionMap() as $partition => $w ) {
- if ( !isset( $this->configByPartition[$partition] ) ) {
+ // Get the partition queue objects
+ foreach ( $this->partitionMap as $partition => $w ) {
+ if ( !isset( $params['configByPartition'][$partition] ) ) {
throw new MWException( "No configuration for partition '$partition'." );
}
$this->partitionQueues[$partition] = JobQueue::factory(
- $baseConfig + $this->configByPartition[$partition]
- );
+ $baseConfig + $params['configByPartition'][$partition] );
}
- // Get the ring of partitions to push job de-duplication information into
- $partitionsTry = array_diff_key(
- $this->getPartitionMap(),
- $this->partitionsNoPush
- ); // (partition => weight)
- $this->partitionRing = new HashRing( $partitionsTry );
+ // Get the ring of partitions to push jobs into
+ $this->partitionPushRing = new HashRing( $partitionPushMap );
// Aggregate cache some per-queue values if there are multiple partition queues
- $this->cache = $this->isFederated() ? wfGetMainCache() : new EmptyBagOStuff();
+ $this->cache = count( $this->partitionMap ) > 1 ? wfGetMainCache() : new EmptyBagOStuff();
}
protected function supportedOrders() {
return false;
}
} catch ( JobQueueError $e ) {
- wfDebugLog( 'exception', $e->getLogMessage() );
+ MWExceptionHandler::logException( $e );
}
}
try {
$count += $queue->$method();
} catch ( JobQueueError $e ) {
- wfDebugLog( 'exception', $e->getLogMessage() );
+ MWExceptionHandler::logException( $e );
}
}
if ( !count( $jobs ) ) {
return true; // nothing to do
}
-
- $partitionsTry = array_diff_key(
- $this->getPartitionMap(),
- $this->partitionsNoPush
- ); // (partition => weight)
-
+ // Local ring variable that may be changed to point to a new ring on failure
+ $partitionRing = $this->partitionPushRing;
// Try to insert the jobs and update $partitionsTry on any failures
- $jobsLeft = $this->tryJobInsertions( $jobs, $partitionsTry, $flags );
+ $jobsLeft = $this->tryJobInsertions( $jobs, $partitionRing, $flags );
if ( count( $jobsLeft ) ) { // some jobs failed to insert?
// Try to insert the remaning jobs once more, ignoring the bad partitions
- return !count( $this->tryJobInsertions( $jobsLeft, $partitionsTry, $flags ) );
- } else {
- return true;
+ return !count( $this->tryJobInsertions( $jobsLeft, $partitionRing, $flags ) );
}
+ return true;
}
/**
* @param array $jobs
- * @param array $partitionsTry
+ * @param HashRing $partitionRing
* @param integer $flags
* @return array List of Job object that could not be inserted
*/
- protected function tryJobInsertions( array $jobs, array &$partitionsTry, $flags ) {
- if ( !count( $partitionsTry ) ) {
- return $jobs; // can't insert anything
- }
-
+ protected function tryJobInsertions( array $jobs, HashRing &$partitionRing, $flags ) {
$jobsLeft = array();
- $partitionRing = new HashRing( $partitionsTry );
// Because jobs are spread across partitions, per-job de-duplication needs
// to use a consistent hash to avoid allowing duplicate jobs per partition.
// When inserting a batch of de-duplicated jobs, QOS_ATOMIC is disregarded.
foreach ( $uJobsByPartition as $partition => $jobBatch ) {
$queue = $this->partitionQueues[$partition];
try {
- $ok = $queue->doBatchPush( $jobBatch, $flags );
+ $ok = $queue->doBatchPush( $jobBatch, $flags | self::QOS_ATOMIC );
} catch ( JobQueueError $e ) {
$ok = false;
- wfDebugLog( 'exception', $e->getLogMessage() );
+ MWExceptionHandler::logException( $e );
}
if ( $ok ) {
$key = $this->getCacheKey( 'empty' );
$this->cache->set( $key, 'false', JobQueueDB::CACHE_TTL_LONG );
} else {
- unset( $partitionsTry[$partition] ); // blacklist partition
+ $partitionRing = $partitionRing->newWithoutLocation( $partition ); // blacklist
+ if ( !$partitionRing ) {
+ throw new JobQueueError( "Could not insert job(s), all partitions are down." );
+ }
$jobsLeft = array_merge( $jobsLeft, $jobBatch ); // not inserted
}
}
+
// Insert the jobs that are not de-duplicated into the queues...
foreach ( $nuJobBatches as $jobBatch ) {
- $partition = ArrayUtils::pickRandom( $partitionsTry );
- if ( $partition === false ) { // all partitions at 0 weight?
- $jobsLeft = array_merge( $jobsLeft, $jobBatch ); // not inserted
+ $partition = ArrayUtils::pickRandom( $partitionRing->getLocationWeights() );
+ $queue = $this->partitionQueues[$partition];
+ try {
+ $ok = $queue->doBatchPush( $jobBatch, $flags | self::QOS_ATOMIC );
+ } catch ( JobQueueError $e ) {
+ $ok = false;
+ MWExceptionHandler::logException( $e );
+ }
+ if ( $ok ) {
+ $key = $this->getCacheKey( 'empty' );
+ $this->cache->set( $key, 'false', JobQueueDB::CACHE_TTL_LONG );
} else {
- $queue = $this->partitionQueues[$partition];
- try {
- $ok = $queue->doBatchPush( $jobBatch, $flags );
- } catch ( JobQueueError $e ) {
- $ok = false;
- wfDebugLog( 'exception', $e->getLogMessage() );
- }
- if ( $ok ) {
- $key = $this->getCacheKey( 'empty' );
- $this->cache->set( $key, 'false', JobQueueDB::CACHE_TTL_LONG );
- } else {
- unset( $partitionsTry[$partition] ); // blacklist partition
- $jobsLeft = array_merge( $jobsLeft, $jobBatch ); // not inserted
+ $partitionRing = $partitionRing->newWithoutLocation( $partition ); // blacklist
+ if ( !$partitionRing ) {
+ throw new JobQueueError( "Could not insert job(s), all partitions are down." );
}
+ $jobsLeft = array_merge( $jobsLeft, $jobBatch ); // not inserted
}
}
return false;
}
- $partitionsTry = $this->getPartitionMap(); // (partition => weight)
+ $partitionsTry = $this->partitionMap; // (partition => weight)
while ( count( $partitionsTry ) ) {
$partition = ArrayUtils::pickRandom( $partitionsTry );
$job = $queue->pop();
} catch ( JobQueueError $e ) {
$job = false;
- wfDebugLog( 'exception', $e->getLogMessage() );
+ MWExceptionHandler::logException( $e );
}
if ( $job ) {
$job->metadata['QueuePartition'] = $partition;
protected function doIsRootJobOldDuplicate( Job $job ) {
$params = $job->getRootJobParams();
- $partitions = $this->partitionRing->getLocations( $params['rootJobSignature'], 2 );
+ $partitions = $this->partitionPushRing->getLocations( $params['rootJobSignature'], 2 );
try {
return $this->partitionQueues[$partitions[0]]->doIsRootJobOldDuplicate( $job );
- } catch ( MWException $e ) {
+ } catch ( JobQueueError $e ) {
if ( isset( $partitions[1] ) ) { // check fallback partition
return $this->partitionQueues[$partitions[1]]->doIsRootJobOldDuplicate( $job );
}
protected function doDeduplicateRootJob( Job $job ) {
$params = $job->getRootJobParams();
- $partitions = $this->partitionRing->getLocations( $params['rootJobSignature'], 2 );
+ $partitions = $this->partitionPushRing->getLocations( $params['rootJobSignature'], 2 );
try {
return $this->partitionQueues[$partitions[0]]->doDeduplicateRootJob( $job );
- } catch ( MWException $e ) {
+ } catch ( JobQueueError $e ) {
if ( isset( $partitions[1] ) ) { // check fallback partition
return $this->partitionQueues[$partitions[1]]->doDeduplicateRootJob( $job );
}
try {
$queue->doDelete();
} catch ( JobQueueError $e ) {
- wfDebugLog( 'exception', $e->getLogMessage() );
+ MWExceptionHandler::logException( $e );
}
}
}
try {
$queue->waitForBackups();
} catch ( JobQueueError $e ) {
- wfDebugLog( 'exception', $e->getLogMessage() );
+ MWExceptionHandler::logException( $e );
}
}
}
}
public function getCoalesceLocationInternal() {
- return "JobQueueFederated:wiki:" . $this->wiki;
+ return "JobQueueFederated:wiki:{$this->wiki}" .
+ sha1( serialize( array_keys( $this->partitionMap ) ) );
}
protected function doGetSiblingQueuesWithJobs( array $types ) {
$result = array();
foreach ( $this->partitionQueues as $queue ) {
- $nonEmpty = $queue->doGetSiblingQueuesWithJobs( $types );
- if ( is_array( $nonEmpty ) ) {
- $result = array_merge( $result, $nonEmpty );
- } else {
- return null; // not supported on all partitions; bail
+ try {
+ $nonEmpty = $queue->doGetSiblingQueuesWithJobs( $types );
+ if ( is_array( $nonEmpty ) ) {
+ $result = array_unique( array_merge( $result, $nonEmpty ) );
+ } else {
+ return null; // not supported on all partitions; bail
+ }
+ if ( count( $result ) == count( $types ) ) {
+ break; // short-circuit
+ }
+ } catch ( JobQueueError $e ) {
+ MWExceptionHandler::logException( $e );
}
}
- return array_values( array_unique( $result ) );
+ return array_values( $result );
}
protected function doGetSiblingQueueSizes( array $types ) {
$result = array();
foreach ( $this->partitionQueues as $queue ) {
- $sizes = $queue->doGetSiblingQueueSizes( $types );
- if ( is_array( $sizes ) ) {
- foreach ( $sizes as $type => $size ) {
- $result[$type] = isset( $result[$type] ) ? $result[$type] + $size : $size;
+ try {
+ $sizes = $queue->doGetSiblingQueueSizes( $types );
+ if ( is_array( $sizes ) ) {
+ foreach ( $sizes as $type => $size ) {
+ $result[$type] = isset( $result[$type] ) ? $result[$type] + $size : $size;
+ }
+ } else {
+ return null; // not supported on all partitions; bail
}
- } else {
- return null; // not supported on all partitions; bail
+ } catch ( JobQueueError $e ) {
+ MWExceptionHandler::logException( $e );
}
}
return $result;
}
}
- /**
- * @return Array Map of (partition name => weight)
- */
- protected function getPartitionMap() {
- $section = isset( $this->sectionsByWiki[$this->wiki] )
- ? $this->sectionsByWiki[$this->wiki]
- : 'default';
- if ( !isset( $this->partitionsBySection[$section] ) ) {
- throw new MWException( "No configuration for section '$section'." );
- }
- return $this->partitionsBySection[$section];
- }
-
- /**
- * @return bool The queue is actually split up across multiple queue partitions
- */
- protected function isFederated() {
- return ( count( $this->getPartitionMap() ) > 1 );
- }
-
/**
* @return string
*/
* to internalParse() which does all the real work.
*/
- global $wgUseTidy, $wgAlwaysUseTidy;
+ global $wgUseTidy, $wgAlwaysUseTidy, $wgShowHostnames;
$fname = __METHOD__ . '-' . wfGetCaller();
wfProfileIn( __METHOD__ );
wfProfileIn( $fname );
wfRunHooks( 'ParserLimitReportPrepare', array( $this, $this->mOutput ) );
$limitReport = "NewPP limit report\n";
+ if ( $wgShowHostnames ) {
+ $limitReport .= 'Parsed by ' . wfHostname() . "\n";
+ }
foreach ( $this->mOutput->getLimitReportData() as $key => $value ) {
if ( wfRunHooks( 'ParserLimitReportFormat',
array( $key, $value, &$limitReport, false, false )
# Show user names for /newbies as there may be different users.
# Note that we already excluded rows with hidden user names.
if ( $this->contribs == 'newbie' ) {
- $userlink = ' . . ' . Linker::userLink( $rev->getUser(), $rev->getUserText() );
+ $userlink = ' . . ' . $lang->getDirMark() . Linker::userLink( $rev->getUser(), $rev->getUserText() );
$userlink .= ' ' . $this->msg( 'parentheses' )->rawParams(
Linker::userTalkLink( $rev->getUser(), $rev->getUserText() ) )->escaped() . ' ';
} else {
'email-address-validity-invalid' => 'أدخل عنوان بريد إلكتروني صالح',
# User rights
-'userrights' => 'إدارة صÙ\84اØÙ\8aات اÙ\84Ù\85ستخدÙ\85',
+'userrights' => 'صلاحيات المستخدم',
'userrights-lookup-user' => 'أدِر مجموعات المستخدم',
'userrights-user-editname' => 'أدخل اسم مستخدم:',
'editusergroup' => 'عدل مجموعات المستخدم',
'sp-contributions-uploads' => 'مرفوعات',
'sp-contributions-logs' => 'سجلات',
'sp-contributions-talk' => 'نقاش',
-'sp-contributions-userrights' => 'إدارة صÙ\84اØÙ\8aات اÙ\84Ù\85ستخدÙ\85',
+'sp-contributions-userrights' => 'صلاحيات المستخدم',
'sp-contributions-blocked-notice' => 'هذا المستخدم ممنوع حاليا.
إن آخر مدخلة في سجل المنع موجودة أدناه كمرجع:',
'sp-contributions-blocked-notice-anon' => 'عنوان الأيبي هذا ممنوع حاليا.
'tags-tag' => 'اسم الوسم',
'tags-display-header' => 'الظهور في قوائم التغييرات',
'tags-description-header' => 'وصف كامل للمعنى',
+'tags-active-header' => 'نشط؟',
'tags-hitcount-header' => 'تغييرات موسومة',
+'tags-active-yes' => 'نعم',
+'tags-active-no' => 'لا',
'tags-edit' => 'عدل',
'tags-hitcount' => '{{PLURAL:$1|لا تغييرات|تغيير واحد|تغييران|$1 تغييرات|$1 تغييرا|$1 تغيير}}',
# All link text and link target definitions of links into project namespace that get used by other message strings, with the exception of user group pages (see grouppage).
'aboutsite' => 'За {{SITENAME}}',
'aboutpage' => 'Project:За {{SITENAME}}',
-'copyright' => 'Съдържанието е достъпно при условията на $1.',
+'copyright' => 'Ð\9eÑ\81вен ако не е поÑ\81оÑ\87ено дÑ\80Ñ\83го, Ñ\81ъдържанието е достъпно при условията на $1.',
'copyrightpage' => '{{ns:project}}:Авторски права',
'currentevents' => 'Текущи събития',
'currentevents-url' => 'Project:Текущи събития',
# General errors
'error' => 'Грешка',
'databaseerror' => 'Грешка при работа с базата от данни',
+'databaseerror-text' => 'Възникна грешка при заявката за базата данни.
+Това може да означава бъг в софтуера.',
'databaseerror-query' => 'Заявка: $1',
'databaseerror-function' => 'Функция: $1',
'databaseerror-error' => 'Грешка: $1',
'badarticleerror' => 'Действието не може да се изпълни върху страницата.',
'cannotdelete' => 'Указаната страница или файл "$1" не можа да бъде изтрит(а). Възможно е вече да е бил(а) изтрит(а) от някой друг.',
'cannotdelete-title' => 'Страницата „$1“ не може да бъде изтрита',
+'no-null-revision' => 'Не може да бъде създадена празна версия на страницата „$1“',
'badtitle' => 'Невалидно заглавие',
'badtitletext' => 'Желаното заглавие на страница е невалидно, празно или неправилна препратка към друго уики. Възможно е да съдържа знаци, които не са позволени в заглавия.',
'perfcached' => 'Следните данни са извлечени от склада и затова може да не отговарят на текущото състояние. В складираното копие {{PLURAL:$1|е допустим най-много един резултат|са допустими най-много $1 резултата}}.',
'customjsprotected' => 'Нямате права за редактиране на тази Джаваскрипт страница, защото тя съдържа чужди потребителски настройки.',
'mycustomcssprotected' => 'Нямате права за редактиране на тази CSS страница.',
'mycustomjsprotected' => 'Нямате права за редактиране на тази JavaScript страница.',
+'mypreferencesprotected' => 'Нямате права да редактирате настройките си.',
'ns-specialprotected' => 'Специалните страници не могат да бъдат редактирани.',
'titleprotected' => "Тази страница е била защитена срещу създаване от [[User:$1|$1]].
Посочената причина е ''$2''.",
'resetpass-wrong-oldpass' => 'Невалидна временна или текуща парола.
Възможно е вече успешно да сте сменили паролата си или да сте поискали нова временна парола.',
'resetpass-temp-password' => 'Временна парола:',
+'resetpass-abort-generic' => 'Промяната на паролата беше прекъсната от използвано разширение.',
# Special:PasswordReset
'passwordreset' => 'Възстановяване на парола',
+'passwordreset-text-many' => '{{PLURAL:$1|За възстановяване на паролата е необходимо да се попълни едно от полетата.}}',
'passwordreset-legend' => 'Възстановяване на парола',
'passwordreset-disabled' => 'Възстановяването на паролата е изключено в това уики.',
'passwordreset-username' => 'Потребителско име:',
'searchprofile-images-tooltip' => 'ফাইলের জন্য অনুসন্ধান',
'searchprofile-everything-tooltip' => 'সকল বিষয়বস্তু অনুসন্ধান করো (আলাপের পাতা সহ)',
'searchprofile-advanced-tooltip' => 'স্বনির্ধারিত নামস্থানে অনুসন্ধান করো',
-'search-result-size' => '$1 ({{PLURAL:$2|1 শব্দ|$2 শব্দসমূহ}})',
+'search-result-size' => '$1 ({{PLURAL:$2|১টি শব্দ|$2টি শব্দ}})',
'search-result-category-size' => '{{PLURAL: $1 | 1 সদস্য | $1 সদস্যবৃন্দ}} ({{PLURAL: $2 | 1 উপবিষয়শ্রেণীটি | $2 টি}}, {{PLURAL: $3 | 1 ফাইল | $3 ফাইল}})',
'search-result-score' => 'মিলেছে: $1%',
'search-redirect' => '(পুনর্নিদেশনা $1)',
# Random page
'randompage' => 'Цахууш нисйелла агӀо',
+# Random page in category
+'randomincategory' => 'Категори чу цахууш нийса елла агӀо',
+'randomincategory-selectcategory' => 'Категори чу цахууш нийса елла агӀона чу гӀо: $1 $2.',
+
# Random redirect
'randomredirect' => 'Цахууш нисделла дIасахьажор',
'tooltip-search' => 'Лаха иза дош',
'tooltip-search-go' => 'Билгала и санна цlе йолучу агlон чу дехьа вала',
'tooltip-search-fulltext' => 'Лаха агlонаш ше чулацамехь хlара йоза долуш',
-'tooltip-p-logo' => 'Коьрта агIо',
-'tooltip-n-mainpage' => 'Ð\94еÑ\85Ñ\8cавалаÑ\80 коÑ\8cÑ\80Ñ\82а агlонÑ\87Ñ\83',
-'tooltip-n-mainpage-description' => 'Ð\94еÑ\85Ñ\8cавалаÑ\80 коÑ\8cÑ\80Ñ\82а агlонÑ\87Ñ\83',
+'tooltip-p-logo' => 'Коьрта агӀона дехьа гӀо',
+'tooltip-n-mainpage' => 'Ð\9aоÑ\8cÑ\80Ñ\82а агÓ\80она деÑ\85Ñ\8cа гÓ\80о',
+'tooltip-n-mainpage-description' => 'Ð\9aоÑ\8cÑ\80Ñ\82а агÓ\80она деÑ\85Ñ\8cа гÓ\80о',
'tooltip-n-portal' => 'Оцу кхолламах, мичахь хlу йу лаьташ а хlудалур ду шуьга',
'tooltip-n-currentevents' => 'Дlаоьхуш болу хаамашна могlам',
'tooltip-n-recentchanges' => 'Тlаьххьаралера хийцаман могlам',
'dberr-outofdate' => 'Хьуна хаалахь, цуьна йолу меттиг хила мега тишйелла черахь.',
# HTML forms
+'htmlform-invalid-input' => 'Ахьа яздинчу цхьан дакхано гӀалат далина',
'htmlform-submit' => 'ДӀадахьийта',
'htmlform-reset' => 'Цаоьшу хийцамаш',
'htmlform-selectorother-other' => 'Кхин',
'deleteotherreason' => 'Rheswm arall:',
'deletereasonotherlist' => 'Rheswm arall',
'deletereason-dropdown' => "*Rhesymau arferol dros ddileu
-** Ar gais yr awdur
+** Sbam
+** Fandaliaeth
** Torri'r hawlfraint
-** Fandaliaeth",
+** Ar gais yr awdur
+** Ailgyfeiriad wedi torri",
'delete-edit-reasonlist' => 'Golygu rhestr y rhesymau dros ddileu',
'delete-toobig' => "Cafwyd dros $1 {{PLURAL:$1|o olygiadau}} i'r dudalen hon.
Cyfyngwyd ar y gallu i ddileu tudalennau sydd wedi eu golygu cymaint â hyn, er mwyn osgoi amharu ar weithrediad databas {{SITENAME}} yn ddamweiniol.",
'revdelete-uname-unhid' => 'Benutzername freigegeben',
'revdelete-restricted' => 'Einschränkungen gelten auch für Administratoren',
'revdelete-unrestricted' => 'Einschränkungen für Administratoren aufgehoben',
-'logentry-move-move' => '$1 {{GENDER:$2|verschob}} Seite $3 nach $4',
-'logentry-move-move-noredirect' => '$1 {{GENDER:$2|verschob}} Seite $3 nach $4, ohne dabei eine Weiterleitung anzulegen',
-'logentry-move-move_redir' => '$1 {{GENDER:$2|verschob}} Seite $3 nach $4 und überschrieb dabei eine Weiterleitung',
-'logentry-move-move_redir-noredirect' => '$1 {{GENDER:$2|verschob}} Seite $3 nach $4 und überschrieb dabei eine Weiterleitung, ohne selbst eine Weiterleitung anzulegen',
-'logentry-patrol-patrol' => '$1 {{GENDER:$2|markierte}} Version $4 von Seite $3 als kontrolliert',
-'logentry-patrol-patrol-auto' => '$1 {{GENDER:$2|markierte}} automatisch Version $4 von Seite $3 als kontrolliert',
+'logentry-move-move' => '$1 {{GENDER:$2|verschob}} die Seite $3 nach $4',
+'logentry-move-move-noredirect' => '$1 {{GENDER:$2|verschob}} die Seite $3 nach $4, ohne dabei eine Weiterleitung anzulegen',
+'logentry-move-move_redir' => '$1 {{GENDER:$2|verschob}} die Seite $3 nach $4 und überschrieb dabei eine Weiterleitung',
+'logentry-move-move_redir-noredirect' => '$1 {{GENDER:$2|verschob}} die Seite $3 nach $4 und überschrieb dabei eine Weiterleitung, ohne selbst eine Weiterleitung anzulegen',
+'logentry-patrol-patrol' => '$1 {{GENDER:$2|markierte}} die Version $4 von Seite $3 als kontrolliert',
+'logentry-patrol-patrol-auto' => '$1 {{GENDER:$2|markierte}} automatisch die Version $4 von Seite $3 als kontrolliert',
'logentry-newusers-newusers' => 'Benutzerkonto $1 wurde {{GENDER:$2|erstellt}}',
'logentry-newusers-create' => 'Benutzerkonto $1 wurde {{GENDER:$2|erstellt}}',
'logentry-newusers-create2' => 'Benutzerkonto $3 wurde von $1 {{GENDER:$2|erstellt}}',
'articlepage' => 'View content page',
'talk' => 'Discussion',
'views' => 'Views',
-'toolbox' => 'Toolbox',
+'toolbox' => 'Tools',
'userpage' => 'View user page',
'projectpage' => 'View project page',
'imagepage' => 'View file page',
'right-editmyprivateinfo' => 'دادههای خصوصی خود را ویرایش کنید (مانند رایانشانی و نام واقعی)',
'right-editmyoptions' => 'ترجیحات خود را ویرایش',
'right-rollback' => 'واگردانی سریع ویرایشهای آخرین کاربری که یک صفحه را ویرایش کردهاست',
-'right-markbotedits' => 'علامت زدن ویرایشهای واگردانی شده به عنوان ویرایش ربات',
+'right-markbotedits' => 'علامتزدن ویرایشهای واگردانیشده بهعنوان ویرایش ربات',
'right-noratelimit' => 'تاثیر نپذیرفتن از محدودیت سرعت',
'right-import' => 'وارد کردن صفحه از ویکیهای دیگر',
'right-importupload' => 'وارد کردن صفحه از طریق بارگذاری پرونده',
'movenotallowedfile' => 'شما اجازهٔ انتقال پروندهها را ندارید.',
'cant-move-user-page' => 'شما اجازه ندارید صفحههای کاربری سرشاخه را انتقال دهید.',
'cant-move-to-user-page' => 'شما اجازه ندارید که یک صفحه را به یک صفحهٔ کاربر انتقال دهید (به استثنای زیر صفحههای کاربری).',
-'newtitle' => 'به عنوان جدید',
+'newtitle' => 'بهعنوان جدید',
'move-watch' => 'پیگیری صفحههای مبدأ و مقصد',
'movepagebtn' => 'صفحه منتقل شود',
'pagemovedsub' => 'انتقال با موفقیت انجام شد',
** Vandalisme
** Violation des droits d’auteur
** Demande de l’auteur
-** Redirection erronée',
+** Redirection cassée',
'delete-edit-reasonlist' => 'Modifier les motifs de suppression de page',
'delete-toobig' => 'Cette page possède un historique important de modifications, dépassant $1 version{{PLURAL:$1||s}}.
La suppression de telles pages a été restreinte pour prévenir des perturbations accidentelles de {{SITENAME}}.',
'specialpage' => 'विशेष पृष्ठ',
'personaltools' => 'वैयक्तिक औज़ार',
'postcomment' => 'नया अनुभाग',
-'articlepage' => 'लà¥\87à¤\96 देखें',
+'articlepage' => 'सामà¤\97à¥\8dरà¥\80 पà¥\83षà¥\8dठदेखें',
'talk' => 'चर्चा',
'views' => 'दर्शाव',
'toolbox' => 'साधन पेटी',
'aboutsite' => '{{SITENAME}} के बारे में',
'aboutpage' => 'Project:परिचय',
'copyright' => 'उपलब्ध सामग्री $1 के अधीन है जब तक अलग से उल्लेख ना किया गया हो।',
-'copyrightpage' => '{{ns:project}}:सरà¥\8dवाधिà¤\95ार',
+'copyrightpage' => '{{ns:project}}:à¤\95à¥\89पà¥\80राà¤\87à¤\9f',
'currentevents' => 'हाल की घटनाएँ',
'currentevents-url' => 'Project:हाल की घटनाएँ',
'disclaimers' => 'अस्वीकरण',
'hidetoc' => 'छिपाएँ',
'collapsible-collapse' => 'छोटा करें',
'collapsible-expand' => 'विस्तार करें',
-'thisisdeleted' => '$1 दà¥\87à¤\96à¥\87à¤\82 या बदलà¥\87à¤\82?',
+'thisisdeleted' => '$1 दà¥\87à¤\96à¥\87à¤\82 या वापिस लाà¤\8fà¤\81?',
'viewdeleted' => '$1 दिखायें?',
'restorelink' => '{{PLURAL:$1|एक हटाया हुआ|$1 हटाये हुए}} बदलाव',
'feedlinks' => 'फ़ीड:',
'prefs-personal' => 'सदस्य व्यक्तिरेखा',
'prefs-rc' => 'हाल में हुए बदलाव',
'prefs-watchlist' => 'ध्यानसूची',
-'prefs-watchlist-days' => 'ध्यानसूचीमें दिखाने के दिन:',
+'prefs-watchlist-days' => 'ध्यानसूची में दिखाने के दिन:',
'prefs-watchlist-days-max' => 'अधिकतम $1 {{PLURAL:$1|दिन}}',
'prefs-watchlist-edits' => 'बढ़ाई हुई ध्यानसूची में दिखाने हेतु अधिकतम बदलाव:',
-'prefs-watchlist-edits-max' => 'à¤\85धिà¤\95तम सà¤\82à¤\96à¥\8dया: १०००',
+'prefs-watchlist-edits-max' => 'à¤\85धिà¤\95तम सà¤\82à¤\96à¥\8dया: à¤\8fà¤\95 हà¤\9c़ार',
'prefs-watchlist-token' => 'ध्यानसूची टोकन',
'prefs-misc' => 'अन्य',
'prefs-resetpass' => 'कूटशब्द बदलें',
'newuserlogpagetext' => 'यह सदस्य खातों के निर्माण का लॉग है।',
# User rights log
-'rightslog' => 'सदसà¥\8dय à¤\85धिà¤\95ार सà¥\82à¤\9aà¥\80',
+'rightslog' => 'सदसà¥\8dय à¤\85धिà¤\95ार लà¥\89à¤\97',
'rightslogtext' => 'यह सदस्य अधिकारों में हुए बदलावों की सूची है।',
# Associated actions - in the sentence "You do not have permission to X"
'action-block' => 'इस सदस्य को संपादन करने से ब्लॉक करने',
'action-protect' => 'इस पृष्ठ के सुरक्षा स्तर बदलने',
'action-rollback' => 'किसी पृष्ठ का अंतिम सम्पादन करने वाले सदस्य के सम्पादन वापिस लेने',
-'action-import' => 'à¤\95िसà¥\80 à¤\94र विà¤\95ि सà¥\87 यह पà¥\83षà¥\8dठà¤\86यात à¤\95रनà¥\87',
+'action-import' => 'किसी और विकि से पृष्ठ आयात करने',
'action-importupload' => 'फ़ाइल अपलोड द्वारा यह पृष्ठ आयात करे',
'action-patrol' => 'अन्य सदस्यों के सम्पादन परीक्षित करने',
'action-autopatrol' => 'अपने सम्पादन स्वचालित रूप से परीक्षित करने',
'withoutinterwiki' => 'बिना अंतरविकि कड़ियों वाले पृष्ठ',
'withoutinterwiki-summary' => 'निम्न पृष्ठ अन्य भाषाओं के अवतरणों से नहीं जुड़ते हैं।',
-'withoutinterwiki-legend' => 'à¤\89पपद',
+'withoutinterwiki-legend' => 'à¤\89पसरà¥\8dà¤\97',
'withoutinterwiki-submit' => 'दिखायें',
'fewestrevisions' => 'सबसे कम अवतरणों वाले पृष्ठ',
'listusers' => 'सदस्यसूची',
'listusers-editsonly' => 'केवल संपादन कर चुके सदस्य दिखाएँ',
'listusers-creationsort' => 'निर्माण तिथि के आधार पर क्रमांकन करें',
-'usereditcount' => '$1 {{PLURAL:$1|सà¤\82पादन|सà¤\82पादन}}',
+'usereditcount' => '$1 {{PLURAL:$1|समà¥\8dपादन}}',
'usercreated' => '$1 को $2 बजे बनाया गया, सदस्यनाम $3 है',
'newpages' => 'नए पृष्ठ',
'newpages-username' => 'सदस्यनाम:',
# Special:Log
'specialloguserlabel' => 'कर्ता:',
-'speciallogtitlelabel' => 'प्रयोजन (शीर्षक):',
+'speciallogtitlelabel' => 'प्रयोजन (शीर्षक अथवा सदस्यनाम):',
'log' => 'लॉग',
'all-logs-page' => 'सभी सार्वजनिक लॉग',
'alllogstext' => '{{SITENAME}} की सभी उपलब्ध लॉगों की प्रविष्टियों का मिला-जुला प्रदर्शन।
'deleteotherreason' => 'अन्य/अतिरिक्त कारण:',
'deletereasonotherlist' => 'अन्य कारण',
'deletereason-dropdown' => '*हटाने के सामान्य कारण
-** लेखक की बिनती
+** स्पैम
+** बर्बरता
** कॉपीराइट उल्लंघन
-** बर्बरता',
+** लेखक का अनुरोध
+** टूटा अनुप्रेषण',
'delete-edit-reasonlist' => 'हटाने के कारण संपादित करें',
'delete-toobig' => 'इस पृष्ठ का संपादन इतिहास $1 से अधिक {{PLURAL:$1|अवतरण}} होने की वजह से बहुत बड़ा है।
{{SITENAME}} के अनपेक्षित रूप से बंद होने से रोकने के लिये ऐसे पृष्ठों को हटाने की अनुमति नहीं है।',
इस पृष्ठ का अन्तिम संपादन [[User:$3|$3]] ([[User talk:$3|वार्ता]]{{int:pipe-separator}}[[Special:Contributions/$3|{{int:contribslink}}]]) ने किया है।',
'editcomment' => "संपादन सारांश था: \"''\$1''\"।",
'revertpage' => '[[Special:Contributions/$2|$2]] ([[User talk:$2|Talk]]) के संपादनों को हटाकर [[User:$1|$1]] के अन्तिम अवतरण को पूर्ववत किया',
-'revertpage-nouser' => '(सदसà¥\8dय नाम हà¤\9fाया à¤\97या हà¥\88) दà¥\8dवारा à¤\95िà¤\8f à¤\97à¤\8f सà¤\82पादन à¤\95à¥\8b वापिस पà¥\81रानà¥\80 सà¥\8dथिति मà¥\87à¤\82 ला à¤\95र à¤\87सà¤\95à¥\87 पहलà¥\87 à¤\95à¥\87 [[User:$1|$1]] दà¥\8dवारा बनà¥\87 à¤\85वतरण à¤\95à¥\8b फिर सà¥\87 ताà¤\9c़ा à¤\85वतरण बनाया।',
+'revertpage-nouser' => '(सदसà¥\8dय नाम हà¤\9fाया à¤\97या हà¥\88) à¤\95à¥\87 सà¤\82पादनà¥\8bà¤\82 à¤\95à¥\8b हà¤\9fाà¤\95र {{GENDER:$1|[[User:$1|$1]]}} à¤\95à¥\87 à¤\85नà¥\8dतिम à¤\85वतरण à¤\95à¥\8b पà¥\82रà¥\8dववत à¤\95िया।',
'rollback-success' => '$1 के संपादन हटाए;
$2 द्वारा संपादित अन्तिम अवतरण को पुनर्स्थापित किया।',
**अफलदायी सम्पादन युद्ध
**अधिक यातायात वाला पृष्ठ',
'protect-edit-reasonlist' => 'सुरक्षा के कारण बदलें',
-'protect-expiry-options' => '१ à¤\98à¤\82à¤\9fा:1 hour,१ दिन:1 day,१ सपà¥\8dताह:1 week,२ सपà¥\8dताह:2 weeks,१ महà¥\80ना:1 month,३ महà¥\80नà¥\87:3 months,६ महà¥\80नà¥\87:6 months,१ साल:1 year,हमेशा के लिए:infinite',
+'protect-expiry-options' => 'à¤\8fà¤\95 à¤\98à¤\82à¤\9fा:1 hour,à¤\8fà¤\95 दिन:1 day,à¤\8fà¤\95 सपà¥\8dताह:1 week,दà¥\8b सपà¥\8dताह:2 weeks,à¤\8fà¤\95 महà¥\80ना:1 month,तà¥\80न महà¥\80नà¥\87:3 months,à¤\9bà¤\83 महà¥\80नà¥\87:6 months,à¤\8fà¤\95 साल:1 year,हमेशा के लिए:infinite',
'restriction-type' => 'अधिकार:',
'restriction-level' => 'सुरक्षा-स्तर:',
'minimum-size' => 'न्यूनतम आकार',
'restriction-level-all' => 'कोई भी स्तर',
# Undelete
-'undelete' => 'हà¤\9fाया पृष्ठ देखें',
+'undelete' => 'हà¤\9fाà¤\8f पृष्ठ देखें',
'undeletepage' => 'हटाए गए पृष्ठ देखें और पुनर्स्थापित करें',
'undeletepagetitle' => "'''नीचे [[:$1|$1]] के हटाए गए अवतरण दर्शाए गये हैं।'''",
'viewdeletedpage' => 'हटाए गए पृष्ठ देखें',
'undelete-fieldset-title' => 'अवतरण पुरानी स्थिति पर लाएँ',
'undeleteextrahelp' => "पृष्ठ का संपूर्ण इतिहास वापस लाने के लिए सभी बक्सों से सही का निशान हटा दें और '''''{{int:undeletebtn}}''''' पर क्लिक करें।
चुनिंदा इतिहास को वापस लाने के लिए उन अवतरणों के बगल के बक्सों पर सही का निशान लगाएँ और '''''{{int:undeletebtn}}''''' पर क्लिक करें।",
-'undeleterevisions' => '$1 {{PLURAL:$1|अवतरण}} लेखागार में हैं',
+'undeleterevisions' => '$1 अवतरण लेखागार में {{PLURAL:$1|है|हैं}}',
'undeletehistory' => 'यदि आप पृष्ठ को पुनर्स्थापित करते हैं तो सभी अवतरण इतिहास में पुनर्स्थापित हो जायेंगे।
हटाने के बाद यदि एक नया पृष्ठ उसी नाम से बनाया गया है तो पुनर्स्थापित अवतरण पिछले इतिहास में दर्शित होंगे।',
'undeleterevdel' => 'यदि पुनर्स्थापन के फलस्वरूप शीर्ष पृष्ठ या फ़ाइल अवतरण आंशिक रूप से मिट सकता है, तो इसे नहीं किया जायेगा।
ऐसी स्थिति में, आपको नवीनतम मिटाए गए अवतरण को बिना सही के निशान लगाये हुए या बिना छुपाये रखना होगा।',
-'undeletehistorynoadmin' => 'यह पà¥\83षà¥\8dठनिà¤\95ाल दिया गया है।
-निà¤\95ालà¥\87 à¤\9cानà¥\87 à¤\95ा à¤\95ारन नà¥\80à¤\9aà¥\87 साराà¤\82श मà¥\87à¤\82 दिया à¤\97या हà¥\88, à¤\94र साथ हà¥\80 à¤\89न सदसà¥\8dयà¥\8bà¤\82 à¤\95à¥\87 बारà¥\87 मà¥\87à¤\82 विसà¥\8dतार à¤à¥\80 दिया à¤\97या हà¥\88, à¤\9cिनà¥\8dहà¥\8bà¤\82नà¥\87 निà¤\95ालà¥\87 à¤\9cानà¥\87 सà¥\87 पहलà¥\87 à¤\87स पà¥\83षà¥\8dठà¤\95à¥\8b सà¤\82पादित à¤\95िया हà¥\88।
-à¤\87न हà¤\9fायà¥\87 à¤\97à¤\8f à¤\85वतरणà¥\8bà¤\82 à¤\95à¥\87 विदà¥\8dयमान विषय वसà¥\8dतà¥\81 à¤\95à¥\87वल पà¥\8dरशासकों को ही उपलब्ध है।',
+'undeletehistorynoadmin' => 'यह पà¥\83षà¥\8dठहà¤\9fा दिया गया है।
+हà¤\9fाà¤\8f à¤\9cानà¥\87 à¤\95ा à¤\95ारन नà¥\80à¤\9aà¥\87 साराà¤\82श मà¥\87à¤\82 दिया à¤\97या हà¥\88, à¤\94र साथ हà¥\80 à¤\89न सदसà¥\8dयà¥\8bà¤\82 à¤\95à¥\87 बारà¥\87 मà¥\87à¤\82 विसà¥\8dतार à¤à¥\80 दिया à¤\97या हà¥\88, à¤\9cिनà¥\8dहà¥\8bà¤\82नà¥\87 हà¤\9fाà¤\8f à¤\9cानà¥\87 सà¥\87 पहलà¥\87 à¤\87स पà¥\83षà¥\8dठà¤\95à¥\8b सà¤\82पादित à¤\95िया था।
+à¤\87न हà¤\9fायà¥\87 à¤\97à¤\8f à¤\85वतरणà¥\8bà¤\82 à¤\95ा पाठà¤\95à¥\87वल पà¥\8dरबà¤\82धकों को ही उपलब्ध है।',
'undelete-revision' => '$1 ($4 को $5 बजे $3 द्वारा बनाया गया) का मिटाया हुआ संस्करण:',
'undeleterevision-missing' => 'अमान्य अथवा अनुपस्थित अवतरण।
-या तà¥\8b à¤\86प à¤\97़लत समà¥\8dपरà¥\8dà¤\95 पà¥\8dरयà¥\8bà¤\97 à¤\95र रहà¥\87 हà¥\88à¤\82, या यह à¤\85वतरण पà¥\81नरà¥\8dसà¥\8dथापित à¤\95िया à¤\9cा à¤\9aà¥\81à¤\95ा हà¥\88, à¤\85थवा à¤\87सà¥\87 लà¥\87à¤\96ाà¤\97ार सà¥\87 निà¤\95ाल दिया गया है।',
-'undelete-nodiff' => 'पà¥\81रान à¤\85वतरण नहà¥\80à¤\82 हà¥\88à¤\82।',
+या तà¥\8b à¤\86प à¤\97़लत à¤\95ड़à¥\80 पà¥\8dरयà¥\8bà¤\97 à¤\95र रहà¥\87 हà¥\88à¤\82, या यह à¤\85वतरण पà¥\81नरà¥\8dसà¥\8dथापित à¤\95िया à¤\9cा à¤\9aà¥\81à¤\95ा हà¥\88, à¤\85थवा à¤\87सà¥\87 लà¥\87à¤\96ाà¤\97ार सà¥\87 हà¤\9fा दिया गया है।',
+'undelete-nodiff' => 'à¤\95à¥\8bà¤\88 पà¥\81राना à¤\85वतरण नहà¥\80à¤\82 मिला।',
'undeletebtn' => 'वापस ले आयें',
-'undeletelink' => 'दà¥\87à¤\96à¥\87à¤\82/पà¥\81रानà¥\80 सà¥\8dथिति पर लाà¤\8fà¤\81',
+'undeletelink' => 'दà¥\87à¤\96à¥\87à¤\82/पà¥\81नरà¥\8dसà¥\8dथापित à¤\95रà¥\87à¤\82',
'undeleteviewlink' => 'देखें',
'undeletereset' => 'पूर्ववत करें',
'undeleteinvert' => 'चुनाव उलटें',
-'undeletecomment' => 'à¤\9fिपà¥\8dपणà¥\80 हà¤\9fाना',
-'undeletedrevisions' => '{{PLURAL:$1|à¤\8fà¤\95 रà¥\82पानà¥\8dतर वापस लाया à¤\97या|$1 रà¥\82पानà¥\8dतर वापस लायà¥\87 à¤\97यà¥\87}} हà¥\88',
-'undeletedrevisions-files' => '{{PLURAL:$1|1 à¤\85वतरण|$1 à¤\85वतरण}} à¤\94र {{PLURAL:$2|1 फ़ाà¤\88ल|$2 फ़ाà¤\87लà¥\87à¤\82}} पà¥\81नरà¥\8dसà¥\8dथापित à¤\95र दियà¥\87ं',
-'undeletedfiles' => '{{PLURAL:$1|1 फ़ाà¤\88ल|$1 फ़ाà¤\88लें}} पुनर्स्थापित',
+'undeletecomment' => 'à¤\95ारण:',
+'undeletedrevisions' => '{{PLURAL:$1|à¤\8fà¤\95 à¤\85वतरण पà¥\81नरà¥\8dसà¥\8dथापित à¤\95िया|$1 à¤\85वतरण पà¥\81नरà¥\8dसà¥\8dथापित à¤\95ियà¥\87}}',
+'undeletedrevisions-files' => '{{PLURAL:$1|1 à¤\85वतरण|$1 à¤\85वतरण}} à¤\94र {{PLURAL:$2|1 फ़ाà¤\87ल|$2 फ़ाà¤\87लà¥\87à¤\82}} पà¥\81नरà¥\8dसà¥\8dथापित à¤\95र दà¥\80ं',
+'undeletedfiles' => '{{PLURAL:$1|1 फ़ाà¤\87ल|$1 फ़ाà¤\87लें}} पुनर्स्थापित',
'cannotundelete' => 'पुनर्स्थापित नहीं कर सके:
$1',
'undeletedpage' => "'''$1 को पुनर्स्थापित कर दिया गया है'''
हाल में हटाये गये तथा पुनर्स्थापित किये गए पन्नों की जानकारी के लिये [[Special:Log/delete|हटाने की लॉग]] देखें।",
-'undelete-header' => 'हाल में हटाये गये पृष्ठ देखने के लियें [[Special:Log/delete|हटाने की सूची]] देखें।',
+'undelete-header' => 'हाल में हटाये गये पृष्ठ देखने के लिये [[Special:Log/delete|हटाने का लॉग]] देखें।',
'undelete-search-title' => 'हटाये गये पृष्ठ खोजें',
'undelete-search-box' => 'हटाये गये पृष्ठ खोजें',
'undelete-search-prefix' => 'शुरूआती शब्द अनुसार पृष्ठ खोजें:',
'undelete-search-submit' => 'खोजें',
-'undelete-no-results' => 'हà¤\9fायà¥\87à¤\82 à¤\97यà¥\87à¤\82 पनà¥\8dनà¥\8bà¤\82à¤\95à¥\87 à¤\86रà¥\8dà¤\9aिवà¥\8dहमà¥\87à¤\82 मà¥\87ल à¤\96ानà¥\87 वालà¥\87 पà¥\83षà¥\8dठमिलà¥\87 नहà¥\80à¤\82।',
-'undelete-filename-mismatch' => '$1 समयà¤\95à¥\87 फ़ाà¤\87लà¤\95à¥\87 हà¤\9fायà¥\87 à¤\97यà¥\87 à¤\85वतरणà¤\95à¥\8b पà¥\81नरà¥\8dसà¥\8dथापित नहà¥\80à¤\82 à¤\95िया à¤\9cा सà¤\95ता: फ़ाà¤\88ल का नाम मेल नहीं खाता',
-'undelete-bad-store-key' => '$1 समयà¤\95ा फ़ाà¤\88ल à¤\85वतरण पà¥\81नरà¥\8dसà¥\8dथापित नहà¥\80à¤\82 à¤\95र सà¤\95तà¥\87à¤\82 हà¥\88à¤\82: हà¤\9fानà¥\87 सà¥\87 पहलà¥\87 फ़ाà¤\88ल à¤\85सà¥\8dतितà¥\8dवमà¥\87à¤\82 नहीं थी।',
-'undelete-cleanup-error' => 'à¤\87सà¥\8dतà¥\87मालमà¥\87à¤\82 न लाà¤\88 à¤\97à¤\88 "$1" à¤\86रà¥\8dà¤\9aिवà¥\8dह फ़ाà¤\88ल हà¤\9fानà¥\87 मà¥\87à¤\82 समसà¥\8dया हà¥\81à¤\88 हà¥\88à¤\82।',
-'undelete-missing-filearchive' => 'सिà¤\9aिà¤\95ा पà¥\81रालà¥\87à¤\96 à¤\95à¥\8dरमाà¤\82à¤\95 $1 à¤\95à¥\8b पà¥\81नरà¥\8dसà¥\8dथापित à¤\95रनà¥\87 मà¥\87à¤\82 à¤\85सà¤\95à¥\8dषम हà¥\88à¤\82, à¤\95à¥\8dयà¥\8bà¤\82à¤\95ि यह à¤\86à¤\81à¤\95ड़ाà¤\95à¥\8bष में उपलब्ध नहीं है।
+'undelete-no-results' => 'हà¤\9fाà¤\8f à¤\97à¤\8f पà¥\83षà¥\8dठà¥\8bà¤\82 à¤\95à¥\87 लà¥\87à¤\96ाà¤\97ार मà¥\87à¤\82 मà¥\87ल à¤\96ातà¥\87 à¤\95à¥\8bà¤\88 पà¥\83षà¥\8dठनहà¥\80à¤\82 मिलà¥\87।',
+'undelete-filename-mismatch' => '$1 à¤\95à¥\87 फ़ाà¤\87ल à¤\95à¥\87 हà¤\9fायà¥\87 à¤\97यà¥\87 à¤\85वतरण à¤\95à¥\8b पà¥\81नरà¥\8dसà¥\8dथापित नहà¥\80à¤\82 à¤\95िया à¤\9cा सà¤\95ता: फ़ाà¤\87ल का नाम मेल नहीं खाता',
+'undelete-bad-store-key' => '$1 à¤\95ा फ़ाà¤\87ल à¤\85वतरण पà¥\81नरà¥\8dसà¥\8dथापित नहà¥\80à¤\82 à¤\95र सà¤\95तà¥\87 हà¥\88à¤\82: हà¤\9fानà¥\87 सà¥\87 पहलà¥\87 à¤à¥\80 फ़ाà¤\87ल मà¥\8cà¤\9cà¥\82द नहीं थी।',
+'undelete-cleanup-error' => 'पà¥\81रालà¥\87à¤\96 मà¥\87à¤\82 सà¥\87 à¤\85पà¥\8dरयà¥\81à¤\95à¥\8dत फ़ाà¤\87ल "$1" हà¤\9fानà¥\87 मà¥\87à¤\82 तà¥\8dरà¥\81à¤\9fि।',
+'undelete-missing-filearchive' => 'फ़ाà¤\87ल पà¥\81रालà¥\87à¤\96 à¤\86à¤\88॰डà¥\80 $1 à¤\95à¥\8b पà¥\81नरà¥\8dसà¥\8dथापित à¤\95रनà¥\87 मà¥\87à¤\82 à¤\85सà¤\95à¥\8dषम हà¥\88à¤\82, à¤\95à¥\8dयà¥\8bà¤\82à¤\95ि यह डाà¤\9fाबà¥\87स में उपलब्ध नहीं है।
या ऐसा भी हो सकता है कि इसे पहले से ही पुनर्स्थापित किया जा चुका हो।',
-'undelete-error' => 'पà¥\83षà¥\8dठà¤\85विलà¥\8bपन में त्रुटि',
-'undelete-error-short' => 'फ़ाà¤\88ल पà¥\81नरà¥\8dसà¥\8dथापित à¤\95रनà¥\87 मà¥\87à¤\82 समसà¥\8dया: $1',
-'undelete-error-long' => 'फ़ाà¤\88ल पà¥\81नरà¥\8dसà¥\8dथापित à¤\95रनà¥\87 मà¥\87à¤\82 à¤\86à¤\88 हà¥\81à¤\88 समसà¥\8dयाà¤\8fà¤\82:
+'undelete-error' => 'पà¥\83षà¥\8dठपà¥\81नरà¥\8dसà¥\8dथापन में त्रुटि',
+'undelete-error-short' => 'फ़ाà¤\87ल पà¥\81नरà¥\8dसà¥\8dथापन मà¥\87à¤\82 तà¥\8dरà¥\81à¤\9fि: $1',
+'undelete-error-long' => 'फ़ाà¤\87ल पà¥\81नरà¥\8dसà¥\8dथापन मà¥\87à¤\82 à¤\86à¤\88 तà¥\8dरà¥\81à¤\9fियाà¤\81:
$1',
'undelete-show-file-confirm' => 'क्या आप वाकई फ़ाइल "<nowiki>$1</nowiki>" के $2 को $3 बजे बने, हटाए जा चुके अवतरण को देखना चाहते हैं?',
'ipbenableautoblock' => 'इस सदस्यद्वारा इस्तेमाल किया गया आखिरी आईपी एड्रेस और यहां से आगे इस सदस्य द्वारा इस्तेमालमें लाये जाने वाले सभी एड्रेस ब्लॉक करें।',
'ipbsubmit' => 'इस सदस्य को और बदलाव करने से रोकें',
'ipbother' => 'अन्य समय:',
-'ipboptions' => '२ à¤\98à¤\82à¤\9fà¥\87:2 hours,१ दिन:1 day,३ दिन:3 days,१ हफà¥\8dता:1 week,२ हफà¥\8dतà¥\87:2 weeks,१ महिना:1 month,३ महिनà¥\87:3 months,६ महिनà¥\87:6 months,१ साल:1 year,हमेशा के लिये:infinite',
+'ipboptions' => 'दà¥\8b à¤\98à¤\82à¤\9fà¥\87:2 hours,à¤\8fà¤\95 दिन:1 day,तà¥\80न दिन:3 days,à¤\8fà¤\95 सपà¥\8dताह:1 week,दà¥\8b सपà¥\8dताह:2 weeks,à¤\8fà¤\95 महà¥\80ना:1 month,तà¥\80न महà¥\80नà¥\87:3 months,à¤\9bà¤\83 महà¥\80नà¥\87:6 months,à¤\8fà¤\95 साल:1 year,हमेशा के लिये:infinite',
'ipbotheroption' => 'अन्य',
'ipbotherreason' => 'अन्य/दूसरा कारण:',
'ipbhidename' => 'संपादन व सूचियों से सदस्य नाम छिपाएँ',
'tooltip-ca-protect' => 'इस पृष्ठको सुरक्षित किजीयें',
'tooltip-ca-unprotect' => 'इस पृष्ठ की सुरक्षा बदलें ।',
'tooltip-ca-delete' => 'इस पृष्ठ को हटाएं',
-'tooltip-ca-undelete' => 'इस पृष्ठको हटाने से पहले किये गये बदलाव पुनर्स्थापित करें',
+'tooltip-ca-undelete' => 'इस पृष्ठ को हटाने से पहले किये गये बदलाव पुनर्स्थापित करें',
'tooltip-ca-move' => 'यह पृष्ठ स्थानांतरित करें',
'tooltip-ca-watch' => 'इस पृष्ठ को अपनी ध्यानसूची में डालें',
'tooltip-ca-unwatch' => 'यह पृष्ठ अपने ध्यानसूचीसे हटाएं',
'underline-default' => 'Prema postavkama preglednika',
# Font style option in Special:Preferences
-'editfont-style' => 'Uredi područje font stila:',
+'editfont-style' => 'Font u okviru za uređivanje',
'editfont-default' => 'Prema postavkama preglednika',
'editfont-monospace' => 'Font s jednakim razmakom',
'editfont-sansserif' => 'Font Sans-serif',
'mailerror' => 'Pogrješka pri slanju e-pošte: $1',
'acct_creation_throttle_hit' => 'Posjetitelji ovog wikija koji rabe Vašu IP adresu napravili su {{PLURAL:$1|1 račun|$1 računa}} u posljednjem danu, što je najveći dopušteni broj u tom vremenskom razdoblju.
Zbog toga posjetitelji s ove IP adrese trenutačno ne mogu otvoriti nove suradničke račune.',
-'emailauthenticated' => 'Vaša e-mail adresa je ovjerena $2 u $3.',
+'emailauthenticated' => 'vaša e-mail adresa je ovjerena $2 u $3.',
'emailnotauthenticated' => 'Vaša e-mail adresa još nije ovjerena.
Ne možemo poslati e-mail ni u jednoj od sljedećih naredbi.',
'noemailprefs' => 'Nije navedena adresa elektroničke pošte, stoga sljedeće naredbe ne će raditi.',
'passwordreset-text-one' => 'Ispunite ovaj obrazac ako želite ponovno postaviti Vašu zaporku.',
'passwordreset-legend' => 'Poništi lozinku',
'passwordreset-disabled' => 'Poništavanje lozinke je onemogućeno na ovom wikiju.',
+'passwordreset-emaildisabled' => 'Funkcija e-pošte je onemogućena na ovom wikiju.',
'passwordreset-username' => 'Suradničko ime:',
'passwordreset-domain' => 'Domena:',
'passwordreset-capture' => 'Pogledati krajnju poruku?',
'stub-threshold-disabled' => 'Onemogućeno',
'recentchangesdays' => 'Broj dana prikazanih u nedavnim promjenama:',
'recentchangesdays-max' => '(maksimalno $1 {{PLURAL:$1|dan|dana}})',
-'recentchangescount' => 'Broj izmjena za prikaz kao zadano:',
+'recentchangescount' => 'Zadani broj izmjena koje se prikazuju:',
'prefs-help-recentchangescount' => 'Ovo uključuje nedavne promjene, stare izmjene, i evidencije.',
'savedprefs' => 'Vaše postavke su sačuvane.',
'timezonelegend' => 'Vremenska zona:',
'prefs-editor' => 'Uređivač',
'prefs-preview' => 'Prikaži kako će izgledati',
'prefs-advancedrc' => 'Napredne mogućnosti',
-'prefs-advancedrendering' => 'Napredne opcije',
-'prefs-advancedsearchoptions' => 'Napredne opcije',
-'prefs-advancedwatchlist' => 'Napredne opcije',
+'prefs-advancedrendering' => 'Napredne mogućnosti',
+'prefs-advancedsearchoptions' => 'Napredne mogućnosti',
+'prefs-advancedwatchlist' => 'Napredne mogućnosti',
'prefs-displayrc' => 'Prikaži opcije',
'prefs-displaysearchoptions' => 'Mogućnosti prikaza',
'prefs-displaywatchlist' => 'Mogućnosti prikaza',
'userlogin-noaccount' => '계정이 없나요?',
'userlogin-joinproject' => '{{SITENAME}}에 가입하세요',
'nologin' => '계정이 없나요? $1.',
-'nologinlink' => 'ê³\84ì \95ì\9d\84 ë§\8cë\93\9cì\84¸ì\9a\94',
+'nologinlink' => 'ê³\84ì \95ì\9d\84 ë§\8cë\93¤ê¸°',
'createaccount' => '계정 만들기',
'gotaccount' => '계정이 이미 있다면, $1.',
'gotaccountlink' => '로그인하세요',
'deletereasonotherlist' => '다른 이유',
'deletereason-dropdown' => '* 일반적인 삭제 이유
** 스팸
-** 훼손 행위
+** 문서 훼손 행위
** 저작권 침해
** 작성자의 요청
** 깨진 넘겨주기',
'edit_form_incomplete' => "'''Chèiche part dël formolari ëd modìfica a son pa rivà al servent; ch'a contròla për da bin che soe modìfiche a-i sio ancora e ch'a preuva torna.'''",
'editing' => 'Modìfica ëd $1',
'creating' => 'Creé $1',
-'editingsection' => 'I soma dapress a modifiché $1 (session)',
-'editingcomment' => 'I soma dapress a modifiché $1 (neuva session)',
-'editconflict' => "Conflit d'edission: $1",
-'explainconflict' => "Cheidun d'àutr a l'ha salvà soa version dl'artìcol antramentré che chiel (chila) as prontava la soa.
+'editingsection' => 'Modìfica ëd $1 (session)',
+'editingcomment' => 'Modìfica ëd $1 (neuva session)',
+'editconflict' => 'Conflit ëd modìfica: $1',
+'explainconflict' => "Cheidun d'àutr a l'ha salvà soa version dl'artìcol antramentre che chiel as prontava la soa.
Ël quàder ëd modìfica dë dzora a mostra ël test ëd l'artìcol coma a resta adess (visadì, lòn che a-i é ant sla Ragnà). Soe modìfiche a stan ant ël quàder dë sota.
Ën volend a peul gionté soe modìfiche ant ël quàder dë dzora.
'''Mach''' ël test ant ël quàder dë dzora a sarà salvà, ën sgnacand ël boton \"{{int:savearticle}}\".",
'yourtext' => 'Sò test',
-'storedversion' => 'Version memorisà',
-'nonunicodebrowser' => "'''A L'EUJ! Sò programa ëd navigassion (browser) a travaja pa giust con lë stàndard unicode. I soma obligà a dovré dij truschin përchè a peula salvesse sò artìcoj sensa problema: ij caràter che a son nen ASCII a jë s-ciairerà ant ël quàder ëd modìfica test coma còdes esadecimaj.'''",
+'storedversion' => 'La version memorisà',
+'nonunicodebrowser' => "'''A L'EUJ! Sò programa ëd navigassion a marcia pa giust con lë stàndard Unicode. I soma obligà a dovré dij truschin përchè a peula salvesse sò artìcoj sensa problema: ij caràter che a son nen ASCII a jë s-ciairerà ant ël quàder ëd modìfica dël test coma còdes esadecimaj.'''",
'editingold' => "'''CHE A FASA MACH ATENSION: che a sta fasend-je dle modìfiche a na version nen agiornà dl'artìcol.<br />
Se a la salva parèj, lòn che a l'era stàit fàit dapress a sta revision-sì as perdrà d'autut.'''",
'yourdiff' => 'Diferense',
'yourgender' => 'Como prefere ser descrito?',
'gender-unknown' => 'Prefiro não dizer',
'gender-male' => 'Ele edita páginas wiki',
-'gender-female' => 'Feminino',
-'prefs-help-gender' => 'Opcional: usado pelo programa para ajuste das mensagens ao género do utilizador.
+'gender-female' => 'Ela edita páginas wiki',
+'prefs-help-gender' => 'Esta preferência é opcional.
+O software usa o seu valor para o endereçar e para o mencionar a outros usando o género gramatical apropriado.
Esta informação será pública.',
'email' => 'Correio electrónico',
'prefs-help-realname' => 'O fornecimento do nome verdadeiro é opcional.
'reuploaddesc' => 'Cancelar o envio e voltar ao formulário de carregamento',
'upload-tryagain' => 'Submeta a descrição do ficheiro modificado',
'uploadnologin' => 'Não autenticado',
-'uploadnologintext' => 'Tem de estar [[Special:UserLogin|autenticado]] para enviar ficheiros.',
+'uploadnologintext' => 'Tem de $1 para enviar ficheiros.',
'upload_directory_missing' => 'O diretório de carregamento de ficheiros ($1) não existe e o servidor de internet não conseguiu criá-lo.',
'upload_directory_read_only' => 'O servidor de internet não possui permissão de escrita no diretório de carregamento de ficheiros ($1).',
'uploaderror' => 'Erro ao carregar',
'deleteotherreason' => 'Outro/motivo adicional:',
'deletereasonotherlist' => 'Outro motivo',
'deletereason-dropdown' => '* Motivos de eliminação comuns
-** Pedido do autor
+** Spam
+** Vandalismo
** Violação de direitos de autor
-** Vandalismo',
+** Pedido do autor
+** Redirecionamento quebrado',
'delete-edit-reasonlist' => 'Editar motivos de eliminação',
'delete-toobig' => 'Esta página tem um histórico longo, com mais de $1 {{PLURAL:$1|edição|edições}}.
A eliminação de páginas como esta foi restringida na {{SITENAME}}, para evitar problemas acidentais.',
# Limit report
'limitreport-cputime-value' => '$1 {{PLURAL:$1|segundo|segundos}}',
-'limitreport-postexpandincludesize-value' => '$1/$2 bytes',
-'limitreport-templateargumentsize-value' => '$1/$2 bytes',
+'limitreport-postexpandincludesize-value' => '$1/$2 {{PLURAL:$2|byte|bytes}}',
+'limitreport-templateargumentsize-value' => '$1/$2 {{PLURAL:$2|byte|bytes}}',
);
'userlogin-resetpassword-link' => 'Troque sua senha',
'helplogin-url' => 'Help:Iniciar sessão',
'userlogin-helplink' => '[[{{MediaWiki:helplogin-url}}|Ajuda para iniciar sessão]]',
+'userlogin-loggedin' => 'Você já está conectado como {{GENDER:$1|$1}}.
+Use o formulário abaixo para iniciar sessão como outro usuário.',
'createacct-join' => 'Insira suas informações abaixo.',
'createacct-another-join' => 'Preeencha as informações para a nova conta',
'createacct-emailrequired' => 'Endereço de e-mail',
'deletecomment' => 'Motivo:',
'deleteotherreason' => 'Justificativa adicional:',
'deletereasonotherlist' => 'Outro motivo',
-'deletereason-dropdown' => '* Motivos de eliminação comuns
-** Pedido do autor
+'deletereason-dropdown' => '* Motivos comuns para eliminação
+** Spam
+** Vandalismo
** Violação de direitos de autor
-** Vandalismo',
+** A pedido do autor
+** Redirecionamento inválido',
'delete-edit-reasonlist' => 'Editar motivos de eliminação',
'delete-toobig' => 'Esta página possui um longo histórico de edições, com mais de $1 {{PLURAL:$1|edição|edições}}.
A eliminação de tais páginas foi restrita, a fim de se evitarem problemas acidentais em {{SITENAME}}.',
'contributions' => 'Contribuições {{GENDER:$1|do usuário|da usuária}}',
'contributions-title' => 'Contribuições {{GENDER:$1|do usuário|da usuária}} $1',
'mycontris' => 'Contribuições',
-'contribsub2' => 'Para $1 ($2)',
+'contribsub2' => 'Para {{GENDER:$3|$1}} ($2)',
'nocontribs' => 'Não foram encontradas mudanças com este critério.',
'uctop' => '(atual)',
'month' => 'Mês (inclusive anteriores):',
'tags-tag' => 'Nome da etiqueta',
'tags-display-header' => 'Aparência nas listas de modificações',
'tags-description-header' => 'Descrição completa do significado',
+'tags-active-header' => 'Ativo?',
'tags-hitcount-header' => 'Modificações etiquetadas',
+'tags-active-yes' => 'Sim',
+'tags-active-no' => 'Não',
'tags-edit' => 'editar',
'tags-hitcount' => '$1 {{PLURAL:$1|modificação|modificações}}',
'limitreport-walltime-value' => '$1 {{PLURAL:$1|segundo|segundos}}',
'limitreport-ppvisitednodes' => 'Número de nós visitados pelo pré-processador',
'limitreport-ppgeneratednodes' => 'Número de nós gerados pelo pré-processador',
+'limitreport-postexpandincludesize' => 'Tamanho de inclusão pós-expansão',
'limitreport-postexpandincludesize-value' => '$1/$2 {{PLURAL:$2|byte|bytes}}',
+'limitreport-templateargumentsize' => 'Argumento do tamanho da predefinição',
'limitreport-templateargumentsize-value' => '$1/$2 {{PLURAL:$2|byte|bytes}}',
'limitreport-expansiondepth' => 'Máxima profundidade de expansão',
+'limitreport-expensivefunctioncount' => 'Conta da função expansiva do analizador',
);
* @author Incnis Mrsi
* @author Iniquity
* @author Innv
+ * @author Ivan Shmakov
* @author Jackie
* @author JenVan
* @author Jl
'revdelete-concurrent-change' => 'Ошибка изменения записи от $2, $1: её статус был изменён кем-то другим, пока вы пытались изменить его.
Пожалуйста, проверьте журналы.',
'revdelete-only-restricted' => 'Ошибка сокрытия записи от $2 $1: вы не можете скрыть запись от просмотра администраторами без выбора одной из других настроек сокрытия.',
-'revdelete-reason-dropdown' => 'Стандартные причины удаления
+'revdelete-reason-dropdown' => '* Стандартные причины удаления
** Нарушение авторских прав
** Неуместные личные сведения
+** Неуместное имя участника
** Потенциально клеветнические сведения',
'revdelete-otherreason' => 'Другая/дополнительная причина:',
'revdelete-reasonotherlist' => 'Другая причина',
'tog-newpageshidepatrolled' => 'Göm patrullerade sidor från listan över nya sidor',
'tog-extendwatchlist' => 'Utöka bevakningslistan till att visa alla ändringar, inte bara den senaste',
'tog-usenewrc' => 'Gruppera ändringar efter sida i senaste ändringar och bevakningslistan',
-'tog-numberheadings' => 'Numrerade rubriker',
+'tog-numberheadings' => 'Automatisk numrerade rubriker',
'tog-showtoolbar' => 'Visa redigerings-verktygsraden',
'tog-editondblclick' => 'Redigera sidor med dubbelklick',
'tog-editsection' => 'Aktivera redigering av avsnitt genom [redigera]-länkar',
'category-subcat-count' => 'Di ghadegorii umfasd {{PLURAL:$2|bloos a undâr-ghadegorii|dsam $2 undâr-ghadegoriâ, wofoo {{PLURAL:$1|nôr ôône| $1}}}} undn ôôdsajchd wärn.',
'category-article-count' => 'Di ghadegorii umfasd {{PLURAL:$2|bloos a sajdn|$2 sajdn, wofoo hiir {{PLURAL:$1|aane undn ôôdsajchd wärd|l$1 ôôdsajchd undn wärn}}}}.',
'listingcontinuesabbrev' => '(Fôrdsedsung)',
+'noindex-category' => 'Seidn, wou net indexierd sin',
+'about' => 'Ieber',
'newwindow' => '(Wärd in am najn fenschdâ daargschdeld)',
'cancel' => 'Abbrechn',
'mytalk' => 'Disghusjoonssajdn',
Wen's des ned is, bisd womeeglich iwa ân feela in dr sofdwäâr gschdolbäd. In dämm Fall melds´däs, bidde mid där URL, am [[Special:ListUsers/sysop|Administrator]].",
'missingarticle-rev' => '(wärsjoonsnumâr: $1)',
+'badtitle' => 'Ungüldicher Addigl',
'badtitletext' => "Dii fârlangde sajdn gibd's ned, odâr sii had ân uugildichn sajdnnôôma ghabd, odâr s'wôôr â gschlambdâr fârwajs fonâm andârn wighi häär. Filajchd is aa â buuchschdôôb drin'n, däär in sajdnnôôm gôôr ned schdena däf.",
'viewsource' => 'Gwäl-dhägsd ôôgugn',
'remembermypassword' => 'Af dem ghombjuudâr schdändich ôôgmäld blajm (for a maximum of $1 {{PLURAL:$1|day|days}})',
'login' => 'Ôômeldn',
'nav-login-createaccount' => 'Oomeldn / Ghondoo ooleeng',
+'loginprompt' => 'Zum Omelldn mäin Guggies agdivierd sei.',
'userlogin' => 'Ôômeldn / Als Bajdräächâr ajschrajm',
'logout' => 'Abmeldn',
'userlogout' => 'Abmeldn',
+'nologin' => 'Du hast ka Nutzergonto? $1',
'nologinlink' => 'Sich als najâr Ôôgmeldâr ôômäldn',
'gotaccountlink' => 'Omeldn',
'mailmypassword' => 'Â najs passwôrd iwâr iimejl dsuschign lasn',
* '''({{int:cur}})''' = undârschiid dsur geechnwärdichn wärsjoon, '''({{int:last}})''' = undârschiid dsur foorichn wärsjoon
* Uurdsajd/Daadum = wärsjoon dsu dära dsajd, '''{{int:minoreditletter}}''' = glane ändärung.",
'history-fieldset-title' => 'Suchng in där wärsjoonsfolche',
-'histfirst' => 'Ã\84ldâschde',
-'histlast' => 'Najsde',
+'histfirst' => 'älldsde',
+'histlast' => 'neisde',
# Revision deletion
'rev-delundel' => 'ôôdsajng/fârbärng',
'notextmatches' => 'Närchnds gfundn.',
'prevn' => '{{PLURAL:$1|foorichâr|fooriche $1}}',
'nextn' => '{{PLURAL:$1|nägschdâr|nägschde $1}}',
+'prevn-title' => '{{PLURAL:$1|Vuurherichs Ergebnis|Vuurheriche $1 Ergebniss}}',
+'shown-title' => 'Zeich mer $1 {{PLURAL:$1|Ergebnis|Ergebniss}} bro Seidn',
'viewprevnext' => 'Dsajch ($1 {{int:pipe-separator}} $2) ($3)',
'searchmenu-new' => "'''Derschdell dai Seidn „[[:$1]]“ in diesn Wigi.'''",
'searchprofile-articles' => 'Inhaldsseidn',
+'searchprofile-project' => 'Hilf- un Brojegdseidn',
'searchprofile-images' => 'Muldimedia',
'searchprofile-everything' => 'Alls',
'searchprofile-advanced' => 'Erweiderd',
+'searchprofile-articles-tooltip' => 'Soung in $1',
+'searchprofile-project-tooltip' => 'Soung in $1',
+'searchprofile-images-tooltip' => 'Nach Daddein soung',
+'searchprofile-everything-tooltip' => 'Gsamdn Inhald durchsoung (aa Disgussionsseidn)',
+'searchprofile-advanced-tooltip' => 'Soung in weidere Namensraim',
'search-result-size' => '$1 ({{PLURAL:$2|1 wôrd|$2 wärdâr}})',
'search-result-score' => 'Âjschleechich: $1 %',
'search-redirect' => '(Wajdalajdung fon „$1“ häa)',
'timezoneregion-pacific' => 'Bhadsiifischâr Oodseaan',
'allowemail' => 'Iimejl-embfang fon andrâ ôôschdeln',
'youremail' => 'E-mail:',
+'yourrealname' => 'Bürcherlicher Noma:',
# Groups
'group-sysop' => 'Adminisdradoorn',
# Upload
'upload' => 'Nauflôôdn',
'uploadlogpage' => 'Brodoghol fom dadaj-hoochlôôdn',
+'filedesc' => 'Bschreibung',
'uploadedimage' => 'had „[[$1]]“ naufglôôdn',
'license' => 'Lizenz',
'emailuser' => 'Dem ôôgmeldn â iimejl schign',
# Watchlist
-'watchlist' => 'Maj beoobachdungs-lisdn',
+'watchlist' => 'Beoobachdungslisdn',
'mywatchlist' => 'Beoobachdungslisdn',
'addedwatchtext' => "Di sajdn „[[:$1]]“ schdäd eds mid af dajnâr [[Special:Watchlist|beoobachdungs-lisdn]] .
'contributions-title' => 'Bajdrääch fo „$1“',
'mycontris' => 'Bajdreech',
'contribsub2' => 'Fär $1 ($2)',
-'uctop' => '(ledsdâr schdand)',
+'uctop' => '(agduell)',
'month' => 'bis moonad:',
'year' => 'bis dsum jôôr:',
'sp-contributions-newbies' => 'Bloos bajdrääch fo naj Ôôgmeldâ dsajchn',
'sp-contributions-blocklog' => 'Schbär-brodoghol',
+'sp-contributions-uploads' => 'Houchglodne Daddein',
+'sp-contributions-logs' => 'Logbäicher',
'sp-contributions-talk' => 'Disgussion',
'sp-contributions-search' => 'Bajdreech suchng',
'sp-contributions-username' => 'IP-adresn odär nôômâ fom Ôôgmeldn:',
'linkshere' => "Dii afgfiirdn sajdn fârwajsn af ''„[[:$1]]“''':",
'isredirect' => 'Wajdârlajdungssajdn',
'istemplate' => 'Foorlaachn-ajbindung',
-'isimage' => 'fârwajs af des bild hiir',
+'isimage' => 'Daddeilink',
'whatlinkshere-prev' => '{{PLURAL:$1|vorhäärichâr|vorhääriche $1}}',
'whatlinkshere-next' => '{{PLURAL:$1|nägschdâr|nägschde $1}}',
'whatlinkshere-links' => '← fârwajse hiirhäär',
'blockip-title' => 'Bearbajdâr aus-schbärn',
'blockip-legend' => 'IP-Adresn odr Bearbajdâr aus-schbärn',
'ipboptions' => '2 schdund:2 hours,1 dooch:1 day,3 dooch:3 days,1 wochng:1 week,2 wochng:2 weeks,1 moonad:1 month,3 moonad:3 months,6 moonad:6 months,1 jôôr:1 year,oone dsajdschrangng:infinite',
-'ipblocklist' => 'Gschbärde IP-adresn un Ôôgmelde',
+'ipblocklist' => 'Gschberrder Nutzer',
'blocklink' => 'Schbärn',
'unblocklink' => 'frajgeem',
'change-blocklink' => 'Schbärn ändârn',
# Export
'export' => 'Sajdn ägsbhôrdiirn',
+# Namespace 8 related
+'allmessagesname' => 'Noma',
+'allmessagesdefault' => 'Schdandaddexd',
+
# Thumbnails
'thumbnail-more' => 'Grässär machng',
'metadata-help' => 'Dii dadaj umfasd annäre ôôgam, dii normaalârwajs fo där digidaal-ghamâraa odär fo am sghänâr häärghumma. Wen dii dadaj indswischn fârändârd wôrn is, meechn dii nimä dsum bild basn.',
'metadata-expand' => 'Ajdslhajdn dsajchn',
'metadata-collapse' => 'Ajdslhajdn ausblendn',
-'metadata-fields' => 'Hiir afgfiirde fäldâr fo dâ EXIF-medha-daadn wärn af alle bildbeschrajwungs-sajdn afgfiird, aa wen dii medhadaadn-dabelln ajgfalded is. Annäre sin ärschdâmôôl fârschdegd.
+'metadata-fields' => 'Folgnde Felder vo däi EXIF-Medadaden, däi wou in den MediaWigi-Sysdemdexd ogeem sin, werrn af Bildbeschreibungsseidn miid eiglabbder Medadadndabelln ozeichd. Weidere werrn schdandaddmäßich net ozeichd.
* make
* model
* datetimeoriginal
'userlogin-resetpassword-link' => 'צוריקשטעלן אײַער פאַסווארט',
'helplogin-url' => 'Help:אריינלאגירן',
'userlogin-helplink' => '[[{{MediaWiki:helplogin-url}}|הילף מיט אריינלאגירן]]',
+'userlogin-loggedin' => 'איר זענט שוין אריינלאגירט ווי {{GENDER:$1|$1}}.
+ניצט די פארעם אונטן כדי אריינלאגירן ווי אן אנדער באניצער.',
'userlogin-createanother' => 'שאפֿן נאך א קאנטע',
'createacct-join' => 'גיט ארײַן אײַער אינפֿארמאציע אונטן.',
'createacct-another-join' => 'ארײַנגעבן דער נײַער קאנטעס אינפארמאציע אונטן.',
'recentchanges-label-bot' => '该编辑由机器人进行',
'recentchanges-label-unpatrolled' => '该编辑尚未巡查',
'rcnote' => "下面是过去'''$2'''天的最后'''$1'''个更改,截至$4 $5。",
-'rcnotefrom' => "下面是自'''$2'''起的更改(最多显示'''$1'''个)。",
-'rclistfrom' => '显示自$1起的新更改',
+'rcnotefrom' => "下面是'''$2'''之后的更改(最多显示'''$1'''个)。",
+'rclistfrom' => '显示$1之后的新更改',
'rcshowhideminor' => '$1小编辑',
'rcshowhidebots' => '$1机器人的编辑',
'rcshowhideliu' => '$1登录用户的编辑',
'deletereasonotherlist' => '其他原因',
'deletereason-dropdown' => '*常见删除原因
** 广告
-** 作者申请
-** 侵犯著作权
** 破坏行为
-** 损坏重定向',
+** 侵犯著作权
+** 作者申请
+** 损坏的重定向',
'delete-edit-reasonlist' => '编辑删除原因',
'delete-toobig' => '这个页面有一个十分大量的编辑历史,超过$1次修订。删除此类页面的动作已经被限制,以防止在{{SITENAME}}上的意外扰乱。',
'delete-warning-toobig' => '这个页面有一个十分大量的编辑历史,超过$1次修订。删除它可能会扰乱{{SITENAME}}的数据库操作;在继续此动作前请小心。',
--- /dev/null
+--
+-- patch-archive-ar_id.sql
+--
+-- Bug 39675. Add archive.ar_id.
+
+ALTER TABLE /*$wgDBprefix*/archive
+ ADD COLUMN ar_id int unsigned NOT NULL AUTO_INCREMENT FIRST,
+ ADD PRIMARY KEY (ar_id);
CREATE UNIQUE INDEX /*i*/change_tag_rev_tag ON /*_*/change_tag (ct_rev_id,ct_tag);
-- Covering index, so we can pull all the info only out of the index.
CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id);
-
--- Rollup table to pull a LIST of tags simply without ugly GROUP_CONCAT that only works on MySQL 4.1+
-CREATE TABLE /*_*/tag_summary (
- ts_rc_id int NULL,
- ts_log_id int NULL,
- ts_rev_id int NULL,
- ts_tags BLOB NOT NULL
-) /*$wgDBTableOptions*/;
-
-CREATE UNIQUE INDEX /*i*/tag_summary_rc_id ON /*_*/tag_summary (ts_rc_id);
-CREATE UNIQUE INDEX /*i*/tag_summary_log_id ON /*_*/tag_summary (ts_log_id);
-CREATE UNIQUE INDEX /*i*/tag_summary_rev_id ON /*_*/tag_summary (ts_rev_id);
-
-
-CREATE TABLE /*_*/valid_tag (
- vt_tag varchar(255) NOT NULL PRIMARY KEY
-) /*$wgDBTableOptions*/;
--- /dev/null
+--
+-- patch-extenallinks-el_id.sql
+--
+-- Bug 15441. Add externallinks.el_id.
+
+ALTER TABLE /*$wgDBprefix*/externallinks
+ ADD COLUMN el_id int unsigned NOT NULL AUTO_INCREMENT FIRST,
+ ADD PRIMARY KEY (el_id);
--- /dev/null
+-- Rollup table to pull a LIST of tags simply without ugly GROUP_CONCAT that only works on MySQL 4.1+
+-- Andrew Garrett, 2009-01
+CREATE TABLE /*_*/tag_summary (
+ ts_rc_id int NULL,
+ ts_log_id int NULL,
+ ts_rev_id int NULL,
+ ts_tags BLOB NOT NULL
+) /*$wgDBTableOptions*/;
+
+CREATE UNIQUE INDEX /*i*/tag_summary_rc_id ON /*_*/tag_summary (ts_rc_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_log_id ON /*_*/tag_summary (ts_log_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_rev_id ON /*_*/tag_summary (ts_rev_id);
--- /dev/null
+-- Andrew Garrett, 2009-01
+CREATE TABLE /*_*/valid_tag (
+ vt_tag varchar(255) NOT NULL PRIMARY KEY
+) /*$wgDBTableOptions*/;
$default = wfMessage( $key )->inLanguage( $langCode )->useDatabase( false )->plain();
$messageInfo['relevantPages']++;
- if ( $actual === $default ) {
+
+ if (
+ // Exclude messages that are empty by default, such as sitenotice, specialpage
+ // summaries and accesskeys.
+ $default !== '' && $default !== '-' &&
+ $actual === $default
+ ) {
$hasTalk = isset( $statuses['talks'][$key] );
$messageInfo['results'][] = array(
'title' => $key . $titleSuffix,
} else {
$props = FSFile::getPropsFromPath( $file );
$flags = 0;
- $options = array();
+ $publishOptions = array();
$handler = MediaHandler::getHandler( $props['mime'] );
if ( $handler ) {
- $options['headers'] = $handler->getStreamHeaders( $props['metadata'] );
+ $publishOptions['headers'] = $handler->getStreamHeaders( $props['metadata'] );
} else {
- $options['headers'] = array();
+ $publishOptions['headers'] = array();
}
- $archive = $image->publish( $file, $flags, $options );
+ $archive = $image->publish( $file, $flags, $publishOptions );
if ( !$archive->isGood() ) {
echo "failed. (" .
$archive->getWikiText() .
}
$commentText = SpecialUpload::getInitialPageText( $commentText, $license );
- if ( !$summary ) {
+ if ( !isset( $options['summary'] ) ) {
$summary = $commentText;
}
-- Cannot reasonably create views on this table, due to the presence of TEXT
-- columns.
CREATE TABLE /*$wgDBprefix*/archive (
+ ar_id NOT NULL PRIMARY KEY clustered IDENTITY,
ar_namespace SMALLINT NOT NULL DEFAULT 0,
ar_title NVARCHAR(255) NOT NULL DEFAULT '',
ar_text NVARCHAR(MAX) NOT NULL,
-- Track links to external URLs
-- IE >= 4 supports no more than 2083 characters in a URL
CREATE TABLE /*$wgDBprefix*/externallinks (
+ el_id INT NOT NULL PRIMARY KEY clustered IDENTITY,
el_from INT NOT NULL DEFAULT '0',
el_to VARCHAR(2083) NOT NULL,
el_index VARCHAR(896) NOT NULL,
--- /dev/null
+define mw_prefix='{$wgDBprefix}';
+
+ALTER TABLE &mw_prefix.archive ADD (
+ar_id NUMBER NOT NULL,
+);
+ALTER TABLE &mw_prefix.archive ADD CONSTRAINT &mw_prefix.archive_pk PRIMARY KEY (ar_id);
--- /dev/null
+define mw_prefix='{$wgDBprefix}';
+
+ALTER TABLE &mw_prefix.externallinks ADD el_id NUMBER NOT NULL;
+ALTER TABLE &mw_prefix.externallinks ADD CONSTRAINT &mw_prefix.externallinks_pk PRIMARY KEY (el_id);
\ No newline at end of file
);
ALTER TABLE &mw_prefix.pagecontent ADD CONSTRAINT &mw_prefix.pagecontent_pk PRIMARY KEY (old_id);
+CREATE SEQUENCE archive_ar_id_seq;
CREATE TABLE &mw_prefix.archive (
+ ar_id NUMBER NOT NULL,
ar_namespace NUMBER DEFAULT 0 NOT NULL,
ar_title VARCHAR2(255) NOT NULL,
ar_text CLOB,
ar_content_model VARCHAR2(32),
ar_content_format VARCHAR2(64)
);
+ALTER TABLE &mw_prefix.archive ADD CONSTRAINT &mw_prefix.archive_pk PRIMARY KEY (ar_id);
ALTER TABLE &mw_prefix.archive ADD CONSTRAINT &mw_prefix.archive_fk1 FOREIGN KEY (ar_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX &mw_prefix.archive_i01 ON &mw_prefix.archive (ar_namespace,ar_title,ar_timestamp);
CREATE INDEX &mw_prefix.archive_i02 ON &mw_prefix.archive (ar_user_text,ar_timestamp);
CREATE UNIQUE INDEX &mw_prefix.category_u01 ON &mw_prefix.category (cat_title);
CREATE INDEX &mw_prefix.category_i01 ON &mw_prefix.category (cat_pages);
+CREATE SEQUENCE externallinks_el_id_seq;
CREATE TABLE &mw_prefix.externallinks (
+ el_id NUMBER NOT NULL,
el_from NUMBER NOT NULL,
el_to VARCHAR2(2048) NOT NULL,
el_index VARCHAR2(2048) NOT NULL
);
+ALTER TABLE &mw_prefix.externallinks ADD CONSTRAINT &mw_prefix.externallinks_pk PRIMARY KEY (el_id);
ALTER TABLE &mw_prefix.externallinks ADD CONSTRAINT &mw_prefix.externallinks_fk1 FOREIGN KEY (el_from) REFERENCES &mw_prefix.page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX &mw_prefix.externallinks_i01 ON &mw_prefix.externallinks (el_from, el_to);
CREATE INDEX &mw_prefix.externallinks_i02 ON &mw_prefix.externallinks (el_to, el_from);
DROP SEQUENCE IF EXISTS logging_log_id_seq CASCADE;
DROP SEQUENCE IF EXISTS job_job_id_seq CASCADE;
DROP SEQUENCE IF EXISTS category_cat_id_seq CASCADE;
+DROP SEQUENCE IF EXISTS archive_ar_id_seq CASCADE;
+DROP SEQUENCE IF EXISTS externallinks_el_id_seq CASCADE;
DROP FUNCTION IF EXISTS page_deleted() CASCADE;
DROP FUNCTION IF EXISTS ts2_page_title() CASCADE;
DROP FUNCTION IF EXISTS ts2_page_text() CASCADE;
CREATE INDEX page_props_propname ON page_props (pp_propname);
CREATE UNIQUE INDEX pp_propname_page ON page_props (pp_propname,pp_page);
+CREATE SEQUENCE archive_ar_id_seq;
CREATE TABLE archive (
+ ar_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('archive_ar_id_seq'),
ar_namespace SMALLINT NOT NULL,
ar_title TEXT NOT NULL,
ar_text TEXT, -- technically should be bytea, but not used anymore
CREATE UNIQUE INDEX cl_from ON categorylinks (cl_from, cl_to);
CREATE INDEX cl_sortkey ON categorylinks (cl_to, cl_sortkey, cl_from);
+CREATE SEQUENCE externallinks_id_seq;
CREATE TABLE externallinks (
+ el_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('externallinks_id_seq'),
el_from INTEGER NOT NULL REFERENCES page(page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
el_to TEXT NOT NULL,
el_index TEXT NOT NULL
DROP TABLE IF EXISTS /*_*/page_restrictions_tmp;
DROP TABLE IF EXISTS /*_*/protected_titles_tmp;
DROP TABLE IF EXISTS /*_*/page_props_tmp;
+DROP TABLE IF EXISTS /*_*/archive_tmp;
+DROP TABLE IF EXISTS /*_*/externallinks_tmp;
--------------------------------------------------------------------------------
-- Create new tables
);
CREATE UNIQUE INDEX /*i*/pp_page_propname ON /*_*/page_props_tmp (pp_page,pp_propname);
+--
+-- Holding area for deleted articles, which may be viewed
+-- or restored by admins through the Special:Undelete interface.
+-- The fields generally correspond to the page, revision, and text
+-- fields, with several caveats.
+-- Cannot reasonably create views on this table, due to the presence of TEXT
+-- columns.
+CREATE TABLE /*$wgDBprefix*/archive_tmp (
+ ar_id NOT NULL PRIMARY KEY clustered IDENTITY,
+ ar_namespace SMALLINT NOT NULL DEFAULT 0,
+ ar_title NVARCHAR(255) NOT NULL DEFAULT '',
+ ar_text NVARCHAR(MAX) NOT NULL,
+ ar_comment NVARCHAR(255) NOT NULL,
+ ar_user INT NULL REFERENCES /*$wgDBprefix*/[user](user_id) ON DELETE SET NULL,
+ ar_user_text NVARCHAR(255) NOT NULL,
+ ar_timestamp DATETIME NOT NULL DEFAULT GETDATE(),
+ ar_minor_edit BIT NOT NULL DEFAULT 0,
+ ar_flags NVARCHAR(255) NOT NULL,
+ ar_rev_id INT,
+ ar_text_id INT,
+ ar_deleted BIT NOT NULL DEFAULT 0,
+ ar_len INT DEFAULT NULL,
+ ar_page_id INT NULL,
+ ar_parent_id INT NULL
+);
+CREATE INDEX /*$wgDBprefix*/ar_name_title_timestamp ON /*$wgDBprefix*/archive_tmp(ar_namespace,ar_title,ar_timestamp);
+CREATE INDEX /*$wgDBprefix*/ar_usertext_timestamp ON /*$wgDBprefix*/archive_tmp(ar_user_text,ar_timestamp);
+CREATE INDEX /*$wgDBprefix*/ar_user_text ON /*$wgDBprefix*/archive_tmp(ar_user_text);
+
+--
+-- Track links to external URLs
+-- IE >= 4 supports no more than 2083 characters in a URL
+CREATE TABLE /*$wgDBprefix*/externallinks_tmp (
+ el_id INT NOT NULL PRIMARY KEY clustered IDENTITY,
+ el_from INT NOT NULL DEFAULT '0',
+ el_to VARCHAR(2083) NOT NULL,
+ el_index VARCHAR(896) NOT NULL,
+);
+-- Maximum key length ON SQL Server is 900 bytes
+CREATE INDEX /*$wgDBprefix*/externallinks_index ON /*$wgDBprefix*/externallinks_tmp(el_index);
+
--------------------------------------------------------------------------------
-- Populate the new tables using INSERT SELECT
--------------------------------------------------------------------------------
INSERT OR IGNORE INTO /*_*/page_restrictions_tmp SELECT * FROM /*_*/page_restrictions;
INSERT OR IGNORE INTO /*_*/protected_titles_tmp SELECT * FROM /*_*/protected_titles;
INSERT OR IGNORE INTO /*_*/page_props_tmp SELECT * FROM /*_*/page_props;
+INSERT OR IGNORE INTO /*_*/archive_tmp SELECT * FROM /*_*/archive;
+INSERT OR IGNORE INTO /*_*/externallinks_tmp SELECT * FROM /*_*/externallinks;
--------------------------------------------------------------------------------
-- Do the table renames
ALTER TABLE /*_*/protected_titles_tmp RENAME TO /*_*/protected_titles;
DROP TABLE /*_*/page_props;
ALTER TABLE /*_*/page_props_tmp RENAME TO /*_*/page_props;
+DROP TABLE /*_*/archive;
+ALTER TABLE /*_*/archive_tmp RENAME TO /*_*/archive;
+DROP TABLE /*_*/externalllinks;
+ALTER TABLE /*_*/externallinks_tmp RENAME TO /*_*/externallinks;
--------------------------------------------------------------------------------
-- Drop and create tables with unique indexes but no valuable data
--- /dev/null
+DROP TABLE IF EXISTS /*_*/archive_tmp;
+
+CREATE TABLE /*$wgDBprefix*/archive_tmp (
+ ar_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ ar_namespace int NOT NULL default 0,
+ ar_title varchar(255) binary NOT NULL default '',
+ ar_text mediumblob NOT NULL,
+ ar_comment tinyblob NOT NULL,
+ ar_user int unsigned NOT NULL default 0,
+ ar_user_text varchar(255) binary NOT NULL,
+ ar_timestamp binary(14) NOT NULL default '',
+ ar_minor_edit tinyint NOT NULL default 0,
+ ar_flags tinyblob NOT NULL,
+ ar_rev_id int unsigned,
+ ar_text_id int unsigned,
+ ar_deleted tinyint unsigned NOT NULL default 0,
+ ar_len int unsigned,
+ ar_page_id int unsigned,
+ ar_parent_id int unsigned default NULL,
+ ar_sha1 varbinary(32) NOT NULL default '',
+ ar_content_model varbinary(32) DEFAULT NULL,
+ ar_content_format varbinary(64) DEFAULT NULL
+);
+
+INSERT OR IGNORE INTO /*_*/archive_tmp (
+ ar_namespace, ar_title, ar_title, ar_text, ar_comment, ar_user, ar_user_text, ar_timestamp,
+ ar_minor_edit, ar_flags, ar_rev_id, ar_text_id, ar_deleted, ar_len, ar_page_id, ar_parent_id )
+ SELECT
+ ar_namespace, ar_title, ar_title, ar_text, ar_comment, ar_user, ar_user_text, ar_timestamp,
+ ar_minor_edit, ar_flags, ar_rev_id, ar_text_id, ar_deleted, ar_len, ar_page_id, ar_parent_id
+ FROM /*_*/archive;
+
+DROP TABLE /*_*/archive;
+
+ALTER TABLE /*_*/archive_tmp RENAME TO /*_*/archive;
+
+CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
+CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
+CREATE INDEX /*i*/ar_revid ON /*_*/archive (ar_rev_id);
--- /dev/null
+DROP TABLE IF EXISTS /*_*/externallinks_tmp;
+
+CREATE TABLE /*$wgDBprefix*/externallinks_tmp (
+ el_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ el_from int unsigned NOT NULL default 0,
+ el_to blob NOT NULL,
+ el_index blob NOT NULL
+);
+
+INSERT OR IGNORE INTO /*_*/externallinks_tmp (el_from, el_to, el_index) SELECT
+ el_from, el_to, el_index FROM /*_*/externallinks;
+
+DROP TABLE /*_*/externallinks;
+
+ALTER TABLE /*_*/externallinks_tmp RENAME TO /*_*/externallinks;
+
+CREATE INDEX /*i*/el_from ON /*_*/externallinks (el_from, el_to(40));
+CREATE INDEX /*i*/el_to ON /*_*/externallinks (el_to(60), el_from);
+CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index(60));
\ No newline at end of file
-- fields, with several caveats.
--
CREATE TABLE /*_*/archive (
+ -- Primary key
+ ar_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
ar_namespace int NOT NULL default 0,
ar_title varchar(255) binary NOT NULL default '',
-- content format, see CONTENT_FORMAT_XXX constants
ar_content_format varbinary(64) DEFAULT NULL
-
) /*$wgDBTableOptions*/;
CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
-- Track links to external URLs
--
CREATE TABLE /*_*/externallinks (
+ -- Primary key
+ el_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+
-- page_id of the referring page
el_from int unsigned NOT NULL default 0,
-- Visibility of recent changes items, bitfield
rc_deleted tinyint unsigned NOT NULL default 0,
- -- Value corresonding to log_id, specific log entries
+ -- Value corresponding to log_id, specific log entries
rc_logid int unsigned NOT NULL default 0,
-- Store log type info here, or null
rc_log_type varbinary(255) NULL default NULL,
*/
( function ( mw, $ ) {
- // Cache token so we don't have to keep fetching new ones for every single request.
- var cachedToken = null;
-
$.extend( mw.Api.prototype, {
/**
* @return {jQuery.Promise} See #post
*/
postWithEditToken: function ( params, ok, err ) {
- var useTokenToPost, getTokenIfBad,
- api = this;
- if ( cachedToken === null ) {
- // We don't have a valid cached token, so get a fresh one and try posting.
- // We do not trap any 'badtoken' or 'notoken' errors, because we don't want
- // an infinite loop. If this fresh token is bad, something else is very wrong.
- useTokenToPost = function ( token ) {
- params.token = token;
- api.post( params, { ok: ok, err: err } );
- };
- return api.getEditToken( useTokenToPost, err );
- } else {
- // We do have a token, but it might be expired. So if it is 'bad' then
- // start over with a new token.
- params.token = cachedToken;
- getTokenIfBad = function ( code, result ) {
- if ( code === 'badtoken' ) {
- // force a new token, clear any old one
- cachedToken = null;
- api.postWithEditToken( params, ok, err );
- } else {
- err( code, result );
- }
- };
- return api.post( params, { ok: ok, err: getTokenIfBad } );
- }
+ return this.postWithToken( 'edit', params ).done( ok ).fail( err );
},
/**
* @return {string} return.done.token Received token.
*/
getEditToken: function ( ok, err ) {
- var d = $.Deferred(),
- apiPromise;
-
- // Backwards compatibility (< MW 1.20)
- d.done( ok ).fail( err );
-
- apiPromise = this.get( {
- action: 'tokens',
- type: 'edit'
- }, {
- // Due to the API assuming we're logged out if we pass the callback-parameter,
- // we have to disable jQuery's callback system, and instead parse JSON string,
- // by setting 'jsonp' to false.
- // TODO: This concern seems genuine but no other module has it. Is it still
- // needed and/or should we pass this by default?
- jsonp: false
- } )
- .done( function ( data ) {
- var token;
- // If token type is not available for this user,
- // key 'edittoken' is missing or can contain Boolean false
- if ( data.tokens && data.tokens.edittoken ) {
- token = data.tokens.edittoken;
- cachedToken = token;
- d.resolve( token );
- } else {
- d.reject( 'token-missing', data );
- }
- } )
- .fail( d.reject );
-
- return d.promise( { abort: apiPromise.abort } );
+ return this.getToken( 'edit' ).done( ok ).fail( err );
},
/**
text: message
}, ok, err );
}
-
- } );
+ } );
/**
* @class mw.Api
dataType: 'json'
}
- };
+ },
+ tokenCache = {};
/**
* Constructor to create an object to interact with the API of a particular MediaWiki server.
return apiDeferred.promise( { abort: xhr.abort } ).fail( function ( code, details ) {
mw.log( 'mw.Api error: ', code, details );
} );
- }
+ },
+
+ /**
+ * Post to API with specified type of token. If we have no token, get one and try to post.
+ * If we have a cached token try using that, and if it fails, blank out the
+ * cached token and start over. For example to change an user option you could do:
+ *
+ * new mw.Api().postWithToken( 'options', {
+ * action: 'options',
+ * optionname: 'gender',
+ * optionvalue: 'female'
+ * } );
+ *
+ * @param {string} tokenType The name of the token, like options or edit.
+ * @param {Object} params API parameters
+ * @return {jQuery.Promise} See #post
+ */
+ postWithToken: function ( tokenType, params ) {
+ var api = this, hasOwn = tokenCache.hasOwnProperty;
+ if ( hasOwn.call( tokenCache, tokenType ) && tokenCache[tokenType] !== undefined ) {
+ params.token = tokenCache[tokenType];
+ return api.post( params ).then(
+ null,
+ function ( code ) {
+ if ( code === 'badtoken' ) {
+ // force a new token, clear any old one
+ tokenCache[tokenType] = params.token = undefined;
+ return api.post( params );
+ }
+ }
+ );
+ } else {
+ return api.getToken( tokenType ).then( function ( token ) {
+ tokenCache[tokenType] = params.token = token;
+ return api.post( params );
+ } );
+ }
+ },
+ /**
+ * Api helper to grab any token.
+ *
+ * @param {string} type Token type.
+ * @return {jQuery.Promise}
+ * @return {Function} return.done
+ * @return {string} return.done.token Received token.
+ */
+ getToken: function ( type ) {
+ var apiPromise,
+ d = $.Deferred();
+
+ apiPromise = this.get( {
+ action: 'tokens',
+ type: type
+ }, {
+ // Due to the API assuming we're logged out if we pass the callback-parameter,
+ // we have to disable jQuery's callback system, and instead parse JSON string,
+ // by setting 'jsonp' to false.
+ // TODO: This concern seems genuine but no other module has it. Is it still
+ // needed and/or should we pass this by default?
+ } )
+ .done( function ( data ) {
+ // If token type is not available for this user,
+ // key '...token' is missing or can contain Boolean false
+ if ( data.tokens && data.tokens[type + 'token'] ) {
+ d.resolve( data.tokens[type + 'token'] );
+ } else {
+ d.reject( 'token-missing', data );
+ }
+ } )
+ .fail( d.reject );
+
+ return d.promise( { abort: apiPromise.abort } );
+ }
};
/**
* @param {HTMLElement|jQuery|mw.Message|string} message
* @param {Object} options The options to use for the notification.
* See #defaults for details.
+ * @return {Object} Object with a close function to close the notification
*/
notify: function ( message, options ) {
var notif;
} else {
preReadyNotifQueue.push( notif );
}
+ return { close: $.proxy( notif.close, notif ) };
},
/**
/**
* @class mw.plugin.notify
*/
-( function ( mw ) {
+( function ( mw, $ ) {
'use strict';
/**
* @see mw.notification#notify
* @param message
* @param options
+ * @return {jQuery.Promise}
*/
mw.notify = function ( message, options ) {
+ var d = $.Deferred();
// Don't bother loading the whole notification system if we never use it.
mw.loader.using( 'mediawiki.notification', function () {
- // Don't bother calling mw.loader.using a second time after we've already loaded mw.notification.
- mw.notify = mw.notification.notify;
// Call notify with the notification the user requested of us.
- mw.notify( message, options );
- } );
+ d.resolve( mw.notification.notify( message, options ) );
+ }, d.reject );
+ return d.promise();
};
/**
* @mixins mw.plugin.notify
*/
-}( mediaWiki ) );
+}( mediaWiki, jQuery ) );
$this->assertEquals( 'abcdefghijka2', $msg->params( $params )->plain(), 'Params > 9 are replaced correctly' );
}
+ /**
+ * FIXME: This should not need database, but Language#formatExpiry does (bug 55912)
+ * @group Database
+ */
+ function testMessageParamTypes() {
+ $lang = Language::factory( 'en' );
+
+ $msg = new RawMessage( '$1' );
+ $this->assertEquals(
+ $lang->formatNum( 123456.789 ),
+ $msg->inLanguage( $lang )->numParams( 123456.789 )->plain(),
+ 'numParams is handled correctly'
+ );
+
+ $msg = new RawMessage( '$1' );
+ $this->assertEquals(
+ $lang->formatDuration( 1234 ),
+ $msg->inLanguage( $lang )->durationParams( 1234 )->plain(),
+ 'durationParams is handled correctly'
+ );
+
+ $msg = new RawMessage( '$1' );
+ $this->assertEquals(
+ $lang->formatExpiry( wfTimestampNow() ),
+ $msg->inLanguage( $lang )->expiryParams( wfTimestampNow() )->plain(),
+ 'expiryParams is handled correctly'
+ );
+
+ $msg = new RawMessage( '$1' );
+ $this->assertEquals(
+ $lang->formatTimePeriod( 1234 ),
+ $msg->inLanguage( $lang )->timeperiodParams( 1234 )->plain(),
+ 'timeperiodParams is handled correctly'
+ );
+
+ $msg = new RawMessage( '$1' );
+ $this->assertEquals(
+ $lang->formatSize( 123456 ),
+ $msg->inLanguage( $lang )->sizeParams( 123456 )->plain(),
+ 'sizeParams is handled correctly'
+ );
+
+ $msg = new RawMessage( '$1' );
+ $this->assertEquals(
+ $lang->formatBitrate( 123456 ),
+ $msg->inLanguage( $lang )->bitrateParams( 123456 )->plain(),
+ 'bitrateParams is handled correctly'
+ );
+ }
+
function testInContentLanguageDisabled() {
$this->setMwGlobals( 'wgLang', Language::factory( 'fr' ) );
* test usernames
*
* @dataProvider provideUserGenders
+ * @covers GenderCache::getGenderOf
*/
function testUserName( $username, $expectedGender ) {
$genderCache = GenderCache::singleton();
* genderCache should work with user objects, too
*
* @dataProvider provideUserGenders
+ * @covers GenderCache::getGenderOf
*/
function testUserObjects( $username, $expectedGender ) {
$genderCache = GenderCache::singleton();
* against the never existing username
*
* @dataProvider provideStripSubpages
+ * @covers GenderCache::getGenderOf
*/
function testStripSubpages( $pageWithSubpage, $expectedGender ) {
$genderCache = GenderCache::singleton();
/**
* @group Database
* @group Cache
+ * @covers MessageCache
*/
class MessageCacheTest extends MediaWikiLangTestCase {
/**
* @dataProvider dataGetDefaultModelFor
+ * @covers ContentHandler::getDefaultModelFor
*/
public function testGetDefaultModelFor( $title, $expectedModelId ) {
$title = Title::newFromText( $title );
/**
* @dataProvider dataGetDefaultModelFor
+ * @covers ContentHandler::getForTitle
*/
public function testGetForTitle( $title, $expectedContentModel ) {
$title = Title::newFromText( $title );
/**
* @dataProvider dataGetLocalizedName
+ * @covers ContentHandler::getLocalizedName
*/
public function testGetLocalizedName( $id, $expected ) {
$name = ContentHandler::getLocalizedName( $id );
/**
* @dataProvider dataGetPageLanguage
+ * @covers ContentHandler::getPageLanguage
*/
public function testGetPageLanguage( $title, $expected ) {
if ( is_string( $title ) ) {
/**
* @dataProvider dataGetContentText_Null
+ * @covers ContentHandler::getContentText
*/
public function testGetContentText_Null( $contentHandlerTextFallback ) {
$this->setMwGlobals( 'wgContentHandlerTextFallback', $contentHandlerTextFallback );
/**
* @dataProvider dataGetContentText_TextContent
+ * @covers ContentHandler::getContentText
*/
public function testGetContentText_TextContent( $contentHandlerTextFallback ) {
$this->setMwGlobals( 'wgContentHandlerTextFallback', $contentHandlerTextFallback );
/**
* ContentHandler::getContentText should have thrown an exception for non-text Content object
* @expectedException MWException
+ * @covers ContentHandler::getContentText
*/
public function testGetContentText_NonTextContent_fail() {
$this->setMwGlobals( 'wgContentHandlerTextFallback', 'fail' );
ContentHandler::getContentText( $content );
}
+ /**
+ * @covers ContentHandler::getContentText
+ */
public function testGetContentText_NonTextContent_serialize() {
$this->setMwGlobals( 'wgContentHandlerTextFallback', 'serialize' );
$this->assertEquals( $content->serialize(), $text );
}
+ /**
+ * @covers ContentHandler::getContentText
+ */
public function testGetContentText_NonTextContent_ignore() {
$this->setMwGlobals( 'wgContentHandlerTextFallback', 'ignore' );
/**
* @dataProvider dataMakeContent
+ * @covers ContentHandler::makeContent
*/
public function testMakeContent( $data, $title, $modelId, $format, $expectedModelId, $expectedNativeData, $shouldFail ) {
$title = Title::newFromText( $title );
}
*/
+ /**
+ * @covers ContentHandler::runLegacyHooks
+ */
public function testRunLegacyHooks() {
Hooks::register( 'testRunLegacyHooks', __CLASS__ . '::dummyHookHandler' );
);
}
+ /**
+ * @covers CssContent::getModel
+ */
public function testGetModel() {
$content = $this->newContent( 'hello world.' );
$this->assertEquals( CONTENT_MODEL_CSS, $content->getModel() );
}
+ /**
+ * @covers CssContent::getContentHandler
+ */
public function testGetContentHandler() {
$content = $this->newContent( 'hello world.' );
/**
* @dataProvider dataEquals
+ * @covers CssContent::equals
*/
public function testEquals( Content $a, Content $b = null, $equal = false ) {
$this->assertEquals( $equal, $a->equals( $b ) );
);
}
+ /**
+ * @covers JavaScriptContent::addSectionHeader
+ */
public function testAddSectionHeader() {
$content = $this->newContent( 'hello world' );
$c = $content->addSectionHeader( 'test' );
);
}
+ /**
+ * @covers JavaScriptContent::matchMagicWord
+ */
public function testMatchMagicWord() {
$mw = MagicWord::get( "staticredirect" );
$this->assertFalse( $content->matchMagicWord( $mw ), "should not have matched magic word, since it's not wikitext" );
}
+ /**
+ * @covers JavaScriptContent::updateRedirect
+ */
public function testUpdateRedirect() {
$target = Title::newFromText( "testUpdateRedirect_target" );
$this->assertTrue( $content->equals( $newContent ), "content should be unchanged since it's not wikitext" );
}
+ /**
+ * @covers JavaScriptContent::getModel
+ */
public function testGetModel() {
$content = $this->newContent( "hello world." );
$this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $content->getModel() );
}
+ /**
+ * @covers JavaScriptContent::getContentHandler
+ */
public function testGetContentHandler() {
$content = $this->newContent( "hello world." );
/**
* @dataProvider dataGetParserOutput
+ * @covers TextContent::getParserOutput
*/
public function testGetParserOutput( $title, $model, $text, $expectedHtml, $expectedFields = null ) {
$title = Title::newFromText( $title );
/**
* @dataProvider dataPreSaveTransform
+ * @covers TextContent::preSaveTransform
*/
public function testPreSaveTransform( $text, $expected ) {
global $wgContLang;
/**
* @dataProvider dataPreloadTransform
+ * @covers TextContent::preloadTransform
*/
public function testPreloadTransform( $text, $expected ) {
global $wgContLang;
/**
* @dataProvider dataGetRedirectTarget
+ * @covers TextContent::getRedirectTarget
*/
public function testGetRedirectTarget( $text, $expected ) {
$content = $this->newContent( $text );
/**
* @dataProvider dataGetRedirectTarget
+ * @covers TextContent::isRedirect
*/
public function testIsRedirect( $text, $expected ) {
$content = $this->newContent( $text );
/**
* @dataProvider dataIsCountable
* @group Database
+ * @covers TextContent::isCountable
*/
public function testIsCountable( $text, $hasLinks, $mode, $expected ) {
$this->setMwGlobals( 'wgArticleCountMethod', $mode );
/**
* @dataProvider dataGetTextForSummary
+ * @covers TextContent::getTextForSummary
*/
public function testGetTextForSummary( $text, $maxlength, $expected ) {
$content = $this->newContent( $text );
$this->assertEquals( $expected, $content->getTextForSummary( $maxlength ) );
}
+ /**
+ * @covers TextContent::getTextForSearchIndex
+ */
public function testGetTextForSearchIndex() {
$content = $this->newContent( 'hello world.' );
$this->assertEquals( 'hello world.', $content->getTextForSearchIndex() );
}
+ /**
+ * @covers TextContent::copy
+ */
public function testCopy() {
$content = $this->newContent( 'hello world.' );
$copy = $content->copy();
$this->assertEquals( 'hello world.', $copy->getNativeData() );
}
+ /**
+ * @covers TextContent::getSize
+ */
public function testGetSize() {
$content = $this->newContent( 'hello world.' );
$this->assertEquals( 12, $content->getSize() );
}
+ /**
+ * @covers TextContent::getNativeData
+ */
public function testGetNativeData() {
$content = $this->newContent( 'hello world.' );
$this->assertEquals( 'hello world.', $content->getNativeData() );
}
+ /**
+ * @covers TextContent::getWikitextForTransclusion
+ */
public function testGetWikitextForTransclusion() {
$content = $this->newContent( 'hello world.' );
$this->assertEquals( 'hello world.', $content->getWikitextForTransclusion() );
}
+ /**
+ * @covers TextContent::getModel
+ */
public function testGetModel() {
$content = $this->newContent( "hello world." );
$this->assertEquals( CONTENT_MODEL_TEXT, $content->getModel() );
}
+ /**
+ * @covers TextContent::getContentHandler
+ */
public function testGetContentHandler() {
$content = $this->newContent( "hello world." );
/**
* @dataProvider dataIsEmpty
+ * @covers TextContent::isEmpty
*/
public function testIsEmpty( $text, $empty ) {
$content = $this->newContent( $text );
/**
* @dataProvider dataEquals
+ * @covers TextContent::equals
*/
public function testEquals( Content $a, Content $b = null, $equal = false ) {
$this->assertEquals( $equal, $a->equals( $b ) );
/**
* @dataProvider dataGetDeletionUpdates
+ * @covers TextContent::getDeletionUpdates
*/
public function testDeletionUpdates( $title, $model, $text, $expectedStuff ) {
$ns = $this->getDefaultWikitextNS();
/**
* @dataProvider provideConvert
+ * @covers TextContent::convert
*/
public function testConvert( $text, $model, $lossy, $expectedNative ) {
$content = $this->newContent( $text );
$this->handler = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT );
}
+ /**
+ * @covers WikitextContentHandler::serializeContent
+ */
public function testSerializeContent() {
$content = new WikitextContent( 'hello world' );
}
}
+ /**
+ * @covers WikitextContentHandler::unserializeContent
+ */
public function testUnserializeContent() {
$content = $this->handler->unserializeContent( 'hello world' );
$this->assertEquals( 'hello world', $content->getNativeData() );
}
}
+ /**
+ * @covers WikitextContentHandler::makeEmptyContent
+ */
public function testMakeEmptyContent() {
$content = $this->handler->makeEmptyContent();
* @dataProvider provideMakeRedirectContent
* @param Title|string $title Title object or string for Title::newFromText()
* @param string $expected Serialized form of the content object built
+ * @covers WikitextContentHandler::makeRedirectContent
*/
public function testMakeRedirectContent( $title, $expected ) {
global $wgContLang;
/**
* @dataProvider dataIsSupportedFormat
+ * @covers WikitextContentHandler::isSupportedFormat
*/
public function testIsSupportedFormat( $format, $supported ) {
$this->assertEquals( $supported, $this->handler->isSupportedFormat( $format ) );
/**
* @dataProvider dataMerge3
+ * @covers WikitextContentHandler::merge3
*/
public function testMerge3( $old, $mine, $yours, $expected ) {
$this->checkHasDiff3();
/**
* @dataProvider dataGetAutosummary
+ * @covers WikitextContentHandler::getAutosummary
*/
public function testGetAutosummary( $old, $new, $flags, $expected ) {
$oldContent = is_null( $old ) ? null : new WikitextContent( $old );
/**
* @dataProvider dataGetSecondaryDataUpdates
* @group Database
+ * @covers WikitextContent::getSecondaryDataUpdates
*/
public function testGetSecondaryDataUpdates( $title, $model, $text, $expectedStuff ) {
$ns = $this->getDefaultWikitextNS();
/**
* @dataProvider dataGetSection
+ * @covers WikitextContent::getSection
*/
public function testGetSection( $text, $sectionId, $expectedText ) {
$content = $this->newContent( $text );
/**
* @dataProvider dataReplaceSection
+ * @covers WikitextContent::replaceSection
*/
public function testReplaceSection( $text, $section, $with, $sectionTitle, $expected ) {
$content = $this->newContent( $text );
$this->assertEquals( $expected, is_null( $c ) ? null : $c->getNativeData() );
}
+ /**
+ * @covers WikitextContent::addSectionHeader
+ */
public function testAddSectionHeader() {
$content = $this->newContent( 'hello world' );
$content = $content->addSectionHeader( 'test' );
);
}
+ /**
+ * @covers WikitextContent::matchMagicWord
+ */
public function testMatchMagicWord() {
$mw = MagicWord::get( "staticredirect" );
$this->assertFalse( $content->matchMagicWord( $mw ), "should not have matched magic word" );
}
+ /**
+ * @covers WikitextContent::updateRedirect
+ */
public function testUpdateRedirect() {
$target = Title::newFromText( "testUpdateRedirect_target" );
$this->assertEquals( $target->getFullText(), $newContent->getRedirectTarget()->getFullText() );
}
+ /**
+ * @covers WikitextContent::getModel
+ */
public function testGetModel() {
$content = $this->newContent( "hello world." );
$this->assertEquals( CONTENT_MODEL_WIKITEXT, $content->getModel() );
}
+ /**
+ * @covers WikitextContent::getContentHandler
+ */
public function testGetContentHandler() {
$content = $this->newContent( "hello world." );
/**
* @dataProvider provideDiapers
+ * @covers DatabaseMysqlBase::addIdentifierQuotes
*/
function testAddIdentifierQuotes( $expected, $in ) {
$db = new FakeDatabaseMysqlBase();
*/
class DatabaseSQLTest extends MediaWikiTestCase {
+ /**
+ * @var DatabaseTestHelper
+ */
private $database;
protected function setUp() {
/**
* @dataProvider provideSelect
+ * @covers DatabaseBase::select
*/
function testSelect( $sql, $sqlText ) {
$this->database->select(
/**
* @dataProvider provideUpdate
+ * @covers DatabaseBase::update
*/
function testUpdate( $sql, $sqlText ) {
$this->database->update(
/**
* @dataProvider provideDelete
+ * @covers DatabaseBase::delete
*/
function testDelete( $sql, $sqlText ) {
$this->database->delete(
/**
* @dataProvider provideUpsert
+ * @covers DatabaseBase::upsert
*/
function testUpsert( $sql, $sqlText ) {
$this->database->upsert(
/**
* @dataProvider provideDeleteJoin
+ * @covers DatabaseBase::deleteJoin
*/
function testDeleteJoin( $sql, $sqlText ) {
$this->database->deleteJoin(
/**
* @dataProvider provideInsert
+ * @covers DatabaseBase::insert
*/
function testInsert( $sql, $sqlText ) {
$this->database->insert(
/**
* @dataProvider provideInsertSelect
+ * @covers DatabaseBase::insertSelect
*/
function testInsertSelect( $sql, $sqlText ) {
$this->database->insertSelect(
/**
* @dataProvider provideReplace
+ * @covers DatabaseBase::replace
*/
function testReplace( $sql, $sqlText ) {
$this->database->replace(
/**
* @dataProvider provideNativeReplace
+ * @covers DatabaseBase::nativeReplace
*/
function testNativeReplace( $sql, $sqlText ) {
$this->database->nativeReplace(
/**
* @dataProvider provideConditional
+ * @covers DatabaseBase::conditional
*/
function testConditional( $sql, $sqlText ) {
$this->assertEquals( trim( $this->database->conditional(
/**
* @dataProvider provideBuildConcat
+ * @covers DatabaseBase::buildConcat
*/
function testBuildConcat( $stringList, $sqlText ) {
$this->assertEquals( trim( $this->database->buildConcat(
/**
* @dataProvider provideBuildLike
+ * @covers DatabaseBase::buildLike
*/
function testBuildLike( $array, $sqlText ) {
$this->assertEquals( trim( $this->database->buildLike(
/**
* @dataProvider provideUnionQueries
+ * @covers DatabaseBase::unionQueries
*/
function testUnionQueries( $sql, $sqlText ) {
$this->assertEquals( trim( $this->database->unionQueries(
);
}
+ /**
+ * @covers DatabaseBase::commit
+ */
function testTransactionCommit() {
$this->database->begin( __METHOD__ );
$this->database->commit( __METHOD__ );
$this->assertLastSql( 'BEGIN; COMMIT' );
}
+ /**
+ * @covers DatabaseBase::rollback
+ */
function testTransactionRollback() {
$this->database->begin( __METHOD__ );
$this->database->rollback( __METHOD__ );
$this->assertLastSql( 'BEGIN; ROLLBACK' );
}
+ /**
+ * @covers DatabaseBase::dropTable
+ */
function testDropTable() {
$this->database->setExistingTables( array( 'table' ) );
$this->database->dropTable( 'table', __METHOD__ );
$this->assertLastSql( 'DROP TABLE table' );
}
+ /**
+ * @covers DatabaseBase::dropTable
+ */
function testDropNonExistingTable() {
$this->assertFalse(
$this->database->dropTable( 'non_existing', __METHOD__ )
* @group medium
*/
class DatabaseSqliteTest extends MediaWikiTestCase {
+
+ /**
+ * @var MockDatabaseSqlite
+ */
var $db;
protected function setUp() {
/**
* @dataProvider provideAddQuotes()
+ * @covers DatabaseSqlite::addQuotes
*/
public function testAddQuotes( $value, $expected ) {
// check quoting
}
}
+ /**
+ * @covers DatabaseSqlite::replaceVars
+ */
public function testReplaceVars() {
$this->assertEquals( 'foo', $this->replaceVars( 'foo' ), "Don't break anything accidentally" );
);
}
+ /**
+ * @covers DatabaseSqlite::tableName
+ */
public function testTableName() {
// @todo Moar!
$db = new DatabaseSqliteStandalone( ':memory:' );
$this->assertEquals( 'foobar', $db->tableName( 'bar' ) );
}
+ /**
+ * @covers DatabaseSqlite::duplicateTableStructure
+ */
public function testDuplicateTableStructure() {
$db = new DatabaseSqliteStandalone( ':memory:' );
$db->query( 'CREATE TABLE foo(foo, barfoo)' );
);
}
+ /**
+ * @covers DatabaseSqlite::duplicateTableStructure
+ */
public function testDuplicateTableStructureVirtual() {
$db = new DatabaseSqliteStandalone( ':memory:' );
if ( $db->getFulltextSearchModule() != 'FTS3' ) {
);
}
+ /**
+ * @covers DatabaseSqlite::deleteJoin
+ */
public function testDeleteJoin() {
$db = new DatabaseSqliteStandalone( ':memory:' );
$db->query( 'CREATE TABLE a (a_1)', __METHOD__ );
}
}
+ /**
+ * @covers DatabaseSqlite::insertId
+ */
public function testInsertIdType() {
$db = new DatabaseSqliteStandalone( ':memory:' );
- $this->assertInstanceOf( 'ResultWrapper',
- $db->query( 'CREATE TABLE a ( a_1 )', __METHOD__ ), "Database creationg" );
- $this->assertTrue( $db->insert( 'a', array( 'a_1' => 10 ), __METHOD__ ),
- "Insertion worked" );
+
+ $databaseCreation = $db->query( 'CREATE TABLE a ( a_1 )', __METHOD__ );
+ $this->assertInstanceOf( 'ResultWrapper', $databaseCreation, "Database creation" );
+
+ $insertion = $db->insert( 'a', array( 'a_1' => 10 ), __METHOD__ );
+ $this->assertTrue( $insertion, "Insertion worked" );
+
$this->assertInternalType( 'integer', $db->insertId(), "Actual typecheck" );
$this->assertTrue( $db->close(), "closing database" );
}
* @group DatabaseBase
*/
class DatabaseTest extends MediaWikiTestCase {
- var $db, $functionTest = false;
+ /**
+ * @var DatabaseBase
+ */
+ var $db;
+ var $functionTest = false;
protected function setUp() {
parent::setUp();
$this->functionTest = false;
}
}
-
+ /**
+ * @covers DatabaseBase::dropTable
+ */
function testAddQuotesNull() {
$check = "NULL";
if ( $this->db->getType() === 'sqlite' || $this->db->getType() === 'oracle' ) {