/// should be private. To include the variable {{REVISIONID}}
var $mRevisionId = null;
+ private $mRevisionTimestamp = null;
var $mFileVersion = null;
'Cookie' => null
);
+ /**
+ * If the current page was reached through a redirect, $mRedirectedFrom contains the Title
+ * of the redirect.
+ *
+ * @var Title
+ */
+ private $mRedirectedFrom = null;
+
/**
* Constructor for OutputPage. This should not be called directly.
* Instead a new RequestContext should be created and it will implicitly create
return $this->mHTMLtitle;
}
+ /**
+ * Set $mRedirectedFrom, the Title of the page which redirected us to the current page.
+ *
+ * param @t Title
+ */
+ public function setRedirectedFrom( $t ) {
+ $this->mRedirectedFrom = $t;
+ }
+
/**
* "Page title" means the contents of \<h1\>. It is stored as a valid HTML fragment.
* This function allows good tags like \<sup\> in the \<h1\> tag, but not bad tags like \<script\>.
$this->mPagetitle = $nameWithTags;
# change "<i>foo&bar</i>" to "foo&bar"
- $this->setHTMLTitle( $this->msg( 'pagetitle', Sanitizer::stripAllTags( $nameWithTags ) ) );
+ $this->setHTMLTitle( $this->msg( 'pagetitle' )->rawParams( Sanitizer::stripAllTags( $nameWithTags ) ) );
}
/**
/**
* 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 ) {
* Return whether user JavaScript is allowed for this page
* @deprecated since 1.18 Load modules with ResourceLoader, and origin and
* trustworthiness is identified and enforced automagically.
+ * Will be removed in 1.20.
* @return Boolean
*/
public function isUserJsAllowed() {
+ wfDeprecated( __METHOD__, '1.18' );
return $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) >= ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL;
}
return $this->mRevisionId;
}
+ /**
+ * Set the timestamp of the revision which will be displayed. This is used
+ * to avoid a extra DB call in Skin::lastModified().
+ *
+ * @param $revid Mixed: string, or null
+ * @return Mixed: previous value
+ */
+ public function setRevisionTimestamp( $timestmap ) {
+ return wfSetVar( $this->mRevisionTimestamp, $timestmap );
+ }
+
+ /**
+ * Get the timestamp of displayed revision.
+ * This will be null if not filled by setRevisionTimestamp().
+ *
+ * @return String or null
+ */
+ public function getRevisionTimestamp() {
+ return $this->mRevisionTimestamp;
+ }
+
/**
* Set the displayed file version
*
- * @param $file File|false
+ * @param $file File|bool
* @return Mixed: previous value
*/
public function setFileVersion( $file ) {
wfProfileIn( __METHOD__ );
- wfIncrStats( 'pcache_not_possible' );
-
$popts = $this->parserOptions();
$oldTidy = $popts->setTidy( $tidy );
$popts->setInterfaceMessage( (bool) $interface );
if ( $this->mRedirect != '' ) {
# Standards require redirect URLs to be absolute
$this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT );
- if( $this->mRedirectCode == '301' || $this->mRedirectCode == '303' ) {
- if( !$wgDebugRedirects ) {
- $message = HttpStatus::getMessage( $this->mRedirectCode );
- $response->header( "HTTP/1.1 {$this->mRedirectCode} $message" );
+
+ $redirect = $this->mRedirect;
+ $code = $this->mRedirectCode;
+
+ if( wfRunHooks( "BeforePageRedirect", array( $this, &$redirect, &$code ) ) ) {
+ if( $code == '301' || $code == '303' ) {
+ if( !$wgDebugRedirects ) {
+ $message = HttpStatus::getMessage( $code );
+ $response->header( "HTTP/1.1 $code $message" );
+ }
+ $this->mLastModified = wfTimestamp( TS_RFC2822 );
+ }
+ if ( $wgVaryOnXFP ) {
+ $this->addVaryHeader( 'X-Forwarded-Proto' );
+ }
+ $this->sendCacheControl();
+
+ $response->header( "Content-Type: text/html; charset=utf-8" );
+ if( $wgDebugRedirects ) {
+ $url = htmlspecialchars( $redirect );
+ print "<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
+ print "<p>Location: <a href=\"$url\">$url</a></p>\n";
+ print "</body>\n</html>\n";
+ } else {
+ $response->header( 'Location: ' . $redirect );
}
- $this->mLastModified = wfTimestamp( TS_RFC2822 );
- }
- if ( $wgVaryOnXFP ) {
- $this->addVaryHeader( 'X-Forwarded-Proto' );
- }
- $this->sendCacheControl();
-
- $response->header( "Content-Type: text/html; charset=utf-8" );
- if( $wgDebugRedirects ) {
- $url = htmlspecialchars( $this->mRedirect );
- print "<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
- print "<p>Location: <a href=\"$url\">$url</a></p>\n";
- print "</body>\n</html>\n";
- } else {
- $response->header( 'Location: ' . $this->mRedirect );
}
+
wfProfileOut( __METHOD__ );
return;
} elseif ( $this->mStatusCode ) {
|| ( isset( $wgGroupPermissions['autoconfirmed'][$action] ) && $wgGroupPermissions['autoconfirmed'][$action] ) )
) {
$displayReturnto = null;
- $returnto = $this->getTitle();
+
+ # Due to bug 32276, if a user does not have read permissions,
+ # $this->getTitle() will just give Special:Badtitle, which is
+ # not especially useful as a returnto parameter. Use the title
+ # from the request instead, if there was one.
+ $request = $this->getRequest();
+ $returnto = Title::newFromURL( $request->getVal( 'title', '' ) );
if ( $action == 'edit' ) {
$msg = 'whitelistedittext';
$displayReturnto = $returnto;
}
$query = array();
+
if ( $returnto ) {
$query['returnto'] = $returnto->getPrefixedText();
- $request = $this->getRequest();
+
if ( !$request->wasPosted() ) {
$returntoquery = $request->getValues();
unset( $returntoquery['title'] );
# Don't return to a page the user can't read otherwise
# we'll end up in a pointless loop
- if ( $displayReturnto && $displayReturnto->userCanRead() ) {
+ if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) {
$this->returnToMain( null, $displayReturnto );
}
} else {
'cols' => $this->getUser()->getOption( 'cols' ),
'rows' => $this->getUser()->getOption( 'rows' ),
'readonly' => 'readonly',
- 'lang' => $pageLang->getCode(),
+ 'lang' => $pageLang->getHtmlCode(),
'dir' => $pageLang->getDir(),
);
$this->addHTML( Html::element( 'textarea', $params, $source ) );
// Show templates used by this article
- $page = WikiPage::factory( $this->getTitle() );
- $templates = Linker::formatTemplates( $page->getUsedTemplates() );
+ $templates = Linker::formatTemplates( $this->getTitle()->getTemplateLinksFrom() );
$this->addHTML( "<div class='templatesUsed'>
$templates
</div>
*/
public function headElement( Skin $sk, $includeStyle = true ) {
global $wgContLang;
+
$userdir = $this->getLanguage()->getDir();
$sitedir = $wgContLang->getDir();
$this->addModuleStyles( 'mediawiki.legacy.wikiprintable' );
}
- $ret = Html::htmlHeader( array( 'lang' => $this->getLanguage()->getCode(), 'dir' => $userdir, 'class' => 'client-nojs' ) );
+ $ret = Html::htmlHeader( array( 'lang' => $this->getLanguage()->getHtmlCode(), 'dir' => $userdir, 'class' => 'client-nojs' ) );
if ( $this->getHTMLTitle() == '' ) {
$this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() ) );
}
$bodyAttrs['class'] .= ' ' . $sk->getPageClasses( $this->getTitle() );
$bodyAttrs['class'] .= ' skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
+ $bodyAttrs['class'] .= ' action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
$sk->addToBodyAttributes( $this, $bodyAttrs ); // Allow skins to add body attributes they need
wfRunHooks( 'OutputPageBodyAttributes', array( $this, $sk, &$bodyAttrs ) );
* Add the default ResourceLoader modules to this object
*/
private function addDefaultModules() {
- global $wgIncludeLegacyJavaScript, $wgUseAjax, $wgAjaxWatch, $wgEnableMWSuggest;
+ global $wgIncludeLegacyJavaScript, $wgPreloadJavaScriptMwUtil, $wgUseAjax,
+ $wgAjaxWatch, $wgEnableMWSuggest;
// Add base resources
$this->addModules( array(
'mediawiki.user',
- 'mediawiki.util',
'mediawiki.page.startup',
'mediawiki.page.ready',
) );
$this->addModules( 'mediawiki.legacy.wikibits' );
}
+ if ( $wgPreloadJavaScriptMwUtil ) {
+ $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
* @return string html <script> and <style> tags
*/
- protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array() ) {
+ protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array(), $loadCall = false ) {
global $wgResourceLoaderUseESI, $wgResourceLoaderInlinePrivateModules;
if ( !count( $modules ) ) {
continue;
}
- // Support inlining of private modules if configured as such
+ // 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 ) {
if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
$links .= Html::inlineStyle(
// Automatically select style/script elements
if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
$link = Html::linkedStyle( $url );
+ } else if ( $loadCall ) {
+ $link = Html::inlineScript(
+ ResourceLoader::makeLoaderConditionalScript(
+ Xml::encodeJsCall( 'mw.loader.load', array( $url ) )
+ )
+ );
} else {
$link = Html::linkedScript( $url );
}
* @return String: HTML fragment
*/
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 );
+
// Script and Messages "only" requests marked for top inclusion
// Messages should go first
$scripts .= $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'top' ), ResourceLoaderModule::TYPE_MESSAGES );
if ( $modules ) {
$scripts .= Html::inlineScript(
ResourceLoader::makeLoaderConditionalScript(
- Xml::encodeJsCall( 'mw.loader.load', array( $modules ) )
+ Xml::encodeJsCall( 'mw.loader.load', array( $modules, null, true ) )
)
);
}
+
+ if ( $wgResourceLoaderExperimentalAsyncLoading ) {
+ $scripts .= $this->getScriptsForBottomQueue( true );
+ }
return $scripts;
}
/**
- * JS stuff to put at the bottom of the <body>: modules marked with position 'bottom',
- * legacy scripts ($this->mScripts), user preferences, site JS and user JS
+ * 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
*
+ * @param $inHead boolean If true, this HTML goes into the <head>, if false it goes into the <body>
* @return string
*/
- function getBottomScripts() {
+ function getScriptsForBottomQueue( $inHead ) {
global $wgUseSiteJs, $wgAllowUserJs;
// Script and Messages "only" requests marked for bottom inclusion
+ // If we're in the <head>, use load() calls rather than <script src="..."> tags
// Messages should go first
- $scripts = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'bottom' ), ResourceLoaderModule::TYPE_MESSAGES );
- $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ), ResourceLoaderModule::TYPE_SCRIPTS );
+ $scripts = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'bottom' ),
+ ResourceLoaderModule::TYPE_MESSAGES, /* $useESI = */ false, /* $extraQuery = */ array(),
+ /* $loadCall = */ $inHead
+ );
+ $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ),
+ ResourceLoaderModule::TYPE_SCRIPTS, /* $useESI = */ false, /* $extraQuery = */ array(),
+ /* $loadCall = */ $inHead
+ );
// 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
// Legacy Scripts
$scripts .= "\n" . $this->mScripts;
- $userScripts = array( 'user.options', 'user.tokens' );
+ $userScripts = array();
// Add site JS if enabled
if ( $wgUseSiteJs ) {
- $scripts .= $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS );
+ $scripts .= $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS,
+ /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
+ );
if( $this->getUser()->isLoggedIn() ){
$userScripts[] = 'user.groups';
}
// 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() )
+ array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead
);
// Load the previewed JS
$scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n";
// 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 );
+ $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS,
+ /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
+ );
}
}
- $scripts .= $this->makeResourceLoaderLink( $userScripts, ResourceLoaderModule::TYPE_COMBINED );
+ $scripts .= $this->makeResourceLoaderLink( $userScripts, ResourceLoaderModule::TYPE_COMBINED,
+ /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
+ );
return $scripts;
}
+ /**
+ * JS stuff to put at the bottom of the <body>
+ */
+ function getBottomScripts() {
+ global $wgResourceLoaderExperimentalAsyncLoading;
+ if ( !$wgResourceLoaderExperimentalAsyncLoading ) {
+ return $this->getScriptsForBottomQueue( false );
+ } else {
+ return '';
+ }
+ }
+
/**
* Add one or more variables to be set in mw.config in JavaScript.
*
* @param $key {String|Array} Key or array of key/value pars.
- * @param $value {Mixed} Value of the configuration variable.
+ * @param $value {Mixed} [optional] Value of the configuration variable.
*/
- public function addJsConfigVars( $keys, $value ) {
+ public function addJsConfigVars( $keys, $value = null ) {
if ( is_array( $keys ) ) {
foreach ( $keys as $key => $value ) {
$this->mJsConfigVars[$key] = $value;
/**
* Get an array containing the variables to be set in mw.config in JavaScript.
*
+ * DO NOT CALL THIS FROM OUTSIDE OF THIS CLASS OR Skin::makeGlobalVariablesScript().
+ * This is only public until that function is removed. You have been warned.
+ *
* Do not add things here which can be evaluated in ResourceLoaderStartupScript
* - in other words, page-independent/site-wide variables (without state).
* You will only be adding bloat to the html page and causing page caches to
* have to be purged on configuration changes.
+ * @return array
*/
- protected function getJSVars() {
+ public function getJSVars() {
global $wgUseAjax, $wgEnableMWSuggest;
+ $latestRevID = 0;
+ $pageID = 0;
+ $canonicalName = false; # bug 21115
+
$title = $this->getTitle();
$ns = $title->getNamespace();
$nsname = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $title->getNsText();
+
if ( $ns == NS_SPECIAL ) {
list( $canonicalName, /*...*/ ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
- } else {
- $canonicalName = false; # bug 21115
+ } elseif ( $this->canUseWikiPage() ) {
+ $wikiPage = $this->getWikiPage();
+ $latestRevID = $wikiPage->getLatest();
+ $pageID = $wikiPage->getId();
}
+ $lang = $title->getPageLanguage();
+
+ // Pre-process information
+ $separatorTransTable = $lang->separatorTransformTable();
+ $separatorTransTable = $separatorTransTable ? $separatorTransTable : array();
+ $compactSeparatorTransTable = array(
+ implode( "\t", array_keys( $separatorTransTable ) ),
+ implode( "\t", $separatorTransTable ),
+ );
+ $digitTransTable = $lang->digitTransformTable();
+ $digitTransTable = $digitTransTable ? $digitTransTable : array();
+ $compactDigitTransTable = array(
+ implode( "\t", array_keys( $digitTransTable ) ),
+ implode( "\t", $digitTransTable ),
+ );
+
$vars = array(
'wgCanonicalNamespace' => $nsname,
'wgCanonicalSpecialPageName' => $canonicalName,
'wgNamespaceNumber' => $title->getNamespace(),
'wgPageName' => $title->getPrefixedDBKey(),
'wgTitle' => $title->getText(),
- 'wgCurRevisionId' => $title->getLatestRevID(),
- 'wgArticleId' => $title->getArticleId(),
+ 'wgCurRevisionId' => $latestRevID,
+ 'wgArticleId' => $pageID,
'wgIsArticle' => $this->isArticle(),
- 'wgAction' => $this->getRequest()->getText( 'action', 'view' ),
+ 'wgAction' => Action::getActionName( $this->getContext() ),
'wgUserName' => $this->getUser()->isAnon() ? null : $this->getUser()->getName(),
'wgUserGroups' => $this->getUser()->getEffectiveGroups(),
'wgCategories' => $this->getCategories(),
'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
+ 'wgPageContentLanguage' => $lang->getCode(),
+ 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
+ 'wgDigitTransformTable' => $compactDigitTransTable,
);
- $lang = $this->getTitle()->getPageLanguage();
if ( $lang->hasVariants() ) {
$vars['wgUserVariant'] = $lang->getPreferredVariant();
}
if ( $title->isMainPage() ) {
$vars['wgIsMainPage'] = true;
}
+ if ( $this->mRedirectedFrom ) {
+ $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBKey();
+ }
// Allow extensions to add their custom variables to the mw.config map.
// Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
$tags[] = Html::element( 'link', array(
'rel' => 'alternate',
'hreflang' => $_v,
- 'href' => $this->getTitle()->getLocalURL( '', $_v ) )
+ 'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) )
);
}
} else {
);
}
} elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
+ $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
foreach ( $wgAdvertisedFeedTypes as $format ) {
$tags[] = $this->feedLink(
$format,
- $this->getTitle()->getLocalURL( "feed={$format}" ),
+ $rctitle->getLocalURL( "feed={$format}" ),
$this->msg( "site-{$format}-feed", $wgSitename )->text() # For grep: 'site-rss-feed', 'site-atom-feed'.
);
}
* @return string
*/
public function buildCssLinks() {
- global $wgUseSiteCss, $wgAllowUserCss, $wgAllowUserCssPrefs;
+ global $wgUseSiteCss, $wgAllowUserCss, $wgAllowUserCssPrefs,
+ $wgLang, $wgContLang;
$this->getSkin()->setupSkinUserCss( $this );
$otherTags .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES, false,
array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() )
);
+
// Load the previewed CSS
- $otherTags .= Html::inlineStyle( $this->getRequest()->getText( 'wpTextbox1' ) );
+ // If needed, Janus it first. This is user-supplied CSS, so it's
+ // assumed to be right for the content language directionality.
+ $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' );
+ if ( $wgLang->getDir() !== $wgContLang->getDir() ) {
+ $previewedCSS = CSSJanus::transform( $previewedCSS, true, false );
+ }
+ $otherTags .= Html::inlineStyle( $previewedCSS );
} else {
// Load the user styles normally
$moduleStyles[] = 'user';
// Per-user preference styles
if ( $wgAllowUserCssPrefs ) {
- $moduleStyles[] = 'user.options';
+ $moduleStyles[] = 'user.cssprefs';
}
foreach ( $moduleStyles as $name ) {