OutputPage.php 56 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883
  1. <?php
  2. if ( ! defined( 'MEDIAWIKI' ) )
  3. die( 1 );
  4. /**
  5. * @todo document
  6. */
  7. class OutputPage {
  8. var $mMetatags = array(), $mKeywords = array(), $mLinktags = array();
  9. var $mExtStyles = array();
  10. var $mPagetitle = '', $mBodytext = '', $mDebugtext = '';
  11. var $mHTMLtitle = '', $mIsarticle = true, $mPrintable = false;
  12. var $mSubtitle = '', $mRedirect = '', $mStatusCode;
  13. var $mLastModified = '', $mETag = false;
  14. var $mCategoryLinks = array(), $mLanguageLinks = array();
  15. var $mScripts = '', $mLinkColours, $mPageLinkTitle = '', $mHeadItems = array();
  16. var $mTemplateIds = array();
  17. var $mAllowUserJs;
  18. var $mSuppressQuickbar = false;
  19. var $mOnloadHandler = '';
  20. var $mDoNothing = false;
  21. var $mContainsOldMagic = 0, $mContainsNewMagic = 0;
  22. var $mIsArticleRelated = true;
  23. protected $mParserOptions = null; // lazy initialised, use parserOptions()
  24. var $mShowFeedLinks = false;
  25. var $mFeedLinksAppendQuery = false;
  26. var $mEnableClientCache = true;
  27. var $mArticleBodyOnly = false;
  28. var $mNewSectionLink = false;
  29. var $mHideNewSectionLink = false;
  30. var $mNoGallery = false;
  31. var $mPageTitleActionText = '';
  32. var $mParseWarnings = array();
  33. var $mSquidMaxage = 0;
  34. var $mRevisionId = null;
  35. /**
  36. * An array of stylesheet filenames (relative from skins path), with options
  37. * for CSS media, IE conditions, and RTL/LTR direction.
  38. * For internal use; add settings in the skin via $this->addStyle()
  39. */
  40. var $styles = array();
  41. private $mIndexPolicy = 'index';
  42. private $mFollowPolicy = 'follow';
  43. /**
  44. * Constructor
  45. * Initialise private variables
  46. */
  47. function __construct() {
  48. global $wgAllowUserJs;
  49. $this->mAllowUserJs = $wgAllowUserJs;
  50. }
  51. public function redirect( $url, $responsecode = '302' ) {
  52. # Strip newlines as a paranoia check for header injection in PHP<5.1.2
  53. $this->mRedirect = str_replace( "\n", '', $url );
  54. $this->mRedirectCode = $responsecode;
  55. }
  56. public function getRedirect() {
  57. return $this->mRedirect;
  58. }
  59. /**
  60. * Set the HTTP status code to send with the output.
  61. *
  62. * @param int $statusCode
  63. * @return nothing
  64. */
  65. function setStatusCode( $statusCode ) { $this->mStatusCode = $statusCode; }
  66. /**
  67. * Add a new <meta> tag
  68. * To add an http-equiv meta tag, precede the name with "http:"
  69. *
  70. * @param $name tag name
  71. * @param $val tag value
  72. */
  73. function addMeta( $name, $val ) {
  74. array_push( $this->mMetatags, array( $name, $val ) );
  75. }
  76. function addKeyword( $text ) { array_push( $this->mKeywords, $text ); }
  77. function addScript( $script ) { $this->mScripts .= "\t\t".$script; }
  78. function addExtensionStyle( $url ) {
  79. $linkarr = array( 'rel' => 'stylesheet', 'href' => $url, 'type' => 'text/css' );
  80. array_push( $this->mExtStyles, $linkarr );
  81. }
  82. /**
  83. * Add a JavaScript file out of skins/common, or a given relative path.
  84. * @param string $file filename in skins/common or complete on-server path (/foo/bar.js)
  85. */
  86. function addScriptFile( $file ) {
  87. global $wgStylePath, $wgStyleVersion, $wgJsMimeType;
  88. if( substr( $file, 0, 1 ) == '/' ) {
  89. $path = $file;
  90. } else {
  91. $path = "{$wgStylePath}/common/{$file}";
  92. }
  93. $this->addScript( "<script type=\"{$wgJsMimeType}\" src=\"$path?$wgStyleVersion\"></script>\n" );
  94. }
  95. /**
  96. * Add a self-contained script tag with the given contents
  97. * @param string $script JavaScript text, no <script> tags
  98. */
  99. function addInlineScript( $script ) {
  100. global $wgJsMimeType;
  101. $this->mScripts .= "<script type=\"$wgJsMimeType\">/*<![CDATA[*/\n$script\n/*]]>*/</script>";
  102. }
  103. function getScript() {
  104. return $this->mScripts . $this->getHeadItems();
  105. }
  106. function getHeadItems() {
  107. $s = '';
  108. foreach ( $this->mHeadItems as $item ) {
  109. $s .= $item;
  110. }
  111. return $s;
  112. }
  113. function addHeadItem( $name, $value ) {
  114. $this->mHeadItems[$name] = $value;
  115. }
  116. function hasHeadItem( $name ) {
  117. return isset( $this->mHeadItems[$name] );
  118. }
  119. function setETag($tag) { $this->mETag = $tag; }
  120. function setArticleBodyOnly($only) { $this->mArticleBodyOnly = $only; }
  121. function getArticleBodyOnly($only) { return $this->mArticleBodyOnly; }
  122. function addLink( $linkarr ) {
  123. # $linkarr should be an associative array of attributes. We'll escape on output.
  124. array_push( $this->mLinktags, $linkarr );
  125. }
  126. # Get all links added by extensions
  127. function getExtStyle() {
  128. return $this->mExtStyles;
  129. }
  130. function addMetadataLink( $linkarr ) {
  131. # note: buggy CC software only reads first "meta" link
  132. static $haveMeta = false;
  133. $linkarr['rel'] = ($haveMeta) ? 'alternate meta' : 'meta';
  134. $this->addLink( $linkarr );
  135. $haveMeta = true;
  136. }
  137. /**
  138. * checkLastModified tells the client to use the client-cached page if
  139. * possible. If sucessful, the OutputPage is disabled so that
  140. * any future call to OutputPage->output() have no effect.
  141. *
  142. * Side effect: sets mLastModified for Last-Modified header
  143. *
  144. * @return bool True iff cache-ok headers was sent.
  145. */
  146. function checkLastModified( $timestamp ) {
  147. global $wgCachePages, $wgCacheEpoch, $wgUser, $wgRequest;
  148. if ( !$timestamp || $timestamp == '19700101000000' ) {
  149. wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
  150. return false;
  151. }
  152. if( !$wgCachePages ) {
  153. wfDebug( __METHOD__ . ": CACHE DISABLED\n", false );
  154. return false;
  155. }
  156. if( $wgUser->getOption( 'nocache' ) ) {
  157. wfDebug( __METHOD__ . ": USER DISABLED CACHE\n", false );
  158. return false;
  159. }
  160. $timestamp = wfTimestamp( TS_MW, $timestamp );
  161. $modifiedTimes = array(
  162. 'page' => $timestamp,
  163. 'user' => $wgUser->getTouched(),
  164. 'epoch' => $wgCacheEpoch
  165. );
  166. wfRunHooks( 'OutputPageCheckLastModified', array( &$modifiedTimes ) );
  167. $maxModified = max( $modifiedTimes );
  168. $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
  169. if( empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
  170. wfDebug( __METHOD__ . ": client did not send If-Modified-Since header\n", false );
  171. return false;
  172. }
  173. # Make debug info
  174. $info = '';
  175. foreach ( $modifiedTimes as $name => $value ) {
  176. if ( $info !== '' ) {
  177. $info .= ', ';
  178. }
  179. $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
  180. }
  181. # IE sends sizes after the date like this:
  182. # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
  183. # this breaks strtotime().
  184. $clientHeader = preg_replace( '/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"] );
  185. wfSuppressWarnings(); // E_STRICT system time bitching
  186. $clientHeaderTime = strtotime( $clientHeader );
  187. wfRestoreWarnings();
  188. if ( !$clientHeaderTime ) {
  189. wfDebug( __METHOD__ . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
  190. return false;
  191. }
  192. $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
  193. wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
  194. wfTimestamp( TS_ISO_8601, $clientHeaderTime ) . "\n", false );
  195. wfDebug( __METHOD__ . ": effective Last-Modified: " .
  196. wfTimestamp( TS_ISO_8601, $maxModified ) . "\n", false );
  197. if( $clientHeaderTime < $maxModified ) {
  198. wfDebug( __METHOD__ . ": STALE, $info\n", false );
  199. return false;
  200. }
  201. # Not modified
  202. # Give a 304 response code and disable body output
  203. wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", false );
  204. ini_set('zlib.output_compression', 0);
  205. $wgRequest->response()->header( "HTTP/1.1 304 Not Modified" );
  206. $this->sendCacheControl();
  207. $this->disable();
  208. // Don't output a compressed blob when using ob_gzhandler;
  209. // it's technically against HTTP spec and seems to confuse
  210. // Firefox when the response gets split over two packets.
  211. wfClearOutputBuffers();
  212. return true;
  213. }
  214. function setPageTitleActionText( $text ) {
  215. $this->mPageTitleActionText = $text;
  216. }
  217. function getPageTitleActionText () {
  218. if ( isset( $this->mPageTitleActionText ) ) {
  219. return $this->mPageTitleActionText;
  220. }
  221. }
  222. /**
  223. * Set the robot policy for the page: <http://www.robotstxt.org/meta.html>
  224. *
  225. * @param $policy string The literal string to output as the contents of
  226. * the meta tag. Will be parsed according to the spec and output in
  227. * standardized form.
  228. * @return null
  229. */
  230. public function setRobotPolicy( $policy ) {
  231. $policy = explode( ',', $policy );
  232. $policy = array_map( 'trim', $policy );
  233. # The default policy is follow, so if nothing is said explicitly, we
  234. # do that.
  235. if( in_array( 'nofollow', $policy ) ) {
  236. $this->mFollowPolicy = 'nofollow';
  237. } else {
  238. $this->mFollowPolicy = 'follow';
  239. }
  240. if( in_array( 'noindex', $policy ) ) {
  241. $this->mIndexPolicy = 'noindex';
  242. } else {
  243. $this->mIndexPolicy = 'index';
  244. }
  245. }
  246. /**
  247. * Set the index policy for the page, but leave the follow policy un-
  248. * touched.
  249. *
  250. * @param $policy string Either 'index' or 'noindex'.
  251. * @return null
  252. */
  253. public function setIndexPolicy( $policy ) {
  254. $policy = trim( $policy );
  255. if( in_array( $policy, array( 'index', 'noindex' ) ) ) {
  256. $this->mIndexPolicy = $policy;
  257. }
  258. }
  259. /**
  260. * Set the follow policy for the page, but leave the index policy un-
  261. * touched.
  262. *
  263. * @param $policy string Either 'follow' or 'nofollow'.
  264. * @return null
  265. */
  266. public function setFollowPolicy( $policy ) {
  267. $policy = trim( $policy );
  268. if( in_array( $policy, array( 'follow', 'nofollow' ) ) ) {
  269. $this->mFollowPolicy = $policy;
  270. }
  271. }
  272. public function setHTMLTitle( $name ) { $this->mHTMLtitle = $name; }
  273. public function setPageTitle( $name ) {
  274. global $wgContLang;
  275. $name = $wgContLang->convert( $name, true );
  276. $this->mPagetitle = $name;
  277. $taction = $this->getPageTitleActionText();
  278. if( !empty( $taction ) ) {
  279. $name .= ' - '.$taction;
  280. }
  281. $this->setHTMLTitle( wfMsg( 'pagetitle', $name ) );
  282. }
  283. public function getHTMLTitle() { return $this->mHTMLtitle; }
  284. public function getPageTitle() { return $this->mPagetitle; }
  285. public function setSubtitle( $str ) { $this->mSubtitle = /*$this->parse(*/$str/*)*/; } // @bug 2514
  286. public function appendSubtitle( $str ) { $this->mSubtitle .= /*$this->parse(*/$str/*)*/; } // @bug 2514
  287. public function getSubtitle() { return $this->mSubtitle; }
  288. public function isArticle() { return $this->mIsarticle; }
  289. public function setPrintable() { $this->mPrintable = true; }
  290. public function isPrintable() { return $this->mPrintable; }
  291. public function setSyndicated( $show = true ) { $this->mShowFeedLinks = $show; }
  292. public function isSyndicated() { return $this->mShowFeedLinks; }
  293. public function setFeedAppendQuery( $val ) { $this->mFeedLinksAppendQuery = $val; }
  294. public function getFeedAppendQuery() { return $this->mFeedLinksAppendQuery; }
  295. public function setOnloadHandler( $js ) { $this->mOnloadHandler = $js; }
  296. public function getOnloadHandler() { return $this->mOnloadHandler; }
  297. public function disable() { $this->mDoNothing = true; }
  298. public function isDisabled() { return $this->mDoNothing; }
  299. public function setArticleRelated( $v ) {
  300. $this->mIsArticleRelated = $v;
  301. if ( !$v ) {
  302. $this->mIsarticle = false;
  303. }
  304. }
  305. public function setArticleFlag( $v ) {
  306. $this->mIsarticle = $v;
  307. if ( $v ) {
  308. $this->mIsArticleRelated = $v;
  309. }
  310. }
  311. public function isArticleRelated() { return $this->mIsArticleRelated; }
  312. public function getLanguageLinks() { return $this->mLanguageLinks; }
  313. public function addLanguageLinks($newLinkArray) {
  314. $this->mLanguageLinks += $newLinkArray;
  315. }
  316. public function setLanguageLinks($newLinkArray) {
  317. $this->mLanguageLinks = $newLinkArray;
  318. }
  319. public function getCategoryLinks() {
  320. return $this->mCategoryLinks;
  321. }
  322. /**
  323. * Add an array of categories, with names in the keys
  324. */
  325. public function addCategoryLinks( $categories ) {
  326. global $wgUser, $wgContLang;
  327. if ( !is_array( $categories ) || count( $categories ) == 0 ) {
  328. return;
  329. }
  330. # Add the links to a LinkBatch
  331. $arr = array( NS_CATEGORY => $categories );
  332. $lb = new LinkBatch;
  333. $lb->setArray( $arr );
  334. # Fetch existence plus the hiddencat property
  335. $dbr = wfGetDB( DB_SLAVE );
  336. $pageTable = $dbr->tableName( 'page' );
  337. $where = $lb->constructSet( 'page', $dbr );
  338. $propsTable = $dbr->tableName( 'page_props' );
  339. $sql = "SELECT page_id, page_namespace, page_title, page_len, page_is_redirect, pp_value
  340. FROM $pageTable LEFT JOIN $propsTable ON pp_propname='hiddencat' AND pp_page=page_id WHERE $where";
  341. $res = $dbr->query( $sql, __METHOD__ );
  342. # Add the results to the link cache
  343. $lb->addResultToCache( LinkCache::singleton(), $res );
  344. # Set all the values to 'normal'. This can be done with array_fill_keys in PHP 5.2.0+
  345. $categories = array_combine( array_keys( $categories ),
  346. array_fill( 0, count( $categories ), 'normal' ) );
  347. # Mark hidden categories
  348. foreach ( $res as $row ) {
  349. if ( isset( $row->pp_value ) ) {
  350. $categories[$row->page_title] = 'hidden';
  351. }
  352. }
  353. # Add the remaining categories to the skin
  354. if ( wfRunHooks( 'OutputPageMakeCategoryLinks', array( &$this, $categories, &$this->mCategoryLinks ) ) ) {
  355. $sk = $wgUser->getSkin();
  356. foreach ( $categories as $category => $type ) {
  357. $origcategory = $category;
  358. $title = Title::makeTitleSafe( NS_CATEGORY, $category );
  359. $wgContLang->findVariantLink( $category, $title, true );
  360. if ( $category != $origcategory )
  361. if ( array_key_exists( $category, $categories ) )
  362. continue;
  363. $text = $wgContLang->convertHtml( $title->getText() );
  364. $this->mCategoryLinks[$type][] = $sk->makeLinkObj( $title, $text );
  365. }
  366. }
  367. }
  368. public function setCategoryLinks($categories) {
  369. $this->mCategoryLinks = array();
  370. $this->addCategoryLinks($categories);
  371. }
  372. public function suppressQuickbar() { $this->mSuppressQuickbar = true; }
  373. public function isQuickbarSuppressed() { return $this->mSuppressQuickbar; }
  374. public function disallowUserJs() { $this->mAllowUserJs = false; }
  375. public function isUserJsAllowed() { return $this->mAllowUserJs; }
  376. public function prependHTML( $text ) { $this->mBodytext = $text . $this->mBodytext; }
  377. public function addHTML( $text ) { $this->mBodytext .= $text; }
  378. public function clearHTML() { $this->mBodytext = ''; }
  379. public function getHTML() { return $this->mBodytext; }
  380. public function debug( $text ) { $this->mDebugtext .= $text; }
  381. /* @deprecated */
  382. public function setParserOptions( $options ) {
  383. wfDeprecated( __METHOD__ );
  384. return $this->parserOptions( $options );
  385. }
  386. public function parserOptions( $options = null ) {
  387. if ( !$this->mParserOptions ) {
  388. $this->mParserOptions = new ParserOptions;
  389. }
  390. return wfSetVar( $this->mParserOptions, $options );
  391. }
  392. /**
  393. * Set the revision ID which will be seen by the wiki text parser
  394. * for things such as embedded {{REVISIONID}} variable use.
  395. * @param mixed $revid an integer, or NULL
  396. * @return mixed previous value
  397. */
  398. public function setRevisionId( $revid ) {
  399. $val = is_null( $revid ) ? null : intval( $revid );
  400. return wfSetVar( $this->mRevisionId, $val );
  401. }
  402. public function getRevisionId() {
  403. return $this->mRevisionId;
  404. }
  405. /**
  406. * Convert wikitext to HTML and add it to the buffer
  407. * Default assumes that the current page title will
  408. * be used.
  409. *
  410. * @param string $text
  411. * @param bool $linestart
  412. */
  413. public function addWikiText( $text, $linestart = true ) {
  414. global $wgTitle;
  415. $this->addWikiTextTitle($text, $wgTitle, $linestart);
  416. }
  417. public function addWikiTextWithTitle($text, &$title, $linestart = true) {
  418. $this->addWikiTextTitle($text, $title, $linestart);
  419. }
  420. function addWikiTextTitleTidy($text, &$title, $linestart = true) {
  421. $this->addWikiTextTitle( $text, $title, $linestart, true );
  422. }
  423. public function addWikiTextTitle($text, &$title, $linestart, $tidy = false) {
  424. global $wgParser;
  425. wfProfileIn( __METHOD__ );
  426. wfIncrStats( 'pcache_not_possible' );
  427. $popts = $this->parserOptions();
  428. $oldTidy = $popts->setTidy( $tidy );
  429. $parserOutput = $wgParser->parse( $text, $title, $popts,
  430. $linestart, true, $this->mRevisionId );
  431. $popts->setTidy( $oldTidy );
  432. $this->addParserOutput( $parserOutput );
  433. wfProfileOut( __METHOD__ );
  434. }
  435. /**
  436. * @todo document
  437. * @param ParserOutput object &$parserOutput
  438. */
  439. public function addParserOutputNoText( &$parserOutput ) {
  440. global $wgTitle, $wgExemptFromUserRobotsControl, $wgContentNamespaces;
  441. $this->mLanguageLinks += $parserOutput->getLanguageLinks();
  442. $this->addCategoryLinks( $parserOutput->getCategories() );
  443. $this->mNewSectionLink = $parserOutput->getNewSection();
  444. $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
  445. if( is_null( $wgExemptFromUserRobotsControl ) ) {
  446. $bannedNamespaces = $wgContentNamespaces;
  447. } else {
  448. $bannedNamespaces = $wgExemptFromUserRobotsControl;
  449. }
  450. if( !in_array( $wgTitle->getNamespace(), $bannedNamespaces ) ) {
  451. # FIXME (bug 14900): This overrides $wgArticleRobotPolicies, and it
  452. # shouldn't
  453. $this->setIndexPolicy( $parserOutput->getIndexPolicy() );
  454. }
  455. $this->addKeywords( $parserOutput );
  456. $this->mParseWarnings = $parserOutput->getWarnings();
  457. if ( $parserOutput->getCacheTime() == -1 ) {
  458. $this->enableClientCache( false );
  459. }
  460. $this->mNoGallery = $parserOutput->getNoGallery();
  461. $this->mHeadItems = array_merge( $this->mHeadItems, (array)$parserOutput->mHeadItems );
  462. // Versioning...
  463. foreach ( (array)$parserOutput->mTemplateIds as $ns => $dbks ) {
  464. if ( isset( $this->mTemplateIds[$ns] ) ) {
  465. $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
  466. } else {
  467. $this->mTemplateIds[$ns] = $dbks;
  468. }
  469. }
  470. // Page title
  471. if( ( $dt = $parserOutput->getDisplayTitle() ) !== false )
  472. $this->setPageTitle( $dt );
  473. else if ( ( $title = $parserOutput->getTitleText() ) != '' )
  474. $this->setPageTitle( $title );
  475. // Hooks registered in the object
  476. global $wgParserOutputHooks;
  477. foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
  478. list( $hookName, $data ) = $hookInfo;
  479. if ( isset( $wgParserOutputHooks[$hookName] ) ) {
  480. call_user_func( $wgParserOutputHooks[$hookName], $this, $parserOutput, $data );
  481. }
  482. }
  483. wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) );
  484. }
  485. /**
  486. * @todo document
  487. * @param ParserOutput &$parserOutput
  488. */
  489. function addParserOutput( &$parserOutput ) {
  490. $this->addParserOutputNoText( $parserOutput );
  491. $text = $parserOutput->getText();
  492. wfRunHooks( 'OutputPageBeforeHTML',array( &$this, &$text ) );
  493. $this->addHTML( $text );
  494. }
  495. /**
  496. * Add wikitext to the buffer, assuming that this is the primary text for a page view
  497. * Saves the text into the parser cache if possible.
  498. *
  499. * @param string $text
  500. * @param Article $article
  501. * @param bool $cache
  502. * @deprecated Use Article::outputWikitext
  503. */
  504. public function addPrimaryWikiText( $text, $article, $cache = true ) {
  505. global $wgParser, $wgUser;
  506. wfDeprecated( __METHOD__ );
  507. $popts = $this->parserOptions();
  508. $popts->setTidy(true);
  509. $parserOutput = $wgParser->parse( $text, $article->mTitle,
  510. $popts, true, true, $this->mRevisionId );
  511. $popts->setTidy(false);
  512. if ( $cache && $article && $parserOutput->getCacheTime() != -1 ) {
  513. $parserCache = ParserCache::singleton();
  514. $parserCache->save( $parserOutput, $article, $popts);
  515. }
  516. $this->addParserOutput( $parserOutput );
  517. }
  518. /**
  519. * @deprecated use addWikiTextTidy()
  520. */
  521. public function addSecondaryWikiText( $text, $linestart = true ) {
  522. global $wgTitle;
  523. wfDeprecated( __METHOD__ );
  524. $this->addWikiTextTitleTidy($text, $wgTitle, $linestart);
  525. }
  526. /**
  527. * Add wikitext with tidy enabled
  528. */
  529. public function addWikiTextTidy( $text, $linestart = true ) {
  530. global $wgTitle;
  531. $this->addWikiTextTitleTidy($text, $wgTitle, $linestart);
  532. }
  533. /**
  534. * Add the output of a QuickTemplate to the output buffer
  535. *
  536. * @param QuickTemplate $template
  537. */
  538. public function addTemplate( &$template ) {
  539. ob_start();
  540. $template->execute();
  541. $this->addHTML( ob_get_contents() );
  542. ob_end_clean();
  543. }
  544. /**
  545. * Parse wikitext and return the HTML.
  546. *
  547. * @param string $text
  548. * @param bool $linestart Is this the start of a line?
  549. * @param bool $interface ??
  550. */
  551. public function parse( $text, $linestart = true, $interface = false ) {
  552. global $wgParser, $wgTitle;
  553. if( is_null( $wgTitle ) ) {
  554. throw new MWException( 'Empty $wgTitle in ' . __METHOD__ );
  555. }
  556. $popts = $this->parserOptions();
  557. if ( $interface) { $popts->setInterfaceMessage(true); }
  558. $parserOutput = $wgParser->parse( $text, $wgTitle, $popts,
  559. $linestart, true, $this->mRevisionId );
  560. if ( $interface) { $popts->setInterfaceMessage(false); }
  561. return $parserOutput->getText();
  562. }
  563. /** Parse wikitext, strip paragraphs, and return the HTML. */
  564. public function parseInline( $text, $linestart = true, $interface = false ) {
  565. $parsed = $this->parse( $text, $linestart, $interface );
  566. $m = array();
  567. if ( preg_match( '/^<p>(.*)\n?<\/p>\n?/sU', $parsed, $m ) ) {
  568. $parsed = $m[1];
  569. }
  570. return $parsed;
  571. }
  572. /**
  573. * @param Article $article
  574. * @param User $user
  575. *
  576. * @return bool True if successful, else false.
  577. */
  578. public function tryParserCache( &$article ) {
  579. $parserCache = ParserCache::singleton();
  580. $parserOutput = $parserCache->get( $article, $this->parserOptions() );
  581. if ( $parserOutput !== false ) {
  582. $this->addParserOutput( $parserOutput );
  583. return true;
  584. } else {
  585. return false;
  586. }
  587. }
  588. /**
  589. * @param int $maxage Maximum cache time on the Squid, in seconds.
  590. */
  591. public function setSquidMaxage( $maxage ) {
  592. $this->mSquidMaxage = $maxage;
  593. }
  594. /**
  595. * Use enableClientCache(false) to force it to send nocache headers
  596. * @param $state ??
  597. */
  598. public function enableClientCache( $state ) {
  599. return wfSetVar( $this->mEnableClientCache, $state );
  600. }
  601. function getCacheVaryCookies() {
  602. global $wgCookiePrefix, $wgCacheVaryCookies;
  603. static $cookies;
  604. if ( $cookies === null ) {
  605. $cookies = array_merge(
  606. array(
  607. "{$wgCookiePrefix}Token",
  608. "{$wgCookiePrefix}LoggedOut",
  609. session_name()
  610. ),
  611. $wgCacheVaryCookies
  612. );
  613. wfRunHooks('GetCacheVaryCookies', array( $this, &$cookies ) );
  614. }
  615. return $cookies;
  616. }
  617. function uncacheableBecauseRequestVars() {
  618. global $wgRequest;
  619. return $wgRequest->getText('useskin', false) === false
  620. && $wgRequest->getText('uselang', false) === false;
  621. }
  622. /**
  623. * Check if the request has a cache-varying cookie header
  624. * If it does, it's very important that we don't allow public caching
  625. */
  626. function haveCacheVaryCookies() {
  627. global $wgRequest;
  628. $cookieHeader = $wgRequest->getHeader( 'cookie' );
  629. if ( $cookieHeader === false ) {
  630. return false;
  631. }
  632. $cvCookies = $this->getCacheVaryCookies();
  633. foreach ( $cvCookies as $cookieName ) {
  634. # Check for a simple string match, like the way squid does it
  635. if ( strpos( $cookieHeader, $cookieName ) ) {
  636. wfDebug( __METHOD__.": found $cookieName\n" );
  637. return true;
  638. }
  639. }
  640. wfDebug( __METHOD__.": no cache-varying cookies found\n" );
  641. return false;
  642. }
  643. /** Get a complete X-Vary-Options header */
  644. public function getXVO() {
  645. $cvCookies = $this->getCacheVaryCookies();
  646. $xvo = 'X-Vary-Options: Accept-Encoding;list-contains=gzip,Cookie;';
  647. $first = true;
  648. foreach ( $cvCookies as $cookieName ) {
  649. if ( $first ) {
  650. $first = false;
  651. } else {
  652. $xvo .= ';';
  653. }
  654. $xvo .= 'string-contains=' . $cookieName;
  655. }
  656. return $xvo;
  657. }
  658. public function sendCacheControl() {
  659. global $wgUseSquid, $wgUseESI, $wgUseETag, $wgSquidMaxage, $wgRequest;
  660. $response = $wgRequest->response();
  661. if ($wgUseETag && $this->mETag)
  662. $response->header("ETag: $this->mETag");
  663. # don't serve compressed data to clients who can't handle it
  664. # maintain different caches for logged-in users and non-logged in ones
  665. $response->header( 'Vary: Accept-Encoding, Cookie' );
  666. # Add an X-Vary-Options header for Squid with Wikimedia patches
  667. $response->header( $this->getXVO() );
  668. if( !$this->uncacheableBecauseRequestVars() && $this->mEnableClientCache ) {
  669. if( $wgUseSquid && session_id() == '' &&
  670. ! $this->isPrintable() && $this->mSquidMaxage != 0 && !$this->haveCacheVaryCookies() )
  671. {
  672. if ( $wgUseESI ) {
  673. # We'll purge the proxy cache explicitly, but require end user agents
  674. # to revalidate against the proxy on each visit.
  675. # Surrogate-Control controls our Squid, Cache-Control downstream caches
  676. wfDebug( __METHOD__ . ": proxy caching with ESI; {$this->mLastModified} **\n", false );
  677. # start with a shorter timeout for initial testing
  678. # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
  679. $response->header( 'Surrogate-Control: max-age='.$wgSquidMaxage.'+'.$this->mSquidMaxage.', content="ESI/1.0"');
  680. $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
  681. } else {
  682. # We'll purge the proxy cache for anons explicitly, but require end user agents
  683. # to revalidate against the proxy on each visit.
  684. # IMPORTANT! The Squid needs to replace the Cache-Control header with
  685. # Cache-Control: s-maxage=0, must-revalidate, max-age=0
  686. wfDebug( __METHOD__ . ": local proxy caching; {$this->mLastModified} **\n", false );
  687. # start with a shorter timeout for initial testing
  688. # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
  689. $response->header( 'Cache-Control: s-maxage='.$this->mSquidMaxage.', must-revalidate, max-age=0' );
  690. }
  691. } else {
  692. # We do want clients to cache if they can, but they *must* check for updates
  693. # on revisiting the page.
  694. wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **\n", false );
  695. $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
  696. $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
  697. }
  698. if($this->mLastModified) {
  699. $response->header( "Last-Modified: {$this->mLastModified}" );
  700. }
  701. } else {
  702. wfDebug( __METHOD__ . ": no caching **\n", false );
  703. # In general, the absence of a last modified header should be enough to prevent
  704. # the client from using its cache. We send a few other things just to make sure.
  705. $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
  706. $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
  707. $response->header( 'Pragma: no-cache' );
  708. }
  709. }
  710. /**
  711. * Finally, all the text has been munged and accumulated into
  712. * the object, let's actually output it:
  713. */
  714. public function output() {
  715. global $wgUser, $wgOutputEncoding, $wgRequest;
  716. global $wgContLanguageCode, $wgDebugRedirects, $wgMimeType;
  717. global $wgJsMimeType, $wgUseAjax, $wgAjaxWatch;
  718. global $wgEnableMWSuggest, $wgUniversalEditButton;
  719. global $wgArticle, $wgTitle;
  720. if( $this->mDoNothing ){
  721. return;
  722. }
  723. wfProfileIn( __METHOD__ );
  724. if ( '' != $this->mRedirect ) {
  725. # Standards require redirect URLs to be absolute
  726. $this->mRedirect = wfExpandUrl( $this->mRedirect );
  727. if( $this->mRedirectCode == '301') {
  728. if( !$wgDebugRedirects ) {
  729. $wgRequest->response()->header("HTTP/1.1 {$this->mRedirectCode} Moved Permanently");
  730. }
  731. $this->mLastModified = wfTimestamp( TS_RFC2822 );
  732. }
  733. $this->sendCacheControl();
  734. $wgRequest->response()->header("Content-Type: text/html; charset=utf-8");
  735. if( $wgDebugRedirects ) {
  736. $url = htmlspecialchars( $this->mRedirect );
  737. print "<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
  738. print "<p>Location: <a href=\"$url\">$url</a></p>\n";
  739. print "</body>\n</html>\n";
  740. } else {
  741. $wgRequest->response()->header( 'Location: '.$this->mRedirect );
  742. }
  743. wfProfileOut( __METHOD__ );
  744. return;
  745. }
  746. elseif ( $this->mStatusCode )
  747. {
  748. $statusMessage = array(
  749. 100 => 'Continue',
  750. 101 => 'Switching Protocols',
  751. 102 => 'Processing',
  752. 200 => 'OK',
  753. 201 => 'Created',
  754. 202 => 'Accepted',
  755. 203 => 'Non-Authoritative Information',
  756. 204 => 'No Content',
  757. 205 => 'Reset Content',
  758. 206 => 'Partial Content',
  759. 207 => 'Multi-Status',
  760. 300 => 'Multiple Choices',
  761. 301 => 'Moved Permanently',
  762. 302 => 'Found',
  763. 303 => 'See Other',
  764. 304 => 'Not Modified',
  765. 305 => 'Use Proxy',
  766. 307 => 'Temporary Redirect',
  767. 400 => 'Bad Request',
  768. 401 => 'Unauthorized',
  769. 402 => 'Payment Required',
  770. 403 => 'Forbidden',
  771. 404 => 'Not Found',
  772. 405 => 'Method Not Allowed',
  773. 406 => 'Not Acceptable',
  774. 407 => 'Proxy Authentication Required',
  775. 408 => 'Request Timeout',
  776. 409 => 'Conflict',
  777. 410 => 'Gone',
  778. 411 => 'Length Required',
  779. 412 => 'Precondition Failed',
  780. 413 => 'Request Entity Too Large',
  781. 414 => 'Request-URI Too Large',
  782. 415 => 'Unsupported Media Type',
  783. 416 => 'Request Range Not Satisfiable',
  784. 417 => 'Expectation Failed',
  785. 422 => 'Unprocessable Entity',
  786. 423 => 'Locked',
  787. 424 => 'Failed Dependency',
  788. 500 => 'Internal Server Error',
  789. 501 => 'Not Implemented',
  790. 502 => 'Bad Gateway',
  791. 503 => 'Service Unavailable',
  792. 504 => 'Gateway Timeout',
  793. 505 => 'HTTP Version Not Supported',
  794. 507 => 'Insufficient Storage'
  795. );
  796. if ( $statusMessage[$this->mStatusCode] )
  797. $wgRequest->response()->header( 'HTTP/1.1 ' . $this->mStatusCode . ' ' . $statusMessage[$this->mStatusCode] );
  798. }
  799. $sk = $wgUser->getSkin();
  800. if ( $wgUseAjax ) {
  801. $this->addScriptFile( 'ajax.js' );
  802. wfRunHooks( 'AjaxAddScript', array( &$this ) );
  803. if( $wgAjaxWatch && $wgUser->isLoggedIn() ) {
  804. $this->addScriptFile( 'ajaxwatch.js' );
  805. }
  806. if ( $wgEnableMWSuggest && !$wgUser->getOption( 'disablesuggest', false ) ){
  807. $this->addScriptFile( 'mwsuggest.js' );
  808. }
  809. }
  810. if( $wgUser->getBoolOption( 'editsectiononrightclick' ) ) {
  811. $this->addScriptFile( 'rightclickedit.js' );
  812. }
  813. if( $wgUniversalEditButton ) {
  814. if( isset( $wgArticle ) && isset( $wgTitle ) && $wgTitle->quickUserCan( 'edit' )
  815. && ( $wgTitle->exists() || $wgTitle->quickUserCan( 'create' ) ) ) {
  816. // Original UniversalEditButton
  817. $this->addLink( array(
  818. 'rel' => 'alternate',
  819. 'type' => 'application/x-wiki',
  820. 'title' => wfMsg( 'edit' ),
  821. 'href' => $wgTitle->getLocalURL( 'action=edit' )
  822. ) );
  823. // Alternate edit link
  824. $this->addLink( array(
  825. 'rel' => 'edit',
  826. 'title' => wfMsg( 'edit' ),
  827. 'href' => $wgTitle->getLocalURL( 'action=edit' )
  828. ) );
  829. }
  830. }
  831. # Buffer output; final headers may depend on later processing
  832. ob_start();
  833. $wgRequest->response()->header( "Content-type: $wgMimeType; charset={$wgOutputEncoding}" );
  834. $wgRequest->response()->header( 'Content-language: '.$wgContLanguageCode );
  835. if ($this->mArticleBodyOnly) {
  836. $this->out($this->mBodytext);
  837. } else {
  838. // Hook that allows last minute changes to the output page, e.g.
  839. // adding of CSS or Javascript by extensions.
  840. wfRunHooks( 'BeforePageDisplay', array( &$this, &$sk ) );
  841. wfProfileIn( 'Output-skin' );
  842. $sk->outputPage( $this );
  843. wfProfileOut( 'Output-skin' );
  844. }
  845. $this->sendCacheControl();
  846. ob_end_flush();
  847. wfProfileOut( __METHOD__ );
  848. }
  849. /**
  850. * @todo document
  851. * @param string $ins
  852. */
  853. public function out( $ins ) {
  854. global $wgInputEncoding, $wgOutputEncoding, $wgContLang;
  855. if ( 0 == strcmp( $wgInputEncoding, $wgOutputEncoding ) ) {
  856. $outs = $ins;
  857. } else {
  858. $outs = $wgContLang->iconv( $wgInputEncoding, $wgOutputEncoding, $ins );
  859. if ( false === $outs ) { $outs = $ins; }
  860. }
  861. print $outs;
  862. }
  863. /**
  864. * @todo document
  865. */
  866. public static function setEncodings() {
  867. global $wgInputEncoding, $wgOutputEncoding;
  868. global $wgUser, $wgContLang;
  869. $wgInputEncoding = strtolower( $wgInputEncoding );
  870. if ( empty( $_SERVER['HTTP_ACCEPT_CHARSET'] ) ) {
  871. $wgOutputEncoding = strtolower( $wgOutputEncoding );
  872. return;
  873. }
  874. $wgOutputEncoding = $wgInputEncoding;
  875. }
  876. /**
  877. * Deprecated, use wfReportTime() instead.
  878. * @return string
  879. * @deprecated
  880. */
  881. public function reportTime() {
  882. wfDeprecated( __METHOD__ );
  883. $time = wfReportTime();
  884. return $time;
  885. }
  886. /**
  887. * Produce a "user is blocked" page.
  888. *
  889. * @param bool $return Whether to have a "return to $wgTitle" message or not.
  890. * @return nothing
  891. */
  892. function blockedPage( $return = true ) {
  893. global $wgUser, $wgContLang, $wgTitle, $wgLang;
  894. $this->setPageTitle( wfMsg( 'blockedtitle' ) );
  895. $this->setRobotPolicy( 'noindex,nofollow' );
  896. $this->setArticleRelated( false );
  897. $name = User::whoIs( $wgUser->blockedBy() );
  898. $reason = $wgUser->blockedFor();
  899. if( $reason == '' ) {
  900. $reason = wfMsg( 'blockednoreason' );
  901. }
  902. $blockTimestamp = $wgLang->timeanddate( wfTimestamp( TS_MW, $wgUser->mBlock->mTimestamp ), true );
  903. $ip = wfGetIP();
  904. $link = '[[' . $wgContLang->getNsText( NS_USER ) . ":{$name}|{$name}]]";
  905. $blockid = $wgUser->mBlock->mId;
  906. $blockExpiry = $wgUser->mBlock->mExpiry;
  907. if ( $blockExpiry == 'infinity' ) {
  908. // Entry in database (table ipblocks) is 'infinity' but 'ipboptions' uses 'infinite' or 'indefinite'
  909. // Search for localization in 'ipboptions'
  910. $scBlockExpiryOptions = wfMsg( 'ipboptions' );
  911. foreach ( explode( ',', $scBlockExpiryOptions ) as $option ) {
  912. if ( strpos( $option, ":" ) === false )
  913. continue;
  914. list( $show, $value ) = explode( ":", $option );
  915. if ( $value == 'infinite' || $value == 'indefinite' ) {
  916. $blockExpiry = $show;
  917. break;
  918. }
  919. }
  920. } else {
  921. $blockExpiry = $wgLang->timeanddate( wfTimestamp( TS_MW, $blockExpiry ), true );
  922. }
  923. if ( $wgUser->mBlock->mAuto ) {
  924. $msg = 'autoblockedtext';
  925. } else {
  926. $msg = 'blockedtext';
  927. }
  928. /* $ip returns who *is* being blocked, $intended contains who was meant to be blocked.
  929. * This could be a username, an ip range, or a single ip. */
  930. $intended = $wgUser->mBlock->mAddress;
  931. $this->addWikiMsg( $msg, $link, $reason, $ip, $name, $blockid, $blockExpiry, $intended, $blockTimestamp );
  932. # Don't auto-return to special pages
  933. if( $return ) {
  934. $return = $wgTitle->getNamespace() > -1 ? $wgTitle : NULL;
  935. $this->returnToMain( null, $return );
  936. }
  937. }
  938. /**
  939. * Output a standard error page
  940. *
  941. * @param string $title Message key for page title
  942. * @param string $msg Message key for page text
  943. * @param array $params Message parameters
  944. */
  945. public function showErrorPage( $title, $msg, $params = array() ) {
  946. global $wgTitle;
  947. if ( isset($wgTitle) ) {
  948. $this->mDebugtext .= 'Original title: ' . $wgTitle->getPrefixedText() . "\n";
  949. }
  950. $this->setPageTitle( wfMsg( $title ) );
  951. $this->setHTMLTitle( wfMsg( 'errorpagetitle' ) );
  952. $this->setRobotPolicy( 'noindex,nofollow' );
  953. $this->setArticleRelated( false );
  954. $this->enableClientCache( false );
  955. $this->mRedirect = '';
  956. $this->mBodytext = '';
  957. array_unshift( $params, 'parse' );
  958. array_unshift( $params, $msg );
  959. $this->addHTML( call_user_func_array( 'wfMsgExt', $params ) );
  960. $this->returnToMain();
  961. }
  962. /**
  963. * Output a standard permission error page
  964. *
  965. * @param array $errors Error message keys
  966. */
  967. public function showPermissionsErrorPage( $errors, $action = null )
  968. {
  969. global $wgTitle;
  970. $this->mDebugtext .= 'Original title: ' .
  971. $wgTitle->getPrefixedText() . "\n";
  972. $this->setPageTitle( wfMsg( 'permissionserrors' ) );
  973. $this->setHTMLTitle( wfMsg( 'permissionserrors' ) );
  974. $this->setRobotPolicy( 'noindex,nofollow' );
  975. $this->setArticleRelated( false );
  976. $this->enableClientCache( false );
  977. $this->mRedirect = '';
  978. $this->mBodytext = '';
  979. $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) );
  980. }
  981. /** @deprecated */
  982. public function errorpage( $title, $msg ) {
  983. wfDeprecated( __METHOD__ );
  984. throw new ErrorPageError( $title, $msg );
  985. }
  986. /**
  987. * Display an error page indicating that a given version of MediaWiki is
  988. * required to use it
  989. *
  990. * @param mixed $version The version of MediaWiki needed to use the page
  991. */
  992. public function versionRequired( $version ) {
  993. $this->setPageTitle( wfMsg( 'versionrequired', $version ) );
  994. $this->setHTMLTitle( wfMsg( 'versionrequired', $version ) );
  995. $this->setRobotPolicy( 'noindex,nofollow' );
  996. $this->setArticleRelated( false );
  997. $this->mBodytext = '';
  998. $this->addWikiMsg( 'versionrequiredtext', $version );
  999. $this->returnToMain();
  1000. }
  1001. /**
  1002. * Display an error page noting that a given permission bit is required.
  1003. *
  1004. * @param string $permission key required
  1005. */
  1006. public function permissionRequired( $permission ) {
  1007. global $wgUser, $wgLang;
  1008. $this->setPageTitle( wfMsg( 'badaccess' ) );
  1009. $this->setHTMLTitle( wfMsg( 'errorpagetitle' ) );
  1010. $this->setRobotPolicy( 'noindex,nofollow' );
  1011. $this->setArticleRelated( false );
  1012. $this->mBodytext = '';
  1013. $groups = array_map( array( 'User', 'makeGroupLinkWiki' ),
  1014. User::getGroupsWithPermission( $permission ) );
  1015. if( $groups ) {
  1016. $this->addWikiMsg( 'badaccess-groups',
  1017. $wgLang->commaList( $groups ),
  1018. count( $groups) );
  1019. } else {
  1020. $this->addWikiMsg( 'badaccess-group0' );
  1021. }
  1022. $this->returnToMain();
  1023. }
  1024. /**
  1025. * Use permissionRequired.
  1026. * @deprecated
  1027. */
  1028. public function sysopRequired() {
  1029. throw new MWException( "Call to deprecated OutputPage::sysopRequired() method\n" );
  1030. }
  1031. /**
  1032. * Use permissionRequired.
  1033. * @deprecated
  1034. */
  1035. public function developerRequired() {
  1036. throw new MWException( "Call to deprecated OutputPage::developerRequired() method\n" );
  1037. }
  1038. /**
  1039. * Produce the stock "please login to use the wiki" page
  1040. */
  1041. public function loginToUse() {
  1042. global $wgUser, $wgTitle, $wgContLang;
  1043. if( $wgUser->isLoggedIn() ) {
  1044. $this->permissionRequired( 'read' );
  1045. return;
  1046. }
  1047. $skin = $wgUser->getSkin();
  1048. $this->setPageTitle( wfMsg( 'loginreqtitle' ) );
  1049. $this->setHtmlTitle( wfMsg( 'errorpagetitle' ) );
  1050. $this->setRobotPolicy( 'noindex,nofollow' );
  1051. $this->setArticleFlag( false );
  1052. $loginTitle = SpecialPage::getTitleFor( 'Userlogin' );
  1053. $loginLink = $skin->makeKnownLinkObj( $loginTitle, wfMsgHtml( 'loginreqlink' ), 'returnto=' . $wgTitle->getPrefixedUrl() );
  1054. $this->addHTML( wfMsgWikiHtml( 'loginreqpagetext', $loginLink ) );
  1055. $this->addHTML( "\n<!--" . $wgTitle->getPrefixedUrl() . "-->" );
  1056. # Don't return to the main page if the user can't read it
  1057. # otherwise we'll end up in a pointless loop
  1058. $mainPage = Title::newMainPage();
  1059. if( $mainPage->userCanRead() )
  1060. $this->returnToMain( null, $mainPage );
  1061. }
  1062. /** @deprecated */
  1063. public function databaseError( $fname, $sql, $error, $errno ) {
  1064. throw new MWException( "OutputPage::databaseError is obsolete\n" );
  1065. }
  1066. /**
  1067. * @param array $errors An array of arrays returned by Title::getUserPermissionsErrors
  1068. * @return string The wikitext error-messages, formatted into a list.
  1069. */
  1070. public function formatPermissionsErrorMessage( $errors, $action = null ) {
  1071. if ($action == null) {
  1072. $text = wfMsgNoTrans( 'permissionserrorstext', count($errors)). "\n\n";
  1073. } else {
  1074. global $wgLang;
  1075. $action_desc = wfMsg( "action-$action" );
  1076. $text = wfMsgNoTrans( 'permissionserrorstext-withaction', count($errors), $action_desc ) . "\n\n";
  1077. }
  1078. if (count( $errors ) > 1) {
  1079. $text .= '<ul class="permissions-errors">' . "\n";
  1080. foreach( $errors as $error )
  1081. {
  1082. $text .= '<li>';
  1083. $text .= call_user_func_array( 'wfMsgNoTrans', $error );
  1084. $text .= "</li>\n";
  1085. }
  1086. $text .= '</ul>';
  1087. } else {
  1088. $text .= '<div class="permissions-errors">' . call_user_func_array( 'wfMsgNoTrans', reset( $errors ) ) . '</div>';
  1089. }
  1090. return $text;
  1091. }
  1092. /**
  1093. * Display a page stating that the Wiki is in read-only mode,
  1094. * and optionally show the source of the page that the user
  1095. * was trying to edit. Should only be called (for this
  1096. * purpose) after wfReadOnly() has returned true.
  1097. *
  1098. * For historical reasons, this function is _also_ used to
  1099. * show the error message when a user tries to edit a page
  1100. * they are not allowed to edit. (Unless it's because they're
  1101. * blocked, then we show blockedPage() instead.) In this
  1102. * case, the second parameter should be set to true and a list
  1103. * of reasons supplied as the third parameter.
  1104. *
  1105. * @todo Needs to be split into multiple functions.
  1106. *
  1107. * @param string $source Source code to show (or null).
  1108. * @param bool $protected Is this a permissions error?
  1109. * @param array $reasons List of reasons for this error, as returned by Title::getUserPermissionsErrors().
  1110. */
  1111. public function readOnlyPage( $source = null, $protected = false, $reasons = array(), $action = null ) {
  1112. global $wgUser, $wgTitle;
  1113. $skin = $wgUser->getSkin();
  1114. $this->setRobotPolicy( 'noindex,nofollow' );
  1115. $this->setArticleRelated( false );
  1116. // If no reason is given, just supply a default "I can't let you do
  1117. // that, Dave" message. Should only occur if called by legacy code.
  1118. if ( $protected && empty($reasons) ) {
  1119. $reasons[] = array( 'badaccess-group0' );
  1120. }
  1121. if ( !empty($reasons) ) {
  1122. // Permissions error
  1123. if( $source ) {
  1124. $this->setPageTitle( wfMsg( 'viewsource' ) );
  1125. $this->setSubtitle( wfMsg( 'viewsourcefor', $skin->makeKnownLinkObj( $wgTitle ) ) );
  1126. } else {
  1127. $this->setPageTitle( wfMsg( 'badaccess' ) );
  1128. }
  1129. $this->addWikiText( $this->formatPermissionsErrorMessage( $reasons, $action ) );
  1130. } else {
  1131. // Wiki is read only
  1132. $this->setPageTitle( wfMsg( 'readonly' ) );
  1133. $reason = wfReadOnlyReason();
  1134. $this->wrapWikiMsg( '<div class="mw-readonly-error">$1</div>', array( 'readonlytext', $reason ) );
  1135. }
  1136. // Show source, if supplied
  1137. if( is_string( $source ) ) {
  1138. $this->addWikiMsg( 'viewsourcetext' );
  1139. $text = Xml::openElement( 'textarea',
  1140. array( 'id' => 'wpTextbox1',
  1141. 'name' => 'wpTextbox1',
  1142. 'cols' => $wgUser->getOption( 'cols' ),
  1143. 'rows' => $wgUser->getOption( 'rows' ),
  1144. 'readonly' => 'readonly' ) );
  1145. $text .= htmlspecialchars( $source );
  1146. $text .= Xml::closeElement( 'textarea' );
  1147. $this->addHTML( $text );
  1148. // Show templates used by this article
  1149. $skin = $wgUser->getSkin();
  1150. $article = new Article( $wgTitle );
  1151. $this->addHTML( "<div class='templatesUsed'>
  1152. {$skin->formatTemplates( $article->getUsedTemplates() )}
  1153. </div>
  1154. " );
  1155. }
  1156. # If the title doesn't exist, it's fairly pointless to print a return
  1157. # link to it. After all, you just tried editing it and couldn't, so
  1158. # what's there to do there?
  1159. if( $wgTitle->exists() ) {
  1160. $this->returnToMain( null, $wgTitle );
  1161. }
  1162. }
  1163. /** @deprecated */
  1164. public function fatalError( $message ) {
  1165. wfDeprecated( __METHOD__ );
  1166. throw new FatalError( $message );
  1167. }
  1168. /** @deprecated */
  1169. public function unexpectedValueError( $name, $val ) {
  1170. wfDeprecated( __METHOD__ );
  1171. throw new FatalError( wfMsg( 'unexpected', $name, $val ) );
  1172. }
  1173. /** @deprecated */
  1174. public function fileCopyError( $old, $new ) {
  1175. wfDeprecated( __METHOD__ );
  1176. throw new FatalError( wfMsg( 'filecopyerror', $old, $new ) );
  1177. }
  1178. /** @deprecated */
  1179. public function fileRenameError( $old, $new ) {
  1180. wfDeprecated( __METHOD__ );
  1181. throw new FatalError( wfMsg( 'filerenameerror', $old, $new ) );
  1182. }
  1183. /** @deprecated */
  1184. public function fileDeleteError( $name ) {
  1185. wfDeprecated( __METHOD__ );
  1186. throw new FatalError( wfMsg( 'filedeleteerror', $name ) );
  1187. }
  1188. /** @deprecated */
  1189. public function fileNotFoundError( $name ) {
  1190. wfDeprecated( __METHOD__ );
  1191. throw new FatalError( wfMsg( 'filenotfound', $name ) );
  1192. }
  1193. public function showFatalError( $message ) {
  1194. $this->setPageTitle( wfMsg( "internalerror" ) );
  1195. $this->setRobotPolicy( "noindex,nofollow" );
  1196. $this->setArticleRelated( false );
  1197. $this->enableClientCache( false );
  1198. $this->mRedirect = '';
  1199. $this->mBodytext = $message;
  1200. }
  1201. public function showUnexpectedValueError( $name, $val ) {
  1202. $this->showFatalError( wfMsg( 'unexpected', $name, $val ) );
  1203. }
  1204. public function showFileCopyError( $old, $new ) {
  1205. $this->showFatalError( wfMsg( 'filecopyerror', $old, $new ) );
  1206. }
  1207. public function showFileRenameError( $old, $new ) {
  1208. $this->showFatalError( wfMsg( 'filerenameerror', $old, $new ) );
  1209. }
  1210. public function showFileDeleteError( $name ) {
  1211. $this->showFatalError( wfMsg( 'filedeleteerror', $name ) );
  1212. }
  1213. public function showFileNotFoundError( $name ) {
  1214. $this->showFatalError( wfMsg( 'filenotfound', $name ) );
  1215. }
  1216. /**
  1217. * Add a "return to" link pointing to a specified title
  1218. *
  1219. * @param Title $title Title to link
  1220. */
  1221. public function addReturnTo( $title ) {
  1222. global $wgUser;
  1223. $this->addLink( array( 'rel' => 'next', 'href' => $title->getFullUrl() ) );
  1224. $link = wfMsg( 'returnto', $wgUser->getSkin()->makeLinkObj( $title ) );
  1225. $this->addHTML( "<p>{$link}</p>\n" );
  1226. }
  1227. /**
  1228. * Add a "return to" link pointing to a specified title,
  1229. * or the title indicated in the request, or else the main page
  1230. *
  1231. * @param null $unused No longer used
  1232. * @param Title $returnto Title to return to
  1233. */
  1234. public function returnToMain( $unused = null, $returnto = NULL ) {
  1235. global $wgRequest;
  1236. if ( $returnto == NULL ) {
  1237. $returnto = $wgRequest->getText( 'returnto' );
  1238. }
  1239. if ( '' === $returnto ) {
  1240. $returnto = Title::newMainPage();
  1241. }
  1242. if ( is_object( $returnto ) ) {
  1243. $titleObj = $returnto;
  1244. } else {
  1245. $titleObj = Title::newFromText( $returnto );
  1246. }
  1247. if ( !is_object( $titleObj ) ) {
  1248. $titleObj = Title::newMainPage();
  1249. }
  1250. $this->addReturnTo( $titleObj );
  1251. }
  1252. /**
  1253. * This function takes the title (first item of mGoodLinks), categories, existing and broken links for the page
  1254. * and uses the first 10 of them for META keywords
  1255. *
  1256. * @param ParserOutput &$parserOutput
  1257. */
  1258. private function addKeywords( &$parserOutput ) {
  1259. global $wgTitle;
  1260. $this->addKeyword( $wgTitle->getPrefixedText() );
  1261. $count = 1;
  1262. $links2d =& $parserOutput->getLinks();
  1263. if ( !is_array( $links2d ) ) {
  1264. return;
  1265. }
  1266. foreach ( $links2d as $dbkeys ) {
  1267. foreach( $dbkeys as $dbkey => $unused ) {
  1268. $this->addKeyword( $dbkey );
  1269. if ( ++$count > 10 ) {
  1270. break 2;
  1271. }
  1272. }
  1273. }
  1274. }
  1275. /**
  1276. * @return string The doctype, opening <html>, and head element.
  1277. */
  1278. public function headElement( Skin $sk ) {
  1279. global $wgDocType, $wgDTD, $wgContLanguageCode, $wgOutputEncoding, $wgMimeType;
  1280. global $wgXhtmlDefaultNamespace, $wgXhtmlNamespaces;
  1281. global $wgUser, $wgContLang, $wgUseTrackbacks, $wgTitle, $wgStyleVersion;
  1282. $this->addMeta( "http:Content-type", "$wgMimeType; charset={$wgOutputEncoding}" );
  1283. $this->addStyle( 'common/wikiprintable.css', 'print' );
  1284. $sk->setupUserCss( $this );
  1285. $ret = '';
  1286. if( $wgMimeType == 'text/xml' || $wgMimeType == 'application/xhtml+xml' || $wgMimeType == 'application/xml' ) {
  1287. $ret .= "<?xml version=\"1.0\" encoding=\"$wgOutputEncoding\" ?" . ">\n";
  1288. }
  1289. $ret .= "<!DOCTYPE html PUBLIC \"$wgDocType\"\n \"$wgDTD\">\n";
  1290. if ( '' == $this->getHTMLTitle() ) {
  1291. $this->setHTMLTitle( wfMsg( 'pagetitle', $this->getPageTitle() ));
  1292. }
  1293. $rtl = $wgContLang->isRTL() ? " dir='RTL'" : '';
  1294. $ret .= "<html xmlns=\"{$wgXhtmlDefaultNamespace}\" ";
  1295. foreach($wgXhtmlNamespaces as $tag => $ns) {
  1296. $ret .= "xmlns:{$tag}=\"{$ns}\" ";
  1297. }
  1298. $ret .= "xml:lang=\"$wgContLanguageCode\" lang=\"$wgContLanguageCode\" $rtl>\n";
  1299. $ret .= "<head>\n<title>" . htmlspecialchars( $this->getHTMLTitle() ) . "</title>\n\t\t";
  1300. $ret .= implode( "\t\t", array(
  1301. $this->getHeadLinks(),
  1302. $this->buildCssLinks(),
  1303. $sk->getHeadScripts( $this->mAllowUserJs ),
  1304. $this->mScripts,
  1305. $this->getHeadItems(),
  1306. ));
  1307. if( $sk->usercss ){
  1308. $ret .= "<style type='text/css'>{$sk->usercss}</style>";
  1309. }
  1310. if ($wgUseTrackbacks && $this->isArticleRelated())
  1311. $ret .= $wgTitle->trackbackRDF();
  1312. $ret .= "</head>\n";
  1313. return $ret;
  1314. }
  1315. protected function addDefaultMeta() {
  1316. global $wgVersion;
  1317. $this->addMeta( 'http:Content-Style-Type', 'text/css' ); //bug 15835
  1318. $this->addMeta( 'generator', "MediaWiki $wgVersion" );
  1319. $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
  1320. if( $p !== 'index,follow' ) {
  1321. // http://www.robotstxt.org/wc/meta-user.html
  1322. // Only show if it's different from the default robots policy
  1323. $this->addMeta( 'robots', $p );
  1324. }
  1325. if ( count( $this->mKeywords ) > 0 ) {
  1326. $strip = array(
  1327. "/<.*?" . ">/" => '',
  1328. "/_/" => ' '
  1329. );
  1330. $this->addMeta( 'keywords', preg_replace(array_keys($strip), array_values($strip),implode( ",", $this->mKeywords ) ) );
  1331. }
  1332. }
  1333. /**
  1334. * @return string HTML tag links to be put in the header.
  1335. */
  1336. public function getHeadLinks() {
  1337. global $wgRequest, $wgFeed;
  1338. // Ideally this should happen earlier, somewhere. :P
  1339. $this->addDefaultMeta();
  1340. $tags = array();
  1341. foreach ( $this->mMetatags as $tag ) {
  1342. if ( 0 == strcasecmp( 'http:', substr( $tag[0], 0, 5 ) ) ) {
  1343. $a = 'http-equiv';
  1344. $tag[0] = substr( $tag[0], 5 );
  1345. } else {
  1346. $a = 'name';
  1347. }
  1348. $tags[] = Xml::element( 'meta',
  1349. array(
  1350. $a => $tag[0],
  1351. 'content' => $tag[1] ) );
  1352. }
  1353. foreach ( $this->mLinktags as $tag ) {
  1354. $tags[] = Xml::element( 'link', $tag );
  1355. }
  1356. if( $wgFeed ) {
  1357. global $wgTitle;
  1358. foreach( $this->getSyndicationLinks() as $format => $link ) {
  1359. # Use the page name for the title (accessed through $wgTitle since
  1360. # there's no other way). In principle, this could lead to issues
  1361. # with having the same name for different feeds corresponding to
  1362. # the same page, but we can't avoid that at this low a level.
  1363. $tags[] = $this->feedLink(
  1364. $format,
  1365. $link,
  1366. wfMsg( "page-{$format}-feed", $wgTitle->getPrefixedText() ) ); # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
  1367. }
  1368. # Recent changes feed should appear on every page (except recentchanges,
  1369. # that would be redundant). Put it after the per-page feed to avoid
  1370. # changing existing behavior. It's still available, probably via a
  1371. # menu in your browser. Some sites might have a different feed they'd
  1372. # like to promote instead of the RC feed (maybe like a "Recent New Articles"
  1373. # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
  1374. # If so, use it instead.
  1375. global $wgOverrideSiteFeed, $wgSitename, $wgFeedClasses;
  1376. $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
  1377. if ( $wgOverrideSiteFeed ) {
  1378. foreach ( $wgOverrideSiteFeed as $type => $feedUrl ) {
  1379. $tags[] = $this->feedLink (
  1380. $type,
  1381. htmlspecialchars( $feedUrl ),
  1382. wfMsg( "site-{$type}-feed", $wgSitename ) );
  1383. }
  1384. }
  1385. else if ( $wgTitle->getPrefixedText() != $rctitle->getPrefixedText() ) {
  1386. foreach( $wgFeedClasses as $format => $class ) {
  1387. $tags[] = $this->feedLink(
  1388. $format,
  1389. $rctitle->getLocalURL( "feed={$format}" ),
  1390. wfMsg( "site-{$format}-feed", $wgSitename ) ); # For grep: 'site-rss-feed', 'site-atom-feed'.
  1391. }
  1392. }
  1393. }
  1394. return implode( "\n\t\t", $tags ) . "\n";
  1395. }
  1396. /**
  1397. * Return URLs for each supported syndication format for this page.
  1398. * @return array associating format keys with URLs
  1399. */
  1400. public function getSyndicationLinks() {
  1401. global $wgTitle, $wgFeedClasses;
  1402. $links = array();
  1403. if( $this->isSyndicated() ) {
  1404. if( is_string( $this->getFeedAppendQuery() ) ) {
  1405. $appendQuery = "&" . $this->getFeedAppendQuery();
  1406. } else {
  1407. $appendQuery = "";
  1408. }
  1409. foreach( $wgFeedClasses as $format => $class ) {
  1410. $links[$format] = $wgTitle->getLocalUrl( "feed=$format{$appendQuery}" );
  1411. }
  1412. }
  1413. return $links;
  1414. }
  1415. /**
  1416. * Generate a <link rel/> for an RSS feed.
  1417. */
  1418. private function feedLink( $type, $url, $text ) {
  1419. return Xml::element( 'link', array(
  1420. 'rel' => 'alternate',
  1421. 'type' => "application/$type+xml",
  1422. 'title' => $text,
  1423. 'href' => $url ) );
  1424. }
  1425. /**
  1426. * Add a local or specified stylesheet, with the given media options.
  1427. * Meant primarily for internal use...
  1428. *
  1429. * @param $media -- to specify a media type, 'screen', 'printable', 'handheld' or any.
  1430. * @param $conditional -- for IE conditional comments, specifying an IE version
  1431. * @param $dir -- set to 'rtl' or 'ltr' for direction-specific sheets
  1432. */
  1433. public function addStyle( $style, $media='', $condition='', $dir='' ) {
  1434. $options = array();
  1435. if( $media )
  1436. $options['media'] = $media;
  1437. if( $condition )
  1438. $options['condition'] = $condition;
  1439. if( $dir )
  1440. $options['dir'] = $dir;
  1441. $this->styles[$style] = $options;
  1442. }
  1443. /**
  1444. * Build a set of <link>s for the stylesheets specified in the $this->styles array.
  1445. * These will be applied to various media & IE conditionals.
  1446. */
  1447. public function buildCssLinks() {
  1448. $links = array();
  1449. foreach( $this->styles as $file => $options ) {
  1450. $link = $this->styleLink( $file, $options );
  1451. if( $link )
  1452. $links[] = $link;
  1453. }
  1454. return implode( "\n\t\t", $links );
  1455. }
  1456. protected function styleLink( $style, $options ) {
  1457. global $wgRequest;
  1458. if( isset( $options['dir'] ) ) {
  1459. global $wgContLang;
  1460. $siteDir = $wgContLang->isRTL() ? 'rtl' : 'ltr';
  1461. if( $siteDir != $options['dir'] )
  1462. return '';
  1463. }
  1464. if( isset( $options['media'] ) ) {
  1465. $media = $this->transformCssMedia( $options['media'] );
  1466. if( is_null( $media ) ) {
  1467. return '';
  1468. }
  1469. } else {
  1470. $media = '';
  1471. }
  1472. if( substr( $style, 0, 1 ) == '/' ||
  1473. substr( $style, 0, 5 ) == 'http:' ||
  1474. substr( $style, 0, 6 ) == 'https:' ) {
  1475. $url = $style;
  1476. } else {
  1477. global $wgStylePath, $wgStyleVersion;
  1478. $url = $wgStylePath . '/' . $style . '?' . $wgStyleVersion;
  1479. }
  1480. $attribs = array(
  1481. 'rel' => 'stylesheet',
  1482. 'href' => $url,
  1483. 'type' => 'text/css' );
  1484. if( $media ) {
  1485. $attribs['media'] = $media;
  1486. }
  1487. $link = Xml::element( 'link', $attribs );
  1488. if( isset( $options['condition'] ) ) {
  1489. $condition = htmlspecialchars( $options['condition'] );
  1490. $link = "<!--[if $condition]>$link<![endif]-->";
  1491. }
  1492. return $link;
  1493. }
  1494. function transformCssMedia( $media ) {
  1495. global $wgRequest, $wgHandheldForIPhone;
  1496. // Switch in on-screen display for media testing
  1497. $switches = array(
  1498. 'printable' => 'print',
  1499. 'handheld' => 'handheld',
  1500. );
  1501. foreach( $switches as $switch => $targetMedia ) {
  1502. if( $wgRequest->getBool( $switch ) ) {
  1503. if( $media == $targetMedia ) {
  1504. $media = '';
  1505. } elseif( $media == 'screen' ) {
  1506. return null;
  1507. }
  1508. }
  1509. }
  1510. // Expand longer media queries as iPhone doesn't grok 'handheld'
  1511. if( $wgHandheldForIPhone ) {
  1512. $mediaAliases = array(
  1513. 'screen' => 'screen and (min-device-width: 481px)',
  1514. 'handheld' => 'handheld, only screen and (max-device-width: 480px)',
  1515. );
  1516. if( isset( $mediaAliases[$media] ) ) {
  1517. $media = $mediaAliases[$media];
  1518. }
  1519. }
  1520. return $media;
  1521. }
  1522. /**
  1523. * Turn off regular page output and return an error reponse
  1524. * for when rate limiting has triggered.
  1525. */
  1526. public function rateLimited() {
  1527. global $wgTitle;
  1528. $this->setPageTitle(wfMsg('actionthrottled'));
  1529. $this->setRobotPolicy( 'noindex,follow' );
  1530. $this->setArticleRelated( false );
  1531. $this->enableClientCache( false );
  1532. $this->mRedirect = '';
  1533. $this->clearHTML();
  1534. $this->setStatusCode(503);
  1535. $this->addWikiMsg( 'actionthrottledtext' );
  1536. $this->returnToMain( null, $wgTitle );
  1537. }
  1538. /**
  1539. * Show an "add new section" link?
  1540. *
  1541. * @return bool
  1542. */
  1543. public function showNewSectionLink() {
  1544. return $this->mNewSectionLink;
  1545. }
  1546. /**
  1547. * Forcibly hide the new section link?
  1548. *
  1549. * @return bool
  1550. */
  1551. public function forceHideNewSectionLink() {
  1552. return $this->mHideNewSectionLink;
  1553. }
  1554. /**
  1555. * Show a warning about slave lag
  1556. *
  1557. * If the lag is higher than $wgSlaveLagCritical seconds,
  1558. * then the warning is a bit more obvious. If the lag is
  1559. * lower than $wgSlaveLagWarning, then no warning is shown.
  1560. *
  1561. * @param int $lag Slave lag
  1562. */
  1563. public function showLagWarning( $lag ) {
  1564. global $wgSlaveLagWarning, $wgSlaveLagCritical;
  1565. if( $lag >= $wgSlaveLagWarning ) {
  1566. $message = $lag < $wgSlaveLagCritical
  1567. ? 'lag-warn-normal'
  1568. : 'lag-warn-high';
  1569. $warning = wfMsgExt( $message, 'parse', $lag );
  1570. $this->addHTML( "<div class=\"mw-{$message}\">\n{$warning}\n</div>\n" );
  1571. }
  1572. }
  1573. /**
  1574. * Add a wikitext-formatted message to the output.
  1575. * This is equivalent to:
  1576. *
  1577. * $wgOut->addWikiText( wfMsgNoTrans( ... ) )
  1578. */
  1579. public function addWikiMsg( /*...*/ ) {
  1580. $args = func_get_args();
  1581. $name = array_shift( $args );
  1582. $this->addWikiMsgArray( $name, $args );
  1583. }
  1584. /**
  1585. * Add a wikitext-formatted message to the output.
  1586. * Like addWikiMsg() except the parameters are taken as an array
  1587. * instead of a variable argument list.
  1588. *
  1589. * $options is passed through to wfMsgExt(), see that function for details.
  1590. */
  1591. public function addWikiMsgArray( $name, $args, $options = array() ) {
  1592. $options[] = 'parse';
  1593. $text = wfMsgExt( $name, $options, $args );
  1594. $this->addHTML( $text );
  1595. }
  1596. /**
  1597. * This function takes a number of message/argument specifications, wraps them in
  1598. * some overall structure, and then parses the result and adds it to the output.
  1599. *
  1600. * In the $wrap, $1 is replaced with the first message, $2 with the second, and so
  1601. * on. The subsequent arguments may either be strings, in which case they are the
  1602. * message names, or an arrays, in which case the first element is the message name,
  1603. * and subsequent elements are the parameters to that message.
  1604. *
  1605. * The special named parameter 'options' in a message specification array is passed
  1606. * through to the $options parameter of wfMsgExt().
  1607. *
  1608. * Don't use this for messages that are not in users interface language.
  1609. *
  1610. * For example:
  1611. *
  1612. * $wgOut->wrapWikiMsg( '<div class="error">$1</div>', 'some-error' );
  1613. *
  1614. * Is equivalent to:
  1615. *
  1616. * $wgOut->addWikiText( '<div class="error">' . wfMsgNoTrans( 'some-error' ) . '</div>' );
  1617. */
  1618. public function wrapWikiMsg( $wrap /*, ...*/ ) {
  1619. $msgSpecs = func_get_args();
  1620. array_shift( $msgSpecs );
  1621. $msgSpecs = array_values( $msgSpecs );
  1622. $s = $wrap;
  1623. foreach ( $msgSpecs as $n => $spec ) {
  1624. $options = array();
  1625. if ( is_array( $spec ) ) {
  1626. $args = $spec;
  1627. $name = array_shift( $args );
  1628. if ( isset( $args['options'] ) ) {
  1629. $options = $args['options'];
  1630. unset( $args['options'] );
  1631. }
  1632. } else {
  1633. $args = array();
  1634. $name = $spec;
  1635. }
  1636. $s = str_replace( '$' . ( $n + 1 ), wfMsgExt( $name, $options, $args ), $s );
  1637. }
  1638. $this->addHTML( $this->parse( $s, /*linestart*/true, /*uilang*/true ) );
  1639. }
  1640. }