$wgEnableScaryTranscluding = false;
/**
- * Expiry time for transcluded templates cached in transcache database table.
+ * Expiry time for transcluded templates cached in object cache.
* Only used $wgEnableInterwikiTranscluding is set to true.
*/
$wgTranscludeCacheExpiry = 3600;
'missing' => $dependencyName,
];
}
+ if ( $constraint === '*' ) {
+ // short-circuit since any version is OK.
+ return false;
+ }
// Check if the dependency has specified a version
if ( !isset( $this->loaded[$dependencyName]['version'] ) ) {
- // If we depend upon any version, and none is set, that's fine.
- if ( $constraint === '*' ) {
- wfDebug( "{$dependencyName} does not expose its version, but {$checkedExt}"
- . " mentions it with constraint '*'. Assume it's ok so." );
- return false;
- } else {
- // Otherwise, mark it as incompatible.
- $msg = "{$dependencyName} does not expose its version, but {$checkedExt}"
- . " requires: {$constraint}.";
- return [
- 'msg' => $msg,
- 'type' => "incompatible-$type",
- 'incompatible' => $checkedExt,
- ];
- }
+ $msg = "{$dependencyName} does not expose its version, but {$checkedExt}"
+ . " requires: {$constraint}.";
+ return [
+ 'msg' => $msg,
+ 'type' => "incompatible-$type",
+ 'incompatible' => $checkedExt,
+ ];
} else {
// Try to get a constraint for the dependency version
try {
* Item class for a archive table row
*/
class RevDelArchiveItem extends RevDelRevisionItem {
- public function __construct( $list, $row ) {
- RevDelItem::__construct( $list, $row );
- $this->revision = Revision::newFromArchiveRow( $row,
- [ 'page' => $this->list->title->getArticleID() ] );
+ protected static function initRevision( $list, $row ) {
+ return Revision::newFromArchiveRow( $row,
+ [ 'page' => $list->title->getArticleID() ] );
}
public function getIdField() {
protected $lockFile;
public function __construct( $list, $row ) {
- RevDelItem::__construct( $list, $row );
- $this->file = ArchivedFile::newFromRow( $row );
+ parent::__construct( $list, $row );
$this->lockFile = RepoGroup::singleton()->getLocalRepo()->newFile( $row->fa_name );
}
+ protected static function initFile( $list, $row ) {
+ return ArchivedFile::newFromRow( $row );
+ }
+
public function getIdField() {
return 'fa_id';
}
* used via RevDelRevisionList.
*/
class RevDelArchivedRevisionItem extends RevDelArchiveItem {
- public function __construct( $list, $row ) {
- RevDelItem::__construct( $list, $row );
-
- $this->revision = Revision::newFromArchiveRow( $row,
- [ 'page' => $this->list->title->getArticleID() ] );
- }
-
public function getIdField() {
return 'ar_rev_id';
}
public function __construct( $list, $row ) {
parent::__construct( $list, $row );
- $this->file = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
+ $this->file = static::initFile( $list, $row );
+ }
+
+ /**
+ * Create file object from $row sourced from $list
+ *
+ * @param RevDelFileList $list
+ * @param mixed $row
+ * @return mixed
+ */
+ protected static function initFile( $list, $row ) {
+ return RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
}
public function getIdField() {
public function __construct( $list, $row ) {
parent::__construct( $list, $row );
- $this->revision = new Revision( $row );
+ $this->revision = static::initRevision( $list, $row );
+ }
+
+ /**
+ * Create revision object from $row sourced from $list
+ *
+ * @param RevisionListBase $list
+ * @param mixed $row
+ * @return Revision
+ */
+ protected static function initRevision( $list, $row ) {
+ return new Revision( $row );
}
public function getIdField() {
$opts->validateIntBounds( 'limit', 0, $this->getConfig()->get( 'QueryPageDefaultLimit' ) );
if ( $par !== null ) {
- $opts->setValue( 'target', $par );
+ // Beautify the username
+ $par = User::getCanonicalName( $par, false );
+ $opts->setValue( 'target', (string)$par );
}
$ns = $opts->getValue( 'namespace' );
"confirm-unwatch-top": "¿Desaniciar esta páxina de la to llista de vixilancia?",
"confirm-rollback-button": "Aceutar",
"confirm-rollback-top": "¿Revertir les ediciones a esta páxina?",
+ "confirm-mcrundo-title": "Desfacer un cambéu",
+ "mcrundofailed": "Falló desfacer",
+ "mcrundo-missingparam": "Faltan parámetros riquíos na solicitú.",
+ "mcrundo-changed": "La páxina cambió desque visti les diferencies. Revisa'l cambiu nuevu.",
"quotation-marks": "«$1»",
"imgmultipageprev": "← páxina anterior",
"imgmultipagenext": "páxina siguiente →",
"confirm-rollback-top": "Адкаціць праўкі на гэтай старонцы?",
"confirm-mcrundo-title": "Адмяніць зьмену",
"mcrundofailed": "Адмена не атрымалася",
+ "mcrundo-missingparam": "Адсутнічаюць абавязковыя парамэтры для запыту.",
+ "mcrundo-changed": "Гэтая старонка была зьмененая з моманту, калі вы праглядалі зьмены. Калі ласка, праглядзіце новую зьмену.",
"quotation-marks": "«$1»",
"imgmultipageprev": "← папярэдняя старонка",
"imgmultipagenext": "наступная старонка →",
"ns-specialprotected": "Специалните страници не могат да бъдат редактирани.",
"titleprotected": "Тази страница е била защитена срещу създаване от [[User:$1|$1]].\nПосочената причина е <em>$2</em>.",
"filereadonlyerror": "Файлът „$1“ не може да бъде променен, тъй като файловото хранилище „$2“ е в режим само за четене.\n\nСистемният администратор, който го е заключил, е посочил следната причина: „$3“.",
+ "invalidtitle": "Невалидно заглавие",
"invalidtitle-knownnamespace": "Невалидно заглавие с именно пространство „$2“ и текст „$3“",
"invalidtitle-unknownnamespace": "Невалидно заглавие с неразпознато именно пространство номер $1 и текст „$2“",
"exception-nologin": "Не сте влезли в системата",
"rcfilters-group-results-by-page": "Групиране на резултатите по страница",
"rcfilters-activefilters": "Активни филтри",
"rcfilters-activefilters-hide": "Скриване",
+ "rcfilters-activefilters-show": "Показване",
"rcfilters-advancedfilters": "Разширени филтри",
"rcfilters-limit-title": "Резултати за показване",
"rcfilters-limit-and-date-label": "$1 {{PLURAL:$1|промяна|промени}}, $2",
"edit-error-long": "Fehler:\n\n$1",
"revid": "Version $1",
"pageid": "Seitenkennung $1",
- "interfaceadmin-info": "$1\n\nBerechtigungen für das Bearbeiten von wikiweiten CSS/JS/JSON-Dateien wurden kürzlich von dem Recht <code>editinterface</code> getrennt. Falls du nicht verstehst, warum du diesen Fehler erhältst, siehe bitte [[mw:MediaWiki_1.32/interface-admin]].",
+ "interfaceadmin-info": "$1\n\nBerechtigungen für das Bearbeiten von wikiweiten CSS/JS/JSON-Dateien wurden kürzlich vom Recht <code>editinterface</code> getrennt. Falls du nicht verstehst, warum du diesen Fehler erhältst, siehe [[mw:MediaWiki_1.32/interface-admin]].",
"rawhtml-notallowed": "<html>-Tags können nicht außerhalb von normalen Seiten verwendet werden.",
"gotointerwiki": "{{SITENAME}} verlassen",
"gotointerwiki-invalid": "Der angegebene Titel ist ungültig.",
"activeusers-noresult": "کاربری پیدا نشد.",
"activeusers-submit": "نمایش کاربران فعال",
"listgrouprights": "اختیارات گروههای کاربری",
- "listgrouprights-summary": "فهرست زیر شامل گروههای کاربری تعریف شده در این ویکی و اختیارات داده شده به آنها است.\nاطلاعات بیشتر در مورد هر یک از اختیارات را در [[{{MediaWiki:Listgrouprights-helppage}}]] بیابید.",
+ "listgrouprights-summary": "فهرست زیر شامل گروههای کاربری تعریف شده در این ویکی و اختیارات داده شده به آنها است.\nاطلاعات بیشتر در مورد هر کدام از آنها را در [[{{MediaWiki:Listgrouprights-helppage}}|اختیارات گروههای کاربری]] بیابید.",
"listgrouprights-key": "* <span class=\"listgrouprights-granted\">اختیارات دادهشده</span>\n* <span class=\"listgrouprights-revoked\">اختیارات گرفتهشده</span>",
"listgrouprights-group": "گروه",
"listgrouprights-rights": "دسترسیها",
"userlogin-yourname-ph": "Antré zòt non di itilizatò",
"createacct-another-username-ph": "Antré non-an di itilizatò",
"yourpassword": "Mo di pas :",
- "userlogin-yourpassword": "Mo di pas",
+ "userlogin-yourpassword": "Modipas",
"userlogin-yourpassword-ph": "Antré zòt mo di pas",
"createacct-yourpassword-ph": "Antré oun mo di pas",
"yourpasswordagain": "Konfirmé mo di pas :",
- "createacct-yourpasswordagain": "Konfirmé mo di pas",
+ "createacct-yourpasswordagain": "Konfirmen modipas-a",
"createacct-yourpasswordagain-ph": "Antré òkò menm mo di pas",
"userlogin-remembermypassword": "Gardé mo sésyon aktiv",
"userlogin-signwithsecure": "Itilizé roun konnègsyon sékirizé",
"sig_tip": "အချိန်ပါပြသော သင့်လက်မှတ်",
"hr_tip": "မျဉ်းလဲ (စိစစ်သုံးရန်)",
"summary": "အကျဉ်းချုပ် -",
- "subject": "အကြောင်းအရာ -",
+ "subject": "အကြောင်းအရာ:",
"minoredit": "အရေးမကြီးသော ပြင်ဆင်မှု ဖြစ်သည်",
"watchthis": "ဤစာမျက်နှာကို စောင့်ကြည့်ရန်",
"savearticle": "ဤစာမျက်နှာကို သိမ်းရန်",
"allmessages": "စနစ်၏ သတင်းများ",
"allmessagesname": "အမည်",
"allmessagesdefault": "ပုံမှန် အသိပေးချက် စာသား",
+ "allmessagescurrent": "လက်ရှိ မက်ဆေ့စာသား",
"allmessages-filter-legend": "စစ်ထုတ်ခြင်း",
"allmessages-filter-unmodified": "မပြုပြင်ထားသော",
"allmessages-filter-all": "အားလုံး",
"tags-title": "အမည်တွဲများ",
"tags-tag": "အမည်တွဲ အမည်",
"tags-description-header": "ဆိုလိုရင်းအဓိပ္ပာယ် အပြည့်အစုံ",
+ "tags-source-header": "ရင်းမြစ်",
"tags-active-yes": "မှန်",
"tags-active-no": "မလုပ်ပါ",
"tags-source-extension": "ဆော့ဝဲလ်မှ သတ်မှတ်ထားသော",
"htmlform-submit": "ထည့်သွင်းရန်",
"htmlform-reset": "ပြောင်းလဲထားသည်များ မလုပ်တော့ရန်",
"htmlform-selectorother-other": "အခြား",
+ "htmlform-no": "မလုပ်ပါ",
"htmlform-chosen-placeholder": "လုပ်ဆောင်ချက်တစ်ခု ရွေးချယ်ရန်",
"htmlform-cloner-create": "ပို၍ ထပ်ပေါင်းရန်",
"htmlform-cloner-delete": "ဖယ်ရှားရန်",
"confirm-rollback-top": "Bewerkingen op deze pagina ongedaan maken?",
"confirm-mcrundo-title": "Een wijziging ongedaan maken",
"mcrundofailed": "Ongedaan maken mislukt",
- "mcrundo-missingparam": "Er ontbreken benodigde parameters in het verzoek.",
+ "mcrundo-missingparam": "Er ontbreken nodige parameters in het verzoek.",
"quotation-marks": "\"$1\"",
"imgmultipageprev": "← vorige pagina",
"imgmultipagenext": "volgende pagina →",
"rcfilters-watchlist-markseen-button": "Merk alle endringar som sette",
"rcfilters-watchlist-edit-watchlist-button": "Endra lista over sider du overvaker",
"rcfilters-watchlist-showupdated": "Sider du ikkje har vitja sidan dei vart endra er viste med <strong>feit</strong> skrift.",
+ "rcfilters-filter-showlinkedfrom-label": "Vis endringar på sider det vert lenkja til på sida",
+ "rcfilters-filter-showlinkedfrom-option-label": "<strong>Sider som det vert lenkja til på</strong> den valde sida",
+ "rcfilters-filter-showlinkedto-label": "Vis endringar på sider som lenkjar til sida",
+ "rcfilters-filter-showlinkedto-option-label": "<strong>Sider som lenkjar til</strong> den valde sida",
"rcnotefrom": "Nedanfor er endringane gjorde sidan <strong>$2</strong> viste (opp til <strong>$1</strong> stykke)",
"rclistfromreset": "Nullstill datoval",
"rclistfrom": "Vis nye endringar sidan $3 $2",
"AttemptToCallNil",
"Stjn",
"Vlad5250",
- "Marshmallych"
+ "Marshmallych",
+ "Atsirlin"
]
},
"tog-underline": "Подчёркивание ссылок:",
"jumpto": "Перейти к:",
"jumptonavigation": "навигация",
"jumptosearch": "поиск",
- "view-pool-error": "Ð\98звиниÑ\82е, в наÑ\81Ñ\82оÑ\8fÑ\89ий моменÑ\82 Ñ\81еÑ\80веÑ\80Ñ\8b пеÑ\80егÑ\80Ñ\83женÑ\8b.\nСлиÑ\88ком много Ñ\83Ñ\87аÑ\81Ñ\82ников пÑ\8bÑ\82аÑ\8eÑ\82Ñ\81Ñ\8f еÑ\91 пÑ\80оÑ\81моÑ\82Ñ\80еÑ\82Ñ\8c.\nПожалуйста, подождите немного перед повторной попыткой обращения к этой странице.\n\n$1",
+ "view-pool-error": "Ð\98звиниÑ\82е, в наÑ\81Ñ\82оÑ\8fÑ\89ий моменÑ\82 Ñ\81еÑ\80веÑ\80Ñ\8b пеÑ\80егÑ\80Ñ\83женÑ\8b.\nСлиÑ\88ком много Ñ\83Ñ\87аÑ\81Ñ\82ников пÑ\8bÑ\82аÑ\8eÑ\82Ñ\81Ñ\8f пÑ\80оÑ\81моÑ\82Ñ\80еÑ\82Ñ\8c еÑ\91 одновÑ\80еменно.\nПожалуйста, подождите немного перед повторной попыткой обращения к этой странице.\n\n$1",
"generic-pool-error": "Извините, в настоящий момент серверы перегружены.\nСлишком много участников пытаются просмотреть этот ресурс.\nПожалуйста, подождите и повторите попытку обращения к нему позже.",
"pool-timeout": "Истекло время ожидания блокировки",
"pool-queuefull": "Пул запросов полон",
"privacypage": "Project:Политика конфиденциальности",
"badaccess": "Ошибка доступа",
"badaccess-group0": "Вы не можете выполнить запрошенное действие.",
- "badaccess-groups": "Ð\97апÑ\80оÑ\88енное дейÑ\81Ñ\82вие могÑ\83Ñ\82 вÑ\8bполнÑ\8fÑ\82Ñ\8c Ñ\82олÑ\8cко Ñ\83Ñ\87аÑ\81Ñ\82ники {{PLURAL:$2|1=из гÑ\80Ñ\83ппÑ\8b «$1»|одной из Ñ\81ледÑ\83Ñ\8eÑ\89иÑ\85 гÑ\80Ñ\83пп: $1}}",
+ "badaccess-groups": "Запрошенное действие могут выполнять участники {{PLURAL:$2|1=из группы «$1»|одной из следующих групп: $1}}",
"versionrequired": "Требуется MediaWiki версии $1",
"versionrequiredtext": "Для работы с этой страницей требуется MediaWiki версии $1. См. [[Special:Version|информацию о программном обеспечении]].",
"ok": "OK",
"perfcachedts": "Данные взяты из кэша; последний раз он обновлялся в $1. В кэше хранится не более {{PLURAL:$4|1=одной записи|$4 записи|$4 записей}}.",
"querypage-no-updates": "Обновление этой страницы сейчас отключено.\nПредставленные здесь данные не будут обновляться.",
"viewsource": "Просмотр кода",
- "viewsource-title": "Ð\9fÑ\80оÑ\81моÑ\82Ñ\80 иÑ\81Ñ\85одного Ñ\82екÑ\81Ñ\82а страницы $1",
+ "viewsource-title": "Ð\9fÑ\80оÑ\81моÑ\82Ñ\80 кода страницы $1",
"actionthrottled": "Ограничение по скорости",
"actionthrottledtext": "Вы исчерпали установленное для борьбы со спамом ограничение на максимальное количество попыток выполнения запрошенного действия в короткий промежуток времени.\nПожалуйста, повторите попытку через несколько минут.",
"protectedpagetext": "Эта страница защищена для предотвращения её редактирования или совершений других действий.",
"virus-scanfailed": "ошибка сканирования (код $1)",
"virus-unknownscanner": "неизвестный антивирус:",
"logouttext": "<strong>Вы завершили сеанс работы.</strong>\n\nНекоторые страницы могут продолжить отображаться так, как будто вы все ещё не завершили сеанс, пока вы не обновите кэш браузера.",
- "cannotlogoutnow-title": "Невозможно выйти прямо сейчас",
- "cannotlogoutnow-text": "Нельзя выйти во время использования $1.",
+ "cannotlogoutnow-title": "Ð\9dевозможно вÑ\8bйÑ\82и из Ñ\81иÑ\81Ñ\82емÑ\8b пÑ\80Ñ\8fмо Ñ\81ейÑ\87аÑ\81",
+ "cannotlogoutnow-text": "Ð\9dелÑ\8cзÑ\8f вÑ\8bйÑ\82и из Ñ\81иÑ\81Ñ\82емÑ\8b во вÑ\80емÑ\8f иÑ\81полÑ\8cзованиÑ\8f $1.",
"welcomeuser": "Добро пожаловать, $1!",
- "welcomecreation-msg": "Ваша учётная запись успешно создана.\nТеперь вы также можете провести [[Special:Preferences|персональную настройку]] сайта {{SITENAME}}.",
+ "welcomecreation-msg": "Ваша учётная запись была создана.\nТеперь вы также можете изменить [[Special:Preferences|персональные настройки]] для сайта {{SITENAME}}, если вы желаете.",
"yourname": "Имя учётной записи:",
"userlogin-yourname": "Имя учётной записи",
"userlogin-yourname-ph": "Введите имя вашей учётной записи",
"userlogin-signwithsecure": "Защищённое соединение",
"cannotlogin-title": "Невозможно войти",
"cannotlogin-text": "Вход в систему невозможен.",
- "cannotloginnow-title": "Невозможно войти прямо сейчас",
+ "cannotloginnow-title": "Ð\9dевозможно войÑ\82и в Ñ\81иÑ\81Ñ\82емÑ\83 пÑ\80Ñ\8fмо Ñ\81ейÑ\87аÑ\81",
"cannotloginnow-text": "Нельзя войти во время использования $1.",
"cannotcreateaccount-title": "Невозможно создать учётные записи",
"cannotcreateaccount-text": "Прямое создание учетных записей не включено в этой вики.",
"userlogout": "Завершение сеанса",
"notloggedin": "Вы не представились системе",
"userlogin-noaccount": "Нет учётной записи?",
- "userlogin-joinproject": "Присоединиться к проекту",
+ "userlogin-joinproject": "Присоединиться к проекту {{SITENAME}}.",
"createaccount": "Создать учётную запись",
"userlogin-resetpassword-link": "Сбросить ваш пароль?",
"userlogin-helplink2": "Помощь по входу",
"createacct-error": "Ошибка создания учётной записи",
"createaccounterror": "Невозможно создать учётную запись: $1",
"nocookiesnew": "Участник зарегистрирован, но не представлен. {{SITENAME}} использует «cookies» для представления участников. У вас «cookies» запрещены. Пожалуйста, разрешите их, а затем представьтесь со своиим новым именем участника и паролем.",
- "nocookieslogin": "{{SITENAME}} использует «cookies» для представления участников. Вы их отключили. Пожалуйста, включите их и попробуйте снова.",
+ "nocookieslogin": "{{SITENAME}} использует «cookies»-файлы для представления участников. Вы отключили использование «cookies»-файлов. Пожалуйста, включите использование «cookies»-файлов и попробуйте снова.",
"nocookiesfornew": "Учётная запись участника не была создана из-за невозможности проверить её источник. \nУбедитесь, что включены «cookies», обновите страницу и попробуйте ещё раз.",
"nocookiesforlogin": "{{int:nocookieslogin}}",
"createacct-loginerror": "Учётная запись была успешно создана, но вы не смогли войти в систему автоматически. Пожалуйста, [[Special:UserLogin|авторизуйтесь вручную]].",
"createacct-another-realname-tip": "Настоящее имя (необязательное поле).\nЕсли вы укажете его, то оно будет использовано для того, чтобы показать, кем была внесена правка страницы.",
"pt-login": "Войти",
"pt-login-button": "Войти",
- "pt-login-continue-button": "Продолжить процедуру входа",
+ "pt-login-continue-button": "Продолжить процедуру входа в систему",
"pt-createaccount": "Создать учётную запись",
"pt-userlogout": "Выйти",
"php-mail-error-unknown": "Неизвестная ошибка в PHP-функции mail()",
"undo-main-slot-only": "Правка не может быть отменена, поскольку оно включает контент вне основного слота.",
"undo-norev": "Правка не может быть отменена, так как её не существует или она была удалена.",
"undo-nochange": "Правка, похоже, уже была отменена.",
- "undo-summary": "Отмена правки $1, сделанной {{gender:$2|участником|участницей}} [[Special:Contributions/$2|$2]] ([[User talk:$2|обсуждение]])",
+ "undo-summary": "Отмена правки $1, сделанной [[Special:Contributions/$2|$2]] ([[User talk:$2|обсуждение]])",
"undo-summary-username-hidden": "Отмена правки $1, сделанной участником, чьё имя скрыто",
"cantcreateaccount-text": "Создание учётных записей с этого IP-адреса (<strong>$1</strong>) было заблокировано {{GENDER:$3|участником|участницей|}} [[User:$3|$3]].\n\n$3 {{GENDER:$3|указал|указала}} следующую причину: <em>$2</em>.",
"cantcreateaccount-range-text": "{{GENDER:$3|Участник|Участница}} [[User:$3|$3]] {{GENDER:$3|установил|установила}} запрет на создание учётных записей для диапазона IP-адресов <strong>$1</strong>, включающего ваш IP-адрес (<strong>$4</strong>). \n\nБыла указана следующая причина: <em>$2</em>.",
"last": "пред.",
"page_first": "первая",
"page_last": "последняя",
- "histlegend": "Выбор версий: отметьте версии страницы, которые вы хотите сравнить, и нажмите <strong>{{int:compare-submit}}/strong>.<br />\nПояснения: <strong>({{int:cur}})/strong> — отличия от текущей версии; <strong>({{int:last}})/strong> — отличия от предшествующей версии; <strong>{{int:minoreditletter}}/strong> — незначительные изменения.",
+ "histlegend": "Выбор версий: отметьте версии страницы, которые вы хотите сравнить, и нажмите <strong>{{int:compare-submit}}</strong>.<br />\nПояснения: <strong>({{int:cur}})</strong> — отличия от текущей версии; <strong>({{int:last}})</strong> — отличия от предшествующей версии; <strong>{{int:minoreditletter}}</strong> — незначительные изменения.",
"history-fieldset-title": "Поиск правок",
"history-show-deleted": "Только удалённые правки",
"histfirst": "старейшие",
"rev-showdeleted": "показать",
"revisiondelete": "Удалить/восстановить версии страницы",
"revdelete-nooldid-title": "Не задана целевая версия",
- "revdelete-nooldid-text": "Ð\92Ñ\8b не задали веÑ\80Ñ\81иÑ\8e (веÑ\80Ñ\81ии), или Ñ\83казаннаÑ\8f веÑ\80Ñ\81иÑ\8f не Ñ\81Ñ\83Ñ\89еÑ\81Ñ\82вÑ\83еÑ\82, или же вы пытаетесь скрыть текущую версию.",
+ "revdelete-nooldid-text": "ЦелеваÑ\8f веÑ\80Ñ\81иÑ\8f не заданÑ\8b, Ñ\83казаннаÑ\8f веÑ\80Ñ\81иÑ\8f не Ñ\81Ñ\83Ñ\89еÑ\81Ñ\82вÑ\83еÑ\82 или же вы пытаетесь скрыть текущую версию.",
"revdelete-no-file": "Указанный файл не существует.",
"revdelete-show-file-confirm": "Вы уверены, что вы хотите просмотреть удалённую версию файла «<nowiki>$1</nowiki>» от $2, $3?",
"revdelete-show-file-submit": "Да",
"default": "по умолчанию",
"prefs-files": "Файлы",
"prefs-custom-css": "Собственный CSS",
- "prefs-custom-json": "Ð\9fолÑ\8cзоваÑ\82елÑ\8cÑ\81кий JSON",
+ "prefs-custom-json": "СобÑ\81Ñ\82веннÑ\8bй JSON",
"prefs-custom-js": "Собственный JS",
"prefs-common-config": "Общие CSS/JSON/JavaScript для всех тем оформления:",
"prefs-reset-intro": "Эта страница может быть использована для сброса ваших настроек на стандартные.\nУчтите, что это действие невозможно отменить.",
"userrights-groupsmember": "Состоит в группах:",
"userrights-groupsmember-auto": "Неявно состоит в группах:",
"userrights-groupsmember-type": "$1",
- "userrights-groups-help": "Вы можете изменить группы, в которые входит {{GENDER:$1|этот участник|эта участница}}.\n* Если около названия группы стоит отметка — {{GENDER:$1|участник|участница}} входит в эту группу.\n* Если отметка не стоит — {{GENDER:$1|участник|участница}} не входит в эту группу.\n* Символ * указывает на то, что вы не сможете удалить {{GENDER:$1|участника|участницу}} из группы, если добавите {{GENDER:$1|его|её}} в неё (или наоборот).\n* Символ # указывает на то, что вы можете только отложить время истечения членства в этой группы, вы не можете перенести его на более ранний срок.",
+ "userrights-groups-help": "Вы можете изменить группы, в которые входит {{GENDER:$1|этот участник|эта участница}}.\n* Если около названия группы стоит отметка — {{GENDER:$1|участник|участница}} входит в эту группу.\n* Если отметка не стоит — {{GENDER:$1|участник|участница}} не входит в эту группу.\n* Символ * указывает на то, что вы не сможете удалить {{GENDER:$1|участника|участницу}} из группы, если добавите {{GENDER:$1|его|её}} в неё (или наоборот).\n* Символ # указывает на то, что вы можете только отложить, но не перенести время истечения членства в этой группе на более ранний срок.",
"userrights-reason": "Причина:",
"userrights-no-interwiki": "У вас нет разрешения изменять права участников в других вики.",
"userrights-nodatabase": "База данных $1 не существует или расположена не локально.",
"block": "Блокировка участника",
"unblock": "Разблокировка участника",
"blockip": "Заблокировать {{GENDER:$1|участника|участницу}}",
- "blockiptext": "Используйте форму ниже, чтобы заблокировать возможность записи с определённого IP-адреса или имени участника.\nЭто может быть сделано только для предотвращения вандализма и только в соответствии с [[{{MediaWiki:Policy-url}}|правилами]].\nНиже укажите конкретную причину (к примеру, процитируйте некоторые страницы с признаками вандализма).\nВы можете заблокировать диапазоны IP-адресов, используя [https://ru.wikipedia.org/wiki/Бесклассовая_адресация CIDR]-синтаксис. Максимально допустимый диапазон — /$1 для протокола IPv4 и /$2 для протокола IPv6.",
+ "blockiptext": "Используйте форму ниже, чтобы заблокировать возможность редактирования с определённого IP-адреса или имени участника.\nЭтот инструмент следует использовать для предотвращения вандализма и только в соответствии с [[{{MediaWiki:Policy-url}}|правилами]].\nНиже укажите конкретную причину (к примеру, процитируйте некоторые страницы с признаками вандализма).\nВы можете заблокировать диапазоны IP-адресов, используя [https://ru.wikipedia.org/wiki/Бесклассовая_адресация CIDR]-синтаксис. Максимально допустимый диапазон — /$1 для протокола IPv4 и /$2 для протокола IPv6.",
"ipaddressorusername": "IP-адрес или имя участника:",
"ipbexpiry": "Закончится через:",
"ipbreason": "Причина:",
"ipboptions": "2 часа:2 hours,1 день:1 day,3 дня:3 days,1 неделя:1 week,2 недели:2 weeks,1 месяц:1 month,3 месяца:3 months,6 месяцев:6 months,1 год:1 year,бессрочно:infinite",
"ipbhidename": "Скрыть имя участника из правок и списков",
"ipbwatchuser": "Добавить в список наблюдения личную страницу участника и его страницу обсуждения",
- "ipb-disableusertalk": "Запретить этому участнику редактировать свою страницу обсуждения во время блокировки",
+ "ipb-disableusertalk": "Запретить этому участнику редактировать свою страницу обсуждения",
"ipb-change-block": "Переблокировать участника с этими настройками",
"ipb-confirm": "Подтвердить блокировку",
"badipaddress": "IP-адрес записан в неправильном формате, или участника с таким именем не существует.",
"autoblocklist-submit": "Найти",
"autoblocklist-legend": "Список автоблокировок",
"autoblocklist-localblocks": "{{PLURAL:$1|Локальная автоблокировка|Локальные автоблокировки}}",
- "autoblocklist-total-autoblocks": "Ð\92Ñ\81его авÑ\82облоков: $1",
+ "autoblocklist-total-autoblocks": "Ð\92Ñ\81его авÑ\82облокиÑ\80овок: $1",
"autoblocklist-empty": "Список автоблокировок пуст.",
"autoblocklist-otherblocks": "{{PLURAL:$1|Другая автоблокировка|Другие автоблокировки}}",
"ipblocklist": "Заблокированные участники",
"moveuserpage-warning": "<strong>Внимание:</strong> вы собираетесь переименовать страницу участника. Пожалуйста, обратите внимание, что переименована будет только страница, участник <strong>не</strong> будет переименован.",
"movecategorypage-warning": "<strong>Предупреждение:</strong> Вы собираетесь переименовать страницу категории. Пожалуйста, обратите внимание, что будет переименована только эта страница, а все страницы старой категории <em>не</em> будут перекатегоризованы в новую.",
"movenologintext": "Вы должны [[Special:UserLogin|представиться системе]],\nчтобы иметь возможность переименовать страницы.",
- "movenotallowed": "У вас нет разрешения переименовывать страницы.",
- "movenotallowedfile": "У вас нет разрешения переименовывать файлы.",
- "cant-move-user-page": "У вас нет разрешения переименовывать основные страницы участников.",
+ "movenotallowed": "У вас нет прав на переименовывание страниц.",
+ "movenotallowedfile": "У вас нет прав на переименовывание файлов.",
+ "cant-move-user-page": "У вас нет прав на переименовывание основных страниц участников.",
"cant-move-to-user-page": "У вас нет прав переименовывать страницу в страницу участника (можно переименовать в подстраницу).",
- "cant-move-category-page": "У вас нет разрешения переименовывать страницы категорий.",
- "cant-move-to-category-page": "У вас нет разрешения переименовывать страницы в страницу категории.",
- "cant-move-subpages": "У вас нет разрешения переименовывать подстраницы.",
+ "cant-move-category-page": "У вас нет прав на переименовывание страниц категорий.",
+ "cant-move-to-category-page": "У вас нет прав на переименовывание страницы в страницу категории.",
+ "cant-move-subpages": "У вас нет прав на переименовывание подстраниц.",
"namespace-nosubpages": "Пространство имён «$1» не разрешает создание страниц.",
"newtitle": "Новое название:",
"move-watch": "Добавить в список наблюдения исходную и целевую страницы",
"movenosubpage": "У этой страницы нет подстраниц.",
"movereason": "Причина:",
"revertmove": "возврат",
- "delete_and_move_text": "ЦелеваÑ\8f Ñ\81траница с именем «[[:$1]]» уже существует. \nХотите удалить её, чтобы сделать возможным переименование?",
+ "delete_and_move_text": "Страница с именем «[[:$1]]» уже существует. \nХотите удалить её, чтобы сделать возможным переименование?",
"delete_and_move_confirm": "Да, удалить эту страницу",
"delete_and_move_reason": "Удалено для возможности переименования «[[$1]]»",
"selfmove": "Невозможно переименовать страницу: исходное и новое имя страницы совпадают.",
"immobile-target-namespace-iw": "Ссылка интервики не может быть использована для переименования.",
"immobile-source-page": "Эту страницу нельзя переименовать.",
"immobile-target-page": "Нельзя присвоить странице это имя.",
- "bad-target-model": "Невозможно преобразовать $1 в $2: несовместимые модели данных.",
+ "bad-target-model": "Невозможно преобразовать $1 в $2. У страниц несовместимые модели содержимого.",
"imagenocrossnamespace": "Невозможно дать файлу имя из другого пространства имён",
- "nonfile-cannot-move-to-file": "Невозможно переименовывать страницы в файлы",
+ "nonfile-cannot-move-to-file": "Невозможно переименовывать не-файловые страницы в файлы",
"imagetypemismatch": "Новое расширение файла не соответствует его типу",
"imageinvalidfilename": "Целевое имя файла ошибочно",
"fix-double-redirects": "Исправить перенаправления, указывающие на прежнее название",
"filedelete-archive-read-only": "Архивная директория «$1» не доступна для записи веб-серверу.",
"previousdiff": "← Предыдущая правка",
"nextdiff": "Следующая правка →",
- "mediawarning": "'''Внимание'''. Этот тип файла может содержать вредоносный программный код.\nПри его запуске ваша система может быть заражена.",
+ "mediawarning": "<strong>Внимание</strong>. Этот тип файла может содержать вредоносный программный код.\nПри его запуске ваша система может быть заражена.",
"imagemaxsize": "Ограничение на размер изображения:<br />''(для страницы описания файла)''",
"thumbsize": "Размер уменьшенной версии изображения:",
"widthheight": "$1 × $2",
"edit-error-long": "Ошибки:\n\n$1",
"revid": "версия $1",
"pageid": "ID страницы $1",
- "interfaceadmin-info": "$1\n\nПрава на редактирование общесайтных CSS/JS/JSON были недавно вынесены из права <code>editinterface</code>. Если вы не понимаете, почему вы наткнулись на эту ошибку, см. [[mw:MediaWiki_1.32/interface-admin]].",
+ "interfaceadmin-info": "$1\n\nПрава на редактирование общесайтных CSS/JS/JSON-файлов были недавно вынесены из права <code>editinterface</code>. Если вы не понимаете, почему вы наткнулись на эту ошибку, см. [[mw:MediaWiki_1.32/interface-admin]].",
"rawhtml-notallowed": "<html> теги могут быть использованы только в пределах обычных страниц.",
"gotointerwiki": "Покидаем {{grammar:accusative|{{SITENAME}}}}...",
"gotointerwiki-invalid": "Указан некорректный заголовок.",
"php-uploaddisabledtext": "Отпремање датотека је онемогућено у PHP-у.\nПроверите подешавања file_uploads.",
"uploadscripted": "Датотека садржи HTML или скриптни код који може бити погрешно протумачен од стране прегледача.",
"upload-scripted-pi-callback": "Датотека која садржи инструкције за обраду XML стилског облика се не може отпремити.",
- "upload-scripted-dtd": "Ð\9dе могÑ\83 да оÑ\82пÑ\80емим SVG даÑ\82оÑ\82еке које садрже нестандардну DTD декларацију.",
+ "upload-scripted-dtd": "Ð\9dиÑ\98е могÑ\83Ñ\9bе оÑ\82пÑ\80емаÑ\9aе SVG даÑ\82оÑ\82ека које садрже нестандардну DTD декларацију.",
"uploaded-script-svg": "Пронађен скриптни елеменат „$1“ у постављеној SVG датотеци.",
"uploaded-hostile-svg": "Пронађен небезбедан CSS у стилском елементу постављене SVG датотеке.",
"uploaded-event-handler-on-svg": "Није дозвољено постављање атрибута који контролишу догађаје <code>$1=\"$2\"</code> у SVG датотекама.",
"log": "Günlükler",
"logeventslist-submit": "Göster",
"logeventslist-more-filters": "Daha fazla süzgeç:",
+ "logeventslist-patrol-log": "Devriye günlüğü",
+ "logeventslist-tag-log": "Etiket günlüğü",
"all-logs-page": "Tüm genel günlükler",
"alllogstext": "{{SITENAME}} için mevcut tüm günlüklerin birleşik gösterimi.\nGünlük tipini, kullanıcı adını (büyük-küçük harf duyarlı), ya da etkilenen sayfayı (yine büyük-küçük harf duyarlı) seçerek görünümü daraltabilirsiniz.",
"logempty": "Kayıtlarda eşleşen bilgi yok.",
"dellogpage": "Silme günlüğü",
"dellogpagetext": "Aşağıda en son silme işlemlerinin bir listesi bulunmaktadır.",
"deletionlog": "silme günlüğü",
+ "logentry-create-create": "$1, $3 adlı sayfayı {{GENDER:$2|oluşturdu}}",
"reverted": "Önceki sürüm geri getirildi",
"deletecomment": "Neden:",
"deleteotherreason": "Diğer/ilave neden:",
"compare-rev1": "Беренче юрама",
"compare-rev2": "Икенче юрама",
"compare-submit": "Чагыштыр",
+ "permanentlink": "Даими сылтама",
"dberr-problems": "Гафу итегез! Сайтта техник кыенлыклар чыкты.",
"dberr-again": "Сәхифәне берничә минуттан соң яңартып карагыз.",
"dberr-info": "(Мәгълүматлар базасы серверы белән тоташырга мөмкин түгел: $1)",
"move-page-legend": "منتقلئ صفحہ",
"movepagetext": "درج ذیل فارم کے ذریعہ صفحہ کو نیا نام دیا جاسکتا ہے، اس کے ساتھ صفحہ کا تاریخچہ بھی منتقل ہو جائے گا اور نئے عنوان کے جانب قدیم عنوان کو رجوع مکرر کردیا جائے گا۔\n\nاس بات کا یقین کر لیں کہ [[Special:DoubleRedirects|دوہرے]] یا [[Special:BrokenRedirects|شکستہ رجوع مکررات]] موجود نہ ہوں۔\n\nنیز آپ اس بات کے بھی ذمہ دار ہیں کہ روابط انہیں جگہوں سے مربوط رہیں جہاں سے ابھی ہیں۔\n\nخیال رہے کہ اگر نئے عنوان سے کوئی صفحہ پہلے سے موجود ہو تو یہ صفحہ منتقل '''نہیں''' ہوگا، ہاں اگر صفحہ خالی ہو اور اس کا گذشتہ ترمیمی تاریخچہ موجود نہ ہو تو منتقل کیا جا سکتا ہے۔\nاس کا مطلب یہ ہے کہ آپ سے اگر غلطی ہوجائے تو آپ صفحہ کو اسی جگہ لوٹا سکتے ہیں، تاہم موجود صفحہ پر برتحریر (overwrite) نہیں کرسکتے۔\n\n<strong>اطلاع:</strong>\nکسی اہم اور مقبول صفحہ کی منتقلی، غیرمتوقع اور پریشان کن بھی ہی ہوسکتی ہے اس لیے منتقلی سے قبل یقین کرلیں کہ آپ اس کے منطقی نتائج سے باخبر ہیں۔",
"movepagetext-noredirectfixer": "درج ذیل فارم کے ذریعہ صفحہ کو نیا نام دیا جاسکتا ہے، اس کے ساتھ صفحہ کا تاریخچہ بھی منتقل ہو جائے گا اور نئے عنوان کے جانب قدیم عنوان کو رجوع مکرر کردیا جائے گا۔\n\nاس بات کا یقین کر لیں کہ [[Special:DoubleRedirects|دوہرے]] یا [[Special:BrokenRedirects|شکستہ رجوع مکررات]] موجود نہ ہوں۔\n\nنیز آپ اس بات کے بھی ذمہ دار ہیں کہ روابط انہیں جگہوں سے مربوط رہیں جہاں سے ابھی ہیں۔\n\nخیال رہے کہ اگر نئے عنوان سے کوئی صفحہ پہلے سے موجود ہو تو یہ صفحہ منتقل '''نہیں''' ہوگا، ہاں اگر صفحہ خالی ہو اور اس کا گذشتہ ترمیمی تاریخچہ موجود نہ ہو تو منتقل کیا جا سکتا ہے۔\nاس کا مطلب یہ ہے کہ آپ سے اگر غلطی ہوجائے تو آپ صفحہ کو اسی جگہ لوٹا سکتے ہیں، تاہم موجود صفحہ پر برتحریر (overwrite) نہیں کرسکتے۔\n\n<strong>اطلاع:</strong>\nکسی اہم اور مقبول صفحہ کی منتقلی، غیرمتوقع اور پریشان کن بھی ہی ہوسکتی ہے اس لیے منتقلی سے قبل یقین کرلیں کہ آپ اس کے منطقی نتائج سے باخبر ہیں۔",
- "movepagetalktext": "اگر آپ اس خانے کو نشان زد کریں تو ملحقہ تبادلہ خیال صفحہ بھی نئے عنوان کی جانب خودکار طور پر منتقل ہو جائے گا اگر اس عنوان کے تحت پہلے سے کوئی تبادلۂ خیال صفحہ موجود نہ ہو۔\n\nاس صورت میں آپ کو دستی طور پر اس صفحہ کو منتقل ضم کرنا ہوگا۔",
+ "movepagetalktext": "اگر آپ اس خانے کو نشان زد کریں تو ملحقہ تبادلہ خیال صفحہ بھی (بشرطیکہ موجود ہو) نئے عنوان کی جانب خودکار طور پر منتقل ہو جائے گا۔\n\nاگر آپ نے اس خانہ کو نشان زد نہیں کیا تو ملحقہ تبادلہ خیال صفحہ کو دستی طور پر منتقل کرکے ضم کرنا ہوگا۔",
"moveuserpage-warning": "<strong>انتباہ:</strong> آپ صارف صفحہ کو منتقل کر رہے ہیں۔ واضح رہے کہ اس منتقلی کے بعد صارف کا محض صفحہ منتقل ہوگا، اس کا صارف نام تبدیل <em>نہیں</em> ہوگا۔",
"movecategorypage-warning": "<strong>انتباہ:</strong> آپ زمرہ منتقل کر رہے ہیں۔ واضح رہے کہ منتقلی کے بعد اس زمرے میں موجود صفحات نئے زمرے میں منتقل <em>نہیں</em> ہونگے۔",
"movenologintext": "صفحہ کو منتقل کرنے کے لیے آپ کو اپنے کھاتے میں [[Special:UserLogin|داخل ہونا]] ضروری ہے۔",
"edit-error-long": "錯誤:\n\n$1",
"revid": "修訂 $1",
"pageid": "頁面 ID $1",
- "interfaceadmin-info": "$1\n\n編輯全站 CSS/JS/JSON 檔案的權限,剛剛已從 <code>editinterface</code> 權限裡拆分。若您不清楚為何會收到此錯誤,請查看 [[mw:MediaWiki_1.32/interface-admin]]。",
+ "interfaceadmin-info": "$1\n\n編輯全站 CSS/JS/JSON 檔案的權限,近期已從 <code>editinterface</code> 權限裡拆分。若您不清楚為何會收到此錯誤,請查看 [[mw:MediaWiki_1.32/interface-admin]]。",
"rawhtml-notallowed": "<html> 標籤無法在一般頁面之外使用。",
"gotointerwiki": "離開 {{SITENAME}}",
"gotointerwiki-invalid": "指定的標題無效。",
### Format of this file
#
# The top-level keys are module names (as registered in Resources.php).
-# The values of these keys are resource descriptors.
+# Each top-level key holds a resource descriptor that must have one of
+# the following `type` values:
#
-# In each resource descriptor object, the `src` and `integrity` keys are required.
+# - `tar`: For tarball archive (may be gzip-compressed).
+# - `file: For a plain file.
+# - `multi-file`: For multiple plain files.
#
-# * `src`: Full URL to a remote resource.
-# * `integrity`: Cryptographic hash used to verify the remote content.
-# Uses the "integrity metadata" format defined at <https://www.w3.org/TR/SRI/>.
-# * `dest`: An object mapping paths from the remote resource to a destination in
-# `/resources/lib/$module/`. The value may be omitted to indicate that
-# paths should be extracted to the destination directory itself.
+### Type tar
+#
+# The `src` and `integrity` keys are quired.
+#
+# * `src`: Full URL to thes remote resource.
+# * `integrity`: Cryptographic hash (integrity metadata format per <https://www.w3.org/TR/SRI/>).
+# * `dest`: An object mapping paths to files or directory from the remote resource to a destination
+# in the module directory. The value of key in dest may be omitted, which will extract the key
+# directly to the module directory.
+#
+### Type file
+#
+# The `src` and `integrity` keys are quired.
+#
+# * `src`: Full URL to thes remote resource.
+# * `integrity`: Cryptographic hash (integrity metadata format per <https://www.w3.org/TR/SRI/>).
+# * `dest`: The name of the file in the module directory. Default: Basename of URL.
+#
+### Type mult-file
+#
+# The `files` key is required.
+#
+# * `files`: An object mapping destination paths to an object containing `src` and `integrity`
+# keys.
oojs:
+ type: tar
src: https://registry.npmjs.org/oojs/-/oojs-2.2.2.tgz
integrity: sha256-ebgQW2EGrSkBCnDJBGqDpsBDjA3PMN/M8U5DyLHt9mw=
dest:
package/LICENSE-MIT:
package/README.md:
oojs-ui:
+ type: tar
src: https://registry.npmjs.org/oojs-ui/-/oojs-ui-0.28.0.tgz
integrity: sha384-j8bzlCPrfS4sca+U9JO9tdcewDlLlDlOVOsLn+Vqlcg5GU59vLSd7TVm4FiuTowy
dest:
package/dist/History.md:
package/dist/LICENSE-MIT:
package/dist/README.md:
+jquery:
+ type: file
+ src: https://code.jquery.com/jquery-3.2.1.js
+ # From https://code.jquery.com/jquery/
+ integrity: sha256-DZAnKJ/6XZ9si04Hgrsxu/8s717jcIzLy3oi35EouyE=
+ dest: jquery.js
+qunitjs:
+ type: multi-file
+ files:
+ qunit.js:
+ src: https://code.jquery.com/qunit/qunit-2.6.0.js
+ integrity: sha384-5O3bKbJBbAbxsqV+w/I1fcXgWJgbqM+hmYAPOE9aELSYpcTEsv48X8H+Hnq66V/9
+ qunit.css:
+ src: https://code.jquery.com/qunit/qunit-2.6.0.css
+ integrity: sha384-8vDvsmsuiD7tCQyC+pW2LOwDDgsluGsIPeCqr3rHsDSF2k4WpmfvKKxcgSV5zPai
class ManageForeignResources extends Maintenance {
private $defaultAlgo = 'sha384';
private $tmpParentDir;
+ private $action;
+ private $failAfterOutput = false;
public function __construct() {
global $IP;
This helps developers to download, verify and update local copies of upstream
libraries registered as ResourceLoader modules. See also foreign-resources.yaml.
-For sources that don't publish an integrity hash, leave the value empty at
-first, and run this script with --make-sri to compute the hashes.
+For sources that don't publish an integrity hash, omit "integrity" (or leave empty)
+and run the "make-sri" action to compute the missing hashes.
This script runs in dry mode by default. Use --update to actually change, remove,
or add files to /resources/lib/.
TEXT
);
+ $this->addArg( 'action', 'One of "update", "verify" or "make-sri"', true );
$this->addArg( 'module', 'Name of a single module (Default: all)', false );
- $this->addOption( 'update', ' resources/lib/ missing integrity metadata' );
- $this->addOption( 'make-sri', 'Compute missing integrity metadata' );
- $this->addOption( 'verbose', 'Be verbose' );
+ $this->addOption( 'verbose', 'Be verbose', false, false, 'v' );
// Use a directory in $IP instead of wfTempDir() because
// PHP's rename() does not work across file systems.
public function execute() {
global $IP;
- $module = $this->getArg();
- $makeSRI = $this->hasOption( 'make-sri' );
+ $this->action = $this->getArg( 0 );
+ if ( !in_array( $this->action, [ 'update', 'verify', 'make-sri' ] ) ) {
+ $this->fatalError( "Invalid action argument." );
+ }
$registry = $this->parseBasicYaml(
file_get_contents( __DIR__ . '/foreign-resources.yaml' )
);
+ $module = $this->getArg( 1, 'all' );
foreach ( $registry as $moduleName => $info ) {
- if ( $module !== null && $moduleName !== $module ) {
+ if ( $module !== 'all' && $moduleName !== $module ) {
continue;
}
$this->verbose( "\n### {$moduleName}\n\n" );
+ $destDir = "{$IP}/resources/lib/$moduleName";
- // Validate required keys
- $info += [ 'src' => null, 'integrity' => null, 'dest' => null ];
- if ( $info['src'] === null ) {
- $this->fatalError( "Module '$moduleName' must have a 'src' key." );
+ if ( $this->action === 'update' ) {
+ $this->output( "... updating '{$moduleName}'\n" );
+ $this->verbose( "... emptying /resources/lib/$moduleName\n" );
+ wfRecursiveRemoveDir( $destDir );
+ } elseif ( $this->action === 'verify' ) {
+ $this->output( "... verifying '{$moduleName}'\n" );
+ } else {
+ $this->output( "... checking '{$moduleName}'\n" );
}
- $integrity = is_string( $info['integrity'] ) ? $info['integrity'] : $makeSRI;
- if ( $integrity === false ) {
- $this->fatalError( "Module '$moduleName' must have an 'integrity' key." );
+
+ $this->verbose( "... preparing {$this->tmpParentDir}\n" );
+ wfRecursiveRemoveDir( $this->tmpParentDir );
+ if ( !wfMkdirParents( $this->tmpParentDir ) ) {
+ $this->fatalError( "Unable to create {$this->tmpParentDir}" );
}
- // Download the resource
- $data = Http::get( $info['src'], [ 'followRedirects' => false ] );
- if ( $data === false ) {
- $this->fatalError( "Failed to download resource for '$moduleName'." );
+ if ( !isset( $info['type'] ) ) {
+ $this->fatalError( "Module '$moduleName' must have a 'type' key." );
+ }
+ switch ( $info['type'] ) {
+ case 'tar':
+ $this->handleTypeTar( $moduleName, $destDir, $info );
+ break;
+ case 'file':
+ $this->handleTypeFile( $moduleName, $destDir, $info );
+ break;
+ case 'multi-file':
+ $this->handleTypeMultiFile( $moduleName, $destDir, $info );
+ break;
+ default:
+ $this->fatalError( "Unknown type '{$info['type']}' for '$moduleName'" );
}
+ }
- // Validate integrity metadata
- $this->output( "... checking integrity of '{$moduleName}'\n" );
- $algo = $integrity === true ? $this->defaultAlgo : explode( '-', $integrity )[0];
- $actualIntegrity = $algo . '-' . base64_encode( hash( $algo, $data, true ) );
- if ( $integrity === true ) {
- $this->output( "Integrity for '{$moduleName}':\n\t${actualIntegrity}\n" );
- continue;
- } elseif ( $integrity !== $actualIntegrity ) {
- $this->fatalError( "Integrity check failed for '{$moduleName}:\n" .
- "Expected: {$integrity}\n" .
- "Actual: {$actualIntegrity}"
+ $this->cleanUp();
+ $this->output( "\nDone!\n" );
+ if ( $this->failAfterOutput ) {
+ // The verify mode should check all modules/files and fail after, not during.
+ return false;
+ }
+ }
+
+ private function fetch( $src, $integrity ) {
+ $data = Http::get( $src, [ 'followRedirects' => false ] );
+ if ( $data === false ) {
+ $this->fatalError( "Failed to download resource at {$src}" );
+ }
+ $algo = $integrity === null ? $this->defaultAlgo : explode( '-', $integrity )[0];
+ $actualIntegrity = $algo . '-' . base64_encode( hash( $algo, $data, true ) );
+ if ( $integrity === $actualIntegrity ) {
+ $this->verbose( "... passed integrity check for {$src}\n" );
+ } else {
+ if ( $this->action === 'make-sri' ) {
+ $this->output( "Integrity for {$src}\n\tintegrity: ${actualIntegrity}\n" );
+ } else {
+ $this->fatalError( "Integrity check failed for {$src}\n" .
+ "\tExpected: {$integrity}\n" .
+ "\tActual: {$actualIntegrity}"
);
}
-
- // Determine destination
- $destDir = "{$IP}/resources/lib/$moduleName";
- $this->output( "... extracting files for '{$moduleName}'\n" );
- $this->handleTypeTar( $moduleName, $data, $destDir, $info );
}
+ return $data;
+ }
- // Clean up
- wfRecursiveRemoveDir( $this->tmpParentDir );
- $this->output( "\nDone!\n" );
+ private function handleTypeFile( $moduleName, $destDir, array $info ) {
+ if ( !isset( $info['src'] ) ) {
+ $this->fatalError( "Module '$moduleName' must have a 'src' key." );
+ }
+ $data = $this->fetch( $info['src'], $info['integrity'] ?? null );
+ $dest = $info['dest'] ?? basename( $info['src'] );
+ $path = "$destDir/$dest";
+ if ( $this->action === 'verify' && sha1_file( $path ) !== sha1( $data ) ) {
+ $this->fatalError( "File for '$moduleName' is different." );
+ } elseif ( $this->action === 'update' ) {
+ wfMkdirParents( $destDir );
+ file_put_contents( "$destDir/$dest", $data );
+ }
}
- private function handleTypeTar( $moduleName, $data, $destDir, array $info ) {
- global $IP;
- wfRecursiveRemoveDir( $this->tmpParentDir );
- if ( !wfMkdirParents( $this->tmpParentDir ) ) {
- $this->fatalError( "Unable to create {$this->tmpParentDir}" );
+ private function handleTypeMultiFile( $moduleName, $destDir, array $info ) {
+ if ( !isset( $info['files'] ) ) {
+ $this->fatalError( "Module '$moduleName' must have a 'files' key." );
}
+ foreach ( $info['files'] as $dest => $file ) {
+ if ( !isset( $file['src'] ) ) {
+ $this->fatalError( "Module '$moduleName' file '$dest' must have a 'src' key." );
+ }
+ $data = $this->fetch( $file['src'], $file['integrity'] ?? null );
+ $path = "$destDir/$dest";
+ if ( $this->action === 'verify' && sha1_file( $path ) !== sha1( $data ) ) {
+ $this->fatalError( "File '$dest' for '$moduleName' is different." );
+ } elseif ( $this->action === 'update' ) {
+ wfMkdirParents( $destDir );
+ file_put_contents( "$destDir/$dest", $data );
+ }
+ }
+ }
- // Write resource to temporary file and open it
+ private function handleTypeTar( $moduleName, $destDir, array $info ) {
+ $info += [ 'src' => null, 'integrity' => null, 'dest' => null ];
+ if ( $info['src'] === null ) {
+ $this->fatalError( "Module '$moduleName' must have a 'src' key." );
+ }
+ // Download the resource to a temporary file and open it
+ $data = $this->fetch( $info['src'], $info['integrity' ] );
$tmpFile = "{$this->tmpParentDir}/$moduleName.tar";
$this->verbose( "... writing '$moduleName' src to $tmpFile\n" );
file_put_contents( $tmpFile, $data );
unset( $data, $p );
if ( $info['dest'] === null ) {
- // Replace the entire directory as-is
- if ( !$this->hasOption( 'update' ) ) {
- $this->output( "[dry run] Would replace /resources/lib/$moduleName\n" );
- } else {
- wfRecursiveRemoveDir( $destDir );
- if ( !rename( $tmpDir, $destDir ) ) {
- $this->fatalError( "Could not move $destDir to $tmpDir." );
- }
- }
- return;
- }
-
- // Create and/or empty the destination
- if ( !$this->hasOption( 'update' ) ) {
- $this->output( "... [dry run] would empty /resources/lib/$moduleName\n" );
+ // Default: Replace the entire directory
+ $toCopy = [ $tmpDir => $destDir ];
} else {
- wfRecursiveRemoveDir( $destDir );
- wfMkdirParents( $destDir );
- }
-
- // Expand and normalise the 'dest' entries
- $toCopy = [];
- foreach ( $info['dest'] as $fromSubPath => $toSubPath ) {
- // Use glob() to expand wildcards and check existence
- $fromPaths = glob( "{$tmpDir}/{$fromSubPath}", GLOB_BRACE );
- if ( !$fromPaths ) {
- $this->fatalError( "Path '$fromSubPath' of '$moduleName' not found." );
- }
- foreach ( $fromPaths as $fromPath ) {
- $toCopy[$fromPath] = $toSubPath === null
- ? "$destDir/" . basename( $fromPath )
- : "$destDir/$toSubPath/" . basename( $fromPath );
+ // Expand and normalise the 'dest' entries
+ $toCopy = [];
+ foreach ( $info['dest'] as $fromSubPath => $toSubPath ) {
+ // Use glob() to expand wildcards and check existence
+ $fromPaths = glob( "{$tmpDir}/{$fromSubPath}", GLOB_BRACE );
+ if ( !$fromPaths ) {
+ $this->fatalError( "Path '$fromSubPath' of '$moduleName' not found." );
+ }
+ foreach ( $fromPaths as $fromPath ) {
+ $toCopy[$fromPath] = $toSubPath === null
+ ? "$destDir/" . basename( $fromPath )
+ : "$destDir/$toSubPath/" . basename( $fromPath );
+ }
}
}
foreach ( $toCopy as $from => $to ) {
- if ( !$this->hasOption( 'update' ) ) {
- $shortFrom = strtr( $from, [ "$tmpDir/" => '' ] );
- $shortTo = strtr( $to, [ "$IP/" => '' ] );
- $this->output( "... [dry run] would move $shortFrom to $shortTo\n" );
- } else {
+ if ( $this->action === 'verify' ) {
+ $this->verbose( "... verifying $to\n" );
+ if ( is_dir( $from ) ) {
+ $rii = new RecursiveIteratorIterator( new RecursiveDirectoryIterator(
+ $from,
+ RecursiveDirectoryIterator::SKIP_DOTS
+ ) );
+ foreach ( $rii as $file ) {
+ $remote = $file->getPathname();
+ $local = strtr( $remote, [ $from => $to ] );
+ if ( sha1_file( $remote ) !== sha1_file( $local ) ) {
+ $this->error( "File '$local' is different." );
+ $this->failAfterOutput = true;
+ }
+ }
+ } elseif ( sha1_file( $from ) !== sha1_file( $to ) ) {
+ $this->error( "File '$to' is different." );
+ $this->failAfterOutput = true;
+ }
+ } elseif ( $this->action === 'update' ) {
$this->verbose( "... moving $from to $to\n" );
wfMkdirParents( dirname( $to ) );
if ( !rename( $from, $to ) ) {
}
}
+ private function cleanUp() {
+ wfRecursiveRemoveDir( $this->tmpParentDir );
+ }
+
+ protected function fatalError( $msg, $exitCode = 1 ) {
+ $this->cleanUp();
+ parent::fatalError( $msg, $exitCode );
+ }
+
/**
* Basic YAML parser.
*
mwLoaderTrack = mw.track,
trackCallbacks = $.Callbacks( 'memory' ),
trackHandlers = [],
- hasOwn = Object.prototype.hasOwnProperty,
queue;
/**
* @class mw.hook
*/
mw.hook = ( function () {
- var lists = {};
+ var lists = Object.create( null );
/**
* Create an instance of mw.hook.
* @return {mw.hook}
*/
return function ( name ) {
- var list = hasOwn.call( lists, name ) ?
- lists[ name ] :
- lists[ name ] = $.Callbacks( 'memory' );
+ var list = lists[ name ] || ( lists[ name ] = $.Callbacks( 'memory' ) );
return {
/**
'use strict';
var notification,
- // The #mw-notification-area div that all notifications are contained inside.
+ // The .mw-notification-area div that all notifications are contained inside.
$area,
// Number of open notification boxes at any time
openNotificationCount = 0,
// Write to the DOM:
// Prepend the notification area to the content area and save its object.
+ // The ID attribute here is deprecated.
$area = $( '<div id="mw-notification-area" class="mw-notification-area mw-notification-area-layout"></div>' )
// Pause auto-hide timers when the mouse is in the notification area.
.on( {
* @class
*/
function StringSet() {
- this.set = {};
+ this.set = Object.create( null );
}
StringSet.prototype.add = function ( value ) {
this.set[ value ] = true;
};
StringSet.prototype.has = function ( value ) {
- return hasOwn.call( this.set, value );
+ return value in this.set;
};
return StringSet;
}() );
* copied in one direction only. Changes to globals do not reflect in the map.
*/
function Map( global ) {
- this.values = {};
+ this.values = Object.create( null );
if ( global === true ) {
// Override #set to also set the global variable
this.set = function ( selection, value ) {
results = {};
for ( i = 0; i < selection.length; i++ ) {
if ( typeof selection[ i ] === 'string' ) {
- results[ selection[ i ] ] = hasOwn.call( this.values, selection[ i ] ) ?
+ results[ selection[ i ] ] = selection[ i ] in this.values ?
this.values[ selection[ i ] ] :
fallback;
}
}
if ( typeof selection === 'string' ) {
- return hasOwn.call( this.values, selection ) ?
+ return selection in this.values ?
this.values[ selection ] :
fallback;
}
var i;
if ( Array.isArray( selection ) ) {
for ( i = 0; i < selection.length; i++ ) {
- if ( typeof selection[ i ] !== 'string' || !hasOwn.call( this.values, selection[ i ] ) ) {
+ if ( typeof selection[ i ] !== 'string' || !( selection[ i ] in this.values ) ) {
return false;
}
}
return true;
}
- return typeof selection === 'string' && hasOwn.call( this.values, selection );
+ return typeof selection === 'string' && selection in this.values;
}
};
// This embeds mediawiki.js, which defines 'mw' and 'mw.loader'.
$CODE.defineLoader();
- mw.requestIdleCallback( startUp );
+ startUp();
}() );
"PhanUndeclaredMethod",
// approximate error count: 1224
"PhanUndeclaredProperty",
- // approximate error count: 3
- "PhanUndeclaredStaticMethod",
// approximate error count: 58
"PhanUndeclaredVariableDim",
],
*/
private $loggers = [];
+ /**
+ * @var LoggerInterface
+ */
+ private $testLogger;
+
/**
* Table name prefixes. Oracle likes it shorter.
*/
$this->backupGlobals = false;
$this->backupStaticAttributes = false;
+ $this->testLogger = self::getTestLogger();
+ }
+
+ private static function getTestLogger() {
+ return LoggerFactory::getInstance( 'tests-phpunit' );
}
public function __destruct() {
if ( !self::$dbSetup || $this->needsDB() ) {
// set up a DB connection for this test to use
+ $this->testLogger->info( "Setting up DB for " . $this->toString() );
self::$useTemporaryTables = !$this->getCliArg( 'use-normal-tables' );
self::$reuseDB = $this->getCliArg( 'reuse-db' );
$needsResetDB = true;
}
+ $this->testLogger->info( "Starting test " . $this->toString() );
parent::run( $result );
+ $this->testLogger->info( "Finished test " . $this->toString() );
if ( $needsResetDB ) {
+ $this->testLogger->info( "Resetting DB" );
$this->resetDB( $this->db, $this->tablesUsed );
}
}
}
// Check for unsafe queries
if ( $this->db->getType() === 'mysql' ) {
- $this->db->query( "SET sql_mode = 'STRICT_ALL_TABLES'" );
+ $this->db->query( "SET sql_mode = 'STRICT_ALL_TABLES'", __METHOD__ );
}
}
$this->db->rollback( __METHOD__, 'flush' );
}
if ( $this->db->getType() === 'mysql' ) {
- $this->db->query( "SET sql_mode = " . $this->db->addQuotes( $wgSQLMode ) );
+ $this->db->query( "SET sql_mode = " . $this->db->addQuotes( $wgSQLMode ),
+ __METHOD__ );
}
}
* @since 1.32
*/
protected function addCoreDBData() {
+ $this->testLogger->info( __METHOD__ );
if ( $this->db->getType() == 'oracle' ) {
# Insert 0 user to prevent FK violations
# Anonymous user
// Assuming this isn't needed for External Store database, and not sure if the procedure
// would be available there.
if ( $db->getType() == 'oracle' ) {
- $db->query( 'BEGIN FILL_WIKI_INFO; END;' );
+ $db->query( 'BEGIN FILL_WIKI_INFO; END;', __METHOD__ );
}
Hooks::run( 'UnitTestsAfterDatabaseSetup', [ $db, $prefix ] );
$this->tablesUsed += $this->getMcrTablesToReset();
- $this->setMwGlobals(
- 'wgMultiContentRevisionSchemaMigrationStage',
- $this->getMcrMigrationStage()
- );
-
- $this->setMwGlobals(
- 'wgContentHandlerUseDB',
- $this->getContentHandlerUseDB()
- );
+ $this->setMwGlobals( [
+ 'wgMultiContentRevisionSchemaMigrationStage' => $this->getMcrMigrationStage(),
+ 'wgContentHandlerUseDB' => $this->getContentHandlerUseDB(),
+ 'wgCommentTableSchemaMigrationStage' => MIGRATION_OLD,
+ 'wgActorTableSchemaMigrationStage' => MIGRATION_OLD,
+ ] );
$this->overrideMwServices();
}