/// should be private. To include the variable {{REVISIONID}}
var $mRevisionId = null;
- /// Stores a Title object (of the current page).
- protected $mTitle = null;
-
- /// Stores a User object (the one the page is being rendered for)
- protected $mUser = null;
+ private $mContext;
/**
* An array of stylesheet filenames (relative from skins path), with options
'Cookie' => null
);
+ /**
+ * 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.
+ */
+ function __construct( RequestContext $context = null ) {
+ if ( !isset($context) ) {
+ # Extensions should use `new RequestContext` instead of `new OutputPage` now.
+ wfDeprecated( __METHOD__ );
+ }
+ $this->mContext = $context;
+ }
+
/**
* Redirect to $url rather than displaying the normal page
*
* Filter an array of modules to remove insufficiently trustworthy members, and modules
* which are no longer registered (eg a page is cached before an extension is disabled)
* @param $modules Array
+ * @param $position String if not null, only return modules with this position
* @return Array
*/
- protected function filterModules( $modules, $type = ResourceLoaderModule::TYPE_COMBINED ){
+ protected function filterModules( $modules, $position = null, $type = ResourceLoaderModule::TYPE_COMBINED ){
$resourceLoader = $this->getResourceLoader();
$filteredModules = array();
foreach( $modules as $val ){
$module = $resourceLoader->getModule( $val );
if( $module instanceof ResourceLoaderModule
- && $module->getOrigin() <= $this->getAllowedModules( $type ) )
+ && $module->getOrigin() <= $this->getAllowedModules( $type )
+ && ( is_null( $position ) || $module->getPosition() == $position ) )
{
$filteredModules[] = $val;
}
* Get the list of modules to include on this page
*
* @param $filter Bool whether to filter out insufficiently trustworthy modules
+ * @param $position String if not null, only return modules with this position
* @return Array of module names
*/
- public function getModules( $filter = false, $param = 'mModules' ) {
+ public function getModules( $filter = false, $position = null, $param = 'mModules' ) {
$modules = array_values( array_unique( $this->$param ) );
return $filter
- ? $this->filterModules( $modules )
+ ? $this->filterModules( $modules, $position )
: $modules;
}
* Get the list of module JS to include on this page
* @return array of module names
*/
- public function getModuleScripts( $filter = false ) {
- return $this->getModules( $filter, 'mModuleScripts' );
+ public function getModuleScripts( $filter = false, $position = null ) {
+ return $this->getModules( $filter, $position, 'mModuleScripts' );
}
/**
*
* @return Array of module names
*/
- public function getModuleStyles( $filter = false ) {
- return $this->getModules( $filter, 'mModuleStyles' );
+ public function getModuleStyles( $filter = false, $position = null ) {
+ return $this->getModules( $filter, $position, 'mModuleStyles' );
}
/**
*
* @return Array of module names
*/
- public function getModuleMessages( $filter = false ) {
- return $this->getModules( $filter, 'mModuleMessages' );
+ public function getModuleMessages( $filter = false, $position = null ) {
+ return $this->getModules( $filter, $position, 'mModuleMessages' );
}
/**
* @return Boolean: true iff cache-ok headers was sent.
*/
public function checkLastModified( $timestamp ) {
- global $wgCachePages, $wgCacheEpoch, $wgRequest;
+ global $wgCachePages, $wgCacheEpoch;
if ( !$timestamp || $timestamp == '19700101000000' ) {
wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
# Give a 304 response code and disable body output
wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", false );
ini_set( 'zlib.output_compression', 0 );
- $wgRequest->response()->header( "HTTP/1.1 304 Not Modified" );
+ $this->getRequest()->response()->header( "HTTP/1.1 304 Not Modified" );
$this->sendCacheControl();
$this->disable();
return $this->mPagetitle;
}
+ /**
+ * Get the RequestContext used in this instance
+ *
+ * @return RequestContext
+ */
+ private function getContext() {
+ if ( !isset($this->mContext) ) {
+ wfDebug( __METHOD__ . " called and \$mContext is null. Using RequestContext::getMain(); for sanity\n" );
+ $this->mContext = RequestContext::getMain();
+ }
+ return $this->mContext;
+ }
+
+ /**
+ * Get the WebRequest being used for this instance
+ *
+ * @return WebRequest
+ * @since 1.18
+ */
+ public function getRequest() {
+ return $this->getContext()->getRequest();
+ }
+
/**
* Set the Title object to use
*
* @param $t Title object
*/
public function setTitle( $t ) {
- $this->mTitle = $t;
+ $this->getContext()->setTitle($t);
}
/**
* @return Title
*/
public function getTitle() {
- if ( $this->mTitle instanceof Title ) {
- return $this->mTitle;
- } else {
- wfDebug( __METHOD__ . " called and \$mTitle is null. Return \$wgTitle for sanity\n" );
- global $wgTitle;
- return $wgTitle;
- }
- }
-
- /**
- * Set the User object to use
- *
- * @param $u User object
- */
- public function setUser( $u ) {
- $this->mUser = $u;
+ return $this->getContext()->getTitle();
}
/**
* Get the User object used in this instance
*
* @return User
+ * @since 1.18
*/
public function getUser() {
- if ( !isset($this->mUser) ) {
- wfDebug( __METHOD__ . " called and \$mUser is null. Return \$wgUser for sanity\n" );
- global $wgUser;
- return $wgUser;
- }
- return $this->mUser;
+ return $this->getContext()->getUser();
}
/**
* Get the Skin object used to render this instance
*
* @return Skin
+ * @since 1.18
*/
public function getSkin() {
- // For now we'll just proxy to the user. In the future a saner location for
- // organizing what skin to use may be chosen
- return $this->getUser()->getSkin();
+ return $this->getContext()->getSkin();
}
/**
* Get the templates used on this page
*
* @return Array (namespace => dbKey => revId)
+ * @since 1.18
*/
public function getTemplateIds() {
return $this->mTemplateIds;
* Get the files used on this page
*
* @return Array (dbKey => array('time' => MW timestamp or null, 'sha1' => sha1 or ''))
+ * @since 1.18
*/
public function getImageTimeKeys() {
return $this->mImageTimeKeys;
* @return Boolean
*/
function uncacheableBecauseRequestVars() {
- global $wgRequest;
- return $wgRequest->getText( 'useskin', false ) === false
- && $wgRequest->getText( 'uselang', false ) === false;
+ $request = $this->getRequest();
+ return $request->getText( 'useskin', false ) === false
+ && $request->getText( 'uselang', false ) === false;
}
/**
* @return Boolean
*/
function haveCacheVaryCookies() {
- global $wgRequest;
- $cookieHeader = $wgRequest->getHeader( 'cookie' );
+ $cookieHeader = $this->getRequest()->getHeader( 'cookie' );
if ( $cookieHeader === false ) {
return false;
}
* /w/index.php?title=Main_page&variant=zh-cn should never be served.
*/
function addAcceptLanguage() {
- global $wgRequest, $wgContLang;
- if( !$wgRequest->getCheck( 'variant' ) && $wgContLang->hasVariants() ) {
+ global $wgContLang;
+ if( !$this->getRequest()->getCheck( 'variant' ) && $wgContLang->hasVariants() ) {
$variants = $wgContLang->getVariants();
$aloption = array();
foreach ( $variants as $variant ) {
* Send cache control HTTP headers
*/
public function sendCacheControl() {
- global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgRequest, $wgUseXVO;
+ global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgUseXVO;
- $response = $wgRequest->response();
+ $response = $this->getRequest()->response();
if ( $wgUseETag && $this->mETag ) {
$response->header( "ETag: $this->mETag" );
}
* the object, let's actually output it:
*/
public function output() {
- global $wgOutputEncoding, $wgRequest;
+ global $wgOutputEncoding;
global $wgLanguageCode, $wgDebugRedirects, $wgMimeType;
if( $this->mDoNothing ) {
wfProfileIn( __METHOD__ );
- $response = $wgRequest->response();
+ $response = $this->getRequest()->response();
if ( $this->mRedirect != '' ) {
# Standards require redirect URLs to be absolute
/**
* Produce a "user is blocked" page.
- *
- * @param $return Boolean: whether to have a "return to $wgTitle" message or not.
- * @return nothing
+ * @deprecated since 1.18
*/
- function blockedPage( $return = true ) {
- global $wgContLang, $wgLang;
-
- $this->setPageTitle( wfMsg( 'blockedtitle' ) );
- $this->setRobotPolicy( 'noindex,nofollow' );
- $this->setArticleRelated( false );
-
- $name = $this->getUser()->blockedBy();
- $reason = $this->getUser()->blockedFor();
- if( $reason == '' ) {
- $reason = wfMsg( 'blockednoreason' );
- }
- $blockTimestamp = $wgLang->timeanddate(
- wfTimestamp( TS_MW, $this->getUser()->mBlock->mTimestamp ), true
- );
- $ip = wfGetIP();
-
- $link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]";
-
- $blockid = $this->getUser()->mBlock->getId();
-
- $blockExpiry = $wgLang->formatExpiry( $this->getUser()->mBlock->mExpiry );
-
- if ( $this->getUser()->mBlock->mAuto ) {
- $msg = 'autoblockedtext';
- } else {
- $msg = 'blockedtext';
- }
-
- /* $ip returns who *is* being blocked, $intended contains who was meant to be blocked.
- * This could be a username, an IP range, or a single IP. */
- $intended = $this->getUser()->mBlock->getTarget();
-
- $this->addWikiMsg(
- $msg, $link, $reason, $ip, $name, $blockid, $blockExpiry,
- $intended, $blockTimestamp
- );
-
- # Don't auto-return to special pages
- if( $return ) {
- $return = $this->getTitle()->getNamespace() > -1 ? $this->getTitle() : null;
- $this->returnToMain( null, $return );
- }
+ function blockedPage() {
+ throw new UserBlockedError( $this->getUser()->mBlock );
}
/**
/**
* Display an error page noting that a given permission bit is required.
- *
+ * @deprecated since 1.18, just throw the exception directly
* @param $permission String: key required
*/
public function permissionRequired( $permission ) {
- global $wgLang;
-
- $this->setPageTitle( wfMsg( 'badaccess' ) );
- $this->setHTMLTitle( wfMsg( 'errorpagetitle' ) );
- $this->setRobotPolicy( 'noindex,nofollow' );
- $this->setArticleRelated( false );
- $this->mBodytext = '';
-
- $groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
- User::getGroupsWithPermission( $permission ) );
- if( $groups ) {
- $this->addWikiMsg(
- 'badaccess-groups',
- $wgLang->commaList( $groups ),
- count( $groups )
- );
- } else {
- $this->addWikiMsg( 'badaccess-group0' );
- }
- $this->returnToMain();
+ throw new PermissionsError( $permission );
}
/**
*/
public function loginToUse() {
if( $this->getUser()->isLoggedIn() ) {
- $this->permissionRequired( 'read' );
- return;
+ throw new PermissionsError( 'read' );
}
$this->setPageTitle( wfMsg( 'loginreqtitle' ) );
$this->addWikiText( $this->formatPermissionsErrorMessage( $reasons, $action ) );
} else {
// Wiki is read only
- $this->setPageTitle( wfMsg( 'readonly' ) );
- $reason = wfReadOnlyReason();
- $this->wrapWikiMsg( "<div class='mw-readonly-error'>\n$1\n</div>", array( 'readonlytext', $reason ) );
+ throw new ReadOnlyError;
}
// Show source, if supplied
}
}
+ /**
+ * Turn off regular page output and return an error reponse
+ * for when rate limiting has triggered.
+ */
+ public function rateLimited() {
+ $this->setPageTitle( wfMsg( 'actionthrottled' ) );
+ $this->setRobotPolicy( 'noindex,follow' );
+ $this->setArticleRelated( false );
+ $this->enableClientCache( false );
+ $this->mRedirect = '';
+ $this->clearHTML();
+ $this->setStatusCode( 503 );
+ $this->addWikiMsg( 'actionthrottledtext' );
+
+ $this->returnToMain( null, $this->getTitle() );
+ }
+
+ /**
+ * Show a warning about slave lag
+ *
+ * If the lag is higher than $wgSlaveLagCritical seconds,
+ * then the warning is a bit more obvious. If the lag is
+ * lower than $wgSlaveLagWarning, then no warning is shown.
+ *
+ * @param $lag Integer: slave lag
+ */
+ public function showLagWarning( $lag ) {
+ global $wgSlaveLagWarning, $wgSlaveLagCritical;
+ if( $lag >= $wgSlaveLagWarning ) {
+ $message = $lag < $wgSlaveLagCritical
+ ? 'lag-warn-normal'
+ : 'lag-warn-high';
+ $wrap = Html::rawElement( 'div', array( 'class' => "mw-{$message}" ), "\n$1\n" );
+ $this->wrapWikiMsg( "$wrap\n", array( $message, $this->getContext()->getLang()->formatNum( $lag ) ) );
+ }
+ }
+
/**
* Adds JS-based password security checker
* @param $passwordId String ID of input box containing password
* @param $returntoquery String: query string for the return to link
*/
public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
- global $wgRequest;
-
if ( $returnto == null ) {
- $returnto = $wgRequest->getText( 'returnto' );
+ $returnto = $this->getRequest()->getText( 'returnto' );
}
if ( $returntoquery == null ) {
- $returntoquery = $wgRequest->getText( 'returntoquery' );
+ $returntoquery = $this->getRequest()->getText( 'returntoquery' );
}
if ( $returnto === '' ) {
* @return String: The doctype, opening <html>, and head element.
*/
public function headElement( Skin $sk, $includeStyle = true ) {
- global $wgOutputEncoding, $wgMimeType;
- global $wgUseTrackbacks, $wgHtml5;
- global $wgRequest, $wgLang;
+ global $wgUseTrackbacks;
if ( $sk->commonPrintStylesheet() ) {
$this->addModuleStyles( 'mediawiki.legacy.wikiprintable' );
$ret .= implode( "\n", array(
$this->getHeadLinks( $sk, true ),
$this->buildCssLinks( $sk ),
+ $this->getHeadScripts( $sk ),
$this->getHeadItems()
) );
$bodyAttrs = array();
# Crazy edit-on-double-click stuff
- $action = $wgRequest->getVal( 'action', 'view' );
+ $action = $this->getRequest()->getVal( 'action', 'view' );
if (
$this->getTitle()->getNamespace() != NS_SPECIAL &&
$this->getUser()->getOption( 'editondblclick' )
)
{
- $bodyAttrs['ondblclick'] = "document.location = '" . Xml::escapeJsString( $this->getTitle()->getEditURL() ) . "'";
+ $editUrl = $this->getTitle()->getLocalUrl( $sk->editUrlOptions() );
+ $bodyAttrs['ondblclick'] = "document.location = '" .
+ Xml::escapeJsString( $editUrl ) . "'";
}
# Class bloat
$dir = wfUILang()->getDir();
$bodyAttrs['class'] = "mediawiki $dir";
- if ( $wgLang->capitalizeAllNouns() ) {
+ if ( $this->getContext()->getLang()->capitalizeAllNouns() ) {
# A <body> class is probably not the best way to do this . . .
$bodyAttrs['class'] .= ' capitalize-all-nouns';
}
* @return string html <script> and <style> tags
*/
protected function makeResourceLoaderLink( Skin $skin, $modules, $only, $useESI = false ) {
- global $wgLang, $wgLoadScript, $wgResourceLoaderUseESI,
- $wgResourceLoaderInlinePrivateModules, $wgRequest;
+ global $wgLoadScript, $wgResourceLoaderUseESI,
+ $wgResourceLoaderInlinePrivateModules;
// Lazy-load ResourceLoader
// TODO: Should this be a static function of ResourceLoader instead?
// TODO: Divide off modules starting with "user", and add the user parameter to them
$baseQuery = array(
- 'lang' => $wgLang->getCode(),
+ 'lang' => $this->getContext()->getLang()->getCode(),
'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false',
'skin' => $skin->getSkinName(),
'only' => $only,
if ( $this->isPrintable() ) {
$baseQuery['printable'] = 1;
}
- if ( $wgRequest->getBool( 'handheld' ) ) {
+ if ( $this->getRequest()->getBool( 'handheld' ) ) {
$baseQuery['handheld'] = 1;
}
}
/**
- * Gets the global variables and mScripts; also adds userjs to the end if
- * enabled. Despite the name, these scripts are no longer put in the
- * <head> but at the bottom of the <body>
+ * JS stuff to put in the <head>. This is the startup module, config
+ * vars and modules marked with position 'top'
*
* @param $sk Skin object to use
* @return String: HTML fragment
*/
function getHeadScripts( Skin $sk ) {
- global $wgRequest, $wgUseSiteJs, $wgAllowUserJs;
-
// Startup - this will immediately load jquery and mediawiki modules
$scripts = $this->makeResourceLoaderLink( $sk, 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true );
-
- // Script and Messages "only" requests
- $scripts .= $this->makeResourceLoaderLink( $sk, $this->getModuleScripts( true ), ResourceLoaderModule::TYPE_SCRIPTS );
- $scripts .= $this->makeResourceLoaderLink( $sk, $this->getModuleMessages( true ), ResourceLoaderModule::TYPE_MESSAGES );
-
- // Modules requests - let the client calculate dependencies and batch requests as it likes
- $loader = '';
- if ( $this->getModules( true ) ) {
- $loader = Xml::encodeJsCall( 'mw.loader.load', array( $this->getModules( true ) ) ) .
- Xml::encodeJsCall( 'mw.loader.go', array() );
- }
+ // Load config before anything else
$scripts .= Html::inlineScript(
ResourceLoader::makeLoaderConditionalScript(
- ResourceLoader::makeConfigSetScript( $this->getJSVars() ) . $loader
+ ResourceLoader::makeConfigSetScript( $this->getJSVars() )
)
);
+
+ // Script and Messages "only" requests marked for top inclusion
+ // Messages should go first
+ $scripts .= $this->makeResourceLoaderLink( $sk, $this->getModuleMessages( true, 'top' ), ResourceLoaderModule::TYPE_MESSAGES );
+ $scripts .= $this->makeResourceLoaderLink( $sk, $this->getModuleScripts( true, 'top' ), ResourceLoaderModule::TYPE_SCRIPTS );
+
+ // 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 ) {
+ $scripts .= Html::inlineScript(
+ ResourceLoader::makeLoaderConditionalScript(
+ Xml::encodeJsCall( 'mw.loader.load', array( $modules ) ) .
+ Xml::encodeJsCall( 'mw.loader.go', array() )
+ )
+ );
+ }
+ 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
+ */
+ function getBottomScripts( Skin $sk ) {
+ global $wgUseSiteJs, $wgAllowUserJs;
+
+ // Script and Messages "only" requests marked for bottom inclusion
+ // Messages should go first
+ $scripts = $this->makeResourceLoaderLink( $sk, $this->getModuleMessages( true, 'bottom' ), ResourceLoaderModule::TYPE_MESSAGES );
+ $scripts .= $this->makeResourceLoaderLink( $sk, $this->getModuleScripts( true, 'bottom' ), ResourceLoaderModule::TYPE_SCRIPTS );
+
+ // 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 ) {
+ $scripts .= Html::inlineScript(
+ ResourceLoader::makeLoaderConditionalScript(
+ Xml::encodeJsCall( 'mw.loader.load', array( $modules ) ) .
+ // the go() call is unnecessary if we inserted top modules, but we don't know for sure that we did
+ Xml::encodeJsCall( 'mw.loader.go', array() )
+ )
+ );
+ }
+
// Legacy Scripts
$scripts .= "\n" . $this->mScripts;
// Add user JS if enabled
if ( $wgAllowUserJs && $this->getUser()->isLoggedIn() ) {
- $action = $wgRequest->getVal( 'action', 'view' );
- if( $this->mTitle && $this->mTitle->isJsSubpage() && $sk->userCanPreview( $action ) ) {
+ $action = $this->getRequest()->getVal( 'action', 'view' );
+ if( $this->getTitle() && $this->getTitle()->isJsSubpage() && $sk->userCanPreview( $action ) ) {
# XXX: additional security check/prompt?
- $scripts .= Html::inlineScript( "\n" . $wgRequest->getText( 'wpTextbox1' ) . "\n" ) . "\n";
+ $scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n";
} else {
# FIXME: this means that User:Me/Common.js doesn't load when previewing
# User:Me/Vector.js, and vice versa (bug26283)
}
}
$scripts .= $this->makeResourceLoaderLink( $sk, $userScripts, ResourceLoaderModule::TYPE_SCRIPTS );
-
+
return $scripts;
}
* have to be purged on configuration changes.
*/
protected function getJSVars() {
- global $wgRequest, $wgUseAjax, $wgEnableMWSuggest, $wgContLang;
+ global $wgUseAjax, $wgEnableMWSuggest, $wgContLang;
$title = $this->getTitle();
$ns = $title->getNamespace();
'wgCurRevisionId' => $title->getLatestRevID(),
'wgArticleId' => $title->getArticleId(),
'wgIsArticle' => $this->isArticle(),
- 'wgAction' => $wgRequest->getText( 'action', 'view' ),
+ 'wgAction' => $this->getRequest()->getText( 'action', 'view' ),
'wgUserName' => $this->getUser()->isAnon() ? null : $this->getUser()->getName(),
'wgUserGroups' => $this->getUser()->getEffectiveGroups(),
'wgCategories' => $this->getCategories(),
return $media;
}
- /**
- * Turn off regular page output and return an error reponse
- * for when rate limiting has triggered.
- */
- public function rateLimited() {
- $this->setPageTitle( wfMsg( 'actionthrottled' ) );
- $this->setRobotPolicy( 'noindex,follow' );
- $this->setArticleRelated( false );
- $this->enableClientCache( false );
- $this->mRedirect = '';
- $this->clearHTML();
- $this->setStatusCode( 503 );
- $this->addWikiMsg( 'actionthrottledtext' );
-
- $this->returnToMain( null, $this->getTitle() );
- }
-
- /**
- * Show a warning about slave lag
- *
- * If the lag is higher than $wgSlaveLagCritical seconds,
- * then the warning is a bit more obvious. If the lag is
- * lower than $wgSlaveLagWarning, then no warning is shown.
- *
- * @param $lag Integer: slave lag
- */
- public function showLagWarning( $lag ) {
- global $wgSlaveLagWarning, $wgSlaveLagCritical, $wgLang;
- if( $lag >= $wgSlaveLagWarning ) {
- $message = $lag < $wgSlaveLagCritical
- ? 'lag-warn-normal'
- : 'lag-warn-high';
- $wrap = Html::rawElement( 'div', array( 'class' => "mw-{$message}" ), "\n$1\n" );
- $this->wrapWikiMsg( "$wrap\n", array( $message, $wgLang->formatNum( $lag ) ) );
- }
- }
-
/**
* Add a wikitext-formatted message to the output.
* This is equivalent to: