ChangesListSpecialPageQuery.
* The global function wfUsePHP, deprecated since 1.30, has now been removed. To
assert a newer version of PHP than MediaWiki does, use extension registration.
+* The hook 'ChangesListSpecialPageFilters', deprecated in 1.29, has now been
+ removed. Use the 'ChangesListSpecialPageStructuredFilters' hook instead.
+* DeferredUpdates::setImmediateMode(), deprecated since 1.29, has been removed.
+* File / MediaHandler::getStreamHeaders(), deprecated since 1.30, was removed.
=== Deprecations in 1.32 ===
* HTMLForm::setSubmitProgressive() is deprecated. No need to call it. Submit
$unpatrolled: Whether or not we are showing unpatrolled changes.
$watched: Whether or not the change is watched by the user.
-'ChangesListSpecialPageFilters': DEPRECATED since 1.29! Use
-'ChangesListSpecialPageStructuredFilters' instead.
-Called after building form options on pages
-inheriting from ChangesListSpecialPage (in core: RecentChanges,
-RecentChangesLinked and Watchlist).
-$special: ChangesListSpecialPage instance
-&$filters: associative array of filter definitions. The keys are the HTML
- name/URL parameters. Each key maps to an associative array with a 'msg'
- (message key) and a 'default' value.
-
'ChangesListSpecialPageQuery': Called when building SQL query on pages
inheriting from ChangesListSpecialPage (in core: RecentChanges,
RecentChangesLinked and Watchlist).
$this->contentFormat,
$ex->getMessage()
);
- $out->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
+ $out->addWikiText( '<div class="error">' . $msg->plain() . '</div>' );
}
}
$this->contentFormat,
$ex->getMessage()
);
- $out->addWikiText( '<div class="error">' . $msg->text() . '</div>' );
+ $out->addWikiText( '<div class="error">' . $msg->plain() . '</div>' );
}
}
}
// Avoid PHP 7.1 warning of passing $this by reference
$apiModule = $this;
- Hooks::run( 'APIGetDescription', [ &$apiModule, &$desc ] );
+ Hooks::run( 'APIGetDescription', [ &$apiModule, &$desc ], '1.25' );
$desc = self::escapeWikiText( $desc );
if ( is_array( $desc ) ) {
$desc = implode( "\n", $desc );
// Avoid PHP 7.1 warning of passing $this by reference
$apiModule = $this;
- Hooks::run( 'APIGetParamDescription', [ &$apiModule, &$desc ] );
+ Hooks::run( 'APIGetParamDescription', [ &$apiModule, &$desc ], '1.25' );
if ( !$desc ) {
$desc = [];
$cache = [];
- # Common conditions
- $conds = [
- 'page_is_redirect' => 0,
- 'page_namespace' => NS_MEDIAWIKI,
- ];
-
- $mostused = [];
+ $mostused = []; // list of "<cased message key>/<code>"
if ( $wgAdaptiveMessageCache && $code !== $wgLanguageCode ) {
if ( !$this->cache->has( $wgLanguageCode ) ) {
$this->load( $wgLanguageCode );
}
}
+ // Get the list of software-defined messages in core/extensions
+ $overridable = array_flip( Language::getMessageKeysFor( $wgLanguageCode ) );
+
+ // Common conditions
+ $conds = [
+ 'page_is_redirect' => 0,
+ 'page_namespace' => NS_MEDIAWIKI,
+ ];
if ( count( $mostused ) ) {
$conds['page_title'] = $mostused;
} elseif ( $code !== $wgLanguageCode ) {
$dbr->buildLike( $dbr->anyString(), '/', $dbr->anyString() );
}
- # Conditions to fetch oversized pages to ignore them
- $bigConds = $conds;
- $bigConds[] = 'page_len > ' . intval( $wgMaxMsgCacheEntrySize );
-
- # Load titles for all oversized pages in the MediaWiki namespace
+ // Set the stubs for oversized software-defined messages in the main cache map
$res = $dbr->select(
'page',
[ 'page_title', 'page_latest' ],
- $bigConds,
+ array_merge( $conds, [ 'page_len > ' . intval( $wgMaxMsgCacheEntrySize ) ] ),
__METHOD__ . "($code)-big"
);
foreach ( $res as $row ) {
- $cache[$row->page_title] = '!TOO BIG';
+ $name = $this->contLang->lcfirst( $row->page_title );
+ // Include entries/stubs for all keys in $mostused in adaptive mode
+ if ( $wgAdaptiveMessageCache || isset( $overridable[$name] ) ) {
+ $cache[$row->page_title] = '!TOO BIG';
+ }
// At least include revision ID so page changes are reflected in the hash
$cache['EXCESSIVE'][$row->page_title] = $row->page_latest;
}
- # Conditions to load the remaining pages with their contents
- $smallConds = $conds;
- $smallConds[] = 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize );
-
+ // Set the text for small software-defined messages in the main cache map
$res = $dbr->select(
[ 'page', 'revision', 'text' ],
- [ 'page_title', 'old_id', 'old_text', 'old_flags' ],
- $smallConds,
+ [ 'page_title', 'page_latest', 'old_id', 'old_text', 'old_flags' ],
+ array_merge( $conds, [ 'page_len <= ' . intval( $wgMaxMsgCacheEntrySize ) ] ),
__METHOD__ . "($code)-small",
[],
[
'text' => [ 'JOIN', 'rev_text_id=old_id' ],
]
);
-
foreach ( $res as $row ) {
- $text = Revision::getRevisionText( $row );
- if ( $text === false ) {
- // Failed to fetch data; possible ES errors?
- // Store a marker to fetch on-demand as a workaround...
- // TODO Use a differnt marker
- $entry = '!TOO BIG';
- wfDebugLog(
- 'MessageCache',
- __METHOD__
- . ": failed to load message page text for {$row->page_title} ($code)"
- );
+ $name = $this->contLang->lcfirst( $row->page_title );
+ // Include entries/stubs for all keys in $mostused in adaptive mode
+ if ( $wgAdaptiveMessageCache || isset( $overridable[$name] ) ) {
+ $text = Revision::getRevisionText( $row );
+ if ( $text === false ) {
+ // Failed to fetch data; possible ES errors?
+ // Store a marker to fetch on-demand as a workaround...
+ // TODO Use a differnt marker
+ $entry = '!TOO BIG';
+ wfDebugLog(
+ 'MessageCache',
+ __METHOD__
+ . ": failed to load message page text for {$row->page_title} ($code)"
+ );
+ } else {
+ $entry = ' ' . $text;
+ }
+ $cache[$row->page_title] = $entry;
} else {
- $entry = ' ' . $text;
+ // T193271: cache object gets too big and slow to generate.
+ // At least include revision ID so page changes are reflected in the hash.
+ $cache['EXCESSIVE'][$row->page_title] = $row->page_latest;
}
- $cache[$row->page_title] = $entry;
}
$cache['VERSION'] = MSG_CACHE_VERSION;
Hooks::run( 'MessageCache::get', [ &$lckey ] );
// Loop through each language in the fallback list until we find something useful
- $lang = wfGetLangObj( $langcode );
$message = $this->getMessageFromFallbackChain(
- $lang,
+ wfGetLangObj( $langcode ),
$lckey,
!$this->mDisable && $useDB
);
$this->getMessagePageName( $langcode, $uckey ),
$langcode
);
-
if ( $message !== false ) {
return $message;
}
$this->load( $code );
$entry = $this->cache->getField( $code, $title );
+
if ( $entry !== null ) {
+ // Message page exists as an override of a software messages
if ( substr( $entry, 0, 1 ) === ' ' ) {
// The message exists and is not '!TOO BIG'
return (string)substr( $entry, 1 );
} elseif ( $entry === '!NONEXISTENT' ) {
+ // The text might be '-' or missing due to some data loss
return false;
}
- // Fall through and try invididual message cache below
- } else {
- // Message does not have a MediaWiki page definition
- $message = false;
- Hooks::run( 'MessagesPreLoad', [ $title, &$message, $code ] );
- if ( $message !== false ) {
- $this->cache->setField( $code, $title, ' ' . $message );
- } else {
- $this->cache->setField( $code, $title, '!NONEXISTENT' );
- }
-
- return $message;
- }
-
- if ( $this->cacheVolatile[$code] ) {
- $entry = false;
- // Make sure that individual keys respect the WAN cache holdoff period too
- LoggerFactory::getInstance( 'MessageCache' )->debug(
- __METHOD__ . ': loading volatile key \'{titleKey}\'',
- [ 'titleKey' => $title, 'code' => $code ] );
+ // Load the message page, utilizing the individual message cache.
+ // If the page does not exist, there will be no hook handler fallbacks.
+ $entry = $this->loadCachedMessagePageEntry(
+ $title,
+ $code,
+ $this->cache->getField( $code, 'HASH' )
+ );
} else {
- // Try the individual message cache
+ // Message page does not exist or does not override a software message.
+ // Load the message page, utilizing the individual message cache.
$entry = $this->loadCachedMessagePageEntry(
$title,
$code,
$this->cache->getField( $code, 'HASH' )
);
+ if ( substr( $entry, 0, 1 ) !== ' ' ) {
+ // Message does not have a MediaWiki page definition; try hook handlers
+ $message = false;
+ Hooks::run( 'MessagesPreLoad', [ $title, &$message, $code ] );
+ if ( $message !== false ) {
+ $this->cache->setField( $code, $title, ' ' . $message );
+ } else {
+ $this->cache->setField( $code, $title, '!NONEXISTENT' );
+ }
+
+ return $message;
+ }
}
if ( $entry !== false && substr( $entry, 0, 1 ) === ' ' ) {
- $this->cache->setField( $code, $title, $entry );
+ if ( $this->cacheVolatile[$code] ) {
+ // Make sure that individual keys respect the WAN cache holdoff period too
+ LoggerFactory::getInstance( 'MessageCache' )->debug(
+ __METHOD__ . ': loading volatile key \'{titleKey}\'',
+ [ 'titleKey' => $title, 'code' => $code ] );
+ } else {
+ $this->cache->setField( $code, $title, $entry );
+ }
// The message exists, so make sure a string is returned
return (string)substr( $entry, 1 );
}
}
}
- /**
- * @param bool $value Whether to just immediately run updates in addUpdate()
- * @since 1.28
- * @deprecated 1.29 Causes issues in Web-executed jobs - see T165714 and T100085.
- */
- public static function setImmediateMode( $value ) {
- wfDeprecated( __METHOD__, '1.29' );
- }
-
/**
* @param DeferrableUpdate[] $queue
* @param DeferrableUpdate $update
return true;
}
- /**
- * @deprecated since 1.30, use File::getContentHeaders instead
- */
- function getStreamHeaders() {
- wfDeprecated( __METHOD__, '1.30' );
- return $this->getContentHeaders();
- }
-
/**
* @return string[] HTTP header name/value map to use for HEAD/GET request responses
* @since 1.30
/**
* @param string $siteName
* @param string|null $admin
- * @param array $option
+ * @param array $options
*/
- function __construct( $siteName, $admin = null, array $option = [] ) {
+ function __construct( $siteName, $admin = null, array $options = [] ) {
global $wgContLang;
parent::__construct();
- if ( isset( $option['scriptpath'] ) ) {
+ if ( isset( $options['scriptpath'] ) ) {
$this->specifiedScriptPath = true;
}
foreach ( $this->optionMap as $opt => $global ) {
- if ( isset( $option[$opt] ) ) {
- $GLOBALS[$global] = $option[$opt];
- $this->setVar( $global, $option[$opt] );
+ if ( isset( $options[$opt] ) ) {
+ $GLOBALS[$global] = $options[$opt];
+ $this->setVar( $global, $options[$opt] );
}
}
- if ( isset( $option['lang'] ) ) {
+ if ( isset( $options['lang'] ) ) {
global $wgLang, $wgLanguageCode;
- $this->setVar( '_UserLang', $option['lang'] );
- $wgLanguageCode = $option['lang'];
+ $this->setVar( '_UserLang', $options['lang'] );
+ $wgLanguageCode = $options['lang'];
$wgContLang = MediaWikiServices::getInstance()->getContentLanguage();
- $wgLang = Language::factory( $option['lang'] );
+ $wgLang = Language::factory( $options['lang'] );
RequestContext::getMain()->setLanguage( $wgLang );
}
$this->setVar( '_AdminName', $admin );
}
- if ( !isset( $option['installdbuser'] ) ) {
+ if ( !isset( $options['installdbuser'] ) ) {
$this->setVar( '_InstallUser',
$this->getVar( 'wgDBuser' ) );
$this->setVar( '_InstallPassword',
$this->getVar( 'wgDBpassword' ) );
} else {
$this->setVar( '_InstallUser',
- $option['installdbuser'] );
+ $options['installdbuser'] );
$this->setVar( '_InstallPassword',
- $option['installdbpass'] ?? "" );
+ $options['installdbpass'] ?? "" );
// Assume that if we're given the installer user, we'll create the account.
$this->setVar( '_CreateDBAccount', true );
}
- if ( isset( $option['pass'] ) ) {
- $this->setVar( '_AdminPassword', $option['pass'] );
+ if ( isset( $options['pass'] ) ) {
+ $this->setVar( '_AdminPassword', $options['pass'] );
}
// Detect and inject any extension found
- if ( isset( $option['with-extensions'] ) ) {
+ if ( isset( $options['extensions'] ) ) {
+ $status = $this->validateExtensions(
+ 'extension', 'extensions', $options['extensions'] );
+ if ( !$status->isOK() ) {
+ $this->showStatusMessage( $status );
+ }
+ $this->setVar( '_Extensions', $status->value );
+ } elseif ( isset( $options['with-extensions'] ) ) {
$this->setVar( '_Extensions', array_keys( $this->findExtensions() ) );
}
// Set up the default skins
- $skins = array_keys( $this->findExtensions( 'skins' ) );
+ if ( isset( $options['skins'] ) ) {
+ $status = $this->validateExtensions( 'skin', 'skins', $options['skins'] );
+ if ( !$status->isOK() ) {
+ $this->showStatusMessage( $status );
+ }
+ $skins = $status->value;
+ } else {
+ $skins = array_keys( $this->findExtensions( 'skins' ) );
+ }
$this->setVar( '_Skins', $skins );
if ( $skins ) {
}
}
+ private function validateExtensions( $type, $directory, $nameLists ) {
+ $extensions = [];
+ $status = new Status;
+ foreach ( (array)$nameLists as $nameList ) {
+ foreach ( explode( ',', $nameList ) as $name ) {
+ $name = trim( $name );
+ if ( $name === '' ) {
+ continue;
+ }
+ $extStatus = $this->getExtensionInfo( $type, $directory, $name );
+ if ( $extStatus->isOK() ) {
+ $extensions[] = $name;
+ } else {
+ $status->merge( $extStatus );
+ }
+ }
+ }
+ $extensions = array_unique( $extensions );
+ $status->value = $extensions;
+ return $status;
+ }
+
/**
* Main entry point.
*/
}
/**
- * Finds extensions that follow the format /$directory/Name/Name.php,
- * and returns an array containing the value for 'Name' for each found extension.
+ * Find extensions or skins in a subdirectory of $IP.
+ * Returns an array containing the value for 'Name' for each found extension.
*
- * Reasonable values for $directory include 'extensions' (the default) and 'skins'.
- *
- * @param string $directory Directory to search in
+ * @param string $directory Directory to search in, relative to $IP, must be either "extensions"
+ * or "skins"
* @return array [ $extName => [ 'screenshots' => [ '...' ] ]
*/
public function findExtensions( $directory = 'extensions' ) {
+ switch ( $directory ) {
+ case 'extensions':
+ return $this->findExtensionsByType( 'extension', 'extensions' );
+ case 'skins':
+ return $this->findExtensionsByType( 'skin', 'skins' );
+ default:
+ throw new InvalidArgumentException( "Invalid extension type" );
+ }
+ }
+
+ /**
+ * Find extensions or skins, and return an array containing the value for 'Name' for each found
+ * extension.
+ *
+ * @param string $type Either "extension" or "skin"
+ * @param string $directory Directory to search in, relative to $IP
+ * @return array [ $extName => [ 'screenshots' => [ '...' ] ]
+ */
+ protected function findExtensionsByType( $type = 'extension', $directory = 'extensions' ) {
if ( $this->getVar( 'IP' ) === null ) {
return [];
}
return [];
}
- // extensions -> extension.json, skins -> skin.json
- $jsonFile = substr( $directory, 0, strlen( $directory ) - 1 ) . '.json';
-
$dh = opendir( $extDir );
$exts = [];
while ( ( $file = readdir( $dh ) ) !== false ) {
if ( !is_dir( "$extDir/$file" ) ) {
continue;
}
- $fullJsonFile = "$extDir/$file/$jsonFile";
- $isJson = file_exists( $fullJsonFile );
- $isPhp = false;
- if ( !$isJson ) {
- // Only fallback to PHP file if JSON doesn't exist
- $fullPhpFile = "$extDir/$file/$file.php";
- $isPhp = file_exists( $fullPhpFile );
- }
- if ( $isJson || $isPhp ) {
- // Extension exists. Now see if there are screenshots
- $exts[$file] = [];
- if ( is_dir( "$extDir/$file/screenshots" ) ) {
- $paths = glob( "$extDir/$file/screenshots/*.png" );
- foreach ( $paths as $path ) {
- $exts[$file]['screenshots'][] = str_replace( $extDir, "../$directory", $path );
- }
-
- }
- }
- if ( $isJson ) {
- $info = $this->readExtension( $fullJsonFile );
- if ( $info === false ) {
- continue;
- }
- $exts[$file] += $info;
+ $status = $this->getExtensionInfo( $type, $directory, $file );
+ if ( $status->isOK() ) {
+ $exts[$file] = $status->value;
}
}
closedir( $dh );
return $exts;
}
+ /**
+ * @param string $type Either "extension" or "skin"
+ * @param string $parentRelPath The parent directory relative to $IP
+ * @param string $name The extension or skin name
+ * @return Status An object containing an error list. If there were no errors, an associative
+ * array of information about the extension can be found in $status->value.
+ */
+ protected function getExtensionInfo( $type, $parentRelPath, $name ) {
+ if ( $this->getVar( 'IP' ) === null ) {
+ throw new Exception( 'Cannot find extensions since the IP variable is not yet set' );
+ }
+ if ( $type !== 'extension' && $type !== 'skin' ) {
+ throw new InvalidArgumentException( "Invalid extension type" );
+ }
+ $absDir = $this->getVar( 'IP' ) . "/$parentRelPath/$name";
+ $relDir = "../$parentRelPath/$name";
+ if ( !is_dir( $absDir ) ) {
+ return Status::newFatal( 'config-extension-not-found', $name );
+ }
+ $jsonFile = $type . '.json';
+ $fullJsonFile = "$absDir/$jsonFile";
+ $isJson = file_exists( $fullJsonFile );
+ $isPhp = false;
+ if ( !$isJson ) {
+ // Only fallback to PHP file if JSON doesn't exist
+ $fullPhpFile = "$absDir/$name.php";
+ $isPhp = file_exists( $fullPhpFile );
+ }
+ if ( !$isJson && !$isPhp ) {
+ return Status::newFatal( 'config-extension-not-found', $name );
+ }
+
+ // Extension exists. Now see if there are screenshots
+ $info = [];
+ if ( is_dir( "$absDir/screenshots" ) ) {
+ $paths = glob( "$absDir/screenshots/*.png" );
+ foreach ( $paths as $path ) {
+ $info['screenshots'][] = str_replace( $absDir, $relDir, $path );
+ }
+ }
+
+ if ( $isJson ) {
+ $jsonStatus = $this->readExtension( $fullJsonFile );
+ if ( !$jsonStatus->isOK() ) {
+ return $jsonStatus;
+ }
+ $info += $jsonStatus->value;
+ }
+
+ return Status::newGood( $info );
+ }
+
/**
* @param string $fullJsonFile
* @param array $extDeps
* @param array $skinDeps
*
- * @return array|bool False if this extension can't be loaded
+ * @return Status On success, an array of extension information is in $status->value. On
+ * failure, the Status object will have an error list.
*/
private function readExtension( $fullJsonFile, $extDeps = [], $skinDeps = [] ) {
$load = [
foreach ( $extDeps as $dep ) {
$fname = "$extDir/$dep/extension.json";
if ( !file_exists( $fname ) ) {
- return false;
+ return Status::newFatal( 'config-extension-not-found', $dep );
}
$load[$fname] = 1;
}
foreach ( $skinDeps as $dep ) {
$fname = "$skinDir/$dep/skin.json";
if ( !file_exists( $fname ) ) {
- return false;
+ return Status::newFatal( 'config-extension-not-found', $dep );
}
$load[$fname] = 1;
}
) {
// If something is incompatible with a dependency, we have no real
// option besides skipping it
- return false;
+ return Status::newFatal( 'config-extension-dependency',
+ basename( dirname( $fullJsonFile ) ), $e->getMessage() );
} elseif ( $e->missingExtensions || $e->missingSkins ) {
// There's an extension missing in the dependency tree,
// so add those to the dependency list and try again
);
}
// Some other kind of dependency error?
- return false;
+ return Status::newFatal( 'config-extension-dependency',
+ basename( dirname( $fullJsonFile ) ), $e->getMessage() );
}
$ret = [];
// The order of credits will be the order of $load,
}
$ret['type'] = $credits['type'];
- return $ret;
+ return Status::newGood( $ret );
}
/**
"config-skins-screenshot": "$1 ($2)",
"config-extensions-requires": "$1 (requires $2)",
"config-screenshot": "screenshot",
+ "config-extension-not-found": "Could not find the registration file for the extension \"$1\"",
+ "config-extension-dependency": "A dependency error was encountered while installing the extension \"$1\": $2",
"mainpagetext": "<strong>MediaWiki has been installed.</strong>",
"mainpagedocfooter": "Consult the [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User's Guide] for information on using the wiki software.\n\n== Getting started ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce MediaWiki release mailing list]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]"
}
"config-skins-screenshot": "Radio button text, $1 is the skin name, and $2 is a link to a screenshot of that skin, where the link text is {{msg-mw|config-screenshot}}.",
"config-extensions-requires": "Radio button text, $1 is the extension name, and $2 are links to other extensions that this one requires.\n{{Identical|Require}}",
"config-screenshot": "Link text for the link in {{msg-mw|config-skins-screenshot}}\n{{Identical|Screenshot}}",
+ "config-extension-not-found": "An error shown when an extension or skin named by the user could not be found.\n* $1 is the extension name",
+ "config-extension-dependency": "An error shown if an extension could not be loaded due to it depending on the wrong version of MediaWiki or an uninstallable extension.\n* $1 is the extension name\n* $2 is a more detailed explanation, in English",
"mainpagetext": "Along with {{msg-mw|mainpagedocfooter}}, the text you will see on the Main Page when your wiki is installed.",
"mainpagedocfooter": "Along with {{msg-mw|mainpagetext}}, the text you will see on the Main Page when your wiki is installed.\nThis might be a good place to put information about <nowiki>{{GRAMMAR:}}</nowiki>. See [[{{NAMESPACE}}:{{BASEPAGENAME}}/fi]] for an example. For languages having grammatical distinctions and not having an appropriate <nowiki>{{GRAMMAR:}}</nowiki> software available, a suggestion to check and possibly amend the messages having <nowiki>{{SITENAME}}</nowiki> may be valuable. See [[{{NAMESPACE}}:{{BASEPAGENAME}}/ksh]] for an example."
}
}
return false;
}
+
+ /**
+ * Whether to retry the job.
+ * @return bool
+ */
+ public function allowRetries() {
+ // ThumbnailRenderJob is a warmup for the thumbnails cache,
+ // so loosing it is not a problem. Most times the job fails
+ // for non-renderable or missing images which will not be fixed
+ // by a retry, but will create additional load on the renderer.
+ return false;
+ }
}
return [ $ext, $mime ];
}
- /**
- * @deprecated since 1.30, use MediaHandler::getContentHeaders instead
- * @param array $metadata
- * @return array
- */
- public function getStreamHeaders( $metadata ) {
- wfDeprecated( __METHOD__, '1.30' );
- return $this->getContentHeaders( $metadata );
- }
-
/**
* True if the handled types can be transformed
*
);
}
- // TODO remove this method once old parser cache objects have expired, probably mid-October 2018
- public function __wakeup() {
- // T203716 remove wrapper that was added by logic in an older version of this class,
- // where the wrapper was included in mText. This might sometimes remove a wrapper that's
- // genuine content (manually added to a system message), but that will work out OK, see below.
- $text = $this->getRawText();
- $start = Html::openElement( 'div', [
- 'class' => 'mw-parser-output'
- ] );
- $startLen = strlen( $start );
- $end = Html::closeElement( 'div' );
- $endPos = strrpos( $text, $end );
- $endLen = strlen( $end );
- if ( substr( $text, 0, $startLen ) === $start && $endPos !== false
- // if the closing div is followed by real content, bail out of unwrapping
- && preg_match( '/^(?>\s*<!--.*?-->)*\s*$/s', substr( $text, $endPos + $endLen ) )
- ) {
- $text = substr( $text, $startLen );
- $text = substr( $text, 0, $endPos - $startLen ) .
- substr( $text, $endPos - $startLen + $endLen );
- $this->setText( $text );
- // We found a wrapper to remove, so the ParserOutput was probably created by the
- // code path that now contains an addWrapperDivClass( 'mw-parser-output' ) call,
- // but it did not contain it when this object was cached, so we need to fix the
- // wrapper class variable.
- // If this was a message with a manually added wrapper, we are technically wrong about
- // this but we were wrong about the unwrapping as well so it will work out just right,
- // except when this is a normal page view of such a message page, in which case
- // it will be single-wrapped instead of double-wrapped (harmless) or something wants
- // render the message with unwrap=true (in which case the message won't be wrapped even
- // though it should, but the few code paths using unwrap=true only do it for real pages).
- $this->clearWrapperDivClass();
- $this->addWrapperDivClass( 'mw-parser-output' );
- }
- }
-
/**
* Merges internal metadata such as flags, accessed options, and profiling info
* from $source into this ParserOutput. This should be used whenever the state of $source
$this->addOption( 'env-checks', "Run environment checks only, don't change anything" );
$this->addOption( 'with-extensions', "Detect and include extensions" );
+ $this->addOption( 'extensions', 'Comma-separated list of extensions to install',
+ false, true, false, true );
+ $this->addOption( 'skins', 'Comma-separated list of skins to install (default: all)',
+ false, true, false, true );
}
public function getDbType() {
}
function execute() {
- global $wgVersion, $wgLang, $wgAllowSchemaUpdates;
+ global $wgVersion, $wgLang, $wgAllowSchemaUpdates, $wgMessagesDirs;
if ( !$wgAllowSchemaUpdates
&& !( $this->hasOption( 'force' )
}
}
+ // T206765: We need to load the installer i18n files as some of errors come installer/updater code
+ $wgMessagesDirs['MediawikiInstaller'] = dirname( __DIR__ ) . '/includes/installer/i18n';
+
$lang = Language::factory( 'en' );
// Set global language to ensure localised errors are in English (T22633)
RequestContext::getMain()->setLanguage( $lang );
$this->assertNotContains( 'class="foo bar"', $text );
}
- public function testT203716() {
- // simulate extra wrapping from old parser cache
- $out = new ParserOutput( '<div class="mw-parser-output">Foo</div>' );
- $out = unserialize( serialize( $out ) );
-
- $plainText = $out->getText( [ 'unwrap' => true ] );
- $wrappedText = $out->getText( [ 'unwrap' => false ] );
- $wrappedText2 = $out->getText( [ 'wrapperDivClass' => 'mw-parser-output' ] );
-
- $this->assertNotContains( '<div', $plainText );
- $this->assertContains( '<div', $wrappedText );
- $this->assertStringNotMatchesFormat( '<div%s<div%s', $wrappedText );
- $this->assertContains( '<div', $wrappedText2 );
- $this->assertStringNotMatchesFormat( '<div%s<div%s', $wrappedText2 );
-
- // simulate ParserOuput creation by new parser code
- $out = new ParserOutput( 'Foo' );
- $out->addWrapperDivClass( 'mw-parser-outout' );
- $out = unserialize( serialize( $out ) );
-
- $plainText = $out->getText( [ 'unwrap' => true ] );
- $wrappedText = $out->getText( [ 'unwrap' => false ] );
- $wrappedText2 = $out->getText( [ 'wrapperDivClass' => 'mw-parser-output' ] );
-
- $this->assertNotContains( '<div', $plainText );
- $this->assertContains( '<div', $wrappedText );
- $this->assertStringNotMatchesFormat( '<div%s<div%s', $wrappedText );
- $this->assertContains( '<div', $wrappedText2 );
- $this->assertStringNotMatchesFormat( '<div%s<div%s', $wrappedText2 );
- }
-
/**
* @covers ParserOutput::getText
* @dataProvider provideGetText
'patrol' => true,
];
- // Deprecated
- $this->setTemporaryHook(
- 'ChangesListSpecialPageFilters',
- null
- );
-
# setup the ChangesListSpecialPage (or subclass) object
$this->changesListSpecialPage = $this->getPage();
$context = $this->changesListSpecialPage->getContext();
public function setUp() {
parent::setUp();
- $this->setTemporaryHook(
- 'ChangesListSpecialPageFilters',
- null
- );
-
$this->setTemporaryHook(
'ChangesListSpecialPageQuery',
null
$this->newSpecialPage()
);
- $this->setTemporaryHook(
- 'ChangesListSpecialPageFilters',
- null
- );
-
$page->registerFilters();
// Does not consider $preferences, just wiki's defaults