MediaWiki  master
OutputPage.php
Go to the documentation of this file.
1 <?php
30 
46 class OutputPage extends ContextSource {
48  protected $mMetatags = [];
49 
51  protected $mLinktags = [];
52 
54  protected $mCanonicalUrl = false;
55 
59  private $mPageTitle = '';
60 
68  private $displayTitle;
69 
71  private $cacheIsFinal = false;
72 
77  public $mBodytext = '';
78 
80  private $mHTMLtitle = '';
81 
86  private $mIsArticle = false;
87 
89  private $mIsArticleRelated = true;
90 
92  private $mHasCopyright = false;
93 
98  private $mPrintable = false;
99 
104  private $mSubtitle = [];
105 
107  public $mRedirect = '';
108 
110  protected $mStatusCode;
111 
116  protected $mLastModified = '';
117 
119  protected $mCategoryLinks = [];
120 
122  protected $mCategories = [
123  'hidden' => [],
124  'normal' => [],
125  ];
126 
128  protected $mIndicators = [];
129 
131  private $mLanguageLinks = [];
132 
139  private $mScripts = '';
140 
142  protected $mInlineStyles = '';
143 
148  public $mPageLinkTitle = '';
149 
151  protected $mHeadItems = [];
152 
154  protected $mAdditionalBodyClasses = [];
155 
157  protected $mModules = [];
158 
160  protected $mModuleStyles = [];
161 
163  protected $mResourceLoader;
164 
166  private $rlClient;
167 
170 
173 
175  protected $mJsConfigVars = [];
176 
178  protected $mTemplateIds = [];
179 
181  protected $mImageTimeKeys = [];
182 
184  public $mRedirectCode = '';
185 
186  protected $mFeedLinksAppendQuery = null;
187 
193  protected $mAllowedModules = [
194  ResourceLoaderModule::TYPE_COMBINED => ResourceLoaderModule::ORIGIN_ALL,
195  ];
196 
198  protected $mDoNothing = false;
199 
200  // Parser related.
201 
203  protected $mContainsNewMagic = 0;
204 
209  protected $mParserOptions = null;
210 
216  private $mFeedLinks = [];
217 
218  // Gwicke work on squid caching? Roughly from 2003.
219  protected $mEnableClientCache = true;
220 
222  private $mArticleBodyOnly = false;
223 
225  protected $mNewSectionLink = false;
226 
228  protected $mHideNewSectionLink = false;
229 
235  public $mNoGallery = false;
236 
238  protected $mCdnMaxage = 0;
240  protected $mCdnMaxageLimit = INF;
241 
247  protected $mPreventClickjacking = true;
248 
250  private $mRevisionId = null;
251 
253  private $mRevisionTimestamp = null;
254 
256  protected $mFileVersion = null;
257 
266  protected $styles = [];
267 
268  private $mIndexPolicy = 'index';
269  private $mFollowPolicy = 'follow';
270 
276  private $mVaryHeader = [
277  'Accept-Encoding' => null,
278  ];
279 
286  private $mRedirectedFrom = null;
287 
291  private $mProperties = [];
292 
296  private $mTarget = null;
297 
301  private $mEnableTOC = false;
302 
306  private $copyrightUrl;
307 
309  private $limitReportJSData = [];
310 
312  private $contentOverrides = [];
313 
316 
320  private $mLinkHeader = [];
321 
325  private $CSP;
326 
330  private static $cacheVaryCookies = null;
331 
338  public function __construct( IContextSource $context ) {
339  $this->setContext( $context );
340  $this->CSP = new ContentSecurityPolicy(
341  $context->getRequest()->response(),
342  $context->getConfig()
343  );
344  }
345 
352  public function redirect( $url, $responsecode = '302' ) {
353  # Strip newlines as a paranoia check for header injection in PHP<5.1.2
354  $this->mRedirect = str_replace( "\n", '', $url );
355  $this->mRedirectCode = $responsecode;
356  }
357 
363  public function getRedirect() {
364  return $this->mRedirect;
365  }
366 
375  public function setCopyrightUrl( $url ) {
376  $this->copyrightUrl = $url;
377  }
378 
384  public function setStatusCode( $statusCode ) {
385  $this->mStatusCode = $statusCode;
386  }
387 
395  public function addMeta( $name, $val ) {
396  $this->mMetatags[] = [ $name, $val ];
397  }
398 
405  public function getMetaTags() {
406  return $this->mMetatags;
407  }
408 
416  public function addLink( array $linkarr ) {
417  $this->mLinktags[] = $linkarr;
418  }
419 
426  public function getLinkTags() {
427  return $this->mLinktags;
428  }
429 
435  public function setCanonicalUrl( $url ) {
436  $this->mCanonicalUrl = $url;
437  }
438 
446  public function getCanonicalUrl() {
447  return $this->mCanonicalUrl;
448  }
449 
457  public function addScript( $script ) {
458  $this->mScripts .= $script;
459  }
460 
469  public function addScriptFile( $file, $unused = null ) {
470  $this->addScript( Html::linkedScript( $file, $this->CSP->getNonce() ) );
471  }
472 
479  public function addInlineScript( $script ) {
480  $this->mScripts .= Html::inlineScript( "\n$script\n", $this->CSP->getNonce() ) . "\n";
481  }
482 
491  protected function filterModules( array $modules, $position = null,
492  $type = ResourceLoaderModule::TYPE_COMBINED
493  ) {
495  $filteredModules = [];
496  foreach ( $modules as $val ) {
497  $module = $resourceLoader->getModule( $val );
498  if ( $module instanceof ResourceLoaderModule
499  && $module->getOrigin() <= $this->getAllowedModules( $type )
500  ) {
501  if ( $this->mTarget && !in_array( $this->mTarget, $module->getTargets() ) ) {
502  $this->warnModuleTargetFilter( $module->getName() );
503  continue;
504  }
505  $filteredModules[] = $val;
506  }
507  }
508  return $filteredModules;
509  }
510 
511  private function warnModuleTargetFilter( $moduleName ) {
512  static $warnings = [];
513  if ( isset( $warnings[$this->mTarget][$moduleName] ) ) {
514  return;
515  }
516  $warnings[$this->mTarget][$moduleName] = true;
517  $this->getResourceLoader()->getLogger()->debug(
518  'Module "{module}" not loadable on target "{target}".',
519  [
520  'module' => $moduleName,
521  'target' => $this->mTarget,
522  ]
523  );
524  }
525 
535  public function getModules( $filter = false, $position = null, $param = 'mModules',
536  $type = ResourceLoaderModule::TYPE_COMBINED
537  ) {
538  $modules = array_values( array_unique( $this->$param ) );
539  return $filter
540  ? $this->filterModules( $modules, null, $type )
541  : $modules;
542  }
543 
549  public function addModules( $modules ) {
550  $this->mModules = array_merge( $this->mModules, (array)$modules );
551  }
552 
560  public function getModuleStyles( $filter = false, $position = null ) {
561  return $this->getModules( $filter, null, 'mModuleStyles',
562  ResourceLoaderModule::TYPE_STYLES
563  );
564  }
565 
575  public function addModuleStyles( $modules ) {
576  $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
577  }
578 
582  public function getTarget() {
583  return $this->mTarget;
584  }
585 
591  public function setTarget( $target ) {
592  $this->mTarget = $target;
593  }
594 
602  public function addContentOverride( LinkTarget $target, Content $content ) {
603  if ( !$this->contentOverrides ) {
604  // Register a callback for $this->contentOverrides on the first call
605  $this->addContentOverrideCallback( function ( LinkTarget $target ) {
606  $key = $target->getNamespace() . ':' . $target->getDBkey();
607  return $this->contentOverrides[$key] ?? null;
608  } );
609  }
610 
611  $key = $target->getNamespace() . ':' . $target->getDBkey();
612  $this->contentOverrides[$key] = $content;
613  }
614 
622  public function addContentOverrideCallback( callable $callback ) {
623  $this->contentOverrideCallbacks[] = $callback;
624  }
625 
631  public function getHeadItemsArray() {
632  return $this->mHeadItems;
633  }
634 
647  public function addHeadItem( $name, $value ) {
648  $this->mHeadItems[$name] = $value;
649  }
650 
657  public function addHeadItems( $values ) {
658  $this->mHeadItems = array_merge( $this->mHeadItems, (array)$values );
659  }
660 
667  public function hasHeadItem( $name ) {
668  return isset( $this->mHeadItems[$name] );
669  }
670 
677  public function addBodyClasses( $classes ) {
678  $this->mAdditionalBodyClasses = array_merge( $this->mAdditionalBodyClasses, (array)$classes );
679  }
680 
688  public function setArticleBodyOnly( $only ) {
689  $this->mArticleBodyOnly = $only;
690  }
691 
697  public function getArticleBodyOnly() {
699  }
700 
708  public function setProperty( $name, $value ) {
709  $this->mProperties[$name] = $value;
710  }
711 
719  public function getProperty( $name ) {
720  return $this->mProperties[$name] ?? null;
721  }
722 
734  public function checkLastModified( $timestamp ) {
735  if ( !$timestamp || $timestamp == '19700101000000' ) {
736  wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
737  return false;
738  }
739  $config = $this->getConfig();
740  if ( !$config->get( 'CachePages' ) ) {
741  wfDebug( __METHOD__ . ": CACHE DISABLED\n" );
742  return false;
743  }
744 
745  $timestamp = wfTimestamp( TS_MW, $timestamp );
746  $modifiedTimes = [
747  'page' => $timestamp,
748  'user' => $this->getUser()->getTouched(),
749  'epoch' => $config->get( 'CacheEpoch' )
750  ];
751  if ( $config->get( 'UseCdn' ) ) {
752  $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, $this->getCdnCacheEpoch(
753  time(),
754  $config->get( 'CdnMaxAge' )
755  ) );
756  }
757  Hooks::run( 'OutputPageCheckLastModified', [ &$modifiedTimes, $this ] );
758 
759  $maxModified = max( $modifiedTimes );
760  $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
761 
762  $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' );
763  if ( $clientHeader === false ) {
764  wfDebug( __METHOD__ . ": client did not send If-Modified-Since header", 'private' );
765  return false;
766  }
767 
768  # IE sends sizes after the date like this:
769  # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
770  # this breaks strtotime().
771  $clientHeader = preg_replace( '/;.*$/', '', $clientHeader );
772 
773  Wikimedia\suppressWarnings(); // E_STRICT system time warnings
774  $clientHeaderTime = strtotime( $clientHeader );
775  Wikimedia\restoreWarnings();
776  if ( !$clientHeaderTime ) {
777  wfDebug( __METHOD__
778  . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
779  return false;
780  }
781  $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
782 
783  # Make debug info
784  $info = '';
785  foreach ( $modifiedTimes as $name => $value ) {
786  if ( $info !== '' ) {
787  $info .= ', ';
788  }
789  $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
790  }
791 
792  wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
793  wfTimestamp( TS_ISO_8601, $clientHeaderTime ), 'private' );
794  wfDebug( __METHOD__ . ": effective Last-Modified: " .
795  wfTimestamp( TS_ISO_8601, $maxModified ), 'private' );
796  if ( $clientHeaderTime < $maxModified ) {
797  wfDebug( __METHOD__ . ": STALE, $info", 'private' );
798  return false;
799  }
800 
801  # Not modified
802  # Give a 304 Not Modified response code and disable body output
803  wfDebug( __METHOD__ . ": NOT MODIFIED, $info", 'private' );
804  ini_set( 'zlib.output_compression', 0 );
805  $this->getRequest()->response()->statusHeader( 304 );
806  $this->sendCacheControl();
807  $this->disable();
808 
809  // Don't output a compressed blob when using ob_gzhandler;
810  // it's technically against HTTP spec and seems to confuse
811  // Firefox when the response gets split over two packets.
813 
814  return true;
815  }
816 
822  private function getCdnCacheEpoch( $reqTime, $maxAge ) {
823  // Ensure Last-Modified is never more than $wgCdnMaxAge in the past,
824  // because even if the wiki page content hasn't changed since, static
825  // resources may have changed (skin HTML, interface messages, urls, etc.)
826  // and must roll-over in a timely manner (T46570)
827  return $reqTime - $maxAge;
828  }
829 
836  public function setLastModified( $timestamp ) {
837  $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp );
838  }
839 
848  public function setRobotPolicy( $policy ) {
849  $policy = Article::formatRobotPolicy( $policy );
850 
851  if ( isset( $policy['index'] ) ) {
852  $this->setIndexPolicy( $policy['index'] );
853  }
854  if ( isset( $policy['follow'] ) ) {
855  $this->setFollowPolicy( $policy['follow'] );
856  }
857  }
858 
866  public function setIndexPolicy( $policy ) {
867  $policy = trim( $policy );
868  if ( in_array( $policy, [ 'index', 'noindex' ] ) ) {
869  $this->mIndexPolicy = $policy;
870  }
871  }
872 
880  public function setFollowPolicy( $policy ) {
881  $policy = trim( $policy );
882  if ( in_array( $policy, [ 'follow', 'nofollow' ] ) ) {
883  $this->mFollowPolicy = $policy;
884  }
885  }
886 
893  public function setHTMLTitle( $name ) {
894  if ( $name instanceof Message ) {
895  $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
896  } else {
897  $this->mHTMLtitle = $name;
898  }
899  }
900 
906  public function getHTMLTitle() {
907  return $this->mHTMLtitle;
908  }
909 
915  public function setRedirectedFrom( $t ) {
916  $this->mRedirectedFrom = $t;
917  }
918 
931  public function setPageTitle( $name ) {
932  if ( $name instanceof Message ) {
933  $name = $name->setContext( $this->getContext() )->text();
934  }
935 
936  # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
937  # but leave "<i>foobar</i>" alone
939  $this->mPageTitle = $nameWithTags;
940 
941  # change "<i>foo&amp;bar</i>" to "foo&bar"
942  $this->setHTMLTitle(
943  $this->msg( 'pagetitle' )->plaintextParams( Sanitizer::stripAllTags( $nameWithTags ) )
944  ->inContentLanguage()
945  );
946  }
947 
953  public function getPageTitle() {
954  return $this->mPageTitle;
955  }
956 
964  public function setDisplayTitle( $html ) {
965  $this->displayTitle = $html;
966  }
967 
976  public function getDisplayTitle() {
977  $html = $this->displayTitle;
978  if ( $html === null ) {
979  $html = $this->getTitle()->getPrefixedText();
980  }
981 
983  }
984 
991  public function getUnprefixedDisplayTitle() {
992  $text = $this->getDisplayTitle();
993  $nsPrefix = $this->getTitle()->getNsText() . ':';
994  $prefix = preg_quote( $nsPrefix, '/' );
995 
996  return preg_replace( "/^$prefix/i", '', $text );
997  }
998 
1004  public function setTitle( Title $t ) {
1005  // @phan-suppress-next-next-line PhanUndeclaredMethod
1006  // @fixme Not all implementations of IContextSource have this method!
1007  $this->getContext()->setTitle( $t );
1008  }
1009 
1015  public function setSubtitle( $str ) {
1016  $this->clearSubtitle();
1017  $this->addSubtitle( $str );
1018  }
1019 
1025  public function addSubtitle( $str ) {
1026  if ( $str instanceof Message ) {
1027  $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
1028  } else {
1029  $this->mSubtitle[] = $str;
1030  }
1031  }
1032 
1041  public static function buildBacklinkSubtitle( Title $title, $query = [] ) {
1042  if ( $title->isRedirect() ) {
1043  $query['redirect'] = 'no';
1044  }
1045  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1046  return wfMessage( 'backlinksubtitle' )
1047  ->rawParams( $linkRenderer->makeLink( $title, null, [], $query ) );
1048  }
1049 
1056  public function addBacklinkSubtitle( Title $title, $query = [] ) {
1057  $this->addSubtitle( self::buildBacklinkSubtitle( $title, $query ) );
1058  }
1059 
1063  public function clearSubtitle() {
1064  $this->mSubtitle = [];
1065  }
1066 
1072  public function getSubtitle() {
1073  return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
1074  }
1075 
1080  public function setPrintable() {
1081  $this->mPrintable = true;
1082  }
1083 
1089  public function isPrintable() {
1090  return $this->mPrintable;
1091  }
1092 
1096  public function disable() {
1097  $this->mDoNothing = true;
1098  }
1099 
1105  public function isDisabled() {
1106  return $this->mDoNothing;
1107  }
1108 
1114  public function showNewSectionLink() {
1115  return $this->mNewSectionLink;
1116  }
1117 
1123  public function forceHideNewSectionLink() {
1125  }
1126 
1135  public function setSyndicated( $show = true ) {
1136  if ( $show ) {
1137  $this->setFeedAppendQuery( false );
1138  } else {
1139  $this->mFeedLinks = [];
1140  }
1141  }
1142 
1149  protected function getAdvertisedFeedTypes() {
1150  if ( $this->getConfig()->get( 'Feed' ) ) {
1151  return $this->getConfig()->get( 'AdvertisedFeedTypes' );
1152  } else {
1153  return [];
1154  }
1155  }
1156 
1166  public function setFeedAppendQuery( $val ) {
1167  $this->mFeedLinks = [];
1168 
1169  foreach ( $this->getAdvertisedFeedTypes() as $type ) {
1170  $query = "feed=$type";
1171  if ( is_string( $val ) ) {
1172  $query .= '&' . $val;
1173  }
1174  $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
1175  }
1176  }
1177 
1184  public function addFeedLink( $format, $href ) {
1185  if ( in_array( $format, $this->getAdvertisedFeedTypes() ) ) {
1186  $this->mFeedLinks[$format] = $href;
1187  }
1188  }
1189 
1194  public function isSyndicated() {
1195  return count( $this->mFeedLinks ) > 0;
1196  }
1197 
1202  public function getSyndicationLinks() {
1203  return $this->mFeedLinks;
1204  }
1205 
1211  public function getFeedAppendQuery() {
1213  }
1214 
1222  public function setArticleFlag( $newVal ) {
1223  $this->mIsArticle = $newVal;
1224  if ( $newVal ) {
1225  $this->mIsArticleRelated = $newVal;
1226  }
1227  }
1228 
1235  public function isArticle() {
1236  return $this->mIsArticle;
1237  }
1238 
1245  public function setArticleRelated( $newVal ) {
1246  $this->mIsArticleRelated = $newVal;
1247  if ( !$newVal ) {
1248  $this->mIsArticle = false;
1249  }
1250  }
1251 
1257  public function isArticleRelated() {
1258  return $this->mIsArticleRelated;
1259  }
1260 
1266  public function setCopyright( $hasCopyright ) {
1267  $this->mHasCopyright = $hasCopyright;
1268  }
1269 
1279  public function showsCopyright() {
1280  return $this->isArticle() || $this->mHasCopyright;
1281  }
1282 
1289  public function addLanguageLinks( array $newLinkArray ) {
1290  $this->mLanguageLinks = array_merge( $this->mLanguageLinks, $newLinkArray );
1291  }
1292 
1299  public function setLanguageLinks( array $newLinkArray ) {
1300  $this->mLanguageLinks = $newLinkArray;
1301  }
1302 
1308  public function getLanguageLinks() {
1309  return $this->mLanguageLinks;
1310  }
1311 
1317  public function addCategoryLinks( array $categories ) {
1318  if ( !$categories ) {
1319  return;
1320  }
1321 
1322  $res = $this->addCategoryLinksToLBAndGetResult( $categories );
1323 
1324  # Set all the values to 'normal'.
1325  $categories = array_fill_keys( array_keys( $categories ), 'normal' );
1326 
1327  # Mark hidden categories
1328  foreach ( $res as $row ) {
1329  if ( isset( $row->pp_value ) ) {
1330  $categories[$row->page_title] = 'hidden';
1331  }
1332  }
1333 
1334  // Avoid PHP 7.1 warning of passing $this by reference
1335  $outputPage = $this;
1336  # Add the remaining categories to the skin
1337  if ( Hooks::run(
1338  'OutputPageMakeCategoryLinks',
1339  [ &$outputPage, $categories, &$this->mCategoryLinks ] )
1340  ) {
1341  $services = MediaWikiServices::getInstance();
1342  $linkRenderer = $services->getLinkRenderer();
1343  foreach ( $categories as $category => $type ) {
1344  // array keys will cast numeric category names to ints, so cast back to string
1345  $category = (string)$category;
1346  $origcategory = $category;
1347  $title = Title::makeTitleSafe( NS_CATEGORY, $category );
1348  if ( !$title ) {
1349  continue;
1350  }
1351  $services->getContentLanguage()->findVariantLink( $category, $title, true );
1352  if ( $category != $origcategory && array_key_exists( $category, $categories ) ) {
1353  continue;
1354  }
1355  $text = $services->getContentLanguage()->convertHtml( $title->getText() );
1356  $this->mCategories[$type][] = $title->getText();
1357  $this->mCategoryLinks[$type][] = $linkRenderer->makeLink( $title, new HtmlArmor( $text ) );
1358  }
1359  }
1360  }
1361 
1366  protected function addCategoryLinksToLBAndGetResult( array $categories ) {
1367  # Add the links to a LinkBatch
1368  $arr = [ NS_CATEGORY => $categories ];
1369  $lb = new LinkBatch;
1370  $lb->setArray( $arr );
1371 
1372  # Fetch existence plus the hiddencat property
1373  $dbr = wfGetDB( DB_REPLICA );
1374  $fields = array_merge(
1376  [ 'page_namespace', 'page_title', 'pp_value' ]
1377  );
1378 
1379  $res = $dbr->select( [ 'page', 'page_props' ],
1380  $fields,
1381  $lb->constructSet( 'page', $dbr ),
1382  __METHOD__,
1383  [],
1384  [ 'page_props' => [ 'LEFT JOIN', [
1385  'pp_propname' => 'hiddencat',
1386  'pp_page = page_id'
1387  ] ] ]
1388  );
1389 
1390  # Add the results to the link cache
1391  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1392  $lb->addResultToCache( $linkCache, $res );
1393 
1394  return $res;
1395  }
1396 
1402  public function setCategoryLinks( array $categories ) {
1403  $this->mCategoryLinks = [];
1404  $this->addCategoryLinks( $categories );
1405  }
1406 
1415  public function getCategoryLinks() {
1416  return $this->mCategoryLinks;
1417  }
1418 
1428  public function getCategories( $type = 'all' ) {
1429  if ( $type === 'all' ) {
1430  $allCategories = [];
1431  foreach ( $this->mCategories as $categories ) {
1432  $allCategories = array_merge( $allCategories, $categories );
1433  }
1434  return $allCategories;
1435  }
1436  if ( !isset( $this->mCategories[$type] ) ) {
1437  throw new InvalidArgumentException( 'Invalid category type given: ' . $type );
1438  }
1439  return $this->mCategories[$type];
1440  }
1441 
1451  public function setIndicators( array $indicators ) {
1452  $this->mIndicators = $indicators + $this->mIndicators;
1453  // Keep ordered by key
1454  ksort( $this->mIndicators );
1455  }
1456 
1465  public function getIndicators() {
1466  return $this->mIndicators;
1467  }
1468 
1477  public function addHelpLink( $to, $overrideBaseUrl = false ) {
1478  $this->addModuleStyles( 'mediawiki.helplink' );
1479  $text = $this->msg( 'helppage-top-gethelp' )->escaped();
1480 
1481  if ( $overrideBaseUrl ) {
1482  $helpUrl = $to;
1483  } else {
1484  $toUrlencoded = wfUrlencode( str_replace( ' ', '_', $to ) );
1485  $helpUrl = "https://www.mediawiki.org/wiki/Special:MyLanguage/$toUrlencoded";
1486  }
1487 
1488  $link = Html::rawElement(
1489  'a',
1490  [
1491  'href' => $helpUrl,
1492  'target' => '_blank',
1493  'class' => 'mw-helplink',
1494  ],
1495  $text
1496  );
1497 
1498  $this->setIndicators( [ 'mw-helplink' => $link ] );
1499  }
1500 
1509  public function disallowUserJs() {
1510  $this->reduceAllowedModules(
1511  ResourceLoaderModule::TYPE_SCRIPTS,
1512  ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
1513  );
1514 
1515  // Site-wide styles are controlled by a config setting, see T73621
1516  // for background on why. User styles are never allowed.
1517  if ( $this->getConfig()->get( 'AllowSiteCSSOnRestrictedPages' ) ) {
1518  $styleOrigin = ResourceLoaderModule::ORIGIN_USER_SITEWIDE;
1519  } else {
1520  $styleOrigin = ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL;
1521  }
1522  $this->reduceAllowedModules(
1523  ResourceLoaderModule::TYPE_STYLES,
1524  $styleOrigin
1525  );
1526  }
1527 
1534  public function getAllowedModules( $type ) {
1535  if ( $type == ResourceLoaderModule::TYPE_COMBINED ) {
1536  return min( array_values( $this->mAllowedModules ) );
1537  } else {
1538  return $this->mAllowedModules[$type] ?? ResourceLoaderModule::ORIGIN_ALL;
1539  }
1540  }
1541 
1551  public function reduceAllowedModules( $type, $level ) {
1552  $this->mAllowedModules[$type] = min( $this->getAllowedModules( $type ), $level );
1553  }
1554 
1560  public function prependHTML( $text ) {
1561  $this->mBodytext = $text . $this->mBodytext;
1562  }
1563 
1569  public function addHTML( $text ) {
1570  $this->mBodytext .= $text;
1571  }
1572 
1582  public function addElement( $element, array $attribs = [], $contents = '' ) {
1583  $this->addHTML( Html::element( $element, $attribs, $contents ) );
1584  }
1585 
1589  public function clearHTML() {
1590  $this->mBodytext = '';
1591  }
1592 
1598  public function getHTML() {
1599  return $this->mBodytext;
1600  }
1601 
1610  public function parserOptions( $options = null ) {
1611  if ( $options !== null ) {
1612  wfDeprecated( __METHOD__ . ' with non-null $options', '1.31' );
1613  }
1614 
1615  if ( $options !== null && !empty( $options->isBogus ) ) {
1616  // Someone is trying to set a bogus pre-$wgUser PO. Check if it has
1617  // been changed somehow, and keep it if so.
1618  $anonPO = ParserOptions::newFromAnon();
1619  $anonPO->setAllowUnsafeRawHtml( false );
1620  if ( !$options->matches( $anonPO ) ) {
1621  wfLogWarning( __METHOD__ . ': Setting a changed bogus ParserOptions: ' . wfGetAllCallers( 5 ) );
1622  $options->isBogus = false;
1623  }
1624  }
1625 
1626  if ( !$this->mParserOptions ) {
1627  if ( !$this->getUser()->isSafeToLoad() ) {
1628  // $wgUser isn't unstubbable yet, so don't try to get a
1629  // ParserOptions for it. And don't cache this ParserOptions
1630  // either.
1632  $po->setAllowUnsafeRawHtml( false );
1633  $po->isBogus = true;
1634  if ( $options !== null ) {
1635  $this->mParserOptions = empty( $options->isBogus ) ? $options : null;
1636  }
1637  return $po;
1638  }
1639 
1640  $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
1641  $this->mParserOptions->setAllowUnsafeRawHtml( false );
1642  }
1643 
1644  if ( $options !== null && !empty( $options->isBogus ) ) {
1645  // They're trying to restore the bogus pre-$wgUser PO. Do the right
1646  // thing.
1647  return wfSetVar( $this->mParserOptions, null, true );
1648  } else {
1649  return wfSetVar( $this->mParserOptions, $options );
1650  }
1651  }
1652 
1660  public function setRevisionId( $revid ) {
1661  $val = is_null( $revid ) ? null : intval( $revid );
1662  return wfSetVar( $this->mRevisionId, $val, true );
1663  }
1664 
1670  public function getRevisionId() {
1671  return $this->mRevisionId;
1672  }
1673 
1680  public function isRevisionCurrent() {
1681  return $this->mRevisionId == 0 || $this->mRevisionId == $this->getTitle()->getLatestRevID();
1682  }
1683 
1691  public function setRevisionTimestamp( $timestamp ) {
1692  return wfSetVar( $this->mRevisionTimestamp, $timestamp, true );
1693  }
1694 
1701  public function getRevisionTimestamp() {
1703  }
1704 
1711  public function setFileVersion( $file ) {
1712  $val = null;
1713  if ( $file instanceof File && $file->exists() ) {
1714  $val = [ 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ];
1715  }
1716  return wfSetVar( $this->mFileVersion, $val, true );
1717  }
1718 
1724  public function getFileVersion() {
1725  return $this->mFileVersion;
1726  }
1727 
1734  public function getTemplateIds() {
1735  return $this->mTemplateIds;
1736  }
1737 
1744  public function getFileSearchOptions() {
1745  return $this->mImageTimeKeys;
1746  }
1747 
1764  public function addWikiTextAsInterface(
1765  $text, $linestart = true, Title $title = null
1766  ) {
1767  if ( $title === null ) {
1768  $title = $this->getTitle();
1769  }
1770  if ( !$title ) {
1771  throw new MWException( 'Title is null' );
1772  }
1773  $this->addWikiTextTitleInternal( $text, $title, $linestart, /*interface*/true );
1774  }
1775 
1789  public function wrapWikiTextAsInterface(
1790  $wrapperClass, $text
1791  ) {
1792  $this->addWikiTextTitleInternal(
1793  $text, $this->getTitle(),
1794  /*linestart*/true, /*interface*/true,
1795  $wrapperClass
1796  );
1797  }
1798 
1814  public function addWikiTextAsContent(
1815  $text, $linestart = true, Title $title = null
1816  ) {
1817  if ( $title === null ) {
1818  $title = $this->getTitle();
1819  }
1820  if ( !$title ) {
1821  throw new MWException( 'Title is null' );
1822  }
1823  $this->addWikiTextTitleInternal( $text, $title, $linestart, /*interface*/false );
1824  }
1825 
1838  private function addWikiTextTitleInternal(
1839  $text, Title $title, $linestart, $interface, $wrapperClass = null
1840  ) {
1841  $parserOutput = $this->parseInternal(
1842  $text, $title, $linestart, true, $interface, /*language*/null
1843  );
1844 
1845  $this->addParserOutput( $parserOutput, [
1846  'enableSectionEditLinks' => false,
1847  'wrapperDivClass' => $wrapperClass ?? '',
1848  ] );
1849  }
1850 
1859  public function addParserOutputMetadata( ParserOutput $parserOutput ) {
1860  $this->mLanguageLinks =
1861  array_merge( $this->mLanguageLinks, $parserOutput->getLanguageLinks() );
1862  $this->addCategoryLinks( $parserOutput->getCategories() );
1863  $this->setIndicators( $parserOutput->getIndicators() );
1864  $this->mNewSectionLink = $parserOutput->getNewSection();
1865  $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
1866 
1867  if ( !$parserOutput->isCacheable() ) {
1868  $this->enableClientCache( false );
1869  }
1870  $this->mNoGallery = $parserOutput->getNoGallery();
1871  $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
1872  $this->addModules( $parserOutput->getModules() );
1873  $this->addModuleStyles( $parserOutput->getModuleStyles() );
1874  $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1875  $this->mPreventClickjacking = $this->mPreventClickjacking
1876  || $parserOutput->preventClickjacking();
1877 
1878  // Template versioning...
1879  foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
1880  if ( isset( $this->mTemplateIds[$ns] ) ) {
1881  $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
1882  } else {
1883  $this->mTemplateIds[$ns] = $dbks;
1884  }
1885  }
1886  // File versioning...
1887  foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) {
1888  $this->mImageTimeKeys[$dbk] = $data;
1889  }
1890 
1891  // Hooks registered in the object
1892  $parserOutputHooks = $this->getConfig()->get( 'ParserOutputHooks' );
1893  foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
1894  list( $hookName, $data ) = $hookInfo;
1895  if ( isset( $parserOutputHooks[$hookName] ) ) {
1896  $parserOutputHooks[$hookName]( $this, $parserOutput, $data );
1897  }
1898  }
1899 
1900  // Enable OOUI if requested via ParserOutput
1901  if ( $parserOutput->getEnableOOUI() ) {
1902  $this->enableOOUI();
1903  }
1904 
1905  // Include parser limit report
1906  if ( !$this->limitReportJSData ) {
1907  $this->limitReportJSData = $parserOutput->getLimitReportJSData();
1908  }
1909 
1910  // Link flags are ignored for now, but may in the future be
1911  // used to mark individual language links.
1912  $linkFlags = [];
1913  // Avoid PHP 7.1 warning of passing $this by reference
1914  $outputPage = $this;
1915  Hooks::run( 'LanguageLinks', [ $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ] );
1916  Hooks::runWithoutAbort( 'OutputPageParserOutput', [ &$outputPage, $parserOutput ] );
1917 
1918  // This check must be after 'OutputPageParserOutput' runs in addParserOutputMetadata
1919  // so that extensions may modify ParserOutput to toggle TOC.
1920  // This cannot be moved to addParserOutputText because that is not
1921  // called by EditPage for Preview.
1922  if ( $parserOutput->getTOCHTML() ) {
1923  $this->mEnableTOC = true;
1924  }
1925  }
1926 
1935  public function addParserOutputContent( ParserOutput $parserOutput, $poOptions = [] ) {
1936  $this->addParserOutputText( $parserOutput, $poOptions );
1937 
1938  $this->addModules( $parserOutput->getModules() );
1939  $this->addModuleStyles( $parserOutput->getModuleStyles() );
1940 
1941  $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1942  }
1943 
1951  public function addParserOutputText( ParserOutput $parserOutput, $poOptions = [] ) {
1952  $text = $parserOutput->getText( $poOptions );
1953  // Avoid PHP 7.1 warning of passing $this by reference
1954  $outputPage = $this;
1955  Hooks::runWithoutAbort( 'OutputPageBeforeHTML', [ &$outputPage, &$text ] );
1956  $this->addHTML( $text );
1957  }
1958 
1965  public function addParserOutput( ParserOutput $parserOutput, $poOptions = [] ) {
1966  $this->addParserOutputMetadata( $parserOutput );
1967  $this->addParserOutputText( $parserOutput, $poOptions );
1968  }
1969 
1975  public function addTemplate( &$template ) {
1976  $this->addHTML( $template->getHTML() );
1977  }
1978 
1997  public function parse( $text, $linestart = true, $interface = false, $language = null ) {
1998  wfDeprecated( __METHOD__, '1.33' );
1999  return $this->parseInternal(
2000  $text, $this->getTitle(), $linestart, /*tidy*/false, $interface, $language
2001  )->getText( [
2002  'enableSectionEditLinks' => false,
2003  ] );
2004  }
2005 
2017  public function parseAsContent( $text, $linestart = true ) {
2018  return $this->parseInternal(
2019  $text, $this->getTitle(), $linestart, /*tidy*/true, /*interface*/false, /*language*/null
2020  )->getText( [
2021  'enableSectionEditLinks' => false,
2022  'wrapperDivClass' => ''
2023  ] );
2024  }
2025 
2038  public function parseAsInterface( $text, $linestart = true ) {
2039  return $this->parseInternal(
2040  $text, $this->getTitle(), $linestart, /*tidy*/true, /*interface*/true, /*language*/null
2041  )->getText( [
2042  'enableSectionEditLinks' => false,
2043  'wrapperDivClass' => ''
2044  ] );
2045  }
2046 
2061  public function parseInlineAsInterface( $text, $linestart = true ) {
2063  $this->parseAsInterface( $text, $linestart )
2064  );
2065  }
2066 
2080  public function parseInline( $text, $linestart = true, $interface = false ) {
2081  wfDeprecated( __METHOD__, '1.33' );
2082  $parsed = $this->parseInternal(
2083  $text, $this->getTitle(), $linestart, /*tidy*/false, $interface, /*language*/null
2084  )->getText( [
2085  'enableSectionEditLinks' => false,
2086  'wrapperDivClass' => '', /* no wrapper div */
2087  ] );
2088  return Parser::stripOuterParagraph( $parsed );
2089  }
2090 
2105  private function parseInternal( $text, $title, $linestart, $tidy, $interface, $language ) {
2106  if ( is_null( $title ) ) {
2107  throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
2108  }
2109 
2110  $popts = $this->parserOptions();
2111  $oldTidy = $popts->setTidy( $tidy );
2112  $oldInterface = $popts->setInterfaceMessage( (bool)$interface );
2113 
2114  if ( $language !== null ) {
2115  $oldLang = $popts->setTargetLanguage( $language );
2116  }
2117 
2118  $parserOutput = MediaWikiServices::getInstance()->getParser()->getFreshParser()->parse(
2119  $text, $title, $popts,
2120  $linestart, true, $this->mRevisionId
2121  );
2122 
2123  $popts->setTidy( $oldTidy );
2124  $popts->setInterfaceMessage( $oldInterface );
2125 
2126  if ( $language !== null ) {
2127  $popts->setTargetLanguage( $oldLang );
2128  }
2129 
2130  return $parserOutput;
2131  }
2132 
2138  public function setCdnMaxage( $maxage ) {
2139  $this->mCdnMaxage = min( $maxage, $this->mCdnMaxageLimit );
2140  }
2141 
2151  public function lowerCdnMaxage( $maxage ) {
2152  $this->mCdnMaxageLimit = min( $maxage, $this->mCdnMaxageLimit );
2153  $this->setCdnMaxage( $this->mCdnMaxage );
2154  }
2155 
2168  public function adaptCdnTTL( $mtime, $minTTL = 0, $maxTTL = 0 ) {
2169  $minTTL = $minTTL ?: IExpiringStore::TTL_MINUTE;
2170  $maxTTL = $maxTTL ?: $this->getConfig()->get( 'CdnMaxAge' );
2171 
2172  if ( $mtime === null || $mtime === false ) {
2173  return; // entity does not exist
2174  }
2175 
2176  $age = MWTimestamp::time() - (int)wfTimestamp( TS_UNIX, $mtime );
2177  $adaptiveTTL = max( 0.9 * $age, $minTTL );
2178  $adaptiveTTL = min( $adaptiveTTL, $maxTTL );
2179 
2180  $this->lowerCdnMaxage( (int)$adaptiveTTL );
2181  }
2182 
2190  public function enableClientCache( $state ) {
2191  return wfSetVar( $this->mEnableClientCache, $state );
2192  }
2193 
2200  public function couldBePublicCached() {
2201  if ( !$this->cacheIsFinal ) {
2202  // - The entry point handles its own caching and/or doesn't use OutputPage.
2203  // (such as load.php, AjaxDispatcher, or MediaWiki\Rest\EntryPoint).
2204  //
2205  // - Or, we haven't finished processing the main part of the request yet
2206  // (e.g. Action::show, SpecialPage::execute), and the state may still
2207  // change via enableClientCache().
2208  return true;
2209  }
2210  // e.g. various error-type pages disable all client caching
2212  }
2213 
2223  public function considerCacheSettingsFinal() {
2224  $this->cacheIsFinal = true;
2225  }
2226 
2232  public function getCacheVaryCookies() {
2233  if ( self::$cacheVaryCookies === null ) {
2234  $config = $this->getConfig();
2235  self::$cacheVaryCookies = array_values( array_unique( array_merge(
2236  SessionManager::singleton()->getVaryCookies(),
2237  [
2238  'forceHTTPS',
2239  ],
2240  $config->get( 'CacheVaryCookies' )
2241  ) ) );
2242  Hooks::run( 'GetCacheVaryCookies', [ $this, &self::$cacheVaryCookies ] );
2243  }
2244  return self::$cacheVaryCookies;
2245  }
2246 
2253  public function haveCacheVaryCookies() {
2254  $request = $this->getRequest();
2255  foreach ( $this->getCacheVaryCookies() as $cookieName ) {
2256  if ( $request->getCookie( $cookieName, '', '' ) !== '' ) {
2257  wfDebug( __METHOD__ . ": found $cookieName\n" );
2258  return true;
2259  }
2260  }
2261  wfDebug( __METHOD__ . ": no cache-varying cookies found\n" );
2262  return false;
2263  }
2264 
2274  public function addVaryHeader( $header, array $option = null ) {
2275  if ( $option !== null && count( $option ) > 0 ) {
2276  wfDeprecated( 'addVaryHeader $option is ignored', '1.34' );
2277  }
2278  if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
2279  $this->mVaryHeader[$header] = null;
2280  }
2281  }
2282 
2289  public function getVaryHeader() {
2290  // If we vary on cookies, let's make sure it's always included here too.
2291  if ( $this->getCacheVaryCookies() ) {
2292  $this->addVaryHeader( 'Cookie' );
2293  }
2294 
2295  foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2296  $this->addVaryHeader( $header, $options );
2297  }
2298  return 'Vary: ' . implode( ', ', array_keys( $this->mVaryHeader ) );
2299  }
2300 
2306  public function addLinkHeader( $header ) {
2307  $this->mLinkHeader[] = $header;
2308  }
2309 
2315  public function getLinkHeader() {
2316  if ( !$this->mLinkHeader ) {
2317  return false;
2318  }
2319 
2320  return 'Link: ' . implode( ',', $this->mLinkHeader );
2321  }
2322 
2330  private function addAcceptLanguage() {
2331  $title = $this->getTitle();
2332  if ( !$title instanceof Title ) {
2333  return;
2334  }
2335 
2336  $lang = $title->getPageLanguage();
2337  if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
2338  $this->addVaryHeader( 'Accept-Language' );
2339  }
2340  }
2341 
2352  public function preventClickjacking( $enable = true ) {
2353  $this->mPreventClickjacking = $enable;
2354  }
2355 
2361  public function allowClickjacking() {
2362  $this->mPreventClickjacking = false;
2363  }
2364 
2371  public function getPreventClickjacking() {
2373  }
2374 
2382  public function getFrameOptions() {
2383  $config = $this->getConfig();
2384  if ( $config->get( 'BreakFrames' ) ) {
2385  return 'DENY';
2386  } elseif ( $this->mPreventClickjacking && $config->get( 'EditPageFrameOptions' ) ) {
2387  return $config->get( 'EditPageFrameOptions' );
2388  }
2389  return false;
2390  }
2391 
2398  private function getOriginTrials() {
2399  $config = $this->getConfig();
2400 
2401  return $config->get( 'OriginTrials' );
2402  }
2403 
2404  private function getReportTo() {
2405  $config = $this->getConfig();
2406 
2407  $expiry = $config->get( 'ReportToExpiry' );
2408 
2409  if ( !$expiry ) {
2410  return false;
2411  }
2412 
2413  $endpoints = $config->get( 'ReportToEndpoints' );
2414 
2415  if ( !$endpoints ) {
2416  return false;
2417  }
2418 
2419  $output = [ 'max_age' => $expiry, 'endpoints' => [] ];
2420 
2421  foreach ( $endpoints as $endpoint ) {
2422  $output['endpoints'][] = [ 'url' => $endpoint ];
2423  }
2424 
2425  return json_encode( $output, JSON_UNESCAPED_SLASHES );
2426  }
2427 
2428  private function getFeaturePolicyReportOnly() {
2429  $config = $this->getConfig();
2430 
2431  $features = $config->get( 'FeaturePolicyReportOnly' );
2432  return implode( ';', $features );
2433  }
2434 
2438  public function sendCacheControl() {
2439  $response = $this->getRequest()->response();
2440  $config = $this->getConfig();
2441 
2442  $this->addVaryHeader( 'Cookie' );
2443  $this->addAcceptLanguage();
2444 
2445  # don't serve compressed data to clients who can't handle it
2446  # maintain different caches for logged-in users and non-logged in ones
2447  $response->header( $this->getVaryHeader() );
2448 
2449  if ( $this->mEnableClientCache ) {
2450  if (
2451  $config->get( 'UseCdn' ) &&
2452  !$response->hasCookies() &&
2453  // The client might use methods other than cookies to appear logged-in.
2454  // E.g. HTTP headers, or query parameter tokens, OAuth, etc.
2455  !SessionManager::getGlobalSession()->isPersistent() &&
2456  !$this->isPrintable() &&
2457  $this->mCdnMaxage != 0 &&
2458  !$this->haveCacheVaryCookies()
2459  ) {
2460  # We'll purge the proxy cache for anons explicitly, but require end user agents
2461  # to revalidate against the proxy on each visit.
2462  # IMPORTANT! The CDN needs to replace the Cache-Control header with
2463  # Cache-Control: s-maxage=0, must-revalidate, max-age=0
2464  wfDebug( __METHOD__ .
2465  ": local proxy caching; {$this->mLastModified} **", 'private' );
2466  # start with a shorter timeout for initial testing
2467  # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
2468  $response->header( "Cache-Control: " .
2469  "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" );
2470  } else {
2471  # We do want clients to cache if they can, but they *must* check for updates
2472  # on revisiting the page, after the max-age period.
2473  wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **", 'private' );
2474 
2475  if ( $response->hasCookies() || SessionManager::getGlobalSession()->isPersistent() ) {
2476  $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2477  $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
2478  } else {
2479  $response->header(
2480  'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + $config->get( 'LoggedOutMaxAge' ) ) . ' GMT'
2481  );
2482  $response->header(
2483  "Cache-Control: private, must-revalidate, max-age={$config->get( 'LoggedOutMaxAge' )}"
2484  );
2485  }
2486  }
2487  if ( $this->mLastModified ) {
2488  $response->header( "Last-Modified: {$this->mLastModified}" );
2489  }
2490  } else {
2491  wfDebug( __METHOD__ . ": no caching **", 'private' );
2492 
2493  # In general, the absence of a last modified header should be enough to prevent
2494  # the client from using its cache. We send a few other things just to make sure.
2495  $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2496  $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
2497  $response->header( 'Pragma: no-cache' );
2498  }
2499  }
2500 
2506  public function loadSkinModules( $sk ) {
2507  foreach ( $sk->getDefaultModules() as $group => $modules ) {
2508  if ( $group === 'styles' ) {
2509  foreach ( $modules as $key => $moduleMembers ) {
2510  $this->addModuleStyles( $moduleMembers );
2511  }
2512  } else {
2513  $this->addModules( $modules );
2514  }
2515  }
2516  }
2517 
2528  public function output( $return = false ) {
2529  if ( $this->mDoNothing ) {
2530  return $return ? '' : null;
2531  }
2532 
2533  $response = $this->getRequest()->response();
2534  $config = $this->getConfig();
2535 
2536  if ( $this->mRedirect != '' ) {
2537  # Standards require redirect URLs to be absolute
2538  $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT );
2539 
2540  $redirect = $this->mRedirect;
2541  $code = $this->mRedirectCode;
2542 
2543  if ( Hooks::run( "BeforePageRedirect", [ $this, &$redirect, &$code ] ) ) {
2544  if ( $code == '301' || $code == '303' ) {
2545  if ( !$config->get( 'DebugRedirects' ) ) {
2546  $response->statusHeader( $code );
2547  }
2548  $this->mLastModified = wfTimestamp( TS_RFC2822 );
2549  }
2550  if ( $config->get( 'VaryOnXFP' ) ) {
2551  $this->addVaryHeader( 'X-Forwarded-Proto' );
2552  }
2553  $this->sendCacheControl();
2554 
2555  $response->header( "Content-Type: text/html; charset=utf-8" );
2556  if ( $config->get( 'DebugRedirects' ) ) {
2557  $url = htmlspecialchars( $redirect );
2558  print "<!DOCTYPE html>\n<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
2559  print "<p>Location: <a href=\"$url\">$url</a></p>\n";
2560  print "</body>\n</html>\n";
2561  } else {
2562  $response->header( 'Location: ' . $redirect );
2563  }
2564  }
2565 
2566  return $return ? '' : null;
2567  } elseif ( $this->mStatusCode ) {
2568  $response->statusHeader( $this->mStatusCode );
2569  }
2570 
2571  # Buffer output; final headers may depend on later processing
2572  ob_start();
2573 
2574  $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
2575  $response->header( 'Content-language: ' .
2576  MediaWikiServices::getInstance()->getContentLanguage()->getHtmlCode() );
2577 
2578  $linkHeader = $this->getLinkHeader();
2579  if ( $linkHeader ) {
2580  $response->header( $linkHeader );
2581  }
2582 
2583  // Prevent framing, if requested
2584  $frameOptions = $this->getFrameOptions();
2585  if ( $frameOptions ) {
2586  $response->header( "X-Frame-Options: $frameOptions" );
2587  }
2588 
2589  $originTrials = $this->getOriginTrials();
2590  foreach ( $originTrials as $originTrial ) {
2591  $response->header( "Origin-Trial: $originTrial", false );
2592  }
2593 
2594  $reportTo = $this->getReportTo();
2595  if ( $reportTo ) {
2596  $response->header( "Report-To: $reportTo" );
2597  }
2598 
2599  $featurePolicyReportOnly = $this->getFeaturePolicyReportOnly();
2600  if ( $featurePolicyReportOnly ) {
2601  $response->header( "Feature-Policy-Report-Only: $featurePolicyReportOnly" );
2602  }
2603 
2604  $this->CSP->sendHeaders();
2605 
2606  if ( $this->mArticleBodyOnly ) {
2607  echo $this->mBodytext;
2608  } else {
2609  // Enable safe mode if requested (T152169)
2610  if ( $this->getRequest()->getBool( 'safemode' ) ) {
2611  $this->disallowUserJs();
2612  }
2613 
2614  $sk = $this->getSkin();
2615  $this->loadSkinModules( $sk );
2616 
2617  MWDebug::addModules( $this );
2618 
2619  // Avoid PHP 7.1 warning of passing $this by reference
2620  $outputPage = $this;
2621  // Hook that allows last minute changes to the output page, e.g.
2622  // adding of CSS or Javascript by extensions.
2623  Hooks::runWithoutAbort( 'BeforePageDisplay', [ &$outputPage, &$sk ] );
2624 
2625  try {
2626  $sk->outputPage();
2627  } catch ( Exception $e ) {
2628  ob_end_clean(); // bug T129657
2629  throw $e;
2630  }
2631  }
2632 
2633  try {
2634  // This hook allows last minute changes to final overall output by modifying output buffer
2635  Hooks::runWithoutAbort( 'AfterFinalPageOutput', [ $this ] );
2636  } catch ( Exception $e ) {
2637  ob_end_clean(); // bug T129657
2638  throw $e;
2639  }
2640 
2641  $this->sendCacheControl();
2642 
2643  if ( $return ) {
2644  return ob_get_clean();
2645  } else {
2646  ob_end_flush();
2647  return null;
2648  }
2649  }
2650 
2661  public function prepareErrorPage( $pageTitle, $htmlTitle = false ) {
2662  $this->setPageTitle( $pageTitle );
2663  if ( $htmlTitle !== false ) {
2664  $this->setHTMLTitle( $htmlTitle );
2665  }
2666  $this->setRobotPolicy( 'noindex,nofollow' );
2667  $this->setArticleRelated( false );
2668  $this->enableClientCache( false );
2669  $this->mRedirect = '';
2670  $this->clearSubtitle();
2671  $this->clearHTML();
2672  }
2673 
2686  public function showErrorPage( $title, $msg, $params = [] ) {
2687  if ( !$title instanceof Message ) {
2688  $title = $this->msg( $title );
2689  }
2690 
2691  $this->prepareErrorPage( $title );
2692 
2693  if ( $msg instanceof Message ) {
2694  if ( $params !== [] ) {
2695  trigger_error( 'Argument ignored: $params. The message parameters argument '
2696  . 'is discarded when the $msg argument is a Message object instead of '
2697  . 'a string.', E_USER_NOTICE );
2698  }
2699  $this->addHTML( $msg->parseAsBlock() );
2700  } else {
2701  $this->addWikiMsgArray( $msg, $params );
2702  }
2703 
2704  $this->returnToMain();
2705  }
2706 
2713  public function showPermissionsErrorPage( array $errors, $action = null ) {
2714  $services = MediaWikiServices::getInstance();
2715  $permissionManager = $services->getPermissionManager();
2716  foreach ( $errors as $key => $error ) {
2717  $errors[$key] = (array)$error;
2718  }
2719 
2720  // For some action (read, edit, create and upload), display a "login to do this action"
2721  // error if all of the following conditions are met:
2722  // 1. the user is not logged in
2723  // 2. the only error is insufficient permissions (i.e. no block or something else)
2724  // 3. the error can be avoided simply by logging in
2725 
2726  if ( in_array( $action, [ 'read', 'edit', 'createpage', 'createtalk', 'upload' ] )
2727  && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
2728  && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
2729  && ( $permissionManager->groupHasPermission( 'user', $action )
2730  || $permissionManager->groupHasPermission( 'autoconfirmed', $action ) )
2731  ) {
2732  $displayReturnto = null;
2733 
2734  # Due to T34276, if a user does not have read permissions,
2735  # $this->getTitle() will just give Special:Badtitle, which is
2736  # not especially useful as a returnto parameter. Use the title
2737  # from the request instead, if there was one.
2738  $request = $this->getRequest();
2739  $returnto = Title::newFromText( $request->getVal( 'title', '' ) );
2740  if ( $action == 'edit' ) {
2741  $msg = 'whitelistedittext';
2742  $displayReturnto = $returnto;
2743  } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
2744  $msg = 'nocreatetext';
2745  } elseif ( $action == 'upload' ) {
2746  $msg = 'uploadnologintext';
2747  } else { # Read
2748  $msg = 'loginreqpagetext';
2749  $displayReturnto = Title::newMainPage();
2750  }
2751 
2752  $query = [];
2753 
2754  if ( $returnto ) {
2755  $query['returnto'] = $returnto->getPrefixedText();
2756 
2757  if ( !$request->wasPosted() ) {
2758  $returntoquery = $request->getValues();
2759  unset( $returntoquery['title'] );
2760  unset( $returntoquery['returnto'] );
2761  unset( $returntoquery['returntoquery'] );
2762  $query['returntoquery'] = wfArrayToCgi( $returntoquery );
2763  }
2764  }
2765 
2766  $title = SpecialPage::getTitleFor( 'Userlogin' );
2767  $linkRenderer = $services->getLinkRenderer();
2768  $loginUrl = $title->getLinkURL( $query, false, PROTO_RELATIVE );
2769  $loginLink = $linkRenderer->makeKnownLink(
2770  $title,
2771  $this->msg( 'loginreqlink' )->text(),
2772  [],
2773  $query
2774  );
2775 
2776  $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) );
2777  $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->params( $loginUrl )->parse() );
2778 
2779  # Don't return to a page the user can't read otherwise
2780  # we'll end up in a pointless loop
2781  if ( $displayReturnto && $permissionManager->userCan(
2782  'read', $this->getUser(), $displayReturnto
2783  ) ) {
2784  $this->returnToMain( null, $displayReturnto );
2785  }
2786  } else {
2787  $this->prepareErrorPage( $this->msg( 'permissionserrors' ) );
2788  $this->addWikiTextAsInterface( $this->formatPermissionsErrorMessage( $errors, $action ) );
2789  }
2790  }
2791 
2798  public function versionRequired( $version ) {
2799  $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) );
2800 
2801  $this->addWikiMsg( 'versionrequiredtext', $version );
2802  $this->returnToMain();
2803  }
2804 
2812  public function formatPermissionsErrorMessage( array $errors, $action = null ) {
2813  if ( $action == null ) {
2814  $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
2815  } else {
2816  $action_desc = $this->msg( "action-$action" )->plain();
2817  $text = $this->msg(
2818  'permissionserrorstext-withaction',
2819  count( $errors ),
2820  $action_desc
2821  )->plain() . "\n\n";
2822  }
2823 
2824  if ( count( $errors ) > 1 ) {
2825  $text .= '<ul class="permissions-errors">' . "\n";
2826 
2827  foreach ( $errors as $error ) {
2828  $text .= '<li>';
2829  $text .= $this->msg( ...$error )->plain();
2830  $text .= "</li>\n";
2831  }
2832  $text .= '</ul>';
2833  } else {
2834  $text .= "<div class=\"permissions-errors\">\n" .
2835  $this->msg( ...reset( $errors ) )->plain() .
2836  "\n</div>";
2837  }
2838 
2839  return $text;
2840  }
2841 
2851  public function showLagWarning( $lag ) {
2852  $config = $this->getConfig();
2853  if ( $lag >= $config->get( 'SlaveLagWarning' ) ) {
2854  $lag = floor( $lag ); // floor to avoid nano seconds to display
2855  $message = $lag < $config->get( 'SlaveLagCritical' )
2856  ? 'lag-warn-normal'
2857  : 'lag-warn-high';
2858  $wrap = Html::rawElement( 'div', [ 'class' => "mw-{$message}" ], "\n$1\n" );
2859  $this->wrapWikiMsg( "$wrap\n", [ $message, $this->getLanguage()->formatNum( $lag ) ] );
2860  }
2861  }
2862 
2869  public function showFatalError( $message ) {
2870  $this->prepareErrorPage( $this->msg( 'internalerror' ) );
2871 
2872  $this->addHTML( $message );
2873  }
2874 
2883  public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) {
2884  $linkRenderer = MediaWikiServices::getInstance()
2885  ->getLinkRendererFactory()->createFromLegacyOptions( $options );
2886  $link = $this->msg( 'returnto' )->rawParams(
2887  $linkRenderer->makeLink( $title, $text, [], $query ) )->escaped();
2888  $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
2889  }
2890 
2899  public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
2900  if ( $returnto == null ) {
2901  $returnto = $this->getRequest()->getText( 'returnto' );
2902  }
2903 
2904  if ( $returntoquery == null ) {
2905  $returntoquery = $this->getRequest()->getText( 'returntoquery' );
2906  }
2907 
2908  if ( $returnto === '' ) {
2909  $returnto = Title::newMainPage();
2910  }
2911 
2912  if ( is_object( $returnto ) ) {
2913  $titleObj = $returnto;
2914  } else {
2915  $titleObj = Title::newFromText( $returnto );
2916  }
2917  // We don't want people to return to external interwiki. That
2918  // might potentially be used as part of a phishing scheme
2919  if ( !is_object( $titleObj ) || $titleObj->isExternal() ) {
2920  $titleObj = Title::newMainPage();
2921  }
2922 
2923  $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) );
2924  }
2925 
2926  private function getRlClientContext() {
2927  if ( !$this->rlClientContext ) {
2929  [], // modules; not relevant
2930  $this->getLanguage()->getCode(),
2931  $this->getSkin()->getSkinName(),
2932  $this->getUser()->isLoggedIn() ? $this->getUser()->getName() : null,
2933  null, // version; not relevant
2935  null, // only; not relevant
2936  $this->isPrintable(),
2937  $this->getRequest()->getBool( 'handheld' )
2938  );
2939  $this->rlClientContext = new ResourceLoaderContext(
2940  $this->getResourceLoader(),
2941  new FauxRequest( $query )
2942  );
2943  if ( $this->contentOverrideCallbacks ) {
2944  $this->rlClientContext = new DerivativeResourceLoaderContext( $this->rlClientContext );
2945  $this->rlClientContext->setContentOverrideCallback( function ( Title $title ) {
2946  foreach ( $this->contentOverrideCallbacks as $callback ) {
2947  $content = $callback( $title );
2948  if ( $content !== null ) {
2950  if ( strpos( $text, '</script>' ) !== false ) {
2951  // Proactively replace this so that we can display a message
2952  // to the user, instead of letting it go to Html::inlineScript(),
2953  // where it would be considered a server-side issue.
2954  $titleFormatted = $title->getPrefixedText();
2956  Xml::encodeJsCall( 'mw.log.error', [
2957  "Cannot preview $titleFormatted due to script-closing tag."
2958  ] )
2959  );
2960  }
2961  return $content;
2962  }
2963  }
2964  return null;
2965  } );
2966  }
2967  }
2968  return $this->rlClientContext;
2969  }
2970 
2982  public function getRlClient() {
2983  if ( !$this->rlClient ) {
2984  $context = $this->getRlClientContext();
2985  $rl = $this->getResourceLoader();
2986  $this->addModules( [
2987  'user',
2988  'user.options',
2989  'user.tokens',
2990  ] );
2991  $this->addModuleStyles( [
2992  'site.styles',
2993  'noscript',
2994  'user.styles',
2995  ] );
2996  $this->getSkin()->setupSkinUserCss( $this );
2997 
2998  // Prepare exempt modules for buildExemptModules()
2999  $exemptGroups = [ 'site' => [], 'noscript' => [], 'private' => [], 'user' => [] ];
3000  $exemptStates = [];
3001  $moduleStyles = $this->getModuleStyles( /*filter*/ true );
3002 
3003  // Preload getTitleInfo for isKnownEmpty calls below and in ResourceLoaderClientHtml
3004  // Separate user-specific batch for improved cache-hit ratio.
3005  $userBatch = [ 'user.styles', 'user' ];
3006  $siteBatch = array_diff( $moduleStyles, $userBatch );
3007  $dbr = wfGetDB( DB_REPLICA );
3010 
3011  // Filter out modules handled by buildExemptModules()
3012  $moduleStyles = array_filter( $moduleStyles,
3013  function ( $name ) use ( $rl, $context, &$exemptGroups, &$exemptStates ) {
3014  $module = $rl->getModule( $name );
3015  if ( $module ) {
3016  $group = $module->getGroup();
3017  if ( isset( $exemptGroups[$group] ) ) {
3018  $exemptStates[$name] = 'ready';
3019  if ( !$module->isKnownEmpty( $context ) ) {
3020  // E.g. Don't output empty <styles>
3021  $exemptGroups[$group][] = $name;
3022  }
3023  return false;
3024  }
3025  }
3026  return true;
3027  }
3028  );
3029  $this->rlExemptStyleModules = $exemptGroups;
3030 
3032  'target' => $this->getTarget(),
3033  'nonce' => $this->CSP->getNonce(),
3034  // When 'safemode', disallowUserJs(), or reduceAllowedModules() is used
3035  // to only restrict modules to ORIGIN_CORE (ie. disallow ORIGIN_USER), the list of
3036  // modules enqueud for loading on this page is filtered to just those.
3037  // However, to make sure we also apply the restriction to dynamic dependencies and
3038  // lazy-loaded modules at run-time on the client-side, pass 'safemode' down to the
3039  // StartupModule so that the client-side registry will not contain any restricted
3040  // modules either. (T152169, T185303)
3041  'safemode' => ( $this->getAllowedModules( ResourceLoaderModule::TYPE_COMBINED )
3042  <= ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
3043  ) ? '1' : null,
3044  ] );
3045  $rlClient->setConfig( $this->getJSVars() );
3046  $rlClient->setModules( $this->getModules( /*filter*/ true ) );
3047  $rlClient->setModuleStyles( $moduleStyles );
3048  $rlClient->setExemptStates( $exemptStates );
3049  $this->rlClient = $rlClient;
3050  }
3051  return $this->rlClient;
3052  }
3053 
3059  public function headElement( Skin $sk, $includeStyle = true ) {
3060  $config = $this->getConfig();
3061  $userdir = $this->getLanguage()->getDir();
3062  $sitedir = MediaWikiServices::getInstance()->getContentLanguage()->getDir();
3063 
3064  $pieces = [];
3065  $htmlAttribs = Sanitizer::mergeAttributes(
3066  $this->getRlClient()->getDocumentAttributes(),
3068  );
3069  $pieces[] = Html::htmlHeader( $htmlAttribs );
3070  $pieces[] = Html::openElement( 'head' );
3071 
3072  if ( $this->getHTMLTitle() == '' ) {
3073  $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() );
3074  }
3075 
3076  if ( !Html::isXmlMimeType( $config->get( 'MimeType' ) ) ) {
3077  // Add <meta charset="UTF-8">
3078  // This should be before <title> since it defines the charset used by
3079  // text including the text inside <title>.
3080  // The spec recommends defining XHTML5's charset using the XML declaration
3081  // instead of meta.
3082  // Our XML declaration is output by Html::htmlHeader.
3083  // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type
3084  // https://html.spec.whatwg.org/multipage/semantics.html#charset
3085  $pieces[] = Html::element( 'meta', [ 'charset' => 'UTF-8' ] );
3086  }
3087 
3088  $pieces[] = Html::element( 'title', null, $this->getHTMLTitle() );
3089  $pieces[] = $this->getRlClient()->getHeadHtml( $htmlAttribs['class'] ?? null );
3090  $pieces[] = $this->buildExemptModules();
3091  $pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) );
3092  $pieces = array_merge( $pieces, array_values( $this->mHeadItems ) );
3093 
3094  // This library is intended to run on older browsers that MediaWiki no longer
3095  // supports as Grade A. For these Grade C browsers, we provide an experience
3096  // using only HTML and CSS. But, where standards-compliant browsers are able to
3097  // style unknown HTML elements without issue, old IE ignores these styles.
3098  // The html5shiv library fixes that.
3099  // Use an IE conditional comment to serve the script only to old IE
3100  $shivUrl = $config->get( 'ResourceBasePath' ) . '/resources/lib/html5shiv/html5shiv.js';
3101  $pieces[] = '<!--[if lt IE 9]>' .
3102  Html::linkedScript( $shivUrl, $this->CSP->getNonce() ) .
3103  '<![endif]-->';
3104 
3105  $pieces[] = Html::closeElement( 'head' );
3106 
3107  $bodyClasses = $this->mAdditionalBodyClasses;
3108  $bodyClasses[] = 'mediawiki';
3109 
3110  # Classes for LTR/RTL directionality support
3111  $bodyClasses[] = $userdir;
3112  $bodyClasses[] = "sitedir-$sitedir";
3113 
3114  $underline = $this->getUser()->getOption( 'underline' );
3115  if ( $underline < 2 ) {
3116  // The following classes can be used here:
3117  // * mw-underline-always
3118  // * mw-underline-never
3119  $bodyClasses[] = 'mw-underline-' . ( $underline ? 'always' : 'never' );
3120  }
3121 
3122  if ( $this->getLanguage()->capitalizeAllNouns() ) {
3123  # A <body> class is probably not the best way to do this . . .
3124  $bodyClasses[] = 'capitalize-all-nouns';
3125  }
3126 
3127  // Parser feature migration class
3128  // The idea is that this will eventually be removed, after the wikitext
3129  // which requires it is cleaned up.
3130  $bodyClasses[] = 'mw-hide-empty-elt';
3131 
3132  $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
3133  $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
3134  $bodyClasses[] =
3135  'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
3136 
3137  $bodyAttrs = [];
3138  // While the implode() is not strictly needed, it's used for backwards compatibility
3139  // (this used to be built as a string and hooks likely still expect that).
3140  $bodyAttrs['class'] = implode( ' ', $bodyClasses );
3141 
3142  // Allow skins and extensions to add body attributes they need
3143  $sk->addToBodyAttributes( $this, $bodyAttrs );
3144  Hooks::run( 'OutputPageBodyAttributes', [ $this, $sk, &$bodyAttrs ] );
3145 
3146  $pieces[] = Html::openElement( 'body', $bodyAttrs );
3147 
3148  return self::combineWrappedStrings( $pieces );
3149  }
3150 
3156  public function getResourceLoader() {
3157  if ( is_null( $this->mResourceLoader ) ) {
3158  // Lazy-initialise as needed
3159  $this->mResourceLoader = MediaWikiServices::getInstance()->getResourceLoader();
3160  }
3161  return $this->mResourceLoader;
3162  }
3163 
3172  public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) {
3173  // Apply 'target' and 'origin' filters
3174  $modules = $this->filterModules( (array)$modules, null, $only );
3175 
3177  $this->getRlClientContext(),
3178  $modules,
3179  $only,
3180  $extraQuery,
3181  $this->CSP->getNonce()
3182  );
3183  }
3184 
3191  protected static function combineWrappedStrings( array $chunks ) {
3192  // Filter out empty values
3193  $chunks = array_filter( $chunks, 'strlen' );
3194  return WrappedString::join( "\n", $chunks );
3195  }
3196 
3203  public function getBottomScripts() {
3204  $chunks = [];
3205  $chunks[] = $this->getRlClient()->getBodyHtml();
3206 
3207  // Legacy non-ResourceLoader scripts
3208  $chunks[] = $this->mScripts;
3209 
3210  if ( $this->limitReportJSData ) {
3213  [ 'wgPageParseReport' => $this->limitReportJSData ]
3214  ),
3215  $this->CSP->getNonce()
3216  );
3217  }
3218 
3219  return self::combineWrappedStrings( $chunks );
3220  }
3221 
3228  public function getJsConfigVars() {
3229  return $this->mJsConfigVars;
3230  }
3231 
3238  public function addJsConfigVars( $keys, $value = null ) {
3239  if ( is_array( $keys ) ) {
3240  foreach ( $keys as $key => $value ) {
3241  $this->mJsConfigVars[$key] = $value;
3242  }
3243  return;
3244  }
3245 
3246  $this->mJsConfigVars[$keys] = $value;
3247  }
3248 
3258  public function getJSVars() {
3259  $curRevisionId = 0;
3260  $articleId = 0;
3261  $canonicalSpecialPageName = false; # T23115
3262  $services = MediaWikiServices::getInstance();
3263 
3264  $title = $this->getTitle();
3265  $ns = $title->getNamespace();
3266  $nsInfo = $services->getNamespaceInfo();
3267  $canonicalNamespace = $nsInfo->exists( $ns )
3268  ? $nsInfo->getCanonicalName( $ns )
3269  : $title->getNsText();
3270 
3271  $sk = $this->getSkin();
3272  // Get the relevant title so that AJAX features can use the correct page name
3273  // when making API requests from certain special pages (T36972).
3274  $relevantTitle = $sk->getRelevantTitle();
3275  $relevantUser = $sk->getRelevantUser();
3276 
3277  if ( $ns == NS_SPECIAL ) {
3278  list( $canonicalSpecialPageName, /*...*/ ) =
3279  $services->getSpecialPageFactory()->
3280  resolveAlias( $title->getDBkey() );
3281  } elseif ( $this->canUseWikiPage() ) {
3282  $wikiPage = $this->getWikiPage();
3283  $curRevisionId = $wikiPage->getLatest();
3284  $articleId = $wikiPage->getId();
3285  }
3286 
3287  $lang = $title->getPageViewLanguage();
3288 
3289  // Pre-process information
3290  $separatorTransTable = $lang->separatorTransformTable();
3291  $separatorTransTable = $separatorTransTable ?: [];
3292  $compactSeparatorTransTable = [
3293  implode( "\t", array_keys( $separatorTransTable ) ),
3294  implode( "\t", $separatorTransTable ),
3295  ];
3296  $digitTransTable = $lang->digitTransformTable();
3297  $digitTransTable = $digitTransTable ?: [];
3298  $compactDigitTransTable = [
3299  implode( "\t", array_keys( $digitTransTable ) ),
3300  implode( "\t", $digitTransTable ),
3301  ];
3302 
3303  $user = $this->getUser();
3304 
3305  // Internal variables for MediaWiki core
3306  $vars = [
3307  // @internal For mediawiki.page.startup
3308  'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
3309 
3310  // @internal For jquery.tablesorter
3311  'wgSeparatorTransformTable' => $compactSeparatorTransTable,
3312  'wgDigitTransformTable' => $compactDigitTransTable,
3313  'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
3314  'wgMonthNames' => $lang->getMonthNamesArray(),
3315  'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
3316 
3317  // @internal For debugging purposes
3318  'wgRequestId' => WebRequest::getRequestId(),
3319 
3320  // @internal For mw.loader
3321  'wgCSPNonce' => $this->CSP->getNonce(),
3322  ];
3323 
3324  // Start of supported and stable config vars (for use by extensions/gadgets).
3325  $vars += [
3326  'wgCanonicalNamespace' => $canonicalNamespace,
3327  'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
3328  'wgNamespaceNumber' => $title->getNamespace(),
3329  'wgPageName' => $title->getPrefixedDBkey(),
3330  'wgTitle' => $title->getText(),
3331  'wgCurRevisionId' => $curRevisionId,
3332  'wgRevisionId' => (int)$this->getRevisionId(),
3333  'wgArticleId' => $articleId,
3334  'wgIsArticle' => $this->isArticle(),
3335  'wgIsRedirect' => $title->isRedirect(),
3336  'wgAction' => Action::getActionName( $this->getContext() ),
3337  'wgUserName' => $user->isAnon() ? null : $user->getName(),
3338  'wgUserGroups' => $user->getEffectiveGroups(),
3339  'wgCategories' => $this->getCategories(),
3340  'wgPageContentLanguage' => $lang->getCode(),
3341  'wgPageContentModel' => $title->getContentModel(),
3342  'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
3343  'wgRelevantArticleId' => $relevantTitle->getArticleID(),
3344  ];
3345  if ( $user->isLoggedIn() ) {
3346  $vars['wgUserId'] = $user->getId();
3347  $vars['wgUserEditCount'] = $user->getEditCount();
3348  $userReg = $user->getRegistration();
3349  $vars['wgUserRegistration'] = $userReg ? (int)wfTimestamp( TS_UNIX, $userReg ) * 1000 : null;
3350  // Get the revision ID of the oldest new message on the user's talk
3351  // page. This can be used for constructing new message alerts on
3352  // the client side.
3353  $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId();
3354  }
3355  $contLang = $services->getContentLanguage();
3356  if ( $contLang->hasVariants() ) {
3357  $vars['wgUserVariant'] = $contLang->getPreferredVariant();
3358  }
3359  // Same test as SkinTemplate
3360  $vars['wgIsProbablyEditable'] = $this->userCanEditOrCreate( $user, $title );
3361  $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle &&
3362  $this->userCanEditOrCreate( $user, $relevantTitle );
3363  foreach ( $title->getRestrictionTypes() as $type ) {
3364  // Following keys are set in $vars:
3365  // wgRestrictionCreate, wgRestrictionEdit, wgRestrictionMove, wgRestrictionUpload
3366  $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
3367  }
3368  if ( $title->isMainPage() ) {
3369  $vars['wgIsMainPage'] = true;
3370  }
3371  if ( $relevantUser ) {
3372  $vars['wgRelevantUserName'] = $relevantUser->getName();
3373  }
3374  // End of stable config vars
3375 
3376  if ( $this->mRedirectedFrom ) {
3377  // @internal For skin JS
3378  $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey();
3379  }
3380 
3381  // Allow extensions to add their custom variables to the mw.config map.
3382  // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
3383  // page-dependant but site-wide (without state).
3384  // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
3385  Hooks::run( 'MakeGlobalVariablesScript', [ &$vars, $this ] );
3386 
3387  // Merge in variables from addJsConfigVars last
3388  return array_merge( $vars, $this->getJsConfigVars() );
3389  }
3390 
3400  public function userCanPreview() {
3401  $request = $this->getRequest();
3402  if (
3403  $request->getVal( 'action' ) !== 'submit' ||
3404  !$request->wasPosted()
3405  ) {
3406  return false;
3407  }
3408 
3409  $user = $this->getUser();
3410 
3411  if ( !$user->isLoggedIn() ) {
3412  // Anons have predictable edit tokens
3413  return false;
3414  }
3415  if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
3416  return false;
3417  }
3418 
3419  $title = $this->getTitle();
3420  $errors = $title->getUserPermissionsErrors( 'edit', $user );
3421  if ( count( $errors ) !== 0 ) {
3422  return false;
3423  }
3424 
3425  return true;
3426  }
3427 
3433  private function userCanEditOrCreate(
3434  User $user,
3436  ) {
3437  $pm = MediaWikiServices::getInstance()->getPermissionManager();
3438  return $pm->quickUserCan( 'edit', $user, $title )
3439  && ( $this->getTitle()->exists() ||
3440  $pm->quickUserCan( 'create', $user, $title ) );
3441  }
3442 
3446  public function getHeadLinksArray() {
3447  global $wgVersion;
3448 
3449  $tags = [];
3450  $config = $this->getConfig();
3451 
3452  $canonicalUrl = $this->mCanonicalUrl;
3453 
3454  $tags['meta-generator'] = Html::element( 'meta', [
3455  'name' => 'generator',
3456  'content' => "MediaWiki $wgVersion",
3457  ] );
3458 
3459  if ( $config->get( 'ReferrerPolicy' ) !== false ) {
3460  // Per https://w3c.github.io/webappsec-referrer-policy/#unknown-policy-values
3461  // fallbacks should come before the primary value so we need to reverse the array.
3462  foreach ( array_reverse( (array)$config->get( 'ReferrerPolicy' ) ) as $i => $policy ) {
3463  $tags["meta-referrer-$i"] = Html::element( 'meta', [
3464  'name' => 'referrer',
3465  'content' => $policy,
3466  ] );
3467  }
3468  }
3469 
3470  $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
3471  if ( $p !== 'index,follow' ) {
3472  // http://www.robotstxt.org/wc/meta-user.html
3473  // Only show if it's different from the default robots policy
3474  $tags['meta-robots'] = Html::element( 'meta', [
3475  'name' => 'robots',
3476  'content' => $p,
3477  ] );
3478  }
3479 
3480  foreach ( $this->mMetatags as $tag ) {
3481  if ( strncasecmp( $tag[0], 'http:', 5 ) === 0 ) {
3482  $a = 'http-equiv';
3483  $tag[0] = substr( $tag[0], 5 );
3484  } elseif ( strncasecmp( $tag[0], 'og:', 3 ) === 0 ) {
3485  $a = 'property';
3486  } else {
3487  $a = 'name';
3488  }
3489  $tagName = "meta-{$tag[0]}";
3490  if ( isset( $tags[$tagName] ) ) {
3491  $tagName .= $tag[1];
3492  }
3493  $tags[$tagName] = Html::element( 'meta',
3494  [
3495  $a => $tag[0],
3496  'content' => $tag[1]
3497  ]
3498  );
3499  }
3500 
3501  foreach ( $this->mLinktags as $tag ) {
3502  $tags[] = Html::element( 'link', $tag );
3503  }
3504 
3505  # Universal edit button
3506  if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) {
3507  if ( $this->userCanEditOrCreate( $this->getUser(), $this->getTitle() ) ) {
3508  // Original UniversalEditButton
3509  $msg = $this->msg( 'edit' )->text();
3510  $tags['universal-edit-button'] = Html::element( 'link', [
3511  'rel' => 'alternate',
3512  'type' => 'application/x-wiki',
3513  'title' => $msg,
3514  'href' => $this->getTitle()->getEditURL(),
3515  ] );
3516  // Alternate edit link
3517  $tags['alternative-edit'] = Html::element( 'link', [
3518  'rel' => 'edit',
3519  'title' => $msg,
3520  'href' => $this->getTitle()->getEditURL(),
3521  ] );
3522  }
3523  }
3524 
3525  # Generally the order of the favicon and apple-touch-icon links
3526  # should not matter, but Konqueror (3.5.9 at least) incorrectly
3527  # uses whichever one appears later in the HTML source. Make sure
3528  # apple-touch-icon is specified first to avoid this.
3529  if ( $config->get( 'AppleTouchIcon' ) !== false ) {
3530  $tags['apple-touch-icon'] = Html::element( 'link', [
3531  'rel' => 'apple-touch-icon',
3532  'href' => $config->get( 'AppleTouchIcon' )
3533  ] );
3534  }
3535 
3536  if ( $config->get( 'Favicon' ) !== false ) {
3537  $tags['favicon'] = Html::element( 'link', [
3538  'rel' => 'shortcut icon',
3539  'href' => $config->get( 'Favicon' )
3540  ] );
3541  }
3542 
3543  # OpenSearch description link
3544  $tags['opensearch'] = Html::element( 'link', [
3545  'rel' => 'search',
3546  'type' => 'application/opensearchdescription+xml',
3547  'href' => wfScript( 'opensearch_desc' ),
3548  'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
3549  ] );
3550 
3551  # Real Simple Discovery link, provides auto-discovery information
3552  # for the MediaWiki API (and potentially additional custom API
3553  # support such as WordPress or Twitter-compatible APIs for a
3554  # blogging extension, etc)
3555  $tags['rsd'] = Html::element( 'link', [
3556  'rel' => 'EditURI',
3557  'type' => 'application/rsd+xml',
3558  // Output a protocol-relative URL here if $wgServer is protocol-relative.
3559  // Whether RSD accepts relative or protocol-relative URLs is completely
3560  // undocumented, though.
3561  'href' => wfExpandUrl( wfAppendQuery(
3562  wfScript( 'api' ),
3563  [ 'action' => 'rsd' ] ),
3565  ),
3566  ] );
3567 
3568  # Language variants
3569  if ( !$config->get( 'DisableLangConversion' ) ) {
3570  $lang = $this->getTitle()->getPageLanguage();
3571  if ( $lang->hasVariants() ) {
3572  $variants = $lang->getVariants();
3573  foreach ( $variants as $variant ) {
3574  $tags["variant-$variant"] = Html::element( 'link', [
3575  'rel' => 'alternate',
3576  'hreflang' => LanguageCode::bcp47( $variant ),
3577  'href' => $this->getTitle()->getLocalURL(
3578  [ 'variant' => $variant ] )
3579  ]
3580  );
3581  }
3582  # x-default link per https://support.google.com/webmasters/answer/189077?hl=en
3583  $tags["variant-x-default"] = Html::element( 'link', [
3584  'rel' => 'alternate',
3585  'hreflang' => 'x-default',
3586  'href' => $this->getTitle()->getLocalURL() ] );
3587  }
3588  }
3589 
3590  # Copyright
3591  if ( $this->copyrightUrl !== null ) {
3592  $copyright = $this->copyrightUrl;
3593  } else {
3594  $copyright = '';
3595  if ( $config->get( 'RightsPage' ) ) {
3596  $copy = Title::newFromText( $config->get( 'RightsPage' ) );
3597 
3598  if ( $copy ) {
3599  $copyright = $copy->getLocalURL();
3600  }
3601  }
3602 
3603  if ( !$copyright && $config->get( 'RightsUrl' ) ) {
3604  $copyright = $config->get( 'RightsUrl' );
3605  }
3606  }
3607 
3608  if ( $copyright ) {
3609  $tags['copyright'] = Html::element( 'link', [
3610  'rel' => 'license',
3611  'href' => $copyright ]
3612  );
3613  }
3614 
3615  # Feeds
3616  if ( $config->get( 'Feed' ) ) {
3617  $feedLinks = [];
3618 
3619  foreach ( $this->getSyndicationLinks() as $format => $link ) {
3620  # Use the page name for the title. In principle, this could
3621  # lead to issues with having the same name for different feeds
3622  # corresponding to the same page, but we can't avoid that at
3623  # this low a level.
3624 
3625  $feedLinks[] = $this->feedLink(
3626  $format,
3627  $link,
3628  # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
3629  $this->msg(
3630  "page-{$format}-feed", $this->getTitle()->getPrefixedText()
3631  )->text()
3632  );
3633  }
3634 
3635  # Recent changes feed should appear on every page (except recentchanges,
3636  # that would be redundant). Put it after the per-page feed to avoid
3637  # changing existing behavior. It's still available, probably via a
3638  # menu in your browser. Some sites might have a different feed they'd
3639  # like to promote instead of the RC feed (maybe like a "Recent New Articles"
3640  # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
3641  # If so, use it instead.
3642  $sitename = $config->get( 'Sitename' );
3643  $overrideSiteFeed = $config->get( 'OverrideSiteFeed' );
3644  if ( $overrideSiteFeed ) {
3645  foreach ( $overrideSiteFeed as $type => $feedUrl ) {
3646  // Note, this->feedLink escapes the url.
3647  $feedLinks[] = $this->feedLink(
3648  $type,
3649  $feedUrl,
3650  $this->msg( "site-{$type}-feed", $sitename )->text()
3651  );
3652  }
3653  } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
3654  $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
3655  foreach ( $this->getAdvertisedFeedTypes() as $format ) {
3656  $feedLinks[] = $this->feedLink(
3657  $format,
3658  $rctitle->getLocalURL( [ 'feed' => $format ] ),
3659  # For grep: 'site-rss-feed', 'site-atom-feed'
3660  $this->msg( "site-{$format}-feed", $sitename )->text()
3661  );
3662  }
3663  }
3664 
3665  # Allow extensions to change the list pf feeds. This hook is primarily for changing,
3666  # manipulating or removing existing feed tags. If you want to add new feeds, you should
3667  # use OutputPage::addFeedLink() instead.
3668  Hooks::run( 'AfterBuildFeedLinks', [ &$feedLinks ] );
3669 
3670  $tags += $feedLinks;
3671  }
3672 
3673  # Canonical URL
3674  if ( $config->get( 'EnableCanonicalServerLink' ) ) {
3675  if ( $canonicalUrl !== false ) {
3676  $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL );
3677  } elseif ( $this->isArticleRelated() ) {
3678  // This affects all requests where "setArticleRelated" is true. This is
3679  // typically all requests that show content (query title, curid, oldid, diff),
3680  // and all wikipage actions (edit, delete, purge, info, history etc.).
3681  // It does not apply to File pages and Special pages.
3682  // 'history' and 'info' actions address page metadata rather than the page
3683  // content itself, so they may not be canonicalized to the view page url.
3684  // TODO: this ought to be better encapsulated in the Action class.
3685  $action = Action::getActionName( $this->getContext() );
3686  if ( in_array( $action, [ 'history', 'info' ] ) ) {
3687  $query = "action={$action}";
3688  } else {
3689  $query = '';
3690  }
3691  $canonicalUrl = $this->getTitle()->getCanonicalURL( $query );
3692  } else {
3693  $reqUrl = $this->getRequest()->getRequestURL();
3694  $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL );
3695  }
3696  }
3697  if ( $canonicalUrl !== false ) {
3698  $tags[] = Html::element( 'link', [
3699  'rel' => 'canonical',
3700  'href' => $canonicalUrl
3701  ] );
3702  }
3703 
3704  // Allow extensions to add, remove and/or otherwise manipulate these links
3705  // If you want only to *add* <head> links, please use the addHeadItem()
3706  // (or addHeadItems() for multiple items) method instead.
3707  // This hook is provided as a last resort for extensions to modify these
3708  // links before the output is sent to client.
3709  Hooks::run( 'OutputPageAfterGetHeadLinksArray', [ &$tags, $this ] );
3710 
3711  return $tags;
3712  }
3713 
3722  private function feedLink( $type, $url, $text ) {
3723  return Html::element( 'link', [
3724  'rel' => 'alternate',
3725  'type' => "application/$type+xml",
3726  'title' => $text,
3727  'href' => $url ]
3728  );
3729  }
3730 
3740  public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
3741  $options = [];
3742  if ( $media ) {
3743  $options['media'] = $media;
3744  }
3745  if ( $condition ) {
3746  $options['condition'] = $condition;
3747  }
3748  if ( $dir ) {
3749  $options['dir'] = $dir;
3750  }
3751  $this->styles[$style] = $options;
3752  }
3753 
3761  public function addInlineStyle( $style_css, $flip = 'noflip' ) {
3762  if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
3763  # If wanted, and the interface is right-to-left, flip the CSS
3764  $style_css = CSSJanus::transform( $style_css, true, false );
3765  }
3766  $this->mInlineStyles .= Html::inlineStyle( $style_css );
3767  }
3768 
3774  protected function buildExemptModules() {
3775  $chunks = [];
3776 
3777  // Requirements:
3778  // - Within modules provided by the software (core, skin, extensions),
3779  // styles from skin stylesheets should be overridden by styles
3780  // from modules dynamically loaded with JavaScript.
3781  // - Styles from site-specific, private, and user modules should override
3782  // both of the above.
3783  //
3784  // The effective order for stylesheets must thus be:
3785  // 1. Page style modules, formatted server-side by ResourceLoaderClientHtml.
3786  // 2. Dynamically-loaded styles, inserted client-side by mw.loader.
3787  // 3. Styles that are site-specific, private or from the user, formatted
3788  // server-side by this function.
3789  //
3790  // The 'ResourceLoaderDynamicStyles' marker helps JavaScript know where
3791  // point #2 is.
3792 
3793  // Add legacy styles added through addStyle()/addInlineStyle() here
3794  $chunks[] = implode( '', $this->buildCssLinksArray() ) . $this->mInlineStyles;
3795 
3796  // Things that go after the ResourceLoaderDynamicStyles marker
3797  $append = [];
3798  $separateReq = [ 'site.styles', 'user.styles' ];
3799  foreach ( $this->rlExemptStyleModules as $group => $moduleNames ) {
3800  if ( $moduleNames ) {
3801  $append[] = $this->makeResourceLoaderLink(
3802  array_diff( $moduleNames, $separateReq ),
3803  ResourceLoaderModule::TYPE_STYLES
3804  );
3805 
3806  foreach ( array_intersect( $moduleNames, $separateReq ) as $name ) {
3807  // These require their own dedicated request in order to support "@import"
3808  // syntax, which is incompatible with concatenation. (T147667, T37562)
3809  $append[] = $this->makeResourceLoaderLink( $name,
3810  ResourceLoaderModule::TYPE_STYLES
3811  );
3812  }
3813  }
3814  }
3815  if ( $append ) {
3816  $chunks[] = Html::element(
3817  'meta',
3818  [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ]
3819  );
3820  $chunks = array_merge( $chunks, $append );
3821  }
3822 
3823  return self::combineWrappedStrings( $chunks );
3824  }
3825 
3829  public function buildCssLinksArray() {
3830  $links = [];
3831 
3832  foreach ( $this->styles as $file => $options ) {
3833  $link = $this->styleLink( $file, $options );
3834  if ( $link ) {
3835  $links[$file] = $link;
3836  }
3837  }
3838  return $links;
3839  }
3840 
3848  protected function styleLink( $style, array $options ) {
3849  if ( isset( $options['dir'] ) && $this->getLanguage()->getDir() != $options['dir'] ) {
3850  return '';
3851  }
3852 
3853  if ( isset( $options['media'] ) ) {
3854  $media = self::transformCssMedia( $options['media'] );
3855  if ( is_null( $media ) ) {
3856  return '';
3857  }
3858  } else {
3859  $media = 'all';
3860  }
3861 
3862  if ( substr( $style, 0, 1 ) == '/' ||
3863  substr( $style, 0, 5 ) == 'http:' ||
3864  substr( $style, 0, 6 ) == 'https:' ) {
3865  $url = $style;
3866  } else {
3867  $config = $this->getConfig();
3868  // Append file hash as query parameter
3869  $url = self::transformResourcePath(
3870  $config,
3871  $config->get( 'StylePath' ) . '/' . $style
3872  );
3873  }
3874 
3875  $link = Html::linkedStyle( $url, $media );
3876 
3877  if ( isset( $options['condition'] ) ) {
3878  $condition = htmlspecialchars( $options['condition'] );
3879  $link = "<!--[if $condition]>$link<![endif]-->";
3880  }
3881  return $link;
3882  }
3883 
3905  public static function transformResourcePath( Config $config, $path ) {
3906  global $IP;
3907 
3908  $localDir = $IP;
3909  $remotePathPrefix = $config->get( 'ResourceBasePath' );
3910  if ( $remotePathPrefix === '' ) {
3911  // The configured base path is required to be empty string for
3912  // wikis in the domain root
3913  $remotePath = '/';
3914  } else {
3915  $remotePath = $remotePathPrefix;
3916  }
3917  if ( strpos( $path, $remotePath ) !== 0 || substr( $path, 0, 2 ) === '//' ) {
3918  // - Path is outside wgResourceBasePath, ignore.
3919  // - Path is protocol-relative. Fixes T155310. Not supported by RelPath lib.
3920  return $path;
3921  }
3922  // For files in resources, extensions/ or skins/, ResourceBasePath is preferred here.
3923  // For other misc files in $IP, we'll fallback to that as well. There is, however, a fourth
3924  // supported dir/path pair in the configuration (wgUploadDirectory, wgUploadPath)
3925  // which is not expected to be in wgResourceBasePath on CDNs. (T155146)
3926  $uploadPath = $config->get( 'UploadPath' );
3927  if ( strpos( $path, $uploadPath ) === 0 ) {
3928  $localDir = $config->get( 'UploadDirectory' );
3929  $remotePathPrefix = $remotePath = $uploadPath;
3930  }
3931 
3932  $path = RelPath::getRelativePath( $path, $remotePath );
3933  return self::transformFilePath( $remotePathPrefix, $localDir, $path );
3934  }
3935 
3947  public static function transformFilePath( $remotePathPrefix, $localPath, $file ) {
3948  $hash = md5_file( "$localPath/$file" );
3949  if ( $hash === false ) {
3950  wfLogWarning( __METHOD__ . ": Failed to hash $localPath/$file" );
3951  $hash = '';
3952  }
3953  return "$remotePathPrefix/$file?" . substr( $hash, 0, 5 );
3954  }
3955 
3963  public static function transformCssMedia( $media ) {
3964  global $wgRequest;
3965 
3966  // https://www.w3.org/TR/css3-mediaqueries/#syntax
3967  $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
3968 
3969  // Switch in on-screen display for media testing
3970  $switches = [
3971  'printable' => 'print',
3972  'handheld' => 'handheld',
3973  ];
3974  foreach ( $switches as $switch => $targetMedia ) {
3975  if ( $wgRequest->getBool( $switch ) ) {
3976  if ( $media == $targetMedia ) {
3977  $media = '';
3978  } elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
3979  /* This regex will not attempt to understand a comma-separated media_query_list
3980  *
3981  * Example supported values for $media:
3982  * 'screen', 'only screen', 'screen and (min-width: 982px)' ),
3983  * Example NOT supported value for $media:
3984  * '3d-glasses, screen, print and resolution > 90dpi'
3985  *
3986  * If it's a print request, we never want any kind of screen stylesheets
3987  * If it's a handheld request (currently the only other choice with a switch),
3988  * we don't want simple 'screen' but we might want screen queries that
3989  * have a max-width or something, so we'll pass all others on and let the
3990  * client do the query.
3991  */
3992  if ( $targetMedia == 'print' || $media == 'screen' ) {
3993  return null;
3994  }
3995  }
3996  }
3997  }
3998 
3999  return $media;
4000  }
4001 
4010  public function addWikiMsg( ...$args ) {
4011  $name = array_shift( $args );
4012  $this->addWikiMsgArray( $name, $args );
4013  }
4014 
4023  public function addWikiMsgArray( $name, $args ) {
4024  $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
4025  }
4026 
4053  public function wrapWikiMsg( $wrap, ...$msgSpecs ) {
4054  $msgSpecs = array_values( $msgSpecs );
4055  $s = $wrap;
4056  foreach ( $msgSpecs as $n => $spec ) {
4057  if ( is_array( $spec ) ) {
4058  $args = $spec;
4059  $name = array_shift( $args );
4060  } else {
4061  $args = [];
4062  $name = $spec;
4063  }
4064  $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
4065  }
4066  $this->addWikiTextAsInterface( $s );
4067  }
4068 
4074  public function isTOCEnabled() {
4075  return $this->mEnableTOC;
4076  }
4077 
4085  public static function setupOOUI( $skinName = 'default', $dir = 'ltr' ) {
4087  $theme = $themes[$skinName] ?? $themes['default'];
4088  // For example, 'OOUI\WikimediaUITheme'.
4089  $themeClass = "OOUI\\{$theme}Theme";
4090  OOUI\Theme::setSingleton( new $themeClass() );
4091  OOUI\Element::setDefaultDir( $dir );
4092  }
4093 
4100  public function enableOOUI() {
4101  self::setupOOUI(
4102  strtolower( $this->getSkin()->getSkinName() ),
4103  $this->getLanguage()->getDir()
4104  );
4105  $this->addModuleStyles( [
4106  'oojs-ui-core.styles',
4107  'oojs-ui.styles.indicators',
4108  'mediawiki.widgets.styles',
4109  'oojs-ui-core.icons',
4110  ] );
4111  }
4112 
4123  public function getCSPNonce() {
4124  return $this->CSP->getNonce();
4125  }
4126 
4133  public function getCSP() {
4134  return $this->CSP;
4135  }
4136 }
getPreventClickjacking()
Get the prevent-clickjacking flag.
setContext(IContextSource $context)
isDisabled()
Return whether the output will be completely disabled.
static linkedScript( $url, $nonce=null)
Output a "<script>" tag linking to the given URL, e.g., "<script src=foo.js></script>".
Definition: Html.php:596
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
static static static getSkinThemeMap()
Return a map of skin names (in lowercase) to OOUI theme names, defining which theme a given skin shou...
getModules( $filter=false, $position=null, $param='mModules', $type=ResourceLoaderModule::TYPE_COMBINED)
Get the list of modules to include on this page.
Definition: OutputPage.php:535
$response
static newFromContext(IContextSource $context)
Get a ParserOptions object from a IContextSource object.
ResourceLoader $mResourceLoader
Definition: OutputPage.php:163
setArray( $array)
Set the link list to a given 2-d array First key is the namespace, second is the DB key...
Definition: LinkBatch.php:100
array $rlExemptStyleModules
Definition: OutputPage.php:172
bool $cacheIsFinal
See OutputPage::couldBePublicCached.
Definition: OutputPage.php:71
array $mTemplateIds
Definition: OutputPage.php:178
setConfig(array $vars)
Set mw.config variables.
addWikiTextAsContent( $text, $linestart=true, Title $title=null)
Convert wikitext in the page content language to HTML and add it to the buffer.
$resourceLoader
Definition: load.php:44
getHeadItemsArray()
Get an array of head items.
Definition: OutputPage.php:631
considerCacheSettingsFinal()
Set the expectation that cache control will not change after this point.
addStyle( $style, $media='', $condition='', $dir='')
Add a local or specified stylesheet, with the given media options.
sendCacheControl()
Send cache control HTTP headers.
setModuleStyles(array $modules)
Ensure the styles of one or more modules are loaded.
static linkedStyle( $url, $media='all')
Output a "<link rel=stylesheet>" linking to the given URL for the given media type (if any)...
Definition: Html.php:648
buildExemptModules()
Build exempt modules and legacy non-ResourceLoader styles.
$mScripts
Used for JavaScript (predates ResourceLoader)
Definition: OutputPage.php:139
int $mCdnMaxage
Cache stuff.
Definition: OutputPage.php:238
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:231
setFileVersion( $file)
Set the displayed file version.
getHTMLTitle()
Return the "HTML title", i.e.
Definition: OutputPage.php:906
int $mCdnMaxageLimit
Upper limit on mCdnMaxage.
Definition: OutputPage.php:240
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
styleLink( $style, array $options)
Generate <link> tags for stylesheets.
isTOCEnabled()
Whether the output has a table of contents.
static getRequestId()
Get the unique request ID.
Definition: WebRequest.php:309
$wgVersion
MediaWiki version number.
setTarget( $target)
Sets ResourceLoader target for load.php links.
Definition: OutputPage.php:591
bool $mEnableTOC
Whether parser output contains a table of contents.
Definition: OutputPage.php:301
setRevisionId( $revid)
Set the revision ID which will be seen by the wiki text parser for things such as embedded {{REVISION...
addBodyClasses( $classes)
Add a class to the <body> element.
Definition: OutputPage.php:677
callable [] $contentOverrideCallbacks
Definition: OutputPage.php:315
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
setCdnMaxage( $maxage)
Set the value of the "s-maxage" part of the "Cache-control" HTTP header.
string [][] $mMetatags
Should be private.
Definition: OutputPage.php:48
static newMainPage(MessageLocalizer $localizer=null)
Create a new Title for the Main Page.
Definition: Title.php:646
getCanonicalUrl()
Returns the URL to be used for the <link rel="canonical"> if one is set.
Definition: OutputPage.php:446
int $mContainsNewMagic
Definition: OutputPage.php:203
$IP
Definition: WebStart.php:41
setStatusCode( $statusCode)
Set the HTTP status code to send with the output.
Definition: OutputPage.php:384
setArticleFlag( $newVal)
Set whether the displayed content is related to the source of the corresponding article on the wiki S...
addModules( $modules)
Load one or more ResourceLoader modules on this page.
Definition: OutputPage.php:549
string $mPageTitle
The contents of.
Definition: OutputPage.php:59
setLanguageLinks(array $newLinkArray)
Reset the language links and add new language links.
The Message class provides methods which fulfil two basic services:
Definition: Message.php:162
setSubtitle( $str)
Replace the subtitle with $str.
string $mInlineStyles
Inline CSS styles.
Definition: OutputPage.php:142
preventClickjacking( $flag=null)
Get or set the prevent-clickjacking flag.
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
addCategoryLinks(array $categories)
Add an array of categories, with names in the keys.
haveCacheVaryCookies()
Check if the request has a cache-varying cookie header If it does, it&#39;s very important that we don&#39;t ...
getPageTitle()
Return the "page title", i.e.
Definition: OutputPage.php:953
setCopyrightUrl( $url)
Set the copyright URL to send with the output.
Definition: OutputPage.php:375
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
if(!isset( $args[0])) $lang
warnModuleTargetFilter( $moduleName)
Definition: OutputPage.php:511
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing &#39;/&#39;...
Definition: Html.php:251
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:209
redirect( $url, $responsecode='302')
Redirect to $url rather than displaying the normal page.
Definition: OutputPage.php:352
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
& getFileSearchOptions()
array $mHeadItems
Array of elements in "<head>".
Definition: OutputPage.php:151
showFatalError( $message)
Output an error page.
allowClickjacking()
Turn off frame-breaking.
loadSkinModules( $sk)
Transfer styles and JavaScript modules from skin.
parseInlineAsInterface( $text, $linestart=true)
Parse wikitext in the user interface language, strip paragraph wrapper, and return the HTML...
array $mModules
Definition: OutputPage.php:157
const NS_SPECIAL
Definition: Defines.php:49
const PROTO_CURRENT
Definition: Defines.php:202
string null $mTarget
ResourceLoader target for load.php links.
Definition: OutputPage.php:296
addWikiTextTitleInternal( $text, Title $title, $linestart, $interface, $wrapperClass=null)
Add wikitext with a custom Title object.
getArticleBodyOnly()
Return whether the output will contain only the body of the article.
Definition: OutputPage.php:697
array $limitReportJSData
Profiling data.
Definition: OutputPage.php:309
prependHTML( $text)
Prepend $text to the body HTML.
static makeConfigSetScript(array $configuration)
Returns JS code which will set the MediaWiki configuration array to the given value.
setProperty( $name, $value)
Set an additional output property.
Definition: OutputPage.php:708
addVaryHeader( $header, array $option=null)
Add an HTTP header that will influence on the cache.
static makeLoaderQuery(array $modules, $lang, $skin, $user=null, $version=null, $debug=false, $only=null, $printable=false, $handheld=false, array $extraQuery=[])
Build a query array (array representation of query string) for load.php.
wrapWikiMsg( $wrap,... $msgSpecs)
This function takes a number of message/argument specifications, wraps them in some overall structure...
wrapWikiTextAsInterface( $wrapperClass, $text)
Convert wikitext in the user interface language to HTML and add it to the buffer with a <div class="$...
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
getUnprefixedDisplayTitle()
Returns page display title without namespace prefix if possible.
Definition: OutputPage.php:991
filterModules(array $modules, $position=null, $type=ResourceLoaderModule::TYPE_COMBINED)
Filter an array of modules to remove insufficiently trustworthy members, and modules which are no lon...
Definition: OutputPage.php:491
string $mPageLinkTitle
Used by skin template.
Definition: OutputPage.php:148
userCanPreview()
To make it harder for someone to slip a user a fake JavaScript or CSS preview, a random token is asso...
setPrintable()
Set the page as printable, i.e.
string null $copyrightUrl
The URL to send in a <link> element with rel=license.
Definition: OutputPage.php:306
adaptCdnTTL( $mtime, $minTTL=0, $maxTTL=0)
Get TTL in [$minTTL,$maxTTL] and pass it to lowerCdnMaxage()
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1853
disable()
Disable output completely, i.e.
addWikiMsg(... $args)
Add a wikitext-formatted message to the output.
Content for JavaScript pages.
formatPermissionsErrorMessage(array $errors, $action=null)
Format a list of error messages.
static inlineScript( $contents, $nonce=null)
Output an HTML script tag with the given contents.
Definition: Html.php:572
addReturnTo( $title, array $query=[], $text=null, $options=[])
Add a "return to" link pointing to a specified title.
checkLastModified( $timestamp)
checkLastModified tells the client to use the client-cached page if possible.
Definition: OutputPage.php:734
string $mRevisionTimestamp
Definition: OutputPage.php:253
array $mLanguageLinks
Array of Interwiki Prefixed (non DB key) Titles (e.g.
Definition: OutputPage.php:131
setHTMLTitle( $name)
"HTML title" means the contents of "<title>".
Definition: OutputPage.php:893
IContextSource $context
array Title $mRedirectedFrom
If the current page was reached through a redirect, $mRedirectedFrom contains the Title of the redire...
Definition: OutputPage.php:286
string $displayTitle
The displayed title of the page.
Definition: OutputPage.php:68
setPageTitle( $name)
"Page title" means the contents of <h1>.
Definition: OutputPage.php:931
getRlClient()
Call this to freeze the module queue and JS config and create a formatter.
getFileSearchOptions()
Get the files used on this page.
setCategoryLinks(array $categories)
Reset the category links (but not the category list) and add $categories.
isSyndicated()
Should we output feed links for this page?
static setupOOUI( $skinName='default', $dir='ltr')
Helper function to setup the PHP implementation of OOUI to use in this request.
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
array $styles
An array of stylesheet filenames (relative from skins path), with options for CSS media...
Definition: OutputPage.php:266
isPrintable()
Return whether the page is "printable".
$mProperties
Additional key => value data.
Definition: OutputPage.php:291
getNamespace()
Get the namespace index.
string bool $mCanonicalUrl
Definition: OutputPage.php:54
parseInternal( $text, $title, $linestart, $tidy, $interface, $language)
Parse wikitext and return the HTML (internal implementation helper)
array bool $mDoNothing
Whether output is disabled.
Definition: OutputPage.php:198
getAllowedModules( $type)
Show what level of JavaScript / CSS untrustworthiness is allowed on this page.
setRedirectedFrom( $t)
Set $mRedirectedFrom, the Title of the page which redirected us to the current page.
Definition: OutputPage.php:915
bool $mPreventClickjacking
Controls if anti-clickjacking / frame-breaking headers will be sent.
Definition: OutputPage.php:247
static stripOuterParagraph( $html)
Strip outer.
Definition: Parser.php:6387
lowerCdnMaxage( $maxage)
Set the value of the "s-maxage" part of the "Cache-control" HTTP header to $maxage if that is lower t...
addInlineStyle( $style_css, $flip='noflip')
Adds inline CSS styles Internal use only.
getPageClasses( $title)
TODO: document.
Definition: Skin.php:439
array $mJsConfigVars
Definition: OutputPage.php:175
getJsConfigVars()
Get the javascript config vars to include on this page.
enableOOUI()
Add ResourceLoader module styles for OOUI and set up the PHP implementation of it for use with MediaW...
addToBodyAttributes( $out, &$bodyAttrs)
This will be called by OutputPage::headElement when it is creating the "<body>" tag, skins can override it if they have a need to add in any body attributes or classes of their own.
Definition: Skin.php:493
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
bool $mPrintable
We have to set isPrintable().
Definition: OutputPage.php:98
A mutable version of ResourceLoaderContext.
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e.g.
clearSubtitle()
Clear the subtitles.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition: LinkBatch.php:34
bool $mIsArticleRelated
Stores "article flag" toggle.
Definition: OutputPage.php:89
wfGetAllCallers( $limit=3)
Return a string consisting of callers in the stack.
canUseWikiPage()
Check whether a WikiPage object can be get with getWikiPage().
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
getCdnCacheEpoch( $reqTime, $maxAge)
Definition: OutputPage.php:822
array $mAllowedModules
What level of &#39;untrustworthiness&#39; is allowed in CSS/JS modules loaded on this page?
Definition: OutputPage.php:193
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
static array $cacheVaryCookies
A cache of the names of the cookies that will influence the cache.
Definition: OutputPage.php:330
bool $mNoGallery
Comes from the parser.
Definition: OutputPage.php:235
output( $return=false)
Finally, all the text has been munged and accumulated into the object, let&#39;s actually output it: ...
setCopyright( $hasCopyright)
Set whether the standard copyright should be shown for the current page.
getTemplateIds()
Get the templates used on this page.
if( $line===false) $args
Definition: mcc.php:124
addParserOutput(ParserOutput $parserOutput, $poOptions=[])
Add everything from a ParserOutput object.
setFollowPolicy( $policy)
Set the follow policy for the page, but leave the index policy un- touched.
Definition: OutputPage.php:880
getHTML()
Get the body HTML.
getConfig()
Get the site configuration.
static buildBacklinkSubtitle(Title $title, $query=[])
Build message object for a subtitle containing a backlink to a page.
$modules
static stripAllTags( $html)
Take a fragment of (potentially invalid) HTML and return a version with any tags removed, encoded as plain text.
Definition: Sanitizer.php:2041
getLanguageLinks()
Get the list of language links.
array $mFileVersion
Definition: OutputPage.php:256
array $mModuleStyles
Definition: OutputPage.php:160
isRevisionCurrent()
Whether the revision displayed is the latest revision of the page.
bool $mHasCopyright
Is the content subject to copyright.
Definition: OutputPage.php:92
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness, which urlencode encodes by default.
makeResourceLoaderLink( $modules, $only, array $extraQuery=[])
Explicily load or embed modules on a page.
enableClientCache( $state)
Use enableClientCache(false) to force it to send nocache headers.
addAcceptLanguage()
T23672: Add Accept-Language to Vary header if there&#39;s no &#39;variant&#39; parameter in GET.
Interface for configuration instances.
Definition: Config.php:28
parse( $text, $linestart=true, $interface=false, $language=null)
Parse wikitext and return the HTML.
wfCgiToArray( $query)
This is the logical opposite of wfArrayToCgi(): it accepts a query string as its argument and returns...
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition: Xml.php:677
setSyndicated( $show=true)
Add or remove feed links in the page header This is mainly kept for backward compatibility, see OutputPage::addFeedLink() for the new version.
addSubtitle( $str)
Add $str to the subtitle.
headElement(Skin $sk, $includeStyle=true)
getJSVars()
Get an array containing the variables to be set in mw.config in JavaScript.
setExemptStates(array $states)
Set state of special modules that are handled by the caller manually.
static transformResourcePath(Config $config, $path)
Transform path to web-accessible static resource.
addCategoryLinksToLBAndGetResult(array $categories)
setRobotPolicy( $policy)
Set the robot policy for the page: http://www.robotstxt.org/meta.html
Definition: OutputPage.php:848
array array $mIndicators
Definition: OutputPage.php:128
addMeta( $name, $val)
Add a new "<meta>" tag To add an http-equiv meta tag, precede the name with "http:".
Definition: OutputPage.php:395
showsCopyright()
Return whether the standard copyright should be shown for the current page.
addLinkHeader( $header)
Add an HTTP Link: header.
$mFeedLinks
Handles the Atom / RSS links.
Definition: OutputPage.php:216
static getContentText(Content $content=null)
Convenience function for getting flat text from a Content object.
getSubtitle()
Get the subtitle.
isArticleRelated()
Return whether this page is related an article on the wiki.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
getCSP()
Get the ContentSecurityPolicy object.
static preloadTitleInfo(ResourceLoaderContext $context, IDatabase $db, array $moduleNames)
hasHeadItem( $name)
Check if the header item $name is already set.
Definition: OutputPage.php:667
$mFeedLinksAppendQuery
Definition: OutputPage.php:186
getMetaTags()
Returns the current <meta> tags.
Definition: OutputPage.php:405
$mLinkHeader
Link: header contents.
Definition: OutputPage.php:320
getCategoryLinks()
Get the list of category links, in a 2-D array with the following format: $arr[$type][] = $link...
bool $mArticleBodyOnly
Flag if output should only contain the body of the article.
Definition: OutputPage.php:222
static mergeAttributes( $a, $b)
Merge two sets of HTML attributes.
Definition: Sanitizer.php:936
prepareErrorPage( $pageTitle, $htmlTitle=false)
Prepare this object to display an error page; disable caching and indexing, clear the current text an...
addTemplate(&$template)
Add the output of a QuickTemplate to the output buffer.
clearHTML()
Clear the body HTML.
getFrameOptions()
Get the X-Frame-Options header value (without the name part), or false if there isn&#39;t one...
Load and configure a ResourceLoader client on an HTML page.
array $mAdditionalBodyClasses
Additional <body> classes; there are also <body> classes from other sources.
Definition: OutputPage.php:154
parseAsInterface( $text, $linestart=true)
Parse wikitext in the user interface language and return the HTML.
addFeedLink( $format, $href)
Add a feed link to the page header.
getIndicators()
Get the indicators associated with this page.
getContext()
Get the base IContextSource object.
getDBkey()
Get the main part with underscores.
static combineWrappedStrings(array $chunks)
Combine WrappedString chunks and filter out empty ones.
showNewSectionLink()
Show an "add new section" link?
const NS_CATEGORY
Definition: Defines.php:74
addJsConfigVars( $keys, $value=null)
Add one or more variables to be set in mw.config in JavaScript.
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:315
getCategories( $type='all')
Get the list of category names this page belongs to.
string $mHTMLtitle
Stores contents of "<title>" tag.
Definition: OutputPage.php:80
bool $mHideNewSectionLink
Definition: OutputPage.php:228
string $mBodytext
Contains all of the "<body>" content.
Definition: OutputPage.php:77
buildCssLinksArray()
getRlClientContext()
getDisplayTitle()
Returns page display title.
Definition: OutputPage.php:976
getAdvertisedFeedTypes()
Return effective list of advertised feed types.
static runWithoutAbort( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:231
addHeadItems( $values)
Add one or more head items to the output.
Definition: OutputPage.php:657
getHtmlElementAttributes()
Return values for <html> element.
Definition: Skin.php:477
static escapeClass( $class)
Given a value, escape it so that it can be used as a CSS class and return it.
Definition: Sanitizer.php:1418
array $mVaryHeader
Headers that cause the cache to vary.
Definition: OutputPage.php:276
preventClickjacking( $enable=true)
Set a flag which will cause an X-Frame-Options header appropriate for edit pages to be sent...
static addModules(OutputPage $out)
Add ResourceLoader modules to the OutputPage object if debugging is enabled.
Definition: MWDebug.php:120
addLink(array $linkarr)
Add a new <link> tag to the page header.
Definition: OutputPage.php:416
addModuleStyles( $modules)
Load the styles of one or more ResourceLoader modules on this page.
Definition: OutputPage.php:575
const PROTO_RELATIVE
Definition: Defines.php:201
array $mCategories
Definition: OutputPage.php:122
static getActionName(IContextSource $context)
Get the action that will be executed, not necessarily the one passed passed through the "action" requ...
Definition: Action.php:123
couldBePublicCached()
Whether the output might become publicly cached.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don&#39;t need a full Title object...
Definition: SpecialPage.php:83
$header
static normalizeCharReferences( $text)
Ensure that any entities and character references are legal for XML and XHTML specifically.
Definition: Sanitizer.php:1569
getModuleStyles( $filter=false, $position=null)
Get the list of style-only modules to load on this page.
Definition: OutputPage.php:560
bool $mNewSectionLink
Definition: OutputPage.php:225
reduceAllowedModules( $type, $level)
Limit the highest level of CSS/JS untrustworthiness allowed.
static htmlHeader(array $attribs=[])
Constructs the opening html-tag with necessary doctypes depending on global variables.
Definition: Html.php:963
__construct(IContextSource $context)
Constructor for OutputPage.
Definition: OutputPage.php:338
static makeInlineScript( $script, $nonce=null)
Returns an HTML script tag that runs given JS code after startup and base modules.
static makeLoad(ResourceLoaderContext $mainContext, array $modules, $only, array $extraQuery=[], $nonce=null)
Explicily load or embed modules on a page.
static newFromAnon()
Get a ParserOptions object for an anonymous user.
addScriptFile( $file, $unused=null)
Add a JavaScript file to be loaded as <script> on this page.
Definition: OutputPage.php:469
getFeedAppendQuery()
Will currently always return null.
addScript( $script)
Add raw HTML to the list of scripts (including <script> tag, etc.) Internal use only...
Definition: OutputPage.php:457
array $mCategoryLinks
Definition: OutputPage.php:119
static getSelectFields()
Fields that LinkCache needs to select.
Definition: LinkCache.php:219
parseAsContent( $text, $linestart=true)
Parse wikitext in the page content language and return the HTML.
setTitle(Title $t)
Set the Title object to use.
addBacklinkSubtitle(Title $title, $query=[])
Add a subtitle containing a backlink to a page.
static transformFilePath( $remotePathPrefix, $localPath, $file)
Utility method for transformResourceFilePath().
int $mStatusCode
Definition: OutputPage.php:110
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:610
array $mLinktags
Definition: OutputPage.php:51
addLanguageLinks(array $newLinkArray)
Add new language links.
setArticleBodyOnly( $only)
Set whether the output should only contain the body of the article, without any skin, sidebar, etc.
Definition: OutputPage.php:688
setLastModified( $timestamp)
Override the last modified timestamp.
Definition: OutputPage.php:836
addHeadItem( $name, $value)
Add or replace a head item to the output.
Definition: OutputPage.php:647
string $mLastModified
Used for sending cache control.
Definition: OutputPage.php:116
wfSetVar(&$dest, $source, $force=false)
Sets dest to source and returns the original value of dest If source is NULL, it just returns the val...
static inDebugMode()
Determine whether debug mode was requested Order of priority is 1) request param, 2) cookie...
addContentOverrideCallback(callable $callback)
Add a callback for mapping from a Title to a Content object, for things like page preview...
Definition: OutputPage.php:622
const PROTO_CANONICAL
Definition: Defines.php:203
static transformCssMedia( $media)
Transform "media" attribute based on request parameters.
addParserOutputContent(ParserOutput $parserOutput, $poOptions=[])
Add the HTML and enhancements for it (like ResourceLoader modules) associated with a ParserOutput obj...
versionRequired( $version)
Display an error page indicating that a given version of MediaWiki is required to use it...
int $mRevisionId
To include the variable {{REVISIONID}}.
Definition: OutputPage.php:250
getSyndicationLinks()
Return URLs for each supported syndication format for this page.
getText( $options=[])
Get the output HTML.
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
addContentOverride(LinkTarget $target, Content $content)
Add a mapping from a LinkTarget to a Content, for things like page preview.
Definition: OutputPage.php:602
array $contentOverrides
Map Title to Content.
Definition: OutputPage.php:312
feedLink( $type, $url, $text)
Generate a "<link rel/>" for a feed.
getOriginTrials()
Get the Origin-Trial header values.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
string $mRedirectCode
Definition: OutputPage.php:184
array $mImageTimeKeys
Definition: OutputPage.php:181
getResourceLoader()
Get a ResourceLoader object associated with this OutputPage.
getFeaturePolicyReportOnly()
getRevisionId()
Get the displayed revision ID.
wfClearOutputBuffers()
More legible than passing a &#39;false&#39; parameter to wfResetOutputBuffers():
getFileVersion()
Get the displayed file version.
returnToMain( $unused=null, $returnto=null, $returntoquery=null)
Add a "return to" link pointing to a specified title, or the title indicated in the request...
setArticleRelated( $newVal)
Set whether this page is related an article on the wiki Setting false will cause the change of "artic...
userCanEditOrCreate(User $user, LinkTarget $title)
forceHideNewSectionLink()
Forcibly hide the new section link?
static isXmlMimeType( $mimetype)
Determines if the given MIME type is xml.
Definition: Html.php:1001
isRedirect( $flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition: Title.php:3190
getBottomScripts()
JS stuff to put at the bottom of the <body>.
addParserOutputMetadata(ParserOutput $parserOutput)
Add all metadata associated with a ParserOutput object, but without the actual HTML.
static inlineStyle( $contents, $media='all', $attribs=[])
Output a "<style>" tag with the given contents for the given media type (if any). ...
Definition: Html.php:619
addHTML( $text)
Append $text to the body HTML.
getRedirect()
Get the URL to redirect to, or an empty string if not redirect URL set.
Definition: OutputPage.php:363
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:729
addWikiTextAsInterface( $text, $linestart=true, Title $title=null)
Convert wikitext in the user interface language to HTML and add it to the buffer. ...
const DB_REPLICA
Definition: defines.php:25
$content
Definition: router.php:78
array $mSubtitle
Contains the page subtitle.
Definition: OutputPage.php:104
showLagWarning( $lag)
Show a warning about replica DB lag.
static removeHTMLtags( $text, $processCallback=null, $args=[], $extratags=[], $removetags=[], $warnCallback=null)
Cleans up HTML, removes dangerous tags and attributes, and removes HTML comments. ...
Definition: Sanitizer.php:497
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
showPermissionsErrorPage(array $errors, $action=null)
Output a standard permission error page.
getCacheVaryCookies()
Get the list of cookie names that will influence the cache.
ResourceLoaderContext $rlClientContext
Definition: OutputPage.php:169
addElement( $element, array $attribs=[], $contents='')
Shortcut for adding an Html::element via addHTML.
setModules(array $modules)
Ensure one or more modules are loaded.
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:61
getCSPNonce()
Get (and set if not yet set) the CSP nonce.
parseInline( $text, $linestart=true, $interface=false)
Parse wikitext, strip paragraph wrapper, and return the HTML.
parserOptions( $options=null)
Get/set the ParserOptions object to use for wikitext parsing.
setFeedAppendQuery( $val)
Add default feeds to the page header This is mainly kept for backward compatibility, see OutputPage::addFeedLink() for the new version.
getWikiPage()
Get the WikiPage object.
getSkinName()
Definition: Skin.php:158
setIndicators(array $indicators)
Add an array of indicators, with their identifiers as array keys and HTML contents as values...
setCanonicalUrl( $url)
Set the URL to be used for the <link rel="canonical">.
Definition: OutputPage.php:435
bool $mIsArticle
Is the displayed content related to the source of the corresponding wiki article. ...
Definition: OutputPage.php:86
disallowUserJs()
Do not allow scripts which can be modified by wiki users to load on this page; only allow scripts bun...
getOrigin()
Get this module&#39;s origin.
setIndexPolicy( $policy)
Set the index policy for the page, but leave the follow policy un- touched.
Definition: OutputPage.php:866
getVaryHeader()
Return a Vary: header on which to vary caches.
addInlineScript( $script)
Add a self-contained script tag with the given contents Internal use only.
Definition: OutputPage.php:479
ContentSecurityPolicy $CSP
Definition: OutputPage.php:325
string $mRedirect
Definition: OutputPage.php:107
static bcp47( $code)
Get the normalised IETF language tag See unit test for examples.
getRevisionTimestamp()
Get the timestamp of displayed revision.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
getLinkTags()
Returns the current <link> tags.
Definition: OutputPage.php:426
setRevisionTimestamp( $timestamp)
Set the timestamp of the revision which will be displayed.
ResourceLoaderClientHtml $rlClient
Definition: OutputPage.php:166
isArticle()
Return whether the content displayed page is related to the source of the corresponding article on th...
addWikiMsgArray( $name, $args)
Add a wikitext-formatted message to the output.
Context object that contains information about the state of a specific ResourceLoader web request...
ParserOptions $mParserOptions
lazy initialised, use parserOptions()
Definition: OutputPage.php:209
showErrorPage( $title, $msg, $params=[])
Output a standard error page.
getLinkHeader()
Return a Link: header.
addParserOutputText(ParserOutput $parserOutput, $poOptions=[])
Add the HTML associated with a ParserOutput object, without any metadata.
getProperty( $name)
Get an additional output property.
Definition: OutputPage.php:719
setDisplayTitle( $html)
Same as page title but only contains name of the page, not any other text.
Definition: OutputPage.php:964
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:317
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition: Article.php:1050