00001 <?php
00002 if ( ! defined( 'MEDIAWIKI' ) )
00003 die( 1 );
00004
00008 class OutputPage {
00009 var $mMetatags = array(), $mKeywords = array(), $mLinktags = array();
00010 var $mExtStyles = array();
00011 var $mPagetitle = '', $mBodytext = '', $mDebugtext = '';
00012 var $mHTMLtitle = '', $mHTMLtitleFromPagetitle = true, $mIsarticle = true, $mPrintable = false;
00013 var $mSubtitle = '', $mRedirect = '', $mStatusCode;
00014 var $mLastModified = '', $mETag = false;
00015 var $mCategoryLinks = array(), $mCategories = array(), $mLanguageLinks = array();
00016
00017 var $mScripts = '', $mLinkColours, $mPageLinkTitle = '', $mHeadItems = array();
00018 var $mInlineMsg = array();
00019
00020 var $mTemplateIds = array();
00021
00022 var $mAllowUserJs;
00023 var $mSuppressQuickbar = false;
00024 var $mDoNothing = false;
00025 var $mContainsOldMagic = 0, $mContainsNewMagic = 0;
00026 var $mIsArticleRelated = true;
00027 protected $mParserOptions = null;
00028
00029 var $mFeedLinks = array();
00030
00031 var $mEnableClientCache = true;
00032 var $mArticleBodyOnly = false;
00033
00034 var $mNewSectionLink = false;
00035 var $mHideNewSectionLink = false;
00036 var $mNoGallery = false;
00037 var $mPageTitleActionText = '';
00038 var $mParseWarnings = array();
00039 var $mSquidMaxage = 0;
00040 var $mPreventClickjacking = true;
00041 var $mRevisionId = null;
00042 protected $mTitle = null;
00043
00049 var $styles = array();
00050
00054 protected $mJQueryDone = false;
00055
00056 private $mIndexPolicy = 'index';
00057 private $mFollowPolicy = 'follow';
00058 private $mVaryHeader = array( 'Accept-Encoding' => array('list-contains=gzip'),
00059 'Cookie' => null );
00060
00061
00066 function __construct() {
00067 global $wgAllowUserJs;
00068 $this->mAllowUserJs = $wgAllowUserJs;
00069 }
00070
00077 public function redirect( $url, $responsecode = '302' ) {
00078 # Strip newlines as a paranoia check for header injection in PHP<5.1.2
00079 $this->mRedirect = str_replace( "\n", '', $url );
00080 $this->mRedirectCode = $responsecode;
00081 }
00082
00088 public function getRedirect() {
00089 return $this->mRedirect;
00090 }
00091
00098 public function setStatusCode( $statusCode ) {
00099 $this->mStatusCode = $statusCode;
00100 }
00101
00102
00110 function addMeta( $name, $val ) {
00111 array_push( $this->mMetatags, array( $name, $val ) );
00112 }
00113
00119 function addKeyword( $text ) {
00120 if( is_array( $text ) ) {
00121 $this->mKeywords = array_merge( $this->mKeywords, $text );
00122 } else {
00123 array_push( $this->mKeywords, $text );
00124 }
00125 }
00126
00132 function addLink( $linkarr ) {
00133 array_push( $this->mLinktags, $linkarr );
00134 }
00135
00143 function addMetadataLink( $linkarr ) {
00144 # note: buggy CC software only reads first "meta" link
00145 static $haveMeta = false;
00146 $linkarr['rel'] = $haveMeta ? 'alternate meta' : 'meta';
00147 $this->addLink( $linkarr );
00148 $haveMeta = true;
00149 }
00150
00151
00157 function addScript( $script ) {
00158 $this->mScripts .= $script . "\n";
00159 }
00160
00169 public function addExtensionStyle( $url ) {
00170 array_push( $this->mExtStyles, $url );
00171 }
00172
00178 function getExtStyle() {
00179 return $this->mExtStyles;
00180 }
00181
00188 public function addScriptFile( $file ) {
00189 global $wgStylePath, $wgStyleVersion;
00190 if( substr( $file, 0, 1 ) == '/' || substr( $file, 0, 7 ) == 'http://' ) {
00191 $path = $file;
00192 } else {
00193 $path = "{$wgStylePath}/common/{$file}";
00194 }
00195 $this->addScript( Html::linkedScript( wfAppendQuery( $path, $wgStyleVersion ) ) );
00196 }
00197
00203 public function addInlineScript( $script ) {
00204 $this->mScripts .= Html::inlineScript( "\n$script\n" ) . "\n";
00205 }
00206
00212 function getScript() {
00213 return $this->mScripts . $this->getHeadItems();
00214 }
00215
00221 function getHeadItems() {
00222 $s = '';
00223 foreach ( $this->mHeadItems as $item ) {
00224 $s .= $item;
00225 }
00226 return $s;
00227 }
00228
00235 public function addHeadItem( $name, $value ) {
00236 $this->mHeadItems[$name] = $value;
00237 }
00238
00245 public function hasHeadItem( $name ) {
00246 return isset( $this->mHeadItems[$name] );
00247 }
00248
00254 function setETag( $tag ) {
00255 $this->mETag = $tag;
00256 }
00257
00265 public function setArticleBodyOnly( $only ) {
00266 $this->mArticleBodyOnly = $only;
00267 }
00268
00274 public function getArticleBodyOnly() {
00275 return $this->mArticleBodyOnly;
00276 }
00277
00278
00288 public function checkLastModified( $timestamp ) {
00289 global $wgCachePages, $wgCacheEpoch, $wgUser, $wgRequest;
00290
00291 if ( !$timestamp || $timestamp == '19700101000000' ) {
00292 wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
00293 return false;
00294 }
00295 if( !$wgCachePages ) {
00296 wfDebug( __METHOD__ . ": CACHE DISABLED\n", false );
00297 return false;
00298 }
00299 if( $wgUser->getOption( 'nocache' ) ) {
00300 wfDebug( __METHOD__ . ": USER DISABLED CACHE\n", false );
00301 return false;
00302 }
00303
00304 $timestamp = wfTimestamp( TS_MW, $timestamp );
00305 $modifiedTimes = array(
00306 'page' => $timestamp,
00307 'user' => $wgUser->getTouched(),
00308 'epoch' => $wgCacheEpoch
00309 );
00310 wfRunHooks( 'OutputPageCheckLastModified', array( &$modifiedTimes ) );
00311
00312 $maxModified = max( $modifiedTimes );
00313 $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
00314
00315 if( empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
00316 wfDebug( __METHOD__ . ": client did not send If-Modified-Since header\n", false );
00317 return false;
00318 }
00319
00320 # Make debug info
00321 $info = '';
00322 foreach ( $modifiedTimes as $name => $value ) {
00323 if ( $info !== '' ) {
00324 $info .= ', ';
00325 }
00326 $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
00327 }
00328
00329 # IE sends sizes after the date like this:
00330 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
00331 # this breaks strtotime().
00332 $clientHeader = preg_replace( '/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"] );
00333
00334 wfSuppressWarnings();
00335 $clientHeaderTime = strtotime( $clientHeader );
00336 wfRestoreWarnings();
00337 if ( !$clientHeaderTime ) {
00338 wfDebug( __METHOD__ . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
00339 return false;
00340 }
00341 $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
00342
00343 wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
00344 wfTimestamp( TS_ISO_8601, $clientHeaderTime ) . "\n", false );
00345 wfDebug( __METHOD__ . ": effective Last-Modified: " .
00346 wfTimestamp( TS_ISO_8601, $maxModified ) . "\n", false );
00347 if( $clientHeaderTime < $maxModified ) {
00348 wfDebug( __METHOD__ . ": STALE, $info\n", false );
00349 return false;
00350 }
00351
00352 # Not modified
00353 # Give a 304 response code and disable body output
00354 wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", false );
00355 ini_set('zlib.output_compression', 0);
00356 $wgRequest->response()->header( "HTTP/1.1 304 Not Modified" );
00357 $this->sendCacheControl();
00358 $this->disable();
00359
00360
00361
00362
00363 wfClearOutputBuffers();
00364
00365 return true;
00366 }
00367
00368
00377 public function setRobotPolicy( $policy ) {
00378 $policy = Article::formatRobotPolicy( $policy );
00379
00380 if( isset( $policy['index'] ) ){
00381 $this->setIndexPolicy( $policy['index'] );
00382 }
00383 if( isset( $policy['follow'] ) ){
00384 $this->setFollowPolicy( $policy['follow'] );
00385 }
00386 }
00387
00395 public function setIndexPolicy( $policy ) {
00396 $policy = trim( $policy );
00397 if( in_array( $policy, array( 'index', 'noindex' ) ) ) {
00398 $this->mIndexPolicy = $policy;
00399 }
00400 }
00401
00409 public function setFollowPolicy( $policy ) {
00410 $policy = trim( $policy );
00411 if( in_array( $policy, array( 'follow', 'nofollow' ) ) ) {
00412 $this->mFollowPolicy = $policy;
00413 }
00414 }
00415
00416
00423 public function setPageTitleActionText( $text ) {
00424 $this->mPageTitleActionText = $text;
00425 }
00426
00432 public function getPageTitleActionText() {
00433 if ( isset( $this->mPageTitleActionText ) ) {
00434 return $this->mPageTitleActionText;
00435 }
00436 }
00437
00444 public function setHTMLTitle( $name, $frompagetitle = false ) {
00445 if ( $frompagetitle && $this->mHTMLtitleFromPagetitle ) {
00446 $this->mHTMLtitle = $name;
00447 }
00448 elseif ( $this->mHTMLtitleFromPagetitle ) {
00449 $this->mHTMLtitle = $name;
00450 $this->mHTMLtitleFromPagetitle = false;
00451 }
00452 }
00453
00459 public function getHTMLTitle() {
00460 return $this->mHTMLtitle;
00461 }
00462
00469 public function setPageTitle( $name ) {
00470 # change "<script>foo&bar</script>" to "<script>foo&bar</script>"
00471 # but leave "<i>foobar</i>" alone
00472 $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) );
00473 $this->mPagetitle = $nameWithTags;
00474
00475 $taction = $this->getPageTitleActionText();
00476 if( !empty( $taction ) ) {
00477 $name .= ' - '.$taction;
00478 }
00479
00480 # change "<i>foo&bar</i>" to "foo&bar"
00481 $this->setHTMLTitle( wfMsg( 'pagetitle', Sanitizer::stripAllTags( $nameWithTags ) ) );
00482 }
00483
00489 public function getPageTitle() {
00490 return $this->mPagetitle;
00491 }
00492
00498 public function setTitle( $t ) {
00499 $this->mTitle = $t;
00500 }
00501
00507 public function getTitle() {
00508 if ( $this->mTitle instanceof Title ) {
00509 return $this->mTitle;
00510 } else {
00511 wfDebug( __METHOD__ . ' called and $mTitle is null. Return $wgTitle for sanity' );
00512 global $wgTitle;
00513 return $wgTitle;
00514 }
00515 }
00516
00522 public function setSubtitle( $str ) {
00523 $this->mSubtitle = $str ;
00524 }
00525
00531 public function appendSubtitle( $str ) {
00532 $this->mSubtitle .= $str ;
00533 }
00534
00540 public function getSubtitle() {
00541 return $this->mSubtitle;
00542 }
00543
00544
00549 public function setPrintable() {
00550 $this->mPrintable = true;
00551 }
00552
00558 public function isPrintable() {
00559 return $this->mPrintable;
00560 }
00561
00562
00566 public function disable() {
00567 $this->mDoNothing = true;
00568 }
00569
00575 public function isDisabled() {
00576 return $this->mDoNothing;
00577 }
00578
00579
00585 public function showNewSectionLink() {
00586 return $this->mNewSectionLink;
00587 }
00588
00594 public function forceHideNewSectionLink() {
00595 return $this->mHideNewSectionLink;
00596 }
00597
00598
00607 public function setSyndicated( $show = true ) {
00608 if ( $show ) {
00609 $this->setFeedAppendQuery( false );
00610 } else {
00611 $this->mFeedLinks = array();
00612 }
00613 }
00614
00624 public function setFeedAppendQuery( $val ) {
00625 global $wgAdvertisedFeedTypes;
00626
00627 $this->mFeedLinks = array();
00628
00629 foreach ( $wgAdvertisedFeedTypes as $type ) {
00630 $query = "feed=$type";
00631 if ( is_string( $val ) ) {
00632 $query .= '&' . $val;
00633 }
00634 $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
00635 }
00636 }
00637
00644 public function addFeedLink( $format, $href ) {
00645 $this->mFeedLinks[$format] = $href;
00646 }
00647
00652 public function isSyndicated() {
00653 return count( $this->mFeedLinks ) > 0;
00654 }
00655
00660 public function getSyndicationLinks() {
00661 return $this->mFeedLinks;
00662 }
00663
00669 public function getFeedAppendQuery() {
00670 return $this->mFeedLinksAppendQuery;
00671 }
00672
00680 public function setArticleFlag( $v ) {
00681 $this->mIsarticle = $v;
00682 if ( $v ) {
00683 $this->mIsArticleRelated = $v;
00684 }
00685 }
00686
00693 public function isArticle() {
00694 return $this->mIsarticle;
00695 }
00696
00703 public function setArticleRelated( $v ) {
00704 $this->mIsArticleRelated = $v;
00705 if ( !$v ) {
00706 $this->mIsarticle = false;
00707 }
00708 }
00709
00715 public function isArticleRelated() {
00716 return $this->mIsArticleRelated;
00717 }
00718
00719
00726 public function addLanguageLinks( $newLinkArray ) {
00727 $this->mLanguageLinks += $newLinkArray;
00728 }
00729
00736 public function setLanguageLinks( $newLinkArray ) {
00737 $this->mLanguageLinks = $newLinkArray;
00738 }
00739
00745 public function getLanguageLinks() {
00746 return $this->mLanguageLinks;
00747 }
00748
00749
00755 public function addCategoryLinks( $categories ) {
00756 global $wgUser, $wgContLang;
00757
00758 if ( !is_array( $categories ) || count( $categories ) == 0 ) {
00759 return;
00760 }
00761
00762 # Add the links to a LinkBatch
00763 $arr = array( NS_CATEGORY => $categories );
00764 $lb = new LinkBatch;
00765 $lb->setArray( $arr );
00766
00767 # Fetch existence plus the hiddencat property
00768 $dbr = wfGetDB( DB_SLAVE );
00769 $pageTable = $dbr->tableName( 'page' );
00770 $where = $lb->constructSet( 'page', $dbr );
00771 $propsTable = $dbr->tableName( 'page_props' );
00772 $sql = "SELECT page_id, page_namespace, page_title, page_len, page_is_redirect, pp_value
00773 FROM $pageTable LEFT JOIN $propsTable ON pp_propname='hiddencat' AND pp_page=page_id WHERE $where";
00774 $res = $dbr->query( $sql, __METHOD__ );
00775
00776 # Add the results to the link cache
00777 $lb->addResultToCache( LinkCache::singleton(), $res );
00778
00779 # Set all the values to 'normal'. This can be done with array_fill_keys in PHP 5.2.0+
00780 $categories = array_combine( array_keys( $categories ),
00781 array_fill( 0, count( $categories ), 'normal' ) );
00782
00783 # Mark hidden categories
00784 foreach ( $res as $row ) {
00785 if ( isset( $row->pp_value ) ) {
00786 $categories[$row->page_title] = 'hidden';
00787 }
00788 }
00789
00790 # Add the remaining categories to the skin
00791 if ( wfRunHooks( 'OutputPageMakeCategoryLinks', array( &$this, $categories, &$this->mCategoryLinks ) ) ) {
00792 $sk = $wgUser->getSkin();
00793 foreach ( $categories as $category => $type ) {
00794 $origcategory = $category;
00795 $title = Title::makeTitleSafe( NS_CATEGORY, $category );
00796 $wgContLang->findVariantLink( $category, $title, true );
00797 if ( $category != $origcategory )
00798 if ( array_key_exists( $category, $categories ) )
00799 continue;
00800 $text = $wgContLang->convertHtml( $title->getText() );
00801 $this->mCategories[] = $title->getText();
00802 $this->mCategoryLinks[$type][] = $sk->link( $title, $text );
00803 }
00804 }
00805 }
00806
00812 public function setCategoryLinks( $categories ) {
00813 $this->mCategoryLinks = array();
00814 $this->addCategoryLinks( $categories );
00815 }
00816
00825 public function getCategoryLinks() {
00826 return $this->mCategoryLinks;
00827 }
00828
00834 public function getCategories() {
00835 return $this->mCategories;
00836 }
00837
00838
00843 public function suppressQuickbar() {
00844 $this->mSuppressQuickbar = true;
00845 }
00846
00852 public function isQuickbarSuppressed() {
00853 return $this->mSuppressQuickbar;
00854 }
00855
00856
00860 public function disallowUserJs() {
00861 $this->mAllowUserJs = false;
00862 }
00863
00869 public function isUserJsAllowed() {
00870 return $this->mAllowUserJs;
00871 }
00872
00873
00879 public function prependHTML( $text ) {
00880 $this->mBodytext = $text . $this->mBodytext;
00881 }
00882
00888 public function addHTML( $text ) {
00889 $this->mBodytext .= $text;
00890 }
00891
00895 public function clearHTML() {
00896 $this->mBodytext = '';
00897 }
00898
00904 public function getHTML() {
00905 return $this->mBodytext;
00906 }
00907
00908
00914 public function debug( $text ) {
00915 $this->mDebugtext .= $text;
00916 }
00917
00918
00922 public function setParserOptions( $options ) {
00923 wfDeprecated( __METHOD__ );
00924 return $this->parserOptions( $options );
00925 }
00926
00934 public function parserOptions( $options = null ) {
00935 if ( !$this->mParserOptions ) {
00936 $this->mParserOptions = new ParserOptions;
00937 }
00938 return wfSetVar( $this->mParserOptions, $options );
00939 }
00940
00948 public function setRevisionId( $revid ) {
00949 $val = is_null( $revid ) ? null : intval( $revid );
00950 return wfSetVar( $this->mRevisionId, $val );
00951 }
00952
00958 public function getRevisionId() {
00959 return $this->mRevisionId;
00960 }
00961
00969 public function addWikiText( $text, $linestart = true ) {
00970 $title = $this->getTitle();
00971 $this->addWikiTextTitle( $text, $title, $linestart );
00972 }
00973
00981 public function addWikiTextWithTitle( $text, &$title, $linestart = true ) {
00982 $this->addWikiTextTitle( $text, $title, $linestart );
00983 }
00984
00992 function addWikiTextTitleTidy( $text, &$title, $linestart = true ) {
00993 $this->addWikiTextTitle( $text, $title, $linestart, true );
00994 }
00995
01002 public function addWikiTextTidy( $text, $linestart = true ) {
01003 $title = $this->getTitle();
01004 $this->addWikiTextTitleTidy($text, $title, $linestart);
01005 }
01006
01015 public function addWikiTextTitle( $text, &$title, $linestart, $tidy = false ) {
01016 global $wgParser;
01017
01018 wfProfileIn( __METHOD__ );
01019
01020 wfIncrStats( 'pcache_not_possible' );
01021
01022 $popts = $this->parserOptions();
01023 $oldTidy = $popts->setTidy( $tidy );
01024
01025 $parserOutput = $wgParser->parse( $text, $title, $popts,
01026 $linestart, true, $this->mRevisionId );
01027
01028 $popts->setTidy( $oldTidy );
01029
01030 $this->addParserOutput( $parserOutput );
01031
01032 wfProfileOut( __METHOD__ );
01033 }
01034
01044 public function addPrimaryWikiText( $text, $article, $cache = true ) {
01045 global $wgParser;
01046
01047 wfDeprecated( __METHOD__ );
01048
01049 $popts = $this->parserOptions();
01050 $popts->setTidy(true);
01051 $parserOutput = $wgParser->parse( $text, $article->mTitle,
01052 $popts, true, true, $this->mRevisionId );
01053 $popts->setTidy(false);
01054 if ( $cache && $article && $parserOutput->getCacheTime() != -1 ) {
01055 $parserCache = ParserCache::singleton();
01056 $parserCache->save( $parserOutput, $article, $popts);
01057 }
01058
01059 $this->addParserOutput( $parserOutput );
01060 }
01061
01065 public function addSecondaryWikiText( $text, $linestart = true ) {
01066 wfDeprecated( __METHOD__ );
01067 $this->addWikiTextTitleTidy($text, $this->getTitle(), $linestart);
01068 }
01069
01070
01076 public function addParserOutputNoText( &$parserOutput ) {
01077 global $wgExemptFromUserRobotsControl, $wgContentNamespaces;
01078
01079 $this->mLanguageLinks += $parserOutput->getLanguageLinks();
01080 $this->addCategoryLinks( $parserOutput->getCategories() );
01081 $this->mNewSectionLink = $parserOutput->getNewSection();
01082 $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
01083
01084 $this->mParseWarnings = $parserOutput->getWarnings();
01085 if ( $parserOutput->getCacheTime() == -1 ) {
01086 $this->enableClientCache( false );
01087 }
01088 $this->mNoGallery = $parserOutput->getNoGallery();
01089 $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
01090
01091 foreach ( (array)$parserOutput->mTemplateIds as $ns => $dbks ) {
01092 if ( isset( $this->mTemplateIds[$ns] ) ) {
01093 $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
01094 } else {
01095 $this->mTemplateIds[$ns] = $dbks;
01096 }
01097 }
01098
01099 $title = $parserOutput->getTitleText();
01100 if ( $title != '' ) {
01101 $this->setPageTitle( $title );
01102 }
01103
01104
01105 global $wgParserOutputHooks;
01106 foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
01107 list( $hookName, $data ) = $hookInfo;
01108 if ( isset( $wgParserOutputHooks[$hookName] ) ) {
01109 call_user_func( $wgParserOutputHooks[$hookName], $this, $parserOutput, $data );
01110 }
01111 }
01112
01113 wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) );
01114 }
01115
01121 function addParserOutput( &$parserOutput ) {
01122 $this->addParserOutputNoText( $parserOutput );
01123 $text = $parserOutput->getText();
01124 wfRunHooks( 'OutputPageBeforeHTML',array( &$this, &$text ) );
01125 $this->addHTML( $text );
01126 }
01127
01128
01134 public function addTemplate( &$template ) {
01135 ob_start();
01136 $template->execute();
01137 $this->addHTML( ob_get_contents() );
01138 ob_end_clean();
01139 }
01140
01151 public function parse( $text, $linestart = true, $interface = false ) {
01152 global $wgParser;
01153 if( is_null( $this->getTitle() ) ) {
01154 throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
01155 }
01156 $popts = $this->parserOptions();
01157 if ( $interface) { $popts->setInterfaceMessage(true); }
01158 $parserOutput = $wgParser->parse( $text, $this->getTitle(), $popts,
01159 $linestart, true, $this->mRevisionId );
01160 if ( $interface) { $popts->setInterfaceMessage(false); }
01161 return $parserOutput->getText();
01162 }
01163
01174 public function parseInline( $text, $linestart = true, $interface = false ) {
01175 $parsed = $this->parse( $text, $linestart, $interface );
01176
01177 $m = array();
01178 if ( preg_match( '/^<p>(.*)\n?<\/p>\n?/sU', $parsed, $m ) ) {
01179 $parsed = $m[1];
01180 }
01181
01182 return $parsed;
01183 }
01184
01191 public function tryParserCache( &$article ) {
01192 wfDeprecated( __METHOD__ );
01193 $parserOutput = ParserCache::singleton()->get( $article, $article->getParserOptions() );
01194
01195 if ($parserOutput !== false) {
01196 $this->addParserOutput( $parserOutput );
01197 return true;
01198 } else {
01199 return false;
01200 }
01201 }
01202
01208 public function setSquidMaxage( $maxage ) {
01209 $this->mSquidMaxage = $maxage;
01210 }
01211
01217 public function enableClientCache( $state ) {
01218 return wfSetVar( $this->mEnableClientCache, $state );
01219 }
01220
01226 function getCacheVaryCookies() {
01227 global $wgCookiePrefix, $wgCacheVaryCookies;
01228 static $cookies;
01229 if ( $cookies === null ) {
01230 $cookies = array_merge(
01231 array(
01232 "{$wgCookiePrefix}Token",
01233 "{$wgCookiePrefix}LoggedOut",
01234 session_name()
01235 ),
01236 $wgCacheVaryCookies
01237 );
01238 wfRunHooks('GetCacheVaryCookies', array( $this, &$cookies ) );
01239 }
01240 return $cookies;
01241 }
01242
01249 function uncacheableBecauseRequestVars() {
01250 global $wgRequest;
01251 return $wgRequest->getText('useskin', false) === false
01252 && $wgRequest->getText('uselang', false) === false;
01253 }
01254
01261 function haveCacheVaryCookies() {
01262 global $wgRequest;
01263 $cookieHeader = $wgRequest->getHeader( 'cookie' );
01264 if ( $cookieHeader === false ) {
01265 return false;
01266 }
01267 $cvCookies = $this->getCacheVaryCookies();
01268 foreach ( $cvCookies as $cookieName ) {
01269 # Check for a simple string match, like the way squid does it
01270 if ( strpos( $cookieHeader, $cookieName ) !== false ) {
01271 wfDebug( __METHOD__.": found $cookieName\n" );
01272 return true;
01273 }
01274 }
01275 wfDebug( __METHOD__.": no cache-varying cookies found\n" );
01276 return false;
01277 }
01278
01285 public function addVaryHeader( $header, $option = null ) {
01286 if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
01287 $this->mVaryHeader[$header] = $option;
01288 }
01289 elseif( is_array( $option ) ) {
01290 if( is_array( $this->mVaryHeader[$header] ) ) {
01291 $this->mVaryHeader[$header] = array_merge( $this->mVaryHeader[$header], $option );
01292 }
01293 else {
01294 $this->mVaryHeader[$header] = $option;
01295 }
01296 }
01297 $this->mVaryHeader[$header] = array_unique( $this->mVaryHeader[$header] );
01298 }
01299
01305 public function getXVO() {
01306 $cvCookies = $this->getCacheVaryCookies();
01307
01308 $cookiesOption = array();
01309 foreach ( $cvCookies as $cookieName ) {
01310 $cookiesOption[] = 'string-contains=' . $cookieName;
01311 }
01312 $this->addVaryHeader( 'Cookie', $cookiesOption );
01313
01314 $headers = array();
01315 foreach( $this->mVaryHeader as $header => $option ) {
01316 $newheader = $header;
01317 if( is_array( $option ) )
01318 $newheader .= ';' . implode( ';', $option );
01319 $headers[] = $newheader;
01320 }
01321 $xvo = 'X-Vary-Options: ' . implode( ',', $headers );
01322
01323 return $xvo;
01324 }
01325
01336 function addAcceptLanguage() {
01337 global $wgRequest, $wgContLang;
01338 if( !$wgRequest->getCheck('variant') && $wgContLang->hasVariants() ) {
01339 $variants = $wgContLang->getVariants();
01340 $aloption = array();
01341 foreach ( $variants as $variant ) {
01342 if( $variant === $wgContLang->getCode() )
01343 continue;
01344 else
01345 $aloption[] = "string-contains=$variant";
01346 }
01347 $this->addVaryHeader( 'Accept-Language', $aloption );
01348 }
01349 }
01350
01359 public function preventClickjacking( $enable = true ) {
01360 $this->mPreventClickjacking = $enable;
01361 }
01362
01368 public function allowClickjacking() {
01369 $this->mPreventClickjacking = false;
01370 }
01371
01377 public function getFrameOptions() {
01378 global $wgBreakFrames, $wgEditPageFrameOptions;
01379 if ( $wgBreakFrames ) {
01380 return 'DENY';
01381 } elseif ( $this->mPreventClickjacking && $wgEditPageFrameOptions ) {
01382 return $wgEditPageFrameOptions;
01383 }
01384 }
01385
01389 public function sendCacheControl() {
01390 global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgRequest, $wgUseXVO;
01391
01392 $response = $wgRequest->response();
01393 if ($wgUseETag && $this->mETag)
01394 $response->header("ETag: $this->mETag");
01395
01396 $this->addAcceptLanguage();
01397
01398 # don't serve compressed data to clients who can't handle it
01399 # maintain different caches for logged-in users and non-logged in ones
01400 $response->header( 'Vary: ' . join( ', ', array_keys( $this->mVaryHeader ) ) );
01401
01402 if ( $wgUseXVO ) {
01403 # Add an X-Vary-Options header for Squid with Wikimedia patches
01404 $response->header( $this->getXVO() );
01405 }
01406
01407 if( !$this->uncacheableBecauseRequestVars() && $this->mEnableClientCache ) {
01408 if( $wgUseSquid && session_id() == '' &&
01409 ! $this->isPrintable() && $this->mSquidMaxage != 0 && !$this->haveCacheVaryCookies() )
01410 {
01411 if ( $wgUseESI ) {
01412 # We'll purge the proxy cache explicitly, but require end user agents
01413 # to revalidate against the proxy on each visit.
01414 # Surrogate-Control controls our Squid, Cache-Control downstream caches
01415 wfDebug( __METHOD__ . ": proxy caching with ESI; {$this->mLastModified} **\n", false );
01416 # start with a shorter timeout for initial testing
01417 # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
01418 $response->header( 'Surrogate-Control: max-age='.$wgSquidMaxage.'+'.$this->mSquidMaxage.', content="ESI/1.0"');
01419 $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
01420 } else {
01421 # We'll purge the proxy cache for anons explicitly, but require end user agents
01422 # to revalidate against the proxy on each visit.
01423 # IMPORTANT! The Squid needs to replace the Cache-Control header with
01424 # Cache-Control: s-maxage=0, must-revalidate, max-age=0
01425 wfDebug( __METHOD__ . ": local proxy caching; {$this->mLastModified} **\n", false );
01426 # start with a shorter timeout for initial testing
01427 # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
01428 $response->header( 'Cache-Control: s-maxage='.$this->mSquidMaxage.', must-revalidate, max-age=0' );
01429 }
01430 } else {
01431 # We do want clients to cache if they can, but they *must* check for updates
01432 # on revisiting the page.
01433 wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **\n", false );
01434 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
01435 $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
01436 }
01437 if($this->mLastModified) {
01438 $response->header( "Last-Modified: {$this->mLastModified}" );
01439 }
01440 } else {
01441 wfDebug( __METHOD__ . ": no caching **\n", false );
01442
01443 # In general, the absence of a last modified header should be enough to prevent
01444 # the client from using its cache. We send a few other things just to make sure.
01445 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
01446 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
01447 $response->header( 'Pragma: no-cache' );
01448 }
01449 }
01450
01458 public static function getStatusMessage( $code ) {
01459 static $statusMessage = array(
01460 100 => 'Continue',
01461 101 => 'Switching Protocols',
01462 102 => 'Processing',
01463 200 => 'OK',
01464 201 => 'Created',
01465 202 => 'Accepted',
01466 203 => 'Non-Authoritative Information',
01467 204 => 'No Content',
01468 205 => 'Reset Content',
01469 206 => 'Partial Content',
01470 207 => 'Multi-Status',
01471 300 => 'Multiple Choices',
01472 301 => 'Moved Permanently',
01473 302 => 'Found',
01474 303 => 'See Other',
01475 304 => 'Not Modified',
01476 305 => 'Use Proxy',
01477 307 => 'Temporary Redirect',
01478 400 => 'Bad Request',
01479 401 => 'Unauthorized',
01480 402 => 'Payment Required',
01481 403 => 'Forbidden',
01482 404 => 'Not Found',
01483 405 => 'Method Not Allowed',
01484 406 => 'Not Acceptable',
01485 407 => 'Proxy Authentication Required',
01486 408 => 'Request Timeout',
01487 409 => 'Conflict',
01488 410 => 'Gone',
01489 411 => 'Length Required',
01490 412 => 'Precondition Failed',
01491 413 => 'Request Entity Too Large',
01492 414 => 'Request-URI Too Large',
01493 415 => 'Unsupported Media Type',
01494 416 => 'Request Range Not Satisfiable',
01495 417 => 'Expectation Failed',
01496 422 => 'Unprocessable Entity',
01497 423 => 'Locked',
01498 424 => 'Failed Dependency',
01499 500 => 'Internal Server Error',
01500 501 => 'Not Implemented',
01501 502 => 'Bad Gateway',
01502 503 => 'Service Unavailable',
01503 504 => 'Gateway Timeout',
01504 505 => 'HTTP Version Not Supported',
01505 507 => 'Insufficient Storage'
01506 );
01507 return isset( $statusMessage[$code] ) ? $statusMessage[$code] : null;
01508 }
01509
01514 public function output() {
01515 global $wgUser, $wgOutputEncoding, $wgRequest;
01516 global $wgContLanguageCode, $wgDebugRedirects, $wgMimeType;
01517 global $wgUseAjax, $wgAjaxWatch;
01518 global $wgEnableMWSuggest, $wgUniversalEditButton;
01519 global $wgArticle;
01520
01521 if( $this->mDoNothing ){
01522 return;
01523 }
01524 wfProfileIn( __METHOD__ );
01525 if ( $this->mRedirect != '' ) {
01526 # Standards require redirect URLs to be absolute
01527 $this->mRedirect = wfExpandUrl( $this->mRedirect );
01528 if( $this->mRedirectCode == '301' || $this->mRedirectCode == '303' ) {
01529 if( !$wgDebugRedirects ) {
01530 $message = self::getStatusMessage( $this->mRedirectCode );
01531 $wgRequest->response()->header( "HTTP/1.1 {$this->mRedirectCode} $message" );
01532 }
01533 $this->mLastModified = wfTimestamp( TS_RFC2822 );
01534 }
01535 $this->sendCacheControl();
01536
01537 $wgRequest->response()->header( "Content-Type: text/html; charset=utf-8" );
01538 if( $wgDebugRedirects ) {
01539 $url = htmlspecialchars( $this->mRedirect );
01540 print "<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
01541 print "<p>Location: <a href=\"$url\">$url</a></p>\n";
01542 print "</body>\n</html>\n";
01543 } else {
01544 $wgRequest->response()->header( 'Location: ' . $this->mRedirect );
01545 }
01546 wfProfileOut( __METHOD__ );
01547 return;
01548 } elseif ( $this->mStatusCode ) {
01549 $message = self::getStatusMessage( $this->mStatusCode );
01550 if ( $message )
01551 $wgRequest->response()->header( 'HTTP/1.1 ' . $this->mStatusCode . ' ' . $message );
01552 }
01553
01554 $sk = $wgUser->getSkin();
01555
01556 if ( $wgUseAjax ) {
01557 $this->addScriptFile( 'ajax.js' );
01558
01559 wfRunHooks( 'AjaxAddScript', array( &$this ) );
01560
01561 if( $wgAjaxWatch && $wgUser->isLoggedIn() ) {
01562 $this->addScriptFile( 'ajaxwatch.js' );
01563 }
01564
01565 if ( $wgEnableMWSuggest && !$wgUser->getOption( 'disablesuggest', false ) ){
01566 $this->addScriptFile( 'mwsuggest.js' );
01567 }
01568 }
01569
01570 if( $wgUser->getBoolOption( 'editsectiononrightclick' ) ) {
01571 $this->addScriptFile( 'rightclickedit.js' );
01572 }
01573
01574 if( $wgUniversalEditButton ) {
01575 if( isset( $wgArticle ) && $this->getTitle() && $this->getTitle()->quickUserCan( 'edit' )
01576 && ( $this->getTitle()->exists() || $this->getTitle()->quickUserCan( 'create' ) ) ) {
01577
01578 $msg = wfMsg('edit');
01579 $this->addLink( array(
01580 'rel' => 'alternate',
01581 'type' => 'application/x-wiki',
01582 'title' => $msg,
01583 'href' => $this->getTitle()->getLocalURL( 'action=edit' )
01584 ) );
01585
01586 $this->addLink( array(
01587 'rel' => 'edit',
01588 'title' => $msg,
01589 'href' => $this->getTitle()->getLocalURL( 'action=edit' )
01590 ) );
01591 }
01592 }
01593
01594 # Buffer output; final headers may depend on later processing
01595 ob_start();
01596
01597 $wgRequest->response()->header( "Content-type: $wgMimeType; charset={$wgOutputEncoding}" );
01598 $wgRequest->response()->header( 'Content-language: '.$wgContLanguageCode );
01599
01600
01601 $frameOptions = $this->getFrameOptions();
01602 if ( $frameOptions ) {
01603 $wgRequest->response()->header( "X-Frame-Options: $frameOptions" );
01604 }
01605
01606
01607 if ($this->mArticleBodyOnly) {
01608 $this->out($this->mBodytext);
01609 } else {
01610
01611
01612 wfRunHooks( 'BeforePageDisplay', array( &$this, &$sk ) );
01613
01614 wfProfileIn( 'Output-skin' );
01615 $sk->outputPage( $this );
01616 wfProfileOut( 'Output-skin' );
01617 }
01618
01619 $this->sendCacheControl();
01620 ob_end_flush();
01621 wfProfileOut( __METHOD__ );
01622 }
01623
01630 public function out( $ins ) {
01631 global $wgInputEncoding, $wgOutputEncoding, $wgContLang;
01632 if ( 0 == strcmp( $wgInputEncoding, $wgOutputEncoding ) ) {
01633 $outs = $ins;
01634 } else {
01635 $outs = $wgContLang->iconv( $wgInputEncoding, $wgOutputEncoding, $ins );
01636 if ( false === $outs ) { $outs = $ins; }
01637 }
01638 print $outs;
01639 }
01640
01644 public static function setEncodings() {
01645 global $wgInputEncoding, $wgOutputEncoding;
01646 global $wgContLang;
01647
01648 $wgInputEncoding = strtolower( $wgInputEncoding );
01649
01650 if ( empty( $_SERVER['HTTP_ACCEPT_CHARSET'] ) ) {
01651 $wgOutputEncoding = strtolower( $wgOutputEncoding );
01652 return;
01653 }
01654 $wgOutputEncoding = $wgInputEncoding;
01655 }
01656
01662 public function reportTime() {
01663 wfDeprecated( __METHOD__ );
01664 $time = wfReportTime();
01665 return $time;
01666 }
01667
01674 function blockedPage( $return = true ) {
01675 global $wgUser, $wgContLang, $wgLang;
01676
01677 $this->setPageTitle( wfMsg( 'blockedtitle' ) );
01678 $this->setRobotPolicy( 'noindex,nofollow' );
01679 $this->setArticleRelated( false );
01680
01681 $name = User::whoIs( $wgUser->blockedBy() );
01682 $reason = $wgUser->blockedFor();
01683 if( $reason == '' ) {
01684 $reason = wfMsg( 'blockednoreason' );
01685 }
01686 $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $wgUser->mBlock->mTimestamp ), true );
01687 $ip = wfGetIP();
01688
01689 $link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]";
01690
01691 $blockid = $wgUser->mBlock->mId;
01692
01693 $blockExpiry = $wgUser->mBlock->mExpiry;
01694 if ( $blockExpiry == 'infinity' ) {
01695
01696
01697 $scBlockExpiryOptions = wfMsg( 'ipboptions' );
01698 foreach ( explode( ',', $scBlockExpiryOptions ) as $option ) {
01699 if ( strpos( $option, ":" ) === false )
01700 continue;
01701 list( $show, $value ) = explode( ":", $option );
01702 if ( $value == 'infinite' || $value == 'indefinite' ) {
01703 $blockExpiry = $show;
01704 break;
01705 }
01706 }
01707 } else {
01708 $blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true );
01709 }
01710
01711 if ( $wgUser->mBlock->mAuto ) {
01712 $msg = 'autoblockedtext';
01713 } else {
01714 $msg = 'blockedtext';
01715 }
01716
01717
01718
01719 $intended = $wgUser->mBlock->mAddress;
01720
01721 $this->addWikiMsg( $msg, $link, $reason, $ip, $name, $blockid, $blockExpiry, $intended, $blockTimestamp );
01722
01723 # Don't auto-return to special pages
01724 if( $return ) {
01725 $return = $this->getTitle()->getNamespace() > -1 ? $this->getTitle() : null;
01726 $this->returnToMain( null, $return );
01727 }
01728 }
01729
01737 public function showErrorPage( $title, $msg, $params = array() ) {
01738 if ( $this->getTitle() ) {
01739 $this->mDebugtext .= 'Original title: ' . $this->getTitle()->getPrefixedText() . "\n";
01740 }
01741 $this->setPageTitle( wfMsg( $title ) );
01742 $this->setHTMLTitle( wfMsg( 'errorpagetitle' ) );
01743 $this->setRobotPolicy( 'noindex,nofollow' );
01744 $this->setArticleRelated( false );
01745 $this->enableClientCache( false );
01746 $this->mRedirect = '';
01747 $this->mBodytext = '';
01748
01749 array_unshift( $params, 'parse' );
01750 array_unshift( $params, $msg );
01751 $this->addHTML( call_user_func_array( 'wfMsgExt', $params ) );
01752
01753 $this->returnToMain();
01754 }
01755
01762 public function showPermissionsErrorPage( $errors, $action = null ) {
01763 $this->mDebugtext .= 'Original title: ' .
01764 $this->getTitle()->getPrefixedText() . "\n";
01765 $this->setPageTitle( wfMsg( 'permissionserrors' ) );
01766 $this->setHTMLTitle( wfMsg( 'permissionserrors' ) );
01767 $this->setRobotPolicy( 'noindex,nofollow' );
01768 $this->setArticleRelated( false );
01769 $this->enableClientCache( false );
01770 $this->mRedirect = '';
01771 $this->mBodytext = '';
01772 $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) );
01773 }
01774
01781 public function versionRequired( $version ) {
01782 $this->setPageTitle( wfMsg( 'versionrequired', $version ) );
01783 $this->setHTMLTitle( wfMsg( 'versionrequired', $version ) );
01784 $this->setRobotPolicy( 'noindex,nofollow' );
01785 $this->setArticleRelated( false );
01786 $this->mBodytext = '';
01787
01788 $this->addWikiMsg( 'versionrequiredtext', $version );
01789 $this->returnToMain();
01790 }
01791
01797 public function permissionRequired( $permission ) {
01798 global $wgLang;
01799
01800 $this->setPageTitle( wfMsg( 'badaccess' ) );
01801 $this->setHTMLTitle( wfMsg( 'errorpagetitle' ) );
01802 $this->setRobotPolicy( 'noindex,nofollow' );
01803 $this->setArticleRelated( false );
01804 $this->mBodytext = '';
01805
01806 $groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
01807 User::getGroupsWithPermission( $permission ) );
01808 if( $groups ) {
01809 $this->addWikiMsg( 'badaccess-groups',
01810 $wgLang->commaList( $groups ),
01811 count( $groups) );
01812 } else {
01813 $this->addWikiMsg( 'badaccess-group0' );
01814 }
01815 $this->returnToMain();
01816 }
01817
01821 public function sysopRequired() {
01822 throw new MWException( "Call to deprecated OutputPage::sysopRequired() method\n" );
01823 }
01824
01828 public function developerRequired() {
01829 throw new MWException( "Call to deprecated OutputPage::developerRequired() method\n" );
01830 }
01831
01835 public function loginToUse() {
01836 global $wgUser, $wgContLang;
01837
01838 if( $wgUser->isLoggedIn() ) {
01839 $this->permissionRequired( 'read' );
01840 return;
01841 }
01842
01843 $skin = $wgUser->getSkin();
01844
01845 $this->setPageTitle( wfMsg( 'loginreqtitle' ) );
01846 $this->setHtmlTitle( wfMsg( 'errorpagetitle' ) );
01847 $this->setRobotPolicy( 'noindex,nofollow' );
01848 $this->setArticleFlag( false );
01849
01850 $loginTitle = SpecialPage::getTitleFor( 'Userlogin' );
01851 $loginLink = $skin->link(
01852 $loginTitle,
01853 wfMsgHtml( 'loginreqlink' ),
01854 array(),
01855 array( 'returnto' => $this->getTitle()->getPrefixedText() ),
01856 array( 'known', 'noclasses' )
01857 );
01858 $this->addHTML( wfMsgWikiHtml( 'loginreqpagetext', $loginLink ) );
01859 $this->addHTML( "\n<!--" . $this->getTitle()->getPrefixedUrl() . "-->" );
01860
01861 # Don't return to the main page if the user can't read it
01862 # otherwise we'll end up in a pointless loop
01863 $mainPage = Title::newMainPage();
01864 if( $mainPage->userCanRead() )
01865 $this->returnToMain( null, $mainPage );
01866 }
01867
01875 public function formatPermissionsErrorMessage( $errors, $action = null ) {
01876 if ($action == null) {
01877 $text = wfMsgNoTrans( 'permissionserrorstext', count($errors)). "\n\n";
01878 } else {
01879 global $wgLang;
01880 $action_desc = wfMsgNoTrans( "action-$action" );
01881 $text = wfMsgNoTrans( 'permissionserrorstext-withaction', count($errors), $action_desc ) . "\n\n";
01882 }
01883
01884 if (count( $errors ) > 1) {
01885 $text .= '<ul class="permissions-errors">' . "\n";
01886
01887 foreach( $errors as $error )
01888 {
01889 $text .= '<li>';
01890 $text .= call_user_func_array( 'wfMsgNoTrans', $error );
01891 $text .= "</li>\n";
01892 }
01893 $text .= '</ul>';
01894 } else {
01895 $text .= "<div class=\"permissions-errors\">\n" . call_user_func_array( 'wfMsgNoTrans', reset( $errors ) ) . "\n</div>";
01896 }
01897
01898 return $text;
01899 }
01900
01921 public function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
01922 global $wgUser;
01923 $skin = $wgUser->getSkin();
01924
01925 $this->setRobotPolicy( 'noindex,nofollow' );
01926 $this->setArticleRelated( false );
01927
01928
01929
01930 if ( $protected && empty($reasons) ) {
01931 $reasons[] = array( 'badaccess-group0' );
01932 }
01933
01934 if ( !empty($reasons) ) {
01935
01936 if( $source ) {
01937 $this->setPageTitle( wfMsg( 'viewsource' ) );
01938 $this->setSubtitle(
01939 wfMsg(
01940 'viewsourcefor',
01941 $skin->link(
01942 $this->getTitle(),
01943 null,
01944 array(),
01945 array(),
01946 array( 'known', 'noclasses' )
01947 )
01948 )
01949 );
01950 } else {
01951 $this->setPageTitle( wfMsg( 'badaccess' ) );
01952 }
01953 $this->addWikiText( $this->formatPermissionsErrorMessage( $reasons, $action ) );
01954 } else {
01955
01956 $this->setPageTitle( wfMsg( 'readonly' ) );
01957 $reason = wfReadOnlyReason();
01958 $this->wrapWikiMsg( '<div class="mw-readonly-error">\n$1</div>', array( 'readonlytext', $reason ) );
01959 }
01960
01961
01962 if( is_string( $source ) ) {
01963 $this->addWikiMsg( 'viewsourcetext' );
01964
01965 $params = array(
01966 'id' => 'wpTextbox1',
01967 'name' => 'wpTextbox1',
01968 'cols' => $wgUser->getOption( 'cols' ),
01969 'rows' => $wgUser->getOption( 'rows' ),
01970 'readonly' => 'readonly'
01971 );
01972 $this->addHTML( Html::element( 'textarea', $params, $source ) );
01973
01974
01975 $skin = $wgUser->getSkin();
01976 $article = new Article( $this->getTitle() );
01977 $this->addHTML( "<div class='templatesUsed'>
01978 {$skin->formatTemplates( $article->getUsedTemplates() )}
01979 </div>
01980 " );
01981 }
01982
01983 # If the title doesn't exist, it's fairly pointless to print a return
01984 # link to it. After all, you just tried editing it and couldn't, so
01985 # what's there to do there?
01986 if( $this->getTitle()->exists() ) {
01987 $this->returnToMain( null, $this->getTitle() );
01988 }
01989 }
01990
01992 public function errorpage( $title, $msg ) {
01993 wfDeprecated( __METHOD__ );
01994 throw new ErrorPageError( $title, $msg );
01995 }
01996
01998 public function databaseError( $fname, $sql, $error, $errno ) {
01999 throw new MWException( "OutputPage::databaseError is obsolete\n" );
02000 }
02001
02003 public function fatalError( $message ) {
02004 wfDeprecated( __METHOD__ );
02005 throw new FatalError( $message );
02006 }
02007
02009 public function unexpectedValueError( $name, $val ) {
02010 wfDeprecated( __METHOD__ );
02011 throw new FatalError( wfMsg( 'unexpected', $name, $val ) );
02012 }
02013
02015 public function fileCopyError( $old, $new ) {
02016 wfDeprecated( __METHOD__ );
02017 throw new FatalError( wfMsg( 'filecopyerror', $old, $new ) );
02018 }
02019
02021 public function fileRenameError( $old, $new ) {
02022 wfDeprecated( __METHOD__ );
02023 throw new FatalError( wfMsg( 'filerenameerror', $old, $new ) );
02024 }
02025
02027 public function fileDeleteError( $name ) {
02028 wfDeprecated( __METHOD__ );
02029 throw new FatalError( wfMsg( 'filedeleteerror', $name ) );
02030 }
02031
02033 public function fileNotFoundError( $name ) {
02034 wfDeprecated( __METHOD__ );
02035 throw new FatalError( wfMsg( 'filenotfound', $name ) );
02036 }
02037
02038 public function showFatalError( $message ) {
02039 $this->setPageTitle( wfMsg( "internalerror" ) );
02040 $this->setRobotPolicy( "noindex,nofollow" );
02041 $this->setArticleRelated( false );
02042 $this->enableClientCache( false );
02043 $this->mRedirect = '';
02044 $this->mBodytext = $message;
02045 }
02046
02047 public function showUnexpectedValueError( $name, $val ) {
02048 $this->showFatalError( wfMsg( 'unexpected', $name, $val ) );
02049 }
02050
02051 public function showFileCopyError( $old, $new ) {
02052 $this->showFatalError( wfMsg( 'filecopyerror', $old, $new ) );
02053 }
02054
02055 public function showFileRenameError( $old, $new ) {
02056 $this->showFatalError( wfMsg( 'filerenameerror', $old, $new ) );
02057 }
02058
02059 public function showFileDeleteError( $name ) {
02060 $this->showFatalError( wfMsg( 'filedeleteerror', $name ) );
02061 }
02062
02063 public function showFileNotFoundError( $name ) {
02064 $this->showFatalError( wfMsg( 'filenotfound', $name ) );
02065 }
02066
02073 public function addReturnTo( $title, $query = array() ) {
02074 global $wgUser;
02075 $this->addLink( array( 'rel' => 'next', 'href' => $title->getFullUrl() ) );
02076 $link = wfMsgHtml( 'returnto', $wgUser->getSkin()->link(
02077 $title, null, array(), $query ) );
02078 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
02079 }
02080
02089 public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
02090 global $wgRequest;
02091
02092 if ( $returnto == null ) {
02093 $returnto = $wgRequest->getText( 'returnto' );
02094 }
02095
02096 if ( $returntoquery == null ) {
02097 $returntoquery = $wgRequest->getText( 'returntoquery' );
02098 }
02099
02100 if ( $returnto === '' ) {
02101 $returnto = Title::newMainPage();
02102 }
02103
02104 if ( is_object( $returnto ) ) {
02105 $titleObj = $returnto;
02106 } else {
02107 $titleObj = Title::newFromText( $returnto );
02108 }
02109 if ( !is_object( $titleObj ) ) {
02110 $titleObj = Title::newMainPage();
02111 }
02112
02113 $this->addReturnTo( $titleObj, $returntoquery );
02114 }
02115
02121 public function headElement( Skin $sk, $includeStyle = true ) {
02122 global $wgDocType, $wgDTD, $wgContLanguageCode, $wgOutputEncoding, $wgMimeType;
02123 global $wgXhtmlDefaultNamespace, $wgXhtmlNamespaces, $wgHtml5Version;
02124 global $wgContLang, $wgUseTrackbacks, $wgStyleVersion, $wgHtml5, $wgWellFormedXml;
02125 global $wgUser, $wgRequest, $wgLang;
02126
02127 $this->addMeta( "http:Content-Type", "$wgMimeType; charset={$wgOutputEncoding}" );
02128 if ( $sk->commonPrintStylesheet() ) {
02129 $this->addStyle( 'common/wikiprintable.css', 'print' );
02130 }
02131 $sk->setupUserCss( $this );
02132
02133 $ret = '';
02134
02135 if( $wgMimeType == 'text/xml' || $wgMimeType == 'application/xhtml+xml' || $wgMimeType == 'application/xml' ) {
02136 $ret .= "<?xml version=\"1.0\" encoding=\"$wgOutputEncoding\" ?" . ">\n";
02137 }
02138
02139 if ( $this->getHTMLTitle() == '' ) {
02140 $this->setHTMLTitle( wfMsg( 'pagetitle', $this->getPageTitle() ));
02141 }
02142
02143 $dir = $wgContLang->getDir();
02144
02145 if ( $wgHtml5 ) {
02146 if ( $wgWellFormedXml ) {
02147 # Unknown elements and attributes are okay in XML, but unknown
02148 # named entities are well-formedness errors and will break XML
02149 # parsers. Thus we need a doctype that gives us appropriate
02150 # entity definitions. The HTML5 spec permits four legacy
02151 # doctypes as obsolete but conforming, so let's pick one of
02152 # those, although it makes our pages look like XHTML1 Strict.
02153 # Isn't compatibility great?
02154 $ret .= "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
02155 } else {
02156 # Much saner.
02157 $ret .= "<!doctype html>\n";
02158 }
02159 $ret .= "<html lang=\"$wgContLanguageCode\" dir=\"$dir\"";
02160 if ( $wgHtml5Version ) $ret .= " version=\"$wgHtml5Version\"";
02161 $ret .= ">\n";
02162 } else {
02163 $ret .= "<!DOCTYPE html PUBLIC \"$wgDocType\" \"$wgDTD\">\n";
02164 $ret .= "<html xmlns=\"{$wgXhtmlDefaultNamespace}\" ";
02165 foreach($wgXhtmlNamespaces as $tag => $ns) {
02166 $ret .= "xmlns:{$tag}=\"{$ns}\" ";
02167 }
02168 $ret .= "lang=\"$wgContLanguageCode\" dir=\"$dir\">\n";
02169 }
02170
02171 $ret .= "<head>\n";
02172 $ret .= "<title>" . htmlspecialchars( $this->getHTMLTitle() ) . "</title>\n";
02173 $ret .= implode( "\n", array(
02174 $this->getHeadLinks(),
02175 $this->buildCssLinks(),
02176 $this->getHeadScripts( $sk ),
02177 $this->getHeadItems(),
02178 ));
02179 if( $sk->usercss ){
02180 $ret .= Html::inlineStyle( $sk->usercss );
02181 }
02182
02183 if ($wgUseTrackbacks && $this->isArticleRelated())
02184 $ret .= $this->getTitle()->trackbackRDF();
02185
02186 $ret .= "</head>\n";
02187
02188 $bodyAttrs = array();
02189
02190 # Crazy edit-on-double-click stuff
02191 $action = $wgRequest->getVal( 'action', 'view' );
02192
02193 if ( $this->getTitle()->getNamespace() != NS_SPECIAL
02194 && !in_array( $action, array( 'edit', 'submit' ) )
02195 && $wgUser->getOption( 'editondblclick' ) ) {
02196 $bodyAttrs['ondblclick'] = "document.location = '" . Xml::escapeJsString( $this->getTitle()->getEditURL() ) . "'";
02197 }
02198
02199 # Class bloat
02200 $bodyAttrs['class'] = "mediawiki $dir";
02201
02202 if ( $wgLang->capitalizeAllNouns() ) {
02203 # A <body> class is probably not the best way to do this . . .
02204 $bodyAttrs['class'] .= ' capitalize-all-nouns';
02205 }
02206 $bodyAttrs['class'] .= ' ns-' . $this->getTitle()->getNamespace();
02207 if ( $this->getTitle()->getNamespace() == NS_SPECIAL ) {
02208 $bodyAttrs['class'] .= ' ns-special';
02209 } elseif ( $this->getTitle()->isTalkPage() ) {
02210 $bodyAttrs['class'] .= ' ns-talk';
02211 } else {
02212 $bodyAttrs['class'] .= ' ns-subject';
02213 }
02214 $bodyAttrs['class'] .= ' ' . Sanitizer::escapeClass( 'page-' . $this->getTitle()->getPrefixedText() );
02215 $bodyAttrs['class'] .= ' skin-' . Sanitizer::escapeClass( $wgUser->getSkin()->getSkinName() );
02216
02217 $ret .= Html::openElement( 'body', $bodyAttrs ) . "\n";
02218
02219 return $ret;
02220 }
02221
02229 function getHeadScripts( Skin $sk ) {
02230 global $wgUser, $wgRequest, $wgJsMimeType, $wgUseSiteJs;
02231 global $wgStylePath, $wgStyleVersion;
02232
02233 $scripts = Skin::makeGlobalVariablesScript( $sk->getSkinName() );
02234 $scripts .= Html::linkedScript( "{$wgStylePath}/common/wikibits.js?$wgStyleVersion" );
02235
02236
02237 if( $wgUseSiteJs ) {
02238 $jsCache = $wgUser->isLoggedIn() ? '&smaxage=0' : '';
02239 $this->addScriptFile( Skin::makeUrl( '-',
02240 "action=raw$jsCache&gen=js&useskin=" .
02241 urlencode( $sk->getSkinName() )
02242 )
02243 );
02244 }
02245
02246
02247 if( $this->isUserJsAllowed() && $wgUser->isLoggedIn() ) {
02248 $action = $wgRequest->getVal( 'action', 'view' );
02249 if( $this->mTitle && $this->mTitle->isJsSubpage() and $sk->userCanPreview( $action ) ) {
02250 # XXX: additional security check/prompt?
02251 $this->addInlineScript( $wgRequest->getText( 'wpTextbox1' ) );
02252 } else {
02253 $userpage = $wgUser->getUserPage();
02254 $scriptpage = Title::makeTitleSafe(
02255 NS_USER,
02256 $userpage->getDBkey() . '/' . $sk->getSkinName() . '.js'
02257 );
02258 if ( $scriptpage && $scriptpage->exists() ) {
02259 $userjs = Skin::makeUrl( $scriptpage->getPrefixedText(), 'action=raw&ctype=' . $wgJsMimeType );
02260 $this->addScriptFile( $userjs );
02261 }
02262 }
02263 }
02264
02265 $scripts .= "\n" . $this->mScripts;
02266 return $scripts;
02267 }
02268
02272 protected function addDefaultMeta() {
02273 global $wgVersion, $wgHtml5;
02274
02275 static $called = false;
02276 if ( $called ) {
02277 # Don't run this twice
02278 return;
02279 }
02280 $called = true;
02281
02282 if ( !$wgHtml5 ) {
02283 $this->addMeta( 'http:Content-Style-Type', 'text/css' );
02284 }
02285 $this->addMeta( 'generator', "MediaWiki $wgVersion" );
02286
02287 $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
02288 if( $p !== 'index,follow' ) {
02289
02290
02291 $this->addMeta( 'robots', $p );
02292 }
02293
02294 if ( count( $this->mKeywords ) > 0 ) {
02295 $strip = array(
02296 "/<.*?" . ">/" => '',
02297 "/_/" => ' '
02298 );
02299 $this->addMeta( 'keywords', preg_replace(array_keys($strip), array_values($strip),implode( ",", $this->mKeywords ) ) );
02300 }
02301 }
02302
02306 public function getHeadLinks() {
02307 global $wgRequest, $wgFeed;
02308
02309
02310 $this->addDefaultMeta();
02311
02312 $tags = array();
02313
02314 foreach ( $this->mMetatags as $tag ) {
02315 if ( 0 == strcasecmp( 'http:', substr( $tag[0], 0, 5 ) ) ) {
02316 $a = 'http-equiv';
02317 $tag[0] = substr( $tag[0], 5 );
02318 } else {
02319 $a = 'name';
02320 }
02321 $tags[] = Html::element( 'meta',
02322 array(
02323 $a => $tag[0],
02324 'content' => $tag[1] ) );
02325 }
02326 foreach ( $this->mLinktags as $tag ) {
02327 $tags[] = Html::element( 'link', $tag );
02328 }
02329
02330 if( $wgFeed ) {
02331 foreach( $this->getSyndicationLinks() as $format => $link ) {
02332 # Use the page name for the title (accessed through $wgTitle since
02333 # there's no other way). In principle, this could lead to issues
02334 # with having the same name for different feeds corresponding to
02335 # the same page, but we can't avoid that at this low a level.
02336
02337 $tags[] = $this->feedLink(
02338 $format,
02339 $link,
02340 # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
02341 wfMsg( "page-{$format}-feed", $this->getTitle()->getPrefixedText() ) );
02342 }
02343
02344 # Recent changes feed should appear on every page (except recentchanges,
02345 # that would be redundant). Put it after the per-page feed to avoid
02346 # changing existing behavior. It's still available, probably via a
02347 # menu in your browser. Some sites might have a different feed they'd
02348 # like to promote instead of the RC feed (maybe like a "Recent New Articles"
02349 # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
02350 # If so, use it instead.
02351
02352 global $wgOverrideSiteFeed, $wgSitename, $wgAdvertisedFeedTypes;
02353 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
02354
02355 if ( $wgOverrideSiteFeed ) {
02356 foreach ( $wgOverrideSiteFeed as $type => $feedUrl ) {
02357 $tags[] = $this->feedLink (
02358 $type,
02359 htmlspecialchars( $feedUrl ),
02360 wfMsg( "site-{$type}-feed", $wgSitename ) );
02361 }
02362 } elseif ( $this->getTitle()->getPrefixedText() != $rctitle->getPrefixedText() ) {
02363 foreach ( $wgAdvertisedFeedTypes as $format ) {
02364 $tags[] = $this->feedLink(
02365 $format,
02366 $rctitle->getLocalURL( "feed={$format}" ),
02367 wfMsg( "site-{$format}-feed", $wgSitename ) ); # For grep: 'site-rss-feed', 'site-atom-feed'.
02368 }
02369 }
02370 }
02371
02372 return implode( "\n", $tags );
02373 }
02374
02383 private function feedLink( $type, $url, $text ) {
02384 return Html::element( 'link', array(
02385 'rel' => 'alternate',
02386 'type' => "application/$type+xml",
02387 'title' => $text,
02388 'href' => $url ) );
02389 }
02390
02400 public function addStyle( $style, $media='', $condition='', $dir='' ) {
02401 $options = array();
02402
02403
02404 if( $media )
02405 $options['media'] = $media;
02406 if( $condition )
02407 $options['condition'] = $condition;
02408 if( $dir )
02409 $options['dir'] = $dir;
02410 $this->styles[$style] = $options;
02411 }
02412
02417 public function addInlineStyle( $style_css ){
02418 $this->mScripts .= Html::inlineStyle( $style_css );
02419 }
02420
02425 public function buildCssLinks() {
02426 $links = array();
02427 foreach( $this->styles as $file => $options ) {
02428 $link = $this->styleLink( $file, $options );
02429 if( $link )
02430 $links[] = $link;
02431 }
02432
02433 return implode( "\n", $links );
02434 }
02435
02444 protected function styleLink( $style, $options ) {
02445 global $wgRequest;
02446
02447 if( isset( $options['dir'] ) ) {
02448 global $wgContLang;
02449 $siteDir = $wgContLang->getDir();
02450 if( $siteDir != $options['dir'] )
02451 return '';
02452 }
02453
02454 if( isset( $options['media'] ) ) {
02455 $media = $this->transformCssMedia( $options['media'] );
02456 if( is_null( $media ) ) {
02457 return '';
02458 }
02459 } else {
02460 $media = 'all';
02461 }
02462
02463 if( substr( $style, 0, 1 ) == '/' ||
02464 substr( $style, 0, 5 ) == 'http:' ||
02465 substr( $style, 0, 6 ) == 'https:' ) {
02466 $url = $style;
02467 } else {
02468 global $wgStylePath, $wgStyleVersion;
02469 $url = $wgStylePath . '/' . $style . '?' . $wgStyleVersion;
02470 }
02471
02472 $link = Html::linkedStyle( $url, $media );
02473
02474 if( isset( $options['condition'] ) ) {
02475 $condition = htmlspecialchars( $options['condition'] );
02476 $link = "<!--[if $condition]>$link<![endif]-->";
02477 }
02478 return $link;
02479 }
02480
02487 function transformCssMedia( $media ) {
02488 global $wgRequest, $wgHandheldForIPhone;
02489
02490
02491 $switches = array(
02492 'printable' => 'print',
02493 'handheld' => 'handheld',
02494 );
02495 foreach( $switches as $switch => $targetMedia ) {
02496 if( $wgRequest->getBool( $switch ) ) {
02497 if( $media == $targetMedia ) {
02498 $media = '';
02499 } elseif( $media == 'screen' ) {
02500 return null;
02501 }
02502 }
02503 }
02504
02505
02506 if( $wgHandheldForIPhone ) {
02507 $mediaAliases = array(
02508 'screen' => 'screen and (min-device-width: 481px)',
02509 'handheld' => 'handheld, only screen and (max-device-width: 480px)',
02510 );
02511
02512 if( isset( $mediaAliases[$media] ) ) {
02513 $media = $mediaAliases[$media];
02514 }
02515 }
02516
02517 return $media;
02518 }
02519
02524 public function rateLimited() {
02525 $this->setPageTitle(wfMsg('actionthrottled'));
02526 $this->setRobotPolicy( 'noindex,follow' );
02527 $this->setArticleRelated( false );
02528 $this->enableClientCache( false );
02529 $this->mRedirect = '';
02530 $this->clearHTML();
02531 $this->setStatusCode(503);
02532 $this->addWikiMsg( 'actionthrottledtext' );
02533
02534 $this->returnToMain( null, $this->getTitle() );
02535 }
02536
02546 public function showLagWarning( $lag ) {
02547 global $wgSlaveLagWarning, $wgSlaveLagCritical, $wgLang;
02548 if( $lag >= $wgSlaveLagWarning ) {
02549 $message = $lag < $wgSlaveLagCritical
02550 ? 'lag-warn-normal'
02551 : 'lag-warn-high';
02552 $wrap = Html::rawElement( 'div', array( 'class' => "mw-{$message}" ), "\n$1\n" );
02553 $this->wrapWikiMsg( "$wrap\n", array( $message, $wgLang->formatNum( $lag ) ) );
02554 }
02555 }
02556
02563 public function addWikiMsg( ) {
02564 $args = func_get_args();
02565 $name = array_shift( $args );
02566 $this->addWikiMsgArray( $name, $args );
02567 }
02568
02576 public function addWikiMsgArray( $name, $args, $options = array() ) {
02577 $options[] = 'parse';
02578 $text = wfMsgExt( $name, $options, $args );
02579 $this->addHTML( $text );
02580 }
02581
02606 public function wrapWikiMsg( $wrap ) {
02607 $msgSpecs = func_get_args();
02608 array_shift( $msgSpecs );
02609 $msgSpecs = array_values( $msgSpecs );
02610 $s = $wrap;
02611 foreach ( $msgSpecs as $n => $spec ) {
02612 $options = array();
02613 if ( is_array( $spec ) ) {
02614 $args = $spec;
02615 $name = array_shift( $args );
02616 if ( isset( $args['options'] ) ) {
02617 $options = $args['options'];
02618 unset( $args['options'] );
02619 }
02620 } else {
02621 $args = array();
02622 $name = $spec;
02623 }
02624 $s = str_replace( '$' . ( $n + 1 ), wfMsgExt( $name, $options, $args ), $s );
02625 }
02626 $this->addHTML( $this->parse( $s, true, true ) );
02627 }
02628
02636 public function includeJQuery( $modules = array() ) {
02637 global $wgStylePath, $wgStyleVersion, $wgJsMimeType;
02638
02639 $supportedModules = array( );
02640 $unsupported = array_diff( $modules, $supportedModules );
02641
02642 $params = array(
02643 'type' => $wgJsMimeType,
02644 'src' => "$wgStylePath/common/jquery.min.js?$wgStyleVersion",
02645 );
02646 if ( !$this->mJQueryDone ) {
02647 $this->mJQueryDone = true;
02648 $this->mScripts = Html::element( 'script', $params ) . "\n" . $this->mScripts;
02649 }
02650 return $unsupported;
02651 }
02652
02653 }