Merge "resourceloader: Clean up CSSMinTest"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Mon, 16 Apr 2018 22:58:47 +0000 (22:58 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Mon, 16 Apr 2018 22:58:47 +0000 (22:58 +0000)
22 files changed:
RELEASE-NOTES-1.31
composer.json
includes/GlobalFunctions.php
includes/SiteConfiguration.php
includes/Title.php
includes/libs/rdbms/loadbalancer/ILoadBalancer.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/libs/rdbms/loadmonitor/LoadMonitor.php
includes/shell/CommandFactory.php
includes/shell/Shell.php
languages/i18n/be-tarask.json
languages/i18n/es.json
languages/i18n/fi.json
languages/i18n/fr.json
languages/i18n/io.json
languages/i18n/it.json
languages/i18n/ru.json
languages/i18n/tr.json
languages/i18n/zh-hant.json
maintenance/cleanupPreferences.php
tests/phpunit/includes/db/LoadBalancerTest.php
tests/phpunit/includes/shell/ShellTest.php

index 0ae6a83..3de0402 100644 (file)
@@ -255,6 +255,8 @@ changes to languages because of Phabricator reports.
   MWTidy::checkErrors() and its callee TidyDriverBase::validate() are removed, as are
   MediaWikiTestCase::assertValidHtmlSnippet() and ::assertValidHtmlDocument(). The
   $wgValidateAllHtml configuration option is removed and will be ignored.
+* Execution of external programs using MediaWiki\Shell\Command now applies RESTRICT_DEFAULT
+  Firejail restriction by default.
 
 === Deprecations in 1.31 ===
 * The Revision class was deprecated in favor of RevisionStore, BlobStore, and
@@ -343,6 +345,8 @@ changes to languages because of Phabricator reports.
 * Wikimedia\Rdbms\SavepointPostgres is deprecated.
 * The DO_MAINTENANCE constant is deprecated. RUN_MAINTENANCE_IF_MAIN should be
   used instead.
+* The function wfShellWikiCmd() has been deprecated, use
+  MediaWiki\Shell::makeScriptCommand().
 
 === Other changes in 1.31 ===
 * Browser support for Internet Explorer 10 was lowered from Grade A to Grade C.
index 321a941..313bbd9 100644 (file)
@@ -43,7 +43,7 @@
                "wikimedia/remex-html": "1.0.3",
                "wikimedia/running-stat": "1.2.1",
                "wikimedia/scoped-callback": "1.0.0",
-               "wikimedia/utfnormal": "1.1.0",
+               "wikimedia/utfnormal": "2.0.0",
                "wikimedia/timestamp": "1.0.0",
                "wikimedia/wait-condition-loop": "1.0.1",
                "wikimedia/wrappedstring": "2.3.0",
index 513f593..3dfe12e 100644 (file)
@@ -2327,6 +2327,8 @@ function wfShellExec( $cmd, &$retval = null, $environ = [],
                        ->limits( $limits )
                        ->includeStderr( $includeStderr )
                        ->profileMethod( $profileMethod )
+                       // For b/c
+                       ->restrict( Shell::RESTRICT_NONE )
                        ->execute();
        } catch ( ProcOpenError $ex ) {
                $retval = -1;
@@ -2377,6 +2379,8 @@ function wfInitShellLocale() {
  * Note that $parameters should be a flat array and an option with an argument
  * should consist of two consecutive items in the array (do not use "--option value").
  *
+ * @deprecated since 1.31, use Shell::makeScriptCommand()
+ *
  * @param string $script MediaWiki cli script path
  * @param array $parameters Arguments and options to the script
  * @param array $options Associative array of options:
index 2d1d961..6bd179a 100644 (file)
@@ -20,6 +20,8 @@
  * @file
  */
 
+use MediaWiki\Shell\Shell;
+
 /**
  * This is a class for holding configuration settings, particularly for
  * multi-wiki sites.
@@ -546,19 +548,21 @@ class SiteConfiguration {
                        } else {
                                $this->cfgCache[$wiki] = [];
                        }
-                       $retVal = 1;
-                       $cmd = wfShellWikiCmd(
+                       $result = Shell::makeScriptCommand(
                                "$IP/maintenance/getConfiguration.php",
                                [
                                        '--wiki', $wiki,
                                        '--settings', implode( ' ', $settings ),
-                                       '--format', 'PHP'
+                                       '--format', 'PHP',
                                ]
-                       );
-                       // ulimit5.sh breaks this call
-                       $data = trim( wfShellExec( $cmd, $retVal, [], [ 'memory' => 0, 'filesize' => 0 ] ) );
-                       if ( $retVal != 0 || !strlen( $data ) ) {
-                               throw new MWException( "Failed to run getConfiguration.php." );
+                       )
+                               // limit.sh breaks this call
+                               ->limits( [ 'memory' => 0, 'filesize' => 0 ] )
+                               ->execute();
+
+                       $data = trim( $result->getStdout() );
+                       if ( $result->getExitCode() != 0 || !strlen( $data ) ) {
+                               throw new MWException( "Failed to run getConfiguration.php: {$result->getStdout()}" );
                        }
                        $res = unserialize( $data );
                        if ( !is_array( $res ) ) {
index 58e6885..b771477 100644 (file)
@@ -1027,12 +1027,11 @@ class Title implements LinkTarget {
         */
        public function getNsText() {
                if ( $this->isExternal() ) {
-                       // This probably shouldn't even happen,
-                       // but for interwiki transclusion it sometimes does.
-                       // Use the canonical namespaces if possible to try to
-                       // resolve a foreign namespace.
-                       if ( MWNamespace::exists( $this->mNamespace ) ) {
-                               return MWNamespace::getCanonicalName( $this->mNamespace );
+                       // This probably shouldn't even happen, except for interwiki transclusion.
+                       // If possible, use the canonical name for the foreign namespace.
+                       $nsText = MWNamespace::getCanonicalName( $this->mNamespace );
+                       if ( $nsText !== false ) {
+                               return $nsText;
                        }
                }
 
@@ -4795,14 +4794,12 @@ class Title implements LinkTarget {
         */
        public function getNamespaceKey( $prepend = 'nstab-' ) {
                global $wgContLang;
-               // Gets the subject namespace if this title
-               $namespace = MWNamespace::getSubject( $this->getNamespace() );
-               // Checks if canonical namespace name exists for namespace
-               if ( MWNamespace::exists( $this->getNamespace() ) ) {
-                       // Uses canonical namespace name
-                       $namespaceKey = MWNamespace::getCanonicalName( $namespace );
-               } else {
-                       // Uses text of namespace
+               // Gets the subject namespace of this title
+               $subjectNS = MWNamespace::getSubject( $this->getNamespace() );
+               // Prefer canonical namespace name for HTML IDs
+               $namespaceKey = MWNamespace::getCanonicalName( $subjectNS );
+               if ( $namespaceKey === false ) {
+                       // Fallback to localised text
                        $namespaceKey = $this->getSubjectNsText();
                }
                // Makes namespace key lowercase
index a699b23..2f493c7 100644 (file)
@@ -167,10 +167,13 @@ interface ILoadBalancer {
        /**
         * Get any open connection to a given server index, local or foreign
         *
+        * Use CONN_TRX_AUTOCOMMIT to only look for connections opened with that flag
+        *
         * @param int $i Server index or DB_MASTER/DB_REPLICA
+        * @param int $flags Bitfield of CONN_* class constants
         * @return Database|bool False if no such connection is open
         */
-       public function getAnyOpenConnection( $i );
+       public function getAnyOpenConnection( $i, $flags = 0 );
 
        /**
         * Get a connection handle by server index
index e2d3b33..be7d021 100644 (file)
@@ -566,16 +566,17 @@ class LoadBalancer implements ILoadBalancer {
                }
        }
 
-       /**
-        * @param int $i
-        * @return IDatabase|bool
-        */
-       public function getAnyOpenConnection( $i ) {
+       public function getAnyOpenConnection( $i, $flags = 0 ) {
+               $autocommit = ( ( $flags & self::CONN_TRX_AUTOCOMMIT ) == self::CONN_TRX_AUTOCOMMIT );
                foreach ( $this->conns as $connsByServer ) {
-                       if ( !empty( $connsByServer[$i] ) ) {
-                               /** @var IDatabase[] $serverConns */
-                               $serverConns = $connsByServer[$i];
-                               return reset( $serverConns );
+                       if ( !isset( $connsByServer[$i] ) ) {
+                               continue;
+                       }
+
+                       foreach ( $connsByServer[$i] as $conn ) {
+                               if ( !$autocommit || $conn->getLBInfo( 'autoCommitOnly' ) ) {
+                                       return $conn;
+                               }
                        }
                }
 
@@ -590,7 +591,7 @@ class LoadBalancer implements ILoadBalancer {
         * @return bool
         */
        protected function doWait( $index, $open = false, $timeout = null ) {
-               $timeout = max( 1, $timeout ?: $this->waitTimeout );
+               $timeout = max( 1, intval( $timeout ?: $this->waitTimeout ) );
 
                // Check if we already know that the DB has reached this point
                $server = $this->getServerName( $index );
index 3372839..c7f807a 100644 (file)
@@ -156,13 +156,14 @@ class LoadMonitor implements ILoadMonitor {
                                continue;
                        }
 
-                       $conn = $this->parent->getAnyOpenConnection( $i );
-                       if ( $conn && !$conn->trxLevel() ) {
-                               # Handles with open transactions are avoided since they might be subject
-                               # to REPEATABLE-READ snapshots, which could affect the lag estimate query.
+                       # Handles with open transactions are avoided since they might be subject
+                       # to REPEATABLE-READ snapshots, which could affect the lag estimate query.
+                       $flags = ILoadBalancer::CONN_TRX_AUTOCOMMIT;
+                       $conn = $this->parent->getAnyOpenConnection( $i, $flags );
+                       if ( $conn ) {
                                $close = false; // already open
                        } else {
-                               $conn = $this->parent->openConnection( $i, '' );
+                               $conn = $this->parent->openConnection( $i, ILoadBalancer::DOMAIN_ANY, $flags );
                                $close = true; // new connection
                        }
 
index 78f1d80..b4b9b92 100644 (file)
@@ -100,6 +100,7 @@ class CommandFactory {
        public function create() {
                if ( $this->restrictionMethod === 'firejail' ) {
                        $command = new FirejailCommand( $this->findFirejail() );
+                       $command->restrict( Shell::RESTRICT_DEFAULT );
                } else {
                        $command = new Command();
                }
index d57bf4f..742e142 100644 (file)
@@ -22,6 +22,7 @@
 
 namespace MediaWiki\Shell;
 
+use Hooks;
 use MediaWiki\MediaWikiServices;
 
 /**
@@ -100,6 +101,13 @@ class Shell {
         */
        const NO_LOCALSETTINGS = 32;
 
+       /**
+        * Don't apply any restrictions
+        *
+        * @since 1.31
+        */
+       const RESTRICT_NONE = 0;
+
        /**
         * Returns a new instance of Command class
         *
@@ -212,4 +220,32 @@ class Shell {
                }
                return $retVal;
        }
+
+       /**
+        * Generate a Command object to run a MediaWiki CLI script.
+        * Note that $parameters should be a flat array and an option with an argument
+        * should consist of two consecutive items in the array (do not use "--option value").
+        *
+        * @param string $script MediaWiki CLI script with full path
+        * @param string[] $parameters Arguments and options to the script
+        * @param array $options Associative array of options:
+        *     'php': The path to the php executable
+        *     'wrapper': Path to a PHP wrapper to handle the maintenance script
+        * @return Command
+        */
+       public static function makeScriptCommand( $script, $parameters, $options = [] ) {
+               global $wgPhpCli;
+               // Give site config file a chance to run the script in a wrapper.
+               // The caller may likely want to call wfBasename() on $script.
+               Hooks::run( 'wfShellWikiCmd', [ &$script, &$parameters, &$options ] );
+               $cmd = isset( $options['php'] ) ? [ $options['php'] ] : [ $wgPhpCli ];
+               if ( isset( $options['wrapper'] ) ) {
+                       $cmd[] = $options['wrapper'];
+               }
+               $cmd[] = $script;
+
+               return self::command( $cmd )
+                       ->params( $parameters )
+                       ->restrict( self::RESTRICT_DEFAULT & ~self::NO_LOCALSETTINGS );
+       }
 }
index da1d85c..c66fbc1 100644 (file)
        "rcfilters-filter-humans-label": "Чалавек (ня робат)",
        "rcfilters-filter-humans-description": "Праўкі, зробленыя людзьмі.",
        "rcfilters-filtergroup-reviewstatus": "Статус праверкі",
+       "rcfilters-filter-reviewstatus-unpatrolled-description": "Рэдагаваньні, якія не былі пазначаныя як патруляваныя, уручную ці аўтаматычна.",
        "rcfilters-filter-reviewstatus-unpatrolled-label": "Неправераныя",
        "rcfilters-filtergroup-significance": "Значэньне",
        "rcfilters-filter-minor-label": "Дробныя праўкі",
index 21e7816..b7cf815 100644 (file)
        "version-specialpages": "Páginas especiales",
        "version-parserhooks": "Extensiones del analizador sintáctico",
        "version-variables": "Variables",
+       "version-editors": "Editores",
        "version-antispam": "Prevención de spam",
        "version-other": "Otro",
        "version-mediahandlers": "Manejadores multimedia",
index 01a9fcf..7ccb363 100644 (file)
@@ -56,7 +56,8 @@
                        "Tumm1",
                        "4shadoww",
                        "Pahkiqaz",
-                       "Rueter"
+                       "Rueter",
+                       "Kyykaarme"
                ]
        },
        "tog-underline": "Linkkien alleviivaus:",
        "upload-tryagain": "Lähetä muutettu tiedostokuvaus",
        "upload-tryagain-nostash": "Lähetä uudelleenlähetetty tiedosto ja muokattu kuvaus",
        "uploadnologin": "Et ole kirjautunut sisään",
-       "uploadnologintext": "Ole hyvä ja $1, jotta voit tallentaa tiedostoja.",
+       "uploadnologintext": "Sinun pitää $1, jotta voit tallentaa tiedostoja.",
        "upload_directory_missing": "Tallennushakemisto $1 puuttuu, eikä palvelin pysty luomaan sitä.",
        "upload_directory_read_only": "Palvelimella ei ole kirjoitusoikeuksia tallennushakemistoon $1.",
        "uploaderror": "Tallennusvirhe",
index 39cf7d0..2c38e61 100644 (file)
        "version-specialpages": "Pages spéciales",
        "version-parserhooks": "Greffons de l'analyseur syntaxique",
        "version-variables": "Variables",
+       "version-editors": "Contributeurs",
        "version-antispam": "Prévention du pollupostage",
        "version-other": "Divers",
        "version-mediahandlers": "Manipulateurs de médias",
index 7389382..731baad 100644 (file)
        "noarticletext": "Til nun ne existas texto en ica pagino.\nVu povas [[Special:Search/{{PAGENAME}}|serchar ica titulo]] en altra pagini, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} serchar en la relata registri], o [{{fullurl:{{FULLPAGENAME}}|action=edit}} redaktar ica pagino]</span>.",
        "noarticletext-nopermission": "Til nun ne existas texto en ica pagino.\nVu povas [[Special:Search/{{PAGENAME}}|serchar ica titulo]] en altra pagini, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} serchar en la relata registri], o [{{fullurl:{{FULLPAGENAME}}|action=edit}} redaktar ica pagino]</span>, tamen vu ne havas permiso por krear ica pagino.",
        "userpage-userdoesnotexist": "Uzeronomo \"$1\" ne registragesis.\nVoluntez konfirmar se vu volas krear/redaktar ica pagino.",
-       "userpage-userdoesnotexist-view": "Uzeronomo \"$1\" no registragesis.",
+       "userpage-userdoesnotexist-view": "L'uzeronomo \"$1\" ne enrejistresis.",
        "clearyourcache": "<strong>Atencez:</strong> Pos registragar, vu probable mustas renovigar la tempala-magazino di vua navigilo por vidar la chanji.\n* <strong>Firefox / Safari:</strong>Tenez <em>Shift</em> kliktante <em>Reload</em>, o presez sive <em>Ctrl-F5</em> sive <em>Ctrl-R</em> (<em>⌘-R</em> ye Mac);\n* <strong>Google Chrome:</strong> Press <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> en komputeri Mac)\n* <strong>Internet Explorer:</strong> Tenez <em>Ctrl</em> kliktante <em>Refresh</em>, o presez <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Irez a <em>Menu → Settings</em> (<em>Opera → Preferences</em> ye komputeri Mac) e pose a <em>Privacy & security → Clear browsing data → Cached images and files</em>.",
        "usercsspreview": "'''Memorez ke vu nur previdas vua uzero-CSS.'''\n'''Ol ne registragesis ankore!'''",
        "userjspreview": "'''Memorez ke vu nur previdas vua JavaScript di uzero. Ol ne registragesis ankore!'''",
        "rcfilters-show-new-changes": "Videz la maxim recenta chanji",
        "rcfilters-search-placeholder": "Filtrar la modifikuri (uzez la menuo o serchez segun la nomo dil filtrilo)",
        "rcfilters-filter-editsbyself-label": "Vua modifikuri",
+       "rcfilters-filter-user-experience-level-registered-label": "Enrejistrita",
        "rcfilters-filter-user-experience-level-unregistered-label": "Sen registro",
        "rcfilters-filter-user-experience-level-unregistered-description": "Redakteri qui ne facis 'log in'.",
        "rcfilters-filter-user-experience-level-newcomer-label": "Nova uzeri",
        "tooltip-summary": "Skribez kurta rezumo",
        "anonymous": "Anonima {{PLURAL:$1|uzero|uzeri}} di {{SITENAME}}",
        "siteuser": "Uzero che {{SITENAME}} $1",
-       "lastmodifiedatby": "Ica pagino modifikesis ye $2, $1 da $3.",
+       "lastmodifiedatby": "Ica pagino modifikesis lastafoye ye $2, $1 da $3.",
        "othercontribs": "Bazizita en la laboro da $1.",
        "others": "altra",
        "siteusers": "{{PLURAL:$2|{{GENDER:$1|uzero}}|uzeri}} $1 di {{SITENAME}}",
        "monthsall": "omna",
        "confirmemail": "Konfirmez adreso di e-posto",
        "confirmemail_needlogin": "Vu mustas $1 pro konfirmar vua adreso di e-posto.",
+       "notificationemail_subject_changed": "L'adreso di e-posto en {{SITENAME}} modifikesis",
        "scarytranscludetoolong": "[URL es tro longa]",
        "deletedwhileediting": "'''Averto''': Ta pagino efacesis pos ke vu redakteskis!",
        "confirmrecreate-noreason": "Uzero [[User:$1|$1]] ([[User talk:$1|mesaji]]) {{GENDER:$1|efacis}} la pagino quon vu komencis redaktar. Voluntez konfirmar se vu fakte deziras rikrear ica pagino.",
index cc65318..e1b89d8 100644 (file)
        "rcfilters-filter-humans-label": "Umani (non bot)",
        "rcfilters-filter-humans-description": "Modifiche effettuate da contributori umani.",
        "rcfilters-filtergroup-reviewstatus": "Stato revisione",
+       "rcfilters-filter-reviewstatus-unpatrolled-description": "Modifiche non contrassegnate manualmente o automaticamente come verificate.",
        "rcfilters-filter-reviewstatus-unpatrolled-label": "Non verificate",
+       "rcfilters-filter-reviewstatus-manual-description": "Modifiche contrassegnate manualmente come verificate.",
        "rcfilters-filter-reviewstatus-manual-label": "Verificato manualmente",
        "rcfilters-filter-reviewstatus-auto-label": "Autoverificato",
        "rcfilters-filtergroup-significance": "Significato",
index 6cf9495..89fc7e1 100644 (file)
        "password-login-forbidden": "Использование этого имени участника и пароля запрещено.",
        "mailmypassword": "Сбросить пароль",
        "passwordremindertitle": "Напоминание пароля участника {{grammar:genitive|{{SITENAME}}}}",
-       "passwordremindertext": "Кто-то (с IP-адреса $1) запросил создать\nновый пароль для {{grammar:genitive|{{SITENAME}}}} ($4). Для участника $2\nсоздан временный пароль: $3. Если это был ваш запрос,\nвам следует представиться системе и выбрать новый пароль.\nВаш временный пароль будет действовать в течение $5 {{PLURAL:$5|дня|дней}}.\n\nЕсли вы не посылали запроса на смену пароля, или если вы уже вспомнили свой пароль,\nи не желаете его менять, вы можете проигнорировать данное сообщение и\nпродолжить использовать свой старый пароль.",
+       "passwordremindertext": "Кто-то (с IP-адреса $1) запросил новый пароль для {{grammar:genitive|{{SITENAME}}}} ($4). Для участника $2\nсоздан временный пароль: $3. Если это был ваш запрос,\nвам следует представиться системе и выбрать новый пароль.\nВаш временный пароль будет действовать в течение $5 {{PLURAL:$5|дня|дней}}.\n\nЕсли вы не посылали запроса на смену пароля, или уже вспомнили свой пароль,\nи не желаете его менять, вы можете проигнорировать данное сообщение и\nпродолжить использовать свой старый пароль.",
        "noemail": "Для участника с именем $1 электронный адрес указан не был.",
        "noemailcreate": "Вам необходимо указать корректный адрес электронной почты",
        "passwordsent": "Новый пароль был выслан на адрес электронной почты, указанный для участника $1.\n\nПожалуйста, представьтесь системе заново после получения пароля.",
        "longpageerror": "'''ОШИБКА: записываемый вами текст имеет размер $1 {{PLURAL:$1|килобайт|килобайта|килобайт}}, что больше, чем установленный предел в $2 {{PLURAL:$2|килобайт|килобайта|килобайт}}. Страница не может быть сохранена.'''",
        "readonlywarning": "<strong>Предупреждение: База данных заблокирована в связи с процедурами обслуживания, поэтому вы не можете записать ваши изменения прямо сейчас.</strong>\nВозможно, вам следует скопировать этот текст в текстовый файл, чтобы сохранить его на будущее.\n\nСистемный администратор, заблокировавший базу данных, оставил следующее объяснение: $1",
        "protectedpagewarning": "'''Предупреждение. Эта страница защищена от изменений, её могут редактировать только участники с полномочиями администратора.'''\nНиже для справки приведена последняя запись журнала:",
-       "semiprotectedpagewarning": "'''Замечание.''' Эта страница была защищена; редактировать её могут только автоподтверждённые участники.\nНиже для справки приведена последняя запись журнала:",
+       "semiprotectedpagewarning": "<strong>'''Замечание:'''</strong> эта страница была защищена; редактировать её могут только автоподтверждённые участники.\nНиже для справки приведена последняя запись журнала:",
        "cascadeprotectedwarning": "<strong>Предупреждение:</strong> Эта страница была защищена, так чтобы её могли редактировать только участники с [[Special:ListGroupRights|определёнными правами]], поскольку она включена {{PLURAL:$1|1=в следующую страницу, для которой|в следующие страницы, для которых}} включена каскадная защита:",
        "titleprotectedwarning": "'''Предупреждение.  Это название защищено. Создать эту страницу могут только участники с [[Special:ListGroupRights|соответствующими правами]].'''\nНиже для справки приведена последняя запись журнала:",
        "templatesused": "{{PLURAL:$1|1=Шаблон, используемый|Шаблоны, используемые}} на этой странице:",
        "fix-double-redirects": "Исправить перенаправления, указывающие на прежнее название",
        "move-leave-redirect": "Оставить перенаправление",
        "protectedpagemovewarning": "'''Предупреждение. Эта страница была защищена; переименовать её могут только участники с полномочиями администратора.'''\nНиже для справки приведена последняя запись журнала:",
-       "semiprotectedpagemovewarning": "'''Замечание.''' Эта страница была защищена; переименовать её могут только автоподтверждённые участники.\nНиже для справки приведена последняя запись журнала:",
+       "semiprotectedpagemovewarning": "<strong>'''Замечание:'''</strong> эта страница была защищена; переименовать её могут только автоподтверждённые участники.\nНиже для справки приведена последняя запись журнала:",
        "move-over-sharedrepo": "В общем хранилище существует [[:$1]]. Переименование файла в это название вызовет перекрытие файла из общего хранилища.",
        "file-exists-sharedrepo": "Выбранное имя файла уже используется в общем хранилище.\nПожалуйста, выберите другое имя.",
        "export": "Экспорт страниц",
index 0563534..3f08748 100644 (file)
        "rcfilters-filter-minor-description": "Yazarın küçük olarak etiketlediği düzenlemeler.",
        "rcfilters-filter-major-label": "Küçük olmayan düzenlemeler",
        "rcfilters-filter-major-description": "Küçük olarak etiketlenmemiş düzenlemeler.",
+       "rcfilters-filter-watchlist-watched-label": "İzleme listesinde",
+       "rcfilters-filter-watchlist-notwatched-label": "İzleme listesinde değil",
        "rcfilters-filtergroup-watchlistactivity": "İzleme listesi faaliyetleri",
        "rcfilters-filter-watchlistactivity-unseen-label": "Görülmemiş değişiklikler",
        "rcfilters-filter-watchlistactivity-seen-label": "Görülmüş değişiklikler",
        "rcfilters-filter-logactions-description": "Hizmetli işlemleri, hesap oluşturmalar, sayfa silmeler, yüklemeler...",
        "rcfilters-filter-lastrevision-label": "Son revizyon",
        "rcfilters-filter-previousrevision-label": "Son revizyon değil",
+       "rcfilters-filter-excluded": "Hariç",
+       "rcfilters-exclude-button-off": "Seçileni hariç tut",
+       "rcfilters-exclude-button-on": "Seçilen hariç",
+       "rcfilters-view-tags": "Etiketli düzenlemeler",
        "rcfilters-liveupdates-button": "Canlı güncelleme",
        "rcfilters-liveupdates-button-title-on": "Canlı güncellemeyi kapat",
        "rcfilters-liveupdates-button-title-off": "Yeni değişiklikleri yapıldıkları anda görüntüleyin",
        "tag-mw-new-redirect": "Yeni yönlendirme",
        "tag-mw-removed-redirect": "Yönlendirme kaldırıldı",
        "tag-mw-changed-redirect-target": "Yönlendirme hedefi değiştirildi",
+       "tag-mw-rollback": "Geri döndürme",
+       "tag-mw-undo": "Geri alma",
        "tags-title": "Etiketler",
        "tags-intro": "Bu sayfa, yazılımın bir değişikliği işaretleyebileceği etiketleri ve bunların anlamlarını listeler.",
        "tags-tag": "Etiket adı",
index 25e9de8..140f03f 100644 (file)
        "tag-filter-submit": "篩選器",
        "tag-list-wrapper": "([[Special:Tags|$1 個標籤]]:$2)",
        "tag-mw-contentmodelchange": "內容模型變更",
-       "tag-mw-contentmodelchange-description": "編輯 [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel 更改頁面的內容模型]。",
+       "tag-mw-contentmodelchange-description": "[https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel 更改頁面的內容模型]的編輯。",
        "tag-mw-new-redirect": "新重新導向",
        "tag-mw-new-redirect-description": "建立新重新導向或更改頁面為重新導向的編輯",
        "tag-mw-removed-redirect": "移除重新導向",
index 33cc0ca..b24d72d 100644 (file)
@@ -39,7 +39,7 @@ class CleanupPreferences extends Maintenance {
                $this->addOption( 'dry-run', 'Print debug info instead of actually deleting' );
                $this->addOption( 'hidden', 'Drop hidden preferences ($wgHiddenPrefs)' );
                $this->addOption( 'unknown',
-                       'Drop unknown preferences (not in $wgDefaultUserOptions or a gadget or userjs)' );
+                       'Drop unknown preferences (not in $wgDefaultUserOptions or prefixed with "userjs-")' );
                // TODO: actually implement this
                // $this->addOption( 'bogus', 'Drop preferences that have invalid/unaccepted values' );
        }
@@ -84,11 +84,9 @@ class CleanupPreferences extends Maintenance {
                        }
                }
 
-               // Remove unknown preferences. Special-case gadget- and userjs- as we can't
-               // control those names.
+               // Remove unknown preferences. Special-case 'userjs-' as we can't control those names.
                if ( $unknown ) {
                        $where = [
-                               'up_property NOT' . $dbw->buildLike( 'gadget-', $dbw->anyString() ),
                                'up_property NOT' . $dbw->buildLike( 'userjs-', $dbw->anyString() ),
                                'up_property NOT IN (' . $dbw->makeList( array_keys( $wgDefaultUserOptions ) ) . ')',
                        ];
index 88cf0e0..7e58555 100644 (file)
@@ -32,25 +32,27 @@ use Wikimedia\Rdbms\LoadMonitorNull;
  * @covers \Wikimedia\Rdbms\LoadBalancer
  */
 class LoadBalancerTest extends MediaWikiTestCase {
-       public function testWithoutReplica() {
+       private function makeServerConfig() {
                global $wgDBserver, $wgDBname, $wgDBuser, $wgDBpassword, $wgDBtype, $wgSQLiteDataDir;
 
-               $servers = [
-                       [
-                               'host'        => $wgDBserver,
-                               'dbname'      => $wgDBname,
-                               'tablePrefix' => $this->dbPrefix(),
-                               'user'        => $wgDBuser,
-                               'password'    => $wgDBpassword,
-                               'type'        => $wgDBtype,
-                               'dbDirectory' => $wgSQLiteDataDir,
-                               'load'        => 0,
-                               'flags'       => DBO_TRX // REPEATABLE-READ for consistency
-                       ],
+               return [
+                       'host' => $wgDBserver,
+                       'dbname' => $wgDBname,
+                       'tablePrefix' => $this->dbPrefix(),
+                       'user' => $wgDBuser,
+                       'password' => $wgDBpassword,
+                       'type' => $wgDBtype,
+                       'dbDirectory' => $wgSQLiteDataDir,
+                       'load' => 0,
+                       'flags' => DBO_TRX // REPEATABLE-READ for consistency
                ];
+       }
+
+       public function testWithoutReplica() {
+               global $wgDBname;
 
                $lb = new LoadBalancer( [
-                       'servers' => $servers,
+                       'servers' => [ $this->makeServerConfig() ],
                        'queryLogger' => MediaWiki\Logger\LoggerFactory::getInstance( 'DBQuery' ),
                        'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() )
                ] );
@@ -259,4 +261,39 @@ class LoadBalancerTest extends MediaWikiTestCase {
 
                $this->assertFalse( $lb->getServerAttributes( 1 )[Database::ATTR_DB_LEVEL_LOCKING] );
        }
+
+       /**
+        * @covers LoadBalancer::openConnection()
+        * @covers LoadBalancer::getAnyOpenConnection()
+        */
+       function testOpenConnection() {
+               global $wgDBname;
+
+               $lb = new LoadBalancer( [
+                       'servers' => [ $this->makeServerConfig() ],
+                       'localDomain' => new DatabaseDomain( $wgDBname, null, $this->dbPrefix() )
+               ] );
+
+               $i = $lb->getWriterIndex();
+               $this->assertEquals( null, $lb->getAnyOpenConnection( $i ) );
+               $conn1 = $lb->getConnection( $i );
+               $this->assertNotEquals( null, $conn1 );
+               $this->assertEquals( $conn1, $lb->getAnyOpenConnection( $i ) );
+               $conn2 = $lb->getConnection( $i, [], false, $lb::CONN_TRX_AUTOCOMMIT );
+               $this->assertNotEquals( null, $conn2 );
+               if ( $lb->getServerAttributes( $i )[Database::ATTR_DB_LEVEL_LOCKING] ) {
+                       $this->assertEquals( null,
+                               $lb->getAnyOpenConnection( $i, $lb::CONN_TRX_AUTOCOMMIT ) );
+                       $this->assertEquals( $conn1,
+                               $lb->getConnection(
+                                       $i, [], false, $lb::CONN_TRX_AUTOCOMMIT ), $lb::CONN_TRX_AUTOCOMMIT );
+               } else {
+                       $this->assertEquals( $conn2,
+                               $lb->getAnyOpenConnection( $i, $lb::CONN_TRX_AUTOCOMMIT ) );
+                       $this->assertEquals( $conn2,
+                               $lb->getConnection( $i, [], false, $lb::CONN_TRX_AUTOCOMMIT ) );
+               }
+
+               $lb->closeAll();
+       }
 }
index 8162bc0..bf46f44 100644 (file)
@@ -1,12 +1,14 @@
 <?php
 
+use MediaWiki\Shell\Command;
 use MediaWiki\Shell\Shell;
+use Wikimedia\TestingAccessWrapper;
 
 /**
  * @covers \MediaWiki\Shell\Shell
  * @group Shell
  */
-class ShellTest extends PHPUnit\Framework\TestCase {
+class ShellTest extends MediaWikiTestCase {
 
        use MediaWikiCoversValidator;
 
@@ -32,4 +34,72 @@ class ShellTest extends PHPUnit\Framework\TestCase {
                        'skip nulls' => [ [ 'ls', null ], "'ls'" ],
                ];
        }
+
+       /**
+        * @covers \MediaWiki\Shell\Shell::makeScriptCommand
+        * @dataProvider provideMakeScriptCommand
+        *
+        * @param string $expected
+        * @param string $script
+        * @param string[] $parameters
+        * @param string[] $options
+        * @param callable|null $hook
+        */
+       public function testMakeScriptCommand( $expected,
+                                                                                  $script,
+                                                                                  $parameters,
+                                                                                  $options = [],
+                                                                                  $hook = null
+       ) {
+               // Running tests under Vagrant involves MWMultiVersion that uses the below hook
+               $this->setMwGlobals( 'wgHooks', [] );
+
+               if ( $hook ) {
+                       $this->setTemporaryHook( 'wfShellWikiCmd', $hook );
+               }
+
+               $command = Shell::makeScriptCommand( $script, $parameters, $options );
+               $command->params( 'safe' )
+                       ->unsafeParams( 'unsafe' );
+
+               $this->assertType( Command::class, $command );
+
+               $wrapper = TestingAccessWrapper::newFromObject( $command );
+               $this->assertEquals( $expected, $wrapper->command );
+               $this->assertEquals( 0, $wrapper->restrictions & Shell::NO_LOCALSETTINGS );
+       }
+
+       public function provideMakeScriptCommand() {
+               global $wgPhpCli;
+
+               return [
+                       [
+                               "'$wgPhpCli' 'maintenance/foobar.php' 'bar'\\''\"baz' 'safe' unsafe",
+                               'maintenance/foobar.php',
+                               [ 'bar\'"baz' ],
+                       ],
+                       [
+                               "'$wgPhpCli' 'changed.php' '--wiki=somewiki' 'bar'\\''\"baz' 'safe' unsafe",
+                               'maintenance/foobar.php',
+                               [ 'bar\'"baz' ],
+                               [],
+                               function ( &$script, array &$parameters ) {
+                                       $script = 'changed.php';
+                                       array_unshift( $parameters, '--wiki=somewiki' );
+                               }
+                       ],
+                       [
+                               "'/bin/perl' 'maintenance/foobar.php' 'bar'\\''\"baz' 'safe' unsafe",
+                               'maintenance/foobar.php',
+                               [ 'bar\'"baz' ],
+                               [ 'php' => '/bin/perl' ],
+                       ],
+                       [
+                               "'$wgPhpCli' 'foobinize' 'maintenance/foobar.php' 'bar'\\''\"baz' 'safe' unsafe",
+                               'maintenance/foobar.php',
+                               [ 'bar\'"baz' ],
+                               [ 'wrapper' => 'foobinize' ],
+                       ],
+               ];
+       }
 }