<?php
-if ( !defined( 'MEDIAWIKI' ) ) {
- die( 1 );
-}
+/**
+ * Preparation for the final page rendering.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
/**
* This class should be covered by a general architecture document which does
/// Should be private. Used with addMeta() which adds <meta>
var $mMetatags = array();
- /// <meta keyworkds="stuff"> most of the time the first 10 links to an article
+ /// <meta keywords="stuff"> most of the time the first 10 links to an article
var $mKeywords = array();
var $mLinktags = array();
/**
* Add new language links
*
- * @param $newLinkArray Associative array mapping language code to the page
+ * @param $newLinkArray array Associative array mapping language code to the page
* name
*/
public function addLanguageLinks( $newLinkArray ) {
/**
* Reset the language links and add new language links
*
- * @param $newLinkArray Associative array mapping language code to the page
+ * @param $newLinkArray array Associative array mapping language code to the page
* name
*/
public function setLanguageLinks( $newLinkArray ) {
/**
* Get/set the ParserOptions object to use for wikitext parsing
*
- * @param $options either the ParserOption to use or null to only get the
+ * @param $options ParserOptions|null either the ParserOption to use or null to only get the
* current ParserOption object
* @return ParserOptions object
*/
/**
* Set the displayed file version
*
- * @param $file File|false
+ * @param $file File|bool
* @return Mixed: previous value
*/
public function setFileVersion( $file ) {
return $cookies;
}
- /**
- * Return whether this page is not cacheable because "useskin" or "uselang"
- * URL parameters were passed.
- *
- * @return Boolean
- */
- function uncacheableBecauseRequestVars() {
- $request = $this->getRequest();
- return $request->getText( 'useskin', false ) === false
- && $request->getText( 'uselang', false ) === false;
- }
-
/**
* Check if the request has a cache-varying cookie header
* If it does, it's very important that we don't allow public caching
$response->header( $this->getXVO() );
}
- if( !$this->uncacheableBecauseRequestVars() && $this->mEnableClientCache ) {
+ if( $this->mEnableClientCache ) {
if(
$wgUseSquid && session_id() == '' && !$this->isPrintable() &&
$this->mSquidMaxage != 0 && !$this->haveCacheVaryCookies()
* Add a "return to" link pointing to a specified title,
* or the title indicated in the request, or else the main page
*
- * @param $unused No longer used
+ * @param $unused
* @param $returnto Title or String to return to
* @param $returntoquery String: query string for the return to link
*/
$this->addModules( 'mediawiki.util' );
}
+ MWDebug::addModules( $this );
+
// Add various resources if required
if ( $wgUseAjax ) {
$this->addModules( 'mediawiki.legacy.ajax' );
* @param $only String ResourceLoaderModule TYPE_ class constant
* @param $useESI boolean
* @param $extraQuery Array with extra query parameters to add to each request. array( param => value )
- * @param $loadCall boolean If true, output a mw.loader.load() call rather than a <script src="..."> tag
+ * @param $loadCall boolean If true, output an (asynchronous) mw.loader.load() call rather than a <script src="..."> tag
* @return string html <script> and <style> tags
*/
protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array(), $loadCall = false ) {
- global $wgResourceLoaderUseESI, $wgResourceLoaderInlinePrivateModules;
+ global $wgResourceLoaderUseESI;
if ( !count( $modules ) ) {
return '';
$extraQuery
);
$context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
- // Drop modules that know they're empty
+ // Extract modules that know they're empty
+ $emptyModules = array ();
foreach ( $modules as $key => $module ) {
if ( $module->isKnownEmpty( $context ) ) {
+ $emptyModules[$key] = 'ready';
unset( $modules[$key] );
}
}
+ // Inline empty modules: since they're empty, just mark them as 'ready'
+ if ( count( $emptyModules ) > 0 && $only !== ResourceLoaderModule::TYPE_STYLES ) {
+ // If we're only getting the styles, we don't need to do anything for empty modules.
+ $links .= Html::inlineScript(
+
+ ResourceLoader::makeLoaderConditionalScript(
+
+ ResourceLoader::makeLoaderStateScript( $emptyModules )
+
+ )
+
+ ) . "\n";
+ }
+
// If there are no modules left, skip this group
if ( $modules === array() ) {
continue;
}
- // Support inlining of private modules if configured as such. Note that these
- // modules should be loaded from getHeadScripts() before the first loader call.
- // Otherwise other modules can't properly use them as dependencies (bug 30914)
- if ( $group === 'private' && $wgResourceLoaderInlinePrivateModules ) {
+ // 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
+ // properly use them as dependencies (bug 30914)
+ if ( $group === 'private' ) {
if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
$links .= Html::inlineStyle(
$resourceLoader->makeModuleResponse( $context, $modules )
// Automatically select style/script elements
if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
$link = Html::linkedStyle( $url );
- } else if ( $loadCall ) {
+ } else if ( $loadCall ) {
$link = Html::inlineScript(
ResourceLoader::makeLoaderConditionalScript(
- Xml::encodeJsCall( 'mw.loader.load', array( $url ) )
+ Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) )
)
);
} else {
*/
function getHeadScripts() {
global $wgResourceLoaderExperimentalAsyncLoading;
-
+
// Startup - this will immediately load jquery and mediawiki modules
$scripts = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true );
);
// 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' );
$scripts .= $this->makeResourceLoaderLink( $embedScripts, ResourceLoaderModule::TYPE_COMBINED );
if ( $modules ) {
$scripts .= Html::inlineScript(
ResourceLoader::makeLoaderConditionalScript(
- Xml::encodeJsCall( 'mw.loader.load', array( $modules, null, true ) )
+ Xml::encodeJsCall( 'mw.loader.load', array( $modules ) )
)
);
}
-
+
if ( $wgResourceLoaderExperimentalAsyncLoading ) {
$scripts .= $this->getScriptsForBottomQueue( true );
}
if ( $modules ) {
$scripts .= Html::inlineScript(
ResourceLoader::makeLoaderConditionalScript(
- Xml::encodeJsCall( 'mw.loader.load', array( $modules ) )
+ Xml::encodeJsCall( 'mw.loader.load', array( $modules, null, true ) )
)
);
}
// Legacy Scripts
$scripts .= "\n" . $this->mScripts;
- $userScripts = array();
+ $defaultModules = array();
// Add site JS if enabled
if ( $wgUseSiteJs ) {
$scripts .= $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS,
/* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
);
- if( $this->getUser()->isLoggedIn() ){
- $userScripts[] = 'user.groups';
- }
+ $defaultModules['site'] = 'loading';
+ } else {
+ // The wiki is configured to not allow a site module.
+ $defaultModules['site'] = 'missing';
}
// Add user JS if enabled
- if ( $wgAllowUserJs && $this->getUser()->isLoggedIn() ) {
- if( $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)
- $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false,
- array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead
- );
- // Load the previewed JS
- $scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n";
+ if ( $wgAllowUserJs ) {
+ if ( $this->getUser()->isLoggedIn() ) {
+ if( $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)
+ $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false,
+ array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead
+ );
+ // Load the previewed JS
+ $scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n";
+ // 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...
+ } else {
+ // Include the user module normally, i.e., raw to avoid it being wrapped in a closure.
+ $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS,
+ /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
+ );
+ }
+ $defaultModules['user'] = 'loading';
} else {
- // Include the user module normally
- // We can't do $userScripts[] = 'user'; because the user module would end up
- // being wrapped in a closure, so load it raw like 'site'
- $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS,
+ // Non-logged-in users have no user module. Treat it as empty and 'ready' to avoid
+ // blocking default gadgets that might depend on it. Although arguably default-enabled
+ // gadgets should not depend on the user module, it's harmless and less error-prone to
+ // handle this case.
+ $defaultModules['user'] = 'ready';
+ }
+ } else {
+ // User JS disabled
+ $defaultModules['user'] = 'missing';
+ }
+
+ // Group JS is only enabled if site JS is enabled.
+ if ( $wgUseSiteJs ) {
+ if ( $this->getUser()->isLoggedIn() ) {
+ $scripts .= $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED,
/* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
);
+ $defaultModules['user.groups'] = 'loading';
+ } else {
+ // Non-logged-in users have no user.groups module. Treat it as empty and 'ready' to
+ // avoid blocking gadgets that might depend upon the module.
+ $defaultModules['user.groups'] = 'ready';
}
+ } else {
+ // Site (and group JS) disabled
+ $defaultModules['user.groups'] = 'missing';
}
- $scripts .= $this->makeResourceLoaderLink( $userScripts, ResourceLoaderModule::TYPE_COMBINED,
- /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
- );
- return $scripts;
+ $loaderInit = '';
+ if ( $inHead ) {
+ // We generate loader calls anyway, so no need to fix the client-side loader's state to 'loading'.
+ foreach ( $defaultModules as $m => $state ) {
+ if ( $state == 'loading' ) {
+ unset( $defaultModules[$m] );
+ }
+ }
+ }
+ if ( count( $defaultModules ) > 0 ) {
+ $loaderInit = Html::inlineScript(
+ ResourceLoader::makeLoaderConditionalScript(
+ ResourceLoader::makeLoaderStateScript( $defaultModules )
+ )
+ ) . "\n";
+ }
+ return $loaderInit . $scripts;
}
/**
* JS stuff to put at the bottom of the <body>
+ * @return string
*/
function getBottomScripts() {
global $wgResourceLoaderExperimentalAsyncLoading;
* @return array
*/
public function getJSVars() {
- global $wgUseAjax, $wgEnableMWSuggest;
+ global $wgUseAjax, $wgEnableMWSuggest, $wgContLang;
$latestRevID = 0;
$pageID = 0;
$ns = $title->getNamespace();
$nsname = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $title->getNsText();
+ // Get the relevant title so that AJAX features can use the correct page name
+ // when making API requests from certain special pages (bug 34972).
+ $relevantTitle = $this->getSkin()->getRelevantTitle();
+
if ( $ns == NS_SPECIAL ) {
list( $canonicalName, /*...*/ ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
} elseif ( $this->canUseWikiPage() ) {
'wgPageContentLanguage' => $lang->getCode(),
'wgSeparatorTransformTable' => $compactSeparatorTransTable,
'wgDigitTransformTable' => $compactDigitTransTable,
+ 'wgRelevantPageName' => $relevantTitle->getPrefixedDBKey(),
);
- if ( $lang->hasVariants() ) {
- $vars['wgUserVariant'] = $lang->getPreferredVariant();
+ if ( $wgContLang->hasVariants() ) {
+ $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
}
foreach ( $title->getRestrictionTypes() as $type ) {
$vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
}
/**
- * @param $unused Unused
- * @param $addContentType bool
+ * @param $addContentType bool: Whether <meta> specifying content type should be returned
*
- * @return string HTML tag links to be put in the header.
+ * @return array in format "link name or number => 'link html'".
*/
- public function getHeadLinks( $unused = null, $addContentType = false ) {
+ public function getHeadLinksArray( $addContentType = false ) {
global $wgUniversalEditButton, $wgFavicon, $wgAppleTouchIcon, $wgEnableAPI,
$wgSitename, $wgVersion, $wgHtml5, $wgMimeType,
$wgFeed, $wgOverrideSiteFeed, $wgAdvertisedFeedTypes,
if ( $wgHtml5 ) {
# More succinct than <meta http-equiv=Content-Type>, has the
# same effect
- $tags[] = Html::element( 'meta', array( 'charset' => 'UTF-8' ) );
+ $tags['meta-charset'] = Html::element( 'meta', array( 'charset' => 'UTF-8' ) );
} else {
- $tags[] = Html::element( 'meta', array(
+ $tags['meta-content-type'] = Html::element( 'meta', array(
'http-equiv' => 'Content-Type',
'content' => "$wgMimeType; charset=UTF-8"
) );
- $tags[] = Html::element( 'meta', array( // bug 15835
+ $tags['meta-content-style-type'] = Html::element( 'meta', array( // bug 15835
'http-equiv' => 'Content-Style-Type',
'content' => 'text/css'
) );
}
}
- $tags[] = Html::element( 'meta', array(
+ $tags['meta-generator'] = Html::element( 'meta', array(
'name' => 'generator',
'content' => "MediaWiki $wgVersion",
) );
if( $p !== 'index,follow' ) {
// http://www.robotstxt.org/wc/meta-user.html
// Only show if it's different from the default robots policy
- $tags[] = Html::element( 'meta', array(
+ $tags['meta-robots'] = Html::element( 'meta', array(
'name' => 'robots',
'content' => $p,
) );
"/<.*?" . ">/" => '',
"/_/" => ' '
);
- $tags[] = Html::element( 'meta', array(
+ $tags['meta-keywords'] = Html::element( 'meta', array(
'name' => 'keywords',
'content' => preg_replace(
array_keys( $strip ),
} else {
$a = 'name';
}
- $tags[] = Html::element( 'meta',
+ $tagName = "meta-{$tag[0]}";
+ if ( isset( $tags[$tagName] ) ) {
+ $tagName .= $tag[1];
+ }
+ $tags[$tagName] = Html::element( 'meta',
array(
$a => $tag[0],
'content' => $tag[1]
&& ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create', $user ) ) ) {
// Original UniversalEditButton
$msg = $this->msg( 'edit' )->text();
- $tags[] = Html::element( 'link', array(
+ $tags['universal-edit-button'] = Html::element( 'link', array(
'rel' => 'alternate',
'type' => 'application/x-wiki',
'title' => $msg,
'href' => $this->getTitle()->getLocalURL( 'action=edit' )
) );
// Alternate edit link
- $tags[] = Html::element( 'link', array(
+ $tags['alternative-edit'] = Html::element( 'link', array(
'rel' => 'edit',
'title' => $msg,
'href' => $this->getTitle()->getLocalURL( 'action=edit' )
# uses whichever one appears later in the HTML source. Make sure
# apple-touch-icon is specified first to avoid this.
if ( $wgAppleTouchIcon !== false ) {
- $tags[] = Html::element( 'link', array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) );
+ $tags['apple-touch-icon'] = Html::element( 'link', array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) );
}
if ( $wgFavicon !== false ) {
- $tags[] = Html::element( 'link', array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) );
+ $tags['favicon'] = Html::element( 'link', array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) );
}
# OpenSearch description link
- $tags[] = Html::element( 'link', array(
+ $tags['opensearch'] = Html::element( 'link', array(
'rel' => 'search',
'type' => 'application/opensearchdescription+xml',
'href' => wfScript( 'opensearch_desc' ),
# for the MediaWiki API (and potentially additional custom API
# support such as WordPress or Twitter-compatible APIs for a
# blogging extension, etc)
- $tags[] = Html::element( 'link', array(
+ $tags['rsd'] = Html::element( 'link', array(
'rel' => 'EditURI',
'type' => 'application/rsd+xml',
// Output a protocol-relative URL here if $wgServer is protocol-relative
if ( !$urlvar ) {
$variants = $lang->getVariants();
foreach ( $variants as $_v ) {
- $tags[] = Html::element( 'link', array(
+ $tags["variant-$_v"] = Html::element( 'link', array(
'rel' => 'alternate',
'hreflang' => $_v,
'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) )
);
}
} else {
- $tags[] = Html::element( 'link', array(
+ $tags['canonical'] = Html::element( 'link', array(
'rel' => 'canonical',
'href' => $this->getTitle()->getCanonicalUrl()
) );
}
if ( $copyright ) {
- $tags[] = Html::element( 'link', array(
+ $tags['copyright'] = Html::element( 'link', array(
'rel' => 'copyright',
'href' => $copyright )
);
}
}
}
- return implode( "\n", $tags );
+ return $tags;
+ }
+
+ /**
+ * @param $unused
+ * @param $addContentType bool: Whether <meta> specifying content type should be returned
+ *
+ * @return string HTML tag links to be put in the header.
+ */
+ public function getHeadLinks( $unused = null, $addContentType = false ) {
+ return implode( "\n", $this->getHeadLinksArray( $addContentType ) );
}
/**
$otherTags .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES, false,
array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() )
);
-
+
// Load the previewed CSS
// If needed, Janus it first. This is user-supplied CSS, so it's
// assumed to be right for the content language directionality.
// Per-user preference styles
if ( $wgAllowUserCssPrefs ) {
- $moduleStyles[] = 'user.options';
+ $moduleStyles[] = 'user.cssprefs';
}
foreach ( $moduleStyles as $name ) {
* @param $args array
*/
public function addWikiMsgArray( $name, $args ) {
- $this->addWikiText( $this->msg( $name, $args )->plain() );
+ $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
}
/**