* $wgDebugPrintHttpHeaders - The default of including HTTP headers in the
debug log channel is no longer configurable. The debug log itself remains
configurable via $wgDebugLogFile.
+* $wgMsgCacheExpiry - The MessageCache uses 24 hours as the expiry for values
+ stored in WANObjectCache. This is no longer configurable.
* $wgPasswordSalt – This setting, used for migrating exceptionally old, insecure
password setups and deprecated since 1.24, is now removed.
* $wgDBOracleDRCP - If you must use persistent connections, set DBO_PERSISTENT
([[Special:NewSection/Test]] redirects to creating a new section in "Test").
Otherwise, it displays a basic interface to allow the end user to specify
the target manually.
+* (T220447) Special:Contributions/newbies has been removed for performance and
+ usefulness reasons. Use Special:RecentChanges?userExpLevel=newcomer instead.
+* Special:NewFiles/newbies has been removed for performance and usefulness
+ reasons. Use Special:RecentChanges?userExpLevel=newcomer&namespace=6 instead.
=== New developer features in 1.34 ===
* The ImgAuthModifyHeaders hook was added to img_auth.php to allow modification
* Constructing MovePage directly is deprecated. Use MovePageFactory.
* TempFSFile::factory() has been deprecated. Use TempFSFileFactory instead.
* wfIsBadImage() is deprecated. Use the BadFileLookup service instead.
+* Building a new SearchResult is hard-deprecated, always call
+ SearchResult::newFromTitle(). This class is being refactored into an abstract
+ class. If you extend this class please be sure to override all its methods
+ or extend RevisionSearchResult.
=== Other changes in 1.34 ===
* …
'MediaWiki\\Diff\\ComplexityException' => __DIR__ . '/includes/diff/ComplexityException.php',
'MediaWiki\\Diff\\WordAccumulator' => __DIR__ . '/includes/diff/WordAccumulator.php',
'MediaWiki\\FileBackend\\FSFile\\TempFSFileFactory' => __DIR__ . '/includes/libs/filebackend/fsfile/TempFSFileFactory.php',
+ 'MediaWiki\\FileBackend\\LockManager\\LockManagerGroupFactory' => __DIR__ . '/includes/filebackend/lockmanager/LockManagerGroupFactory.php',
'MediaWiki\\HeaderCallback' => __DIR__ . '/includes/HeaderCallback.php',
'MediaWiki\\Http\\HttpRequestFactory' => __DIR__ . '/includes/http/HttpRequestFactory.php',
'MediaWiki\\Installer\\InstallException' => __DIR__ . '/includes/installer/InstallException.php',
'RevisionItemBase' => __DIR__ . '/includes/revisionlist/RevisionItemBase.php',
'RevisionList' => __DIR__ . '/includes/revisionlist/RevisionList.php',
'RevisionListBase' => __DIR__ . '/includes/revisionlist/RevisionListBase.php',
+ 'RevisionSearchResult' => __DIR__ . '/includes/search/RevisionSearchResult.php',
+ 'RevisionSearchResultTrait' => __DIR__ . '/includes/search/RevisionSearchResultTrait.php',
'RiffExtractor' => __DIR__ . '/includes/libs/RiffExtractor.php',
'RightsLogFormatter' => __DIR__ . '/includes/logging/RightsLogFormatter.php',
'RollbackAction' => __DIR__ . '/includes/actions/RollbackAction.php',
cleared by: Language::loadLocalisation()
Message Cache:
- backend: $wgMessageCacheType
- key: $wgDBname:messages, $wgDBname:messages-hash, $wgDBname:messages-status
- ex: wikidb:messages, wikidb:messages-hash, wikidb:messages-status
- stores: an array where the keys are DB keys and the values are messages
- set in: wfMessage(), Article::editUpdates() and Title::moveTo()
- expiry: $wgMsgCacheExpiry
- cleared by: nothing
+ See MessageCache.php.
Newtalk:
key: $wgDBname:newtalk:ip:$ip
*/
$wgUseDatabaseMessages = true;
-/**
- * Expiry time for the message cache key
- */
-$wgMsgCacheExpiry = 86400;
-
/**
* Maximum entry size in the message cache, in bytes
*/
// Don't share DB, storage, or memcached connections
MediaWikiServices::resetChildProcessServices();
FileBackendGroup::destroySingleton();
- LockManagerGroup::destroySingletons();
JobQueueGroup::destroySingletons();
ObjectCache::clear();
RedisConnectionPool::destroySingletons();
if ( $label == '' ) {
$label = $title->getPrefixedText();
}
- $encLabel = htmlspecialchars( $label );
$currentExists = $time
&& MediaWikiServices::getInstance()->getRepoGroup()->findFile( $title ) !== false;
if ( ( $wgUploadMissingFileUrl || $wgUploadNavigationUrl || $wgEnableUploads )
&& !$currentExists
) {
- $redir = RepoGroup::singleton()->getLocalRepo()->checkRedirect( $title );
-
- if ( $redir ) {
- // We already know it's a redirect, so mark it
- // accordingly
+ if ( RepoGroup::singleton()->getLocalRepo()->checkRedirect( $title ) ) {
+ // We already know it's a redirect, so mark it accordingly
return self::link(
$title,
- $encLabel,
+ htmlspecialchars( $label ),
[ 'class' => 'mw-redirect' ],
wfCgiToArray( $query ),
[ 'known', 'noclasses' ]
);
}
- $href = self::getUploadUrl( $title, $query );
-
- return '<a href="' . htmlspecialchars( $href ) . '" class="new" title="' .
- htmlspecialchars( $title->getPrefixedText(), ENT_QUOTES ) . '">' .
- $encLabel . '</a>';
+ return Html::element( 'a', [
+ 'href' => self::getUploadUrl( $title, $query ),
+ 'class' => 'new',
+ 'title' => $title->getPrefixedText()
+ ], $label );
}
- return self::link( $title, $encLabel, [], wfCgiToArray( $query ), [ 'known', 'noclasses' ] );
+ return self::link(
+ $title,
+ htmlspecialchars( $label ),
+ [],
+ wfCgiToArray( $query ),
+ [ 'known', 'noclasses' ]
+ );
}
/**
use MediaWiki\Block\BlockManager;
use MediaWiki\Block\BlockRestrictionStore;
use MediaWiki\FileBackend\FSFile\TempFSFileFactory;
+use MediaWiki\FileBackend\LockManager\LockManagerGroupFactory;
use MediaWiki\Http\HttpRequestFactory;
use MediaWiki\Page\MovePageFactory;
use MediaWiki\Permissions\PermissionManager;
return $this->getService( 'LocalServerObjectCache' );
}
+ /**
+ * @since 1.34
+ * @return LockManagerGroupFactory
+ */
+ public function getLockManagerGroupFactory() : LockManagerGroupFactory {
+ return $this->getService( 'LockManagerGroupFactory' );
+ }
+
/**
* @since 1.32
* @return MagicWordFactory
use MediaWiki\Config\ConfigRepository;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\FileBackend\FSFile\TempFSFileFactory;
+use MediaWiki\FileBackend\LockManager\LockManagerGroupFactory;
use MediaWiki\Http\HttpRequestFactory;
use MediaWiki\Interwiki\ClassicInterwikiLookup;
use MediaWiki\Interwiki\InterwikiLookup;
return \ObjectCache::newFromParams( $config->get( 'ObjectCaches' )[$cacheId] );
},
+ 'LockManagerGroupFactory' => function ( MediaWikiServices $services ) : LockManagerGroupFactory {
+ return new LockManagerGroupFactory(
+ WikiMap::getCurrentWikiDbDomain()->getId(),
+ $services->getMainConfig()->get( 'LockManagers' ),
+ $services->getDBLoadBalancerFactory()
+ );
+ },
+
'MagicWordFactory' => function ( MediaWikiServices $services ) : MagicWordFactory {
return new MagicWordFactory( $services->getContentLanguage() );
},
? $services->getLocalServerObjectCache()
: new EmptyBagOStuff(),
$mainConfig->get( 'UseDatabaseMessages' ),
- $mainConfig->get( 'MsgCacheExpiry' ),
$services->getContentLanguage()
);
},
header( 'Cache-Control: no-cache' );
header( 'Content-Type: text/html; charset=utf-8' );
HttpStatus::header( 400 );
- $error = wfMessage( 'nonwrite-api-promise-error' )->escaped();
- $content = <<<EOT
+ $errorHtml = wfMessage( 'nonwrite-api-promise-error' )
+ ->useDatabase( false )
+ ->inContentLanguage()
+ ->escaped();
+ $content = <<<HTML
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8" /></head>
<body>
-$error
+$errorHtml
</body>
</html>
-EOT;
+HTML;
header( 'Content-Length: ' . strlen( $content ) );
echo $content;
die();
$this->dieWithError( [ 'apierror-bad-badfilecontexttitle', $p ], 'invalid-title' );
}
} else {
- $badFileContextTitle = false;
+ $badFileContextTitle = null;
}
$pageIds = $this->getPageSet()->getGoodAndMissingTitlesByNamespace();
"apiwarn-deprecation-missingparam": "Comme <var>$1</var> n’a pas été spécifié, un format ancien a été utilisé pour la sortie. Ce format est obsolète, et dans le futur, le nouveau format sera toujours utilisé.",
"apiwarn-deprecation-parameter": "Le paramètre <var>$1</var> est désuet.",
"apiwarn-deprecation-parse-headitems": "<kbd>prop=headitems</kbd> est désuet depuis MédiaWiki 1.28. Utilisez <kbd>prop=headhtml</kbd> lors de la création de nouveaux documents HTML, ou <kbd>prop=modules|jsconfigvars</kbd> lors de la mise à jour d’un document côté client.",
+ "apiwarn-deprecation-post-without-content-type": "Une requête POST a été faite sans entête <code>Content-Type</code>. Cela ne fonctionne pas de façon fiable.",
"apiwarn-deprecation-purge-get": "L’utilisation de <kbd>action=purge</kbd> via un GET est désuète. Utiliser POST à la place.",
"apiwarn-deprecation-withreplacement": "<kbd>$1</kbd> est désuet. Veuillez utiliser <kbd>$2</kbd> à la place.",
"apiwarn-difftohidden": "Impossible de faire un diff avec r$1 : le contenu est masqué.",
"apierror-unknownformat": "Okänt format \"$1\".",
"apiwarn-compare-no-next": "Sidversion $2 är den senaste sidversionen av $1, det finns ingen sidversion för <kbd>torelative=next</kbd> att jämföra med.",
"apiwarn-compare-no-prev": "Sidversionen $2 är den tidigaste sidversion för $1, det finns ingen sidversion för <kbd>torelative=prev</kbd> att jämföra med.",
+ "apiwarn-deprecation-post-without-content-type": "En POST-begäran gjordes utan en <code>Content-Type</code> i sidhuvudet. Det fungerar inte ordentligt.",
"api-feed-error-title": "Fel ($1)"
}
"科劳",
"SolidBlock",
"神樂坂秀吉",
- "94rain"
+ "94rain",
+ "予弦"
]
},
"apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|文档]]\n* [[mw:Special:MyLanguage/API:FAQ|常见问题]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api 邮件列表]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce API公告]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R 程序错误与功能请求]\n</div>\n<strong>状态信息:</strong>MediaWiki API是一个成熟稳定的,不断受到支持和改进的界面。尽管我们尽力避免,但偶尔也需要作出重大更新;请订阅[https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ mediawiki-api-announce 邮件列表]以便获得更新通知。\n\n<strong>错误请求:</strong>当API收到错误请求时,HTTP header将会返回一个包含\"MediaWiki-API-Error\"的值,随后header的值与error code将会送回并设置为相同的值。详细信息请参阅[[mw:Special:MyLanguage/API:Errors_and_warnings|API:错误与警告]]。\n\n<p class=\"mw-apisandbox-link\"><strong>测试中:</strong>测试API请求的易用性,请参见[[Special:ApiSandbox]]。</p>",
$block = DatabaseBlock::newFromID( $blockCookieId );
if (
$block instanceof DatabaseBlock &&
- $this->shouldApplyCookieBlock( $block, $user->isAnon() )
+ $this->shouldApplyCookieBlock( $block, !$user->isRegistered() )
) {
return $block;
}
/** How long memcached locks last */
const LOCK_TTL = 30;
+ /**
+ * Lifetime for cache, for keys stored in $wanCache, in seconds.
+ * @var int
+ */
+ const WAN_TTL = IExpiringStore::TTL_DAY;
+
/**
* Process cache of loaded messages that are defined in MediaWiki namespace
*
*/
protected $mDisable;
- /**
- * Lifetime for cache, used by object caching.
- * Set on construction, see __construct().
- */
- protected $mExpiry;
-
/**
* Message cache has its own parser which it uses to transform messages
* @var ParserOptions
* @param BagOStuff $clusterCache
* @param BagOStuff $serverCache
* @param bool $useDB Whether to look for message overrides (e.g. MediaWiki: pages)
- * @param int $expiry Lifetime for cache. @see $mExpiry.
* @param Language|null $contLang Content language of site
*/
public function __construct(
BagOStuff $clusterCache,
BagOStuff $serverCache,
$useDB,
- $expiry,
Language $contLang = null
) {
$this->wanCache = $wanCache;
$this->cache = new MapCacheLRU( 5 ); // limit size for sanity
$this->mDisable = !$useDB;
- $this->mExpiry = $expiry;
$this->contLang = $contLang ?? MediaWikiServices::getInstance()->getContentLanguage();
}
// Set the text for small software-defined messages in the main cache map
$revisionStore = MediaWikiServices::getInstance()->getRevisionStore();
$revQuery = $revisionStore->getQueryInfo( [ 'page', 'user' ] );
+
+ // T231196: MySQL/MariaDB (10.1.37) can sometimes irrationally decide that querying `actor` then
+ // `revision` then `page` is somehow better than starting with `page`. Tell it not to reorder the
+ // query (and also reorder it ourselves because as generated by RevisionStore it'll have
+ // `revision` first rather than `page`).
+ $revQuery['joins']['revision'] = $revQuery['joins']['page'];
+ unset( $revQuery['joins']['page'] );
+ // It isn't actually necesssary to reorder $revQuery['tables'] as Database does the right thing
+ // when join conditions are given for all joins, but Gergő is wary of relying on that so pull
+ // `page` to the start.
+ $revQuery['tables'] = array_merge(
+ [ 'page' ],
+ array_diff( $revQuery['tables'], [ 'page' ] )
+ );
+
$res = $dbr->select(
$revQuery['tables'],
$revQuery['fields'],
'page_latest = rev_id' // get the latest revision only
] ),
__METHOD__ . "($code)-small",
- [],
+ [ 'STRAIGHT_JOIN' ],
$revQuery['joins']
);
foreach ( $res as $row ) {
# messages larger than $wgMaxMsgCacheEntrySize, since those are only
# stored and fetched from memcache.
$cache['HASH'] = md5( serialize( $cache ) );
- $cache['EXPIRY'] = wfTimestamp( TS_MW, time() + $this->mExpiry );
+ $cache['EXPIRY'] = wfTimestamp( TS_MW, time() + self::WAN_TTL );
unset( $cache['EXCESSIVE'] ); // only needed for hash
return $cache;
$this->wanCache->set(
$this->bigMessageCacheKey( $cache['HASH'], $title ),
' ' . $newTextByTitle[$title],
- $this->mExpiry
+ self::WAN_TTL
);
}
// Mark this cache as definitely being "latest" (non-volatile) so
$fname = __METHOD__;
return $this->srvCache->getWithSetCallback(
$this->srvCache->makeKey( 'messages-big', $hash, $dbKey ),
- IExpiringStore::TTL_MINUTE,
+ BagOStuff::TTL_HOUR,
function () use ( $code, $dbKey, $hash, $fname ) {
return $this->wanCache->getWithSetCallback(
$this->bigMessageCacheKey( $hash, $dbKey ),
- $this->mExpiry,
+ self::WAN_TTL,
function ( $oldValue, &$ttl, &$setOpts ) use ( $dbKey, $code, $fname ) {
// Try loading the message from the database
$dbr = wfGetDB( DB_REPLICA );
*/
use MediaWiki\MediaWikiServices;
use MediaWiki\Logger\LoggerFactory;
+use Wikimedia\Rdbms\LBFactory;
/**
* Class to handle file lock manager registration
* @since 1.19
*/
class LockManagerGroup {
- /** @var LockManagerGroup[] (domain => LockManagerGroup) */
- protected static $instances = [];
+ /** @var string domain (usually wiki ID) */
+ protected $domain;
- protected $domain; // string; domain (usually wiki ID)
+ /** @var LBFactory */
+ protected $lbFactory;
/** @var array Array of (name => ('class' => ..., 'config' => ..., 'instance' => ...)) */
protected $managers = [];
/**
+ * Do not call this directly. Use LockManagerGroupFactory.
+ *
* @param string $domain Domain (usually wiki ID)
+ * @param array[] $lockManagerConfigs In format of $wgLockManagers
+ * @param LBFactory $lbFactory
*/
- protected function __construct( $domain ) {
+ public function __construct( $domain, array $lockManagerConfigs, LBFactory $lbFactory ) {
$this->domain = $domain;
- }
-
- /**
- * @param bool|string $domain Domain (usually wiki ID). Default: false.
- * @return LockManagerGroup
- */
- public static function singleton( $domain = false ) {
- if ( $domain === false ) {
- $domain = WikiMap::getCurrentWikiDbDomain()->getId();
- }
+ $this->lbFactory = $lbFactory;
- if ( !isset( self::$instances[$domain] ) ) {
- self::$instances[$domain] = new self( $domain );
- self::$instances[$domain]->initFromGlobals();
- }
-
- return self::$instances[$domain];
- }
-
- /**
- * Destroy the singleton instances
- */
- public static function destroySingletons() {
- self::$instances = [];
- }
-
- /**
- * Register lock managers from the global variables
- */
- protected function initFromGlobals() {
- global $wgLockManagers;
-
- $this->register( $wgLockManagers );
- }
-
- /**
- * Register an array of file lock manager configurations
- *
- * @param array $configs
- * @throws Exception
- */
- protected function register( array $configs ) {
- foreach ( $configs as $config ) {
+ foreach ( $lockManagerConfigs as $config ) {
$config['domain'] = $this->domain;
if ( !isset( $config['name'] ) ) {
throw new Exception( "Cannot register a lock manager with no name." );
}
}
+ /**
+ * @deprecated since 1.34, use LockManagerGroupFactory
+ *
+ * @param bool|string $domain Domain (usually wiki ID). Default: false.
+ * @return LockManagerGroup
+ */
+ public static function singleton( $domain = false ) {
+ return MediaWikiServices::getInstance()->getLockManagerGroupFactory()
+ ->getLockManagerGroup( $domain );
+ }
+
+ /**
+ * Destroy the singleton instances
+ *
+ * @deprecated since 1.34, use resetServiceForTesting() on LockManagerGroupFactory
+ */
+ public static function destroySingletons() {
+ MediaWikiServices::getInstance()->resetServiceForTesting( 'LockManagerGroupFactory' );
+ }
+
/**
* Get the lock manager object with a given name
*
$class = $this->managers[$name]['class'];
$config = $this->managers[$name]['config'];
if ( $class === DBLockManager::class ) {
- $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
- $lb = $lbFactory->getMainLB( $config['domain'] );
+ $lb = $this->lbFactory->getMainLB( $config['domain'] );
$config['dbServers']['localDBMaster'] = $lb->getLazyConnectionRef(
DB_MASTER,
[],
}
$config['logger'] = LoggerFactory::getInstance( 'LockManager' );
+ // XXX Looks like phan is right, we are trying to instantiate an abstract class and it
+ // throws. Did this ever work? Presumably we need to detect the right subclass? Or
+ // should we just get rid of this? It looks like it never worked since it was first
+ // introduced by 0cf832a3394 in 2016, so if no one's complained until now, clearly it
+ // can't be very useful?
// @phan-suppress-next-line PhanTypeInstantiateAbstract
$this->managers[$name]['instance'] = new $class( $config );
}
* Get the default lock manager configured for the site.
* Returns NullLockManager if no lock manager could be found.
*
+ * XXX This looks unused, should we just get rid of it?
+ *
* @return LockManager
*/
public function getDefault() {
* or at least some other effective configured lock manager.
* Throws an exception if no lock manager could be found.
*
+ * XXX This looks unused, should we just get rid of it?
+ *
* @return LockManager
* @throws Exception
*/
--- /dev/null
+<?php
+
+namespace MediaWiki\FileBackend\LockManager;
+
+use LockManagerGroup;
+use Wikimedia\Rdbms\LBFactory;
+
+/**
+ * Service to construct LockManagerGroups.
+ */
+class LockManagerGroupFactory {
+ /** @var string */
+ private $defaultDomain;
+
+ /** @var array */
+ private $lockManagerConfigs;
+
+ /** @var LBFactory */
+ private $lbFactory;
+
+ /** @var LockManagerGroup[] (domain => LockManagerGroup) */
+ private $instances = [];
+
+ /**
+ * Do not call directly, use MediaWikiServices.
+ *
+ * @param string $defaultDomain
+ * @param array $lockManagerConfigs In format of $wgLockManagers
+ * @param LBFactory $lbFactory
+ */
+ public function __construct( $defaultDomain, array $lockManagerConfigs, LBFactory $lbFactory ) {
+ $this->defaultDomain = $defaultDomain;
+ $this->lockManagerConfigs = $lockManagerConfigs;
+ $this->lbFactory = $lbFactory;
+ }
+
+ /**
+ * @param string|bool $domain Domain (usually wiki ID). false for the default (normally the
+ * current wiki's domain).
+ * @return LockManagerGroup
+ */
+ public function getLockManagerGroup( $domain = false ) : LockManagerGroup {
+ if ( $domain === false ) {
+ $domain = $this->defaultDomain;
+ }
+
+ if ( !isset( $this->instances[$domain] ) ) {
+ $this->instances[$domain] =
+ new LockManagerGroup( $domain, $this->lockManagerConfigs, $this->lbFactory );
+ }
+
+ return $this->instances[$domain];
+ }
+}
public $mParser;
/**
- * @var Title Contextual title, used when images are being screened against
+ * @var Title|null Contextual title, used when images are being screened against
* the bad image list
*/
- protected $contextTitle = false;
+ protected $contextTitle = null;
/** @var array */
protected $mAttribs = [];
/**
* Set the contextual title
*
- * @param Title $title Contextual title
+ * @param Title|null $title Contextual title
*/
public function setContextTitle( $title ) {
$this->contextTitle = $title;
/**
* Get the contextual title, if applicable
*
- * @return Title|bool Title or false
+ * @return Title|null
*/
public function getContextTitle() {
- return is_object( $this->contextTitle ) && $this->contextTitle instanceof Title
- ? $this->contextTitle
- : false;
+ return $this->contextTitle;
}
/**
if ( self::isEnabled() ) {
throw new Exception( 'Profiling is already enabled.' );
}
+
+ $args = [ $flags ];
+ if ( $options ) {
+ $args[] = $options;
+ }
+
self::$enabled = true;
self::callAny(
[
'tideways_enable',
'tideways_xhprof_enable'
],
- [ $flags, $options ]
+ $args
);
}
/** @var array List of shared database already attached to this connection */
private $alreadyAttached = [];
- /** @var bool Whether full text is enabled */
- private static $fulltextEnabled = null;
-
/** @var string[] See https://www.sqlite.org/lang_transaction.html */
private static $VALID_TRX_MODES = [ '', 'DEFERRED', 'IMMEDIATE', 'EXCLUSIVE' ];
}
/**
- * Attaches external database to our connection, see https://sqlite.org/lang_attach.html
- * for details.
+ * Attaches external database to the connection handle
+ *
+ * @see https://sqlite.org/lang_attach.html
*
* @param string $name Database name to be used in queries like
* SELECT foo FROM dbname.table
* @param string $pageStatus
* @throws MWException
*/
- public function actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit,
- $oldid, $watchers, $pageStatus = 'changed' ) {
+ public function actuallyNotifyOnPageChange(
+ $editor,
+ $title,
+ $timestamp,
+ $summary,
+ $minorEdit,
+ $oldid,
+ $watchers,
+ $pageStatus = 'changed'
+ ) {
# we use $wgPasswordSender as sender's address
global $wgUsersNotifiedOnAllChanges;
global $wgEnotifWatchlist, $wgBlockDisablesLogin;
global $wgEnotifMinorEdits, $wgEnotifUserTalk;
+ $messageCache = MediaWikiServices::getInstance()->getMessageCache();
+
# The following code is only run, if several conditions are met:
# 1. EmailNotification for pages (other than user_talk pages) must be enabled
# 2. minor edits (changes) are only regarded if the global flag indicates so
&& $this->canSendUserTalkEmail( $editor, $title, $minorEdit )
) {
$targetUser = User::newFromName( $title->getText() );
- $this->compose( $targetUser, self::USER_TALK );
+ $this->compose( $targetUser, self::USER_TALK, $messageCache );
$userTalkId = $targetUser->getId();
}
&& !( $wgBlockDisablesLogin && $watchingUser->getBlock() )
&& Hooks::run( 'SendWatchlistEmailNotification', [ $watchingUser, $title, $this ] )
) {
- $this->compose( $watchingUser, self::WATCHLIST );
+ $this->compose( $watchingUser, self::WATCHLIST, $messageCache );
}
}
}
continue;
}
$user = User::newFromName( $name );
- $this->compose( $user, self::ALL_CHANGES );
+ $this->compose( $user, self::ALL_CHANGES, $messageCache );
}
$this->sendMails();
/**
* Generate the generic "this page has been changed" e-mail text.
+ * @param MessageCache $messageCache
*/
- private function composeCommonMailtext() {
+ private function composeCommonMailtext( MessageCache $messageCache ) {
global $wgPasswordSender, $wgNoReplyAddress;
global $wgEnotifFromEditor, $wgEnotifRevealEditorAddress;
global $wgEnotifImpersonal, $wgEnotifUseRealName;
$body = wfMessage( 'enotif_body' )->inContentLanguage()->plain();
$body = strtr( $body, $keys );
- $body = MessageCache::singleton()->transform( $body, false, null, $this->title );
+ $body = $messageCache->transform( $body, false, null, $this->title );
$this->body = wordwrap( strtr( $body, $postTransformKeys ), 72 );
# Reveal the page editor's address as REPLY-TO address only if
* Call sendMails() to send any mails that were queued.
* @param User $user
* @param string $source
+ * @param MessageCache $messageCache
*/
- private function compose( $user, $source ) {
+ private function compose( $user, $source, MessageCache $messageCache ) {
global $wgEnotifImpersonal;
if ( !$this->composed_common ) {
- $this->composeCommonMailtext();
+ $this->composeCommonMailtext( $messageCache );
}
if ( $wgEnotifImpersonal ) {
case 'CustomRendered':
switch ( $val ) {
- case 0:
- case 1:
+ case 0: /* normal */
+ case 1: /* custom */
+ /* The following are unofficial Apple additions */
+ case 2: /* HDR (no original saved) */
+ case 3: /* HDR (original saved) */
+ case 4: /* Original (for HDR) */
+ /* Yes 5 is not present ;) */
+ case 6: /* Panorama */
+ case 7: /* Portrait HDR */
+ case 8: /* Portrait */
$val = $this->exifMsg( $tag, $val );
break;
default:
protected $mCurrentRow;
public function __construct( IContextSource $context = null, LinkRenderer $linkRenderer = null ) {
+ if ( $context ) {
+ $this->setContext( $context );
+ }
+
$this->mSort = $this->getRequest()->getText( 'sort' );
if ( !array_key_exists( $this->mSort, $this->getFieldNames() )
|| !$this->isFieldSortable( $this->mSort )
} /* Else leave it at whatever the class default is */
// Parent constructor needs mSort set, so we call it last
- parent::__construct( $context, $linkRenderer );
+ parent::__construct( null, $linkRenderer );
}
/**
if ( !$wgRestrictDisplayTitle ||
( $title instanceof Title
&& !$title->hasFragment()
- && $title->equals( $parser->mTitle ) )
+ && $title->equals( $parser->getTitle() ) )
) {
$old = $parser->mOutput->getProperty( 'displaytitle' );
if ( $old === false || $arg !== 'displaytitle_noreplace' ) {
* @return string
*/
public static function protectionlevel( $parser, $type = '', $title = '' ) {
- $titleObject = Title::newFromText( $title );
- if ( !( $titleObject instanceof Title ) ) {
- $titleObject = $parser->mTitle;
- }
+ $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
if ( $titleObject->areRestrictionsLoaded() || $parser->incrementExpensiveFunctionCount() ) {
$restrictions = $titleObject->getRestrictions( strtolower( $type ) );
# Title::getRestrictions returns an array, its possible it may have
* @return string
*/
public static function protectionexpiry( $parser, $type = '', $title = '' ) {
- $titleObject = Title::newFromText( $title );
- if ( !( $titleObject instanceof Title ) ) {
- $titleObject = $parser->mTitle;
- }
+ $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
if ( $titleObject->areRestrictionsLoaded() || $parser->incrementExpensiveFunctionCount() ) {
$expiry = $titleObject->getRestrictionExpiry( strtolower( $type ) );
// getRestrictionExpiry() returns false on invalid type; trying to
* @since 1.23
*/
public static function cascadingsources( $parser, $title = '' ) {
- $titleObject = Title::newFromText( $title );
- if ( !( $titleObject instanceof Title ) ) {
- $titleObject = $parser->mTitle;
- }
+ $titleObject = Title::newFromText( $title ) ?? $parser->getTitle();
if ( $titleObject->areCascadeProtectionSourcesLoaded()
|| $parser->incrementExpensiveFunctionCount()
) {
public function __construct( $preprocessor ) {
$this->preprocessor = $preprocessor;
$this->parser = $preprocessor->parser;
- $this->title = $this->parser->mTitle;
+ $this->title = $this->parser->getTitle();
$this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ];
$this->loopCheckHash = [];
$this->depth = 0;
public function __construct( $preprocessor ) {
$this->preprocessor = $preprocessor;
$this->parser = $preprocessor->parser;
- $this->title = $this->parser->mTitle;
+ $this->title = $this->parser->getTitle();
$this->titleCache = [ $this->title ? $this->title->getPrefixedDBkey() : false ];
$this->loopCheckHash = [];
$this->depth = 0;
global $wgPopularPasswordFile, $wgSitename;
$status = Status::newGood();
if ( $policyVal > 0 ) {
- wfDeprecated( __METHOD__, '1.33' );
-
$langEn = Language::factory( 'en' );
$passwordKey = $langEn->lc( trim( $password ) );
--- /dev/null
+<?php
+
+/**
+ * SearchResult class based on the Revision information.
+ * This class is suited for search engines that do not store a specialized version of the searched
+ * content.
+ */
+class RevisionSearchResult extends SearchResult {
+ use RevisionSearchResultTrait;
+
+ /**
+ * @param Title|null $title
+ */
+ public function __construct( $title ) {
+ $this->mTitle = $title;
+ $this->initFromTitle( $title );
+ }
+}
--- /dev/null
+<?php
+
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Transitional trait used to share the methods between SearchResult and RevisionSearchResult.
+ * All the content of this trait can be moved to RevisionSearchResult once SearchResult is finally
+ * refactored into an abstract class.
+ * NOTE: This trait MUST NOT be used by something else than SearchResult and RevisionSearchResult.
+ * It will be removed without deprecation period once SearchResult
+ */
+trait RevisionSearchResultTrait {
+ /**
+ * @var Revision
+ */
+ protected $mRevision = null;
+
+ /**
+ * @var File
+ */
+ protected $mImage = null;
+
+ /**
+ * @var Title
+ */
+ protected $mTitle;
+
+ /**
+ * @var string
+ */
+ protected $mText;
+
+ /**
+ * Initialize from a Title and if possible initializes a corresponding
+ * Revision and File.
+ *
+ * @param Title $title
+ */
+ protected function initFromTitle( $title ) {
+ $this->mTitle = $title;
+ $services = MediaWikiServices::getInstance();
+ if ( !is_null( $this->mTitle ) ) {
+ $id = false;
+ Hooks::run( 'SearchResultInitFromTitle', [ $title, &$id ] );
+ $this->mRevision = Revision::newFromTitle(
+ $this->mTitle, $id, Revision::READ_NORMAL );
+ if ( $this->mTitle->getNamespace() === NS_FILE ) {
+ $this->mImage = $services->getRepoGroup()->findFile( $this->mTitle );
+ }
+ }
+ }
+
+ /**
+ * Check if this is result points to an invalid title
+ *
+ * @return bool
+ */
+ public function isBrokenTitle() {
+ return is_null( $this->mTitle );
+ }
+
+ /**
+ * Check if target page is missing, happens when index is out of date
+ *
+ * @return bool
+ */
+ public function isMissingRevision() {
+ return !$this->mRevision && !$this->mImage;
+ }
+
+ /**
+ * @return Title
+ */
+ public function getTitle() {
+ return $this->mTitle;
+ }
+
+ /**
+ * Get the file for this page, if one exists
+ * @return File|null
+ */
+ public function getFile() {
+ return $this->mImage;
+ }
+
+ /**
+ * Lazy initialization of article text from DB
+ */
+ protected function initText() {
+ if ( !isset( $this->mText ) ) {
+ if ( $this->mRevision != null ) {
+ $content = $this->mRevision->getContent();
+ $this->mText = $content !== null ? $content->getTextForSearchIndex() : '';
+ } else { // TODO: can we fetch raw wikitext for commons images?
+ $this->mText = '';
+ }
+ }
+ }
+
+ /**
+ * @param string[] $terms Terms to highlight (this parameter is deprecated and ignored)
+ * @return string Highlighted text snippet, null (and not '') if not supported
+ */
+ public function getTextSnippet( $terms = [] ) {
+ return '';
+ }
+
+ /**
+ * @return string Highlighted title, '' if not supported
+ */
+ public function getTitleSnippet() {
+ return '';
+ }
+
+ /**
+ * @return string Highlighted redirect name (redirect to this page), '' if none or not supported
+ */
+ public function getRedirectSnippet() {
+ return '';
+ }
+
+ /**
+ * @return Title|null Title object for the redirect to this page, null if none or not supported
+ */
+ public function getRedirectTitle() {
+ return null;
+ }
+
+ /**
+ * @return string Highlighted relevant section name, null if none or not supported
+ */
+ public function getSectionSnippet() {
+ return '';
+ }
+
+ /**
+ * @return Title|null Title object (pagename+fragment) for the section,
+ * null if none or not supported
+ */
+ public function getSectionTitle() {
+ return null;
+ }
+
+ /**
+ * @return string Highlighted relevant category name or '' if none or not supported
+ */
+ public function getCategorySnippet() {
+ return '';
+ }
+
+ /**
+ * @return string Timestamp
+ */
+ public function getTimestamp() {
+ if ( $this->mRevision ) {
+ return $this->mRevision->getTimestamp();
+ } elseif ( $this->mImage ) {
+ return $this->mImage->getTimestamp();
+ }
+ return '';
+ }
+
+ /**
+ * @return int Number of words
+ */
+ public function getWordCount() {
+ $this->initText();
+ return str_word_count( $this->mText );
+ }
+
+ /**
+ * @return int Size in bytes
+ */
+ public function getByteSize() {
+ $this->initText();
+ return strlen( $this->mText );
+ }
+
+ /**
+ * @return string Interwiki prefix of the title (return iw even if title is broken)
+ */
+ public function getInterwikiPrefix() {
+ return '';
+ }
+
+ /**
+ * @return string Interwiki namespace of the title (since we likely can't resolve it locally)
+ */
+ public function getInterwikiNamespaceText() {
+ return '';
+ }
+
+ /**
+ * Did this match file contents (eg: PDF/DJVU)?
+ * @return bool
+ */
+ public function isFileMatch() {
+ return false;
+ }
+}
* @ingroup Search
*/
-use MediaWiki\MediaWikiServices;
-
/**
- * @todo FIXME: This class is horribly factored. It would probably be better to
- * have a useful base class to which you pass some standard information, then
- * let the fancy self-highlighters extend that.
+ * NOTE: this class is being refactored into an abstract base class.
+ * If you extend this class directly, please implement all the methods declared
+ * in RevisionSearchResultTrait or extend RevisionSearchResult.
+ *
+ * Once the hard-deprecation period is over (1.36?):
+ * - all methods declared in RevisionSearchResultTrait should be declared
+ * as abstract in this class
+ * - RevisionSearchResultTrait body should be moved to RevisionSearchResult and then removed without
+ * deprecation
+ * - caveat: all classes extending this one may potentially break if they did not properly implement
+ * all the methods.
* @ingroup Search
*/
class SearchResult {
use SearchResultTrait;
+ use RevisionSearchResultTrait;
- /**
- * @var Revision
- */
- protected $mRevision = null;
-
- /**
- * @var File
- */
- protected $mImage = null;
-
- /**
- * @var Title
- */
- protected $mTitle;
-
- /**
- * @var string
- */
- protected $mText;
+ public function __construct() {
+ if ( self::class === static::class ) {
+ wfDeprecated( __METHOD__, '1.34' );
+ }
+ }
/**
* Return a new SearchResult and initializes it with a title.
* @return SearchResult
*/
public static function newFromTitle( $title, ISearchResultSet $parentSet = null ) {
- $result = new static();
- $result->initFromTitle( $title );
+ $result = new RevisionSearchResult( $title );
if ( $parentSet ) {
$parentSet->augmentResult( $result );
}
return $result;
}
-
- /**
- * Initialize from a Title and if possible initializes a corresponding
- * Revision and File.
- *
- * @param Title $title
- */
- protected function initFromTitle( $title ) {
- $this->mTitle = $title;
- $services = MediaWikiServices::getInstance();
- if ( !is_null( $this->mTitle ) ) {
- $id = false;
- Hooks::run( 'SearchResultInitFromTitle', [ $title, &$id ] );
- $this->mRevision = Revision::newFromTitle(
- $this->mTitle, $id, Revision::READ_NORMAL );
- if ( $this->mTitle->getNamespace() === NS_FILE ) {
- $this->mImage = $services->getRepoGroup()->findFile( $this->mTitle );
- }
- }
- }
-
- /**
- * Check if this is result points to an invalid title
- *
- * @return bool
- */
- public function isBrokenTitle() {
- return is_null( $this->mTitle );
- }
-
- /**
- * Check if target page is missing, happens when index is out of date
- *
- * @return bool
- */
- public function isMissingRevision() {
- return !$this->mRevision && !$this->mImage;
- }
-
- /**
- * @return Title
- */
- public function getTitle() {
- return $this->mTitle;
- }
-
- /**
- * Get the file for this page, if one exists
- * @return File|null
- */
- public function getFile() {
- return $this->mImage;
- }
-
- /**
- * Lazy initialization of article text from DB
- */
- protected function initText() {
- if ( !isset( $this->mText ) ) {
- if ( $this->mRevision != null ) {
- $content = $this->mRevision->getContent();
- $this->mText = $content !== null ? $content->getTextForSearchIndex() : '';
- } else { // TODO: can we fetch raw wikitext for commons images?
- $this->mText = '';
- }
- }
- }
-
- /**
- * @param string[] $terms Terms to highlight (this parameter is deprecated and ignored)
- * @return string Highlighted text snippet, null (and not '') if not supported
- */
- public function getTextSnippet( $terms = [] ) {
- return '';
- }
-
- /**
- * @return string Highlighted title, '' if not supported
- */
- public function getTitleSnippet() {
- return '';
- }
-
- /**
- * @return string Highlighted redirect name (redirect to this page), '' if none or not supported
- */
- public function getRedirectSnippet() {
- return '';
- }
-
- /**
- * @return Title|null Title object for the redirect to this page, null if none or not supported
- */
- public function getRedirectTitle() {
- return null;
- }
-
- /**
- * @return string Highlighted relevant section name, null if none or not supported
- */
- public function getSectionSnippet() {
- return '';
- }
-
- /**
- * @return Title|null Title object (pagename+fragment) for the section,
- * null if none or not supported
- */
- public function getSectionTitle() {
- return null;
- }
-
- /**
- * @return string Highlighted relevant category name or '' if none or not supported
- */
- public function getCategorySnippet() {
- return '';
- }
-
- /**
- * @return string Timestamp
- */
- public function getTimestamp() {
- if ( $this->mRevision ) {
- return $this->mRevision->getTimestamp();
- } elseif ( $this->mImage ) {
- return $this->mImage->getTimestamp();
- }
- return '';
- }
-
- /**
- * @return int Number of words
- */
- public function getWordCount() {
- $this->initText();
- return str_word_count( $this->mText );
- }
-
- /**
- * @return int Size in bytes
- */
- public function getByteSize() {
- $this->initText();
- return strlen( $this->mText );
- }
-
- /**
- * @return string Interwiki prefix of the title (return iw even if title is broken)
- */
- public function getInterwikiPrefix() {
- return '';
- }
-
- /**
- * @return string Interwiki namespace of the title (since we likely can't resolve it locally)
- */
- public function getInterwikiNamespaceText() {
- return '';
- }
-
- /**
- * Did this match file contents (eg: PDF/DJVU)?
- * @return bool
- */
- public function isFileMatch() {
- return false;
- }
-
}
* @ingroup Search
*/
-class SqlSearchResult extends SearchResult {
+class SqlSearchResult extends RevisionSearchResult {
/** @var string[] */
private $terms;
* @param string[] $terms list of parsed terms
*/
public function __construct( Title $title, array $terms ) {
- $this->initFromTitle( $title );
+ parent::__construct( $title );
$this->terms = $terms;
}
$target = $par ?? $request->getVal( 'target' );
- if ( $request->getVal( 'contribs' ) == 'newbie' || $par === 'newbies' ) {
- $target = 'newbies';
- $this->opts['contribs'] = 'newbie';
- } else {
- $this->opts['contribs'] = 'user';
- }
-
$this->opts['deletedOnly'] = $request->getBool( 'deletedOnly' );
if ( !strlen( $target ) ) {
$this->opts['hideMinor'] = $request->getBool( 'hideMinor' );
$id = 0;
- if ( $this->opts['contribs'] === 'newbie' ) {
- $userObj = User::newFromName( $target ); // hysterical raisins
- $out->addSubtitle( $this->msg( 'sp-contributions-newbies-sub' ) );
- $out->setHTMLTitle( $this->msg(
- 'pagetitle',
- $this->msg( 'sp-contributions-newbies-title' )->plain()
- )->inContentLanguage() );
- } elseif ( ExternalUserNames::isExternal( $target ) ) {
+ if ( ExternalUserNames::isExternal( $target ) ) {
$userObj = User::newFromName( $target, false );
if ( !$userObj ) {
$out->addHTML( $this->getForm() );
}
$pager = new ContribsPager( $this->getContext(), [
'target' => $target,
- 'contribs' => $this->opts['contribs'],
+ // Temporary, until newbie feature is fully removed from ContribsPager
+ 'contribs' => 'user',
'namespace' => $this->opts['namespace'],
'tagfilter' => $this->opts['tagfilter'],
'start' => $this->opts['start'],
$out->preventClickjacking( $pager->getPreventClickjacking() );
# Show the appropriate "footer" message - WHOIS tools, etc.
- if ( $this->opts['contribs'] == 'newbie' ) {
- $message = 'sp-contributions-footer-newbies';
- } elseif ( IP::isValidRange( $target ) ) {
+ if ( IP::isValidRange( $target ) ) {
$message = 'sp-contributions-footer-anon-range';
} elseif ( IP::isIPAddress( $target ) ) {
$message = 'sp-contributions-footer-anon';
$this->opts['associated'] = false;
}
- if ( !isset( $this->opts['contribs'] ) ) {
- $this->opts['contribs'] = 'user';
- }
-
if ( !isset( $this->opts['start'] ) ) {
$this->opts['start'] = '';
}
$this->opts['end'] = '';
}
- if ( $this->opts['contribs'] == 'newbie' ) {
- $this->opts['target'] = '';
- }
-
if ( !isset( $this->opts['tagfilter'] ) ) {
$this->opts['tagfilter'] = '';
}
$filterSelection = Html::rawElement( 'div', [], '' );
}
- $labelNewbies = Xml::radioLabel(
- $this->msg( 'sp-contributions-newbies' )->text(),
- 'contribs',
- 'newbie',
- 'newbie',
- $this->opts['contribs'] == 'newbie',
- [ 'class' => 'mw-input' ]
- );
$labelUsername = Xml::radioLabel(
$this->msg( 'sp-contributions-username' )->text(),
'contribs',
'user',
'user',
- $this->opts['contribs'] == 'user',
+ true,
[ 'class' => 'mw-input' ]
);
$input = Html::input(
'mw-autocomplete-user', // used by mediawiki.userSuggest
],
] + (
- // Only autofocus if target hasn't been specified or in non-newbies mode
- ( $this->opts['contribs'] === 'newbie' || $this->opts['target'] )
- ? [] : [ 'autofocus' => true ]
- )
+ // Only autofocus if target hasn't been specified
+ $this->opts['target'] ? [] : [ 'autofocus' => true ]
+ )
);
$targetSelection = Html::rawElement(
'div',
[],
- $labelNewbies . '<br>' . $labelUsername . ' ' . $input . ' '
+ $labelUsername . ' ' . $input . ' '
);
$hidden = $this->opts['namespace'] === '' ? ' mw-input-hidden' : '';
$opts->add( 'like', '' );
$opts->add( 'user', '' );
$opts->add( 'showbots', false );
- $opts->add( 'newbies', false );
$opts->add( 'hidepatrolled', false );
$opts->add( 'mediatype', $this->mediaTypes );
$opts->add( 'limit', 50 );
'name' => 'user',
],
- 'newbies' => [
- 'type' => 'check',
- 'label-message' => 'newimages-newbies',
- 'name' => 'newbies',
- ],
-
'showbots' => [
'type' => 'check',
'label-message' => 'newimages-showbots',
public function __construct( IContextSource $context, $userName = null, $search = '',
$including = false, $showAll = false
) {
- parent::__construct( $context );
+ $this->setContext( $context );
$this->mIncluding = $including;
$this->mShowAll = $showAll;
} else {
$this->mDefaultDirection = IndexPager::DIR_DESCENDING;
}
+
+ parent::__construct();
}
/**
* another page
*/
public function __construct( IContextSource $context = null, $par = null, $including = null ) {
+ if ( $context ) {
+ $this->setContext( $context );
+ }
+
$request = $this->getRequest();
$par = $par ?? '';
$parms = explode( '/', $par );
}
}
- parent::__construct( $context );
+ parent::__construct();
}
/**
"rcfilters-filter-showlinkedto-label": "Vis ændringer på sider der linker til",
"rcfilters-filter-showlinkedto-option-label": "<strong>Sider som linker til</strong> den valgte side",
"rcfilters-target-page-placeholder": "Indtast et sidenavn (eller en kategori)",
+ "rcfilters-allcontents-label": "Alt indhold",
"rcnotefrom": "Nedenfor er op til '''$1''' {{PLURAL:$5|ændring|ændringer}} siden '''$2''' vist.",
"rclistfromreset": "Nulstil datovalg",
"rclistfrom": "Vis nye ændringer startende fra den $3 kl. $2",
"Fitoschido",
"KATRINE1993",
"Vlad5250",
- "Sarri.greek"
+ "Sarri.greek",
+ "Kostajh"
]
},
"tog-underline": "Υπογράμμιση συνδέσμων:",
"history": "Ιστορικό σελίδας",
"history_short": "Ιστορικό",
"history_small": "ιστορικό",
- "updatedmarker": "ενημερώθηκαν από την τελευταία επίσκεψή μου",
+ "updatedmarker": "ενημερώθηκαν από την τελευταία επίσκεψή σας",
"printableversion": "Έκδοση εκτύπωσης",
"permalink": "Σταθερός σύνδεσμος",
"print": "Εκτύπωση",
"apisandbox": "API sandbox",
"apisandbox-summary": "",
"apisandbox-jsonly": "JavaScript is required to use the API sandbox.",
- "apisandbox-api-disabled": "The API is disabled on this site.",
"apisandbox-intro": "Use this page to experiment with the <strong>MediaWiki web service API</strong>.\nRefer to [[mw:API:Main page|the API documentation]] for further details of API usage. Example: [https://www.mediawiki.org/wiki/API#A_simple_example get the content of a Main Page]. Select an action to see more examples.\n\nNote that, although this is a sandbox, actions you carry out on this page may modify the wiki.",
"apisandbox-submit": "Make request",
"apisandbox-reset": "Clear",
"month": "From month (and earlier):",
"year": "From year (and earlier):",
"date": "From date (and earlier):",
- "sp-contributions-newbies": "Show contributions of new accounts only",
- "sp-contributions-newbies-sub": "For new accounts",
- "sp-contributions-newbies-title": "User contributions for new accounts",
"sp-contributions-blocklog": "block log",
"sp-contributions-suppresslog": "suppressed {{GENDER:$1|user}} contributions",
"sp-contributions-deleted": "deleted {{GENDER:$1|user}} contributions",
"sp-contributions-footer": "-",
"sp-contributions-footer-anon": "-",
"sp-contributions-footer-anon-range": "-",
- "sp-contributions-footer-newbies": "-",
"sp-contributions-outofrange": "Unable to show any results. The requested IP range is larger than the CIDR limit of /$1.",
"whatlinkshere": "What links here",
"whatlinkshere-title": "Pages that link to \"$1\"",
"newimages-legend": "Filter",
"newimages-label": "Filename (or a part of it):",
"newimages-user": "IP address or username",
- "newimages-newbies": "Show contributions of new accounts only",
"newimages-showbots": "Show uploads by bots",
"newimages-hidepatrolled": "Hide patrolled uploads",
"newimages-mediatype": "Media type:",
"exif-scenetype-1": "A directly photographed image",
"exif-customrendered-0": "Normal process",
"exif-customrendered-1": "Custom process",
+ "exif-customrendered-2": "HDR (no original saved)",
+ "exif-customrendered-3": "HDR (original saved)",
+ "exif-customrendered-4": "Original (for HDR)",
+ "exif-customrendered-6": "Panorama",
+ "exif-customrendered-7": "Portrait HDR",
+ "exif-customrendered-8": "Portrait",
"exif-exposuremode-0": "Auto exposure",
"exif-exposuremode-1": "Manual exposure",
"exif-exposuremode-2": "Auto bracket",
"exif-scenetype-1": "See also:\n* {{msg-mw|Exif-scenetype}}\n* {{msg-mw|Exif-scenetype-1}}",
"exif-customrendered-0": "{{exif-qqq}}\n\nSee also:\n* {{msg-mw|Exif-customrendered}}\n* {{msg-mw|Exif-customrendered-0}}\n* {{msg-mw|Exif-customrendered-1}}",
"exif-customrendered-1": "{{exif-qqq}}\n\nSee also:\n* {{msg-mw|Exif-customrendered}}\n* {{msg-mw|Exif-customrendered-0}}\n* {{msg-mw|Exif-customrendered-1}}",
+ "exif-customrendered-2": "{{exif-qqq}}\n\nSee also:\n* {{msg-mw|Exif-customrendered}}",
+ "exif-customrendered-3": "{{exif-qqq}}\n\nSee also:\n* {{msg-mw|Exif-customrendered}}",
+ "exif-customrendered-4": "{{exif-qqq}}\n\nSee also:\n* {{msg-mw|Exif-customrendered}}",
+ "exif-customrendered-6": "{{exif-qqq}}\n\nSee also:\n* {{msg-mw|Exif-customrendered}}",
+ "exif-customrendered-7": "{{exif-qqq}}\n\nSee also:\n* {{msg-mw|Exif-customrendered}}",
+ "exif-customrendered-8": "{{exif-qqq}}\n\nSee also:\n* {{msg-mw|Exif-customrendered}}",
"exif-exposuremode-0": "{{exif-qqq}}\n{{Related|Exif-exposuremode}}",
"exif-exposuremode-1": "{{exif-qqq}}\n{{Related|Exif-exposuremode}}",
"exif-exposuremode-2": "{{exif-qqq}}\n\nA type of exposure mode shown as part of the metadata on image description pages. The Wikipedia article on [[w:Bracketing#Exposure_bracketing|bracketing]] says that 'auto bracket' is a camera exposure setting which automatically takes a series of pictures at slightly different light exposures.\n\n{{Related|Exif-exposuremode}}",
"PhiLiP",
"Qiyue2001",
"Xiaomingyan",
- "神樂坂秀吉"
+ "神樂坂秀吉",
+ "予弦"
]
},
"exif-imagewidth": "宽度",
"tag-filter": "Filtrer les [[Special:Tags|balises]] :",
"tag-filter-submit": "Filtrer",
"tag-list-wrapper": "[[Special:Tags|{{PLURAL:$1|Balise|Balises}}]] : $2",
- "tag-mw-contentmodelchange": "modification du modèle de contenu",
+ "tag-mw-contentmodelchange": "Modification du modèle de contenu",
"tag-mw-contentmodelchange-description": "Modifications qui [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel changent le modèle de contenu] d'une page",
"tag-mw-new-redirect": "Nouvelle redirection",
"tag-mw-new-redirect-description": "Modifications qui créent une nouvelle redirection ou transforment une page en redirection",
"tag-mw-changed-redirect-target-description": "Modifications qui changent la cible d’une redirection",
"tag-mw-blank": "Blanchiment",
"tag-mw-blank-description": "Modifications qui suppriment le contenu des pages",
- "tag-mw-replace": "Remplacé",
+ "tag-mw-replace": "Contenu remplacé",
"tag-mw-replace-description": "Modifications qui enlèvent plus de 90% du contenu des pages",
"tag-mw-rollback": "Révocation",
"tag-mw-rollback-description": "Modifications qui annulent des modifications existantes en utilisant le lien de révocation (''rollback'')",
"timezoneregion-indian": "Samudera Hindia",
"timezoneregion-pacific": "Samudera Pasifik",
"allowemail": "Izinkan pengguna lain mengirim surel kepada saya",
- "email-allow-new-users-label": "Izinkan email dari pengguna baru",
+ "email-allow-new-users-label": "Izinkan surel dari pengguna baru",
"email-blacklist-label": "Cegah para pengguna ini mengirim saya surel:",
"prefs-searchoptions": "Cari",
"prefs-namespaces": "Ruang nama",
"last": "prec",
"page_first": "prima",
"page_last": "ultima",
- "histlegend": "Confronto tra versioni: selezionare le caselle corrispondenti alle versioni desiderate e premere Invio o il pulsante in basso.\n\nLegenda: '''({{int:cur}})''' = differenze con la versione corrente, '''({{int:last}})''' = differenze con la versione precedente, '''{{int:minoreditletter}}''' = modifica minore",
+ "histlegend": "Confronto tra versioni: selezionare le caselle corrispondenti alle versioni desiderate e premere Invio o il pulsante in basso.\n\nLegenda: '''({{int:cur}})''' = differenze con la versione attuale, '''({{int:last}})''' = differenze con la versione precedente, '''{{int:minoreditletter}}''' = modifica minore",
"history-fieldset-title": "Filtra versioni",
"history-show-deleted": "Solo versioni cancellate",
"histfirst": "prima",
"prefs-personal": "Profil pangguno",
"prefs-rc": "Parubahan baru",
"prefs-watchlist": "Daftar pantau",
+ "prefs-editwatchlist": "Suntiang daftar pantauan",
+ "prefs-editwatchlist-label": "Suntiang entri daftar pantauan Sanak:",
"prefs-watchlist-days": "Jumlah hari dalam daftar pantau:",
"prefs-watchlist-days-max": "Maksimum $1 {{PLURAL:$1|hari}}",
"prefs-watchlist-edits": "Jumlah suntiangan nan ditunjuakan pado daftar pantau:",
"timezoneregion-indian": "Samudera Hindia",
"timezoneregion-pacific": "Samudera Pasifik",
"allowemail": "Izinkan pangguno lain mangirim surel",
+ "email-allow-new-users-label": "Izinkan surel dari pangguno baru",
+ "email-blacklist-label": "Panggono ko indak dapek kirim surel ka Ambo:",
"prefs-searchoptions": "Cari",
"prefs-namespaces": "Ruangnamo",
"default": "baku",
"prefs-files": "Berkas",
- "prefs-custom-css": "CSS paribadi",
- "prefs-custom-js": "JS paribadi",
+ "prefs-custom-css": "CSS surang",
+ "prefs-custom-js": "JS surang",
"prefs-common-config": "CSS/JS untuak kasado kulik:",
"prefs-reset-intro": "Angku dapek manggunokan laman ko untuak mangambalikan pangaturan ka setelan baku situs ko.\nPangambalian pangaturan indak dapek dibatalan.",
"prefs-emailconfirm-label": "Surel konfirmasi:",
"prefs-advancedwatchlist": "Piliahan lanjuik",
"prefs-displayrc": "Piliahan tampilan",
"prefs-displaywatchlist": "Piliahan tampilan",
+ "prefs-changesrc": "Parubahan ditampilkan",
"prefs-diffs": "Pabedoan",
"userrights": "Manajemen hak pangguno",
"userrights-lookup-user": "Mangatua kalompok pangguno",
"KlaasZ4usV",
"Elroy",
"PiefPafPier",
- "Ecthelion3"
+ "Ecthelion3",
+ "RadioAzureus"
]
},
"tog-underline": "Verwijzingen onderstrepen:",
"rcfilters-filter-showlinkedto-label": "Toon wijzigingen op pagina's gekoppeld naar",
"rcfilters-filter-showlinkedto-option-label": "<strong>Pagina's gekoppeld naar</strong> de geselecteerde pagina",
"rcfilters-target-page-placeholder": "Voer een paginanaam (of categorie) in",
- "rcfilters-allcontents-label": "Alle inhoud",
- "rcfilters-alldiscussions-label": "Al het overleg",
+ "rcfilters-allcontents-label": "De volledige inhoud",
+ "rcfilters-alldiscussions-label": "Alle discussies",
"rcnotefrom": "Wijzigingen sinds <strong>$3 om $4</strong> (maximaal <strong>$1</strong> {{PLURAL:$1|wijziging|wijzigingen}}).",
"rclistfromreset": "Datum selectie opnieuw instellen",
"rclistfrom": "Wijzigingen bekijken vanaf $3 $2",
"immobile-target-namespace-iw": "Een interwikikoppeling is geen geldige bestemming voor het hernoemen van een pagina.",
"immobile-source-page": "Deze pagina kan niet hernoemd worden.",
"immobile-target-page": "Het is niet mogelijk te hernoemen naar die paginanaam.",
- "movepage-invalid-target-title": "De opgevraagde naam is ongeldig.",
+ "movepage-invalid-target-title": "De gevraagde naam is ongeldig.",
"bad-target-model": "De gewenste bestemming gebruikt een ander inhoudsmodel. Het is niet mogelijk om te zetten van $1 naar $2.",
"imagenocrossnamespace": "Een mediabestand kan niet naar een andere naamruimte verplaatst worden",
"nonfile-cannot-move-to-file": "Het is niet mogelijk te hernoemen van en naar de bestandsnaamruimte",
"showdiff": "Yong-a wallak",
"anoneditwarning": "<strong>Warning:</strong> You are not logged in. Noonook IP-karl-up will be publicly djinang il noonook wallak. Noonook-il <strong>[$1 log in]</strong> or <strong>[$2 create an gudak]</strong>, noonook wallak will be attributed to noonook niall-kwel-le, along with other benefits.",
"blockedtitle": "Niall be nap-nap",
- "blockedtext": "<strong>Your username or IP address has been blocked.</strong>\n\nThe block was made by $1.\nThe reason given is <em>$2</em>.\n\n* Start of block: $8\n* Expiration of block: $6\n* Intended blockee: $7\n\nYou can contact $1 or another [[{{MediaWiki:Grouppage-sysop}}|administrator]] to discuss the block.\nYou cannot use the \"email this user\" feature unless a valid email address is specified in your [[Special:Preferences|account preferences]] and you have not been blocked from using it.\nYour current IP address is $3, and the block ID is #$5.\nPlease include all above details in any queries you make.",
+ "blockedtext": "<strong>Your username or IP address has been blocked.</strong>\n\nThe block was made by $1.\nThe reason given is <em>$2</em>.\n\n* Start of block: $8\n* Expiration of block: $6\n* Intended blockee: $7\n\nYou can contact $1 or another [[{{MediaWiki:Grouppage-sysop}}|administrator]] to discuss the block.\nYou cannot use the \"{{int:emailuser}}\" feature unless a valid email address is specified in your [[Special:Preferences|account preferences]] and you have not been blocked from using it.\nYour current IP address is $3, and the block ID is #$5.\nPlease include all above details in any queries you make.",
"loginreqlink": "yaarlkoorl",
"newarticletext": "Noonook ngwaliny beda bibol uart-yogow yeye.\nWallak bibol qadgin mar waangkin ngardal (djinang [$1 mar yira bibol] ngatta katitjiny)\nWarra bainya noonook nidja, click noonook bowser's <strong>woort koorl</strong>button",
- "anontalkpagetext": "----\n<em>Nidja waangkininy bibol for an anonymous niall uart-quadga gudak, or who does not use it.</em>\nWe therefore have to use the numerical IP-karl-up to identify him/her.\nSuch an IP-karl-up can be shared by several niall.\nIf noonook anonymous niall and feel that irrelevant waangkin have been directed at noonook, please [[Special:CreateAccount|quadga gudak]] or [[Special:UserLogin|log in]] to avoid future confusion with other anonymous niall.",
+ "anontalkpagetext": "----\n<em>Nidja waangkininy bibol for an anonymous niall uart-quadga gudak, or who does not use it.</em>\nWe therefore have to use the numerical IP-karl-up to identify balang.\nSuch an IP-karl-up can be shared by several niall.\nIf noonook anonymous niall and feel that irrelevant waangkin have been directed at noonook, please [[Special:CreateAccount|quadga gudak]] or [[Special:UserLogin|log in]] to avoid future confusion with other anonymous niall.",
"noarticletext": "There is currently no text in this page.\nYou can [[Special:Search/{{PAGENAME}}|search for this page title]] in other pages,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} search the related logs],\nor [{{fullurl:{{FULLPAGENAME}}|action=edit}} create this page]</span>.",
"noarticletext-nopermission": "Nidja yeye uart text il nidja bibol.\nNoonook [[Special:Search/{{PAGENAME}}|genuniny-ung nidja bibol katta wir-iny]] bura wam bibol, ka <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} genuniny boonadairn]</span>, noonook uart kaya ijow walbirniny nidja bibol.",
"userpage-userdoesnotexist-view": "Niall gaduk $1 be uart yeye-quadga",
"recentchangeslinked-feed": "Noyyang wallak",
"recentchangeslinked-toolbox": "Noyyang wallak",
"recentchangeslinked-title": "Wallak noyyanging $1",
- "recentchangeslinked-summary": "Nidga list-ang wallak yeye bibol beda wer-ang ngela bibol (or il ngela warrangan)\n\nBibol il [[Special:Watchlist|noonook djinanglist]] be <strong>moorn</strong>",
+ "recentchangeslinked-summary": "Nidga list-ang wallak yeye bibol beda wer-ang ngela bibol ({{ns:category}} il ngela warrangan)\n\nBibol il [[Special:Watchlist|noonook djinanglist]] be <strong>moorn</strong>",
"recentchangeslinked-page": "Bibol kwel-le:",
"recentchangeslinked-to": "Yong-a wallak bibol beda nitja bibol",
"upload": "Yirra file",
"rcfilters-filter-showlinkedto-label": "Pokaż zmiany na stronach linkujących do",
"rcfilters-filter-showlinkedto-option-label": "<strong>Strony linkujące do</strong> zaznaczonej strony",
"rcfilters-target-page-placeholder": "Wprowadź nazwę strony (lub kategorii)",
- "rcfilters-alldiscussions-label": "Wszystkie dyskusje",
+ "rcfilters-allcontents-label": "Wszystkie (treść)",
+ "rcfilters-alldiscussions-label": "Wszystkie (dyskusje)",
"rcnotefrom": "Poniżej {{PLURAL:$5|pokazano zmianę|pokazano zmiany}} {{PLURAL:$5|wykonaną|wykonane}} po <strong>$3, $4</strong> (nie więcej niż '''$1''' pozycji).",
"rclistfromreset": "Zresetuj wybór daty",
"rclistfrom": "Pokaż nowe zmiany od $3 $2",
"ipb-disableusertalk": "Edytowanie przez tego użytkownika swojej strony dyskusji",
"ipb-change-block": "Zmień ustawienia blokady",
"ipb-confirm": "Potwierdzam blokadę",
- "ipb-sitewide": "Całkowita",
- "ipb-partial": "Częściowa",
+ "ipb-sitewide": "Całkowicie",
+ "ipb-partial": "Częściowo",
"ipb-sitewide-help": "Wszystkie strony na wiki i wszystkie akcje inne edycyjne.",
"ipb-partial-help": "Konkretne strony lub przestrzenie nazw.",
"ipb-pages-label": "Strony",
"versionrequired": "This message is not used in the MediaWiki core, but was introduced with the reason that it could be useful for extensions.\n\nParameters:\n* $1 - MediaWiki version number\nSee also:\n* {{msg-mw|Versionrequiredtext}}",
"versionrequiredtext": "This message is not used in the MediaWiki core, but was introduced with the reason that it could be useful for extensions.\n\nParameters:\n* $1 - MediaWiki version number\nSee also:\n* {{msg-mw|Versionrequired}}",
"ok": "{{Identical|OK}}",
- "pagetitle": "{{Optional}}\n{{doc-important|You most probably do not need to translate this message.}}\nDo '''not''' replace SITENAME with a translation of Wikipedia or some encyclopedic additions. The message has to be neutral for all projects.\n\nParameters:\n* $1 - page title or any one of the following messages:\n** {{msg-mw|Contributions-title}}\n** {{msg-mw|Searchresults-title}}\n** {{msg-mw|Sp-contributions-newbies-title}}",
+ "pagetitle": "{{Optional}}\n{{doc-important|You most probably do not need to translate this message.}}\nDo '''not''' replace SITENAME with a translation of Wikipedia or some encyclopedic additions. The message has to be neutral for all projects.\n\nParameters:\n* $1 - page title or any one of the following messages:\n** {{msg-mw|Contributions-title}}\n** {{msg-mw|Searchresults-title}}",
"pagetitle-view-mainpage": "{{optional}}",
"backlinksubtitle": "{{optional}}\nAppears in subtitle. Parameters:\n* $1 - a link to the page (HTML)",
"retrievedfrom": "Message which appears in the source of every page, but it is hidden. It is shown when printing.\n\nParameters:\n* $1 - a link back to the current page: {{FULLURL:{{FULLPAGENAME}}}}",
"apisandbox": "{{doc-special|ApiSandbox}}",
"apisandbox-summary": "{{ignored}}\n{{doc-specialpagesummary|ApiSandbox}}",
"apisandbox-jsonly": "Displayed as an error message if the browser does not have JavaScript enabled.",
- "apisandbox-api-disabled": "Displayed as an error message if the API is disabled on this site.",
"apisandbox-intro": "Displayed (from JavaScript) as a header on [[Special:ApiSandbox]].",
"apisandbox-submit": "JavaScript button label for submitting the request.",
"apisandbox-reset": "JavaScript button label for clearing the form.\n{{Identical|Clear}}",
"month": "Used in [[Special:Contributions]] and history pages ([{{fullurl:Sandbox|action=history}} example]), as label for a dropdown box to select a specific month to view the edits made in that month, and the earlier months. See also {{msg-mw|year}}.",
"year": "Used in [[Special:Contributions]] and history pages ([{{fullurl:Sandbox|action=history}} example]), as label for an input box to select a specific year to view the edits made in that year, and the earlier years.\n\nSee also:\n* {{msg-mw|month}}",
"date": "Used in [[Special:Contributions]] and history pages ([{{fullurl:Sandbox|action=history}} example]), as label for an input box to select a specific date to view the edits made on that date, and earlier.",
- "sp-contributions-newbies": "Text of radio button on special page [[Special:Contributions]].",
- "sp-contributions-newbies-sub": "Note at the top of the page of results for a search on [[Special:Contributions]] where 'Show contributions for new accounts only' has been selected.",
- "sp-contributions-newbies-title": "The page title in your browser bar, but not the page title.\n\nSee also:\n* {{msg-mw|Sp-contributions-newbies-sub}}",
"sp-contributions-blocklog": "Used as a display name for a link to the block log on for example [[Special:Contributions/Mediawiki default]]\n\nUsed as link title in [[Special:Contributions]] and in [[Special:DeletedContributions]].\n\nSee also:\n* {{msg-mw|Sp-contributions-talk}}\n* {{msg-mw|Change-blocklink}}\n* {{msg-mw|Unblocklink}}\n* {{msg-mw|Blocklink}}\n* {{msg-mw|Sp-contributions-uploads}}\n* {{msg-mw|Sp-contributions-logs}}\n* {{msg-mw|Sp-contributions-deleted}}\n* {{msg-mw|Sp-contributions-userrights}}\n{{Identical|Block log}}",
"sp-contributions-suppresslog": "Used as a display name for a link to log entries of suppressed edits made by that user.\n\nUsed as link title in [[Special:Contributions]] and in [[Special:DeletedContributions]]. Parameters:\n* $1 is a plain text username used for GENDER.\nSee also {{msg-mw|sp-contributions-deleted}}, {{msg-mw|sp-deletedcontributions-contribs}}, {{msg-mw|contributions}}, {{msg-mw|deletedcontributions-title}}.",
"sp-contributions-deleted": "This is a link anchor used in [[Special:Contributions]]/''name'', when user viewing the page has the right to delete pages, or to restore deleted pages.\n\nUsed as link title in [[Special:Contributions]]. Parameters:\n* $1 is a plain text username used for GENDER.\nSee also:\n* {{msg-mw|Sp-contributions-talk}}\n* {{msg-mw|Change-blocklink}}\n* {{msg-mw|Unblocklink}}\n* {{msg-mw|Blocklink}}\n* {{msg-mw|Sp-contributions-blocklog}}\n* {{msg-mw|Sp-contributions-uploads}}\n* {{msg-mw|Sp-contributions-logs}}\n* {{msg-mw|Sp-contributions-userrights}}",
"sp-contributions-footer": "{{ignored}}This is the footer for users that are not anonymous or newbie on [[Special:Contributions]].",
"sp-contributions-footer-anon": "{{ignored}}This is the footer for anonymous users on [[Special:Contributions]].",
"sp-contributions-footer-anon-range": "{{ignored}}This is the footer for IP ranges on [[Special:Contributions]].",
- "sp-contributions-footer-newbies": "{{ignored}}This is the footer for newbie users on [[Special:Contributions]].",
"sp-contributions-outofrange": "Message shown when a user tries to view contributions of an IP range that's too large. $1 is the numerical limit imposed on the CIDR range.",
"whatlinkshere": "The text of the link in the toolbox (on the left, below the search menu) going to [[Special:WhatLinksHere]].\n\nSee also:\n* {{msg-mw|Whatlinkshere}}\n* {{msg-mw|Accesskey-t-whatlinkshere}}\n* {{msg-mw|Tooltip-t-whatlinkshere}}",
"whatlinkshere-title": "Title of the special page [[Special:WhatLinksHere]]. This page appears when you click on the 'What links here' button in the toolbox. $1 is the name of the page concerned.",
"newimages-legend": "Caption of the fieldset for the filter on [[Special:NewImages]]\n\n{{Identical|Filter}}",
"newimages-label": "Caption of the filter editbox on [[Special:NewImages]]",
"newimages-user": "Caption of the username/IP address editbox on [[Special:NewImages]]",
- "newimages-newbies": "Used as label for a checkbox. When checked, [[Special:NewImages]] will only display uploads by new users.",
"newimages-showbots": "Used as label for a checkbox. When checked, [[Special:NewImages]] will also display uploads by users in the bots group.",
"newimages-hidepatrolled": "Used as label for a checkbox. When checked, [[Special:NewImages]] will not display patrolled uploads.\n\nCf. {{msg-mw|tog-hidepatrolled}} and {{msg-mw|apihelp-feedrecentchanges-param-hidepatrolled}}.",
"newimages-mediatype": "Used as label for a multiselect where users can select the media types to display.",
"revdelete-hide-image": "فائيل جو مواد لڪايو",
"revdelete-hide-name": "هدف ۽ نيمپيما لڪايو",
"revdelete-hide-comment": "سنوار جو تتُ",
- "revdelete-hide-user": "اÙ\8aÚ\8aÙ\8aٽر جÙ\88 Ù\88اپرائÙ\8aÙ\86دÚ\99-Ù\86اÙ\86Ø¡Ù\8f/آءÙ\90Ù¾Ù\90ي پتو",
+ "revdelete-hide-user": "سÙ\86Ù\88ارÙ\8aÙ\86دÚ\99 جÙ\88 Ù\88اپرائÙ\8aÙ\86دÚ\99-Ù\86اÙ\86Ø¡Ù\8f/آئÙ\90Ù¾ي پتو",
"revdelete-hide-restricted": "منتظمن توڙي ٻين کان مليل اعداد دٻايو",
"revdelete-radio-same": "(نہ بدلايو)",
"revdelete-radio-set": "لڪل",
"rcfilters-filter-editsbyother-label": "ٻين پاران تبديليون",
"rcfilters-filtergroup-user-experience-level": "واپرائيندڙن جي رجسٽريشن ۽ تجربو",
"rcfilters-filter-user-experience-level-registered-label": "رجسٽر ٿيل",
- "rcfilters-filter-user-experience-level-registered-description": "داخÙ\84 Ù¿Ù\8aÙ\84 اÙ\8aÚ\8aÙ\8aٽر.",
+ "rcfilters-filter-user-experience-level-registered-description": "داخÙ\84 Ù¿Ù\8aÙ\84 سÙ\86Ù\88ارÙ\8aÙ\86دÚ\99.",
"rcfilters-filter-user-experience-level-unregistered-label": "اڻرجسٽر ٿيل",
"rcfilters-filter-user-experience-level-unregistered-description": "سنواريندڙ جيڪي داخل ٿيل ناھن.",
"rcfilters-filter-user-experience-level-newcomer-label": "نوان ايندڙ",
"imagelinks": "فائيل جو استعمال",
"linkstoimage": "ھن فائيل کي {{PLURAL:$1|ھيٺيون صفحو استعمال ڪري ٿو|$1 ھيٺيان صفحا استعمال ڪن ٿا}}:",
"nolinkstoimage": "ڪي بہ صفحا ناھن جيڪي ھن فائيل کي استعمال ڪندا ھجن.",
+ "linkstoimage-redirect": "$1 (فائيل چورڻو) $2",
"sharedupload": "هيءَ فائيل $1 کان آهي ۽ ان کي ٻيون رٿائون به استعمال ڪري سگھن ٿيون.",
"sharedupload-desc-here": "ھي فائيل $1 مان آھي ۽ ٻين رٿائن پاران پڻ استعمال ٿي سگھي ٿو. تشريح انجي [[$2 جو تشريحي صفحو]] ھيٺان ڏنل آھي.",
"filepage-nofile": "ھن نالي سان ڪوبہ فائيل وجود نٿو رکي.",
"protectlogpage": "تحفظ لاگ",
"protectedarticle": "محفوظ ٿيل \"[[$1]]\"",
"modifiedarticleprotection": "\"[[$1]]\" جي تحفظ جي سطح تبديل ڪئي",
+ "unprotectedarticle": "\"[[$1]]\" تان تحفظ ھٽايو ويو",
"movedarticleprotection": "\"[[$2]]\" جو حفاظت درجو \"[[$1]]\" جي طرف منتقل ڪيو",
+ "unprotectedarticle-comment": "\"[[$1]]\" تان {{GENDER:$2|تحفظ ھٽايو}}",
"prot_1movedto2": "[[$1]] کي چوري [[$2]] تي رکيو ويو",
"protect-legend": "تحفظڻ جي پڪ ڪريو",
"protectcomment": "سبب:",
"htmlform-cloner-delete": "هٽايو",
"htmlform-title-not-exists": "$1 وجود نٿو رکي.",
"logentry-delete-delete": "$1 {{GENDER:$2|ڊاٿو}} صفحو $3",
+ "logentry-delete-restore": "$1 {{GENDER:$2|بحاليو}} صفحو $3 ($4)",
"logentry-delete-revision": "$1 $3: $4 صفحي تي {{PLURAL:$5|ھڪ مسودي|$5 مسودن}} جي ظاھريت {{GENDER:$2|تبديل ڪئي}}",
"revdelete-content-hid": "مواد لڪيل",
"revdelete-uname-hid": "واپرائيندڙ-نانءُ لڪل",
+ "revdelete-unrestricted": "منتظمن تان پابنديون ھٽايون ويون",
"logentry-block-block": "$1، {{GENDER:$4|$3}} تي $5 وقت جي خاتمي تائين {{GENDER:$2|بندش هئي آهي}} $6",
"logentry-move-move": "$1 {{GENDER:$2|چوريو}} صفحو $3 ڏانهن $4",
"logentry-move-move-noredirect": "$1 $3 صفحي کي $4 ڏانھن {{GENDER:$2|چوريو}} سواءِ ڪو ريڊائريڪٽ ڇڏيندي",
"logentry-patrol-patrol-auto": "$1 پاڻمرادو صفحي $3 جي $4 مسودي تي گشت ڪيل طور {{GENDER:$2|نشان لڳايو}}",
"logentry-newusers-create": "واپرائيندڙ کاتو $1 {{GENDER:$2|سرجيو ويو}}",
"logentry-newusers-autocreate": "واپرائيندڙ کاتو $1 پاڻمرادو {{GENDER:$2|کوليو ويو}}",
+ "logentry-protect-unprotect": "$1 $3 تان تحفظ {{GENDER:$2|ھٽايو}}",
"logentry-protect-protect": "$1 {{GENDER:$2|محفوظ ڪيو}} $3 $4",
"logentry-upload-upload": "$1 {{GENDER:$2|چاڙهيو}} $3",
"logentry-upload-overwrite": "$1 $3 جو ھڪ نئون ورزن {{GENDER:$2|چاڙھيو}}",
"mw-widgets-usersmultiselect-placeholder": "وڌيڪ شامل ڪيو...",
"date-range-from": "تاريخ کان:",
"date-range-to": "تاريخ تائين:",
+ "randomrootpage": "بلاترتيب پاڙ صفحو",
"log-action-filter-all": "سڀ"
}
"Suchichi02",
"神樂坂秀吉",
"WQL",
- "Looong"
+ "Looong",
+ "予弦"
]
},
"tog-underline": "链接下划线:",
}
protected function doDBUpdates() {
- global $wgActorTableSchemaMigrationStage;
-
$batchSize = $this->getBatchSize();
$db = $this->getDB( DB_MASTER );
if ( !$db->tableExists( 'log_search' ) ) {
}
$end = $db->selectField( 'logging', 'MAX(log_id)', '', __FUNCTION__ );
+ // This maintenance script is for updating pre-1.16 to 1.16. The target_author_id and
+ // target_author_ip relations it adds will later be migrated to target_author_actor by
+ // migrateActors.php. If the schema is already 1.34, we should have nothing to do.
+ if ( !$db->fieldExists( 'logging', 'log_user' ) ) {
+ $this->output(
+ "This does not appear to be an upgrade from MediaWiki pre-1.16 "
+ . "(logging.log_user does not exist).\n"
+ );
+ $this->output( "Nothing to do.\n" );
+
+ return true;
+ }
+
# Do remaining chunk
$end += $batchSize - 1;
$blockStart = $start;
'logging', [ 'log_id', 'log_type', 'log_action', 'log_params' ], $cond, __FUNCTION__
);
foreach ( $res as $row ) {
+ // RevisionDelete logs - revisions
if ( LogEventsList::typeAction( $row, $delTypes, 'revision' ) ) {
- // RevisionDelete logs - revisions
$params = LogPage::extractParams( $row->log_params );
// Param format: <urlparam> <item CSV> [<ofield> <nfield>]
if ( count( $params ) < 2 ) {
$log = new LogPage( $row->log_type );
// Add item relations...
$log->addRelations( $field, $items, $row->log_id );
- // Query item author relations...
+ // Determine what table to query...
$prefix = substr( $field, 0, strpos( $field, '_' ) ); // db prefix
if ( !isset( self::$tableMap[$prefix] ) ) {
continue; // bad row?
}
- $tables = [ self::$tableMap[$prefix] ];
- $fields = [];
- $joins = [];
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
- // Read the old fields if we're still writing them regardless of read mode, to handle upgrades
- $fields['userid'] = $prefix . '_user';
- $fields['username'] = $prefix . '_user_text';
- }
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- // Read the new fields if we're writing them regardless of read mode, to handle upgrades
- if ( $prefix === 'rev' ) {
- $tables[] = 'revision_actor_temp';
- $joins['revision_actor_temp'] = [
- ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ? 'LEFT JOIN' : 'JOIN',
- 'rev_id = revactor_rev',
- ];
- $fields['actorid'] = 'revactor_actor';
- } else {
- $fields['actorid'] = $prefix . '_actor';
+ $table = self::$tableMap[$prefix];
+ $userField = $prefix . '_user';
+ $userTextField = $prefix . '_user_text';
+ // Add item author relations...
+ $userIds = $userIPs = [];
+ $sres = $db->select( $table,
+ [ $userField, $userTextField ],
+ [ $field => $items ]
+ );
+ foreach ( $sres as $srow ) {
+ if ( $srow->$userField > 0 ) {
+ $userIds[] = intval( $srow->$userField );
+ } elseif ( $srow->$userTextField != '' ) {
+ $userIPs[] = $srow->$userTextField;
}
}
- $sres = $db->select( $tables, $fields, [ $field => $items ], __METHOD__, [], $joins );
+ // Add item author relations...
+ $log->addRelations( 'target_author_id', $userIds, $row->log_id );
+ $log->addRelations( 'target_author_ip', $userIPs, $row->log_id );
} elseif ( LogEventsList::typeAction( $row, $delTypes, 'event' ) ) {
// RevisionDelete logs - log events
$params = LogPage::extractParams( $row->log_params );
$log = new LogPage( $row->log_type );
// Add item relations...
$log->addRelations( 'log_id', $items, $row->log_id );
- // Query item author relations...
- $fields = [];
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
- // Read the old fields if we're still writing them regardless of read mode, to handle upgrades
- $fields['userid'] = 'log_user';
- $fields['username'] = 'log_user_text';
- }
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- // Read the new fields if we're writing them regardless of read mode, to handle upgrades
- $fields['actorid'] = 'log_actor';
- }
-
- $sres = $db->select( 'logging', $fields, [ 'log_id' => $items ], __METHOD__ );
- } else {
- continue;
- }
-
- // Add item author relations...
- $userIds = $userIPs = $userActors = [];
- foreach ( $sres as $srow ) {
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
- if ( $srow->userid > 0 ) {
- $userIds[] = intval( $srow->userid );
- } elseif ( $srow->username != '' ) {
- $userIPs[] = $srow->username;
+ // Add item author relations...
+ $userIds = $userIPs = [];
+ $sres = $db->select( 'logging',
+ [ 'log_user', 'log_user_text' ],
+ [ 'log_id' => $items ]
+ );
+ foreach ( $sres as $srow ) {
+ if ( $srow->log_user > 0 ) {
+ $userIds[] = intval( $srow->log_user );
+ } elseif ( IP::isIPAddress( $srow->log_user_text ) ) {
+ $userIPs[] = $srow->log_user_text;
}
}
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- if ( $srow->actorid ) {
- $userActors[] = intval( $srow->actorid );
- } elseif ( $srow->userid > 0 ) {
- $userActors[] = User::newFromId( $srow->userid )->getActorId( $db );
- } else {
- $userActors[] = User::newFromName( $srow->username, false )->getActorId( $db );
- }
- }
- }
- // Add item author relations...
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
$log->addRelations( 'target_author_id', $userIds, $row->log_id );
$log->addRelations( 'target_author_ip', $userIPs, $row->log_id );
}
- if ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
- $log->addRelations( 'target_author_actor', $userActors, $row->log_id );
- }
}
$blockStart += $batchSize;
$blockEnd += $batchSize;
$this->output( "MediaWiki {$wgVersion} Updater\n\n" );
- foreach ( SpecialVersion::getSoftwareInformation() as $name => $version ) {
- $this->output( "{$name}: {$version}\n" );
- }
-
wfWaitForSlaves();
if ( !$this->hasOption( 'skip-compat-checks' ) ) {
"requires": {
"lodash": "^4.17.11"
}
+ },
+ "lodash": {
+ "version": "4.17.15",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
+ "dev": true
}
}
},
}
}
},
- "humanize-duration": {
- "version": "3.15.3",
- "resolved": "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.15.3.tgz",
- "integrity": "sha512-BMz6w8p3NVa6QP9wDtqUkXfwgBqDaZ5z/np0EYdoWrLqL849Onp6JWMXMhbHtuvO9jUThLN5H1ThRQ8dUWnYkA==",
- "dev": true
- },
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
}
},
"lodash": {
- "version": "4.17.11",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
- "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
+ "version": "4.17.15",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
},
"lodash.get": {
}
},
"mixin-deep": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
- "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
+ "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==",
"dev": true,
"requires": {
"for-in": "^1.0.2",
"dev": true
},
"set-value": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
- "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
+ "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
}
},
"union-value": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
- "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=",
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
+ "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
"dev": true,
"requires": {
"arr-union": "^3.1.0",
"get-value": "^2.0.6",
"is-extendable": "^0.1.1",
- "set-value": "^0.4.3"
- },
- "dependencies": {
- "extend-shallow": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
- "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
- "dev": true,
- "requires": {
- "is-extendable": "^0.1.0"
- }
- },
- "set-value": {
- "version": "0.4.3",
- "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
- "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
- "dev": true,
- "requires": {
- "extend-shallow": "^2.0.1",
- "is-extendable": "^0.1.1",
- "is-plain-object": "^2.0.1",
- "to-object-path": "^0.3.0"
- }
- }
+ "set-value": "^2.0.1"
}
},
"uniq": {
"sauce-connect-launcher": "~1.2.3"
}
},
- "wdio-spec-reporter": {
- "version": "0.1.5",
- "resolved": "https://registry.npmjs.org/wdio-spec-reporter/-/wdio-spec-reporter-0.1.5.tgz",
- "integrity": "sha512-MqvgTow8hFwhFT47q67JwyJyeynKodGRQCxF7ijKPGfsaG1NLssbXYc0JhiL7SiAyxnQxII0UxzTCd3I6sEdkg==",
- "dev": true,
- "requires": {
- "babel-runtime": "~6.26.0",
- "chalk": "^2.3.0",
- "humanize-duration": "~3.15.0"
- }
- },
"wdio-sync": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/wdio-sync/-/wdio-sync-0.7.3.tgz",
"postcss-less": "2.0.0",
"qunit": "2.9.1",
"stylelint-config-wikimedia": "0.6.0",
+ "wdio-dot-reporter": "0.0.10",
"wdio-junit-reporter": "0.4.4",
"wdio-mediawiki": "file:tests/selenium/wdio-mediawiki",
"wdio-mocha-framework": "0.6.4",
"wdio-sauce-service": "0.4.14",
- "wdio-spec-reporter": "0.1.5",
"webdriverio": "4.14.4"
}
}
integrity: sha384-RPXhaTf22QktT8KTwZ6bUz/C+7CnccaIw5W/y/t0FW5WSDGj3wc3YtRIJC0w47in
jquery:
- type: file
- src: https://code.jquery.com/jquery-3.3.1.js
- # Integrity from link modals https://code.jquery.com/jquery/
- integrity: sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=
- dest: jquery.js
+ type: multi-file
+ files:
+ # Integrities from link modals https://code.jquery.com/jquery/
+ jquery.migrate.js:
+ src: https://code.jquery.com/jquery-migrate-3.0.1.js
+ integrity: sha256-VvnF+Zgpd00LL73P2XULYXEn6ROvoFaa/vbfoiFlZZ4=
+ jquery.js:
+ src: https://code.jquery.com/jquery-3.3.1.js
+ integrity: sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=
jquery.chosen:
type: multi-file
---- jquery-3.3.1.js 2019-04-01 08:39:29.000000000 +0200
-+++ jquery-3.3.1.js 2019-04-01 09:02:39.000000000 +0200
-@@ -260,8 +260,9 @@ jQuery.extend = jQuery.fn.extend = function() {
- for ( name in options ) {
- src = target[ name ];
+diff --git a/resources/lib/jquery/jquery.js b/resources/lib/jquery/jquery.js
+index 9b5206bcc6..34a5703d80 100644
+--- a/resources/lib/jquery/jquery.js
++++ b/resources/lib/jquery/jquery.js
+@@ -261,8 +261,9 @@ jQuery.extend = jQuery.fn.extend = function() {
+ src = target[ name ];
copy = options[ name ];
+ // Prevent Object.prototype pollution
--- /dev/null
+diff --git a/resources/lib/jquery/jquery.migrate.js b/resources/lib/jquery/jquery.migrate.js
+index 6ba8af4a42..711e424a39 100644
+--- a/resources/lib/jquery/jquery.migrate.js
++++ b/resources/lib/jquery/jquery.migrate.js
+@@ -1,6 +1,14 @@
+ /*!
+ * jQuery Migrate - v3.0.1 - 2017-09-26
+ * Copyright jQuery Foundation and other contributors
++ *
++ * Patched for MediaWiki:
++ * - Qualify the global lookup for 'jQuery' as 'window.jQuery',
++ * because within mw.loader.implement() for 'jquery', the closure
++ * specifies '$' and 'jQuery', which are undefined.
++ * - Add mw.track instrumentation for statistics.
++ * - Disable jQuery.migrateTrace by default. They are slow and
++ * redundant given console.warn() already provides a trace.
+ */
+ ;( function( factory ) {
+ if ( typeof define === "function" && define.amd ) {
+@@ -15,7 +23,8 @@
+ } else {
+
+ // Browser globals
+- factory( jQuery, window );
++ // PATCH: Qualify jQuery lookup as window.jQuery. --Krinkle
++ factory( window.jQuery, window );
+ }
+ } )( function( jQuery, window ) {
+ "use strict";
+@@ -58,7 +67,8 @@ jQuery.migrateWarnings = [];
+
+ // Set to false to disable traces that appear with warnings
+ if ( jQuery.migrateTrace === undefined ) {
+- jQuery.migrateTrace = true;
++ // PATCH: Disable extra console.trace() call --Krinkle
++ jQuery.migrateTrace = false;
+ }
+
+ // Forget any warnings we've already given; public
+@@ -72,6 +82,10 @@ function migrateWarn( msg ) {
+ if ( !warnedAbout[ msg ] ) {
+ warnedAbout[ msg ] = true;
+ jQuery.migrateWarnings.push( msg );
++ // PATCH: Add instrumentation for statistics --Krinkle
++ if ( window.mw && window.mw.track ) {
++ window.mw.track( "mw.deprecate", "jquery-migrate" );
++ }
+ if ( console && console.warn && !jQuery.migrateMute ) {
+ console.warn( "JQMIGRATE: " + msg );
+ if ( jQuery.migrateTrace && console.trace ) {
+@@ -466,20 +480,6 @@ jQuery.each( [ "load", "unload", "error" ], function( _, name ) {
+
+ } );
+
+-jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " +
+- "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+- "change select submit keydown keypress keyup contextmenu" ).split( " " ),
+- function( i, name ) {
+-
+- // Handle event binding
+- jQuery.fn[ name ] = function( data, fn ) {
+- migrateWarn( "jQuery.fn." + name + "() event shorthand is deprecated" );
+- return arguments.length > 0 ?
+- this.on( name, null, data, fn ) :
+- this.trigger( name );
+- };
+-} );
+-
+ // Trigger "ready" event only once, on document ready
+ jQuery( function() {
+ jQuery( window.document ).triggerHandler( "ready" );
* @param {boolean} [options.strictMode=false] Trigger strict mode parsing of the url.
* @param {boolean} [options.overrideKeys=false] Whether to let duplicate query parameters
* override each other (`true`) or automagically convert them to an array (`false`).
+ * @param {boolean} [options.arrayParams=false] Whether to parse array query parameters (e.g.
+ * `&foo[0]=a&foo[1]=b` or `&foo[]=a&foo[]=b`) or leave them alone. Currently this does not
+ * handle associative or multi-dimensional arrays, but that may be improved in the future.
+ * Implies `overrideKeys: true` (query parameters without `[...]` are not parsed as arrays).
* @throws {Error} when the query string or fragment contains an unknown % sequence
*/
function Uri( uri, options ) {
options = typeof options === 'object' ? options : { strictMode: !!options };
options = $.extend( {
strictMode: false,
- overrideKeys: false
+ overrideKeys: false,
+ arrayParams: false
}, options );
+ this.arrayParams = options.arrayParams;
+
if ( uri !== undefined && uri !== null && uri !== '' ) {
if ( typeof uri === 'string' ) {
this.parse( uri, options );
// using replace to iterate over a string
if ( uri.query ) {
uri.query.replace( /(?:^|&)([^&=]*)(?:(=)([^&]*))?/g, function ( match, k, eq, v ) {
+ var arrayKeyMatch, i;
if ( k ) {
k = Uri.decode( k );
v = ( eq === '' || eq === undefined ) ? null : Uri.decode( v );
+ arrayKeyMatch = k.match( /^([^[]+)\[(\d*)\]$/ );
+
+ // If arrayParams and this parameter name contains an array index...
+ if ( options.arrayParams && arrayKeyMatch ) {
+ // Remove the index from parameter name
+ k = arrayKeyMatch[ 1 ];
+
+ // Turn the parameter value into an array (throw away anything else)
+ if ( !Array.isArray( q[ k ] ) ) {
+ q[ k ] = [];
+ }
+
+ i = arrayKeyMatch[ 2 ];
+ if ( i === '' ) {
+ // If no explicit index, append at the end
+ i = q[ k ].length;
+ }
+
+ q[ k ][ i ] = v;
// If overrideKeys, always (re)set top level value.
// If not overrideKeys but this key wasn't set before, then we set it as well.
- if ( options.overrideKeys || !hasOwn.call( q, k ) ) {
+ // arrayParams implies overrideKeys (no array handling for non-array params).
+ } else if ( options.arrayParams || options.overrideKeys || !hasOwn.call( q, k ) ) {
q[ k ] = v;
// Use arrays if overrideKeys is false and key was already seen before
* @return {string}
*/
getQueryString: function () {
- var args = [];
+ var args = [],
+ arrayParams = this.arrayParams;
// eslint-disable-next-line no-jquery/no-each-util
$.each( this.query, function ( key, val ) {
var k = Uri.encode( key ),
- vals = Array.isArray( val ) ? val : [ val ];
- vals.forEach( function ( v ) {
+ isArrayParam = Array.isArray( val ),
+ vals = isArrayParam ? val : [ val ];
+ vals.forEach( function ( v, i ) {
+ var ki = k;
+ if ( arrayParams && isArrayParam ) {
+ ki += Uri.encode( '[' + i + ']' );
+ }
if ( v === null ) {
- args.push( k );
+ args.push( ki );
} else if ( k === 'title' ) {
- args.push( k + '=' + mw.util.wikiUrlencode( v ) );
+ args.push( ki + '=' + mw.util.wikiUrlencode( v ) );
} else {
- args.push( k + '=' + Uri.encode( v ) );
+ args.push( ki + '=' + Uri.encode( v ) );
}
} );
} );
)
);
- if ( this.cache ) {
- this.cache.set( pageData );
- }
-
// Offer the exact text as a suggestion if the page exists
if ( this.addQueryInput && pageExists && !pageExistsExact ) {
titles.unshift( this.getQueryValue() );
+ // Ensure correct page metadata gets used
+ pageData[ this.getQueryValue() ] = pageData[ titleObj.getPrefixedText() ];
+ }
+
+ if ( this.cache ) {
+ this.cache.set( pageData );
}
for ( i = 0, len = titles.length; i < len; i++ ) {
'class' => NullLockManager::class,
] ];
$reset = function () {
- LockManagerGroup::destroySingletons();
+ MediaWikiServices::getInstance()->resetServiceForTesting( 'LockManagerGroupFactory' );
};
$setup[] = $reset;
$teardown[] = $reset;
!! wikitext
[[File:Cool "Gator".png]]
!! html/php+tidy
-<p><a href="/index.php?title=Special:Upload&wpDestFile=Cool_%22Gator%22.png" class="new" title="File:Cool "Gator".png">File:Cool "Gator".png</a>
+<p><a href="/index.php?title=Special:Upload&wpDestFile=Cool_%22Gator%22.png" class="new" title="File:Cool "Gator".png">File:Cool "Gator".png</a>
</p>
!! html/parsoid
<p><figure-inline class="mw-default-size" typeof="mw:Error mw:Image" data-mw='{"errors":[{"key":"apierror-filedoesnotexist","message":"This image does not exist."}]}'><a href="./Special:FilePath/Cool_%22Gator%22.png"><span resource='./File:Cool_"Gator".png' data-parsoid='{"a":{"resource":"./File:Cool_\"Gator\".png"},"sa":{"resource":"File:Cool \"Gator\".png"}}'>File:Cool "Gator".png</span></a></figure-inline></p>
!! end
+!! test
+File containing single quotes
+!! wikitext
+[[File:Foo's ''italic'' bar.jpg]]
+[[File:Foo's ''italic'' bar.jpg|Foo's ''italic'' bar]]
+!! html/php+tidy
+<p><a href="/index.php?title=Special:Upload&wpDestFile=Foo%27s_%27%27italic%27%27_bar.jpg" class="new" title="File:Foo's ''italic'' bar.jpg">File:Foo's <i>italic</i> bar.jpg</a>
+<a href="/index.php?title=Special:Upload&wpDestFile=Foo%27s_%27%27italic%27%27_bar.jpg" class="new" title="File:Foo's ''italic'' bar.jpg">Foo's italic bar</a>
+</p>
+!! end
+
!! test
Redirect containing double quotes and spaces
!! wikitext
[[File:Nonexistent|<]]
[[File:Nonexistent|a<i>b</i>c]]
!! html/php
-<p><a href="/index.php?title=Special:Upload&wpDestFile=Nonexistent" class="new" title="File:Nonexistent"><script></script></a>
-<a href="/index.php?title=Special:Upload&wpDestFile=Nonexistent" class="new" title="File:Nonexistent"><script></script></a>
+<p><a href="/index.php?title=Special:Upload&wpDestFile=Nonexistent" class="new" title="File:Nonexistent"><script></script></a>
+<a href="/index.php?title=Special:Upload&wpDestFile=Nonexistent" class="new" title="File:Nonexistent"><script></script></a>
<a href="/index.php?title=Special:Upload&wpDestFile=Nonexistent" class="new" title="File:Nonexistent"><</a>
<a href="/index.php?title=Special:Upload&wpDestFile=Nonexistent" class="new" title="File:Nonexistent">abc</a>
</p>
*/
private function mockResultClosure( $title, $setters = [] ) {
return function () use ( $title, $setters ){
- $result = MockSearchResult::newFromTitle( Title::newFromText( $title ) );
+ $result = new MockSearchResult( Title::newFromText( $title ) );
foreach ( $setters as $method => $param ) {
$result->$method( $param );
*/
public function testCheckPopularPasswordBlacklist( $expected, $password ) {
global $IP;
- $this->hideDeprecated( 'PasswordPolicyChecks::checkPopularPasswordBlacklist' );
$this->setMwGlobals( [
'wgSitename' => 'sitename',
'wgPopularPasswordFile' => "$IP/includes/password/commonpasswords.cdb"
<?php
-class MockSearchResult extends SearchResult {
+class MockSearchResult extends RevisionSearchResult {
private $isMissingRevision = false;
private $isBrokenTitle = false;
--- /dev/null
+<?php
+
+use MediaWiki\FileBackend\LockManager\LockManagerGroupFactory;
+use Wikimedia\Rdbms\LBFactory;
+
+/**
+ * @covers MediaWiki\FileBackend\LockManager\LockManagerGroupFactory
+ * @todo Should we somehow test that the LockManagerGroup objects are as we expect? How do we do
+ * that without getting into testing LockManagerGroup itself?
+ */
+class LockManagerGroupFactoryTest extends MediaWikiUnitTestCase {
+ public function testGetLockManagerGroup() {
+ $mockLbFactory = $this->createMock( LBFactory::class );
+ $mockLbFactory->expects( $this->never() )->method( $this->anything() );
+
+ $factory = new LockManagerGroupFactory( 'defaultDomain', [], $mockLbFactory );
+ $lbmUnspecified = $factory->getLockManagerGroup();
+ $lbmFalse = $factory->getLockManagerGroup( false );
+ $lbmDefault = $factory->getLockManagerGroup( 'defaultDomain' );
+ $lbmOther = $factory->getLockManagerGroup( 'otherDomain' );
+
+ $this->assertSame( $lbmUnspecified, $lbmFalse );
+ $this->assertSame( $lbmFalse, $lbmDefault );
+ $this->assertSame( $lbmDefault, $lbmUnspecified );
+ $this->assertNotEquals( $lbmUnspecified, $lbmOther );
+ $this->assertNotEquals( $lbmFalse, $lbmOther );
+ $this->assertNotEquals( $lbmDefault, $lbmOther );
+
+ $this->assertSame( $lbmUnspecified, $factory->getLockManagerGroup() );
+ $this->assertSame( $lbmFalse, $factory->getLockManagerGroup( false ) );
+ $this->assertSame( $lbmDefault, $factory->getLockManagerGroup( 'defaultDomain' ) );
+ $this->assertSame( $lbmOther, $factory->getLockManagerGroup( 'otherDomain' ) );
+ }
+}
--- /dev/null
+<?php
+
+use Wikimedia\Rdbms\LBFactory;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * Since this is a unit test, we don't test the singleton() or destroySingletons() methods. We also
+ * can't test get() with a valid argument, because that winds up calling static methods of
+ * ObjectCache and LoggerFactory that aren't yet compatible with proper unit tests. Those will be
+ * tested in the integration test for now.
+ *
+ * @covers LockManagerGroup
+ */
+class LockManagerGroupTest extends MediaWikiUnitTestCase {
+ private function getMockLBFactory() {
+ $mock = $this->createMock( LBFactory::class );
+ $mock->expects( $this->never() )->method( $this->anythingBut( '__destruct' ) );
+ return $mock;
+ }
+
+ public function testConstructorNoConfigs() {
+ new LockManagerGroup( 'domain', [], $this->getMockLBFactory() );
+ $this->assertTrue( true, 'No exception thrown' );
+ }
+
+ public function testConstructorConfigWithNoName() {
+ $this->setExpectedException( Exception::class,
+ 'Cannot register a lock manager with no name.' );
+
+ new LockManagerGroup( 'domain',
+ [ [ 'name' => 'a', 'class' => 'b' ], [ 'class' => 'c' ] ], $this->getMockLBFactory() );
+ }
+
+ public function testConstructorConfigWithNoClass() {
+ $this->setExpectedException( Exception::class,
+ 'Cannot register lock manager `c` with no class.' );
+
+ new LockManagerGroup( 'domain',
+ [ [ 'name' => 'a', 'class' => 'b' ], [ 'name' => 'c' ] ], $this->getMockLBFactory() );
+ }
+
+ public function testGetUndefined() {
+ $this->setExpectedException( Exception::class,
+ 'No lock manager defined with the name `c`.' );
+
+ $lmg = new LockManagerGroup( 'domain', [ [ 'name' => 'a', 'class' => 'b' ] ],
+ $this->getMockLBFactory() );
+ $lmg->get( 'c' );
+ }
+
+ public function testConfigUndefined() {
+ $this->setExpectedException( Exception::class,
+ 'No lock manager defined with the name `c`.' );
+
+ $lmg = new LockManagerGroup( 'domain', [ [ 'name' => 'a', 'class' => 'b' ] ],
+ $this->getMockLBFactory() );
+ $lmg->config( 'c' );
+ }
+
+ public function testConfig() {
+ $lmg = new LockManagerGroup( 'domain', [ [ 'name' => 'a', 'class' => 'b', 'foo' => 'c' ] ],
+ $this->getMockLBFactory() );
+ $this->assertSame(
+ [ 'class' => 'b', 'name' => 'a', 'foo' => 'c', 'domain' => 'domain' ],
+ $lmg->config( 'a' )
+ );
+ }
+
+ public function testGetDefaultNull() {
+ $lmg = new LockManagerGroup( 'domain', [], $this->getMockLBFactory() );
+ $expected = new NullLockManager( [] );
+ $actual = $lmg->getDefault();
+ // Have to get rid of the $sessions for equality check to work
+ TestingAccessWrapper::newFromObject( $actual )->session = null;
+ TestingAccessWrapper::newFromObject( $expected )->session = null;
+ $this->assertEquals( $expected, $actual );
+ }
+
+ public function testGetAnyException() {
+ // XXX Isn't the name 'getAny' misleading if we don't get whatever's available?
+ $this->setExpectedException( Exception::class,
+ 'No lock manager defined with the name `fsLockManager`.' );
+
+ $lmg = new LockManagerGroup( 'domain', [ [ 'name' => 'a', 'class' => 'b' ] ],
+ $this->getMockLBFactory() );
+ $lmg->getAny();
+ }
+}
} );
+ QUnit.test( 'arrayParams', function ( assert ) {
+ var uri1, uri2, uri3, expectedQ, expectedS,
+ uriMissing, expectedMissingQ, expectedMissingS,
+ uriWeird, expectedWeirdQ, expectedWeirdS;
+
+ uri1 = new mw.Uri( 'http://example.com/?foo[]=a&foo[]=b&foo[]=c', { arrayParams: true } );
+ uri2 = new mw.Uri( 'http://example.com/?foo[0]=a&foo[1]=b&foo[2]=c', { arrayParams: true } );
+ uri3 = new mw.Uri( 'http://example.com/?foo[1]=b&foo[0]=a&foo[]=c', { arrayParams: true } );
+ expectedQ = { foo: [ 'a', 'b', 'c' ] };
+ expectedS = 'foo%5B0%5D=a&foo%5B1%5D=b&foo%5B2%5D=c';
+
+ assert.deepEqual( uri1.query, expectedQ,
+ 'array query parameters are parsed (implicit indexes)' );
+ assert.deepEqual( uri1.getQueryString(), expectedS,
+ 'array query parameters are encoded (always with explicit indexes)' );
+ assert.deepEqual( uri2.query, expectedQ,
+ 'array query parameters are parsed (explicit indexes)' );
+ assert.deepEqual( uri2.getQueryString(), expectedS,
+ 'array query parameters are encoded (always with explicit indexes)' );
+ assert.deepEqual( uri3.query, expectedQ,
+ 'array query parameters are parsed (mixed indexes, out of order)' );
+ assert.deepEqual( uri3.getQueryString(), expectedS,
+ 'array query parameters are encoded (always with explicit indexes)' );
+
+ uriMissing = new mw.Uri( 'http://example.com/?foo[0]=a&foo[2]=c', { arrayParams: true } );
+ // eslint-disable-next-line no-sparse-arrays
+ expectedMissingQ = { foo: [ 'a', , 'c' ] };
+ expectedMissingS = 'foo%5B0%5D=a&foo%5B2%5D=c';
+
+ assert.deepEqual( uriMissing.query, expectedMissingQ,
+ 'array query parameters are parsed (missing array item)' );
+ assert.deepEqual( uriMissing.getQueryString(), expectedMissingS,
+ 'array query parameters are encoded (missing array item)' );
+
+ uriWeird = new mw.Uri( 'http://example.com/?foo[0]=a&foo[1][1]=b&foo[x]=c', { arrayParams: true } );
+ expectedWeirdQ = { foo: [ 'a' ], 'foo[1][1]': 'b', 'foo[x]': 'c' };
+ expectedWeirdS = 'foo%5B0%5D=a&foo%5B1%5D%5B1%5D=b&foo%5Bx%5D=c';
+
+ assert.deepEqual( uriWeird.query, expectedWeirdQ,
+ 'array query parameters are parsed (multi-dimensional or associative arrays are ignored)' );
+ assert.deepEqual( uriWeird.getQueryString(), expectedWeirdS,
+ 'array query parameters are encoded (multi-dimensional or associative arrays are ignored)' );
+ } );
+
QUnit.test( '.clone()', function ( assert ) {
var original, clone;
// Test reporter for stdout.
// See also: http://webdriver.io/guide/testrunner/reporters.html
- reporters: [ 'spec', 'junit' ],
+ reporters: [ 'dot', 'junit' ],
reporterOptions: {
junit: {
outputDir: logPath