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 
58  private $mPageTitle = '';
59 
67  private $displayTitle;
68 
73  public $mBodytext = '';
74 
76  private $mHTMLtitle = '';
77 
82  private $mIsArticle = false;
83 
85  private $mIsArticleRelated = true;
86 
88  private $mHasCopyright = false;
89 
94  private $mPrintable = false;
95 
100  private $mSubtitle = [];
101 
103  public $mRedirect = '';
104 
106  protected $mStatusCode;
107 
112  protected $mLastModified = '';
113 
115  protected $mCategoryLinks = [];
116 
118  protected $mCategories = [
119  'hidden' => [],
120  'normal' => [],
121  ];
122 
124  protected $mIndicators = [];
125 
127  private $mLanguageLinks = [];
128 
135  private $mScripts = '';
136 
138  protected $mInlineStyles = '';
139 
144  public $mPageLinkTitle = '';
145 
147  protected $mHeadItems = [];
148 
150  protected $mAdditionalBodyClasses = [];
151 
153  protected $mModules = [];
154 
156  protected $mModuleStyles = [];
157 
159  protected $mResourceLoader;
160 
162  private $rlClient;
163 
166 
169 
171  protected $mJsConfigVars = [];
172 
174  protected $mTemplateIds = [];
175 
177  protected $mImageTimeKeys = [];
178 
180  public $mRedirectCode = '';
181 
183 
189  protected $mAllowedModules = [
191  ];
192 
194  protected $mDoNothing = false;
195 
196  // Parser related.
197 
199  protected $mContainsNewMagic = 0;
200 
205  protected $mParserOptions = null;
206 
212  private $mFeedLinks = [];
213 
214  // Gwicke work on squid caching? Roughly from 2003.
215  protected $mEnableClientCache = true;
216 
218  private $mArticleBodyOnly = false;
219 
221  protected $mNewSectionLink = false;
222 
224  protected $mHideNewSectionLink = false;
225 
231  public $mNoGallery = false;
232 
234  protected $mCdnMaxage = 0;
236  protected $mCdnMaxageLimit = INF;
237 
243  protected $mPreventClickjacking = true;
244 
246  private $mRevisionId = null;
247 
250 
252  protected $mFileVersion = null;
253 
262  protected $styles = [];
263 
264  private $mIndexPolicy = 'index';
265  private $mFollowPolicy = 'follow';
266 
272  private $mVaryHeader = [
273  'Accept-Encoding' => null,
274  ];
275 
283 
287  private $mProperties = [];
288 
292  private $mTarget = null;
293 
297  private $mEnableTOC = false;
298 
302  private $copyrightUrl;
303 
305  private $limitReportJSData = [];
306 
308  private $contentOverrides = [];
309 
312 
316  private $mLinkHeader = [];
317 
321  private $CSPNonce;
322 
326  private static $cacheVaryCookies = null;
327 
335  $this->setContext( $context );
336  }
337 
344  public function redirect( $url, $responsecode = '302' ) {
345  # Strip newlines as a paranoia check for header injection in PHP<5.1.2
346  $this->mRedirect = str_replace( "\n", '', $url );
347  $this->mRedirectCode = $responsecode;
348  }
349 
355  public function getRedirect() {
356  return $this->mRedirect;
357  }
358 
367  public function setCopyrightUrl( $url ) {
368  $this->copyrightUrl = $url;
369  }
370 
376  public function setStatusCode( $statusCode ) {
377  $this->mStatusCode = $statusCode;
378  }
379 
387  function addMeta( $name, $val ) {
388  array_push( $this->mMetatags, [ $name, $val ] );
389  }
390 
397  public function getMetaTags() {
398  return $this->mMetatags;
399  }
400 
408  function addLink( array $linkarr ) {
409  array_push( $this->mLinktags, $linkarr );
410  }
411 
418  public function getLinkTags() {
419  return $this->mLinktags;
420  }
421 
427  function setCanonicalUrl( $url ) {
428  $this->mCanonicalUrl = $url;
429  }
430 
438  public function getCanonicalUrl() {
439  return $this->mCanonicalUrl;
440  }
441 
449  function addScript( $script ) {
450  $this->mScripts .= $script;
451  }
452 
461  public function addScriptFile( $file, $unused = null ) {
462  $this->addScript( Html::linkedScript( $file, $this->getCSPNonce() ) );
463  }
464 
471  public function addInlineScript( $script ) {
472  $this->mScripts .= Html::inlineScript( "\n$script\n", $this->getCSPNonce() ) . "\n";
473  }
474 
483  protected function filterModules( array $modules, $position = null,
485  ) {
487  $filteredModules = [];
488  foreach ( $modules as $val ) {
489  $module = $resourceLoader->getModule( $val );
490  if ( $module instanceof ResourceLoaderModule
491  && $module->getOrigin() <= $this->getAllowedModules( $type )
492  ) {
493  if ( $this->mTarget && !in_array( $this->mTarget, $module->getTargets() ) ) {
494  $this->warnModuleTargetFilter( $module->getName() );
495  continue;
496  }
497  $filteredModules[] = $val;
498  }
499  }
500  return $filteredModules;
501  }
502 
503  private function warnModuleTargetFilter( $moduleName ) {
504  static $warnings = [];
505  if ( isset( $warnings[$this->mTarget][$moduleName] ) ) {
506  return;
507  }
508  $warnings[$this->mTarget][$moduleName] = true;
509  $this->getResourceLoader()->getLogger()->debug(
510  'Module "{module}" not loadable on target "{target}".',
511  [
512  'module' => $moduleName,
513  'target' => $this->mTarget,
514  ]
515  );
516  }
517 
527  public function getModules( $filter = false, $position = null, $param = 'mModules',
529  ) {
530  $modules = array_values( array_unique( $this->$param ) );
531  return $filter
532  ? $this->filterModules( $modules, null, $type )
533  : $modules;
534  }
535 
541  public function addModules( $modules ) {
542  $this->mModules = array_merge( $this->mModules, (array)$modules );
543  }
544 
552  public function getModuleStyles( $filter = false, $position = null ) {
553  return $this->getModules( $filter, null, 'mModuleStyles',
555  );
556  }
557 
567  public function addModuleStyles( $modules ) {
568  $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
569  }
570 
574  public function getTarget() {
575  return $this->mTarget;
576  }
577 
583  public function setTarget( $target ) {
584  $this->mTarget = $target;
585  }
586 
594  public function addContentOverride( LinkTarget $target, Content $content ) {
595  if ( !$this->contentOverrides ) {
596  // Register a callback for $this->contentOverrides on the first call
597  $this->addContentOverrideCallback( function ( LinkTarget $target ) {
598  $key = $target->getNamespace() . ':' . $target->getDBkey();
599  return $this->contentOverrides[$key] ?? null;
600  } );
601  }
602 
603  $key = $target->getNamespace() . ':' . $target->getDBkey();
604  $this->contentOverrides[$key] = $content;
605  }
606 
614  public function addContentOverrideCallback( callable $callback ) {
615  $this->contentOverrideCallbacks[] = $callback;
616  }
617 
623  function getHeadItemsArray() {
624  return $this->mHeadItems;
625  }
626 
639  public function addHeadItem( $name, $value ) {
640  $this->mHeadItems[$name] = $value;
641  }
642 
649  public function addHeadItems( $values ) {
650  $this->mHeadItems = array_merge( $this->mHeadItems, (array)$values );
651  }
652 
659  public function hasHeadItem( $name ) {
660  return isset( $this->mHeadItems[$name] );
661  }
662 
669  public function addBodyClasses( $classes ) {
670  $this->mAdditionalBodyClasses = array_merge( $this->mAdditionalBodyClasses, (array)$classes );
671  }
672 
680  public function setArticleBodyOnly( $only ) {
681  $this->mArticleBodyOnly = $only;
682  }
683 
689  public function getArticleBodyOnly() {
691  }
692 
700  public function setProperty( $name, $value ) {
701  $this->mProperties[$name] = $value;
702  }
703 
711  public function getProperty( $name ) {
712  return $this->mProperties[$name] ?? null;
713  }
714 
726  public function checkLastModified( $timestamp ) {
727  if ( !$timestamp || $timestamp == '19700101000000' ) {
728  wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
729  return false;
730  }
731  $config = $this->getConfig();
732  if ( !$config->get( 'CachePages' ) ) {
733  wfDebug( __METHOD__ . ": CACHE DISABLED\n" );
734  return false;
735  }
736 
737  $timestamp = wfTimestamp( TS_MW, $timestamp );
738  $modifiedTimes = [
739  'page' => $timestamp,
740  'user' => $this->getUser()->getTouched(),
741  'epoch' => $config->get( 'CacheEpoch' )
742  ];
743  if ( $config->get( 'UseCdn' ) ) {
744  $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, $this->getCdnCacheEpoch(
745  time(),
746  $config->get( 'CdnMaxAge' )
747  ) );
748  }
749  Hooks::run( 'OutputPageCheckLastModified', [ &$modifiedTimes, $this ] );
750 
751  $maxModified = max( $modifiedTimes );
752  $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
753 
754  $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' );
755  if ( $clientHeader === false ) {
756  wfDebug( __METHOD__ . ": client did not send If-Modified-Since header", 'private' );
757  return false;
758  }
759 
760  # IE sends sizes after the date like this:
761  # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
762  # this breaks strtotime().
763  $clientHeader = preg_replace( '/;.*$/', '', $clientHeader );
764 
765  Wikimedia\suppressWarnings(); // E_STRICT system time warnings
766  $clientHeaderTime = strtotime( $clientHeader );
767  Wikimedia\restoreWarnings();
768  if ( !$clientHeaderTime ) {
769  wfDebug( __METHOD__
770  . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
771  return false;
772  }
773  $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
774 
775  # Make debug info
776  $info = '';
777  foreach ( $modifiedTimes as $name => $value ) {
778  if ( $info !== '' ) {
779  $info .= ', ';
780  }
781  $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
782  }
783 
784  wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
785  wfTimestamp( TS_ISO_8601, $clientHeaderTime ), 'private' );
786  wfDebug( __METHOD__ . ": effective Last-Modified: " .
787  wfTimestamp( TS_ISO_8601, $maxModified ), 'private' );
788  if ( $clientHeaderTime < $maxModified ) {
789  wfDebug( __METHOD__ . ": STALE, $info", 'private' );
790  return false;
791  }
792 
793  # Not modified
794  # Give a 304 Not Modified response code and disable body output
795  wfDebug( __METHOD__ . ": NOT MODIFIED, $info", 'private' );
796  ini_set( 'zlib.output_compression', 0 );
797  $this->getRequest()->response()->statusHeader( 304 );
798  $this->sendCacheControl();
799  $this->disable();
800 
801  // Don't output a compressed blob when using ob_gzhandler;
802  // it's technically against HTTP spec and seems to confuse
803  // Firefox when the response gets split over two packets.
805 
806  return true;
807  }
808 
814  private function getCdnCacheEpoch( $reqTime, $maxAge ) {
815  // Ensure Last-Modified is never more than $wgCdnMaxAge in the past,
816  // because even if the wiki page content hasn't changed since, static
817  // resources may have changed (skin HTML, interface messages, urls, etc.)
818  // and must roll-over in a timely manner (T46570)
819  return $reqTime - $maxAge;
820  }
821 
828  public function setLastModified( $timestamp ) {
829  $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp );
830  }
831 
840  public function setRobotPolicy( $policy ) {
841  $policy = Article::formatRobotPolicy( $policy );
842 
843  if ( isset( $policy['index'] ) ) {
844  $this->setIndexPolicy( $policy['index'] );
845  }
846  if ( isset( $policy['follow'] ) ) {
847  $this->setFollowPolicy( $policy['follow'] );
848  }
849  }
850 
858  public function setIndexPolicy( $policy ) {
859  $policy = trim( $policy );
860  if ( in_array( $policy, [ 'index', 'noindex' ] ) ) {
861  $this->mIndexPolicy = $policy;
862  }
863  }
864 
872  public function setFollowPolicy( $policy ) {
873  $policy = trim( $policy );
874  if ( in_array( $policy, [ 'follow', 'nofollow' ] ) ) {
875  $this->mFollowPolicy = $policy;
876  }
877  }
878 
885  public function setHTMLTitle( $name ) {
886  if ( $name instanceof Message ) {
887  $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
888  } else {
889  $this->mHTMLtitle = $name;
890  }
891  }
892 
898  public function getHTMLTitle() {
899  return $this->mHTMLtitle;
900  }
901 
907  public function setRedirectedFrom( $t ) {
908  $this->mRedirectedFrom = $t;
909  }
910 
923  public function setPageTitle( $name ) {
924  if ( $name instanceof Message ) {
925  $name = $name->setContext( $this->getContext() )->text();
926  }
927 
928  # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
929  # but leave "<i>foobar</i>" alone
931  $this->mPageTitle = $nameWithTags;
932 
933  # change "<i>foo&amp;bar</i>" to "foo&bar"
934  $this->setHTMLTitle(
935  $this->msg( 'pagetitle' )->plaintextParams( Sanitizer::stripAllTags( $nameWithTags ) )
936  ->inContentLanguage()
937  );
938  }
939 
945  public function getPageTitle() {
946  return $this->mPageTitle;
947  }
948 
956  public function setDisplayTitle( $html ) {
957  $this->displayTitle = $html;
958  }
959 
968  public function getDisplayTitle() {
970  if ( $html === null ) {
971  $html = $this->getTitle()->getPrefixedText();
972  }
973 
975  }
976 
983  public function getUnprefixedDisplayTitle() {
984  $text = $this->getDisplayTitle();
985  $nsPrefix = $this->getTitle()->getNsText() . ':';
986  $prefix = preg_quote( $nsPrefix, '/' );
987 
988  return preg_replace( "/^$prefix/i", '', $text );
989  }
990 
996  public function setTitle( Title $t ) {
997  $this->getContext()->setTitle( $t );
998  }
999 
1005  public function setSubtitle( $str ) {
1006  $this->clearSubtitle();
1007  $this->addSubtitle( $str );
1008  }
1009 
1015  public function addSubtitle( $str ) {
1016  if ( $str instanceof Message ) {
1017  $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
1018  } else {
1019  $this->mSubtitle[] = $str;
1020  }
1021  }
1022 
1031  public static function buildBacklinkSubtitle( Title $title, $query = [] ) {
1032  if ( $title->isRedirect() ) {
1033  $query['redirect'] = 'no';
1034  }
1035  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1036  return wfMessage( 'backlinksubtitle' )
1037  ->rawParams( $linkRenderer->makeLink( $title, null, [], $query ) );
1038  }
1039 
1046  public function addBacklinkSubtitle( Title $title, $query = [] ) {
1047  $this->addSubtitle( self::buildBacklinkSubtitle( $title, $query ) );
1048  }
1049 
1053  public function clearSubtitle() {
1054  $this->mSubtitle = [];
1055  }
1056 
1062  public function getSubtitle() {
1063  return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
1064  }
1065 
1070  public function setPrintable() {
1071  $this->mPrintable = true;
1072  }
1073 
1079  public function isPrintable() {
1080  return $this->mPrintable;
1081  }
1082 
1086  public function disable() {
1087  $this->mDoNothing = true;
1088  }
1089 
1095  public function isDisabled() {
1096  return $this->mDoNothing;
1097  }
1098 
1104  public function showNewSectionLink() {
1105  return $this->mNewSectionLink;
1106  }
1107 
1113  public function forceHideNewSectionLink() {
1115  }
1116 
1125  public function setSyndicated( $show = true ) {
1126  if ( $show ) {
1127  $this->setFeedAppendQuery( false );
1128  } else {
1129  $this->mFeedLinks = [];
1130  }
1131  }
1132 
1139  protected function getAdvertisedFeedTypes() {
1140  if ( $this->getConfig()->get( 'Feed' ) ) {
1141  return $this->getConfig()->get( 'AdvertisedFeedTypes' );
1142  } else {
1143  return [];
1144  }
1145  }
1146 
1156  public function setFeedAppendQuery( $val ) {
1157  $this->mFeedLinks = [];
1158 
1159  foreach ( $this->getAdvertisedFeedTypes() as $type ) {
1160  $query = "feed=$type";
1161  if ( is_string( $val ) ) {
1162  $query .= '&' . $val;
1163  }
1164  $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
1165  }
1166  }
1167 
1174  public function addFeedLink( $format, $href ) {
1175  if ( in_array( $format, $this->getAdvertisedFeedTypes() ) ) {
1176  $this->mFeedLinks[$format] = $href;
1177  }
1178  }
1179 
1184  public function isSyndicated() {
1185  return count( $this->mFeedLinks ) > 0;
1186  }
1187 
1192  public function getSyndicationLinks() {
1193  return $this->mFeedLinks;
1194  }
1195 
1201  public function getFeedAppendQuery() {
1203  }
1204 
1212  public function setArticleFlag( $newVal ) {
1213  $this->mIsArticle = $newVal;
1214  if ( $newVal ) {
1215  $this->mIsArticleRelated = $newVal;
1216  }
1217  }
1218 
1225  public function isArticle() {
1226  return $this->mIsArticle;
1227  }
1228 
1235  public function setArticleRelated( $newVal ) {
1236  $this->mIsArticleRelated = $newVal;
1237  if ( !$newVal ) {
1238  $this->mIsArticle = false;
1239  }
1240  }
1241 
1247  public function isArticleRelated() {
1248  return $this->mIsArticleRelated;
1249  }
1250 
1256  public function setCopyright( $hasCopyright ) {
1257  $this->mHasCopyright = $hasCopyright;
1258  }
1259 
1269  public function showsCopyright() {
1270  return $this->isArticle() || $this->mHasCopyright;
1271  }
1272 
1279  public function addLanguageLinks( array $newLinkArray ) {
1280  $this->mLanguageLinks = array_merge( $this->mLanguageLinks, $newLinkArray );
1281  }
1282 
1289  public function setLanguageLinks( array $newLinkArray ) {
1290  $this->mLanguageLinks = $newLinkArray;
1291  }
1292 
1298  public function getLanguageLinks() {
1299  return $this->mLanguageLinks;
1300  }
1301 
1307  public function addCategoryLinks( array $categories ) {
1308  if ( !$categories ) {
1309  return;
1310  }
1311 
1312  $res = $this->addCategoryLinksToLBAndGetResult( $categories );
1313 
1314  # Set all the values to 'normal'.
1315  $categories = array_fill_keys( array_keys( $categories ), 'normal' );
1316 
1317  # Mark hidden categories
1318  foreach ( $res as $row ) {
1319  if ( isset( $row->pp_value ) ) {
1320  $categories[$row->page_title] = 'hidden';
1321  }
1322  }
1323 
1324  // Avoid PHP 7.1 warning of passing $this by reference
1325  $outputPage = $this;
1326  # Add the remaining categories to the skin
1327  if ( Hooks::run(
1328  'OutputPageMakeCategoryLinks',
1329  [ &$outputPage, $categories, &$this->mCategoryLinks ] )
1330  ) {
1331  $services = MediaWikiServices::getInstance();
1332  $linkRenderer = $services->getLinkRenderer();
1333  foreach ( $categories as $category => $type ) {
1334  // array keys will cast numeric category names to ints, so cast back to string
1335  $category = (string)$category;
1336  $origcategory = $category;
1337  $title = Title::makeTitleSafe( NS_CATEGORY, $category );
1338  if ( !$title ) {
1339  continue;
1340  }
1341  $services->getContentLanguage()->findVariantLink( $category, $title, true );
1342  if ( $category != $origcategory && array_key_exists( $category, $categories ) ) {
1343  continue;
1344  }
1345  $text = $services->getContentLanguage()->convertHtml( $title->getText() );
1346  $this->mCategories[$type][] = $title->getText();
1347  $this->mCategoryLinks[$type][] = $linkRenderer->makeLink( $title, new HtmlArmor( $text ) );
1348  }
1349  }
1350  }
1351 
1356  protected function addCategoryLinksToLBAndGetResult( array $categories ) {
1357  # Add the links to a LinkBatch
1358  $arr = [ NS_CATEGORY => $categories ];
1359  $lb = new LinkBatch;
1360  $lb->setArray( $arr );
1361 
1362  # Fetch existence plus the hiddencat property
1363  $dbr = wfGetDB( DB_REPLICA );
1364  $fields = array_merge(
1366  [ 'page_namespace', 'page_title', 'pp_value' ]
1367  );
1368 
1369  $res = $dbr->select( [ 'page', 'page_props' ],
1370  $fields,
1371  $lb->constructSet( 'page', $dbr ),
1372  __METHOD__,
1373  [],
1374  [ 'page_props' => [ 'LEFT JOIN', [
1375  'pp_propname' => 'hiddencat',
1376  'pp_page = page_id'
1377  ] ] ]
1378  );
1379 
1380  # Add the results to the link cache
1381  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1382  $lb->addResultToCache( $linkCache, $res );
1383 
1384  return $res;
1385  }
1386 
1392  public function setCategoryLinks( array $categories ) {
1393  $this->mCategoryLinks = [];
1394  $this->addCategoryLinks( $categories );
1395  }
1396 
1405  public function getCategoryLinks() {
1406  return $this->mCategoryLinks;
1407  }
1408 
1418  public function getCategories( $type = 'all' ) {
1419  if ( $type === 'all' ) {
1420  $allCategories = [];
1421  foreach ( $this->mCategories as $categories ) {
1422  $allCategories = array_merge( $allCategories, $categories );
1423  }
1424  return $allCategories;
1425  }
1426  if ( !isset( $this->mCategories[$type] ) ) {
1427  throw new InvalidArgumentException( 'Invalid category type given: ' . $type );
1428  }
1429  return $this->mCategories[$type];
1430  }
1431 
1441  public function setIndicators( array $indicators ) {
1442  $this->mIndicators = $indicators + $this->mIndicators;
1443  // Keep ordered by key
1444  ksort( $this->mIndicators );
1445  }
1446 
1455  public function getIndicators() {
1456  return $this->mIndicators;
1457  }
1458 
1467  public function addHelpLink( $to, $overrideBaseUrl = false ) {
1468  $this->addModuleStyles( 'mediawiki.helplink' );
1469  $text = $this->msg( 'helppage-top-gethelp' )->escaped();
1470 
1471  if ( $overrideBaseUrl ) {
1472  $helpUrl = $to;
1473  } else {
1474  $toUrlencoded = wfUrlencode( str_replace( ' ', '_', $to ) );
1475  $helpUrl = "https://www.mediawiki.org/wiki/Special:MyLanguage/$toUrlencoded";
1476  }
1477 
1479  'a',
1480  [
1481  'href' => $helpUrl,
1482  'target' => '_blank',
1483  'class' => 'mw-helplink',
1484  ],
1485  $text
1486  );
1487 
1488  $this->setIndicators( [ 'mw-helplink' => $link ] );
1489  }
1490 
1499  public function disallowUserJs() {
1500  $this->reduceAllowedModules(
1503  );
1504 
1505  // Site-wide styles are controlled by a config setting, see T73621
1506  // for background on why. User styles are never allowed.
1507  if ( $this->getConfig()->get( 'AllowSiteCSSOnRestrictedPages' ) ) {
1509  } else {
1511  }
1512  $this->reduceAllowedModules(
1514  $styleOrigin
1515  );
1516  }
1517 
1524  public function getAllowedModules( $type ) {
1526  return min( array_values( $this->mAllowedModules ) );
1527  } else {
1528  return $this->mAllowedModules[$type] ?? ResourceLoaderModule::ORIGIN_ALL;
1529  }
1530  }
1531 
1541  public function reduceAllowedModules( $type, $level ) {
1542  $this->mAllowedModules[$type] = min( $this->getAllowedModules( $type ), $level );
1543  }
1544 
1550  public function prependHTML( $text ) {
1551  $this->mBodytext = $text . $this->mBodytext;
1552  }
1553 
1559  public function addHTML( $text ) {
1560  $this->mBodytext .= $text;
1561  }
1562 
1572  public function addElement( $element, array $attribs = [], $contents = '' ) {
1573  $this->addHTML( Html::element( $element, $attribs, $contents ) );
1574  }
1575 
1579  public function clearHTML() {
1580  $this->mBodytext = '';
1581  }
1582 
1588  public function getHTML() {
1589  return $this->mBodytext;
1590  }
1591 
1599  public function parserOptions( $options = null ) {
1600  if ( $options !== null ) {
1601  wfDeprecated( __METHOD__ . ' with non-null $options', '1.31' );
1602  }
1603 
1604  if ( $options !== null && !empty( $options->isBogus ) ) {
1605  // Someone is trying to set a bogus pre-$wgUser PO. Check if it has
1606  // been changed somehow, and keep it if so.
1607  $anonPO = ParserOptions::newFromAnon();
1608  $anonPO->setAllowUnsafeRawHtml( false );
1609  if ( !$options->matches( $anonPO ) ) {
1610  wfLogWarning( __METHOD__ . ': Setting a changed bogus ParserOptions: ' . wfGetAllCallers( 5 ) );
1611  $options->isBogus = false;
1612  }
1613  }
1614 
1615  if ( !$this->mParserOptions ) {
1616  if ( !$this->getUser()->isSafeToLoad() ) {
1617  // $wgUser isn't unstubbable yet, so don't try to get a
1618  // ParserOptions for it. And don't cache this ParserOptions
1619  // either.
1621  $po->setAllowUnsafeRawHtml( false );
1622  $po->isBogus = true;
1623  if ( $options !== null ) {
1624  $this->mParserOptions = empty( $options->isBogus ) ? $options : null;
1625  }
1626  return $po;
1627  }
1628 
1629  $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
1630  $this->mParserOptions->setAllowUnsafeRawHtml( false );
1631  }
1632 
1633  if ( $options !== null && !empty( $options->isBogus ) ) {
1634  // They're trying to restore the bogus pre-$wgUser PO. Do the right
1635  // thing.
1636  return wfSetVar( $this->mParserOptions, null, true );
1637  } else {
1638  return wfSetVar( $this->mParserOptions, $options );
1639  }
1640  }
1641 
1649  public function setRevisionId( $revid ) {
1650  $val = is_null( $revid ) ? null : intval( $revid );
1651  return wfSetVar( $this->mRevisionId, $val, true );
1652  }
1653 
1659  public function getRevisionId() {
1660  return $this->mRevisionId;
1661  }
1662 
1670  public function setRevisionTimestamp( $timestamp ) {
1671  return wfSetVar( $this->mRevisionTimestamp, $timestamp, true );
1672  }
1673 
1680  public function getRevisionTimestamp() {
1682  }
1683 
1690  public function setFileVersion( $file ) {
1691  $val = null;
1692  if ( $file instanceof File && $file->exists() ) {
1693  $val = [ 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ];
1694  }
1695  return wfSetVar( $this->mFileVersion, $val, true );
1696  }
1697 
1703  public function getFileVersion() {
1704  return $this->mFileVersion;
1705  }
1706 
1713  public function getTemplateIds() {
1714  return $this->mTemplateIds;
1715  }
1716 
1723  public function getFileSearchOptions() {
1724  return $this->mImageTimeKeys;
1725  }
1726 
1743  public function addWikiTextAsInterface(
1744  $text, $linestart = true, Title $title = null
1745  ) {
1746  if ( $title === null ) {
1747  $title = $this->getTitle();
1748  }
1749  if ( !$title ) {
1750  throw new MWException( 'Title is null' );
1751  }
1752  $this->addWikiTextTitleInternal( $text, $title, $linestart, /*interface*/true );
1753  }
1754 
1768  public function wrapWikiTextAsInterface(
1769  $wrapperClass, $text
1770  ) {
1771  $this->addWikiTextTitleInternal(
1772  $text, $this->getTitle(),
1773  /*linestart*/true, /*interface*/true,
1774  $wrapperClass
1775  );
1776  }
1777 
1793  public function addWikiTextAsContent(
1794  $text, $linestart = true, Title $title = null
1795  ) {
1796  if ( $title === null ) {
1797  $title = $this->getTitle();
1798  }
1799  if ( !$title ) {
1800  throw new MWException( 'Title is null' );
1801  }
1802  $this->addWikiTextTitleInternal( $text, $title, $linestart, /*interface*/false );
1803  }
1804 
1821  private function addWikiTextTitleInternal(
1822  $text, Title $title, $linestart, $interface, $wrapperClass = null
1823  ) {
1824  $parserOutput = $this->parseInternal(
1825  $text, $title, $linestart, true, $interface, /*language*/null
1826  );
1827 
1828  $this->addParserOutput( $parserOutput, [
1829  'enableSectionEditLinks' => false,
1830  'wrapperDivClass' => $wrapperClass ?? '',
1831  ] );
1832  }
1833 
1842  public function addParserOutputMetadata( ParserOutput $parserOutput ) {
1843  $this->mLanguageLinks =
1844  array_merge( $this->mLanguageLinks, $parserOutput->getLanguageLinks() );
1845  $this->addCategoryLinks( $parserOutput->getCategories() );
1846  $this->setIndicators( $parserOutput->getIndicators() );
1847  $this->mNewSectionLink = $parserOutput->getNewSection();
1848  $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
1849 
1850  if ( !$parserOutput->isCacheable() ) {
1851  $this->enableClientCache( false );
1852  }
1853  $this->mNoGallery = $parserOutput->getNoGallery();
1854  $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
1855  $this->addModules( $parserOutput->getModules() );
1856  $this->addModuleStyles( $parserOutput->getModuleStyles() );
1857  $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1858  $this->mPreventClickjacking = $this->mPreventClickjacking
1859  || $parserOutput->preventClickjacking();
1860 
1861  // Template versioning...
1862  foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
1863  if ( isset( $this->mTemplateIds[$ns] ) ) {
1864  $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
1865  } else {
1866  $this->mTemplateIds[$ns] = $dbks;
1867  }
1868  }
1869  // File versioning...
1870  foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) {
1871  $this->mImageTimeKeys[$dbk] = $data;
1872  }
1873 
1874  // Hooks registered in the object
1875  $parserOutputHooks = $this->getConfig()->get( 'ParserOutputHooks' );
1876  foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
1877  list( $hookName, $data ) = $hookInfo;
1878  if ( isset( $parserOutputHooks[$hookName] ) ) {
1879  $parserOutputHooks[$hookName]( $this, $parserOutput, $data );
1880  }
1881  }
1882 
1883  // Enable OOUI if requested via ParserOutput
1884  if ( $parserOutput->getEnableOOUI() ) {
1885  $this->enableOOUI();
1886  }
1887 
1888  // Include parser limit report
1889  if ( !$this->limitReportJSData ) {
1890  $this->limitReportJSData = $parserOutput->getLimitReportJSData();
1891  }
1892 
1893  // Link flags are ignored for now, but may in the future be
1894  // used to mark individual language links.
1895  $linkFlags = [];
1896  // Avoid PHP 7.1 warning of passing $this by reference
1897  $outputPage = $this;
1898  Hooks::run( 'LanguageLinks', [ $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ] );
1899  Hooks::runWithoutAbort( 'OutputPageParserOutput', [ &$outputPage, $parserOutput ] );
1900 
1901  // This check must be after 'OutputPageParserOutput' runs in addParserOutputMetadata
1902  // so that extensions may modify ParserOutput to toggle TOC.
1903  // This cannot be moved to addParserOutputText because that is not
1904  // called by EditPage for Preview.
1905  if ( $parserOutput->getTOCHTML() ) {
1906  $this->mEnableTOC = true;
1907  }
1908  }
1909 
1918  public function addParserOutputContent( ParserOutput $parserOutput, $poOptions = [] ) {
1919  $this->addParserOutputText( $parserOutput, $poOptions );
1920 
1921  $this->addModules( $parserOutput->getModules() );
1922  $this->addModuleStyles( $parserOutput->getModuleStyles() );
1923 
1924  $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1925  }
1926 
1934  public function addParserOutputText( ParserOutput $parserOutput, $poOptions = [] ) {
1935  $text = $parserOutput->getText( $poOptions );
1936  // Avoid PHP 7.1 warning of passing $this by reference
1937  $outputPage = $this;
1938  Hooks::runWithoutAbort( 'OutputPageBeforeHTML', [ &$outputPage, &$text ] );
1939  $this->addHTML( $text );
1940  }
1941 
1948  function addParserOutput( ParserOutput $parserOutput, $poOptions = [] ) {
1949  $this->addParserOutputMetadata( $parserOutput );
1950  $this->addParserOutputText( $parserOutput, $poOptions );
1951  }
1952 
1958  public function addTemplate( &$template ) {
1959  $this->addHTML( $template->getHTML() );
1960  }
1961 
1980  public function parse( $text, $linestart = true, $interface = false, $language = null ) {
1981  wfDeprecated( __METHOD__, '1.33' );
1982  return $this->parseInternal(
1983  $text, $this->getTitle(), $linestart, /*tidy*/false, $interface, $language
1984  )->getText( [
1985  'enableSectionEditLinks' => false,
1986  ] );
1987  }
1988 
2000  public function parseAsContent( $text, $linestart = true ) {
2001  return $this->parseInternal(
2002  $text, $this->getTitle(), $linestart, /*tidy*/true, /*interface*/false, /*language*/null
2003  )->getText( [
2004  'enableSectionEditLinks' => false,
2005  'wrapperDivClass' => ''
2006  ] );
2007  }
2008 
2021  public function parseAsInterface( $text, $linestart = true ) {
2022  return $this->parseInternal(
2023  $text, $this->getTitle(), $linestart, /*tidy*/true, /*interface*/true, /*language*/null
2024  )->getText( [
2025  'enableSectionEditLinks' => false,
2026  'wrapperDivClass' => ''
2027  ] );
2028  }
2029 
2044  public function parseInlineAsInterface( $text, $linestart = true ) {
2046  $this->parseAsInterface( $text, $linestart )
2047  );
2048  }
2049 
2063  public function parseInline( $text, $linestart = true, $interface = false ) {
2064  wfDeprecated( __METHOD__, '1.33' );
2065  $parsed = $this->parseInternal(
2066  $text, $this->getTitle(), $linestart, /*tidy*/false, $interface, /*language*/null
2067  )->getText( [
2068  'enableSectionEditLinks' => false,
2069  'wrapperDivClass' => '', /* no wrapper div */
2070  ] );
2071  return Parser::stripOuterParagraph( $parsed );
2072  }
2073 
2088  private function parseInternal( $text, $title, $linestart, $tidy, $interface, $language ) {
2089  if ( is_null( $title ) ) {
2090  throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
2091  }
2092 
2093  $popts = $this->parserOptions();
2094  $oldTidy = $popts->setTidy( $tidy );
2095  $oldInterface = $popts->setInterfaceMessage( (bool)$interface );
2096 
2097  if ( $language !== null ) {
2098  $oldLang = $popts->setTargetLanguage( $language );
2099  }
2100 
2101  $parserOutput = MediaWikiServices::getInstance()->getParser()->getFreshParser()->parse(
2102  $text, $title, $popts,
2103  $linestart, true, $this->mRevisionId
2104  );
2105 
2106  $popts->setTidy( $oldTidy );
2107  $popts->setInterfaceMessage( $oldInterface );
2108 
2109  if ( $language !== null ) {
2110  $popts->setTargetLanguage( $oldLang );
2111  }
2112 
2113  return $parserOutput;
2114  }
2115 
2121  public function setCdnMaxage( $maxage ) {
2122  $this->mCdnMaxage = min( $maxage, $this->mCdnMaxageLimit );
2123  }
2124 
2134  public function lowerCdnMaxage( $maxage ) {
2135  $this->mCdnMaxageLimit = min( $maxage, $this->mCdnMaxageLimit );
2136  $this->setCdnMaxage( $this->mCdnMaxage );
2137  }
2138 
2151  public function adaptCdnTTL( $mtime, $minTTL = 0, $maxTTL = 0 ) {
2152  $minTTL = $minTTL ?: IExpiringStore::TTL_MINUTE;
2153  $maxTTL = $maxTTL ?: $this->getConfig()->get( 'CdnMaxAge' );
2154 
2155  if ( $mtime === null || $mtime === false ) {
2156  return $minTTL; // entity does not exist
2157  }
2158 
2159  $age = MWTimestamp::time() - wfTimestamp( TS_UNIX, $mtime );
2160  $adaptiveTTL = max( 0.9 * $age, $minTTL );
2161  $adaptiveTTL = min( $adaptiveTTL, $maxTTL );
2162 
2163  $this->lowerCdnMaxage( (int)$adaptiveTTL );
2164  }
2165 
2173  public function enableClientCache( $state ) {
2174  return wfSetVar( $this->mEnableClientCache, $state );
2175  }
2176 
2182  function getCacheVaryCookies() {
2183  if ( self::$cacheVaryCookies === null ) {
2184  $config = $this->getConfig();
2185  self::$cacheVaryCookies = array_values( array_unique( array_merge(
2186  SessionManager::singleton()->getVaryCookies(),
2187  [
2188  'forceHTTPS',
2189  ],
2190  $config->get( 'CacheVaryCookies' )
2191  ) ) );
2192  Hooks::run( 'GetCacheVaryCookies', [ $this, &self::$cacheVaryCookies ] );
2193  }
2194  return self::$cacheVaryCookies;
2195  }
2196 
2204  $request = $this->getRequest();
2205  foreach ( $this->getCacheVaryCookies() as $cookieName ) {
2206  if ( $request->getCookie( $cookieName, '', '' ) !== '' ) {
2207  wfDebug( __METHOD__ . ": found $cookieName\n" );
2208  return true;
2209  }
2210  }
2211  wfDebug( __METHOD__ . ": no cache-varying cookies found\n" );
2212  return false;
2213  }
2214 
2224  public function addVaryHeader( $header, array $option = null ) {
2225  if ( $option !== null && count( $option ) > 0 ) {
2226  wfDeprecated( 'addVaryHeader $option is ignored', '1.34' );
2227  }
2228  if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
2229  $this->mVaryHeader[$header] = null;
2230  }
2231  }
2232 
2239  public function getVaryHeader() {
2240  // If we vary on cookies, let's make sure it's always included here too.
2241  if ( $this->getCacheVaryCookies() ) {
2242  $this->addVaryHeader( 'Cookie' );
2243  }
2244 
2245  foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2246  $this->addVaryHeader( $header, $options );
2247  }
2248  return 'Vary: ' . implode( ', ', array_keys( $this->mVaryHeader ) );
2249  }
2250 
2256  public function addLinkHeader( $header ) {
2257  $this->mLinkHeader[] = $header;
2258  }
2259 
2265  public function getLinkHeader() {
2266  if ( !$this->mLinkHeader ) {
2267  return false;
2268  }
2269 
2270  return 'Link: ' . implode( ',', $this->mLinkHeader );
2271  }
2272 
2280  private function addAcceptLanguage() {
2281  $title = $this->getTitle();
2282  if ( !$title instanceof Title ) {
2283  return;
2284  }
2285 
2286  $lang = $title->getPageLanguage();
2287  if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
2288  $this->addVaryHeader( 'Accept-Language' );
2289  }
2290  }
2291 
2302  public function preventClickjacking( $enable = true ) {
2303  $this->mPreventClickjacking = $enable;
2304  }
2305 
2311  public function allowClickjacking() {
2312  $this->mPreventClickjacking = false;
2313  }
2314 
2321  public function getPreventClickjacking() {
2323  }
2324 
2332  public function getFrameOptions() {
2333  $config = $this->getConfig();
2334  if ( $config->get( 'BreakFrames' ) ) {
2335  return 'DENY';
2336  } elseif ( $this->mPreventClickjacking && $config->get( 'EditPageFrameOptions' ) ) {
2337  return $config->get( 'EditPageFrameOptions' );
2338  }
2339  return false;
2340  }
2341 
2348  private function getOriginTrials() {
2349  $config = $this->getConfig();
2350 
2351  return $config->get( 'OriginTrials' );
2352  }
2353 
2354  private function getReportTo() {
2355  $config = $this->getConfig();
2356 
2357  $expiry = $config->get( 'ReportToExpiry' );
2358 
2359  if ( !$expiry ) {
2360  return false;
2361  }
2362 
2363  $endpoints = $config->get( 'ReportToEndpoints' );
2364 
2365  if ( !$endpoints ) {
2366  return false;
2367  }
2368 
2369  $output = [ 'max_age' => $expiry, 'endpoints' => [] ];
2370 
2371  foreach ( $endpoints as $endpoint ) {
2372  $output['endpoints'][] = [ 'url' => $endpoint ];
2373  }
2374 
2375  return json_encode( $output, JSON_UNESCAPED_SLASHES );
2376  }
2377 
2378  private function getFeaturePolicyReportOnly() {
2379  $config = $this->getConfig();
2380 
2381  $features = $config->get( 'FeaturePolicyReportOnly' );
2382  return implode( ';', $features );
2383  }
2384 
2388  public function sendCacheControl() {
2389  $response = $this->getRequest()->response();
2390  $config = $this->getConfig();
2391 
2392  $this->addVaryHeader( 'Cookie' );
2393  $this->addAcceptLanguage();
2394 
2395  # don't serve compressed data to clients who can't handle it
2396  # maintain different caches for logged-in users and non-logged in ones
2397  $response->header( $this->getVaryHeader() );
2398 
2399  if ( $this->mEnableClientCache ) {
2400  if (
2401  $config->get( 'UseCdn' ) &&
2402  !$response->hasCookies() &&
2403  !SessionManager::getGlobalSession()->isPersistent() &&
2404  !$this->isPrintable() &&
2405  $this->mCdnMaxage != 0 &&
2406  !$this->haveCacheVaryCookies()
2407  ) {
2408  if ( $config->get( 'UseESI' ) ) {
2409  wfDeprecated( '$wgUseESI = true', '1.33' );
2410  # We'll purge the proxy cache explicitly, but require end user agents
2411  # to revalidate against the proxy on each visit.
2412  # Surrogate-Control controls our CDN, Cache-Control downstream caches
2413  wfDebug( __METHOD__ .
2414  ": proxy caching with ESI; {$this->mLastModified} **", 'private' );
2415  # start with a shorter timeout for initial testing
2416  # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
2417  $response->header(
2418  "Surrogate-Control: max-age={$config->get( 'CdnMaxAge' )}" .
2419  "+{$this->mCdnMaxage}, content=\"ESI/1.0\""
2420  );
2421  $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
2422  } else {
2423  # We'll purge the proxy cache for anons explicitly, but require end user agents
2424  # to revalidate against the proxy on each visit.
2425  # IMPORTANT! The CDN needs to replace the Cache-Control header with
2426  # Cache-Control: s-maxage=0, must-revalidate, max-age=0
2427  wfDebug( __METHOD__ .
2428  ": local proxy caching; {$this->mLastModified} **", 'private' );
2429  # start with a shorter timeout for initial testing
2430  # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
2431  $response->header( "Cache-Control: " .
2432  "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" );
2433  }
2434  } else {
2435  # We do want clients to cache if they can, but they *must* check for updates
2436  # on revisiting the page.
2437  wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **", 'private' );
2438  $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2439  $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
2440  }
2441  if ( $this->mLastModified ) {
2442  $response->header( "Last-Modified: {$this->mLastModified}" );
2443  }
2444  } else {
2445  wfDebug( __METHOD__ . ": no caching **", 'private' );
2446 
2447  # In general, the absence of a last modified header should be enough to prevent
2448  # the client from using its cache. We send a few other things just to make sure.
2449  $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2450  $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
2451  $response->header( 'Pragma: no-cache' );
2452  }
2453  }
2454 
2460  public function loadSkinModules( $sk ) {
2461  foreach ( $sk->getDefaultModules() as $group => $modules ) {
2462  if ( $group === 'styles' ) {
2463  foreach ( $modules as $key => $moduleMembers ) {
2464  $this->addModuleStyles( $moduleMembers );
2465  }
2466  } else {
2467  $this->addModules( $modules );
2468  }
2469  }
2470  }
2471 
2482  public function output( $return = false ) {
2483  if ( $this->mDoNothing ) {
2484  return $return ? '' : null;
2485  }
2486 
2487  $response = $this->getRequest()->response();
2488  $config = $this->getConfig();
2489 
2490  if ( $this->mRedirect != '' ) {
2491  # Standards require redirect URLs to be absolute
2492  $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT );
2493 
2494  $redirect = $this->mRedirect;
2496 
2497  if ( Hooks::run( "BeforePageRedirect", [ $this, &$redirect, &$code ] ) ) {
2498  if ( $code == '301' || $code == '303' ) {
2499  if ( !$config->get( 'DebugRedirects' ) ) {
2500  $response->statusHeader( $code );
2501  }
2502  $this->mLastModified = wfTimestamp( TS_RFC2822 );
2503  }
2504  if ( $config->get( 'VaryOnXFP' ) ) {
2505  $this->addVaryHeader( 'X-Forwarded-Proto' );
2506  }
2507  $this->sendCacheControl();
2508 
2509  $response->header( "Content-Type: text/html; charset=utf-8" );
2510  if ( $config->get( 'DebugRedirects' ) ) {
2511  $url = htmlspecialchars( $redirect );
2512  print "<!DOCTYPE html>\n<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
2513  print "<p>Location: <a href=\"$url\">$url</a></p>\n";
2514  print "</body>\n</html>\n";
2515  } else {
2516  $response->header( 'Location: ' . $redirect );
2517  }
2518  }
2519 
2520  return $return ? '' : null;
2521  } elseif ( $this->mStatusCode ) {
2522  $response->statusHeader( $this->mStatusCode );
2523  }
2524 
2525  # Buffer output; final headers may depend on later processing
2526  ob_start();
2527 
2528  $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
2529  $response->header( 'Content-language: ' .
2530  MediaWikiServices::getInstance()->getContentLanguage()->getHtmlCode() );
2531 
2532  $linkHeader = $this->getLinkHeader();
2533  if ( $linkHeader ) {
2534  $response->header( $linkHeader );
2535  }
2536 
2537  // Prevent framing, if requested
2538  $frameOptions = $this->getFrameOptions();
2539  if ( $frameOptions ) {
2540  $response->header( "X-Frame-Options: $frameOptions" );
2541  }
2542 
2543  $originTrials = $this->getOriginTrials();
2544  foreach ( $originTrials as $originTrial ) {
2545  $response->header( "Origin-Trial: $originTrial", false );
2546  }
2547 
2548  $reportTo = $this->getReportTo();
2549  if ( $reportTo ) {
2550  $response->header( "Report-To: $reportTo" );
2551  }
2552 
2553  $featurePolicyReportOnly = $this->getFeaturePolicyReportOnly();
2554  if ( $featurePolicyReportOnly ) {
2555  $response->header( "Feature-Policy-Report-Only: $featurePolicyReportOnly" );
2556  }
2557 
2559 
2560  if ( $this->mArticleBodyOnly ) {
2561  echo $this->mBodytext;
2562  } else {
2563  // Enable safe mode if requested (T152169)
2564  if ( $this->getRequest()->getBool( 'safemode' ) ) {
2565  $this->disallowUserJs();
2566  }
2567 
2568  $sk = $this->getSkin();
2569  $this->loadSkinModules( $sk );
2570 
2571  MWDebug::addModules( $this );
2572 
2573  // Avoid PHP 7.1 warning of passing $this by reference
2574  $outputPage = $this;
2575  // Hook that allows last minute changes to the output page, e.g.
2576  // adding of CSS or Javascript by extensions.
2577  Hooks::runWithoutAbort( 'BeforePageDisplay', [ &$outputPage, &$sk ] );
2578 
2579  try {
2580  $sk->outputPage();
2581  } catch ( Exception $e ) {
2582  ob_end_clean(); // bug T129657
2583  throw $e;
2584  }
2585  }
2586 
2587  try {
2588  // This hook allows last minute changes to final overall output by modifying output buffer
2589  Hooks::runWithoutAbort( 'AfterFinalPageOutput', [ $this ] );
2590  } catch ( Exception $e ) {
2591  ob_end_clean(); // bug T129657
2592  throw $e;
2593  }
2594 
2595  $this->sendCacheControl();
2596 
2597  if ( $return ) {
2598  return ob_get_clean();
2599  } else {
2600  ob_end_flush();
2601  return null;
2602  }
2603  }
2604 
2615  public function prepareErrorPage( $pageTitle, $htmlTitle = false ) {
2616  $this->setPageTitle( $pageTitle );
2617  if ( $htmlTitle !== false ) {
2618  $this->setHTMLTitle( $htmlTitle );
2619  }
2620  $this->setRobotPolicy( 'noindex,nofollow' );
2621  $this->setArticleRelated( false );
2622  $this->enableClientCache( false );
2623  $this->mRedirect = '';
2624  $this->clearSubtitle();
2625  $this->clearHTML();
2626  }
2627 
2640  public function showErrorPage( $title, $msg, $params = [] ) {
2641  if ( !$title instanceof Message ) {
2642  $title = $this->msg( $title );
2643  }
2644 
2645  $this->prepareErrorPage( $title );
2646 
2647  if ( $msg instanceof Message ) {
2648  if ( $params !== [] ) {
2649  trigger_error( 'Argument ignored: $params. The message parameters argument '
2650  . 'is discarded when the $msg argument is a Message object instead of '
2651  . 'a string.', E_USER_NOTICE );
2652  }
2653  $this->addHTML( $msg->parseAsBlock() );
2654  } else {
2655  $this->addWikiMsgArray( $msg, $params );
2656  }
2657 
2658  $this->returnToMain();
2659  }
2660 
2667  public function showPermissionsErrorPage( array $errors, $action = null ) {
2668  foreach ( $errors as $key => $error ) {
2669  $errors[$key] = (array)$error;
2670  }
2671 
2672  // For some action (read, edit, create and upload), display a "login to do this action"
2673  // error if all of the following conditions are met:
2674  // 1. the user is not logged in
2675  // 2. the only error is insufficient permissions (i.e. no block or something else)
2676  // 3. the error can be avoided simply by logging in
2677  if ( in_array( $action, [ 'read', 'edit', 'createpage', 'createtalk', 'upload' ] )
2678  && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
2679  && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
2680  && ( User::groupHasPermission( 'user', $action )
2681  || User::groupHasPermission( 'autoconfirmed', $action ) )
2682  ) {
2683  $displayReturnto = null;
2684 
2685  # Due to T34276, if a user does not have read permissions,
2686  # $this->getTitle() will just give Special:Badtitle, which is
2687  # not especially useful as a returnto parameter. Use the title
2688  # from the request instead, if there was one.
2689  $request = $this->getRequest();
2690  $returnto = Title::newFromText( $request->getVal( 'title', '' ) );
2691  if ( $action == 'edit' ) {
2692  $msg = 'whitelistedittext';
2693  $displayReturnto = $returnto;
2694  } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
2695  $msg = 'nocreatetext';
2696  } elseif ( $action == 'upload' ) {
2697  $msg = 'uploadnologintext';
2698  } else { # Read
2699  $msg = 'loginreqpagetext';
2700  $displayReturnto = Title::newMainPage();
2701  }
2702 
2703  $query = [];
2704 
2705  if ( $returnto ) {
2706  $query['returnto'] = $returnto->getPrefixedText();
2707 
2708  if ( !$request->wasPosted() ) {
2709  $returntoquery = $request->getValues();
2710  unset( $returntoquery['title'] );
2711  unset( $returntoquery['returnto'] );
2712  unset( $returntoquery['returntoquery'] );
2713  $query['returntoquery'] = wfArrayToCgi( $returntoquery );
2714  }
2715  }
2716 
2717  $services = MediaWikiServices::getInstance();
2718 
2719  $title = SpecialPage::getTitleFor( 'Userlogin' );
2720  $linkRenderer = $services->getLinkRenderer();
2721  $loginUrl = $title->getLinkURL( $query, false, PROTO_RELATIVE );
2722  $loginLink = $linkRenderer->makeKnownLink(
2723  $title,
2724  $this->msg( 'loginreqlink' )->text(),
2725  [],
2726  $query
2727  );
2728 
2729  $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) );
2730  $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->params( $loginUrl )->parse() );
2731 
2732  $permissionManager = $services->getPermissionManager();
2733 
2734  # Don't return to a page the user can't read otherwise
2735  # we'll end up in a pointless loop
2736  if ( $displayReturnto && $permissionManager->userCan(
2737  'read', $this->getUser(), $displayReturnto
2738  ) ) {
2739  $this->returnToMain( null, $displayReturnto );
2740  }
2741  } else {
2742  $this->prepareErrorPage( $this->msg( 'permissionserrors' ) );
2743  $this->addWikiTextAsInterface( $this->formatPermissionsErrorMessage( $errors, $action ) );
2744  }
2745  }
2746 
2753  public function versionRequired( $version ) {
2754  $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) );
2755 
2756  $this->addWikiMsg( 'versionrequiredtext', $version );
2757  $this->returnToMain();
2758  }
2759 
2767  public function formatPermissionsErrorMessage( array $errors, $action = null ) {
2768  if ( $action == null ) {
2769  $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
2770  } else {
2771  $action_desc = $this->msg( "action-$action" )->plain();
2772  $text = $this->msg(
2773  'permissionserrorstext-withaction',
2774  count( $errors ),
2775  $action_desc
2776  )->plain() . "\n\n";
2777  }
2778 
2779  if ( count( $errors ) > 1 ) {
2780  $text .= '<ul class="permissions-errors">' . "\n";
2781 
2782  foreach ( $errors as $error ) {
2783  $text .= '<li>';
2784  $text .= $this->msg( ...$error )->plain();
2785  $text .= "</li>\n";
2786  }
2787  $text .= '</ul>';
2788  } else {
2789  $text .= "<div class=\"permissions-errors\">\n" .
2790  $this->msg( ...reset( $errors ) )->plain() .
2791  "\n</div>";
2792  }
2793 
2794  return $text;
2795  }
2796 
2806  public function showLagWarning( $lag ) {
2807  $config = $this->getConfig();
2808  if ( $lag >= $config->get( 'SlaveLagWarning' ) ) {
2809  $lag = floor( $lag ); // floor to avoid nano seconds to display
2810  $message = $lag < $config->get( 'SlaveLagCritical' )
2811  ? 'lag-warn-normal'
2812  : 'lag-warn-high';
2813  $wrap = Html::rawElement( 'div', [ 'class' => "mw-{$message}" ], "\n$1\n" );
2814  $this->wrapWikiMsg( "$wrap\n", [ $message, $this->getLanguage()->formatNum( $lag ) ] );
2815  }
2816  }
2817 
2824  public function showFatalError( $message ) {
2825  $this->prepareErrorPage( $this->msg( 'internalerror' ) );
2826 
2827  $this->addHTML( $message );
2828  }
2829 
2838  public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) {
2839  $linkRenderer = MediaWikiServices::getInstance()
2840  ->getLinkRendererFactory()->createFromLegacyOptions( $options );
2841  $link = $this->msg( 'returnto' )->rawParams(
2842  $linkRenderer->makeLink( $title, $text, [], $query ) )->escaped();
2843  $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
2844  }
2845 
2854  public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
2855  if ( $returnto == null ) {
2856  $returnto = $this->getRequest()->getText( 'returnto' );
2857  }
2858 
2859  if ( $returntoquery == null ) {
2860  $returntoquery = $this->getRequest()->getText( 'returntoquery' );
2861  }
2862 
2863  if ( $returnto === '' ) {
2864  $returnto = Title::newMainPage();
2865  }
2866 
2867  if ( is_object( $returnto ) ) {
2868  $titleObj = $returnto;
2869  } else {
2870  $titleObj = Title::newFromText( $returnto );
2871  }
2872  // We don't want people to return to external interwiki. That
2873  // might potentially be used as part of a phishing scheme
2874  if ( !is_object( $titleObj ) || $titleObj->isExternal() ) {
2875  $titleObj = Title::newMainPage();
2876  }
2877 
2878  $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) );
2879  }
2880 
2881  private function getRlClientContext() {
2882  if ( !$this->rlClientContext ) {
2884  [], // modules; not relevant
2885  $this->getLanguage()->getCode(),
2886  $this->getSkin()->getSkinName(),
2887  $this->getUser()->isLoggedIn() ? $this->getUser()->getName() : null,
2888  null, // version; not relevant
2890  null, // only; not relevant
2891  $this->isPrintable(),
2892  $this->getRequest()->getBool( 'handheld' )
2893  );
2894  $this->rlClientContext = new ResourceLoaderContext(
2895  $this->getResourceLoader(),
2896  new FauxRequest( $query )
2897  );
2898  if ( $this->contentOverrideCallbacks ) {
2899  $this->rlClientContext = new DerivativeResourceLoaderContext( $this->rlClientContext );
2900  $this->rlClientContext->setContentOverrideCallback( function ( Title $title ) {
2901  foreach ( $this->contentOverrideCallbacks as $callback ) {
2902  $content = $callback( $title );
2903  if ( $content !== null ) {
2905  if ( strpos( $text, '</script>' ) !== false ) {
2906  // Proactively replace this so that we can display a message
2907  // to the user, instead of letting it go to Html::inlineScript(),
2908  // where it would be considered a server-side issue.
2909  $titleFormatted = $title->getPrefixedText();
2911  Xml::encodeJsCall( 'mw.log.error', [
2912  "Cannot preview $titleFormatted due to script-closing tag."
2913  ] )
2914  );
2915  }
2916  return $content;
2917  }
2918  }
2919  return null;
2920  } );
2921  }
2922  }
2923  return $this->rlClientContext;
2924  }
2925 
2937  public function getRlClient() {
2938  if ( !$this->rlClient ) {
2939  $context = $this->getRlClientContext();
2940  $rl = $this->getResourceLoader();
2941  $this->addModules( [
2942  'user',
2943  'user.options',
2944  'user.tokens',
2945  ] );
2946  $this->addModuleStyles( [
2947  'site.styles',
2948  'noscript',
2949  'user.styles',
2950  ] );
2951  $this->getSkin()->setupSkinUserCss( $this );
2952 
2953  // Prepare exempt modules for buildExemptModules()
2954  $exemptGroups = [ 'site' => [], 'noscript' => [], 'private' => [], 'user' => [] ];
2955  $exemptStates = [];
2956  $moduleStyles = $this->getModuleStyles( /*filter*/ true );
2957 
2958  // Preload getTitleInfo for isKnownEmpty calls below and in ResourceLoaderClientHtml
2959  // Separate user-specific batch for improved cache-hit ratio.
2960  $userBatch = [ 'user.styles', 'user' ];
2961  $siteBatch = array_diff( $moduleStyles, $userBatch );
2962  $dbr = wfGetDB( DB_REPLICA );
2965 
2966  // Filter out modules handled by buildExemptModules()
2967  $moduleStyles = array_filter( $moduleStyles,
2968  function ( $name ) use ( $rl, $context, &$exemptGroups, &$exemptStates ) {
2969  $module = $rl->getModule( $name );
2970  if ( $module ) {
2971  $group = $module->getGroup();
2972  if ( isset( $exemptGroups[$group] ) ) {
2973  $exemptStates[$name] = 'ready';
2974  if ( !$module->isKnownEmpty( $context ) ) {
2975  // E.g. Don't output empty <styles>
2976  $exemptGroups[$group][] = $name;
2977  }
2978  return false;
2979  }
2980  }
2981  return true;
2982  }
2983  );
2984  $this->rlExemptStyleModules = $exemptGroups;
2985 
2987  'target' => $this->getTarget(),
2988  'nonce' => $this->getCSPNonce(),
2989  // When 'safemode', disallowUserJs(), or reduceAllowedModules() is used
2990  // to only restrict modules to ORIGIN_CORE (ie. disallow ORIGIN_USER), the list of
2991  // modules enqueud for loading on this page is filtered to just those.
2992  // However, to make sure we also apply the restriction to dynamic dependencies and
2993  // lazy-loaded modules at run-time on the client-side, pass 'safemode' down to the
2994  // StartupModule so that the client-side registry will not contain any restricted
2995  // modules either. (T152169, T185303)
2996  'safemode' => ( $this->getAllowedModules( ResourceLoaderModule::TYPE_COMBINED )
2998  ) ? '1' : null,
2999  ] );
3000  $rlClient->setConfig( $this->getJSVars() );
3001  $rlClient->setModules( $this->getModules( /*filter*/ true ) );
3002  $rlClient->setModuleStyles( $moduleStyles );
3003  $rlClient->setExemptStates( $exemptStates );
3004  $this->rlClient = $rlClient;
3005  }
3006  return $this->rlClient;
3007  }
3008 
3014  public function headElement( Skin $sk, $includeStyle = true ) {
3015  $config = $this->getConfig();
3016  $userdir = $this->getLanguage()->getDir();
3017  $sitedir = MediaWikiServices::getInstance()->getContentLanguage()->getDir();
3018 
3019  $pieces = [];
3021  $this->getRlClient()->getDocumentAttributes(),
3023  ) );
3024  $pieces[] = Html::openElement( 'head' );
3025 
3026  if ( $this->getHTMLTitle() == '' ) {
3027  $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() );
3028  }
3029 
3030  if ( !Html::isXmlMimeType( $config->get( 'MimeType' ) ) ) {
3031  // Add <meta charset="UTF-8">
3032  // This should be before <title> since it defines the charset used by
3033  // text including the text inside <title>.
3034  // The spec recommends defining XHTML5's charset using the XML declaration
3035  // instead of meta.
3036  // Our XML declaration is output by Html::htmlHeader.
3037  // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type
3038  // https://html.spec.whatwg.org/multipage/semantics.html#charset
3039  $pieces[] = Html::element( 'meta', [ 'charset' => 'UTF-8' ] );
3040  }
3041 
3042  $pieces[] = Html::element( 'title', null, $this->getHTMLTitle() );
3043  $pieces[] = $this->getRlClient()->getHeadHtml();
3044  $pieces[] = $this->buildExemptModules();
3045  $pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) );
3046  $pieces = array_merge( $pieces, array_values( $this->mHeadItems ) );
3047 
3048  // This library is intended to run on older browsers that MediaWiki no longer
3049  // supports as Grade A. For these Grade C browsers, we provide an experience
3050  // using only HTML and CSS. Where standards-compliant browsers are able to style
3051  // unknown HTML elements without issue, old IE ignores these styles.
3052  // The html5shiv library fixes that.
3053  // Use an IE conditional comment to serve the script only to old IE
3054  $shivUrl = $config->get( 'ResourceBasePath' ) . '/resources/lib/html5shiv/html5shiv.js';
3055  $pieces[] = '<!--[if lt IE 9]>' .
3056  Html::linkedScript( $shivUrl, $this->getCSPNonce() ) .
3057  '<![endif]-->';
3058 
3059  $pieces[] = Html::closeElement( 'head' );
3060 
3061  $bodyClasses = $this->mAdditionalBodyClasses;
3062  $bodyClasses[] = 'mediawiki';
3063 
3064  # Classes for LTR/RTL directionality support
3065  $bodyClasses[] = $userdir;
3066  $bodyClasses[] = "sitedir-$sitedir";
3067 
3068  $underline = $this->getUser()->getOption( 'underline' );
3069  if ( $underline < 2 ) {
3070  // The following classes can be used here:
3071  // * mw-underline-always
3072  // * mw-underline-never
3073  $bodyClasses[] = 'mw-underline-' . ( $underline ? 'always' : 'never' );
3074  }
3075 
3076  if ( $this->getLanguage()->capitalizeAllNouns() ) {
3077  # A <body> class is probably not the best way to do this . . .
3078  $bodyClasses[] = 'capitalize-all-nouns';
3079  }
3080 
3081  // Parser feature migration class
3082  // The idea is that this will eventually be removed, after the wikitext
3083  // which requires it is cleaned up.
3084  $bodyClasses[] = 'mw-hide-empty-elt';
3085 
3086  $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
3087  $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
3088  $bodyClasses[] =
3089  'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
3090 
3091  $bodyAttrs = [];
3092  // While the implode() is not strictly needed, it's used for backwards compatibility
3093  // (this used to be built as a string and hooks likely still expect that).
3094  $bodyAttrs['class'] = implode( ' ', $bodyClasses );
3095 
3096  // Allow skins and extensions to add body attributes they need
3097  $sk->addToBodyAttributes( $this, $bodyAttrs );
3098  Hooks::run( 'OutputPageBodyAttributes', [ $this, $sk, &$bodyAttrs ] );
3099 
3100  $pieces[] = Html::openElement( 'body', $bodyAttrs );
3101 
3102  return self::combineWrappedStrings( $pieces );
3103  }
3104 
3110  public function getResourceLoader() {
3111  if ( is_null( $this->mResourceLoader ) ) {
3112  // Lazy-initialise as needed
3113  $this->mResourceLoader = MediaWikiServices::getInstance()->getResourceLoader();
3114  }
3115  return $this->mResourceLoader;
3116  }
3117 
3126  public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) {
3127  // Apply 'target' and 'origin' filters
3128  $modules = $this->filterModules( (array)$modules, null, $only );
3129 
3131  $this->getRlClientContext(),
3132  $modules,
3133  $only,
3134  $extraQuery,
3135  $this->getCSPNonce()
3136  );
3137  }
3138 
3145  protected static function combineWrappedStrings( array $chunks ) {
3146  // Filter out empty values
3147  $chunks = array_filter( $chunks, 'strlen' );
3148  return WrappedString::join( "\n", $chunks );
3149  }
3150 
3157  public function getBottomScripts() {
3158  $chunks = [];
3159  $chunks[] = $this->getRlClient()->getBodyHtml();
3160 
3161  // Legacy non-ResourceLoader scripts
3162  $chunks[] = $this->mScripts;
3163 
3164  if ( $this->limitReportJSData ) {
3167  [ 'wgPageParseReport' => $this->limitReportJSData ]
3168  ),
3169  $this->getCSPNonce()
3170  );
3171  }
3172 
3173  return self::combineWrappedStrings( $chunks );
3174  }
3175 
3182  public function getJsConfigVars() {
3183  return $this->mJsConfigVars;
3184  }
3185 
3192  public function addJsConfigVars( $keys, $value = null ) {
3193  if ( is_array( $keys ) ) {
3194  foreach ( $keys as $key => $value ) {
3195  $this->mJsConfigVars[$key] = $value;
3196  }
3197  return;
3198  }
3199 
3200  $this->mJsConfigVars[$keys] = $value;
3201  }
3202 
3212  public function getJSVars() {
3213  $curRevisionId = 0;
3214  $articleId = 0;
3215  $canonicalSpecialPageName = false; # T23115
3216  $services = MediaWikiServices::getInstance();
3217 
3218  $title = $this->getTitle();
3219  $ns = $title->getNamespace();
3220  $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
3221  $canonicalNamespace = $nsInfo->exists( $ns )
3222  ? $nsInfo->getCanonicalName( $ns )
3223  : $title->getNsText();
3224 
3225  $sk = $this->getSkin();
3226  // Get the relevant title so that AJAX features can use the correct page name
3227  // when making API requests from certain special pages (T36972).
3228  $relevantTitle = $sk->getRelevantTitle();
3229  $relevantUser = $sk->getRelevantUser();
3230 
3231  if ( $ns == NS_SPECIAL ) {
3232  list( $canonicalSpecialPageName, /*...*/ ) =
3233  $services->getSpecialPageFactory()->
3234  resolveAlias( $title->getDBkey() );
3235  } elseif ( $this->canUseWikiPage() ) {
3236  $wikiPage = $this->getWikiPage();
3237  $curRevisionId = $wikiPage->getLatest();
3238  $articleId = $wikiPage->getId();
3239  }
3240 
3241  $lang = $title->getPageViewLanguage();
3242 
3243  // Pre-process information
3244  $separatorTransTable = $lang->separatorTransformTable();
3245  $separatorTransTable = $separatorTransTable ?: [];
3246  $compactSeparatorTransTable = [
3247  implode( "\t", array_keys( $separatorTransTable ) ),
3248  implode( "\t", $separatorTransTable ),
3249  ];
3250  $digitTransTable = $lang->digitTransformTable();
3251  $digitTransTable = $digitTransTable ?: [];
3252  $compactDigitTransTable = [
3253  implode( "\t", array_keys( $digitTransTable ) ),
3254  implode( "\t", $digitTransTable ),
3255  ];
3256 
3257  $user = $this->getUser();
3258 
3259  $vars = [
3260  'wgCanonicalNamespace' => $canonicalNamespace,
3261  'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
3262  'wgNamespaceNumber' => $title->getNamespace(),
3263  'wgPageName' => $title->getPrefixedDBkey(),
3264  'wgTitle' => $title->getText(),
3265  'wgCurRevisionId' => $curRevisionId,
3266  'wgRevisionId' => (int)$this->getRevisionId(),
3267  'wgArticleId' => $articleId,
3268  'wgIsArticle' => $this->isArticle(),
3269  'wgIsRedirect' => $title->isRedirect(),
3270  'wgAction' => Action::getActionName( $this->getContext() ),
3271  'wgUserName' => $user->isAnon() ? null : $user->getName(),
3272  'wgUserGroups' => $user->getEffectiveGroups(),
3273  'wgCategories' => $this->getCategories(),
3274  'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
3275  'wgPageContentLanguage' => $lang->getCode(),
3276  'wgPageContentModel' => $title->getContentModel(),
3277  'wgSeparatorTransformTable' => $compactSeparatorTransTable,
3278  'wgDigitTransformTable' => $compactDigitTransTable,
3279  'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
3280  'wgMonthNames' => $lang->getMonthNamesArray(),
3281  'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
3282  'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
3283  'wgRelevantArticleId' => $relevantTitle->getArticleID(),
3284  'wgRequestId' => WebRequest::getRequestId(),
3285  'wgCSPNonce' => $this->getCSPNonce(),
3286  ];
3287 
3288  if ( $user->isLoggedIn() ) {
3289  $vars['wgUserId'] = $user->getId();
3290  $vars['wgUserEditCount'] = $user->getEditCount();
3291  $userReg = $user->getRegistration();
3292  $vars['wgUserRegistration'] = $userReg ? wfTimestamp( TS_UNIX, $userReg ) * 1000 : null;
3293  // Get the revision ID of the oldest new message on the user's talk
3294  // page. This can be used for constructing new message alerts on
3295  // the client side.
3296  $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId();
3297  }
3298 
3299  $contLang = $services->getContentLanguage();
3300  if ( $contLang->hasVariants() ) {
3301  $vars['wgUserVariant'] = $contLang->getPreferredVariant();
3302  }
3303  // Same test as SkinTemplate
3304  $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user )
3305  && ( $title->exists() || $title->quickUserCan( 'create', $user ) );
3306 
3307  $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle
3308  && $relevantTitle->quickUserCan( 'edit', $user )
3309  && ( $relevantTitle->exists() || $relevantTitle->quickUserCan( 'create', $user ) );
3310 
3311  foreach ( $title->getRestrictionTypes() as $type ) {
3312  // Following keys are set in $vars:
3313  // wgRestrictionCreate, wgRestrictionEdit, wgRestrictionMove, wgRestrictionUpload
3314  $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
3315  }
3316 
3317  if ( $title->isMainPage() ) {
3318  $vars['wgIsMainPage'] = true;
3319  }
3320 
3321  if ( $this->mRedirectedFrom ) {
3322  $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey();
3323  }
3324 
3325  if ( $relevantUser ) {
3326  $vars['wgRelevantUserName'] = $relevantUser->getName();
3327  }
3328 
3329  // Allow extensions to add their custom variables to the mw.config map.
3330  // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
3331  // page-dependant but site-wide (without state).
3332  // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
3333  Hooks::run( 'MakeGlobalVariablesScript', [ &$vars, $this ] );
3334 
3335  // Merge in variables from addJsConfigVars last
3336  return array_merge( $vars, $this->getJsConfigVars() );
3337  }
3338 
3348  public function userCanPreview() {
3349  $request = $this->getRequest();
3350  if (
3351  $request->getVal( 'action' ) !== 'submit' ||
3352  !$request->wasPosted()
3353  ) {
3354  return false;
3355  }
3356 
3357  $user = $this->getUser();
3358 
3359  if ( !$user->isLoggedIn() ) {
3360  // Anons have predictable edit tokens
3361  return false;
3362  }
3363  if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
3364  return false;
3365  }
3366 
3367  $title = $this->getTitle();
3368  $errors = $title->getUserPermissionsErrors( 'edit', $user );
3369  if ( count( $errors ) !== 0 ) {
3370  return false;
3371  }
3372 
3373  return true;
3374  }
3375 
3379  public function getHeadLinksArray() {
3380  global $wgVersion;
3381 
3382  $tags = [];
3383  $config = $this->getConfig();
3384 
3385  $canonicalUrl = $this->mCanonicalUrl;
3386 
3387  $tags['meta-generator'] = Html::element( 'meta', [
3388  'name' => 'generator',
3389  'content' => "MediaWiki $wgVersion",
3390  ] );
3391 
3392  if ( $config->get( 'ReferrerPolicy' ) !== false ) {
3393  // Per https://w3c.github.io/webappsec-referrer-policy/#unknown-policy-values
3394  // fallbacks should come before the primary value so we need to reverse the array.
3395  foreach ( array_reverse( (array)$config->get( 'ReferrerPolicy' ) ) as $i => $policy ) {
3396  $tags["meta-referrer-$i"] = Html::element( 'meta', [
3397  'name' => 'referrer',
3398  'content' => $policy,
3399  ] );
3400  }
3401  }
3402 
3403  $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
3404  if ( $p !== 'index,follow' ) {
3405  // http://www.robotstxt.org/wc/meta-user.html
3406  // Only show if it's different from the default robots policy
3407  $tags['meta-robots'] = Html::element( 'meta', [
3408  'name' => 'robots',
3409  'content' => $p,
3410  ] );
3411  }
3412 
3413  foreach ( $this->mMetatags as $tag ) {
3414  if ( strncasecmp( $tag[0], 'http:', 5 ) === 0 ) {
3415  $a = 'http-equiv';
3416  $tag[0] = substr( $tag[0], 5 );
3417  } elseif ( strncasecmp( $tag[0], 'og:', 3 ) === 0 ) {
3418  $a = 'property';
3419  } else {
3420  $a = 'name';
3421  }
3422  $tagName = "meta-{$tag[0]}";
3423  if ( isset( $tags[$tagName] ) ) {
3424  $tagName .= $tag[1];
3425  }
3426  $tags[$tagName] = Html::element( 'meta',
3427  [
3428  $a => $tag[0],
3429  'content' => $tag[1]
3430  ]
3431  );
3432  }
3433 
3434  foreach ( $this->mLinktags as $tag ) {
3435  $tags[] = Html::element( 'link', $tag );
3436  }
3437 
3438  # Universal edit button
3439  if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) {
3440  $user = $this->getUser();
3441  if ( $this->getTitle()->quickUserCan( 'edit', $user )
3442  && ( $this->getTitle()->exists() ||
3443  $this->getTitle()->quickUserCan( 'create', $user ) )
3444  ) {
3445  // Original UniversalEditButton
3446  $msg = $this->msg( 'edit' )->text();
3447  $tags['universal-edit-button'] = Html::element( 'link', [
3448  'rel' => 'alternate',
3449  'type' => 'application/x-wiki',
3450  'title' => $msg,
3451  'href' => $this->getTitle()->getEditURL(),
3452  ] );
3453  // Alternate edit link
3454  $tags['alternative-edit'] = Html::element( 'link', [
3455  'rel' => 'edit',
3456  'title' => $msg,
3457  'href' => $this->getTitle()->getEditURL(),
3458  ] );
3459  }
3460  }
3461 
3462  # Generally the order of the favicon and apple-touch-icon links
3463  # should not matter, but Konqueror (3.5.9 at least) incorrectly
3464  # uses whichever one appears later in the HTML source. Make sure
3465  # apple-touch-icon is specified first to avoid this.
3466  if ( $config->get( 'AppleTouchIcon' ) !== false ) {
3467  $tags['apple-touch-icon'] = Html::element( 'link', [
3468  'rel' => 'apple-touch-icon',
3469  'href' => $config->get( 'AppleTouchIcon' )
3470  ] );
3471  }
3472 
3473  if ( $config->get( 'Favicon' ) !== false ) {
3474  $tags['favicon'] = Html::element( 'link', [
3475  'rel' => 'shortcut icon',
3476  'href' => $config->get( 'Favicon' )
3477  ] );
3478  }
3479 
3480  # OpenSearch description link
3481  $tags['opensearch'] = Html::element( 'link', [
3482  'rel' => 'search',
3483  'type' => 'application/opensearchdescription+xml',
3484  'href' => wfScript( 'opensearch_desc' ),
3485  'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
3486  ] );
3487 
3488  # Real Simple Discovery link, provides auto-discovery information
3489  # for the MediaWiki API (and potentially additional custom API
3490  # support such as WordPress or Twitter-compatible APIs for a
3491  # blogging extension, etc)
3492  $tags['rsd'] = Html::element( 'link', [
3493  'rel' => 'EditURI',
3494  'type' => 'application/rsd+xml',
3495  // Output a protocol-relative URL here if $wgServer is protocol-relative.
3496  // Whether RSD accepts relative or protocol-relative URLs is completely
3497  // undocumented, though.
3498  'href' => wfExpandUrl( wfAppendQuery(
3499  wfScript( 'api' ),
3500  [ 'action' => 'rsd' ] ),
3502  ),
3503  ] );
3504 
3505  # Language variants
3506  if ( !$config->get( 'DisableLangConversion' ) ) {
3507  $lang = $this->getTitle()->getPageLanguage();
3508  if ( $lang->hasVariants() ) {
3509  $variants = $lang->getVariants();
3510  foreach ( $variants as $variant ) {
3511  $tags["variant-$variant"] = Html::element( 'link', [
3512  'rel' => 'alternate',
3513  'hreflang' => LanguageCode::bcp47( $variant ),
3514  'href' => $this->getTitle()->getLocalURL(
3515  [ 'variant' => $variant ] )
3516  ]
3517  );
3518  }
3519  # x-default link per https://support.google.com/webmasters/answer/189077?hl=en
3520  $tags["variant-x-default"] = Html::element( 'link', [
3521  'rel' => 'alternate',
3522  'hreflang' => 'x-default',
3523  'href' => $this->getTitle()->getLocalURL() ] );
3524  }
3525  }
3526 
3527  # Copyright
3528  if ( $this->copyrightUrl !== null ) {
3529  $copyright = $this->copyrightUrl;
3530  } else {
3531  $copyright = '';
3532  if ( $config->get( 'RightsPage' ) ) {
3533  $copy = Title::newFromText( $config->get( 'RightsPage' ) );
3534 
3535  if ( $copy ) {
3536  $copyright = $copy->getLocalURL();
3537  }
3538  }
3539 
3540  if ( !$copyright && $config->get( 'RightsUrl' ) ) {
3541  $copyright = $config->get( 'RightsUrl' );
3542  }
3543  }
3544 
3545  if ( $copyright ) {
3546  $tags['copyright'] = Html::element( 'link', [
3547  'rel' => 'license',
3548  'href' => $copyright ]
3549  );
3550  }
3551 
3552  # Feeds
3553  if ( $config->get( 'Feed' ) ) {
3554  $feedLinks = [];
3555 
3556  foreach ( $this->getSyndicationLinks() as $format => $link ) {
3557  # Use the page name for the title. In principle, this could
3558  # lead to issues with having the same name for different feeds
3559  # corresponding to the same page, but we can't avoid that at
3560  # this low a level.
3561 
3562  $feedLinks[] = $this->feedLink(
3563  $format,
3564  $link,
3565  # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
3566  $this->msg(
3567  "page-{$format}-feed", $this->getTitle()->getPrefixedText()
3568  )->text()
3569  );
3570  }
3571 
3572  # Recent changes feed should appear on every page (except recentchanges,
3573  # that would be redundant). Put it after the per-page feed to avoid
3574  # changing existing behavior. It's still available, probably via a
3575  # menu in your browser. Some sites might have a different feed they'd
3576  # like to promote instead of the RC feed (maybe like a "Recent New Articles"
3577  # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
3578  # If so, use it instead.
3579  $sitename = $config->get( 'Sitename' );
3580  $overrideSiteFeed = $config->get( 'OverrideSiteFeed' );
3581  if ( $overrideSiteFeed ) {
3582  foreach ( $overrideSiteFeed as $type => $feedUrl ) {
3583  // Note, this->feedLink escapes the url.
3584  $feedLinks[] = $this->feedLink(
3585  $type,
3586  $feedUrl,
3587  $this->msg( "site-{$type}-feed", $sitename )->text()
3588  );
3589  }
3590  } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
3591  $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
3592  foreach ( $this->getAdvertisedFeedTypes() as $format ) {
3593  $feedLinks[] = $this->feedLink(
3594  $format,
3595  $rctitle->getLocalURL( [ 'feed' => $format ] ),
3596  # For grep: 'site-rss-feed', 'site-atom-feed'
3597  $this->msg( "site-{$format}-feed", $sitename )->text()
3598  );
3599  }
3600  }
3601 
3602  # Allow extensions to change the list pf feeds. This hook is primarily for changing,
3603  # manipulating or removing existing feed tags. If you want to add new feeds, you should
3604  # use OutputPage::addFeedLink() instead.
3605  Hooks::run( 'AfterBuildFeedLinks', [ &$feedLinks ] );
3606 
3607  $tags += $feedLinks;
3608  }
3609 
3610  # Canonical URL
3611  if ( $config->get( 'EnableCanonicalServerLink' ) ) {
3612  if ( $canonicalUrl !== false ) {
3613  $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL );
3614  } elseif ( $this->isArticleRelated() ) {
3615  // This affects all requests where "setArticleRelated" is true. This is
3616  // typically all requests that show content (query title, curid, oldid, diff),
3617  // and all wikipage actions (edit, delete, purge, info, history etc.).
3618  // It does not apply to File pages and Special pages.
3619  // 'history' and 'info' actions address page metadata rather than the page
3620  // content itself, so they may not be canonicalized to the view page url.
3621  // TODO: this ought to be better encapsulated in the Action class.
3622  $action = Action::getActionName( $this->getContext() );
3623  if ( in_array( $action, [ 'history', 'info' ] ) ) {
3624  $query = "action={$action}";
3625  } else {
3626  $query = '';
3627  }
3628  $canonicalUrl = $this->getTitle()->getCanonicalURL( $query );
3629  } else {
3630  $reqUrl = $this->getRequest()->getRequestURL();
3631  $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL );
3632  }
3633  }
3634  if ( $canonicalUrl !== false ) {
3635  $tags[] = Html::element( 'link', [
3636  'rel' => 'canonical',
3637  'href' => $canonicalUrl
3638  ] );
3639  }
3640 
3641  // Allow extensions to add, remove and/or otherwise manipulate these links
3642  // If you want only to *add* <head> links, please use the addHeadItem()
3643  // (or addHeadItems() for multiple items) method instead.
3644  // This hook is provided as a last resort for extensions to modify these
3645  // links before the output is sent to client.
3646  Hooks::run( 'OutputPageAfterGetHeadLinksArray', [ &$tags, $this ] );
3647 
3648  return $tags;
3649  }
3650 
3659  private function feedLink( $type, $url, $text ) {
3660  return Html::element( 'link', [
3661  'rel' => 'alternate',
3662  'type' => "application/$type+xml",
3663  'title' => $text,
3664  'href' => $url ]
3665  );
3666  }
3667 
3677  public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
3678  $options = [];
3679  if ( $media ) {
3680  $options['media'] = $media;
3681  }
3682  if ( $condition ) {
3683  $options['condition'] = $condition;
3684  }
3685  if ( $dir ) {
3686  $options['dir'] = $dir;
3687  }
3688  $this->styles[$style] = $options;
3689  }
3690 
3698  public function addInlineStyle( $style_css, $flip = 'noflip' ) {
3699  if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
3700  # If wanted, and the interface is right-to-left, flip the CSS
3701  $style_css = CSSJanus::transform( $style_css, true, false );
3702  }
3703  $this->mInlineStyles .= Html::inlineStyle( $style_css );
3704  }
3705 
3711  protected function buildExemptModules() {
3712  $chunks = [];
3713 
3714  // Requirements:
3715  // - Within modules provided by the software (core, skin, extensions),
3716  // styles from skin stylesheets should be overridden by styles
3717  // from modules dynamically loaded with JavaScript.
3718  // - Styles from site-specific, private, and user modules should override
3719  // both of the above.
3720  //
3721  // The effective order for stylesheets must thus be:
3722  // 1. Page style modules, formatted server-side by ResourceLoaderClientHtml.
3723  // 2. Dynamically-loaded styles, inserted client-side by mw.loader.
3724  // 3. Styles that are site-specific, private or from the user, formatted
3725  // server-side by this function.
3726  //
3727  // The 'ResourceLoaderDynamicStyles' marker helps JavaScript know where
3728  // point #2 is.
3729 
3730  // Add legacy styles added through addStyle()/addInlineStyle() here
3731  $chunks[] = implode( '', $this->buildCssLinksArray() ) . $this->mInlineStyles;
3732 
3733  // Things that go after the ResourceLoaderDynamicStyles marker
3734  $append = [];
3735  $separateReq = [ 'site.styles', 'user.styles' ];
3736  foreach ( $this->rlExemptStyleModules as $group => $moduleNames ) {
3737  if ( $moduleNames ) {
3738  $append[] = $this->makeResourceLoaderLink(
3739  array_diff( $moduleNames, $separateReq ),
3741  );
3742 
3743  foreach ( array_intersect( $moduleNames, $separateReq ) as $name ) {
3744  // These require their own dedicated request in order to support "@import"
3745  // syntax, which is incompatible with concatenation. (T147667, T37562)
3746  $append[] = $this->makeResourceLoaderLink( $name,
3748  );
3749  }
3750  }
3751  }
3752  if ( $append ) {
3753  $chunks[] = Html::element(
3754  'meta',
3755  [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ]
3756  );
3757  $chunks = array_merge( $chunks, $append );
3758  }
3759 
3760  return self::combineWrappedStrings( $chunks );
3761  }
3762 
3766  public function buildCssLinksArray() {
3767  $links = [];
3768 
3769  foreach ( $this->styles as $file => $options ) {
3770  $link = $this->styleLink( $file, $options );
3771  if ( $link ) {
3772  $links[$file] = $link;
3773  }
3774  }
3775  return $links;
3776  }
3777 
3785  protected function styleLink( $style, array $options ) {
3786  if ( isset( $options['dir'] ) && $this->getLanguage()->getDir() != $options['dir'] ) {
3787  return '';
3788  }
3789 
3790  if ( isset( $options['media'] ) ) {
3791  $media = self::transformCssMedia( $options['media'] );
3792  if ( is_null( $media ) ) {
3793  return '';
3794  }
3795  } else {
3796  $media = 'all';
3797  }
3798 
3799  if ( substr( $style, 0, 1 ) == '/' ||
3800  substr( $style, 0, 5 ) == 'http:' ||
3801  substr( $style, 0, 6 ) == 'https:' ) {
3802  $url = $style;
3803  } else {
3804  $config = $this->getConfig();
3805  // Append file hash as query parameter
3806  $url = self::transformResourcePath(
3807  $config,
3808  $config->get( 'StylePath' ) . '/' . $style
3809  );
3810  }
3811 
3812  $link = Html::linkedStyle( $url, $media );
3813 
3814  if ( isset( $options['condition'] ) ) {
3815  $condition = htmlspecialchars( $options['condition'] );
3816  $link = "<!--[if $condition]>$link<![endif]-->";
3817  }
3818  return $link;
3819  }
3820 
3842  public static function transformResourcePath( Config $config, $path ) {
3843  global $IP;
3844 
3845  $localDir = $IP;
3846  $remotePathPrefix = $config->get( 'ResourceBasePath' );
3847  if ( $remotePathPrefix === '' ) {
3848  // The configured base path is required to be empty string for
3849  // wikis in the domain root
3850  $remotePath = '/';
3851  } else {
3852  $remotePath = $remotePathPrefix;
3853  }
3854  if ( strpos( $path, $remotePath ) !== 0 || substr( $path, 0, 2 ) === '//' ) {
3855  // - Path is outside wgResourceBasePath, ignore.
3856  // - Path is protocol-relative. Fixes T155310. Not supported by RelPath lib.
3857  return $path;
3858  }
3859  // For files in resources, extensions/ or skins/, ResourceBasePath is preferred here.
3860  // For other misc files in $IP, we'll fallback to that as well. There is, however, a fourth
3861  // supported dir/path pair in the configuration (wgUploadDirectory, wgUploadPath)
3862  // which is not expected to be in wgResourceBasePath on CDNs. (T155146)
3863  $uploadPath = $config->get( 'UploadPath' );
3864  if ( strpos( $path, $uploadPath ) === 0 ) {
3865  $localDir = $config->get( 'UploadDirectory' );
3866  $remotePathPrefix = $remotePath = $uploadPath;
3867  }
3868 
3869  $path = RelPath::getRelativePath( $path, $remotePath );
3870  return self::transformFilePath( $remotePathPrefix, $localDir, $path );
3871  }
3872 
3884  public static function transformFilePath( $remotePathPrefix, $localPath, $file ) {
3885  $hash = md5_file( "$localPath/$file" );
3886  if ( $hash === false ) {
3887  wfLogWarning( __METHOD__ . ": Failed to hash $localPath/$file" );
3888  $hash = '';
3889  }
3890  return "$remotePathPrefix/$file?" . substr( $hash, 0, 5 );
3891  }
3892 
3900  public static function transformCssMedia( $media ) {
3901  global $wgRequest;
3902 
3903  // https://www.w3.org/TR/css3-mediaqueries/#syntax
3904  $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
3905 
3906  // Switch in on-screen display for media testing
3907  $switches = [
3908  'printable' => 'print',
3909  'handheld' => 'handheld',
3910  ];
3911  foreach ( $switches as $switch => $targetMedia ) {
3912  if ( $wgRequest->getBool( $switch ) ) {
3913  if ( $media == $targetMedia ) {
3914  $media = '';
3915  } elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
3916  /* This regex will not attempt to understand a comma-separated media_query_list
3917  *
3918  * Example supported values for $media:
3919  * 'screen', 'only screen', 'screen and (min-width: 982px)' ),
3920  * Example NOT supported value for $media:
3921  * '3d-glasses, screen, print and resolution > 90dpi'
3922  *
3923  * If it's a print request, we never want any kind of screen stylesheets
3924  * If it's a handheld request (currently the only other choice with a switch),
3925  * we don't want simple 'screen' but we might want screen queries that
3926  * have a max-width or something, so we'll pass all others on and let the
3927  * client do the query.
3928  */
3929  if ( $targetMedia == 'print' || $media == 'screen' ) {
3930  return null;
3931  }
3932  }
3933  }
3934  }
3935 
3936  return $media;
3937  }
3938 
3945  public function addWikiMsg( /*...*/ ) {
3946  $args = func_get_args();
3947  $name = array_shift( $args );
3948  $this->addWikiMsgArray( $name, $args );
3949  }
3950 
3959  public function addWikiMsgArray( $name, $args ) {
3960  $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
3961  }
3962 
3988  public function wrapWikiMsg( $wrap /*, ...*/ ) {
3989  $msgSpecs = func_get_args();
3990  array_shift( $msgSpecs );
3991  $msgSpecs = array_values( $msgSpecs );
3992  $s = $wrap;
3993  foreach ( $msgSpecs as $n => $spec ) {
3994  if ( is_array( $spec ) ) {
3995  $args = $spec;
3996  $name = array_shift( $args );
3997  } else {
3998  $args = [];
3999  $name = $spec;
4000  }
4001  $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
4002  }
4003  $this->addWikiTextAsInterface( $s );
4004  }
4005 
4011  public function isTOCEnabled() {
4012  return $this->mEnableTOC;
4013  }
4014 
4022  public static function setupOOUI( $skinName = 'default', $dir = 'ltr' ) {
4024  $theme = $themes[$skinName] ?? $themes['default'];
4025  // For example, 'OOUI\WikimediaUITheme'.
4026  $themeClass = "OOUI\\{$theme}Theme";
4027  OOUI\Theme::setSingleton( new $themeClass() );
4028  OOUI\Element::setDefaultDir( $dir );
4029  }
4030 
4037  public function enableOOUI() {
4038  self::setupOOUI(
4039  strtolower( $this->getSkin()->getSkinName() ),
4040  $this->getLanguage()->getDir()
4041  );
4042  $this->addModuleStyles( [
4043  'oojs-ui-core.styles',
4044  'oojs-ui.styles.indicators',
4045  'mediawiki.widgets.styles',
4046  'oojs-ui-core.icons',
4047  ] );
4048  }
4049 
4059  public function getCSPNonce() {
4061  return false;
4062  }
4063  if ( $this->CSPNonce === null ) {
4064  // XXX It might be expensive to generate randomness
4065  // on every request, on Windows.
4066  $rand = random_bytes( 15 );
4067  $this->CSPNonce = base64_encode( $rand );
4068  }
4069  return $this->CSPNonce;
4070  }
4071 
4072 }
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
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:527
static newFromContext(IContextSource $context)
Get a ParserOptions object from a IContextSource object.
ResourceLoader $mResourceLoader
Definition: OutputPage.php:159
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
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
Definition: hooks.txt:1963
array $rlExemptStyleModules
Definition: OutputPage.php:168
array $mTemplateIds
Definition: OutputPage.php:174
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
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.
getHeadItemsArray()
Get an array of head items.
Definition: OutputPage.php:623
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:135
int $mCdnMaxage
Cache stuff.
Definition: OutputPage.php:234
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1566
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.
either a plain
Definition: hooks.txt:2024
getHTMLTitle()
Return the "HTML title", i.e.
Definition: OutputPage.php:898
int $mCdnMaxageLimit
Upper limit on mCdnMaxage.
Definition: OutputPage.php:236
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:275
$wgVersion
MediaWiki version number.
setTarget( $target)
Sets ResourceLoader target for load.php links.
Definition: OutputPage.php:583
bool $mEnableTOC
Whether parser output contains a table of contents.
Definition: OutputPage.php:297
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:669
callable [] $contentOverrideCallbacks
Definition: OutputPage.php:311
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.
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
static newMainPage(MessageLocalizer $localizer=null)
Create a new Title for the Main Page.
Definition: Title.php:654
getCanonicalUrl()
Returns the URL to be used for the <link rel="canonical"> if one is set.
Definition: OutputPage.php:438
int $mContainsNewMagic
Definition: OutputPage.php:199
$IP
Definition: WebStart.php:41
setStatusCode( $statusCode)
Set the HTTP status code to send with the output.
Definition: OutputPage.php:376
setArticleFlag( $newVal)
Set whether the displayed content is related to the source of the corresponding article on the wiki S...
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext such as when responding to a resource loader request or generating HTML output & $resourceLoader
Definition: hooks.txt:2614
wrapWikiMsg( $wrap)
This function takes a number of message/argument specifications, wraps them in some overall structure...
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing after in associative array form before processing starts Return false to skip default processing and return $ret $linkRenderer
Definition: hooks.txt:1963
addModules( $modules)
Load one or more ResourceLoader modules on this page.
Definition: OutputPage.php:541
string $mPageTitle
The contents of.
Definition: OutputPage.php:58
setLanguageLinks(array $newLinkArray)
Reset the language links and add new language links.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Definition: router.php:42
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2140
setSubtitle( $str)
Replace the subtitle with $str.
string $mInlineStyles
Inline CSS styles.
Definition: OutputPage.php:138
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 ...
array $mMetatags
Should be private.
Definition: OutputPage.php:48
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:945
setCopyrightUrl( $url)
Set the copyright URL to send with the output.
Definition: OutputPage.php:367
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:503
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:344
static isNonceRequired(Config $config)
Should we set nonce attribute.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
& getFileSearchOptions()
array $mHeadItems
Array of elements in "<head>".
Definition: OutputPage.php:147
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:153
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition: hooks.txt:175
$value
const NS_SPECIAL
Definition: Defines.php:49
const PROTO_CURRENT
Definition: Defines.php:218
string null $mTarget
ResourceLoader target for load.php links.
Definition: OutputPage.php:292
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:689
array $limitReportJSData
Profiling data.
Definition: OutputPage.php:305
prependHTML( $text)
Prepend $text to the body HTML.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
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:700
addVaryHeader( $header, array $option=null)
Add an HTTP header that will influence on the cache.
msg( $key)
Get a Message object with context set Parameters are the same as wfMessage()
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:983
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:483
string $mPageLinkTitle
Used by skin template.
Definition: OutputPage.php:144
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:302
adaptCdnTTL( $mtime, $minTTL=0, $maxTTL=0)
Get TTL in [$minTTL,$maxTTL] in pass it to lowerCdnMaxage()
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1722
disable()
Disable output completely, i.e.
Content for JavaScript pages.
this hook is for auditing only $response
Definition: hooks.txt:767
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:726
string $mRevisionTimestamp
Definition: OutputPage.php:249
array $mLanguageLinks
Array of Interwiki Prefixed (non DB key) Titles (e.g.
Definition: OutputPage.php:127
setHTMLTitle( $name)
"HTML title" means the contents of "<title>".
Definition: OutputPage.php:885
IContextSource $context
array Title $mRedirectedFrom
If the current page was reached through a redirect, $mRedirectedFrom contains the Title of the redire...
Definition: OutputPage.php:282
string $displayTitle
The displayed title of the page.
Definition: OutputPage.php:67
setPageTitle( $name)
"Page title" means the contents of <h1>.
Definition: OutputPage.php:923
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.
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
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:262
isPrintable()
Return whether the page is "printable".
$mProperties
Additional key => value data.
Definition: OutputPage.php:287
getNamespace()
Get the namespace index.
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:194
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:907
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title e g db for database replication lag or jobqueue for job queue size converted to pseudo seconds It is possible to add more fields and they will be returned to the user in the API response after the basic globals have been set but before ordinary actions take place $output
Definition: hooks.txt:2198
bool $mPreventClickjacking
Controls if anti-clickjacking / frame-breaking headers will be sent.
Definition: OutputPage.php:243
if( $line===false) $args
Definition: cdb.php:64
static stripOuterParagraph( $html)
Strip outer.
Definition: Parser.php:6403
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:438
array $mJsConfigVars
Definition: OutputPage.php:171
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...
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:3032
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:490
passed in as a query string parameter to the various URLs constructed here(i.e. $prevlink) $ldel you ll need to handle error messages
Definition: hooks.txt:1268
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:94
Allows changing specific properties of a context object, without changing the main one...
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:85
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:814
array $mAllowedModules
What level of &#39;untrustworthiness&#39; is allowed in CSS/JS modules loaded on this page?
Definition: OutputPage.php:189
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:326
bool $mNoGallery
Comes from the parser.
Definition: OutputPage.php:231
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.
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:872
getHTML()
Get the body HTML.
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:2040
getLanguageLinks()
Get the list of language links.
array $mFileVersion
Definition: OutputPage.php:252
array $mModuleStyles
Definition: OutputPage.php:156
bool $mHasCopyright
Is the content subject to copyright.
Definition: OutputPage.php:88
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.
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
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.
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title e g db for database replication lag or jobqueue for job queue size converted to pseudo seconds It is possible to add more fields and they will be returned to the user in the API response after the basic globals have been set but before ordinary actions take place or wrap services the preferred way to define a new service is the $wgServiceWiringFiles array $services
Definition: hooks.txt:2198
addSubtitle( $str)
Add $str to the subtitle.
headElement(Skin $sk, $includeStyle=true)
static sendHeaders(IContextSource $context)
Send CSP headers based on wiki config.
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:840
array array $mIndicators
Definition: OutputPage.php:124
addMeta( $name, $val)
Add a new "<meta>" tag To add an http-equiv meta tag, precede the name with "http:".
Definition: OutputPage.php:387
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:212
$res
Definition: database.txt:21
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.
static preloadTitleInfo(ResourceLoaderContext $context, IDatabase $db, array $moduleNames)
hasHeadItem( $name)
Check if the header item $name is already set.
Definition: OutputPage.php:659
$mFeedLinksAppendQuery
Definition: OutputPage.php:182
getMetaTags()
Returns the current <meta> tags.
Definition: OutputPage.php:397
$mLinkHeader
Link: header contents.
Definition: OutputPage.php:316
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:218
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:150
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.
addWikiMsg()
Add a wikitext-formatted message to the output.
static combineWrappedStrings(array $chunks)
Combine WrappedString chunks and filter out empty ones.
$params
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.
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1963
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:315
and(b) You must cause any modified files to carry prominent notices stating that You changed the files
getCategories( $type='all')
Get the list of category names this page belongs to.
string $mHTMLtitle
Stores contents of "<title>" tag.
Definition: OutputPage.php:76
bool $mHideNewSectionLink
Definition: OutputPage.php:224
string $mBodytext
Contains all of the "<body>" content.
Definition: OutputPage.php:73
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Definition: hooks.txt:1963
buildCssLinksArray()
getRlClientContext()
getDisplayTitle()
Returns page display title.
Definition: OutputPage.php:968
getAdvertisedFeedTypes()
Return effective list of advertised feed types.
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:767
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:649
getHtmlElementAttributes()
Return values for <html> element.
Definition: Skin.php:474
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:912
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:272
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:96
addLink(array $linkarr)
Add a new <link> tag to the page header.
Definition: OutputPage.php:408
$filter
addModuleStyles( $modules)
Load the styles of one or more ResourceLoader modules on this page.
Definition: OutputPage.php:567
const PROTO_RELATIVE
Definition: Defines.php:217
array $mCategories
Definition: OutputPage.php:118
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
string $CSPNonce
The nonce for Content-Security-Policy.
Definition: OutputPage.php:321
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:552
bool $mNewSectionLink
Definition: OutputPage.php:221
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:959
__construct(IContextSource $context)
Constructor for OutputPage.
Definition: OutputPage.php:334
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition: hooks.txt:767
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
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.
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:4831
addScriptFile( $file, $unused=null)
Add a JavaScript file to be loaded as <script> on this page.
Definition: OutputPage.php:461
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:449
array $mCategoryLinks
Definition: OutputPage.php:115
static getSelectFields()
Fields that LinkCache needs to select.
Definition: LinkCache.php:218
parseAsContent( $text, $linestart=true)
Parse wikitext in the page content language and return the HTML.
setTitle(Title $t)
Set the Title object to use.
Definition: OutputPage.php:996
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:106
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:618
bool $mCanonicalUrl
Definition: OutputPage.php:54
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:680
setLastModified( $timestamp)
Override the last modified timestamp.
Definition: OutputPage.php:828
addHeadItem( $name, $value)
Add or replace a head item to the output.
Definition: OutputPage.php:639
string $mLastModified
Used for sending cache control.
Definition: OutputPage.php:112
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
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...
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping $template
Definition: hooks.txt:767
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:614
const PROTO_CANONICAL
Definition: Defines.php:219
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:246
getSyndicationLinks()
Return URLs for each supported syndication format for this page.
getText( $options=[])
Get the output HTML.
addContentOverride(LinkTarget $target, Content $content)
Add a mapping from a LinkTarget to a Content, for things like page preview.
Definition: OutputPage.php:594
array $contentOverrides
Map Title to Content.
Definition: OutputPage.php:308
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:180
array $mImageTimeKeys
Definition: OutputPage.php:177
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...
forceHideNewSectionLink()
Forcibly hide the new section link?
static isXmlMimeType( $mimetype)
Determines if the given MIME type is xml.
Definition: Html.php:997
isRedirect( $flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition: Title.php:3049
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
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
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:355
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:791
static makeLoaderQuery( $modules, $lang, $skin, $user=null, $version=null, $debug=false, $only=null, $printable=false, $handheld=false, $extraQuery=[])
Build a query array (array representation of query string) for load.php.
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
array $mSubtitle
Contains the page subtitle.
Definition: OutputPage.php:100
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
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:165
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.
$content
Definition: pageupdater.txt:72
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:156
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:427
bool $mIsArticle
Is the displayed content related to the source of the corresponding wiki article. ...
Definition: OutputPage.php:82
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.
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2614
setIndexPolicy( $policy)
Set the index policy for the page, but leave the follow policy un- touched.
Definition: OutputPage.php:858
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:471
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition: hooks.txt:2198
string $mRedirect
Definition: OutputPage.php:103
static bcp47( $code)
Get the normalised IETF language tag See unit test for examples.
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1454
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:418
setRevisionTimestamp( $timestamp)
Set the timestamp of the revision which will be displayed.
ResourceLoaderClientHtml $rlClient
Definition: OutputPage.php:162
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.
Object passed around to modules which contains information about the state of a specific loader reque...
ParserOptions $mParserOptions
lazy initialised, use parserOptions()
Definition: OutputPage.php:205
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:711
setDisplayTitle( $html)
Same as page title but only contains name of the page, not any other text.
Definition: OutputPage.php:956
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:320
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition: Article.php:1028