passed a bad code.
* ApiBase::checkTitleUserPermissions() now takes an options array as its third
parameter. Passing a User object or null is deprecated.
+* The api-feature-usage log channel now has log context. The text message is
+ deprecated and will be removed in the future.
=== Languages updated in 1.33 ===
MediaWiki supports over 350 languages. Many localisations are updated regularly.
Block::isCreateAccountBlocked and Block::isUsertalkEditAllowed to get and set
block properties; use Block::appliesToRight and Block::appliesToUsertalk to
check block behaviour.
+* The api-feature-usage log channel now has log context. The text message is
+ deprecated and will be removed in the future.
=== Other changes in 1.33 ===
* (T201747) Html::openElement() warns if given an element name with a space
'MediaWiki\\Logger\\ConsoleSpi' => __DIR__ . '/includes/debug/logger/ConsoleSpi.php',
'MediaWiki\\Logger\\LegacyLogger' => __DIR__ . '/includes/debug/logger/LegacyLogger.php',
'MediaWiki\\Logger\\LegacySpi' => __DIR__ . '/includes/debug/logger/LegacySpi.php',
+ 'MediaWiki\\Logger\\LogCapturingSpi' => __DIR__ . '/includes/debug/logger/LogCapturingSpi.php',
'MediaWiki\\Logger\\LoggerFactory' => __DIR__ . '/includes/debug/logger/LoggerFactory.php',
'MediaWiki\\Logger\\MonologSpi' => __DIR__ . '/includes/debug/logger/MonologSpi.php',
'MediaWiki\\Logger\\Monolog\\AvroFormatter' => __DIR__ . '/includes/debug/logger/monolog/AvroFormatter.php',
'MediaWiki\\Widget\\TitlesMultiselectWidget' => __DIR__ . '/includes/widget/TitlesMultiselectWidget.php',
'MediaWiki\\Widget\\UserInputWidget' => __DIR__ . '/includes/widget/UserInputWidget.php',
'MediaWiki\\Widget\\UsersMultiselectWidget' => __DIR__ . '/includes/widget/UsersMultiselectWidget.php',
- 'Mediawiki\\Logger\\LogCapturingSpi' => __DIR__ . '/includes/debug/logger/LogCapturingSpi.php',
'MemcLockManager' => __DIR__ . '/includes/libs/lockmanager/MemcLockManager.php',
'MemcachedBagOStuff' => __DIR__ . '/includes/libs/objectcache/MemcachedBagOStuff.php',
'MemcachedClient' => __DIR__ . '/includes/libs/objectcache/MemcachedClient.php',
* they can never edit it. (Ideally the flag would be stored as
* null in these cases, but the database field isn't nullable.)
*
+ * This method does not validate that the passed in talk page belongs to the
+ * block target since the target (an IP) might not be the same as the user's
+ * talk page (if they are logged in).
+ *
* @since 1.33
* @param Title|null $usertalk The user's user talk page. If null,
* and if the target is a User, the target's userpage is used
* @return bool The user can edit their talk page
*/
public function appliesToUsertalk( Title $usertalk = null ) {
- $target = $this->target;
- $targetIsUser = $target instanceof User;
- $targetName = $targetIsUser ? $target->getName() : $target;
-
if ( !$usertalk ) {
- if ( $targetIsUser ) {
+ if ( $this->target instanceof User ) {
$usertalk = $this->target->getTalkPage();
} else {
throw new InvalidArgumentException(
);
}
- switch ( $this->type ) {
- case self::TYPE_USER:
- case self::TYPE_IP:
- if ( $usertalk->getText() !== $targetName ) {
- throw new InvalidArgumentException(
- '$usertalk must be a talk page for the block target'
- );
- }
- break;
- case self::TYPE_RANGE:
- if ( !IP::isInRange( $usertalk->getText(), $target ) ) {
- throw new InvalidArgumentException(
- '$usertalk must be a talk page for an IP within the block target range'
- );
- }
- break;
- default:
- throw new LogicException(
- 'Cannot determine validity of $usertalk for this type of block'
- );
- }
-
if ( !$this->isSitewide() ) {
if ( $this->appliesToPage( $usertalk->getArticleID() ) ) {
return true;
*/
$wgEnableBlockNoticeStats = false;
+/**
+ * Origin Trials tokens.
+ *
+ * @since 1.34
+ * @var array
+ */
+$wgOriginTrials = [];
+
+/**
+ * Enable client-side Priority Hints.
+ *
+ * @warning EXPERIMENTAL!
+ *
+ * @since 1.34
+ * @var bool
+ */
+$wgPriorityHints = false;
+
/**
* For really cool vim folding this needs to be at the end:
* vim: foldmarker=@{,@} foldmethod=marker
return false;
}
+ /**
+ * Get the Origin-Trial header values. This is used to enable Chrome Origin
+ * Trials: https://github.com/GoogleChrome/OriginTrials
+ *
+ * @return array
+ */
+ private function getOriginTrials() {
+ $config = $this->getConfig();
+
+ return $config->get( 'OriginTrials' );
+ }
+
/**
* Send cache control HTTP headers
*/
$response->header( "X-Frame-Options: $frameOptions" );
}
+ $originTrials = $this->getOriginTrials();
+ foreach ( $originTrials as $originTrial ) {
+ $response->header( "Origin-Trial: $originTrial", false );
+ }
+
ContentSecurityPolicy::sendHeaders( $this );
if ( $this->mArticleBodyOnly ) {
if ( !class_exists( $classOrCallable ) ) {
return false;
}
- $obj = new $classOrCallable( $page, $context );
- return $obj;
+ return new $classOrCallable( $page, $context );
}
if ( is_callable( $classOrCallable ) ) {
} else {
$actionName = 'view';
}
- } elseif ( $actionName == 'editredlink' ) {
+ } elseif ( $actionName === 'editredlink' ) {
$actionName = 'edit';
}
*/
protected function setHeaders() {
$out = $this->getOutput();
- $out->setRobotPolicy( "noindex,nofollow" );
+ $out->setRobotPolicy( 'noindex,nofollow' );
$out->setPageTitle( $this->getPageTitle() );
$out->setSubtitle( $this->getDescription() );
$out->setArticleRelated( true );
*/
public function logFeatureUsage( $feature ) {
$request = $this->getRequest();
- $s = '"' . addslashes( $feature ) . '"' .
- ' "' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) . '"' .
- ' "' . $request->getIP() . '"' .
- ' "' . addslashes( $request->getHeader( 'Referer' ) ) . '"' .
- ' "' . addslashes( $this->getMain()->getUserAgent() ) . '"';
- wfDebugLog( 'api-feature-usage', $s, 'private' );
+ $ctx = [
+ 'feature' => $feature,
+ // Spaces to underscores in 'username' for historical reasons.
+ 'username' => str_replace( ' ', '_', $this->getUser()->getName() ),
+ 'ip' => $request->getIP(),
+ 'referer' => (string)$request->getHeader( 'Referer' ),
+ 'agent' => $this->getMain()->getUserAgent(),
+ ];
+
+ // Text string is deprecated. Remove (or replace with just $feature) in MW 1.34.
+ $s = '"' . addslashes( $ctx['feature'] ) . '"' .
+ ' "' . wfUrlencode( $ctx['username'] ) . '"' .
+ ' "' . $ctx['ip'] . '"' .
+ ' "' . addslashes( $ctx['referer'] ) . '"' .
+ ' "' . addslashes( $ctx['agent'] ) . '"';
+
+ wfDebugLog( 'api-feature-usage', $s, 'private', $ctx );
}
/**@}*/
*/
protected function logRequest( $time, $e = null ) {
$request = $this->getRequest();
- $logCtx = [
+ $legacyLogCtx = [
'ts' => time(),
'ip' => $request->getIP(),
'userAgent' => $this->getUserAgent(),
'params' => [],
];
+ $logCtx = [
+ '$schema' => '/mediawiki/api/request/0.0.1',
+ 'meta' => [
+ 'id' => UIDGenerator::newUUIDv1(),
+ 'dt' => gmdate( 'c' ),
+ 'domain' => $this->getConfig()->get( 'ServerName' ),
+ 'stream' => 'mediawiki.api-request'
+ ],
+ 'http' => [
+ 'method' => $request->getMethod(),
+ 'client_ip' => $request->getIP(),
+ 'request_headers' => [
+ 'user-agent' => $request->getHeader( 'User-agent' ),
+ 'api-user-agent' => $request->getHeader( 'Api-user-agent' )
+ ],
+ ],
+ 'database' => wfWikiID(),
+ 'backend_time_ms' => (int)round( $time * 1000 ),
+ 'params' => []
+ ];
+
+ $logCtx['meta']['request_id'] =
+ $logCtx['http']['request_headers']['x-request-id'] = WebRequest::getRequestId();
+
if ( $e ) {
+ $logCtx['api_error_codes'] = [];
foreach ( $this->errorMessagesFromException( $e ) as $msg ) {
- $logCtx['errorCodes'][] = $msg->getApiCode();
+ $legacyLogCtx['errorCodes'][] = $msg->getApiCode();
+ $logCtx['api_error_codes'][] = $msg->getApiCode();
}
}
// Construct space separated message for 'api' log channel
$msg = "API {$request->getMethod()} " .
wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) .
- " {$logCtx['ip']} " .
- "T={$logCtx['timeSpentBackend']}ms";
+ " {$legacyLogCtx['ip']} " .
+ "T={$legacyLogCtx['timeSpentBackend']}ms";
$sensitive = array_flip( $this->getSensitiveParams() );
foreach ( $this->getParamsUsed() as $name ) {
$encValue = $this->encodeRequestLogValue( $value );
}
+ $legacyLogCtx['params'][$name] = $value;
$logCtx['params'][$name] = $value;
$msg .= " {$name}={$encValue}";
}
wfDebugLog( 'api', $msg, 'private' );
- // ApiAction channel is for structured data consumers
- wfDebugLog( 'ApiAction', '', 'private', $logCtx );
+ // ApiAction channel is for structured data consumers.
+ // The ApiAction was using logging channel is deprecated and is replaced
+ // by the api-request channel.
+ wfDebugLog( 'ApiAction', '', 'private', $legacyLogCtx );
+ wfDebugLog( 'api-request', '', 'private', $logCtx );
}
/**
"apihelp-query+blocks-paramvalue-prop-range": "يضيف نطاق عناوين الآيبي المتأثرة بالمنع.",
"apihelp-query+blocks-paramvalue-prop-flags": "يوسم المنع بـ(المنع التلقائي والمجهولون فقط وما إلى ذلك).",
"apihelp-query+blocks-paramvalue-prop-restrictions": "يضيف قيود المنع الجزئي إذا لم يكن المنع على مستوى الموقع.",
- "apihelp-query+blocks-param-show": "إظÙ\87ار اÙ\84عÙ\86اصر اÙ\84تÙ\8a تستÙ\88Ù\81Ù\8a Ù\87Ø°Ù\87 اÙ\84Ù\85عاÙ\8aÙ\8aر Ù\81Ù\82Ø·Ø\8c\nعÙ\84Ù\89 سبÙ\8aÙ\84 اÙ\84Ù\85ثاÙ\84Ø\8c Ù\84Ù\85شاÙ\87دة عÙ\85Ù\84Ù\8aات اÙ\84Ù\85Ù\86ع غÙ\8aر اÙ\84Ù\85Øددة Ù\81Ù\82Ø· عÙ\84Ù\89 عÙ\86اÙ\88Ù\8aÙ\86 Ø¢يبي; اضبط <kbd>$1show=ip|!temp</kbd>.",
+ "apihelp-query+blocks-param-show": "إظÙ\87ار اÙ\84عÙ\86اصر اÙ\84تÙ\8a تستÙ\88Ù\81Ù\8a Ù\87Ø°Ù\87 اÙ\84Ù\85عاÙ\8aÙ\8aر Ù\81Ù\82Ø·Ø\8c\nعÙ\84Ù\89 سبÙ\8aÙ\84 اÙ\84Ù\85ثاÙ\84Ø\8c Ù\84Ù\85شاÙ\87دة عÙ\85Ù\84Ù\8aات اÙ\84Ù\85Ù\86ع غÙ\8aر اÙ\84Ù\85Øددة Ù\81Ù\82Ø· عÙ\84Ù\89 عÙ\86اÙ\88Ù\8aÙ\86 Ø£يبي; اضبط <kbd>$1show=ip|!temp</kbd>.",
"apihelp-query+blocks-example-simple": "قائمة المنع.",
"apihelp-query+blocks-example-users": "إدراج عمليات منع المستخدمين <kbd>Alice</kbd> و<kbd>Bob</kbd>.",
"apihelp-query+categories-summary": "أدرج جميع التصنيفات التي تنتمي إليها الصفحات.",
"apihelp-query+recentchanges-param-excludeuser": "لا تسرد التغييرات بواسطة هذا المستخدم.",
"apihelp-query+recentchanges-param-tag": "إدراج التغييرات الموسومة بهذ الوسم فقط.",
"apihelp-query+recentchanges-param-prop": "تضمين أجزاء إضافية من المعلومات:",
- "apihelp-query+recentchanges-paramvalue-prop-user": "Ù\8aضÙ\8aÙ\81 اÙ\84Ù\85ستخدÙ\85 اÙ\84Ù\85سؤÙ\88Ù\84 عÙ\86 اÙ\84تØرÙ\8aر Ù\88اÙ\84Ù\88سÙ\88Ù\85 إذا Ù\83اÙ\86 Ù\8aÙ\88جد Ø¢يبي.",
+ "apihelp-query+recentchanges-paramvalue-prop-user": "Ù\8aضÙ\8aÙ\81 اÙ\84Ù\85ستخدÙ\85 اÙ\84Ù\85سؤÙ\88Ù\84 عÙ\86 اÙ\84تØرÙ\8aر Ù\88اÙ\84Ù\88سÙ\88Ù\85 إذا Ù\83اÙ\86 Ù\8aÙ\88جد Ø£يبي.",
"apihelp-query+recentchanges-paramvalue-prop-userid": "يضيف المستخدم المسؤول عن التعديل.",
"apihelp-query+recentchanges-paramvalue-prop-comment": "يضيف التعليق للتحرير.",
"apihelp-query+recentchanges-paramvalue-prop-parsedcomment": "يضيف التعليق المحلل للتحرير.",
"apihelp-tokens-example-emailmove": "استرداد رمز بريد إلكتروني ورمز نقل.",
"apihelp-unblock-summary": "إلغاء منع المستخدم.",
"apihelp-unblock-param-id": "معرف المنع لرفع المنع (تم الحصول عليه من خلال <kbd>list=blocks</kbd>)، لا يمكن استخدامه مع <var>$1user</var> أو <var>$1userid</var>.",
- "apihelp-unblock-param-user": "اسÙ\85 اÙ\84Ù\85ستخدÙ\85Ø\8c Ø£Ù\88 عÙ\86Ù\88اÙ\86 Ø¢Ù\8aبÙ\8a Ø£Ù\88 Ù\86طاÙ\82 عÙ\86Ù\88اÙ\86 Ø¢يبي لمنعه، لا يمكن أن يُستخدَم جنبا إلى جنب مع <var>$1id</var> أو <var>$1userid</var>.",
+ "apihelp-unblock-param-user": "اسÙ\85 اÙ\84Ù\85ستخدÙ\85Ø\8c Ø£Ù\88 عÙ\86Ù\88اÙ\86 Ø£Ù\8aبÙ\8a Ø£Ù\88 Ù\86طاÙ\82 عÙ\86Ù\88اÙ\86 Ø£يبي لمنعه، لا يمكن أن يُستخدَم جنبا إلى جنب مع <var>$1id</var> أو <var>$1userid</var>.",
"apihelp-unblock-param-userid": "معرف الآيبي لرفع منعه، لا يمكن أن يُستخدَم جنبا إلى جنب مع <var>$1id</var> أو <var>$1user</var>.",
"apihelp-unblock-param-reason": "سبب لرفع للمنع.",
"apihelp-unblock-param-tags": "تغيير الوسوم للتطبيق على الإدخال في سجل المنع.",
"apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|Documentation]]\n* [[mw:Special:MyLanguage/API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Liste de diffusion]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Annonces de l’API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Bogues et demandes]\n</div>\n<strong>État :</strong> L’API MédiaWiki est une interface stable et mature qui est supportée et améliorée de façon active. Bien que nous essayions de l’éviter, nous pouvons avoir parfois besoin de faire des modifications impactantes ; inscrivez-vous à [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ la liste de diffusion mediawiki-api-announce] pour être informé des mises à jour.\n\n<strong>Requêtes erronées :</strong> Si des requêtes erronées sont envoyées à l’API, un entête HTTP sera renvoyé avec la clé « MediaWiki-API-Error ». La valeur de cet entête et le code d’erreur renvoyé prendront la même valeur. Pour plus d’information, voyez [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Errors and warnings]].\n\n<p class=\"mw-apisandbox-link\"><strong>Test :</strong> Pour faciliter le test des requêtes de l’API, voyez [[Special:ApiSandbox]].</p>",
"apihelp-main-param-action": "Quelle action effectuer.",
"apihelp-main-param-format": "Le format de sortie.",
- "apihelp-main-param-maxlag": "La latence maximale peut être utilisée quand MédiaWiki est installé sur un cluster de base de données répliqué. Pour éviter des actions provoquant un supplément de latence de réplication de site, ce paramètre peut faire attendre le client jusqu’à ce que la latence de réplication soit inférieure à une valeur spécifiée. En cas de latence excessive, le code d’erreur <samp>maxlag</samp> est renvoyé avec un message tel que <samp>Attente de $host : $lag secondes de délai</samp>.<br />Voyez [[mw:Special:MyLanguage/Manual:Maxlag_parameter|Manuel: Maxlag parameter]] pour plus d’information.",
+ "apihelp-main-param-maxlag": "La latence maximale peut être utilisée quand MédiaWiki est installé sur un cluster de base de données répliqué. Pour éviter des actions provoquant un supplément de latence de réplication de site, ce paramètre peut faire attendre le client jusqu’à ce que la latence de réplication soit inférieure à une valeur spécifiée. En cas de latence excessive, le code d’erreur <samp>maxlag</samp> est renvoyé avec un message tel que <samp>Attente de $host : $lag secondes de délai</samp>.<br />Voyez [[mw:Special:MyLanguage/Manual:Maxlag_parameter|Manuel: paramètre Maxlag]] pour plus d’information.",
"apihelp-main-param-smaxage": "Fixer l’entête HTTP de contrôle de cache <code>s-maxage</code> à ce nombre de secondes. Les erreurs ne sont jamais mises en cache.",
"apihelp-main-param-maxage": "Fixer l’entête HTTP de contrôle de cache <code>max-age</code> à ce nombre de secondes. Les erreurs ne sont jamais mises en cache.",
"apihelp-main-param-assert": "Vérifier si l’utilisateur est connecté si la valeur est <kbd>user</kbd>, ou s’il a le droit d’un utilisateur robot si la valeur est <kbd>bot</kbd>.",
"apihelp-query+deletedrevs-param-excludeuser": "Ne pas lister les révisions par cet utilisateur.",
"apihelp-query+deletedrevs-param-namespace": "Lister uniquement les pages dans cet espace de noms.",
"apihelp-query+deletedrevs-param-limit": "Le nombre maximal de révisions à lister.",
- "apihelp-query+deletedrevs-param-prop": "Quelles propriétés obtenir :\n;revid : Ajoute l’ID de la révision supprimée.\n;parentid : Ajoute l’ID de la révision précédente de la page.\n;user : Ajoute l’utilisateur ayant fait la révision.\n;userid : Ajoute l’ID de l’utilisateur qui a fait la révision.\n;comment : Ajoute le commentaire de la révision.\n;parsedcomment : Ajoute le commentaire analysé de la révision.\n;minor : Marque si la révision est mineure.\n;len : Ajoute la longueur (en octets) de la révision.\n;sha1 : Ajoute le SHA-1 (base 16) de la révision.\n;content : Ajoute le contenu de la révision.\n;token : <span class=\"apihelp-deprecated\">Désuet.</span> Fournit le jeton de modification.\n;tags : Balises pour la révision.",
+ "apihelp-query+deletedrevs-param-prop": "Quelles propriétés obtenir :\n;revid : ajoute l’ID de la révision supprimée.\n;parentid : ajoute l’ID de la révision précédente de la page.\n;user : ajoute l’utilisateur ayant fait la révision.\n;userid : ajoute l’ID de l’utilisateur qui a fait la révision.\n;comment : ajoute le commentaire de la révision.\n;parsedcomment : ajoute le commentaire analysé de la révision.\n;minor : marque si la révision est mineure.\n;len : ajoute la longueur (en octets) de la révision.\n;sha1 : ajoute le SHA-1 (base 16) de la révision.\n;content : ajoute le contenu de la révision.\n;token : <span class=\"apihelp-deprecated\">désuet.</span> Fournit le jeton de modification.\n;tags : balises pour la révision.",
"apihelp-query+deletedrevs-example-mode1": "Lister les dernières révisions supprimées des pages <kbd>Main Page</kbd> et <kbd>Talk:Main Page</kbd>, avec le contenu (mode 1).",
"apihelp-query+deletedrevs-example-mode2": "Lister les 50 dernières contributions de <kbd>Bob</kbd> supprimées (mode 2).",
"apihelp-query+deletedrevs-example-mode3-main": "Lister les 50 premières révisions supprimées dans l’espace de noms principal (mode 3)",
"apihelp-unlinkaccount-summary": "Supprimer un compte tiers lié de l’utilisateur actuel.",
"apihelp-unlinkaccount-example-simple": "Essayer de supprimer le lien de l’utilisateur actuel pour le fournisseur associé avec <kbd>FooAuthenticationRequest</kbd>.",
"apihelp-upload-summary": "Téléverser un fichier, ou obtenir l’état des téléversements en cours.",
- "apihelp-upload-extended-description": "Plusieurs méthodes sont disponibles :\n* Téléverser directement le contenu du fichier, en utilisant le paramètre <var>$1file</var>.\n* Téléverser le fichier par morceaux, en utilisant les paramètres <var>$1filesize</var>, <var>$1chunk</var>, and <var>$1offset</var>.\n* Pour que le serveur MédiaWiki cherche un fichier depuis une URL, utilisez le paramètre <var>$1url</var>.\n* Terminer un téléversement précédent qui a échoué à cause d’avertissements, en utilisant le paramètre <var>$1filekey</var>.\nNoter que le POST HTTP doit être fait comme un téléversement de fichier (par ex. en utilisant <code>multipart/form-data</code>) en envoyant le <code>multipart/form-data</code>.",
+ "apihelp-upload-extended-description": "Plusieurs méthodes sont disponibles :\n* Téléverser directement le contenu du fichier, en utilisant le paramètre <var>$1file</var>.\n* Téléverser le fichier par morceaux, en utilisant les paramètres <var>$1filesize</var>, <var>$1chunk</var>, and <var>$1offset</var>.\n* Pour que le serveur MédiaWiki cherche un fichier depuis une URL, utilisez le paramètre <var>$1url</var>.\n* Terminer un téléversement précédent qui a échoué à cause d’avertissements, en utilisant le paramètre <var>$1filekey</var>.\nNoter que le POST HTTP doit être fait comme un téléversement de fichier (par exemple en utilisant <code>multipart/form-data</code>) en envoyant le <var>$1file</var>.",
"apihelp-upload-param-filename": "Nom de fichier cible.",
"apihelp-upload-param-comment": "Téléverser le commentaire. Utilisé aussi comme texte de la page initiale pour les nouveaux fichiers si <var>$1text</var> n’est pas spécifié.",
"apihelp-upload-param-tags": "Modifier les balises à appliquer à l’entrée du journal de téléversement et à la révision de la page du fichier.",
<?php
-namespace Mediawiki\Logger;
+namespace MediaWiki\Logger;
use Psr\Log\AbstractLogger;
use Psr\Log\LoggerInterface;
"config-memcached-servers": "خوادم Memcached:",
"config-memcached-help": "قائمة عناوين الآيبي لاستخدامها في Memcached،\nيجب تحديد واحد لكل سطر وتحديد المنفذ المراد استخدامه. على سبيل المثال:\n 127.0.0.1:11211\n 192.168.1.25:1234",
"config-memcache-needservers": "لقد حددت Memcached كنوع ذاكرة تخزين مؤقت ولكن لم تحدد أية خوادم.",
- "config-memcache-badip": "Ù\84Ù\82د أدخÙ\84ت عÙ\86Ù\88اÙ\86 Ø¢يبي غير صالح لـMemcached: $1.",
+ "config-memcache-badip": "Ù\84Ù\82د أدخÙ\84ت عÙ\86Ù\88اÙ\86 Ø£يبي غير صالح لـMemcached: $1.",
"config-memcache-noport": "لم تحدد منفذا لاستخدامه في خادم Memcached: $1،\nإذا كنت لا تعرف المنفذ، يكون الافتراضي هو 11211.",
"config-memcache-badport": "يجب أن تتراوح أرقام منافذ الذاكرة بين $1 و$2.",
"config-extensions": "امتدادات",
const FLD_VALUE = 1; // key to the cached value
const FLD_TTL = 2; // key to the original TTL
const FLD_TIME = 3; // key to the cache time
- const FLD_FLAGS = 4; // key to the flags bitfield
+ const FLD_FLAGS = 4; // key to the flags bitfield (reserved number)
const FLD_HOLDOFF = 5; // key to any hold-off TTL
- /** @var int Treat this value as expired-on-arrival */
- const FLG_STALE = 1;
-
const ERR_NONE = 0; // no error
const ERR_NO_RESPONSE = 1; // no response
const ERR_UNREACHABLE = 2; // can't connect
// Do not cache potentially uncommitted data as it might get rolled back
if ( !empty( $opts['pending'] ) ) {
- $this->logger->info( 'Rejected set() for {cachekey} due to pending writes.',
- [ 'cachekey' => $key ] );
+ $this->logger->info(
+ 'Rejected set() for {cachekey} due to pending writes.',
+ [ 'cachekey' => $key ]
+ );
return true; // no-op the write for being unsafe
}
- $wrapExtra = []; // additional wrapped value fields
+ $logicalTTL = null; // logical TTL override
// Check if there's a risk of writing stale data after the purge tombstone expired
if ( $lag === false || ( $lag + $age ) > self::MAX_READ_LAG ) {
- // Case A: read lag with "lockTSE"; save but record value as stale
- if ( $lockTSE >= 0 ) {
- $ttl = max( 1, (int)$lockTSE ); // set() expects seconds
- $wrapExtra[self::FLD_FLAGS] = self::FLG_STALE; // mark as stale
- // Case B: any long-running transaction; ignore this set()
- } elseif ( $age > self::MAX_READ_LAG ) {
- $this->logger->info( 'Rejected set() for {cachekey} due to snapshot lag.',
- [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ] );
-
- return true; // no-op the write for being unsafe
- // Case C: high replication lag; lower TTL instead of ignoring all set()s
+ // Case A: any long-running transaction
+ if ( $age > self::MAX_READ_LAG ) {
+ if ( $lockTSE >= 0 ) {
+ // Store value as *almost* stale to avoid cache and mutex stampedes
+ $logicalTTL = self::TTL_SECOND;
+ $this->logger->info(
+ 'Lowered set() TTL for {cachekey} due to snapshot lag.',
+ [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ]
+ );
+ } else {
+ $this->logger->info(
+ 'Rejected set() for {cachekey} due to snapshot lag.',
+ [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ]
+ );
+
+ return true; // no-op the write for being unsafe
+ }
+ // Case B: high replication lag; lower TTL instead of ignoring all set()s
} elseif ( $lag === false || $lag > self::MAX_READ_LAG ) {
- $ttl = $ttl ? min( $ttl, self::TTL_LAGGED ) : self::TTL_LAGGED;
- $this->logger->warning( 'Lowered set() TTL for {cachekey} due to replication lag.',
- [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ] );
- // Case D: medium length request with medium replication lag; ignore this set()
+ if ( $lockTSE >= 0 ) {
+ $logicalTTL = min( $ttl ?: INF, self::TTL_LAGGED );
+ } else {
+ $ttl = min( $ttl ?: INF, self::TTL_LAGGED );
+ }
+ $this->logger->warning(
+ 'Lowered set() TTL for {cachekey} due to replication lag.',
+ [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ]
+ );
+ // Case C: medium length request with medium replication lag
} else {
- $this->logger->info( 'Rejected set() for {cachekey} due to high read lag.',
- [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ] );
+ if ( $lockTSE >= 0 ) {
+ // Store value as *almost* stale to avoid cache and mutex stampedes
+ $logicalTTL = self::TTL_SECOND;
+ $this->logger->info(
+ 'Lowered set() TTL for {cachekey} due to high read lag.',
+ [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ]
+ );
+ } else {
+ $this->logger->info(
+ 'Rejected set() for {cachekey} due to high read lag.',
+ [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ]
+ );
- return true; // no-op the write for being unsafe
+ return true; // no-op the write for being unsafe
+ }
}
}
// Wrap that value with time/TTL/version metadata
- $wrapped = $this->wrap( $value, $ttl, $now ) + $wrapExtra;
+ $wrapped = $this->wrap( $value, $logicalTTL ?: $ttl, $now );
$func = function ( $cache, $key, $cWrapped ) use ( $wrapped ) {
return ( is_string( $cWrapped ) )
$minTime = $opts['minAsOf'] ?? self::MIN_TIMESTAMP_NONE;
$versioned = isset( $opts['version'] );
$touchedCallback = $opts['touchedCallback'] ?? null;
+ $initialTime = $this->getCurrentTime();
// Get a collection name to describe this class of key
$kClass = $this->determineKeyClass( $key );
- // Get the current key value
+ // Get the current key value and populate $curTTL and $asOf accordingly
$curTTL = null;
$cValue = $this->get( $key, $curTTL, $checkKeys, $asOf ); // current value
$value = $cValue; // return value
-
// Apply additional dynamic expiration logic if supplied
$curTTL = $this->applyTouchedCallback( $value, $asOf, $curTTL, $touchedCallback );
- $preCallbackTime = $this->getCurrentTime();
// Determine if a cached value regeneration is needed or desired
if (
$this->isValid( $value, $versioned, $asOf, $minTime ) &&
) {
$preemptiveRefresh = (
$this->worthRefreshExpiring( $curTTL, $lowTTL ) ||
- $this->worthRefreshPopular( $asOf, $ageNew, $popWindow, $preCallbackTime )
+ $this->worthRefreshPopular( $asOf, $ageNew, $popWindow, $initialTime )
);
if ( !$preemptiveRefresh ) {
// Decide if only one thread should handle regeneration at a time
$useMutex =
// Note that since tombstones no-op set(), $lockTSE and $curTTL cannot be used to
- // deduce the key hotness because $curTTL will always keep increasing until the
+ // deduce the key hotness because |$curTTL| will always keep increasing until the
// tombstone expires or is overwritten by a new tombstone. Also, even if $lockTSE
// is not set, constant regeneration of a key for the tombstone lifetime might be
// very expensive. Assume tombstoned keys are possibly hot in order to reduce
throw new InvalidArgumentException( "Invalid cache miss callback provided." );
}
+ $preCallbackTime = $this->getCurrentTime();
// Generate the new value from the callback...
$setOpts = [];
++$this->callbackDepth;
$valueIsCacheable = ( $value !== false && $ttl >= 0 );
if ( $valueIsCacheable ) {
- $ago = max( $this->getCurrentTime() - $preCallbackTime, 0.0 );
+ $ago = max( $this->getCurrentTime() - $initialTime, 0.0 );
+ $this->stats->timing( "wanobjectcache.$kClass.regen_set_delay", 1000 * $ago );
+
if ( $isKeyTombstoned ) {
if ( $this->checkAndSetCooloff( $key, $kClass, $ago, $lockTSE, $hasLock ) ) {
// When delete() is called, writes are write-holed by the tombstone,
if ( $hasLock ) {
// Avoid using delete() to avoid pointless mcrouter broadcasting
- $this->cache->changeTTL( self::MUTEX_KEY_PREFIX . $key, (int)$preCallbackTime - 60 );
+ $this->cache->changeTTL( self::MUTEX_KEY_PREFIX . $key, (int)$initialTime - 60 );
}
$miss = is_infinite( $minTime ) ? 'renew' : 'miss';
return [ false, null ];
}
- $flags = $wrapped[self::FLD_FLAGS] ?? 0;
- if ( ( $flags & self::FLG_STALE ) == self::FLG_STALE ) {
- // Treat as expired, with the cache time as the expiration
- $age = $now - $wrapped[self::FLD_TIME];
- $curTTL = min( -$age, self::TINY_NEGATIVE );
- } elseif ( $wrapped[self::FLD_TTL] > 0 ) {
+ if ( $wrapped[self::FLD_TTL] > 0 ) {
// Get the approximate time left on the key
$age = $now - $wrapped[self::FLD_TIME];
$curTTL = max( $wrapped[self::FLD_TTL] - $age, 0.0 );
return true;
}
+ protected function makeUpdateOptionsArray( $options ) {
+ if ( !is_array( $options ) ) {
+ $options = [ $options ];
+ }
+
+ // PostgreSQL doesn't support anything like "ignore" for
+ // UPDATE.
+ $options = array_diff( $options, [ 'IGNORE' ] );
+
+ return parent::makeUpdateOptionsArray( $options );
+ }
+
/**
* INSERT SELECT wrapper
* $varMap must be an associative array of the form [ 'dest1' => 'source1', ... ]
}
}
- /**
- * @param IDatabase $conn
- * @param DBMasterPos|bool $pos
- * @param int|null $timeout
- * @return bool
- */
public function safeWaitForMasterPos( IDatabase $conn, $pos = false, $timeout = null ) {
$timeout = max( 1, $timeout ?: $this->waitTimeout );
$pos = $masterConn->getMasterPos();
} else {
$masterConn = $this->openConnection( $this->getWriterIndex(), self::DOMAIN_ANY );
+ if ( !$masterConn ) {
+ throw new DBReplicationWaitError(
+ null,
+ "Could not obtain a master database connection to get the position"
+ );
+ }
$pos = $masterConn->getMasterPos();
$this->closeConnection( $masterConn );
}
use InvalidArgumentException;
/**
- * Trivial LoadBalancer that always returns an injected connection handle
+ * Trivial LoadBalancer that always returns an injected connection handle.
+ *
+ * Note that, while this LoadBalancer does not open any connections itself,
+ * it still closes the injected connection at times, including during destruction.
+ * It is therefore unsuitable for use in tests unless you have a Database instance
+ * separate from the main test database (which is expected to stay open).
*/
class LoadBalancerSingle extends LoadBalancer {
/** @var IDatabase */
'trxProfiler' => $params['trxProfiler'] ?? null,
'srvCache' => $params['srvCache'] ?? null,
'wanCache' => $params['wanCache'] ?? null,
- 'localDomain' => $params['localDomain'] ?? $this->db->getDomainID()
+ 'localDomain' => $params['localDomain'] ?? $this->db->getDomainID(),
+ 'readOnlyReason' => $params['readOnlyReason'] ?? false,
] );
if ( isset( $params['readOnlyReason'] ) ) {
protected $pool;
/** @var Redis */
protected $conn;
-
- protected $server; // string
- protected $lastError; // string
+ /** @var string */
+ protected $server;
+ /** @var string|null */
+ protected $lastError;
/**
* @var LoggerInterface
* @param array &$params
* @return bool
*/
- function normaliseParams( $image, &$params ) {
+ public function normaliseParams( $image, &$params ) {
global $wgMaxInterlacingAreas;
if ( !parent::normaliseParams( $image, $params ) ) {
return false;
* @param array &$params
* @return bool
*/
- function normaliseParams( $image, &$params ) {
+ public function normaliseParams( $image, &$params ) {
return ImageHandler::normaliseParams( $image, $params );
}
* @param array|null $params
* @return array
*/
- function getThumbType( $text, $mime, $params = null ) {
+ public function getThumbType( $text, $mime, $params = null ) {
return [ 'png', 'image/png' ];
}
/**
* @return bool
*/
- function isEnabled() {
+ public function isEnabled() {
global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML;
if ( !$wgDjvuRenderer || ( !$wgDjvuDump && !$wgDjvuToXML ) ) {
wfDebug( "DjVu is disabled, please set \$wgDjvuRenderer and \$wgDjvuDump\n" );
* @param array $params
* @return array
*/
- function getScriptParams( $params ) {
+ protected function getScriptParams( $params ) {
return [
'width' => $params['width'],
'page' => $params['page'],
return $this->getDjVuImage( $image, $path )->getImageSize();
}
- function getThumbType( $ext, $mime, $params = null ) {
+ public function getThumbType( $ext, $mime, $params = null ) {
global $wgDjvuOutputExtension;
static $mime;
if ( !isset( $mime ) ) {
return [ $wgDjvuOutputExtension, $mime ];
}
- function getMetadata( $image, $path ) {
+ public function getMetadata( $image, $path ) {
wfDebug( "Getting DjVu metadata for $path\n" );
$xml = $this->getDjVuImage( $image, $path )->retrieveMetaData();
return 'djvuxml';
}
- function isMetadataValid( $image, $metadata ) {
+ public function isMetadataValid( $image, $metadata ) {
return !empty( $metadata ) && $metadata != serialize( [] );
}
- function pageCount( File $image ) {
+ public function pageCount( File $image ) {
$info = $this->getDimensionInfo( $image );
return $info ? $info['pageCount'] : false;
}
- function getPageDimensions( File $image, $page ) {
+ public function getPageDimensions( File $image, $page ) {
$index = $page - 1; // MW starts pages at 1
$info = $this->getDimensionInfo( $image );
* @param array $metadata
* @return bool|int
*/
- function isMetadataValid( $image, $metadata ) {
+ public function isMetadataValid( $image, $metadata ) {
global $wgShowEXIF;
if ( !$wgShowEXIF ) {
# Metadata disabled and so an empty field is expected
* @param bool|IContextSource $context Context to use (optional)
* @return array|bool
*/
- function formatMetadata( $image, $context = false ) {
+ public function formatMetadata( $image, $context = false ) {
$meta = $this->getCommonMetaArray( $image );
if ( count( $meta ) === 0 ) {
return false;
class GIFHandler extends BitmapHandler {
const BROKEN_FILE = '0'; // value to store in img_metadata if error extracting metadata.
- function getMetadata( $image, $filename ) {
+ public function getMetadata( $image, $filename ) {
try {
$parsedGIFMetadata = BitmapMetadataHandler::GIF( $filename );
} catch ( Exception $e ) {
* @param bool|IContextSource $context Context to use (optional)
* @return array|bool
*/
- function formatMetadata( $image, $context = false ) {
+ public function formatMetadata( $image, $context = false ) {
$meta = $this->getCommonMetaArray( $image );
if ( count( $meta ) === 0 ) {
return false;
return 'parsed-gif';
}
- function isMetadataValid( $image, $metadata ) {
+ public function isMetadataValid( $image, $metadata ) {
if ( $metadata === self::BROKEN_FILE ) {
// Do not repetitivly regenerate metadata on broken file.
return self::METADATA_GOOD;
* @param File $image
* @return string
*/
- function getLongDesc( $image ) {
+ public function getLongDesc( $image ) {
global $wgLang;
$original = parent::getLongDesc( $image );
}
}
- function getScriptParams( $params ) {
+ protected function getScriptParams( $params ) {
return [ 'width' => $params['width'] ];
}
* @param File $file
* @return string
*/
- function getLongDesc( $file ) {
+ public function getLongDesc( $file ) {
global $wgLang;
$pages = $file->pageCount();
$size = htmlspecialchars( $wgLang->formatSize( $file->getSize() ) );
const SRGB_EXIF_COLOR_SPACE = 'sRGB';
const SRGB_ICC_PROFILE_DESCRIPTION = 'sRGB IEC61966-2.1';
- function normaliseParams( $image, &$params ) {
+ public function normaliseParams( $image, &$params ) {
if ( !parent::normaliseParams( $image, $params ) ) {
return false;
}
return $res;
}
- function getScriptParams( $params ) {
+ protected function getScriptParams( $params ) {
$res = parent::getScriptParams( $params );
if ( isset( $params['quality'] ) ) {
$res['quality'] = $params['quality'];
return $res;
}
- function getMetadata( $image, $filename ) {
+ public function getMetadata( $image, $filename ) {
try {
$meta = BitmapMetadataHandler::Jpeg( $filename );
if ( !is_array( $meta ) ) {
* @param File $image
* @param array &$params
*/
- abstract function normaliseParams( $image, &$params );
+ abstract public function normaliseParams( $image, &$params );
/**
* Get an image size array like that returned by getimagesize(), or false if it
* @param string $path The filename
* @return string A string of metadata in php serialized form (Run through serialize())
*/
- function getMetadata( $image, $path ) {
+ public function getMetadata( $image, $path ) {
return '';
}
* @param string $metadata The metadata in serialized form
* @return bool
*/
- function isMetadataValid( $image, $metadata ) {
+ public function isMetadataValid( $image, $metadata ) {
return self::METADATA_GOOD;
}
* @param array|null $params Handler specific rendering parameters
* @return array Thumbnail extension and MIME type
*/
- function getThumbType( $ext, $mime, $params = null ) {
+ public function getThumbType( $ext, $mime, $params = null ) {
$magic = MediaWiki\MediaWikiServices::getInstance()->getMimeAnalyzer();
if ( !$ext || $magic->isMatchingExtension( $ext, $mime ) === false ) {
// The extension is not valid for this MIME type and we do
* @param File $file
* @return bool
*/
- function pageCount( File $file ) {
+ public function pageCount( File $file ) {
return false;
}
* False if the handler is disabled for all files
* @return bool
*/
- function isEnabled() {
+ public function isEnabled() {
return true;
}
* @param int $page What page to get dimensions of
* @return array|bool
*/
- function getPageDimensions( File $image, $page ) {
+ public function getPageDimensions( File $image, $page ) {
$gis = $this->getImageSize( $image, $image->getLocalRefPath() );
if ( $gis ) {
return [
* @param bool|IContextSource $context Context to use (optional)
* @return array|bool
*/
- function formatMetadata( $image, $context = false ) {
+ public function formatMetadata( $image, $context = false ) {
return false;
}
* @param File $file
* @return string
*/
- function getLongDesc( $file ) {
+ public function getLongDesc( $file ) {
return self::getGeneralLongDesc( $file );
}
* @param string $fileName The local path to the file.
* @return Status
*/
- function verifyUpload( $fileName ) {
+ public function verifyUpload( $fileName ) {
return Status::newGood();
}
* @ingroup Media
*/
class ThumbnailImage extends MediaTransformOutput {
+ private static $firstNonIconImageRendered = false;
+
/**
* Get a thumbnail object from a file and parameters.
* If $path is set to null, the output file is treated as a source copy.
* @return string
*/
function toHtml( $options = [] ) {
+ global $wgPriorityHints;
+
if ( count( func_get_args() ) == 2 ) {
throw new MWException( __METHOD__ . ' called in the old style' );
}
'decoding' => 'async',
];
+ if ( $wgPriorityHints
+ && !self::$firstNonIconImageRendered
+ && $this->width * $this->height > 100 * 100 ) {
+ self::$firstBigImageRendered = true;
+
+ $attribs['importance'] = 'high';
+ }
+
if ( !empty( $options['custom-url-link'] ) ) {
$linkAttribs = [ 'href' => $options['custom-url-link'] ];
if ( !empty( $options['title'] ) ) {
* @param string $filename
* @return string
*/
- function getMetadata( $image, $filename ) {
+ public function getMetadata( $image, $filename ) {
try {
$metadata = BitmapMetadataHandler::PNG( $filename );
} catch ( Exception $e ) {
* @param bool|IContextSource $context Context to use (optional)
* @return array|bool
*/
- function formatMetadata( $image, $context = false ) {
+ public function formatMetadata( $image, $context = false ) {
$meta = $this->getCommonMetaArray( $image );
if ( count( $meta ) === 0 ) {
return false;
return 'parsed-png';
}
- function isMetadataValid( $image, $metadata ) {
+ public function isMetadataValid( $image, $metadata ) {
if ( $metadata === self::BROKEN_FILE ) {
// Do not repetitivly regenerate metadata on broken file.
return self::METADATA_GOOD;
* @param File $image
* @return string
*/
- function getLongDesc( $image ) {
+ public function getLongDesc( $image ) {
global $wgLang;
$original = parent::getLongDesc( $image );
'title' => 'ObjectName',
];
- function isEnabled() {
+ public function isEnabled() {
global $wgSVGConverters, $wgSVGConverter;
if ( !isset( $wgSVGConverters[$wgSVGConverter] ) ) {
wfDebug( "\$wgSVGConverter is invalid, disabling SVG rendering.\n" );
}
}
- function getThumbType( $ext, $mime, $params = null ) {
+ public function getThumbType( $ext, $mime, $params = null ) {
return [ 'png', 'image/png' ];
}
* @param File $file
* @return string
*/
- function getLongDesc( $file ) {
+ public function getLongDesc( $file ) {
global $wgLang;
$metadata = $this->unpackMetadata( $file->getMetadata() );
* @param string $filename
* @return string Serialised metadata
*/
- function getMetadata( $file, $filename ) {
+ public function getMetadata( $file, $filename ) {
$metadata = [ 'version' => self::SVG_METADATA_VERSION ];
try {
$metadata += SVGMetadataExtractor::getMetadata( $filename );
return 'parsed-svg';
}
- function isMetadataValid( $image, $metadata ) {
+ public function isMetadataValid( $image, $metadata ) {
$meta = $this->unpackMetadata( $metadata );
if ( $meta === false ) {
return self::METADATA_BAD;
* @param bool|IContextSource $context Context to use (optional)
* @return array|bool
*/
- function formatMetadata( $file, $context = false ) {
+ public function formatMetadata( $file, $context = false ) {
$result = [
'visible' => [],
'collapsed' => []
* @param array $params
* @return array
*/
- function getScriptParams( $params ) {
+ protected function getScriptParams( $params ) {
$scriptParams = [ 'width' => $params['width'] ];
if ( isset( $params['lang'] ) ) {
$scriptParams['lang'] = $params['lang'];
* @param array|null $params
* @return bool
*/
- function getThumbType( $ext, $mime, $params = null ) {
+ public function getThumbType( $ext, $mime, $params = null ) {
global $wgTiffThumbnailType;
return $wgTiffThumbnailType;
* @throws MWException
* @return string
*/
- function getMetadata( $image, $filename ) {
+ public function getMetadata( $image, $filename ) {
global $wgShowEXIF;
if ( $wgShowEXIF ) {
* 'physicalWidth' and 'physicalHeight' indicate the thumbnail dimensions.
* @return bool
*/
- function normaliseParams( $image, &$params ) {
+ public function normaliseParams( $image, &$params ) {
if ( !parent::normaliseParams( $image, $params ) ) {
return false;
}
* @param array|null $params
* @return array
*/
- function getThumbType( $ext, $mime, $params = null ) {
+ public function getThumbType( $ext, $mime, $params = null ) {
return [ 'png', 'image/png' ];
}
// Add the QUnit testrunner as implicit dependency to extension test suites.
foreach ( $testModules['qunit'] as &$module ) {
// Shuck any single-module dependency as an array
- if ( is_string( $module['dependencies'] ) ) {
+ if ( isset( $module['dependencies'] ) && is_string( $module['dependencies'] ) ) {
$module['dependencies'] = [ $module['dependencies'] ];
}
*/
class ResourceLoaderImageModule extends ResourceLoaderModule {
+ /** @var array|null */
protected $definition = null;
/**
protected $origin = self::ORIGIN_CORE_SITEWIDE;
+ /** @var ResourceLoaderImage[]|null */
+ protected $imageObjects = null;
+ /** @var array */
protected $images = [];
+ /** @var string|null */
protected $defaultColor = null;
protected $useDataURI = true;
+ /** @var array|null */
+ protected $globalVariants = null;
+ /** @var array */
protected $variants = [];
+ /** @var string|null */
protected $prefix = null;
protected $selectorWithoutVariant = '.{prefix}-{name}';
protected $selectorWithVariant = '.{prefix}-{name}-{variant}';
*/
public function getImages( ResourceLoaderContext $context ) {
$skin = $context->getSkin();
- if ( !isset( $this->imageObjects ) ) {
+ if ( $this->imageObjects === null ) {
$this->loadFromDefinition();
$this->imageObjects = [];
}
*/
public function getGlobalVariants( ResourceLoaderContext $context ) {
$skin = $context->getSkin();
- if ( !isset( $this->globalVariants ) ) {
+ if ( $this->globalVariants === null ) {
$this->loadFromDefinition();
$this->globalVariants = [];
}
"revdelete-hide-image": "أخف محتوى الملف",
"revdelete-hide-name": "أخف الفعل والهدف",
"revdelete-hide-comment": "أخف تعليق التعديل",
- "revdelete-hide-user": "أخÙ\81 اسÙ\85/Ø¢يبي المستخدم",
+ "revdelete-hide-user": "اسÙ\85/Ø£يبي المستخدم",
"revdelete-hide-restricted": "أخف البيانات عن الإداريين إضافة إلى الآخرين",
"revdelete-radio-same": "(لا تغير)",
"revdelete-radio-set": "نعم",
"block": "منع المستخدم",
"unblock": "إلغاء منع مستخدم",
"blockip": "منع {{GENDER:$1|المستخدم|المستخدمة}}",
- "blockiptext": "استخدÙ\85 اÙ\84Ù\86Ù\85Ù\88ذج اÙ\84تاÙ\84Ù\8a Ù\84Ù\85Ù\86ع Ù\85ستخدÙ\85Ø\8c Ø£Ù\88 عÙ\86Ù\88اÙ\86 Ø¢Ù\8aبÙ\8aØ\8c Ù\85عÙ\8aÙ\86 Ù\85Ù\86 اÙ\84تعدÙ\8aÙ\84 Ø£Ù\88 Ø¥Ù\86شاء Øسابات جدÙ\8aدة. تÙ\8fستخدÙ\85 Ù\87Ø°Ù\87 اÙ\84عÙ\85Ù\84Ù\8aØ© Ù\84Ù\85Ù\86ع اÙ\84تخرÙ\8aب Ù\81Ù\82Ø·Ø\8c Ù\88Ù\8aجب Ø£Ù\86 تتÙ\85اشÙ\89 Ù\85ع [[{{MediaWiki:Policy-url}}|سÙ\8aاسة اÙ\84Ù\85Ù\86ع]]. أدخÙ\84 تعÙ\84Ù\8aÙ\84اÙ\8b Ù\88اضØÙ\8bا Ù\84سبب اÙ\84Ù\85Ù\86ع Ù\81Ù\8a اÙ\84خاÙ\86Ø© اÙ\84Ù\85خصصة Ù\84Ø°Ù\84Ù\83 (Ù\85Ø«Ù\84اÙ\8b: Ø°Ù\83ر صÙ\81Øات Ù\85Øددة تÙ\85Ù\91 تخرÙ\8aبÙ\87ا Ù\85Ù\86 Ù\82بÙ\84 اÙ\84Ù\85ستخدÙ\85).\nÙ\8aÙ\85Ù\83Ù\86Ù\83 Ù\85Ù\86ع Ù\86طاÙ\82ات عÙ\86اÙ\88Ù\8aÙ\86 IP باستخداÙ\85 [https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing CIDR] Ù\82Ù\88اعد; Ø£Ù\83بر Ù\86طاÙ\82 Ù\85سÙ\85Ù\88Ø Ø¨Ù\87 Ù\87Ù\88 /$1 Ø¥Ù\84Ù\89 IPv4 Ù\88 /$2 Ø¥Ù\84Ù\89 IPv6.",
+ "blockiptext": "استخدÙ\85 اÙ\84Ù\86Ù\85Ù\88ذج اÙ\84تاÙ\84Ù\8a Ù\84Ù\85Ù\86ع Ù\85ستخدÙ\85Ø\8c Ø£Ù\88 عÙ\86Ù\88اÙ\86 Ø£Ù\8aبÙ\8aØ\8c Ù\85عÙ\8aÙ\86 Ù\85Ù\86 اÙ\84تعدÙ\8aÙ\84 Ø£Ù\88 Ø¥Ù\86شاء Øسابات جدÙ\8aدة.\nتÙ\8fستخدÙ\85 Ù\87Ø°Ù\87 اÙ\84عÙ\85Ù\84Ù\8aØ© Ù\84Ù\85Ù\86ع اÙ\84تخرÙ\8aب Ù\81Ù\82Ø·Ø\8c Ù\88Ù\8aجب Ø£Ù\86 تتÙ\85اشÙ\89 Ù\85ع [[{{MediaWiki:Policy-url}}|سÙ\8aاسة اÙ\84Ù\85Ù\86ع]].\nأدخÙ\84 تعÙ\84Ù\8aÙ\84اÙ\8b Ù\88اضØÙ\8bا Ù\84سبب اÙ\84Ù\85Ù\86ع Ù\81Ù\8a اÙ\84خاÙ\86Ø© اÙ\84Ù\85خصصة Ù\84Ø°Ù\84Ù\83 (Ù\85Ø«Ù\84اÙ\8b: Ø°Ù\83ر صÙ\81Øات Ù\85Øددة تÙ\85Ù\91 تخرÙ\8aبÙ\87ا Ù\85Ù\86 Ù\82بÙ\84 اÙ\84Ù\85ستخدÙ\85).\nÙ\8aÙ\85Ù\83Ù\86Ù\83 Ù\85Ù\86ع Ù\86طاÙ\82ات عÙ\86اÙ\88Ù\8aÙ\86 IP باستخداÙ\85 [https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing CIDR] Ù\82Ù\88اعد; Ø£Ù\83بر Ù\86طاÙ\82 Ù\85سÙ\85Ù\88Ø Ø¨Ù\87 Ù\87Ù\88 /$1 Ù\84IPv4 Ù\88 /$2 Ù\84IPv6.",
"ipaddressorusername": "عنوان الأيبي أو اسم المستخدم:",
"ipbreason": "السبب:",
"ipbreason-dropdown": "*أسباب المنع الشائعة\n** كتابة معلومات زائفة\n** إزالة المحتوى من الصفحات\n** سبام وصلات لمواقع خارجية\n** كتابة كلام لا معنى له في الصفحات\n** سلوك عدواني\n** إساءة استخدام حسابات متعددة\n** اسم مستخدم غير مقبول",
"ipblocklist-legend": "إيجاد مستخدم ممنوع",
"blocklist-userblocks": "أخفِ منع الحسابات",
"blocklist-tempblocks": "أخفِ المنع المؤقت",
- "blocklist-addressblocks": "أخÙ\81Ù\90 Ù\85Ù\86ع عÙ\86Ù\88اÙ\86 Ø¢يبي واحد",
+ "blocklist-addressblocks": "أخÙ\81Ù\90 Ù\85Ù\86ع عÙ\86Ù\88اÙ\86 Ø£يبي واحد",
"blocklist-rangeblocks": "أخفِ منع النطاقات",
"blocklist-timestamp": "الزمن",
"blocklist-target": "الهدف",
"showpreview": "بين معاينة",
"showdiff": "عرض التبديلات",
"blankarticle": "<strong>ردّ البال:</strong> الپاجة الّي كريّيتها راهي خاوية.\nيلا تعاود تكليكي على $1\"، الپاجة غادي تنخلق بلا ما يكون فيها حتا محتاوا.",
- "anoneditwarning": "'''تÙ\88Ù\84Ù\8aÙ\87Ø©:''' راÙ\83 Ù\85ا دخÙ\84تش بÙ\84 Øساب تاعÙ\83.\nÙ\8aÙ\84ا تدÙ\8aر Ø´Ù\8a تبداÙ\84Ø\8c غادÙ\8a تتسجÙ\91Ù\84 Ù\84ادرÙ\8aسة Ø¢يبي تاعك فل متراخ تاع هاد الصفحة و تكون باينة ل كلّ واحد. يلا [$1 تتكونيكتا]</strong> ولا <strong>[$2 تخلق حساب]</strong>، التبدالات تاعك غادي يبانو تحت السميّة تاع المستعملي تاعك، و كاين تاني مزيّات وحدخرين.",
+ "anoneditwarning": "'''تÙ\88Ù\84Ù\8aÙ\87Ø©:''' راÙ\83 Ù\85ا دخÙ\84تش بÙ\84 Øساب تاعÙ\83.\nÙ\8aÙ\84ا تدÙ\8aر Ø´Ù\8a تبداÙ\84Ø\8c غادÙ\8a تتسجÙ\91Ù\84 Ù\84ادرÙ\8aسة Ø£يبي تاعك فل متراخ تاع هاد الصفحة و تكون باينة ل كلّ واحد. يلا [$1 تتكونيكتا]</strong> ولا <strong>[$2 تخلق حساب]</strong>، التبدالات تاعك غادي يبانو تحت السميّة تاع المستعملي تاعك، و كاين تاني مزيّات وحدخرين.",
"anonpreviewwarning": "<em>ما راكش مسجّل داخل. لوكان تحفّظ التبدالات ضركا غادي تتسجّل لادريسة إيپي تاعك فل تاريخ تاع هاد الپاجة.</em>",
"missingsummary": "<strong>تفكار:</strong> راك ما مدّيتش تلخيص على التبدال تاعك.\nيلا تكليكي على \"$1\" مجّديد، التبدال تاعك غادي يتسجّل بلاش.",
"selfredirect": "<strong>ردّ البال:</strong> راك توجّه هاد الپاجة على روحها.\nبالاك راك غلطت فل ختيّار تاع التقيان تاع الپاجة، ولا تاني ما راكش فل پاجة الّي راك حاب تإيديتيها.\nيلا تكليكي على \"$1\" مجّديد، هاد التوجاه غادي ينخلق كيما هاك.",
"wantedfiles": "Запатрабаваныя файлы",
"wantedfiletext-cat": "Наступныя файлы выкарыстоўваюцца, але іх няма. Файлы з вонкавых сховішчаў могуць знаходзіцца ў сьпісе без уліку іх існаваньня. Любыя такія няслушныя ўваходжаньні будуць <del>выкрасьленыя</del>. Дадаткова, старонкі, якія ўбудоўваюць няісныя файлы, прыведзеныя на [[:$1]].",
"wantedfiletext-cat-noforeign": "Наступныя файлы ўжваюцца, але не існуюць. Дадаткова, старонкі, у якія ўключаныя няісныя файлы, прыведзеныя ў [[:$1]].",
- "wantedfiletext-nocat": "Наступныя файлы выкарыстоўваюцца, але іх няма. Файлы са зьнешніх сховішчаў могуць знаходзіцца ў сьпісе без уліку іх існаваньня. Любыя такія няслушныя ўваходжаньні будуць <del>выкрасьленыя</del>.",
+ "wantedfiletext-nocat": "Наступныя файлы выкарыстоўваюцца, але іх няма. Файлы з вонкавых сховішчаў могуць знаходзіцца ў сьпісе без уліку іх існаваньня. Любыя такія няслушныя ўваходжаньні будуць <del>выкрасьленыя</del>.",
"wantedfiletext-nocat-noforeign": "Наступныя файлы выкарыстоўваюцца, але іх няма.",
"wantedtemplates": "Запатрабаваныя шаблёны",
"mostlinked": "Старонкі, на якія найчасьцей спасылаюцца",
"invalidtitle": "Sernuşteyo nêravêrde",
"invalidtitle-knownnamespace": "Canemey \"$2\" u metnê \"$3\" xırabo",
"invalidtitle-unknownnamespace": "Sernameye nêşınasiya yana amraiya canameyo $1 u metno \"$2\" xırab",
- "exception-nologin": "Şıma cıkewtış nêvıraşto",
+ "exception-nologin": "Nêkewt cı",
"exception-nologin-text": "Na pele ya zi nê karkerdışi rê nê wiki de cıkewtış icab keno.",
"exception-nologin-text-manual": "Na pele resayışi re $1 bıgire.",
"virus-badscanner": "Eyaro şaş: no virus-cıgerayox nêzanyeno: ''$1''",
"nav-login-createaccount": "Dekew de / hesab vıraze",
"logout": "Bıveciye",
"userlogout": "Bıveciye",
- "notloggedin": "Şıma cıkewtış nêvıraşto",
+ "notloggedin": "Nêkewt cı",
"userlogin-noaccount": "Hesabê şıma çıniyo?",
"userlogin-joinproject": "Cıkewe {{SITENAME}}",
"createaccount": "Hesab vıraze",
"uploadbtn": "Dosya bar ke",
"reuploaddesc": "Barkerdışi iptal ke u peyser şo formê barkerdışi",
"upload-tryagain": "Deskripyonê dosyayî ke vurîya ey qeyd bike",
- "uploadnologin": "Şıma cıkewtış nêvıraşto",
+ "uploadnologin": "Nêkewt cı",
"uploadnologintext": "Ti şeni $1 dosya bar bikere.",
"upload_directory_missing": "Direktorê dosyayê ($1)î biyo vînî u webserver de nieşkeno viraziye.",
"upload_directory_read_only": "Direktorê dosyayê ($1)î webserver de nieşkeno binuse.",
"watchlistfor2": "Qandê $1 ($2)",
"nowatchlist": "listeya temaşa kerdıişê şıma de yew madde zi çina.",
"watchlistanontext": "qey vurnayişê maddeya listeya temaşakerdiş ronıştış akerê",
- "watchnologin": "Şıma cıkewtış nêvıraşto",
+ "watchnologin": "Nêkewt cı",
"addwatch": "Lista seyrkerdışi ke",
"addedwatchtext": "\"[[:$1]]\" u perra cıya werênayışi [[Special:Watchlist|lista şımaya ewniyayışi]] rê ameyo cıkerdış.",
"addedwatchtext-short": "Pera $1`i çebyê listeya seyran de şıma",
"anonuser": "karberê anonim o {{SITENAME}}i $1",
"lastmodifiedatby": "Ena per tewr peyên roca $2, $1 de terefê $3 ra vurneya ya.",
"othercontribs": "xebatê $1 ıney geriyayo diqqeti/geriyayo nezer.",
- "others": "bini",
+ "others": "ê bini",
"siteusers": "{{SITENAME}} {{PLURAL:$2|karber|karberan}} $1",
"anonusers": "{{SITENAME}} {{PLURAL:$2|karberê eyê|karberanê eyê}} anonimi $1",
"creditspage": "şınasnameyê peli",
"version-editors": "Vurnayoği",
"version-antispam": "Spam vındarnayış",
"version-api": "API",
- "version-other": "Bin",
+ "version-other": "Sewbi",
"version-mediahandlers": "Kulbê medyayî",
"version-hooks": "Çengelî",
"version-parser-extensiontags": "Etiketê ekstensiyon ê parserî",
"htmlform-required": "Ena deger lazim o",
"htmlform-submit": "Bişirav",
"htmlform-reset": "Vurnayişî reyna biyar",
- "htmlform-selectorother-other": "Bin",
+ "htmlform-selectorother-other": "Sewbi",
"htmlform-no": "Nê",
"htmlform-yes": "Eya",
"htmlform-chosen-placeholder": "Opsiyon weçine",
"exif-meteringmode-4": "zaf noqtayın",
"exif-meteringmode-5": "Qalıb",
"exif-meteringmode-6": "qısmî",
- "exif-meteringmode-255": "Bin",
+ "exif-meteringmode-255": "Sewbi",
"exif-lightsource-0": "Nêzanaye",
"exif-lightsource-1": "Roşnê Tici",
"exif-lightsource-2": "Florasant",
"filedelete-maintenance": "Eliminação e restauro de arquivos estão temporariamente desativados durante manutenção.",
"filedelete-maintenance-title": "Não é possível excluir o arquivo",
"mimesearch": "Pesquisa MIME",
- "mimesearch-summary": "Esta página possibilita que os arquivos sejam filtrados a partir de seu [[w:pt:Tipo de mídia da Internet|tipo MIME]]. Sintaxe de busca: \"tipo/subtipo\" ou \"tipo/*\"(por exemplo, <code>image/jpeg</code>, <code>image/png</code>, <code>application/*</code>).",
+ "mimesearch-summary": "Esta página permite a filtragem de arquivos pelo seu tipo MIME.\nSintaxe: \"tipo de conteúdo/subtipo\" ou \"tipo de conteúdo/*\". Exemplos: <code>image/jpeg</code>, <code>image/png</code>, <code>application/*</code>.",
"mimetype": "tipo MIME:",
"download": "download",
"unwatchedpages": "Páginas não vigiadas",
"sp-contributions-newbies-sub": "Para contas novas",
"sp-contributions-newbies-title": "Contribuições de contas novas",
"sp-contributions-blocklog": "registro de bloqueios",
- "sp-contributions-suppresslog": "Contribuições de {{GENDER:$1|usuário}} suprimidas",
+ "sp-contributions-suppresslog": "contribuições suprimidas {{GENDER:$1|do usuário|da usuária}}",
"sp-contributions-deleted": "{{GENDER:$1|contribuições}} eliminadas",
"sp-contributions-uploads": "envios",
"sp-contributions-logs": "registros",
"right-purge": "Osvježavanje keša za stranice bez potvrde",
"right-autoconfirmed": "Izbjegavanje ograničenja stopa temeljenih na IP-u",
"right-bot": "Postavljen kao automatski proces",
- "right-nominornewtalk": "Male izmjene na stranici za razgovor ne uzrokuju prikazivanje oznake ''nova poruka'' na stranici za razgovor",
+ "right-nominornewtalk": "Male izmjene na stranicama za razgovor ne uzrokuju obavještenje o novim porukama",
"right-apihighlimits": "Korištenje viših ograničenja u API upitima",
"right-writeapi": "Korištenje opcije ''write API''",
"right-delete": "Brisanje stranica",
"right-editusercss": "Uređivanje CSS datoteka drugih korisnika",
"right-edituserjson": "Uređivanje JSON datoteka drugih korisnika",
"right-edituserjs": "Uređivanje JavaScript datoteka drugih korisnika",
+ "right-editsitecss": "Uređivanje CSS za cijelo wiki",
+ "right-editsitejson": "Uređivanje JSON-a za cijelo wiki",
+ "right-editsitejs": "Uređivanje JavaScripta za cijelo wiki",
"right-editmyusercss": "Uredite svoje vlastite CSS datoteke",
"right-editmyuserjs": "Uredite vlastite korisničke JavaScript datoteke",
"right-viewmywatchlist": "Pregled vlastitog popisa praćenih stranica",
"listgrouprights-namespaceprotection-header": "Ograničenja imenskog prostora",
"listgrouprights-namespaceprotection-namespace": "Imenski prostor",
"listgrouprights-namespaceprotection-restrictedto": "Prava kojima se dozvoljava korisniku da uređuje",
+ "listgrants-summary": "Ovo je popis dozvola, svaka sa svojim pravima. Korisnici mogu autorizirati prilozi koji će koristiti račun, ali uz ograničena prava u zavisnosti od tog koju dozvolu im korisnik omogući. Međutim, prilog koji djeluje u ime korisnika ograničen je na prava samog korisnika. Moguće je da postoje [[{{MediaWiki:Listgrouprights-helppage}}|dodatne informacije]] o pojedinim pravima.",
"trackingcategories": "Praćenje kategorija",
"trackingcategories-summary": "Ova stranica prikazuje prateće kategorije koje MediaWiki softver automatski popunjava. Njihovi nazivi se mogu promijeniti izmjenom odgovarajućih sistemskih poruka u imenskom prostoru {{ns:8}}.",
"trackingcategories-msg": "Praćenje kategorije",
"mergehistory-submit": "ಪಡಿಪಾಟೊಲೆನ್ ಸಮ್ಮಿಲಾಲೆ.",
"mergehistory-empty": "ಒವ್ವೆ ಪಡಿಪಾಟೊಲು ಸಮ್ಮಿಲಾಪುಜಾ.",
"mergehistory-done": "$1 ಟು {{PLURAL:$3|ಇತ್ತಿನ|ಉಪ್ಪುನ}} $3 {{PLURAL:$3|ಪಡಿಪಾಟ|ಪಡಿಪಾಟೊಲು}} [[:$2]] ಡು ಸಮ್ಮಿಲಾಂಡ್.",
+ "mergehistory-fail": "ಚರಿತ್ರೆ ಸಮ್ಮಿಲ ಮಲ್ಪರೆ ಆತಿಜಿ, ದಯಮಲ್ತ್ ಪುಟ ಬೊಕ ಕಾಲ ಪರಿಮಾನೊಲೆನ್ ಪಿರಪರಿಶೀಲಿಸಾಲೆ.",
+ "mergehistory-fail-bad-timestamp": "ಕಾಲಮೊಹರ್ ಅಮಾನ್ಯ ಆತ್ಂಡ್.",
+ "mergehistory-fail-invalid-source": "ಮೂಲಪುಟ ಅಮಾನ್ಯ ಆತ್ಂಡ್.",
+ "mergehistory-fail-invalid-dest": "ಗಮ್ಯತಾನದ ಪುಟ ಅಮಾನ್ಯ ಆದುಂಡು.",
+ "mergehistory-fail-no-change": "ಚರಿತ್ರೆ ಸಮ್ಮಿಲ ಒವ್ವೆ ಪಡಿಪಾಟೊಲೆನ್ ಸಮ್ಮಿಲ ಮಲ್ತಿಜಿ. ದಯಮಲ್ತ್ ಪುಟ ಬೊಕ ಕಾಲ ಪರಿಮಾನೊಲೆನ್ ಪಿರ ಪರಿಶೀಲಿಸಾಲೆ.",
+ "mergehistory-fail-permission": "ಚರಿತ್ರೆ ಸಮ್ಮಿಲಾವರೆ ಬೋಡಾಯಿನಾತ್ ಅನುಮತಿಲಿಜ್ಜಿ.",
+ "mergehistory-fail-self-merge": "ಮೂಲ ಬೊಕ ಗಮ್ಯತಾನ ಪುಟೊಲು ಒಂಜೇ ಆದುಂಡು.",
+ "mergehistory-fail-timestamps-overlap": "ಮೂಲ ಪಡಿಪಾಟೊಲು ಅತಿವ್ಯಾಪಿ ಆತಾ ಇಜಿಂಡ ಗಮ್ಯತಾನ ಪಡಿಪಾಟೊಲೆನ ನಂತರ ಬರ್ಪಾ.",
+ "mergehistory-fail-toobig": "ಚಲನೆ ಆಪಿನ {{PLURAL:$1|ಪಡಿಪಾಟ|ಪಡಿಪಾಟೊಲು}} $1 ಮಿತಿ ಮೀರಿನ ಕಾರಣ ಚರಿತ್ರೆ ಸಮ್ಮಿಲ ಮಲ್ಪರಾತಿಜಿ.",
+ "mergehistory-no-source": "ಮೂಲ ಪುಟ $1 ಇಜ್ಜಿ.",
+ "mergehistory-no-destination": "ಗಮ್ಯತಾನ ಪುಟ $1 ಇಜ್ಜಿ.",
+ "mergehistory-invalid-source": "ಮೂಲ ಪುಟ ಒಂಜಿ ಮಾನ್ಯ ತರೆಬರವು ಆದಿಪ್ಪೊಡು.",
+ "mergehistory-invalid-destination": "ಗಮ್ಯತಾನ ಪುಟ ಒಂಜಿ ಮಾನ್ಯ ತೆರಬರವು ಆದಿಪ್ಪೊಡು.",
+ "mergehistory-autocomment": " [[:$1]]ನೆನ್ [[:$2]] ಗ್ ಸಮ್ಮಿಲಾಂಡ್",
+ "mergehistory-comment": " [[:$1]] ಎನ್ [[:$2]] ಗ್ ಸಮ್ಮಿಲಾಂಡ್: $3",
+ "mergehistory-same-destination": "ಮೂಲ ಬೊಕ ಗಮ್ಯತಾನ ಪುಟೊಲು ಒಂಜೇ ಆವರೆ ಬಲ್ಲಿ.",
"mergehistory-reason": "ಕಾರಣ:",
"mergelog": "ಸೇರ್ಗೆದ ದಾಕಲೆ",
"revertmerge": "ಅನ್-ಮರ್ಜ್ ಮಲ್ಪುಲೆ",
+ "mergelogpagetext": "ತಿರ್ತ್'ದ ಪಟ್ಟಿಡ್ ಅತೀಇಂಚೊಗು ಒಂಜಿ ಪುಟ ಚರಿತ್ರೆ ಬೊಕೊಂಜೆಕ್ ಸಮ್ಮಿಲಾತಿನವು ಉಂಡು.",
"history-title": "\"$1\" ಪುಟೊತ ಆವೃತ್ತಿ ಇತಿಹಾಸೊ",
"difference-title": "\"$1\" ಆವೃತ್ತಿಲೆನ ನಡುತ ವ್ಯತ್ಯಾಸೊ",
+ "difference-title-multipage": "ಪುಟ \"$1\" ಬೊಕ \"$2\"ನಡುತ ವ್ಯತ್ಯಾಸ",
+ "difference-multipage": "(ಪುಟೊಲೆನ ನಡುಟು ಉಪ್ಪುನ ವ್ಯತ್ಯಾಸ)",
"lineno": "$1ನೇ ಸಾಲ್:",
"compareselectedversions": "ಆಯ್ಕೆ ಮಲ್ತಿನ ಆವೃತ್ತಿಲೆನ್ ಹೊಂದಾಣಿಕೆ ಮಲ್ತ್ ತೂಲೆ",
+ "showhideselectedversions": "ಅಜತಿನ ಪಡಿಪಾಟೊಲು ತೋಜುನೆನ್ ಬದಲಾಲೆ",
"editundo": "ದುಂಬುದಲೆಕೊ",
"diff-empty": "(ದಾಲ ವ್ಯತ್ಯಾಸೊ ಇಜ್ಜಿ)",
"diff-multi-sameuser": "(ಒಂಜೇ ಸದಸ್ಯೆರೆ {{PLURAL:$1|ನಡುತ್ತ ಬದಲಾವಣೆನ್|$1 ನಡುತ್ತ ಬದಲಾವಣೆಲೆನ್}} ತೋಜಾದಿಜಿ)",
"diff-multi-otherusers": "({{PLURAL:$2|ಕುಡೊರಿ ಸದಸ್ಯೆರ್ನ|$2 ಸದಸ್ಯೆರ್ಲೆನ}} {{PLURAL:$1|ಒಂಜಿ ನಡುತ್ತ ಬದಲಾವಣೆನ್|$1 ನಡುತ್ತ ಬದಲಾವಣೆಲೆನ್}} ತೋಜಾದಿಜಿ)",
+ "diff-multi-manyusers": "({{PLURAL:$1|ಒಂಜಿ ನಡುತ ಪಡಿಪಾಟ|$1 ನಡುತ ಪಡಿಪಾಟೊಲು}} $2 ಡುದು ಎಚ್ಚದ {{PLURAL:$2|ಬಳಕೆದಾರೆ|ಬಳಕೆದಾರೆರ್}} ತೋಜಾದಿಜಿ)",
+ "diff-paragraph-moved-tonew": "ವಾಕ್ಯಪಂಕ್ತಿ ಚಲನೆ ಆತ್ಂಡ್. ಪೊಸ ಜಾಗೊಗು ನೆಗೆಪರೆ ಒತ್ತುಲೆ.",
+ "diff-paragraph-moved-toold": "ವಾಕ್ಯಪಂಕ್ತಿ ಚಲನೆ ಆತ್ಂಡ್. ದುಂಬುಇತ್ತಿನ ಜಾಗೊಗು ನೆಗೆಪರೆ ಒತ್ತುಲೆ.",
"searchresults": "ನಾಡ್ಪತ್ತ್ನೆತ ಪಲಿತಾಂಸೊಲು",
"searchresults-title": "\"$1\"ಕ್ ನಾಡ್ಪತ್ತ್ನೆತ ಪಲಿತಾಂಸೊಲು",
"notextmatches": "ವಾ ಪುಟೊತ ಪಠ್ಯೊಡುಲಾ ಹೋಲಿಕೆ ಇಜ್ಜಿ",
"Abdulq",
"Fitoschido",
"Dcljr",
- "Bukhari"
+ "Bukhari",
+ "Sajidkhan"
]
},
"tog-underline": "ربط کی خط کشیدگی:",
"pageinfo-display-title": "عنوان",
"pageinfo-default-sort": "کلید برائے ابتدائی ترتیب",
"pageinfo-length": "صفحہ کا حجم (بائٹ میں)",
- "pageinfo-namespace": "نام فضا",
+ "pageinfo-namespace": "نیم سپیس",
"pageinfo-article-id": "صفحہ کی شناخت",
"pageinfo-language": "زبان",
"pageinfo-language-change": "تبدیلی",
"deletepage": "刪除頁面",
"confirm": "確認",
"excontent": "內容為:「$1」",
- "excontentauthor": "內容為:\"$1\",且僅有一位貢獻者 \"[[Special:Contributions/$2|$2]]\" ([[User talk:$2|對話]])",
+ "excontentauthor": "內容為:「$1」,且僅有一位貢獻者「[[Special:Contributions/$2|$2]]」([[User talk:$2|對話]])",
"exbeforeblank": "被清空前的內容為:\"$1\"",
"delete-confirm": "刪除 \"$1\"",
"delete-legend": "刪除",
],
[
'el_id' => $row->el_id,
- ], __METHOD__, [ 'IGNORE' ]
+ ], __METHOD__
);
}
wfWaitForSlaves();
class ResetUserEmail extends Maintenance {
public function __construct() {
$this->addDescription( "Resets a user's email" );
- $this->addArg( 'user', 'Username or user ID, if starts with #', true );
+ $this->addArg( 'user', 'Username or user ID, if starts with #' );
$this->addArg( 'email', 'Email to assign' );
- $this->addOption( 'no-reset-password', 'Don\'t reset the user\'s password', false, false );
+ $this->addOption( 'no-reset-password', 'Don\'t reset the user\'s password' );
parent::__construct();
}
var deferred = $.Deferred();
// Allow calling with a single dependency as a string
- if ( typeof dependencies === 'string' ) {
+ if ( !Array.isArray( dependencies ) ) {
dependencies = [ dependencies ];
}
return deferred.reject( e ).promise();
}
- mw.loader.enqueue( dependencies, function () {
- deferred.resolve( mw.loader.require );
- }, deferred.reject );
+ mw.loader.enqueue(
+ dependencies,
+ function () { deferred.resolve( mw.loader.require ); },
+ deferred.reject
+ );
return deferred.promise();
};
+ /**
+ * Load a script by URL.
+ *
+ * Example:
+ *
+ * mw.loader.getScript(
+ * 'https://example.org/x-1.0.0.js'
+ * )
+ * .then( function () {
+ * // Script succeeded. You can use X now.
+ * }, function ( e ) {
+ * // Script failed. X is not avaiable
+ * mw.log.error( e.message ); // => "Failed to load script"
+ * } );
+ * } );
+ *
+ * @member mw.loader
+ * @param {string} url Script URL
+ * @return {jQuery.Promise} Resolved when the script is loaded
+ */
+ mw.loader.getScript = function ( url ) {
+ return $.ajax( url, { dataType: 'script', cache: true } )
+ .catch( function () {
+ throw new Error( 'Failed to load script' );
+ } );
+ };
+
// Alias $j to jQuery for backwards compatibility
// @deprecated since 1.23 Use $ or jQuery instead
mw.log.deprecate( window, '$j', $, 'Use $ or jQuery instead.' );
$table = $( '<table>' ).attr( 'id', 'mw-debug-querylist' );
$( '<tr>' )
- .append( $( '<th>' ).text( '#' ).css( 'width', '4em' ) )
- .append( $( '<th>' ).text( 'SQL' ) )
- .append( $( '<th>' ).text( 'Time' ).css( 'width', '8em' ) )
- .append( $( '<th>' ).text( 'Call' ).css( 'width', '18em' ) )
+ .append( $( '<th>' ).attr( 'scope', 'col' ).text( '#' ).css( 'width', '4em' ) )
+ .append( $( '<th>' ).attr( 'scope', 'col' ).text( 'SQL' ) )
+ .append( $( '<th>' ).attr( 'scope', 'col' ).text( 'Time' ).css( 'width', '8em' ) )
+ .append( $( '<th>' ).attr( 'scope', 'col' ).text( 'Call' ).css( 'width', '18em' ) )
.appendTo( $table );
for ( i = 0, length = this.data.queries.length; i < length; i += 1 ) {
$table = $( '<table>' ).appendTo( $unit );
$( '<tr>' )
- .html( '<th>Key</th><th>Value</th>' )
+ .html( '<th scope="col">Key</th><th scope="col">Value</th>' )
.appendTo( $table );
for ( key in data ) {
$( '<tr>' )
- .append( $( '<th>' ).text( key ) )
+ .append( $( '<th>' ).attr( 'scope', 'row' ).text( key ) )
.append( $( '<td>' ).text( data[ key ] ) )
.appendTo( $table );
}
// This specifies styling for individual field validation error messages.
// Show them below the fields to prevent line break glitches, and leave
// some space between the field and the error message box.
- .mw-ui-vform-field .error {
- display: block;
- margin-top: 5px;
+ .mw-ui-vform-field {
+ .error,
+ .warning {
+ display: block;
+ margin-top: 5px;
+ }
}
}
* Wrapper around Hamcrest's assertThat, which marks the assertion
* for PHPUnit so the test is not marked as risky
*/
- public function assertThatHamcrest( /* ... */ ) {
- call_user_func_array( 'assertThat', func_get_args() );
+ public function assertThatHamcrest( ...$args ) {
+ assertThat( ...$args );
$this->addToAssertionCount( 1 );
}
}
<?php
-use Mediawiki\Logger\LoggerFactory;
-use Mediawiki\Logger\Spi;
-use Mediawiki\Logger\LogCapturingSpi;
+use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\Logger\Spi;
+use MediaWiki\Logger\LogCapturingSpi;
/**
* Replaces the logging SPI on each test run. This allows
$this->assertEquals( [],
$this->title->getUserPermissionsErrors( 'edit', $this->user ) );
}
+
+ /**
+ * @covers Title::checkUserBlock
+ *
+ * Tests to determine that the passed in permission does not get mixed up with
+ * an action of the same name.
+ */
+ public function testUserBlockAction() {
+ global $wgLang;
+
+ $tester = $this->getMockBuilder( Action::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $tester->method( 'getName' )
+ ->willReturn( 'tester' );
+ $tester->method( 'getRestriction' )
+ ->willReturn( 'test' );
+ $tester->method( 'requiresUnblock' )
+ ->willReturn( false );
+
+ $this->setMwGlobals( [
+ 'wgActions' => [
+ 'tester' => $tester,
+ ],
+ 'wgGroupPermissions' => [
+ '*' => [
+ 'tester' => true,
+ ],
+ ],
+ ] );
+
+ $now = time();
+ $this->user->mBlockedby = $this->user->getName();
+ $this->user->mBlock = new Block( [
+ 'address' => '127.0.8.1',
+ 'by' => $this->user->getId(),
+ 'reason' => 'no reason given',
+ 'timestamp' => $now,
+ 'auto' => false,
+ 'expiry' => 'infinity',
+ ] );
+
+ $errors = [ [ 'blockedtext',
+ '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
+ 'Useruser', null, 'infinite', '127.0.8.1',
+ $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
+
+ $this->assertEquals( $errors,
+ $this->title->getUserPermissionsErrors( 'tester', $this->user ) );
+ }
}
public function testLockTSESlow() {
$cache = $this->cache;
$key = wfRandomString();
+ $key2 = wfRandomString();
$value = wfRandomString();
+ $mockWallClock = 1549343530.2053;
+ $priorTime = $mockWallClock;
+ $cache->setMockTime( $mockWallClock );
+
$calls = 0;
- $func = function ( $oldValue, &$ttl, &$setOpts ) use ( &$calls, $value, $cache, $key ) {
+ $func = function ( $oldValue, &$ttl, &$setOpts ) use ( &$calls, $value, $priorTime ) {
++$calls;
- $setOpts['since'] = microtime( true ) - 10;
- // Immediately kill any mutex rather than waiting a second
- $cache->delete( $cache::MUTEX_KEY_PREFIX . $key );
+ $setOpts['since'] = $priorTime - 10;
return $value;
};
- // Value should be marked as stale due to snapshot lag
+ // Value should be given a low logical TTL due to snapshot lag
$curTTL = null;
- $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] );
+ $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
$this->assertEquals( $value, $ret );
$this->assertEquals( $value, $cache->get( $key, $curTTL ), 'Value was populated' );
- $this->assertLessThan( 0, $curTTL, 'Value has negative curTTL' );
+ $this->assertEquals( 1, $curTTL, 'Value has reduced logical TTL', 0.01 );
$this->assertEquals( 1, $calls, 'Value was generated' );
+ $mockWallClock += 2;
+
+ $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
+ $this->assertEquals( $value, $ret );
+ $this->assertEquals( 2, $calls, 'Callback used (mutex acquired)' );
+
// Acquire a lock to verify that getWithSetCallback uses lockTSE properly
$this->internalCache->add( $cache::MUTEX_KEY_PREFIX . $key, 1, 0 );
- $ret = $cache->getWithSetCallback( $key, 30, $func, [ 'lockTSE' => 5 ] );
+
+ $ret = $cache->getWithSetCallback( $key, 300, $func, [ 'lockTSE' => 5 ] );
$this->assertEquals( $value, $ret );
- $this->assertEquals( 1, $calls, 'Callback was not used' );
+ $this->assertEquals( 3, $calls, 'Callback was not used (mutex not acquired)' );
+
+ $calls = 0;
+ $func2 = function ( $oldValue, &$ttl, &$setOpts ) use ( &$calls, $value, $priorTime ) {
+ ++$calls;
+ $setOpts['lag'] = 15;
+ return $value;
+ };
+
+ // Value should be given a low logical TTL due to replication lag
+ $curTTL = null;
+ $ret = $cache->getWithSetCallback( $key2, 300, $func2, [ 'lockTSE' => 5 ] );
+ $this->assertEquals( $value, $ret );
+ $this->assertEquals( $value, $cache->get( $key2, $curTTL ), 'Value was populated' );
+ $this->assertEquals( 30, $curTTL, 'Value has reduced logical TTL', 0.01 );
+ $this->assertEquals( 1, $calls, 'Value was generated' );
+
+ $ret = $cache->getWithSetCallback( $key2, 300, $func2, [ 'lockTSE' => 5 ] );
+ $this->assertEquals( $value, $ret );
+ $this->assertEquals( 1, $calls, 'Callback was used (not expired)' );
+
+ $mockWallClock += 31;
+
+ $ret = $cache->getWithSetCallback( $key2, 300, $func2, [ 'lockTSE' => 5 ] );
+ $this->assertEquals( $value, $ret );
+ $this->assertEquals( 2, $calls, 'Callback was used (mutex acquired)' );
}
/**
$calls = 0;
$func = function () use ( &$calls, $value, $cache, $key ) {
++$calls;
- // Immediately kill any mutex rather than waiting a second
- $cache->delete( $cache::MUTEX_KEY_PREFIX . $key );
return $value;
};
] );
// 1. Log in a test user, and block them.
- $userBlocker = $this->getTestSysop()->getUser();
$user1tmp = $this->getTestUser()->getUser();
$request1 = new FauxRequest();
$request1->getSession()->setUser( $user1tmp );
] );
$block->setBlocker( $this->getTestSysop()->getUser() );
$block->setTarget( $user1tmp );
- $block->setBlocker( $userBlocker );
$res = $block->insert();
$this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
$user1 = User::newFromSession( $request1 );
] );
// 1. Log in a test user, and block them.
- $userBlocker = $this->getTestSysop()->getUser();
$testUser = $this->getTestUser()->getUser();
$request1 = new FauxRequest();
$request1->getSession()->setUser( $testUser );
$block = new Block( [ 'enableAutoblock' => true ] );
$block->setBlocker( $this->getTestSysop()->getUser() );
$block->setTarget( $testUser );
- $block->setBlocker( $userBlocker );
$res = $block->insert();
$this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
$user = User::newFromSession( $request1 );
] );
// 1. Log in a test user, and block them indefinitely.
- $userBlocker = $this->getTestSysop()->getUser();
$user1Tmp = $this->getTestUser()->getUser();
$request1 = new FauxRequest();
$request1->getSession()->setUser( $user1Tmp );
$block = new Block( [ 'enableAutoblock' => true, 'expiry' => 'infinity' ] );
$block->setBlocker( $this->getTestSysop()->getUser() );
$block->setTarget( $user1Tmp );
- $block->setBlocker( $userBlocker );
$res = $block->insert();
$this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
$user1 = User::newFromSession( $request1 );
] );
// 1. Log in a blocked test user.
- $userBlocker = $this->getTestSysop()->getUser();
$user1tmp = $this->getTestUser()->getUser();
$request1 = new FauxRequest();
$request1->getSession()->setUser( $user1tmp );
$block = new Block( [ 'enableAutoblock' => true ] );
$block->setBlocker( $this->getTestSysop()->getUser() );
$block->setTarget( $user1tmp );
- $block->setBlocker( $userBlocker );
$res = $block->insert();
$this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
$user1 = User::newFromSession( $request1 );
] );
// 1. Log in a blocked test user.
- $userBlocker = $this->getTestSysop()->getUser();
$user1tmp = $this->getTestUser()->getUser();
$request1 = new FauxRequest();
$request1->getSession()->setUser( $user1tmp );
$block = new Block( [ 'enableAutoblock' => true ] );
$block->setBlocker( $this->getTestSysop()->getUser() );
$block->setTarget( $user1tmp );
- $block->setBlocker( $userBlocker );
$res = $block->insert();
$this->assertTrue( (bool)$res['id'], 'Failed to insert block' );
$user1 = User::newFromSession( $request1 );
*/
class MockDjVuHandler extends DjVuHandler {
- function isEnabled() {
+ public function isEnabled() {
return true;
}
--- /dev/null
+mw.getScriptExampleScriptLoaded = true;
// exposed for cross-file mocks.
delete mw.loader.testCallback;
delete mw.loader.testFail;
+ delete mw.getScriptExampleScriptLoaded;
}
} ) );
} );
} );
+ QUnit.test( '.getScript() - success', function ( assert ) {
+ var scriptUrl = QUnit.fixurl(
+ mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/mediawiki.loader.getScript.example.js'
+ );
+
+ return mw.loader.getScript( scriptUrl ).then(
+ function () {
+ assert.strictEqual( mw.getScriptExampleScriptLoaded, true, 'Data attached to a global object is available' );
+ }
+ );
+ } );
+
+ QUnit.test( '.getScript() - failure', function ( assert ) {
+ assert.rejects(
+ mw.loader.getScript( 'https://example.test/not-found' ),
+ /Failed to load script/,
+ 'Descriptive error message'
+ );
+ } );
+
}() );
*/
beforeTest: function ( test ) {
if ( process.env.DISPLAY && process.env.DISPLAY.startsWith( ':' ) ) {
+ var logBuffer;
let videoPath = filePath( test, logPath, 'mp4' );
const { spawn } = require( 'child_process' );
ffmpeg = spawn( 'ffmpeg', [
videoPath // output file
] );
+ logBuffer = function ( buffer, prefix ) {
+ let lines = buffer.toString().trim().split( '\n' );
+ lines.forEach( function ( line ) {
+ console.log( prefix + line );
+ } );
+ };
+
ffmpeg.stdout.on( 'data', ( data ) => {
- console.log( `ffmpeg stdout: ${data}` );
+ logBuffer( data, 'ffmpeg stdout: ' );
} );
ffmpeg.stderr.on( 'data', ( data ) => {
- console.log( `ffmpeg stderr: ${data}` );
+ logBuffer( data, 'ffmpeg stderr: ' );
} );
- ffmpeg.on( 'close', ( code ) => {
+ ffmpeg.on( 'close', ( code, signal ) => {
console.log( '\n\tVideo location:', videoPath, '\n' );
- console.log( `ffmpeg exited with code ${code}` );
+ if ( code !== null ) {
+ console.log( `\tffmpeg exited with code ${code} ${videoPath}` );
+ }
+ if ( signal !== null ) {
+ console.log( `\tffmpeg received signal ${signal} ${videoPath}` );
+ }
} );
}
},