MediaWiki  REL1_19
OutputPage.php
Go to the documentation of this file.
00001 <?php
00002 if ( !defined( 'MEDIAWIKI' ) ) {
00003         die( 1 );
00004 }
00005 
00021 class OutputPage extends ContextSource {
00023         var $mMetatags = array();
00024 
00026         var $mKeywords = array();
00027 
00028         var $mLinktags = array();
00029 
00031         var $mExtStyles = array();
00032 
00034         var $mPagetitle = '';
00035 
00037         var $mBodytext = '';
00038 
00044         public $mDebugtext = ''; // TODO: we might want to replace it by wfDebug() wfDebugLog()
00045 
00047         var $mHTMLtitle = '';
00048 
00050         var $mIsarticle = false;
00051 
00056         var $mIsArticleRelated = true;
00057 
00062         var $mPrintable = false;
00063 
00070         private $mSubtitle = array();
00071 
00072         var $mRedirect = '';
00073         var $mStatusCode;
00074 
00079         var $mLastModified = '';
00080 
00091         var $mETag = false;
00092 
00093         var $mCategoryLinks = array();
00094         var $mCategories = array();
00095 
00097         var $mLanguageLinks = array();
00098 
00105         var $mScripts = '';
00106 
00110         var $mInlineStyles = '';
00111 
00112         //
00113         var $mLinkColours;
00114 
00119         var $mPageLinkTitle = '';
00120 
00122         var $mHeadItems = array();
00123 
00124         // @todo FIXME: Next variables probably comes from the resource loader
00125         var $mModules = array(), $mModuleScripts = array(), $mModuleStyles = array(), $mModuleMessages = array();
00126         var $mResourceLoader;
00127         var $mJsConfigVars = array();
00128 
00130         var $mInlineMsg = array();
00131 
00132         var $mTemplateIds = array();
00133         var $mImageTimeKeys = array();
00134 
00135         var $mRedirectCode = '';
00136 
00137         var $mFeedLinksAppendQuery = null;
00138 
00144         protected $mAllowedModuleOrigin = ResourceLoaderModule::ORIGIN_ALL;
00145 
00150         var $mDoNothing = false;
00151 
00152         // Parser related.
00153         var $mContainsOldMagic = 0, $mContainsNewMagic = 0;
00154 
00159         protected $mParserOptions = null;
00160 
00167         var $mFeedLinks = array();
00168 
00169         // Gwicke work on squid caching? Roughly from 2003.
00170         var $mEnableClientCache = true;
00171 
00176         var $mArticleBodyOnly = false;
00177 
00178         var $mNewSectionLink = false;
00179         var $mHideNewSectionLink = false;
00180 
00186         var $mNoGallery = false;
00187 
00188         // should be private.
00189         var $mPageTitleActionText = '';
00190         var $mParseWarnings = array();
00191 
00192         // Cache stuff. Looks like mEnableClientCache
00193         var $mSquidMaxage = 0;
00194 
00195         // @todo document
00196         var $mPreventClickjacking = true;
00197 
00199         var $mRevisionId = null;
00200         private $mRevisionTimestamp = null;
00201 
00202         var $mFileVersion = null;
00203 
00212         var $styles = array();
00213 
00217         protected $mJQueryDone = false;
00218 
00219         private $mIndexPolicy = 'index';
00220         private $mFollowPolicy = 'follow';
00221         private $mVaryHeader = array(
00222                 'Accept-Encoding' => array( 'list-contains=gzip' ),
00223                 'Cookie' => null
00224         );
00225 
00232         private $mRedirectedFrom = null;
00233 
00239         function __construct( IContextSource $context = null ) {
00240                 if ( $context === null ) {
00241                         # Extensions should use `new RequestContext` instead of `new OutputPage` now.
00242                         wfDeprecated( __METHOD__ );
00243                 } else {
00244                         $this->setContext( $context );
00245                 }
00246         }
00247 
00254         public function redirect( $url, $responsecode = '302' ) {
00255                 # Strip newlines as a paranoia check for header injection in PHP<5.1.2
00256                 $this->mRedirect = str_replace( "\n", '', $url );
00257                 $this->mRedirectCode = $responsecode;
00258         }
00259 
00265         public function getRedirect() {
00266                 return $this->mRedirect;
00267         }
00268 
00274         public function setStatusCode( $statusCode ) {
00275                 $this->mStatusCode = $statusCode;
00276         }
00277 
00285         function addMeta( $name, $val ) {
00286                 array_push( $this->mMetatags, array( $name, $val ) );
00287         }
00288 
00294         function addKeyword( $text ) {
00295                 if( is_array( $text ) ) {
00296                         $this->mKeywords = array_merge( $this->mKeywords, $text );
00297                 } else {
00298                         array_push( $this->mKeywords, $text );
00299                 }
00300         }
00301 
00307         function addLink( $linkarr ) {
00308                 array_push( $this->mLinktags, $linkarr );
00309         }
00310 
00318         function addMetadataLink( $linkarr ) {
00319                 $linkarr['rel'] = $this->getMetadataAttribute();
00320                 $this->addLink( $linkarr );
00321         }
00322 
00328         public function getMetadataAttribute() {
00329                 # note: buggy CC software only reads first "meta" link
00330                 static $haveMeta = false;
00331                 if ( $haveMeta ) {
00332                         return 'alternate meta';
00333                 } else {
00334                         $haveMeta = true;
00335                         return 'meta';
00336                 }
00337         }
00338 
00344         function addScript( $script ) {
00345                 $this->mScripts .= $script . "\n";
00346         }
00347 
00356         public function addExtensionStyle( $url ) {
00357                 array_push( $this->mExtStyles, $url );
00358         }
00359 
00365         function getExtStyle() {
00366                 return $this->mExtStyles;
00367         }
00368 
00376         public function addScriptFile( $file, $version = null ) {
00377                 global $wgStylePath, $wgStyleVersion;
00378                 // See if $file parameter is an absolute URL or begins with a slash
00379                 if( substr( $file, 0, 1 ) == '/' || preg_match( '#^[a-z]*://#i', $file ) ) {
00380                         $path = $file;
00381                 } else {
00382                         $path = "{$wgStylePath}/common/{$file}";
00383                 }
00384                 if ( is_null( $version ) )
00385                         $version = $wgStyleVersion;
00386                 $this->addScript( Html::linkedScript( wfAppendQuery( $path, $version ) ) );
00387         }
00388 
00394         public function addInlineScript( $script ) {
00395                 $this->mScripts .= Html::inlineScript( "\n$script\n" ) . "\n";
00396         }
00397 
00403         function getScript() {
00404                 return $this->mScripts . $this->getHeadItems();
00405         }
00406 
00415         protected function filterModules( $modules, $position = null, $type = ResourceLoaderModule::TYPE_COMBINED ){
00416                 $resourceLoader = $this->getResourceLoader();
00417                 $filteredModules = array();
00418                 foreach( $modules as $val ){
00419                         $module = $resourceLoader->getModule( $val );
00420                         if( $module instanceof ResourceLoaderModule
00421                                 && $module->getOrigin() <= $this->getAllowedModules( $type )
00422                                 && ( is_null( $position ) || $module->getPosition() == $position ) )
00423                         {
00424                                 $filteredModules[] = $val;
00425                         }
00426                 }
00427                 return $filteredModules;
00428         }
00429 
00438         public function getModules( $filter = false, $position = null, $param = 'mModules' ) {
00439                 $modules = array_values( array_unique( $this->$param ) );
00440                 return $filter
00441                         ? $this->filterModules( $modules, $position )
00442                         : $modules;
00443         }
00444 
00452         public function addModules( $modules ) {
00453                 $this->mModules = array_merge( $this->mModules, (array)$modules );
00454         }
00455 
00464         public function getModuleScripts( $filter = false, $position = null ) {
00465                 return $this->getModules( $filter, $position, 'mModuleScripts' );
00466         }
00467 
00475         public function addModuleScripts( $modules ) {
00476                 $this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules );
00477         }
00478 
00487         public function getModuleStyles( $filter = false, $position = null ) {
00488                 return $this->getModules( $filter,  $position, 'mModuleStyles' );
00489         }
00490 
00498         public function addModuleStyles( $modules ) {
00499                 $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
00500         }
00501 
00510         public function getModuleMessages( $filter = false, $position = null ) {
00511                 return $this->getModules( $filter, $position, 'mModuleMessages' );
00512         }
00513 
00521         public function addModuleMessages( $modules ) {
00522                 $this->mModuleMessages = array_merge( $this->mModuleMessages, (array)$modules );
00523         }
00524 
00530         function getHeadItemsArray() {
00531                 return $this->mHeadItems;
00532         }
00533 
00539         function getHeadItems() {
00540                 $s = '';
00541                 foreach ( $this->mHeadItems as $item ) {
00542                         $s .= $item;
00543                 }
00544                 return $s;
00545         }
00546 
00553         public function addHeadItem( $name, $value ) {
00554                 $this->mHeadItems[$name] = $value;
00555         }
00556 
00563         public function hasHeadItem( $name ) {
00564                 return isset( $this->mHeadItems[$name] );
00565         }
00566 
00572         function setETag( $tag ) {
00573                 $this->mETag = $tag;
00574         }
00575 
00583         public function setArticleBodyOnly( $only ) {
00584                 $this->mArticleBodyOnly = $only;
00585         }
00586 
00592         public function getArticleBodyOnly() {
00593                 return $this->mArticleBodyOnly;
00594         }
00595 
00607         public function checkLastModified( $timestamp ) {
00608                 global $wgCachePages, $wgCacheEpoch;
00609 
00610                 if ( !$timestamp || $timestamp == '19700101000000' ) {
00611                         wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
00612                         return false;
00613                 }
00614                 if( !$wgCachePages ) {
00615                         wfDebug( __METHOD__ . ": CACHE DISABLED\n", false );
00616                         return false;
00617                 }
00618                 if( $this->getUser()->getOption( 'nocache' ) ) {
00619                         wfDebug( __METHOD__ . ": USER DISABLED CACHE\n", false );
00620                         return false;
00621                 }
00622 
00623                 $timestamp = wfTimestamp( TS_MW, $timestamp );
00624                 $modifiedTimes = array(
00625                         'page' => $timestamp,
00626                         'user' => $this->getUser()->getTouched(),
00627                         'epoch' => $wgCacheEpoch
00628                 );
00629                 wfRunHooks( 'OutputPageCheckLastModified', array( &$modifiedTimes ) );
00630 
00631                 $maxModified = max( $modifiedTimes );
00632                 $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
00633 
00634                 if( empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
00635                         wfDebug( __METHOD__ . ": client did not send If-Modified-Since header\n", false );
00636                         return false;
00637                 }
00638 
00639                 # Make debug info
00640                 $info = '';
00641                 foreach ( $modifiedTimes as $name => $value ) {
00642                         if ( $info !== '' ) {
00643                                 $info .= ', ';
00644                         }
00645                         $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
00646                 }
00647 
00648                 # IE sends sizes after the date like this:
00649                 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
00650                 # this breaks strtotime().
00651                 $clientHeader = preg_replace( '/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"] );
00652 
00653                 wfSuppressWarnings(); // E_STRICT system time bitching
00654                 $clientHeaderTime = strtotime( $clientHeader );
00655                 wfRestoreWarnings();
00656                 if ( !$clientHeaderTime ) {
00657                         wfDebug( __METHOD__ . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
00658                         return false;
00659                 }
00660                 $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
00661 
00662                 wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
00663                         wfTimestamp( TS_ISO_8601, $clientHeaderTime ) . "\n", false );
00664                 wfDebug( __METHOD__ . ": effective Last-Modified: " .
00665                         wfTimestamp( TS_ISO_8601, $maxModified ) . "\n", false );
00666                 if( $clientHeaderTime < $maxModified ) {
00667                         wfDebug( __METHOD__ . ": STALE, $info\n", false );
00668                         return false;
00669                 }
00670 
00671                 # Not modified
00672                 # Give a 304 response code and disable body output
00673                 wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", false );
00674                 ini_set( 'zlib.output_compression', 0 );
00675                 $this->getRequest()->response()->header( "HTTP/1.1 304 Not Modified" );
00676                 $this->sendCacheControl();
00677                 $this->disable();
00678 
00679                 // Don't output a compressed blob when using ob_gzhandler;
00680                 // it's technically against HTTP spec and seems to confuse
00681                 // Firefox when the response gets split over two packets.
00682                 wfClearOutputBuffers();
00683 
00684                 return true;
00685         }
00686 
00693         public function setLastModified( $timestamp ) {
00694                 $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp );
00695         }
00696 
00705         public function setRobotPolicy( $policy ) {
00706                 $policy = Article::formatRobotPolicy( $policy );
00707 
00708                 if( isset( $policy['index'] ) ) {
00709                         $this->setIndexPolicy( $policy['index'] );
00710                 }
00711                 if( isset( $policy['follow'] ) ) {
00712                         $this->setFollowPolicy( $policy['follow'] );
00713                 }
00714         }
00715 
00723         public function setIndexPolicy( $policy ) {
00724                 $policy = trim( $policy );
00725                 if( in_array( $policy, array( 'index', 'noindex' ) ) ) {
00726                         $this->mIndexPolicy = $policy;
00727                 }
00728         }
00729 
00737         public function setFollowPolicy( $policy ) {
00738                 $policy = trim( $policy );
00739                 if( in_array( $policy, array( 'follow', 'nofollow' ) ) ) {
00740                         $this->mFollowPolicy = $policy;
00741                 }
00742         }
00743 
00750         public function setPageTitleActionText( $text ) {
00751                 $this->mPageTitleActionText = $text;
00752         }
00753 
00759         public function getPageTitleActionText() {
00760                 if ( isset( $this->mPageTitleActionText ) ) {
00761                         return $this->mPageTitleActionText;
00762                 }
00763         }
00764 
00771         public function setHTMLTitle( $name ) {
00772                 if ( $name instanceof Message ) {
00773                         $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
00774                 } else {
00775                         $this->mHTMLtitle = $name;
00776                 }
00777         }
00778 
00784         public function getHTMLTitle() {
00785                 return $this->mHTMLtitle;
00786         }
00787 
00793         public function setRedirectedFrom( $t ) {
00794                 $this->mRedirectedFrom = $t;
00795         }
00796 
00805         public function setPageTitle( $name ) {
00806                 if ( $name instanceof Message ) {
00807                         $name = $name->setContext( $this->getContext() )->text();
00808                 }
00809 
00810                 # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
00811                 # but leave "<i>foobar</i>" alone
00812                 $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) );
00813                 $this->mPagetitle = $nameWithTags;
00814 
00815                 # change "<i>foo&amp;bar</i>" to "foo&bar"
00816                 $this->setHTMLTitle( $this->msg( 'pagetitle' )->rawParams( Sanitizer::stripAllTags( $nameWithTags ) ) );
00817         }
00818 
00824         public function getPageTitle() {
00825                 return $this->mPagetitle;
00826         }
00827 
00833         public function setTitle( Title $t ) {
00834                 $this->getContext()->setTitle( $t );
00835         }
00836 
00837 
00843         public function setSubtitle( $str ) {
00844                 $this->clearSubtitle();
00845                 $this->addSubtitle( $str );
00846         }
00847 
00854         public function appendSubtitle( $str ) {
00855                 $this->addSubtitle( $str );
00856         }
00857 
00863         public function addSubtitle( $str ) {
00864                 if ( $str instanceof Message ) {
00865                         $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
00866                 } else {
00867                         $this->mSubtitle[] = $str;
00868                 }
00869         }
00870 
00876         public function addBacklinkSubtitle( Title $title ) {
00877                 $query = array();
00878                 if ( $title->isRedirect() ) {
00879                         $query['redirect'] = 'no';
00880                 }
00881                 $this->addSubtitle( $this->msg( 'backlinksubtitle' )->rawParams( Linker::link( $title, null, array(), $query ) ) );
00882         }
00883 
00887         public function clearSubtitle() {
00888                 $this->mSubtitle = array();
00889         }
00890 
00896         public function getSubtitle() {
00897                 return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
00898         }
00899 
00904         public function setPrintable() {
00905                 $this->mPrintable = true;
00906         }
00907 
00913         public function isPrintable() {
00914                 return $this->mPrintable;
00915         }
00916 
00920         public function disable() {
00921                 $this->mDoNothing = true;
00922         }
00923 
00929         public function isDisabled() {
00930                 return $this->mDoNothing;
00931         }
00932 
00938         public function showNewSectionLink() {
00939                 return $this->mNewSectionLink;
00940         }
00941 
00947         public function forceHideNewSectionLink() {
00948                 return $this->mHideNewSectionLink;
00949         }
00950 
00959         public function setSyndicated( $show = true ) {
00960                 if ( $show ) {
00961                         $this->setFeedAppendQuery( false );
00962                 } else {
00963                         $this->mFeedLinks = array();
00964                 }
00965         }
00966 
00976         public function setFeedAppendQuery( $val ) {
00977                 global $wgAdvertisedFeedTypes;
00978 
00979                 $this->mFeedLinks = array();
00980 
00981                 foreach ( $wgAdvertisedFeedTypes as $type ) {
00982                         $query = "feed=$type";
00983                         if ( is_string( $val ) ) {
00984                                 $query .= '&' . $val;
00985                         }
00986                         $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
00987                 }
00988         }
00989 
00996         public function addFeedLink( $format, $href ) {
00997                 global $wgAdvertisedFeedTypes;
00998 
00999                 if ( in_array( $format, $wgAdvertisedFeedTypes ) ) {
01000                         $this->mFeedLinks[$format] = $href;
01001                 }
01002         }
01003 
01008         public function isSyndicated() {
01009                 return count( $this->mFeedLinks ) > 0;
01010         }
01011 
01016         public function getSyndicationLinks() {
01017                 return $this->mFeedLinks;
01018         }
01019 
01025         public function getFeedAppendQuery() {
01026                 return $this->mFeedLinksAppendQuery;
01027         }
01028 
01036         public function setArticleFlag( $v ) {
01037                 $this->mIsarticle = $v;
01038                 if ( $v ) {
01039                         $this->mIsArticleRelated = $v;
01040                 }
01041         }
01042 
01049         public function isArticle() {
01050                 return $this->mIsarticle;
01051         }
01052 
01059         public function setArticleRelated( $v ) {
01060                 $this->mIsArticleRelated = $v;
01061                 if ( !$v ) {
01062                         $this->mIsarticle = false;
01063                 }
01064         }
01065 
01071         public function isArticleRelated() {
01072                 return $this->mIsArticleRelated;
01073         }
01074 
01081         public function addLanguageLinks( $newLinkArray ) {
01082                 $this->mLanguageLinks += $newLinkArray;
01083         }
01084 
01091         public function setLanguageLinks( $newLinkArray ) {
01092                 $this->mLanguageLinks = $newLinkArray;
01093         }
01094 
01100         public function getLanguageLinks() {
01101                 return $this->mLanguageLinks;
01102         }
01103 
01109         public function addCategoryLinks( $categories ) {
01110                 global $wgContLang;
01111 
01112                 if ( !is_array( $categories ) || count( $categories ) == 0 ) {
01113                         return;
01114                 }
01115 
01116                 # Add the links to a LinkBatch
01117                 $arr = array( NS_CATEGORY => $categories );
01118                 $lb = new LinkBatch;
01119                 $lb->setArray( $arr );
01120 
01121                 # Fetch existence plus the hiddencat property
01122                 $dbr = wfGetDB( DB_SLAVE );
01123                 $res = $dbr->select( array( 'page', 'page_props' ),
01124                         array( 'page_id', 'page_namespace', 'page_title', 'page_len', 'page_is_redirect', 'page_latest', 'pp_value' ),
01125                         $lb->constructSet( 'page', $dbr ),
01126                         __METHOD__,
01127                         array(),
01128                         array( 'page_props' => array( 'LEFT JOIN', array( 'pp_propname' => 'hiddencat', 'pp_page = page_id' ) ) )
01129                 );
01130 
01131                 # Add the results to the link cache
01132                 $lb->addResultToCache( LinkCache::singleton(), $res );
01133 
01134                 # Set all the values to 'normal'. This can be done with array_fill_keys in PHP 5.2.0+
01135                 $categories = array_combine(
01136                         array_keys( $categories ),
01137                         array_fill( 0, count( $categories ), 'normal' )
01138                 );
01139 
01140                 # Mark hidden categories
01141                 foreach ( $res as $row ) {
01142                         if ( isset( $row->pp_value ) ) {
01143                                 $categories[$row->page_title] = 'hidden';
01144                         }
01145                 }
01146 
01147                 # Add the remaining categories to the skin
01148                 if ( wfRunHooks( 'OutputPageMakeCategoryLinks', array( &$this, $categories, &$this->mCategoryLinks ) ) ) {
01149                         foreach ( $categories as $category => $type ) {
01150                                 $origcategory = $category;
01151                                 $title = Title::makeTitleSafe( NS_CATEGORY, $category );
01152                                 $wgContLang->findVariantLink( $category, $title, true );
01153                                 if ( $category != $origcategory ) {
01154                                         if ( array_key_exists( $category, $categories ) ) {
01155                                                 continue;
01156                                         }
01157                                 }
01158                                 $text = $wgContLang->convertHtml( $title->getText() );
01159                                 $this->mCategories[] = $title->getText();
01160                                 $this->mCategoryLinks[$type][] = Linker::link( $title, $text );
01161                         }
01162                 }
01163         }
01164 
01170         public function setCategoryLinks( $categories ) {
01171                 $this->mCategoryLinks = array();
01172                 $this->addCategoryLinks( $categories );
01173         }
01174 
01183         public function getCategoryLinks() {
01184                 return $this->mCategoryLinks;
01185         }
01186 
01192         public function getCategories() {
01193                 return $this->mCategories;
01194         }
01195 
01202         public function disallowUserJs() {
01203                 $this->reduceAllowedModuleOrigin( ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL );
01204         }
01205 
01213         public function isUserJsAllowed() {
01214                 wfDeprecated( __METHOD__, '1.18' );
01215                 return $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS ) >= ResourceLoaderModule::ORIGIN_USER_INDIVIDUAL;
01216         }
01217 
01226         public function getAllowedModules( $type = null ) {
01227                 return $this->mAllowedModuleOrigin;
01228         }
01229 
01239         public function setAllowedModules( $type, $level ){
01240                 wfDeprecated( __METHOD__, '1.24' );
01241                 $this->reduceAllowedModuleOrigin( $level );
01242         }
01243 
01253         public function reduceAllowedModules( $type, $level ){
01254                 wfDeprecated( __METHOD__, '1.24' );
01255                 $this->reduceAllowedModuleOrigin( $level );
01256         }
01257 
01266         public function reduceAllowedModuleOrigin( $level ) {
01267                 $this->mAllowedModuleOrigin = min( $this->mAllowedModuleOrigin, $level );
01268         }
01269 
01275         public function prependHTML( $text ) {
01276                 $this->mBodytext = $text . $this->mBodytext;
01277         }
01278 
01284         public function addHTML( $text ) {
01285                 $this->mBodytext .= $text;
01286         }
01287 
01297         public function addElement( $element, $attribs = array(), $contents = '' ) {
01298                 $this->addHTML( Html::element( $element, $attribs, $contents ) );
01299         }
01300 
01304         public function clearHTML() {
01305                 $this->mBodytext = '';
01306         }
01307 
01313         public function getHTML() {
01314                 return $this->mBodytext;
01315         }
01316 
01322         public function debug( $text ) {
01323                 $this->mDebugtext .= $text;
01324         }
01325 
01333         public function parserOptions( $options = null ) {
01334                 if ( !$this->mParserOptions ) {
01335                         $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
01336                         $this->mParserOptions->setEditSection( false );
01337                 }
01338                 return wfSetVar( $this->mParserOptions, $options );
01339         }
01340 
01348         public function setRevisionId( $revid ) {
01349                 $val = is_null( $revid ) ? null : intval( $revid );
01350                 return wfSetVar( $this->mRevisionId, $val );
01351         }
01352 
01358         public function getRevisionId() {
01359                 return $this->mRevisionId;
01360         }
01361 
01369         public function setRevisionTimestamp( $timestmap ) {
01370                 return wfSetVar( $this->mRevisionTimestamp, $timestmap );
01371         }
01372 
01379         public function getRevisionTimestamp() {
01380                 return $this->mRevisionTimestamp;
01381         }
01382 
01389         public function setFileVersion( $file ) {
01390                 $val = null;
01391                 if ( $file instanceof File && $file->exists() ) {
01392                         $val = array( 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() );
01393                 }
01394                 return wfSetVar( $this->mFileVersion, $val, true );
01395         }
01396 
01402         public function getFileVersion() {
01403                 return $this->mFileVersion;
01404         }
01405 
01412         public function getTemplateIds() {
01413                 return $this->mTemplateIds;
01414         }
01415 
01422         public function getFileSearchOptions() {
01423                 return $this->mImageTimeKeys;
01424         }
01425 
01434         public function addWikiText( $text, $linestart = true, $interface = true ) {
01435                 $title = $this->getTitle(); // Work arround E_STRICT
01436                 $this->addWikiTextTitle( $text, $title, $linestart, /*tidy*/false, $interface );
01437         }
01438 
01446         public function addWikiTextWithTitle( $text, &$title, $linestart = true ) {
01447                 $this->addWikiTextTitle( $text, $title, $linestart );
01448         }
01449 
01457         function addWikiTextTitleTidy( $text, &$title, $linestart = true ) {
01458                 $this->addWikiTextTitle( $text, $title, $linestart, true );
01459         }
01460 
01467         public function addWikiTextTidy( $text, $linestart = true ) {
01468                 $title = $this->getTitle();
01469                 $this->addWikiTextTitleTidy( $text, $title, $linestart );
01470         }
01471 
01482         public function addWikiTextTitle( $text, &$title, $linestart, $tidy = false, $interface = false ) {
01483                 global $wgParser;
01484 
01485                 wfProfileIn( __METHOD__ );
01486 
01487                 $popts = $this->parserOptions();
01488                 $oldTidy = $popts->setTidy( $tidy );
01489                 $popts->setInterfaceMessage( (bool) $interface );
01490 
01491                 $parserOutput = $wgParser->parse(
01492                         $text, $title, $popts,
01493                         $linestart, true, $this->mRevisionId
01494                 );
01495 
01496                 $popts->setTidy( $oldTidy );
01497 
01498                 $this->addParserOutput( $parserOutput );
01499 
01500                 wfProfileOut( __METHOD__ );
01501         }
01502 
01508         public function addParserOutputNoText( &$parserOutput ) {
01509                 $this->mLanguageLinks += $parserOutput->getLanguageLinks();
01510                 $this->addCategoryLinks( $parserOutput->getCategories() );
01511                 $this->mNewSectionLink = $parserOutput->getNewSection();
01512                 $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
01513 
01514                 $this->mParseWarnings = $parserOutput->getWarnings();
01515                 if ( !$parserOutput->isCacheable() ) {
01516                         $this->enableClientCache( false );
01517                 }
01518                 $this->mNoGallery = $parserOutput->getNoGallery();
01519                 $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
01520                 $this->addModules( $parserOutput->getModules() );
01521                 $this->addModuleScripts( $parserOutput->getModuleScripts() );
01522                 $this->addModuleStyles( $parserOutput->getModuleStyles() );
01523                 $this->addModuleMessages( $parserOutput->getModuleMessages() );
01524                 $this->mPreventClickjacking = $this->mPreventClickjacking
01525                         || $parserOutput->preventClickjacking();
01526 
01527                 // Template versioning...
01528                 foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
01529                         if ( isset( $this->mTemplateIds[$ns] ) ) {
01530                                 $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
01531                         } else {
01532                                 $this->mTemplateIds[$ns] = $dbks;
01533                         }
01534                 }
01535                 // File versioning...
01536                 foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) {
01537                         $this->mImageTimeKeys[$dbk] = $data;
01538                 }
01539 
01540                 // Hooks registered in the object
01541                 global $wgParserOutputHooks;
01542                 foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
01543                         list( $hookName, $data ) = $hookInfo;
01544                         if ( isset( $wgParserOutputHooks[$hookName] ) ) {
01545                                 call_user_func( $wgParserOutputHooks[$hookName], $this, $parserOutput, $data );
01546                         }
01547                 }
01548 
01549                 wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) );
01550         }
01551 
01557         function addParserOutput( &$parserOutput ) {
01558                 $this->addParserOutputNoText( $parserOutput );
01559                 $text = $parserOutput->getText();
01560                 wfRunHooks( 'OutputPageBeforeHTML', array( &$this, &$text ) );
01561                 $this->addHTML( $text );
01562         }
01563 
01564 
01570         public function addTemplate( &$template ) {
01571                 ob_start();
01572                 $template->execute();
01573                 $this->addHTML( ob_get_contents() );
01574                 ob_end_clean();
01575         }
01576 
01590         public function parse( $text, $linestart = true, $interface = false, $language = null ) {
01591                 global $wgParser;
01592 
01593                 if( is_null( $this->getTitle() ) ) {
01594                         throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
01595                 }
01596 
01597                 $popts = $this->parserOptions();
01598                 if ( $interface ) {
01599                         $popts->setInterfaceMessage( true );
01600                 }
01601                 if ( $language !== null ) {
01602                         $oldLang = $popts->setTargetLanguage( $language );
01603                 }
01604 
01605                 $parserOutput = $wgParser->parse(
01606                         $text, $this->getTitle(), $popts,
01607                         $linestart, true, $this->mRevisionId
01608                 );
01609 
01610                 if ( $interface ) {
01611                         $popts->setInterfaceMessage( false );
01612                 }
01613                 if ( $language !== null ) {
01614                         $popts->setTargetLanguage( $oldLang );
01615                 }
01616 
01617                 return $parserOutput->getText();
01618         }
01619 
01630         public function parseInline( $text, $linestart = true, $interface = false ) {
01631                 $parsed = $this->parse( $text, $linestart, $interface );
01632 
01633                 $m = array();
01634                 if ( preg_match( '/^<p>(.*)\n?<\/p>\n?/sU', $parsed, $m ) ) {
01635                         $parsed = $m[1];
01636                 }
01637 
01638                 return $parsed;
01639         }
01640 
01646         public function setSquidMaxage( $maxage ) {
01647                 $this->mSquidMaxage = $maxage;
01648         }
01649 
01657         public function enableClientCache( $state ) {
01658                 return wfSetVar( $this->mEnableClientCache, $state );
01659         }
01660 
01666         function getCacheVaryCookies() {
01667                 global $wgCookiePrefix, $wgCacheVaryCookies;
01668                 static $cookies;
01669                 if ( $cookies === null ) {
01670                         $cookies = array_merge(
01671                                 array(
01672                                         "{$wgCookiePrefix}Token",
01673                                         "{$wgCookiePrefix}LoggedOut",
01674                                         session_name()
01675                                 ),
01676                                 $wgCacheVaryCookies
01677                         );
01678                         wfRunHooks( 'GetCacheVaryCookies', array( $this, &$cookies ) );
01679                 }
01680                 return $cookies;
01681         }
01682 
01689         function uncacheableBecauseRequestVars() {
01690                 $request = $this->getRequest();
01691                 return $request->getText( 'useskin', false ) === false
01692                         && $request->getText( 'uselang', false ) === false;
01693         }
01694 
01701         function haveCacheVaryCookies() {
01702                 $cookieHeader = $this->getRequest()->getHeader( 'cookie' );
01703                 if ( $cookieHeader === false ) {
01704                         return false;
01705                 }
01706                 $cvCookies = $this->getCacheVaryCookies();
01707                 foreach ( $cvCookies as $cookieName ) {
01708                         # Check for a simple string match, like the way squid does it
01709                         if ( strpos( $cookieHeader, $cookieName ) !== false ) {
01710                                 wfDebug( __METHOD__ . ": found $cookieName\n" );
01711                                 return true;
01712                         }
01713                 }
01714                 wfDebug( __METHOD__ . ": no cache-varying cookies found\n" );
01715                 return false;
01716         }
01717 
01726         public function addVaryHeader( $header, $option = null ) {
01727                 if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
01728                         $this->mVaryHeader[$header] = (array)$option;
01729                 } elseif( is_array( $option ) ) {
01730                         if( is_array( $this->mVaryHeader[$header] ) ) {
01731                                 $this->mVaryHeader[$header] = array_merge( $this->mVaryHeader[$header], $option );
01732                         } else {
01733                                 $this->mVaryHeader[$header] = $option;
01734                         }
01735                 }
01736                 $this->mVaryHeader[$header] = array_unique( (array)$this->mVaryHeader[$header] );
01737         }
01738 
01744         public function getXVO() {
01745                 $cvCookies = $this->getCacheVaryCookies();
01746 
01747                 $cookiesOption = array();
01748                 foreach ( $cvCookies as $cookieName ) {
01749                         $cookiesOption[] = 'string-contains=' . $cookieName;
01750                 }
01751                 $this->addVaryHeader( 'Cookie', $cookiesOption );
01752 
01753                 $headers = array();
01754                 foreach( $this->mVaryHeader as $header => $option ) {
01755                         $newheader = $header;
01756                         if( is_array( $option ) ) {
01757                                 $newheader .= ';' . implode( ';', $option );
01758                         }
01759                         $headers[] = $newheader;
01760                 }
01761                 $xvo = 'X-Vary-Options: ' . implode( ',', $headers );
01762 
01763                 return $xvo;
01764         }
01765 
01774         function addAcceptLanguage() {
01775                 $lang = $this->getTitle()->getPageLanguage();
01776                 if( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
01777                         $variants = $lang->getVariants();
01778                         $aloption = array();
01779                         foreach ( $variants as $variant ) {
01780                                 if( $variant === $lang->getCode() ) {
01781                                         continue;
01782                                 } else {
01783                                         $aloption[] = 'string-contains=' . $variant;
01784 
01785                                         // IE and some other browsers use another form of language code
01786                                         // in their Accept-Language header, like "zh-CN" or "zh-TW".
01787                                         // We should handle these too.
01788                                         $ievariant = explode( '-', $variant );
01789                                         if ( count( $ievariant ) == 2 ) {
01790                                                 $ievariant[1] = strtoupper( $ievariant[1] );
01791                                                 $ievariant = implode( '-', $ievariant );
01792                                                 $aloption[] = 'string-contains=' . $ievariant;
01793                                         }
01794                                 }
01795                         }
01796                         $this->addVaryHeader( 'Accept-Language', $aloption );
01797                 }
01798         }
01799 
01810         public function preventClickjacking( $enable = true ) {
01811                 $this->mPreventClickjacking = $enable;
01812         }
01813 
01819         public function allowClickjacking() {
01820                 $this->mPreventClickjacking = false;
01821         }
01822 
01829         public function getPreventClickjacking() {
01830                 return $this->mPreventClickjacking;
01831         }
01832 
01840         public function getFrameOptions() {
01841                 global $wgBreakFrames, $wgEditPageFrameOptions;
01842                 if ( $wgBreakFrames ) {
01843                         return 'DENY';
01844                 } elseif ( $this->mPreventClickjacking && $wgEditPageFrameOptions ) {
01845                         return $wgEditPageFrameOptions;
01846                 }
01847                 return false;
01848         }
01849 
01853         public function sendCacheControl() {
01854                 global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgUseXVO;
01855 
01856                 $response = $this->getRequest()->response();
01857                 if ( $wgUseETag && $this->mETag ) {
01858                         $response->header( "ETag: $this->mETag" );
01859                 }
01860 
01861                 $this->addAcceptLanguage();
01862 
01863                 # don't serve compressed data to clients who can't handle it
01864                 # maintain different caches for logged-in users and non-logged in ones
01865                 $response->header( 'Vary: ' . join( ', ', array_keys( $this->mVaryHeader ) ) );
01866 
01867                 if ( $wgUseXVO ) {
01868                         # Add an X-Vary-Options header for Squid with Wikimedia patches
01869                         $response->header( $this->getXVO() );
01870                 }
01871 
01872                 if( !$this->uncacheableBecauseRequestVars() && $this->mEnableClientCache ) {
01873                         if(
01874                                 $wgUseSquid && session_id() == '' && !$this->isPrintable() &&
01875                                 $this->mSquidMaxage != 0 && !$this->haveCacheVaryCookies()
01876                         )
01877                         {
01878                                 if ( $wgUseESI ) {
01879                                         # We'll purge the proxy cache explicitly, but require end user agents
01880                                         # to revalidate against the proxy on each visit.
01881                                         # Surrogate-Control controls our Squid, Cache-Control downstream caches
01882                                         wfDebug( __METHOD__ . ": proxy caching with ESI; {$this->mLastModified} **\n", false );
01883                                         # start with a shorter timeout for initial testing
01884                                         # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
01885                                         $response->header( 'Surrogate-Control: max-age='.$wgSquidMaxage.'+'.$this->mSquidMaxage.', content="ESI/1.0"');
01886                                         $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
01887                                 } else {
01888                                         # We'll purge the proxy cache for anons explicitly, but require end user agents
01889                                         # to revalidate against the proxy on each visit.
01890                                         # IMPORTANT! The Squid needs to replace the Cache-Control header with
01891                                         # Cache-Control: s-maxage=0, must-revalidate, max-age=0
01892                                         wfDebug( __METHOD__ . ": local proxy caching; {$this->mLastModified} **\n", false );
01893                                         # start with a shorter timeout for initial testing
01894                                         # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
01895                                         $response->header( 'Cache-Control: s-maxage='.$this->mSquidMaxage.', must-revalidate, max-age=0' );
01896                                 }
01897                         } else {
01898                                 # We do want clients to cache if they can, but they *must* check for updates
01899                                 # on revisiting the page.
01900                                 wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **\n", false );
01901                                 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
01902                                 $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
01903                         }
01904                         if($this->mLastModified) {
01905                                 $response->header( "Last-Modified: {$this->mLastModified}" );
01906                         }
01907                 } else {
01908                         wfDebug( __METHOD__ . ": no caching **\n", false );
01909 
01910                         # In general, the absence of a last modified header should be enough to prevent
01911                         # the client from using its cache. We send a few other things just to make sure.
01912                         $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
01913                         $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
01914                         $response->header( 'Pragma: no-cache' );
01915                 }
01916         }
01917 
01927         public static function getStatusMessage( $code ) {
01928                 wfDeprecated( __METHOD__ );
01929                 return HttpStatus::getMessage( $code );
01930         }
01931 
01936         public function output() {
01937                 global $wgLanguageCode, $wgDebugRedirects, $wgMimeType, $wgVaryOnXFP;
01938 
01939                 if( $this->mDoNothing ) {
01940                         return;
01941                 }
01942 
01943                 wfProfileIn( __METHOD__ );
01944 
01945                 $response = $this->getRequest()->response();
01946 
01947                 if ( $this->mRedirect != '' ) {
01948                         # Standards require redirect URLs to be absolute
01949                         $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT );
01950 
01951                         $redirect = $this->mRedirect;
01952                         $code = $this->mRedirectCode;
01953 
01954                         if( wfRunHooks( "BeforePageRedirect", array( $this, &$redirect, &$code ) ) ) {
01955                                 if( $code == '301' || $code == '303' ) {
01956                                         if( !$wgDebugRedirects ) {
01957                                                 $message = HttpStatus::getMessage( $code );
01958                                                 $response->header( "HTTP/1.1 $code $message" );
01959                                         }
01960                                         $this->mLastModified = wfTimestamp( TS_RFC2822 );
01961                                 }
01962                                 if ( $wgVaryOnXFP ) {
01963                                         $this->addVaryHeader( 'X-Forwarded-Proto' );
01964                                 }
01965                                 $this->sendCacheControl();
01966 
01967                                 $response->header( "Content-Type: text/html; charset=utf-8" );
01968                                 if( $wgDebugRedirects ) {
01969                                         $url = htmlspecialchars( $redirect );
01970                                         print "<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
01971                                         print "<p>Location: <a href=\"$url\">$url</a></p>\n";
01972                                         print "</body>\n</html>\n";
01973                                 } else {
01974                                         $response->header( 'Location: ' . $redirect );
01975                                 }
01976                         }
01977 
01978                         wfProfileOut( __METHOD__ );
01979                         return;
01980                 } elseif ( $this->mStatusCode ) {
01981                         $message = HttpStatus::getMessage( $this->mStatusCode );
01982                         if ( $message ) {
01983                                 $response->header( 'HTTP/1.1 ' . $this->mStatusCode . ' ' . $message );
01984                         }
01985                 }
01986 
01987                 # Buffer output; final headers may depend on later processing
01988                 ob_start();
01989 
01990                 $response->header( "Content-type: $wgMimeType; charset=UTF-8" );
01991                 $response->header( 'Content-language: ' . $wgLanguageCode );
01992 
01993                 // Prevent framing, if requested
01994                 $frameOptions = $this->getFrameOptions();
01995                 if ( $frameOptions ) {
01996                         $response->header( "X-Frame-Options: $frameOptions" );
01997                 }
01998 
01999                 if ( $this->mArticleBodyOnly ) {
02000                         $this->out( $this->mBodytext );
02001                 } else {
02002                         $this->addDefaultModules();
02003 
02004                         $sk = $this->getSkin();
02005 
02006                         // Hook that allows last minute changes to the output page, e.g.
02007                         // adding of CSS or Javascript by extensions.
02008                         wfRunHooks( 'BeforePageDisplay', array( &$this, &$sk ) );
02009 
02010                         wfProfileIn( 'Output-skin' );
02011                         $sk->outputPage();
02012                         wfProfileOut( 'Output-skin' );
02013                 }
02014 
02015                 $this->sendCacheControl();
02016                 ob_end_flush();
02017                 wfProfileOut( __METHOD__ );
02018         }
02019 
02025         public function out( $ins ) {
02026                 print $ins;
02027         }
02028 
02033         function blockedPage() {
02034                 throw new UserBlockedError( $this->getUser()->mBlock );
02035         }
02036 
02047         public function prepareErrorPage( $pageTitle, $htmlTitle = false ) {
02048                 if ( $this->getTitle() ) {
02049                         $this->mDebugtext .= 'Original title: ' . $this->getTitle()->getPrefixedText() . "\n";
02050                 }
02051 
02052                 $this->setPageTitle( $pageTitle );
02053                 if ( $htmlTitle !== false ) {
02054                         $this->setHTMLTitle( $htmlTitle );
02055                 }
02056                 $this->setRobotPolicy( 'noindex,nofollow' );
02057                 $this->setArticleRelated( false );
02058                 $this->enableClientCache( false );
02059                 $this->mRedirect = '';
02060                 $this->clearSubtitle();
02061                 $this->clearHTML();
02062         }
02063 
02074         public function showErrorPage( $title, $msg, $params = array() ) {
02075                 $this->prepareErrorPage( $this->msg( $title ), $this->msg( 'errorpagetitle' ) );
02076 
02077                 if ( $msg instanceof Message ){
02078                         $this->addHTML( $msg->parse() );
02079                 } else {
02080                         $this->addWikiMsgArray( $msg, $params );
02081                 }
02082 
02083                 $this->returnToMain();
02084         }
02085 
02092         public function showPermissionsErrorPage( $errors, $action = null ) {
02093                 global $wgGroupPermissions;
02094 
02095                 // For some action (read, edit, create and upload), display a "login to do this action"
02096                 // error if all of the following conditions are met:
02097                 // 1. the user is not logged in
02098                 // 2. the only error is insufficient permissions (i.e. no block or something else)
02099                 // 3. the error can be avoided simply by logging in
02100                 if ( in_array( $action, array( 'read', 'edit', 'createpage', 'createtalk', 'upload' ) )
02101                         && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
02102                         && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
02103                         && ( ( isset( $wgGroupPermissions['user'][$action] ) && $wgGroupPermissions['user'][$action] )
02104                         || ( isset( $wgGroupPermissions['autoconfirmed'][$action] ) && $wgGroupPermissions['autoconfirmed'][$action] ) )
02105                 ) {
02106                         $displayReturnto = null;
02107 
02108                         # Due to bug 32276, if a user does not have read permissions,
02109                         # $this->getTitle() will just give Special:Badtitle, which is
02110                         # not especially useful as a returnto parameter. Use the title
02111                         # from the request instead, if there was one.
02112                         $request = $this->getRequest();
02113                         $returnto = Title::newFromURL( $request->getVal( 'title', '' ) );
02114                         if ( $action == 'edit' ) {
02115                                 $msg = 'whitelistedittext';
02116                                 $displayReturnto = $returnto;
02117                         } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
02118                                 $msg = 'nocreatetext';
02119                         } elseif ( $action == 'upload' ) {
02120                                 $msg = 'uploadnologintext';
02121                         } else { # Read
02122                                 $msg = 'loginreqpagetext';
02123                                 $displayReturnto = Title::newMainPage();
02124                         }
02125 
02126                         $query = array();
02127 
02128                         if ( $returnto ) {
02129                                 $query['returnto'] = $returnto->getPrefixedText();
02130 
02131                                 if ( !$request->wasPosted() ) {
02132                                         $returntoquery = $request->getValues();
02133                                         unset( $returntoquery['title'] );
02134                                         unset( $returntoquery['returnto'] );
02135                                         unset( $returntoquery['returntoquery'] );
02136                                         $query['returntoquery'] = wfArrayToCGI( $returntoquery );
02137                                 }
02138                         }
02139                         $loginLink = Linker::linkKnown(
02140                                 SpecialPage::getTitleFor( 'Userlogin' ),
02141                                 $this->msg( 'loginreqlink' )->escaped(),
02142                                 array(),
02143                                 $query
02144                         );
02145 
02146                         $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) );
02147                         $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->parse() );
02148 
02149                         # Don't return to a page the user can't read otherwise
02150                         # we'll end up in a pointless loop
02151                         if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) {
02152                                 $this->returnToMain( null, $displayReturnto );
02153                         }
02154                 } else {
02155                         $this->prepareErrorPage( $this->msg( 'permissionserrors' ) );
02156                         $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) );
02157                 }
02158         }
02159 
02166         public function versionRequired( $version ) {
02167                 $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) );
02168 
02169                 $this->addWikiMsg( 'versionrequiredtext', $version );
02170                 $this->returnToMain();
02171         }
02172 
02178         public function permissionRequired( $permission ) {
02179                 throw new PermissionsError( $permission );
02180         }
02181 
02187         public function loginToUse() {
02188                 throw new PermissionsError( 'read' );
02189         }
02190 
02198         public function formatPermissionsErrorMessage( $errors, $action = null ) {
02199                 if ( $action == null ) {
02200                         $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
02201                 } else {
02202                         $action_desc = $this->msg( "action-$action" )->plain();
02203                         $text = $this->msg(
02204                                 'permissionserrorstext-withaction',
02205                                 count( $errors ),
02206                                 $action_desc
02207                         )->plain() . "\n\n";
02208                 }
02209 
02210                 if ( count( $errors ) > 1 ) {
02211                         $text .= '<ul class="permissions-errors">' . "\n";
02212 
02213                         foreach( $errors as $error ) {
02214                                 $text .= '<li>';
02215                                 $text .= call_user_func_array( array( $this, 'msg' ), $error )->plain();
02216                                 $text .= "</li>\n";
02217                         }
02218                         $text .= '</ul>';
02219                 } else {
02220                         $text .= "<div class=\"permissions-errors\">\n" .
02221                                         call_user_func_array( array( $this, 'msg' ), reset( $errors ) )->plain() .
02222                                         "\n</div>";
02223                 }
02224 
02225                 return $text;
02226         }
02227 
02248         public function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
02249                 $this->setRobotPolicy( 'noindex,nofollow' );
02250                 $this->setArticleRelated( false );
02251 
02252                 // If no reason is given, just supply a default "I can't let you do
02253                 // that, Dave" message.  Should only occur if called by legacy code.
02254                 if ( $protected && empty( $reasons ) ) {
02255                         $reasons[] = array( 'badaccess-group0' );
02256                 }
02257 
02258                 if ( !empty( $reasons ) ) {
02259                         // Permissions error
02260                         if( $source ) {
02261                                 $this->setPageTitle( $this->msg( 'viewsource-title', $this->getTitle()->getPrefixedText() ) );
02262                                 $this->addBacklinkSubtitle( $this->getTitle() );
02263                         } else {
02264                                 $this->setPageTitle( $this->msg( 'badaccess' ) );
02265                         }
02266                         $this->addWikiText( $this->formatPermissionsErrorMessage( $reasons, $action ) );
02267                 } else {
02268                         // Wiki is read only
02269                         throw new ReadOnlyError;
02270                 }
02271 
02272                 // Show source, if supplied
02273                 if( is_string( $source ) ) {
02274                         $this->addWikiMsg( 'viewsourcetext' );
02275 
02276                         $pageLang = $this->getTitle()->getPageLanguage();
02277                         $params = array(
02278                                 'id'   => 'wpTextbox1',
02279                                 'name' => 'wpTextbox1',
02280                                 'cols' => $this->getUser()->getOption( 'cols' ),
02281                                 'rows' => $this->getUser()->getOption( 'rows' ),
02282                                 'readonly' => 'readonly',
02283                                 'lang' => $pageLang->getHtmlCode(),
02284                                 'dir' => $pageLang->getDir(),
02285                         );
02286                         $this->addHTML( Html::element( 'textarea', $params, $source ) );
02287 
02288                         // Show templates used by this article
02289                         $templates = Linker::formatTemplates( $this->getTitle()->getTemplateLinksFrom() );
02290                         $this->addHTML( "<div class='templatesUsed'>
02291 $templates
02292 </div>
02293 " );
02294                 }
02295 
02296                 # If the title doesn't exist, it's fairly pointless to print a return
02297                 # link to it.  After all, you just tried editing it and couldn't, so
02298                 # what's there to do there?
02299                 if( $this->getTitle()->exists() ) {
02300                         $this->returnToMain( null, $this->getTitle() );
02301                 }
02302         }
02303 
02308         public function rateLimited() {
02309                 throw new ThrottledError;
02310         }
02311 
02321         public function showLagWarning( $lag ) {
02322                 global $wgSlaveLagWarning, $wgSlaveLagCritical;
02323                 if( $lag >= $wgSlaveLagWarning ) {
02324                         $message = $lag < $wgSlaveLagCritical
02325                                 ? 'lag-warn-normal'
02326                                 : 'lag-warn-high';
02327                         $wrap = Html::rawElement( 'div', array( 'class' => "mw-{$message}" ), "\n$1\n" );
02328                         $this->wrapWikiMsg( "$wrap\n", array( $message, $this->getLanguage()->formatNum( $lag ) ) );
02329                 }
02330         }
02331 
02332         public function showFatalError( $message ) {
02333                 $this->prepareErrorPage( $this->msg( 'internalerror' ) );
02334 
02335                 $this->addHTML( $message );
02336         }
02337 
02338         public function showUnexpectedValueError( $name, $val ) {
02339                 $this->showFatalError( $this->msg( 'unexpected', $name, $val )->text() );
02340         }
02341 
02342         public function showFileCopyError( $old, $new ) {
02343                 $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->text() );
02344         }
02345 
02346         public function showFileRenameError( $old, $new ) {
02347                 $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->text() );
02348         }
02349 
02350         public function showFileDeleteError( $name ) {
02351                 $this->showFatalError( $this->msg( 'filedeleteerror', $name )->text() );
02352         }
02353 
02354         public function showFileNotFoundError( $name ) {
02355                 $this->showFatalError( $this->msg( 'filenotfound', $name )->text() );
02356         }
02357 
02365         public function addReturnTo( $title, $query = array(), $text = null ) {
02366                 $this->addLink( array( 'rel' => 'next', 'href' => $title->getFullURL() ) );
02367                 $link = $this->msg( 'returnto' )->rawParams(
02368                         Linker::link( $title, $text, array(), $query ) )->escaped();
02369                 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
02370         }
02371 
02380         public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
02381                 if ( $returnto == null ) {
02382                         $returnto = $this->getRequest()->getText( 'returnto' );
02383                 }
02384 
02385                 if ( $returntoquery == null ) {
02386                         $returntoquery = $this->getRequest()->getText( 'returntoquery' );
02387                 }
02388 
02389                 if ( $returnto === '' ) {
02390                         $returnto = Title::newMainPage();
02391                 }
02392 
02393                 if ( is_object( $returnto ) ) {
02394                         $titleObj = $returnto;
02395                 } else {
02396                         $titleObj = Title::newFromText( $returnto );
02397                 }
02398                 if ( !is_object( $titleObj ) ) {
02399                         $titleObj = Title::newMainPage();
02400                 }
02401 
02402                 $this->addReturnTo( $titleObj, $returntoquery );
02403         }
02404 
02410         public function headElement( Skin $sk, $includeStyle = true ) {
02411                 global $wgContLang;
02412 
02413                 $userdir = $this->getLanguage()->getDir();
02414                 $sitedir = $wgContLang->getDir();
02415 
02416                 if ( $sk->commonPrintStylesheet() ) {
02417                         $this->addModuleStyles( 'mediawiki.legacy.wikiprintable' );
02418                 }
02419 
02420                 $ret = Html::htmlHeader( array( 'lang' => $this->getLanguage()->getHtmlCode(), 'dir' => $userdir, 'class' => 'client-nojs' ) );
02421 
02422                 if ( $this->getHTMLTitle() == '' ) {
02423                         $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() ) );
02424                 }
02425 
02426                 $openHead = Html::openElement( 'head' );
02427                 if ( $openHead ) {
02428                         # Don't bother with the newline if $head == ''
02429                         $ret .= "$openHead\n";
02430                 }
02431 
02432                 $ret .= Html::element( 'title', null, $this->getHTMLTitle() ) . "\n";
02433 
02434                 $ret .= implode( "\n", array(
02435                         $this->getHeadLinks( null, true ),
02436                         $this->buildCssLinks(),
02437                         $this->getHeadScripts(),
02438                         $this->getHeadItems()
02439                 ) );
02440 
02441                 $closeHead = Html::closeElement( 'head' );
02442                 if ( $closeHead ) {
02443                         $ret .= "$closeHead\n";
02444                 }
02445 
02446                 $bodyAttrs = array();
02447 
02448                 # Classes for LTR/RTL directionality support
02449                 $bodyAttrs['class'] = "mediawiki $userdir sitedir-$sitedir";
02450 
02451                 if ( $this->getLanguage()->capitalizeAllNouns() ) {
02452                         # A <body> class is probably not the best way to do this . . .
02453                         $bodyAttrs['class'] .= ' capitalize-all-nouns';
02454                 }
02455                 $bodyAttrs['class'] .= ' ' . $sk->getPageClasses( $this->getTitle() );
02456                 $bodyAttrs['class'] .= ' skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
02457                 $bodyAttrs['class'] .= ' action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
02458 
02459                 $sk->addToBodyAttributes( $this, $bodyAttrs ); // Allow skins to add body attributes they need
02460                 wfRunHooks( 'OutputPageBodyAttributes', array( $this, $sk, &$bodyAttrs ) );
02461 
02462                 $ret .= Html::openElement( 'body', $bodyAttrs ) . "\n";
02463 
02464                 return $ret;
02465         }
02466 
02470         private function addDefaultModules() {
02471                 global $wgIncludeLegacyJavaScript, $wgPreloadJavaScriptMwUtil, $wgUseAjax,
02472                         $wgAjaxWatch, $wgEnableMWSuggest;
02473 
02474                 // Add base resources
02475                 $this->addModules( array(
02476                         'mediawiki.user',
02477                         'mediawiki.page.startup',
02478                         'mediawiki.page.ready',
02479                 ) );
02480                 if ( $wgIncludeLegacyJavaScript ){
02481                         $this->addModules( 'mediawiki.legacy.wikibits' );
02482                 }
02483 
02484                 if ( $wgPreloadJavaScriptMwUtil ) {
02485                         $this->addModules( 'mediawiki.util' );
02486                 }
02487 
02488                 MWDebug::addModules( $this );
02489 
02490                 // Add various resources if required
02491                 if ( $wgUseAjax ) {
02492                         $this->addModules( 'mediawiki.legacy.ajax' );
02493 
02494                         wfRunHooks( 'AjaxAddScript', array( &$this ) );
02495 
02496                         if( $wgAjaxWatch && $this->getUser()->isLoggedIn() ) {
02497                                 $this->addModules( 'mediawiki.action.watch.ajax' );
02498                         }
02499 
02500                         if ( $wgEnableMWSuggest && !$this->getUser()->getOption( 'disablesuggest', false ) ) {
02501                                 $this->addModules( 'mediawiki.legacy.mwsuggest' );
02502                         }
02503                 }
02504 
02505                 if ( $this->getUser()->getBoolOption( 'editsectiononrightclick' ) ) {
02506                         $this->addModules( 'mediawiki.action.view.rightClickEdit' );
02507                 }
02508 
02509                 # Crazy edit-on-double-click stuff
02510                 if ( $this->isArticle() && $this->getUser()->getOption( 'editondblclick' ) ) {
02511                         $this->addModules( 'mediawiki.action.view.dblClickEdit' );
02512                 }
02513         }
02514 
02520         public function getResourceLoader() {
02521                 if ( is_null( $this->mResourceLoader ) ) {
02522                         $this->mResourceLoader = new ResourceLoader();
02523                 }
02524                 return $this->mResourceLoader;
02525         }
02526 
02536         protected function makeResourceLoaderLink( $modules, $only, $useESI = false, array $extraQuery = array(), $loadCall = false ) {
02537                 global $wgResourceLoaderUseESI;
02538 
02539                 if ( !count( $modules ) ) {
02540                         return '';
02541                 }
02542 
02543                 if ( count( $modules ) > 1 ) {
02544                         // Remove duplicate module requests
02545                         $modules = array_unique( (array) $modules );
02546                         // Sort module names so requests are more uniform
02547                         sort( $modules );
02548 
02549                         if ( ResourceLoader::inDebugMode() ) {
02550                                 // Recursively call us for every item
02551                                 $links = '';
02552                                 foreach ( $modules as $name ) {
02553                                         $links .= $this->makeResourceLoaderLink( $name, $only, $useESI );
02554                                 }
02555                                 return $links;
02556                         }
02557                 }
02558 
02559                 // Create keyed-by-group list of module objects from modules list
02560                 $groups = array();
02561                 $resourceLoader = $this->getResourceLoader();
02562                 foreach ( (array) $modules as $name ) {
02563                         $module = $resourceLoader->getModule( $name );
02564                         # Check that we're allowed to include this module on this page
02565                         if ( !$module
02566                                 || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS )
02567                                         && $only == ResourceLoaderModule::TYPE_SCRIPTS )
02568                                 || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_STYLES )
02569                                         && $only == ResourceLoaderModule::TYPE_STYLES )
02570                                 )
02571                         {
02572                                 continue;
02573                         }
02574 
02575                         $group = $module->getGroup();
02576                         if ( !isset( $groups[$group] ) ) {
02577                                 $groups[$group] = array();
02578                         }
02579                         $groups[$group][$name] = $module;
02580                 }
02581 
02582                 $links = '';
02583                 foreach ( $groups as $group => $modules ) {
02584                         // Special handling for user-specific groups
02585                         $user = null;
02586                         if ( ( $group === 'user' || $group === 'private' ) && $this->getUser()->isLoggedIn() ) {
02587                                 $user = $this->getUser()->getName();
02588                         }
02589 
02590                         // Create a fake request based on the one we are about to make so modules return
02591                         // correct timestamp and emptiness data
02592                         $query = ResourceLoader::makeLoaderQuery(
02593                                 array(), // modules; not determined yet
02594                                 $this->getLanguage()->getCode(),
02595                                 $this->getSkin()->getSkinName(),
02596                                 $user,
02597                                 null, // version; not determined yet
02598                                 ResourceLoader::inDebugMode(),
02599                                 $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only,
02600                                 $this->isPrintable(),
02601                                 $this->getRequest()->getBool( 'handheld' ),
02602                                 $extraQuery
02603                         );
02604                         $context = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
02605                         // Drop modules that know they're empty
02606                         foreach ( $modules as $key => $module ) {
02607                                 if ( $module->isKnownEmpty( $context ) ) {
02608                                         unset( $modules[$key] );
02609                                 }
02610                         }
02611                         // If there are no modules left, skip this group
02612                         if ( $modules === array() ) {
02613                                 continue;
02614                         }
02615 
02616                         // Inline private modules. These can't be loaded through load.php for security
02617                         // reasons, see bug 34907. Note that these modules should be loaded from
02618                         // getHeadScripts() before the first loader call. Otherwise other modules can't
02619                         // properly use them as dependencies (bug 30914)
02620                         if ( $group === 'private' ) {
02621                                 if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
02622                                         $links .= Html::inlineStyle(
02623                                                 $resourceLoader->makeModuleResponse( $context, $modules )
02624                                         );
02625                                 } else {
02626                                         $links .= Html::inlineScript(
02627                                                 ResourceLoader::makeLoaderConditionalScript(
02628                                                         $resourceLoader->makeModuleResponse( $context, $modules )
02629                                                 )
02630                                         );
02631                                 }
02632                                 $links .= "\n";
02633                                 continue;
02634                         }
02635                         // Special handling for the user group; because users might change their stuff
02636                         // on-wiki like user pages, or user preferences; we need to find the highest
02637                         // timestamp of these user-changable modules so we can ensure cache misses on change
02638                         // This should NOT be done for the site group (bug 27564) because anons get that too
02639                         // and we shouldn't be putting timestamps in Squid-cached HTML
02640                         $version = null;
02641                         if ( $group === 'user' ) {
02642                                 // Get the maximum timestamp
02643                                 $timestamp = 1;
02644                                 foreach ( $modules as $module ) {
02645                                         $timestamp = max( $timestamp, $module->getModifiedTime( $context ) );
02646                                 }
02647                                 // Add a version parameter so cache will break when things change
02648                                 $version = wfTimestamp( TS_ISO_8601_BASIC, $timestamp );
02649                         }
02650 
02651                         $url = ResourceLoader::makeLoaderURL(
02652                                 array_keys( $modules ),
02653                                 $this->getLanguage()->getCode(),
02654                                 $this->getSkin()->getSkinName(),
02655                                 $user,
02656                                 $version,
02657                                 ResourceLoader::inDebugMode(),
02658                                 $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only,
02659                                 $this->isPrintable(),
02660                                 $this->getRequest()->getBool( 'handheld' ),
02661                                 $extraQuery
02662                         );
02663                         if ( $useESI && $wgResourceLoaderUseESI ) {
02664                                 $esi = Xml::element( 'esi:include', array( 'src' => $url ) );
02665                                 if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
02666                                         $link = Html::inlineStyle( $esi );
02667                                 } else {
02668                                         $link = Html::inlineScript( $esi );
02669                                 }
02670                         } else {
02671                                 // Automatically select style/script elements
02672                                 if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
02673                                         $link = Html::linkedStyle( $url );
02674                                 } else if ( $loadCall ) { 
02675                                         $link = Html::inlineScript(
02676                                                 ResourceLoader::makeLoaderConditionalScript(
02677                                                         Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) )
02678                                                 )
02679                                         );
02680                                 } else {
02681                                         $link = Html::linkedScript( $url );
02682                                 }
02683                         }
02684 
02685                         if( $group == 'noscript' ){
02686                                 $links .= Html::rawElement( 'noscript', array(), $link ) . "\n";
02687                         } else {
02688                                 $links .= $link . "\n";
02689                         }
02690                 }
02691                 return $links;
02692         }
02693 
02700         function getHeadScripts() {
02701                 global $wgResourceLoaderExperimentalAsyncLoading;
02702                 
02703                 // Startup - this will immediately load jquery and mediawiki modules
02704                 $scripts = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS, true );
02705 
02706                 // Load config before anything else
02707                 $scripts .= Html::inlineScript(
02708                         ResourceLoader::makeLoaderConditionalScript(
02709                                 ResourceLoader::makeConfigSetScript( $this->getJSVars() )
02710                         )
02711                 );
02712 
02713                 // Load embeddable private modules before any loader links
02714                 // This needs to be TYPE_COMBINED so these modules are properly wrapped
02715                 // in mw.loader.implement() calls and deferred until mw.user is available
02716                 $embedScripts = array( 'user.options', 'user.tokens' );
02717                 $scripts .= $this->makeResourceLoaderLink( $embedScripts, ResourceLoaderModule::TYPE_COMBINED );
02718 
02719                 // Script and Messages "only" requests marked for top inclusion
02720                 // Messages should go first
02721                 $scripts .= $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'top' ), ResourceLoaderModule::TYPE_MESSAGES );
02722                 $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'top' ), ResourceLoaderModule::TYPE_SCRIPTS );
02723 
02724                 // Modules requests - let the client calculate dependencies and batch requests as it likes
02725                 // Only load modules that have marked themselves for loading at the top
02726                 $modules = $this->getModules( true, 'top' );
02727                 if ( $modules ) {
02728                         $scripts .= Html::inlineScript(
02729                                 ResourceLoader::makeLoaderConditionalScript(
02730                                         Xml::encodeJsCall( 'mw.loader.load', array( $modules ) )
02731                                 )
02732                         );
02733                 }
02734                 
02735                 if ( $wgResourceLoaderExperimentalAsyncLoading ) {
02736                         $scripts .= $this->getScriptsForBottomQueue( true );
02737                 }
02738 
02739                 return $scripts;
02740         }
02741 
02751         function getScriptsForBottomQueue( $inHead ) {
02752                 global $wgUseSiteJs, $wgAllowUserJs;
02753 
02754                 // Script and Messages "only" requests marked for bottom inclusion
02755                 // If we're in the <head>, use load() calls rather than <script src="..."> tags
02756                 // Messages should go first
02757                 $scripts = $this->makeResourceLoaderLink( $this->getModuleMessages( true, 'bottom' ),
02758                         ResourceLoaderModule::TYPE_MESSAGES, /* $useESI = */ false, /* $extraQuery = */ array(),
02759                         /* $loadCall = */ $inHead
02760                 );
02761                 $scripts .= $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ),
02762                         ResourceLoaderModule::TYPE_SCRIPTS, /* $useESI = */ false, /* $extraQuery = */ array(),
02763                         /* $loadCall = */ $inHead
02764                 );
02765 
02766                 // Modules requests - let the client calculate dependencies and batch requests as it likes
02767                 // Only load modules that have marked themselves for loading at the bottom
02768                 $modules = $this->getModules( true, 'bottom' );
02769                 if ( $modules ) {
02770                         $scripts .= Html::inlineScript(
02771                                 ResourceLoader::makeLoaderConditionalScript(
02772                                         Xml::encodeJsCall( 'mw.loader.load', array( $modules, null, true ) )
02773                                 )
02774                         );
02775                 }
02776 
02777                 // Legacy Scripts
02778                 $scripts .= "\n" . $this->mScripts;
02779 
02780                 $userScripts = array();
02781 
02782                 // Add site JS if enabled
02783                 if ( $wgUseSiteJs ) {
02784                         $scripts .= $this->makeResourceLoaderLink( 'site', ResourceLoaderModule::TYPE_SCRIPTS,
02785                                 /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
02786                         );
02787                         if( $this->getUser()->isLoggedIn() ){
02788                                 $userScripts[] = 'user.groups';
02789                         }
02790                 }
02791 
02792                 // Add user JS if enabled
02793                 if ( $wgAllowUserJs && $this->getUser()->isLoggedIn() ) {
02794                         if( $this->getTitle() && $this->getTitle()->isJsSubpage() && $this->userCanPreview() ) {
02795                                 # XXX: additional security check/prompt?
02796                                 // We're on a preview of a JS subpage
02797                                 // Exclude this page from the user module in case it's in there (bug 26283)
02798                                 $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS, false,
02799                                         array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead
02800                                 );
02801                                 // Load the previewed JS
02802                                 $scripts .= Html::inlineScript( "\n" . $this->getRequest()->getText( 'wpTextbox1' ) . "\n" ) . "\n";
02803                         } else {
02804                                 // Include the user module normally
02805                                 // We can't do $userScripts[] = 'user'; because the user module would end up
02806                                 // being wrapped in a closure, so load it raw like 'site'
02807                                 $scripts .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_SCRIPTS,
02808                                         /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
02809                                 );
02810                         }
02811                 }
02812                 $scripts .= $this->makeResourceLoaderLink( $userScripts, ResourceLoaderModule::TYPE_COMBINED,
02813                         /* $useESI = */ false, /* $extraQuery = */ array(), /* $loadCall = */ $inHead
02814                 );
02815 
02816                 return $scripts;
02817         }
02818 
02822         function getBottomScripts() {
02823                 global $wgResourceLoaderExperimentalAsyncLoading;
02824                 if ( !$wgResourceLoaderExperimentalAsyncLoading ) {
02825                         return $this->getScriptsForBottomQueue( false );
02826                 } else {
02827                         return '';
02828                 }
02829         }
02830 
02837         public function addJsConfigVars( $keys, $value = null ) {
02838                 if ( is_array( $keys ) ) {
02839                         foreach ( $keys as $key => $value ) {
02840                                 $this->mJsConfigVars[$key] = $value;
02841                         }
02842                         return;
02843                 }
02844 
02845                 $this->mJsConfigVars[$keys] = $value;
02846         }
02847 
02848 
02861         public function getJSVars() {
02862                 global $wgUseAjax, $wgEnableMWSuggest;
02863 
02864                 $latestRevID = 0;
02865                 $pageID = 0;
02866                 $canonicalName = false; # bug 21115
02867 
02868                 $title = $this->getTitle();
02869                 $ns = $title->getNamespace();
02870                 $nsname = MWNamespace::exists( $ns ) ? MWNamespace::getCanonicalName( $ns ) : $title->getNsText();
02871 
02872                 // Get the relevant title so that AJAX features can use the correct page name
02873                 // when making API requests from certain special pages (bug 34972).
02874                 $relevantTitle = $this->getSkin()->getRelevantTitle();
02875 
02876                 if ( $ns == NS_SPECIAL ) {
02877                         list( $canonicalName, /*...*/ ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
02878                 } elseif ( $this->canUseWikiPage() ) {
02879                         $wikiPage = $this->getWikiPage();
02880                         $latestRevID = $wikiPage->getLatest();
02881                         $pageID = $wikiPage->getId();
02882                 }
02883 
02884                 $lang = $title->getPageLanguage();
02885 
02886                 // Pre-process information
02887                 $separatorTransTable = $lang->separatorTransformTable();
02888                 $separatorTransTable = $separatorTransTable ? $separatorTransTable : array();
02889                 $compactSeparatorTransTable = array(
02890                         implode( "\t", array_keys( $separatorTransTable ) ),
02891                         implode( "\t", $separatorTransTable ),
02892                 );
02893                 $digitTransTable = $lang->digitTransformTable();
02894                 $digitTransTable = $digitTransTable ? $digitTransTable : array();
02895                 $compactDigitTransTable = array(
02896                         implode( "\t", array_keys( $digitTransTable ) ),
02897                         implode( "\t", $digitTransTable ),
02898                 );
02899 
02900                 $vars = array(
02901                         'wgCanonicalNamespace' => $nsname,
02902                         'wgCanonicalSpecialPageName' => $canonicalName,
02903                         'wgNamespaceNumber' => $title->getNamespace(),
02904                         'wgPageName' => $title->getPrefixedDBKey(),
02905                         'wgTitle' => $title->getText(),
02906                         'wgCurRevisionId' => $latestRevID,
02907                         'wgArticleId' => $pageID,
02908                         'wgIsArticle' => $this->isArticle(),
02909                         'wgAction' => Action::getActionName( $this->getContext() ),
02910                         'wgUserName' => $this->getUser()->isAnon() ? null : $this->getUser()->getName(),
02911                         'wgUserGroups' => $this->getUser()->getEffectiveGroups(),
02912                         'wgCategories' => $this->getCategories(),
02913                         'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
02914                         'wgPageContentLanguage' => $lang->getCode(),
02915                         'wgSeparatorTransformTable' => $compactSeparatorTransTable,
02916                         'wgDigitTransformTable' => $compactDigitTransTable,
02917                         'wgRelevantPageName' => $relevantTitle->getPrefixedDBKey(),
02918                 );
02919                 if ( $lang->hasVariants() ) {
02920                         $vars['wgUserVariant'] = $lang->getPreferredVariant();
02921                 }
02922                 foreach ( $title->getRestrictionTypes() as $type ) {
02923                         $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
02924                 }
02925                 if ( $wgUseAjax && $wgEnableMWSuggest && !$this->getUser()->getOption( 'disablesuggest', false ) ) {
02926                         $vars['wgSearchNamespaces'] = SearchEngine::userNamespaces( $this->getUser() );
02927                 }
02928                 if ( $title->isMainPage() ) {
02929                         $vars['wgIsMainPage'] = true;
02930                 }
02931                 if ( $this->mRedirectedFrom ) {
02932                         $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBKey();
02933                 }
02934 
02935                 // Allow extensions to add their custom variables to the mw.config map.
02936                 // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
02937                 // page-dependant but site-wide (without state).
02938                 // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
02939                 wfRunHooks( 'MakeGlobalVariablesScript', array( &$vars, $this ) );
02940 
02941                 // Merge in variables from addJsConfigVars last
02942                 return array_merge( $vars, $this->mJsConfigVars );
02943         }
02944 
02954         public function userCanPreview() {
02955                 if ( $this->getRequest()->getVal( 'action' ) != 'submit'
02956                         || !$this->getRequest()->wasPosted()
02957                         || !$this->getUser()->matchEditToken(
02958                                 $this->getRequest()->getVal( 'wpEditToken' ) )
02959                 ) {
02960                         return false;
02961                 }
02962                 if ( !$this->getTitle()->isJsSubpage() && !$this->getTitle()->isCssSubpage() ) {
02963                         return false;
02964                 }
02965 
02966                 return !count( $this->getTitle()->getUserPermissionsErrors( 'edit', $this->getUser() ) );
02967         }
02968 
02975         public function getHeadLinks( $unused = null, $addContentType = false ) {
02976                 global $wgUniversalEditButton, $wgFavicon, $wgAppleTouchIcon, $wgEnableAPI,
02977                         $wgSitename, $wgVersion, $wgHtml5, $wgMimeType,
02978                         $wgFeed, $wgOverrideSiteFeed, $wgAdvertisedFeedTypes,
02979                         $wgDisableLangConversion, $wgCanonicalLanguageLinks,
02980                         $wgRightsPage, $wgRightsUrl;
02981 
02982                 $tags = array();
02983 
02984                 if ( $addContentType ) {
02985                         if ( $wgHtml5 ) {
02986                                 # More succinct than <meta http-equiv=Content-Type>, has the
02987                                 # same effect
02988                                 $tags[] = Html::element( 'meta', array( 'charset' => 'UTF-8' ) );
02989                         } else {
02990                                 $tags[] = Html::element( 'meta', array(
02991                                         'http-equiv' => 'Content-Type',
02992                                         'content' => "$wgMimeType; charset=UTF-8"
02993                                 ) );
02994                                 $tags[] = Html::element( 'meta', array(  // bug 15835
02995                                         'http-equiv' => 'Content-Style-Type',
02996                                         'content' => 'text/css'
02997                                 ) );
02998                         }
02999                 }
03000 
03001                 $tags[] = Html::element( 'meta', array(
03002                         'name' => 'generator',
03003                         'content' => "MediaWiki $wgVersion",
03004                 ) );
03005 
03006                 $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
03007                 if( $p !== 'index,follow' ) {
03008                         // http://www.robotstxt.org/wc/meta-user.html
03009                         // Only show if it's different from the default robots policy
03010                         $tags[] = Html::element( 'meta', array(
03011                                 'name' => 'robots',
03012                                 'content' => $p,
03013                         ) );
03014                 }
03015 
03016                 if ( count( $this->mKeywords ) > 0 ) {
03017                         $strip = array(
03018                                 "/<.*?" . ">/" => '',
03019                                 "/_/" => ' '
03020                         );
03021                         $tags[] = Html::element( 'meta', array(
03022                                 'name' => 'keywords',
03023                                 'content' =>  preg_replace(
03024                                         array_keys( $strip ),
03025                                         array_values( $strip ),
03026                                         implode( ',', $this->mKeywords )
03027                                 )
03028                         ) );
03029                 }
03030 
03031                 foreach ( $this->mMetatags as $tag ) {
03032                         if ( 0 == strcasecmp( 'http:', substr( $tag[0], 0, 5 ) ) ) {
03033                                 $a = 'http-equiv';
03034                                 $tag[0] = substr( $tag[0], 5 );
03035                         } else {
03036                                 $a = 'name';
03037                         }
03038                         $tags[] = Html::element( 'meta',
03039                                 array(
03040                                         $a => $tag[0],
03041                                         'content' => $tag[1]
03042                                 )
03043                         );
03044                 }
03045 
03046                 foreach ( $this->mLinktags as $tag ) {
03047                         $tags[] = Html::element( 'link', $tag );
03048                 }
03049 
03050                 # Universal edit button
03051                 if ( $wgUniversalEditButton && $this->isArticleRelated() ) {
03052                         $user = $this->getUser();
03053                         if ( $this->getTitle()->quickUserCan( 'edit', $user )
03054                                 && ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create', $user ) ) ) {
03055                                 // Original UniversalEditButton
03056                                 $msg = $this->msg( 'edit' )->text();
03057                                 $tags[] = Html::element( 'link', array(
03058                                         'rel' => 'alternate',
03059                                         'type' => 'application/x-wiki',
03060                                         'title' => $msg,
03061                                         'href' => $this->getTitle()->getLocalURL( 'action=edit' )
03062                                 ) );
03063                                 // Alternate edit link
03064                                 $tags[] = Html::element( 'link', array(
03065                                         'rel' => 'edit',
03066                                         'title' => $msg,
03067                                         'href' => $this->getTitle()->getLocalURL( 'action=edit' )
03068                                 ) );
03069                         }
03070                 }
03071 
03072                 # Generally the order of the favicon and apple-touch-icon links
03073                 # should not matter, but Konqueror (3.5.9 at least) incorrectly
03074                 # uses whichever one appears later in the HTML source. Make sure
03075                 # apple-touch-icon is specified first to avoid this.
03076                 if ( $wgAppleTouchIcon !== false ) {
03077                         $tags[] = Html::element( 'link', array( 'rel' => 'apple-touch-icon', 'href' => $wgAppleTouchIcon ) );
03078                 }
03079 
03080                 if ( $wgFavicon !== false ) {
03081                         $tags[] = Html::element( 'link', array( 'rel' => 'shortcut icon', 'href' => $wgFavicon ) );
03082                 }
03083 
03084                 # OpenSearch description link
03085                 $tags[] = Html::element( 'link', array(
03086                         'rel' => 'search',
03087                         'type' => 'application/opensearchdescription+xml',
03088                         'href' => wfScript( 'opensearch_desc' ),
03089                         'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
03090                 ) );
03091 
03092                 if ( $wgEnableAPI ) {
03093                         # Real Simple Discovery link, provides auto-discovery information
03094                         # for the MediaWiki API (and potentially additional custom API
03095                         # support such as WordPress or Twitter-compatible APIs for a
03096                         # blogging extension, etc)
03097                         $tags[] = Html::element( 'link', array(
03098                                 'rel' => 'EditURI',
03099                                 'type' => 'application/rsd+xml',
03100                                 // Output a protocol-relative URL here if $wgServer is protocol-relative
03101                                 // Whether RSD accepts relative or protocol-relative URLs is completely undocumented, though
03102                                 'href' => wfExpandUrl( wfAppendQuery( wfScript( 'api' ), array( 'action' => 'rsd' ) ), PROTO_RELATIVE ),
03103                         ) );
03104                 }
03105 
03106 
03107                 # Language variants
03108                 if ( !$wgDisableLangConversion && $wgCanonicalLanguageLinks ) {
03109                         $lang = $this->getTitle()->getPageLanguage();
03110                         if ( $lang->hasVariants() ) {
03111 
03112                                 $urlvar = $lang->getURLVariant();
03113 
03114                                 if ( !$urlvar ) {
03115                                         $variants = $lang->getVariants();
03116                                         foreach ( $variants as $_v ) {
03117                                                 $tags[] = Html::element( 'link', array(
03118                                                         'rel' => 'alternate',
03119                                                         'hreflang' => $_v,
03120                                                         'href' => $this->getTitle()->getLocalURL( array( 'variant' => $_v ) ) )
03121                                                 );
03122                                         }
03123                                 } else {
03124                                         $tags[] = Html::element( 'link', array(
03125                                                 'rel' => 'canonical',
03126                                                 'href' => $this->getTitle()->getCanonicalUrl()
03127                                         ) );
03128                                 }
03129                         }
03130                 }
03131 
03132                 # Copyright
03133                 $copyright = '';
03134                 if ( $wgRightsPage ) {
03135                         $copy = Title::newFromText( $wgRightsPage );
03136 
03137                         if ( $copy ) {
03138                                 $copyright = $copy->getLocalURL();
03139                         }
03140                 }
03141 
03142                 if ( !$copyright && $wgRightsUrl ) {
03143                         $copyright = $wgRightsUrl;
03144                 }
03145 
03146                 if ( $copyright ) {
03147                         $tags[] = Html::element( 'link', array(
03148                                 'rel' => 'copyright',
03149                                 'href' => $copyright )
03150                         );
03151                 }
03152 
03153                 # Feeds
03154                 if ( $wgFeed ) {
03155                         foreach( $this->getSyndicationLinks() as $format => $link ) {
03156                                 # Use the page name for the title.  In principle, this could
03157                                 # lead to issues with having the same name for different feeds
03158                                 # corresponding to the same page, but we can't avoid that at
03159                                 # this low a level.
03160 
03161                                 $tags[] = $this->feedLink(
03162                                         $format,
03163                                         $link,
03164                                         # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
03165                                         $this->msg( "page-{$format}-feed", $this->getTitle()->getPrefixedText() )->text()
03166                                 );
03167                         }
03168 
03169                         # Recent changes feed should appear on every page (except recentchanges,
03170                         # that would be redundant). Put it after the per-page feed to avoid
03171                         # changing existing behavior. It's still available, probably via a
03172                         # menu in your browser. Some sites might have a different feed they'd
03173                         # like to promote instead of the RC feed (maybe like a "Recent New Articles"
03174                         # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
03175                         # If so, use it instead.
03176                         if ( $wgOverrideSiteFeed ) {
03177                                 foreach ( $wgOverrideSiteFeed as $type => $feedUrl ) {
03178                                         // Note, this->feedLink escapes the url.
03179                                         $tags[] = $this->feedLink(
03180                                                 $type,
03181                                                 $feedUrl,
03182                                                 $this->msg( "site-{$type}-feed", $wgSitename )->text()
03183                                         );
03184                                 }
03185                         } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
03186                                 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
03187                                 foreach ( $wgAdvertisedFeedTypes as $format ) {
03188                                         $tags[] = $this->feedLink(
03189                                                 $format,
03190                                                 $rctitle->getLocalURL( "feed={$format}" ),
03191                                                 $this->msg( "site-{$format}-feed", $wgSitename )->text() # For grep: 'site-rss-feed', 'site-atom-feed'.
03192                                         );
03193                                 }
03194                         }
03195                 }
03196                 return implode( "\n", $tags );
03197         }
03198 
03207         private function feedLink( $type, $url, $text ) {
03208                 return Html::element( 'link', array(
03209                         'rel' => 'alternate',
03210                         'type' => "application/$type+xml",
03211                         'title' => $text,
03212                         'href' => $url )
03213                 );
03214         }
03215 
03225         public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
03226                 $options = array();
03227                 // Even though we expect the media type to be lowercase, but here we
03228                 // force it to lowercase to be safe.
03229                 if( $media ) {
03230                         $options['media'] = $media;
03231                 }
03232                 if( $condition ) {
03233                         $options['condition'] = $condition;
03234                 }
03235                 if( $dir ) {
03236                         $options['dir'] = $dir;
03237                 }
03238                 $this->styles[$style] = $options;
03239         }
03240 
03246         public function addInlineStyle( $style_css, $flip = 'noflip' ) {
03247                 if( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
03248                         # If wanted, and the interface is right-to-left, flip the CSS
03249                         $style_css = CSSJanus::transform( $style_css, true, false );
03250                 }
03251                 $this->mInlineStyles .= Html::inlineStyle( $style_css );
03252         }
03253 
03260         public function buildCssLinks() {
03261                 global $wgUseSiteCss, $wgAllowUserCss, $wgAllowUserCssPrefs,
03262                         $wgLang, $wgContLang;
03263 
03264                 $this->getSkin()->setupSkinUserCss( $this );
03265 
03266                 // Add ResourceLoader styles
03267                 // Split the styles into four groups
03268                 $styles = array( 'other' => array(), 'user' => array(), 'site' => array(), 'private' => array(), 'noscript' => array() );
03269                 $otherTags = ''; // Tags to append after the normal <link> tags
03270                 $resourceLoader = $this->getResourceLoader();
03271 
03272                 $moduleStyles = $this->getModuleStyles();
03273 
03274                 // Per-site custom styles
03275                 if ( $wgUseSiteCss ) {
03276                         $moduleStyles[] = 'site';
03277                         $moduleStyles[] = 'noscript';
03278                         if( $this->getUser()->isLoggedIn() ){
03279                                 $moduleStyles[] = 'user.groups';
03280                         }
03281                 }
03282 
03283                 // Per-user custom styles
03284                 if ( $wgAllowUserCss ) {
03285                         if ( $this->getTitle()->isCssSubpage() && $this->userCanPreview() ) {
03286                                 // We're on a preview of a CSS subpage
03287                                 // Exclude this page from the user module in case it's in there (bug 26283)
03288                                 $otherTags .= $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_STYLES, false,
03289                                         array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() )
03290                                 );
03291                                 
03292                                 // Load the previewed CSS
03293                                 // If needed, Janus it first. This is user-supplied CSS, so it's
03294                                 // assumed to be right for the content language directionality.
03295                                 $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' );
03296                                 if ( $wgLang->getDir() !== $wgContLang->getDir() ) {
03297                                         $previewedCSS = CSSJanus::transform( $previewedCSS, true, false );
03298                                 }
03299                                 $otherTags .= Html::inlineStyle( $previewedCSS );
03300                         } else {
03301                                 // Load the user styles normally
03302                                 $moduleStyles[] = 'user';
03303                         }
03304                 }
03305 
03306                 // Per-user preference styles
03307                 if ( $wgAllowUserCssPrefs ) {
03308                         $moduleStyles[] = 'user.cssprefs';
03309                 }
03310 
03311                 foreach ( $moduleStyles as $name ) {
03312                         $module = $resourceLoader->getModule( $name );
03313                         if ( !$module ) {
03314                                 continue;
03315                         }
03316                         $group = $module->getGroup();
03317                         // Modules in groups named "other" or anything different than "user", "site" or "private"
03318                         // will be placed in the "other" group
03319                         $styles[isset( $styles[$group] ) ? $group : 'other'][] = $name;
03320                 }
03321 
03322                 // We want site, private and user styles to override dynamically added styles from modules, but we want
03323                 // dynamically added styles to override statically added styles from other modules. So the order
03324                 // has to be other, dynamic, site, private, user
03325                 // Add statically added styles for other modules
03326                 $ret = $this->makeResourceLoaderLink( $styles['other'], ResourceLoaderModule::TYPE_STYLES );
03327                 // Add normal styles added through addStyle()/addInlineStyle() here
03328                 $ret .= implode( "\n", $this->buildCssLinksArray() ) . $this->mInlineStyles;
03329                 // Add marker tag to mark the place where the client-side loader should inject dynamic styles
03330                 // We use a <meta> tag with a made-up name for this because that's valid HTML
03331                 $ret .= Html::element( 'meta', array( 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ) ) . "\n";
03332 
03333                 // Add site, private and user styles
03334                 // 'private' at present only contains user.options, so put that before 'user'
03335                 // Any future private modules will likely have a similar user-specific character
03336                 foreach ( array( 'site', 'noscript', 'private', 'user' ) as $group ) {
03337                         $ret .= $this->makeResourceLoaderLink( $styles[$group],
03338                                         ResourceLoaderModule::TYPE_STYLES
03339                         );
03340                 }
03341 
03342                 // Add stuff in $otherTags (previewed user CSS if applicable)
03343                 $ret .= $otherTags;
03344                 return $ret;
03345         }
03346 
03350         public function buildCssLinksArray() {
03351                 $links = array();
03352 
03353                 // Add any extension CSS
03354                 foreach ( $this->mExtStyles as $url ) {
03355                         $this->addStyle( $url );
03356                 }
03357                 $this->mExtStyles = array();
03358 
03359                 foreach( $this->styles as $file => $options ) {
03360                         $link = $this->styleLink( $file, $options );
03361                         if( $link ) {
03362                                 $links[$file] = $link;
03363                         }
03364                 }
03365                 return $links;
03366         }
03367 
03376         protected function styleLink( $style, $options ) {
03377                 if( isset( $options['dir'] ) ) {
03378                         if( $this->getLanguage()->getDir() != $options['dir'] ) {
03379                                 return '';
03380                         }
03381                 }
03382 
03383                 if( isset( $options['media'] ) ) {
03384                         $media = self::transformCssMedia( $options['media'] );
03385                         if( is_null( $media ) ) {
03386                                 return '';
03387                         }
03388                 } else {
03389                         $media = 'all';
03390                 }
03391 
03392                 if( substr( $style, 0, 1 ) == '/' ||
03393                         substr( $style, 0, 5 ) == 'http:' ||
03394                         substr( $style, 0, 6 ) == 'https:' ) {
03395                         $url = $style;
03396                 } else {
03397                         global $wgStylePath, $wgStyleVersion;
03398                         $url = $wgStylePath . '/' . $style . '?' . $wgStyleVersion;
03399                 }
03400 
03401                 $link = Html::linkedStyle( $url, $media );
03402 
03403                 if( isset( $options['condition'] ) ) {
03404                         $condition = htmlspecialchars( $options['condition'] );
03405                         $link = "<!--[if $condition]>$link<![endif]-->";
03406                 }
03407                 return $link;
03408         }
03409 
03416         public static function transformCssMedia( $media ) {
03417                 global $wgRequest, $wgHandheldForIPhone;
03418 
03419                 // Switch in on-screen display for media testing
03420                 $switches = array(
03421                         'printable' => 'print',
03422                         'handheld' => 'handheld',
03423                 );
03424                 foreach( $switches as $switch => $targetMedia ) {
03425                         if( $wgRequest->getBool( $switch ) ) {
03426                                 if( $media == $targetMedia ) {
03427                                         $media = '';
03428                                 } elseif( $media == 'screen' ) {
03429                                         return null;
03430                                 }
03431                         }
03432                 }
03433 
03434                 // Expand longer media queries as iPhone doesn't grok 'handheld'
03435                 if( $wgHandheldForIPhone ) {
03436                         $mediaAliases = array(
03437                                 'screen' => 'screen and (min-device-width: 481px)',
03438                                 'handheld' => 'handheld, only screen and (max-device-width: 480px)',
03439                         );
03440 
03441                         if( isset( $mediaAliases[$media] ) ) {
03442                                 $media = $mediaAliases[$media];
03443                         }
03444                 }
03445 
03446                 return $media;
03447         }
03448 
03455         public function addWikiMsg( /*...*/ ) {
03456                 $args = func_get_args();
03457                 $name = array_shift( $args );
03458                 $this->addWikiMsgArray( $name, $args );
03459         }
03460 
03469         public function addWikiMsgArray( $name, $args ) {
03470                 $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
03471         }
03472 
03499         public function wrapWikiMsg( $wrap /*, ...*/ ) {
03500                 $msgSpecs = func_get_args();
03501                 array_shift( $msgSpecs );
03502                 $msgSpecs = array_values( $msgSpecs );
03503                 $s = $wrap;
03504                 foreach ( $msgSpecs as $n => $spec ) {
03505                         $options = array();
03506                         if ( is_array( $spec ) ) {
03507                                 $args = $spec;
03508                                 $name = array_shift( $args );
03509                                 if ( isset( $args['options'] ) ) {
03510                                         $options = $args['options'];
03511                                         unset( $args['options'] );
03512                                 }
03513                         }  else {
03514                                 $args = array();
03515                                 $name = $spec;
03516                         }
03517                         $s = str_replace( '$' . ( $n + 1 ), wfMsgExt( $name, $options, $args ), $s );
03518                 }
03519                 $this->addWikiText( $s );
03520         }
03521 
03531         public function includeJQuery( $modules = array() ) {
03532                 return array();
03533         }
03534 
03535 }