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
* 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.
"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",
->limits( $limits )
->includeStderr( $includeStderr )
->profileMethod( $profileMethod )
+ // For b/c
+ ->restrict( Shell::RESTRICT_NONE )
->execute();
} catch ( ProcOpenError $ex ) {
$retval = -1;
* 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:
* @file
*/
+use MediaWiki\Shell\Shell;
+
/**
* This is a class for holding configuration settings, particularly for
* multi-wiki sites.
} 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 ) ) {
*/
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;
}
}
*/
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
/**
* 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
}
}
- /**
- * @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;
+ }
}
}
* @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 );
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
}
public function create() {
if ( $this->restrictionMethod === 'firejail' ) {
$command = new FirejailCommand( $this->findFirejail() );
+ $command->restrict( Shell::RESTRICT_DEFAULT );
} else {
$command = new Command();
}
namespace MediaWiki\Shell;
+use Hooks;
use MediaWiki\MediaWikiServices;
/**
*/
const NO_LOCALSETTINGS = 32;
+ /**
+ * Don't apply any restrictions
+ *
+ * @since 1.31
+ */
+ const RESTRICT_NONE = 0;
+
/**
* Returns a new instance of Command class
*
}
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 );
+ }
}
"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": "Дробныя праўкі",
"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",
"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",
"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",
"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.",
"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",
"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": "Экспорт страниц",
"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ı",
"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": "移除重新導向",
$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' );
}
}
}
- // 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 ) ) . ')',
];
* @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() )
] );
$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();
+ }
}
<?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;
'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' ],
+ ],
+ ];
+ }
}