summaryrefslogtreecommitdiff
path: root/includes/OutputPage.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/OutputPage.php')
-rw-r--r--includes/OutputPage.php539
1 files changed, 311 insertions, 228 deletions
diff --git a/includes/OutputPage.php b/includes/OutputPage.php
index 7e671878..69ed8def 100644
--- a/includes/OutputPage.php
+++ b/includes/OutputPage.php
@@ -20,6 +20,9 @@
* @file
*/
+use MediaWiki\Logger\LoggerFactory;
+use WrappedString\WrappedString;
+
/**
* This class should be covered by a general architecture document which does
* not exist as of January 2011. This is one of the Core classes and should
@@ -139,9 +142,6 @@ class OutputPage extends ContextSource {
/** @var string Inline CSS styles. Use addInlineStyle() sparingly */
protected $mInlineStyles = '';
- /** @todo Unused? */
- private $mLinkColours;
-
/**
* @var string Used by skin template.
* Example: $tpl->set( 'displaytitle', $out->mPageLinkTitle );
@@ -162,9 +162,6 @@ class OutputPage extends ContextSource {
/** @var array */
protected $mModuleStyles = array();
- /** @var array */
- protected $mModuleMessages = array();
-
/** @var ResourceLoader */
protected $mResourceLoader;
@@ -239,6 +236,8 @@ class OutputPage extends ContextSource {
/** @var int Cache stuff. Looks like mEnableClientCache */
protected $mSquidMaxage = 0;
+ /** @var int Upper limit on mCdnMaxage */
+ protected $mCdnMaxageLimit = INF;
/**
* @var bool Controls if anti-clickjacking / frame-breaking headers will
@@ -306,6 +305,11 @@ class OutputPage extends ContextSource {
private $mEnableSectionEditLinks = true;
/**
+ * @var string|null The URL to send in a <link> element with rel=copyright
+ */
+ private $copyrightUrl;
+
+ /**
* Constructor for OutputPage. This should not be called directly.
* Instead a new RequestContext should be created and it will implicitly create
* a OutputPage tied to that context.
@@ -342,6 +346,18 @@ class OutputPage extends ContextSource {
}
/**
+ * Set the copyright URL to send with the output.
+ * Empty string to omit, null to reset.
+ *
+ * @since 1.26
+ *
+ * @param string|null $url
+ */
+ public function setCopyrightUrl( $url ) {
+ $this->copyrightUrl = $url;
+ }
+
+ /**
* Set the HTTP status code to send with the output.
*
* @param int $statusCode
@@ -594,6 +610,20 @@ class OutputPage extends ContextSource {
* @return array Array of module names
*/
public function getModuleStyles( $filter = false, $position = null ) {
+ // T97420
+ $resourceLoader = $this->getResourceLoader();
+
+ foreach ( $this->mModuleStyles as $val ) {
+ $module = $resourceLoader->getModule( $val );
+
+ if ( $module instanceof ResourceLoaderModule && $module->isPositionDefault() ) {
+ $warning = __METHOD__ . ': style module should define its position explicitly: ' .
+ $val . ' ' . get_class( $module );
+ wfDebugLog( 'resourceloader', $warning );
+ wfLogWarning( $warning );
+ }
+ }
+
return $this->getModules( $filter, $position, 'mModuleStyles' );
}
@@ -613,24 +643,24 @@ class OutputPage extends ContextSource {
/**
* Get the list of module messages to include on this page
*
+ * @deprecated since 1.26 Obsolete
* @param bool $filter
* @param string|null $position
- *
* @return array Array of module names
*/
public function getModuleMessages( $filter = false, $position = null ) {
- return $this->getModules( $filter, $position, 'mModuleMessages' );
+ wfDeprecated( __METHOD__, '1.26' );
+ return array();
}
/**
- * Add only messages of one or more modules recognized by the resource loader.
- * Module messages added through this function will be loaded by the resource
- * loader when the page loads.
+ * Load messages of one or more ResourceLoader modules.
*
+ * @deprecated since 1.26 Use addModules() instead
* @param string|array $modules Module name (string) or array of module names
*/
public function addModuleMessages( $modules ) {
- $this->mModuleMessages = array_merge( $this->mModuleMessages, (array)$modules );
+ wfDeprecated( __METHOD__, '1.26' );
}
/**
@@ -797,9 +827,9 @@ class OutputPage extends ContextSource {
# this breaks strtotime().
$clientHeader = preg_replace( '/;.*$/', '', $clientHeader );
- wfSuppressWarnings(); // E_STRICT system time bitching
+ MediaWiki\suppressWarnings(); // E_STRICT system time bitching
$clientHeaderTime = strtotime( $clientHeader );
- wfRestoreWarnings();
+ MediaWiki\restoreWarnings();
if ( !$clientHeaderTime ) {
wfDebug( __METHOD__
. ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
@@ -826,10 +856,10 @@ class OutputPage extends ContextSource {
}
# Not modified
- # Give a 304 response code and disable body output
+ # Give a 304 Not Modified response code and disable body output
wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", 'log' );
ini_set( 'zlib.output_compression', 0 );
- $this->getRequest()->response()->header( "HTTP/1.1 304 Not Modified" );
+ $this->getRequest()->response()->statusHeader( 304 );
$this->sendCacheControl();
$this->disable();
@@ -1761,7 +1791,6 @@ class OutputPage extends ContextSource {
$this->addModules( $parserOutput->getModules() );
$this->addModuleScripts( $parserOutput->getModuleScripts() );
$this->addModuleStyles( $parserOutput->getModuleStyles() );
- $this->addModuleMessages( $parserOutput->getModuleMessages() );
$this->addJsConfigVars( $parserOutput->getJsConfigVars() );
$this->mPreventClickjacking = $this->mPreventClickjacking
|| $parserOutput->preventClickjacking();
@@ -1788,6 +1817,11 @@ class OutputPage extends ContextSource {
}
}
+ // enable OOUI if requested via ParserOutput
+ if ( $parserOutput->getEnableOOUI() ) {
+ $this->enableOOUI();
+ }
+
// Link flags are ignored for now, but may in the future be
// used to mark individual language links.
$linkFlags = array();
@@ -1808,7 +1842,6 @@ class OutputPage extends ContextSource {
$this->addModules( $parserOutput->getModules() );
$this->addModuleScripts( $parserOutput->getModuleScripts() );
$this->addModuleStyles( $parserOutput->getModuleStyles() );
- $this->addModuleMessages( $parserOutput->getModuleMessages() );
$this->addJsConfigVars( $parserOutput->getJsConfigVars() );
}
@@ -1914,7 +1947,17 @@ class OutputPage extends ContextSource {
* @param int $maxage Maximum cache time on the Squid, in seconds.
*/
public function setSquidMaxage( $maxage ) {
- $this->mSquidMaxage = $maxage;
+ $this->mSquidMaxage = min( $maxage, $this->mCdnMaxageLimit );
+ }
+
+ /**
+ * Lower the value of the "s-maxage" part of the "Cache-control" HTTP header
+ *
+ * @param int $maxage Maximum cache time on the CDN, in seconds
+ */
+ public function lowerCdnMaxage( $maxage ) {
+ $this->mCdnMaxageLimit = min( $maxage, $this->mCdnMaxageLimit );
+ $this->setSquidMaxage( $this->mSquidMaxage );
}
/**
@@ -1978,21 +2021,20 @@ class OutputPage extends ContextSource {
* Add an HTTP header that will influence on the cache
*
* @param string $header Header name
- * @param array|null $option
- * @todo FIXME: Document the $option parameter; it appears to be for
- * X-Vary-Options but what format is acceptable?
+ * @param string[]|null $option Options for X-Vary-Options. Possible options are:
+ * - "string-contains=$XXX" varies on whether the header value as a string
+ * contains $XXX as a substring.
+ * - "list-contains=$XXX" varies on whether the header value as a
+ * comma-separated list contains $XXX as one of the list items.
*/
- public function addVaryHeader( $header, $option = null ) {
+ public function addVaryHeader( $header, array $option = null ) {
if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
- $this->mVaryHeader[$header] = (array)$option;
- } elseif ( is_array( $option ) ) {
- if ( is_array( $this->mVaryHeader[$header] ) ) {
- $this->mVaryHeader[$header] = array_merge( $this->mVaryHeader[$header], $option );
- } else {
- $this->mVaryHeader[$header] = $option;
- }
+ $this->mVaryHeader[$header] = array();
+ }
+ if ( !is_array( $option ) ) {
+ $option = array();
}
- $this->mVaryHeader[$header] = array_unique( (array)$this->mVaryHeader[$header] );
+ $this->mVaryHeader[$header] = array_unique( array_merge( $this->mVaryHeader[$header], $option ) );
}
/**
@@ -2210,8 +2252,7 @@ class OutputPage extends ContextSource {
if ( Hooks::run( "BeforePageRedirect", array( $this, &$redirect, &$code ) ) ) {
if ( $code == '301' || $code == '303' ) {
if ( !$config->get( 'DebugRedirects' ) ) {
- $message = HttpStatus::getMessage( $code );
- $response->header( "HTTP/1.1 $code $message" );
+ $response->statusHeader( $code );
}
$this->mLastModified = wfTimestamp( TS_RFC2822 );
}
@@ -2233,10 +2274,7 @@ class OutputPage extends ContextSource {
return;
} elseif ( $this->mStatusCode ) {
- $message = HttpStatus::getMessage( $this->mStatusCode );
- if ( $message ) {
- $response->header( 'HTTP/1.1 ' . $this->mStatusCode . ' ' . $message );
- }
+ $response->statusHeader( $this->mStatusCode );
}
# Buffer output; final headers may depend on later processing
@@ -2258,14 +2296,14 @@ class OutputPage extends ContextSource {
if ( $this->mArticleBodyOnly ) {
echo $this->mBodytext;
} else {
-
$sk = $this->getSkin();
// add skin specific modules
$modules = $sk->getDefaultModules();
- // enforce various default modules for all skins
+ // Enforce various default modules for all skins
$coreModules = array(
- // keep this list as small as possible
+ // Keep this list as small as possible
+ 'site',
'mediawiki.page.startup',
'mediawiki.user',
);
@@ -2672,16 +2710,14 @@ class OutputPage extends ContextSource {
}
$ret .= Html::element( 'title', null, $this->getHTMLTitle() ) . "\n";
+ $ret .= $this->getInlineHeadScripts() . "\n";
+ $ret .= $this->buildCssLinks() . "\n";
+ $ret .= $this->getExternalHeadScripts() . "\n";
foreach ( $this->getHeadLinksArray() as $item ) {
$ret .= $item . "\n";
}
- // No newline after buildCssLinks since makeResourceLoaderLink did that already
- $ret .= $this->buildCssLinks();
-
- $ret .= $this->getHeadScripts() . "\n";
-
foreach ( $this->mHeadItems as $item ) {
$ret .= $item . "\n";
}
@@ -2729,29 +2765,31 @@ class OutputPage extends ContextSource {
*/
public function getResourceLoader() {
if ( is_null( $this->mResourceLoader ) ) {
- $this->mResourceLoader = new ResourceLoader( $this->getConfig() );
+ $this->mResourceLoader = new ResourceLoader(
+ $this->getConfig(),
+ LoggerFactory::getInstance( 'resourceloader' )
+ );
}
return $this->mResourceLoader;
}
/**
- * @todo Document
+ * Construct neccecary html and loader preset states to load modules on a page.
+ *
+ * Use getHtmlFromLoaderLinks() to convert this array to HTML.
+ *
* @param array|string $modules One or more module names
* @param string $only ResourceLoaderModule TYPE_ class constant
- * @param bool $useESI
- * @param array $extraQuery Array with extra query parameters to add to each
- * request. array( param => value ).
- * @param bool $loadCall If true, output an (asynchronous) mw.loader.load()
- * call rather than a "<script src='...'>" tag.
- * @return string The html "<script>", "<link>" and "<style>" tags
- */
- public function makeResourceLoaderLink( $modules, $only, $useESI = false,
- array $extraQuery = array(), $loadCall = false
- ) {
+ * @param array $extraQuery [optional] Array with extra query parameters for the request
+ * @return array A list of HTML strings and array of client loader preset states
+ */
+ public function makeResourceLoaderLink( $modules, $only, array $extraQuery = array() ) {
$modules = (array)$modules;
$links = array(
- 'html' => '',
+ // List of html strings
+ 'html' => array(),
+ // Associative array of module names and their states
'states' => array(),
);
@@ -2768,8 +2806,8 @@ class OutputPage extends ContextSource {
if ( ResourceLoader::inDebugMode() ) {
// Recursively call us for every item
foreach ( $modules as $name ) {
- $link = $this->makeResourceLoaderLink( $name, $only, $useESI );
- $links['html'] .= $link['html'];
+ $link = $this->makeResourceLoaderLink( $name, $only, $extraQuery );
+ $links['html'] = array_merge( $links['html'], $link['html'] );
$links['states'] += $link['states'];
}
return $links;
@@ -2783,7 +2821,6 @@ class OutputPage extends ContextSource {
// Create keyed-by-source and then keyed-by-group list of module objects from modules list
$sortedModules = array();
$resourceLoader = $this->getResourceLoader();
- $resourceLoaderUseESI = $this->getConfig()->get( 'ResourceLoaderUseESI' );
foreach ( $modules as $name ) {
$module = $resourceLoader->getModule( $name );
# Check that we're allowed to include this module on this page
@@ -2849,21 +2886,18 @@ class OutputPage extends ContextSource {
// Inline private modules. These can't be loaded through load.php for security
// reasons, see bug 34907. Note that these modules should be loaded from
- // getHeadScripts() before the first loader call. Otherwise other modules can't
+ // getExternalHeadScripts() before the first loader call. Otherwise other modules can't
// properly use them as dependencies (bug 30914)
if ( $group === 'private' ) {
if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
- $links['html'] .= Html::inlineStyle(
+ $links['html'][] = Html::inlineStyle(
$resourceLoader->makeModuleResponse( $context, $grpModules )
);
} else {
- $links['html'] .= Html::inlineScript(
- ResourceLoader::makeLoaderConditionalScript(
- $resourceLoader->makeModuleResponse( $context, $grpModules )
- )
+ $links['html'][] = ResourceLoader::makeInlineScript(
+ $resourceLoader->makeModuleResponse( $context, $grpModules )
);
}
- $links['html'] .= "\n";
continue;
}
@@ -2874,65 +2908,44 @@ class OutputPage extends ContextSource {
// and we shouldn't be putting timestamps in Squid-cached HTML
$version = null;
if ( $group === 'user' ) {
- // Get the maximum timestamp
- $timestamp = 1;
- foreach ( $grpModules as $module ) {
- $timestamp = max( $timestamp, $module->getModifiedTime( $context ) );
- }
- // Add a version parameter so cache will break when things change
- $query['version'] = wfTimestamp( TS_ISO_8601_BASIC, $timestamp );
+ $query['version'] = $resourceLoader->getCombinedVersion( $context, array_keys( $grpModules ) );
}
$query['modules'] = ResourceLoader::makePackedModulesString( array_keys( $grpModules ) );
$moduleContext = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
$url = $resourceLoader->createLoaderURL( $source, $moduleContext, $extraQuery );
- if ( $useESI && $resourceLoaderUseESI ) {
- $esi = Xml::element( 'esi:include', array( 'src' => $url ) );
- if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
- $link = Html::inlineStyle( $esi );
- } else {
- $link = Html::inlineScript( $esi );
- }
+ // Automatically select style/script elements
+ if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
+ $link = Html::linkedStyle( $url );
} else {
- // Automatically select style/script elements
- if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
- $link = Html::linkedStyle( $url );
- } elseif ( $loadCall ) {
- $link = Html::inlineScript(
- ResourceLoader::makeLoaderConditionalScript(
- Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) )
- )
- );
+ if ( $context->getRaw() || $isRaw ) {
+ // Startup module can't load itself, needs to use <script> instead of mw.loader.load
+ $link = Html::element( 'script', array(
+ // In SpecialJavaScriptTest, QUnit must load synchronous
+ 'async' => !isset( $extraQuery['sync'] ),
+ 'src' => $url
+ ) );
} else {
- $link = Html::linkedScript( $url );
- if ( !$context->getRaw() && !$isRaw ) {
- // Wrap only=script / only=combined requests in a conditional as
- // browsers not supported by the startup module would unconditionally
- // execute this module. Otherwise users will get "ReferenceError: mw is
- // undefined" or "jQuery is undefined" from e.g. a "site" module.
- $link = Html::inlineScript(
- ResourceLoader::makeLoaderConditionalScript(
- Xml::encodeJsCall( 'document.write', array( $link ) )
- )
- );
- }
+ $link = ResourceLoader::makeInlineScript(
+ Xml::encodeJsCall( 'mw.loader.load', array( $url ) )
+ );
+ }
- // For modules requested directly in the html via <link> or <script>,
- // tell mw.loader they are being loading to prevent duplicate requests.
- foreach ( $grpModules as $key => $module ) {
- // Don't output state=loading for the startup module..
- if ( $key !== 'startup' ) {
- $links['states'][$key] = 'loading';
- }
+ // For modules requested directly in the html via <script> or mw.loader.load
+ // tell mw.loader they are being loading to prevent duplicate requests.
+ foreach ( $grpModules as $key => $module ) {
+ // Don't output state=loading for the startup module.
+ if ( $key !== 'startup' ) {
+ $links['states'][$key] = 'loading';
}
}
}
if ( $group == 'noscript' ) {
- $links['html'] .= Html::rawElement( 'noscript', array(), $link ) . "\n";
+ $links['html'][] = Html::rawElement( 'noscript', array(), $link );
} else {
- $links['html'] .= $link . "\n";
+ $links['html'][] = $link;
}
}
}
@@ -2946,26 +2959,26 @@ class OutputPage extends ContextSource {
* @return string HTML
*/
protected static function getHtmlFromLoaderLinks( array $links ) {
- $html = '';
+ $html = array();
$states = array();
foreach ( $links as $link ) {
if ( !is_array( $link ) ) {
- $html .= $link;
+ $html[] = $link;
} else {
- $html .= $link['html'];
+ $html = array_merge( $html, $link['html'] );
$states += $link['states'];
}
}
+ // Filter out empty values
+ $html = array_filter( $html, 'strlen' );
if ( count( $states ) ) {
- $html = Html::inlineScript(
- ResourceLoader::makeLoaderConditionalScript(
- ResourceLoader::makeLoaderStateScript( $states )
- )
- ) . "\n" . $html;
+ array_unshift( $html, ResourceLoader::makeInlineScript(
+ ResourceLoader::makeLoaderStateScript( $states )
+ ) );
}
- return $html;
+ return WrappedString::join( "\n", $html );
}
/**
@@ -2975,127 +2988,149 @@ class OutputPage extends ContextSource {
* @return string HTML fragment
*/
function getHeadScripts() {
- // Startup - this will immediately load jquery and mediawiki modules
+ return $this->getInlineHeadScripts() . "\n" . $this->getExternalHeadScripts();
+ }
+
+ /**
+ * <script src="..."> tags for "<head>". This is the startup module
+ * and other modules marked with position 'top'.
+ *
+ * @return string HTML fragment
+ */
+ function getExternalHeadScripts() {
$links = array();
- $links[] = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true );
- // Load config before anything else
+ // Startup - this provides the client with the module manifest and loads jquery and mediawiki base modules
+ $links[] = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS );
+
+ return self::getHtmlFromLoaderLinks( $links );
+ }
+
+ /**
+ * <script>...</script> tags to put in "<head>".
+ *
+ * @return string HTML fragment
+ */
+ function getInlineHeadScripts() {
+ $links = array();
+
+ // Client profile classes for <html>. Allows for easy hiding/showing of UI components.
+ // Must be done synchronously on every page to avoid flashes of wrong content.
+ // Note: This class distinguishes MediaWiki-supported JavaScript from the rest.
+ // The "rest" includes browsers that support JavaScript but not supported by our runtime.
+ // For the performance benefit of the majority, this is added unconditionally here and is
+ // then fixed up by the startup module for unsupported browsers.
$links[] = Html::inlineScript(
- ResourceLoader::makeLoaderConditionalScript(
- ResourceLoader::makeConfigSetScript( $this->getJSVars() )
- )
+ 'document.documentElement.className = document.documentElement.className'
+ . '.replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" );'
+ );
+
+ // Load config before anything else
+ $links[] = ResourceLoader::makeInlineScript(
+ ResourceLoader::makeConfigSetScript( $this->getJSVars() )
);
// Load embeddable private modules before any loader links
// This needs to be TYPE_COMBINED so these modules are properly wrapped
// in mw.loader.implement() calls and deferred until mw.user is available
- $embedScripts = array( 'user.options', 'user.tokens' );
+ $embedScripts = array( 'user.options' );
$links[] = $this->makeResourceLoaderLink( $embedScripts, ResourceLoaderModule::TYPE_COMBINED );
-
- // Scripts and messages "only" requests marked for top inclusion
- // Messages should go first
- $links[] = $this->makeResourceLoaderLink(
- $this->getModuleMessages( true, 'top' ),
- ResourceLoaderModule::TYPE_MESSAGES
- );
- $links[] = $this->makeResourceLoaderLink(
- $this->getModuleScripts( true, 'top' ),
- ResourceLoaderModule::TYPE_SCRIPTS
- );
+ // Separate user.tokens as otherwise caching will be allowed (T84960)
+ $links[] = $this->makeResourceLoaderLink( 'user.tokens', ResourceLoaderModule::TYPE_COMBINED );
// Modules requests - let the client calculate dependencies and batch requests as it likes
// Only load modules that have marked themselves for loading at the top
$modules = $this->getModules( true, 'top' );
if ( $modules ) {
- $links[] = Html::inlineScript(
- ResourceLoader::makeLoaderConditionalScript(
- Xml::encodeJsCall( 'mw.loader.load', array( $modules ) )
- )
+ $links[] = ResourceLoader::makeInlineScript(
+ Xml::encodeJsCall( 'mw.loader.load', array( $modules ) )
);
}
- if ( $this->getConfig()->get( 'ResourceLoaderExperimentalAsyncLoading' ) ) {
- $links[] = $this->getScriptsForBottomQueue( true );
- }
+ // "Scripts only" modules marked for top inclusion
+ $links[] = $this->makeResourceLoaderLink(
+ $this->getModuleScripts( true, 'top' ),
+ ResourceLoaderModule::TYPE_SCRIPTS
+ );
return self::getHtmlFromLoaderLinks( $links );
}
/**
- * JS stuff to put at the 'bottom', which can either be the bottom of the
- * "<body>" or the bottom of the "<head>" depending on
- * $wgResourceLoaderExperimentalAsyncLoading: modules marked with position
- * 'bottom', legacy scripts ($this->mScripts), user preferences, site JS
- * and user JS.
+ * JS stuff to put at the 'bottom', which goes at the bottom of the `<body>`.
+ * These are modules marked with position 'bottom', legacy scripts ($this->mScripts),
+ * site JS, and user JS.
*
- * @param bool $inHead If true, this HTML goes into the "<head>",
- * if false it goes into the "<body>".
+ * @param bool $unused Previously used to let this method change its output based
+ * on whether it was called by getExternalHeadScripts() or getBottomScripts().
* @return string
*/
- function getScriptsForBottomQueue( $inHead ) {
- // Scripts and messages "only" requests marked for bottom inclusion
+ function getScriptsForBottomQueue( $unused = null ) {
+ // Scripts "only" requests marked for bottom inclusion
// If we're in the <head>, use load() calls rather than <script src="..."> tags
- // Messages should go first
$links = array();
- $links[] = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'bottom' ),
- ResourceLoaderModule::TYPE_MESSAGES, /* $useESI = */ false, /* $extraQuery = */ array(),
- /* $loadCall = */ $inHead
- );
+
$links[] = $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ),
- ResourceLoaderModule::TYPE_SCRIPTS, /* $useESI = */ false, /* $extraQuery = */ array(),
- /* $loadCall = */ $inHead
+ ResourceLoaderModule::TYPE_SCRIPTS
+ );
+
+ $links[] = $this->makeResourceLoaderLink( $this->getModuleStyles( true, 'bottom' ),
+ ResourceLoaderModule::TYPE_STYLES
);
// Modules requests - let the client calculate dependencies and batch requests as it likes
// Only load modules that have marked themselves for loading at the bottom
$modules = $this->getModules( true, 'bottom' );
if ( $modules ) {
- $links[] = Html::inlineScript(
- ResourceLoader::makeLoaderConditionalScript(
- Xml::encodeJsCall( 'mw.loader.load', array( $modules, null, true ) )
- )
+ $links[] = ResourceLoader::makeInlineScript(
+ Xml::encodeJsCall( 'mw.loader.load', array( $modules ) )
);
}
// Legacy Scripts
- $links[] = "\n" . $this->mScripts;
-
- // Add site JS if enabled
- $links[] = $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS,
- /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
- );
+ $links[] = $this->mScripts;
// Add user JS if enabled
+ // This must use TYPE_COMBINED instead of only=scripts so that its request is handled by
+ // mw.loader.implement() which ensures that execution is scheduled after the "site" module.
if ( $this->getConfig()->get( 'AllowUserJs' )
&& $this->getUser()->isLoggedIn()
&& $this->getTitle()
&& $this->getTitle()->isJsSubpage()
&& $this->userCanPreview()
) {
- # XXX: additional security check/prompt?
- // We're on a preview of a JS subpage
- // Exclude this page from the user module in case it's in there (bug 26283)
- $links[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false,
- array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead
+ // We're on a preview of a JS subpage. Exclude this page from the user module (T28283)
+ // and include the draft contents as a raw script instead.
+ $links[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_COMBINED,
+ array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() )
);
// Load the previewed JS
- $links[] = Html::inlineScript( "\n"
- . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n";
+ $links[] = ResourceLoader::makeInlineScript(
+ Xml::encodeJsCall( 'mw.loader.using', array(
+ array( 'user', 'site' ),
+ new XmlJsCode(
+ 'function () {'
+ . Xml::encodeJsCall( '$.globalEval', array(
+ $this->getRequest()->getText( 'wpTextbox1' )
+ ) )
+ . '}'
+ )
+ ) )
+ );
// FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded
// asynchronously and may arrive *after* the inline script here. So the previewed code
- // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js...
+ // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js.
+ // Similarly, when previewing ./common.js and the user module does arrive first, it will
+ // arrive without common.js and the inline script runs after. Thus running common after
+ // the excluded subpage.
} else {
// Include the user module normally, i.e., raw to avoid it being wrapped in a closure.
- $links[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS,
- /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
- );
+ $links[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_COMBINED );
}
// Group JS is only enabled if site JS is enabled.
- $links[] = $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED,
- /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
- );
+ $links[] = $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED );
return self::getHtmlFromLoaderLinks( $links );
}
@@ -3105,17 +3140,10 @@ class OutputPage extends ContextSource {
* @return string
*/
function getBottomScripts() {
- // Optimise jQuery ready event cross-browser.
- // This also enforces $.isReady to be true at </body> which fixes the
- // mw.loader bug in Firefox with using document.write between </body>
- // and the DOMContentReady event (bug 47457).
- $html = Html::inlineScript( 'if(window.jQuery)jQuery.ready();' );
-
- if ( !$this->getConfig()->get( 'ResourceLoaderExperimentalAsyncLoading' ) ) {
- $html .= $this->getScriptsForBottomQueue( false );
- }
+ // In case the skin wants to add bottom CSS
+ $this->getSkin()->setupSkinUserCss( $this );
- return $html;
+ return $this->getScriptsForBottomQueue();
}
/**
@@ -3426,33 +3454,37 @@ class OutputPage extends ContextSource {
$lang = $this->getTitle()->getPageLanguage();
if ( $lang->hasVariants() ) {
$variants = $lang->getVariants();
- foreach ( $variants as $_v ) {
- $tags["variant-$_v"] = Html::element( 'link', array(
+ foreach ( $variants as $variant ) {
+ $tags["variant-$variant"] = Html::element( 'link', array(
'rel' => 'alternate',
- 'hreflang' => wfBCP47( $_v ),
- 'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) )
+ 'hreflang' => wfBCP47( $variant ),
+ 'href' => $this->getTitle()->getLocalURL( array( 'variant' => $variant ) ) )
);
}
+ # x-default link per https://support.google.com/webmasters/answer/189077?hl=en
+ $tags["variant-x-default"] = Html::element( 'link', array(
+ 'rel' => 'alternate',
+ 'hreflang' => 'x-default',
+ 'href' => $this->getTitle()->getLocalURL() ) );
}
- # x-default link per https://support.google.com/webmasters/answer/189077?hl=en
- $tags["variant-x-default"] = Html::element( 'link', array(
- 'rel' => 'alternate',
- 'hreflang' => 'x-default',
- 'href' => $this->getTitle()->getLocalURL() ) );
}
# Copyright
- $copyright = '';
- if ( $config->get( 'RightsPage' ) ) {
- $copy = Title::newFromText( $config->get( 'RightsPage' ) );
+ if ( $this->copyrightUrl !== null ) {
+ $copyright = $this->copyrightUrl;
+ } else {
+ $copyright = '';
+ if ( $config->get( 'RightsPage' ) ) {
+ $copy = Title::newFromText( $config->get( 'RightsPage' ) );
- if ( $copy ) {
- $copyright = $copy->getLocalURL();
+ if ( $copy ) {
+ $copyright = $copy->getLocalURL();
+ }
}
- }
- if ( !$copyright && $config->get( 'RightsUrl' ) ) {
- $copyright = $config->get( 'RightsUrl' );
+ if ( !$copyright && $config->get( 'RightsUrl' ) ) {
+ $copyright = $config->get( 'RightsUrl' );
+ }
}
if ( $copyright ) {
@@ -3513,8 +3545,25 @@ class OutputPage extends ContextSource {
if ( $canonicalUrl !== false ) {
$canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL );
} else {
- $reqUrl = $this->getRequest()->getRequestURL();
- $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL );
+ if ( $this->isArticleRelated() ) {
+ // This affects all requests where "setArticleRelated" is true. This is
+ // typically all requests that show content (query title, curid, oldid, diff),
+ // and all wikipage actions (edit, delete, purge, info, history etc.).
+ // It does not apply to File pages and Special pages.
+ // 'history' and 'info' actions address page metadata rather than the page
+ // content itself, so they may not be canonicalized to the view page url.
+ // TODO: this ought to be better encapsulated in the Action class.
+ $action = Action::getActionName( $this->getContext() );
+ if ( in_array( $action, array( 'history', 'info' ) ) ) {
+ $query = "action={$action}";
+ } else {
+ $query = '';
+ }
+ $canonicalUrl = $this->getTitle()->getCanonicalURL( $query );
+ } else {
+ $reqUrl = $this->getRequest()->getRequestURL();
+ $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL );
+ }
}
}
if ( $canonicalUrl !== false ) {
@@ -3613,10 +3662,10 @@ class OutputPage extends ContextSource {
'noscript' => array()
);
$links = array();
- $otherTags = ''; // Tags to append after the normal <link> tags
+ $otherTags = array(); // Tags to append after the normal <link> tags
$resourceLoader = $this->getResourceLoader();
- $moduleStyles = $this->getModuleStyles();
+ $moduleStyles = $this->getModuleStyles( true, 'top' );
// Per-site custom styles
$moduleStyles[] = 'site';
@@ -3629,10 +3678,10 @@ class OutputPage extends ContextSource {
) {
// We're on a preview of a CSS subpage
// Exclude this page from the user module in case it's in there (bug 26283)
- $link = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES, false,
+ $link = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES,
array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() )
);
- $otherTags .= $link['html'];
+ $otherTags = array_merge( $otherTags, $link['html'] );
// Load the previewed CSS
// If needed, Janus it first. This is user-supplied CSS, so it's
@@ -3641,7 +3690,7 @@ class OutputPage extends ContextSource {
if ( $this->getLanguage()->getDir() !== $wgContLang->getDir() ) {
$previewedCSS = CSSJanus::transform( $previewedCSS, true, false );
}
- $otherTags .= Html::inlineStyle( $previewedCSS ) . "\n";
+ $otherTags[] = Html::inlineStyle( $previewedCSS ) . "\n";
} else {
// Load the user styles normally
$moduleStyles[] = 'user';
@@ -3655,9 +3704,17 @@ class OutputPage extends ContextSource {
if ( !$module ) {
continue;
}
+ if ( $name === 'site' ) {
+ // HACK: The site module shouldn't be fragmented with a cache group and
+ // http request. But in order to ensure its styles are separated and after the
+ // ResourceLoaderDynamicStyles marker, pretend it is in a group called 'site'.
+ // The scripts remain ungrouped and rides the bottom queue.
+ $styles['site'][] = $name;
+ continue;
+ }
$group = $module->getGroup();
- // Modules in groups different than the ones listed on top (see $styles assignment)
- // will be placed in the "other" group
+ // Modules in groups other than the ones needing special treatment (see $styles assignment)
+ // will be placed in the "other" style category.
$styles[isset( $styles[$group] ) ? $group : 'other'][] = $name;
}
@@ -3674,9 +3731,9 @@ class OutputPage extends ContextSource {
$links[] = Html::element(
'meta',
array( 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' )
- ) . "\n";
+ );
- // Add site, private and user styles
+ // Add site-specific and user-specific styles
// 'private' at present only contains user.options, so put that before 'user'
// Any future private modules will likely have a similar user-specific character
foreach ( array( 'site', 'noscript', 'private', 'user' ) as $group ) {
@@ -3686,7 +3743,7 @@ class OutputPage extends ContextSource {
}
// Add stuff in $otherTags (previewed user CSS if applicable)
- return self::getHtmlFromLoaderLinks( $links ) . $otherTags;
+ return self::getHtmlFromLoaderLinks( $links ) . implode( '', $otherTags );
}
/**
@@ -3918,14 +3975,40 @@ class OutputPage extends ContextSource {
}
/**
+ * Helper function to setup the PHP implementation of OOUI to use in this request.
+ *
+ * @since 1.26
+ * @param String $skinName The Skin name to determine the correct OOUI theme
+ * @param String $dir Language direction
+ */
+ public static function setupOOUI( $skinName = '', $dir = 'ltr' ) {
+ $themes = ExtensionRegistry::getInstance()->getAttribute( 'SkinOOUIThemes' );
+ // Make keys (skin names) lowercase for case-insensitive matching.
+ $themes = array_change_key_case( $themes, CASE_LOWER );
+ $theme = isset( $themes[ $skinName ] ) ? $themes[ $skinName ] : 'MediaWiki';
+ // For example, 'OOUI\MediaWikiTheme'.
+ $themeClass = "OOUI\\{$theme}Theme";
+ OOUI\Theme::setSingleton( new $themeClass() );
+ OOUI\Element::setDefaultDir( $dir );
+ }
+
+ /**
* Add ResourceLoader module styles for OOUI and set up the PHP implementation of it for use with
* MediaWiki and this OutputPage instance.
*
* @since 1.25
*/
public function enableOOUI() {
- OOUI\Theme::setSingleton( new OOUI\MediaWikiTheme() );
- OOUI\Element::setDefaultDir( $this->getLanguage()->getDir() );
- $this->addModuleStyles( 'oojs-ui.styles' );
+ self::setupOOUI(
+ strtolower( $this->getSkin()->getSkinName() ),
+ $this->getLanguage()->getDir()
+ );
+ $this->addModuleStyles( array(
+ 'oojs-ui.styles',
+ 'oojs-ui.styles.icons',
+ 'oojs-ui.styles.indicators',
+ 'oojs-ui.styles.textures',
+ 'mediawiki.widgets.styles',
+ ) );
}
}