MediaWiki  master
OutputPage.php
Go to the documentation of this file.
1 <?php
30 
46 class OutputPage extends ContextSource {
48  protected $mMetatags = [];
49 
51  protected $mLinktags = [];
52 
54  protected $mCanonicalUrl = false;
55 
59  private $mPageTitle = '';
60 
68  private $displayTitle;
69 
74  public $mBodytext = '';
75 
77  private $mHTMLtitle = '';
78 
83  private $mIsArticle = false;
84 
86  private $mIsArticleRelated = true;
87 
89  private $mHasCopyright = false;
90 
95  private $mPrintable = false;
96 
101  private $mSubtitle = [];
102 
104  public $mRedirect = '';
105 
107  protected $mStatusCode;
108 
113  protected $mLastModified = '';
114 
116  protected $mCategoryLinks = [];
117 
119  protected $mCategories = [
120  'hidden' => [],
121  'normal' => [],
122  ];
123 
125  protected $mIndicators = [];
126 
128  private $mLanguageLinks = [];
129 
136  private $mScripts = '';
137 
139  protected $mInlineStyles = '';
140 
145  public $mPageLinkTitle = '';
146 
148  protected $mHeadItems = [];
149 
151  protected $mAdditionalBodyClasses = [];
152 
154  protected $mModules = [];
155 
157  protected $mModuleStyles = [];
158 
160  protected $mResourceLoader;
161 
163  private $rlClient;
164 
167 
170 
172  protected $mJsConfigVars = [];
173 
175  protected $mTemplateIds = [];
176 
178  protected $mImageTimeKeys = [];
179 
181  public $mRedirectCode = '';
182 
183  protected $mFeedLinksAppendQuery = null;
184 
190  protected $mAllowedModules = [
191  ResourceLoaderModule::TYPE_COMBINED => ResourceLoaderModule::ORIGIN_ALL,
192  ];
193 
195  protected $mDoNothing = false;
196 
197  // Parser related.
198 
200  protected $mContainsNewMagic = 0;
201 
206  protected $mParserOptions = null;
207 
213  private $mFeedLinks = [];
214 
215  // Gwicke work on squid caching? Roughly from 2003.
216  protected $mEnableClientCache = true;
217 
219  private $mArticleBodyOnly = false;
220 
222  protected $mNewSectionLink = false;
223 
225  protected $mHideNewSectionLink = false;
226 
232  public $mNoGallery = false;
233 
235  protected $mCdnMaxage = 0;
237  protected $mCdnMaxageLimit = INF;
238 
244  protected $mPreventClickjacking = true;
245 
247  private $mRevisionId = null;
248 
250  private $mRevisionTimestamp = null;
251 
253  protected $mFileVersion = null;
254 
263  protected $styles = [];
264 
265  private $mIndexPolicy = 'index';
266  private $mFollowPolicy = 'follow';
267 
273  private $mVaryHeader = [
274  'Accept-Encoding' => null,
275  ];
276 
283  private $mRedirectedFrom = null;
284 
288  private $mProperties = [];
289 
293  private $mTarget = null;
294 
298  private $mEnableTOC = false;
299 
303  private $copyrightUrl;
304 
306  private $limitReportJSData = [];
307 
309  private $contentOverrides = [];
310 
313 
317  private $mLinkHeader = [];
318 
322  private $CSPNonce;
323 
327  private static $cacheVaryCookies = null;
328 
335  public function __construct( IContextSource $context ) {
336  $this->setContext( $context );
337  }
338 
345  public function redirect( $url, $responsecode = '302' ) {
346  # Strip newlines as a paranoia check for header injection in PHP<5.1.2
347  $this->mRedirect = str_replace( "\n", '', $url );
348  $this->mRedirectCode = $responsecode;
349  }
350 
356  public function getRedirect() {
357  return $this->mRedirect;
358  }
359 
368  public function setCopyrightUrl( $url ) {
369  $this->copyrightUrl = $url;
370  }
371 
377  public function setStatusCode( $statusCode ) {
378  $this->mStatusCode = $statusCode;
379  }
380 
388  public function addMeta( $name, $val ) {
389  $this->mMetatags[] = [ $name, $val ];
390  }
391 
398  public function getMetaTags() {
399  return $this->mMetatags;
400  }
401 
409  public function addLink( array $linkarr ) {
410  $this->mLinktags[] = $linkarr;
411  }
412 
419  public function getLinkTags() {
420  return $this->mLinktags;
421  }
422 
428  public function setCanonicalUrl( $url ) {
429  $this->mCanonicalUrl = $url;
430  }
431 
439  public function getCanonicalUrl() {
440  return $this->mCanonicalUrl;
441  }
442 
450  public function addScript( $script ) {
451  $this->mScripts .= $script;
452  }
453 
462  public function addScriptFile( $file, $unused = null ) {
463  $this->addScript( Html::linkedScript( $file, $this->getCSPNonce() ) );
464  }
465 
472  public function addInlineScript( $script ) {
473  $this->mScripts .= Html::inlineScript( "\n$script\n", $this->getCSPNonce() ) . "\n";
474  }
475 
484  protected function filterModules( array $modules, $position = null,
485  $type = ResourceLoaderModule::TYPE_COMBINED
486  ) {
488  $filteredModules = [];
489  foreach ( $modules as $val ) {
490  $module = $resourceLoader->getModule( $val );
491  if ( $module instanceof ResourceLoaderModule
492  && $module->getOrigin() <= $this->getAllowedModules( $type )
493  ) {
494  if ( $this->mTarget && !in_array( $this->mTarget, $module->getTargets() ) ) {
495  $this->warnModuleTargetFilter( $module->getName() );
496  continue;
497  }
498  $filteredModules[] = $val;
499  }
500  }
501  return $filteredModules;
502  }
503 
504  private function warnModuleTargetFilter( $moduleName ) {
505  static $warnings = [];
506  if ( isset( $warnings[$this->mTarget][$moduleName] ) ) {
507  return;
508  }
509  $warnings[$this->mTarget][$moduleName] = true;
510  $this->getResourceLoader()->getLogger()->debug(
511  'Module "{module}" not loadable on target "{target}".',
512  [
513  'module' => $moduleName,
514  'target' => $this->mTarget,
515  ]
516  );
517  }
518 
528  public function getModules( $filter = false, $position = null, $param = 'mModules',
529  $type = ResourceLoaderModule::TYPE_COMBINED
530  ) {
531  $modules = array_values( array_unique( $this->$param ) );
532  return $filter
533  ? $this->filterModules( $modules, null, $type )
534  : $modules;
535  }
536 
542  public function addModules( $modules ) {
543  $this->mModules = array_merge( $this->mModules, (array)$modules );
544  }
545 
553  public function getModuleStyles( $filter = false, $position = null ) {
554  return $this->getModules( $filter, null, 'mModuleStyles',
555  ResourceLoaderModule::TYPE_STYLES
556  );
557  }
558 
568  public function addModuleStyles( $modules ) {
569  $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
570  }
571 
575  public function getTarget() {
576  return $this->mTarget;
577  }
578 
584  public function setTarget( $target ) {
585  $this->mTarget = $target;
586  }
587 
595  public function addContentOverride( LinkTarget $target, Content $content ) {
596  if ( !$this->contentOverrides ) {
597  // Register a callback for $this->contentOverrides on the first call
598  $this->addContentOverrideCallback( function ( LinkTarget $target ) {
599  $key = $target->getNamespace() . ':' . $target->getDBkey();
600  return $this->contentOverrides[$key] ?? null;
601  } );
602  }
603 
604  $key = $target->getNamespace() . ':' . $target->getDBkey();
605  $this->contentOverrides[$key] = $content;
606  }
607 
615  public function addContentOverrideCallback( callable $callback ) {
616  $this->contentOverrideCallbacks[] = $callback;
617  }
618 
624  public function getHeadItemsArray() {
625  return $this->mHeadItems;
626  }
627 
640  public function addHeadItem( $name, $value ) {
641  $this->mHeadItems[$name] = $value;
642  }
643 
650  public function addHeadItems( $values ) {
651  $this->mHeadItems = array_merge( $this->mHeadItems, (array)$values );
652  }
653 
660  public function hasHeadItem( $name ) {
661  return isset( $this->mHeadItems[$name] );
662  }
663 
670  public function addBodyClasses( $classes ) {
671  $this->mAdditionalBodyClasses = array_merge( $this->mAdditionalBodyClasses, (array)$classes );
672  }
673 
681  public function setArticleBodyOnly( $only ) {
682  $this->mArticleBodyOnly = $only;
683  }
684 
690  public function getArticleBodyOnly() {
692  }
693 
701  public function setProperty( $name, $value ) {
702  $this->mProperties[$name] = $value;
703  }
704 
712  public function getProperty( $name ) {
713  return $this->mProperties[$name] ?? null;
714  }
715 
727  public function checkLastModified( $timestamp ) {
728  if ( !$timestamp || $timestamp == '19700101000000' ) {
729  wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
730  return false;
731  }
732  $config = $this->getConfig();
733  if ( !$config->get( 'CachePages' ) ) {
734  wfDebug( __METHOD__ . ": CACHE DISABLED\n" );
735  return false;
736  }
737 
738  $timestamp = wfTimestamp( TS_MW, $timestamp );
739  $modifiedTimes = [
740  'page' => $timestamp,
741  'user' => $this->getUser()->getTouched(),
742  'epoch' => $config->get( 'CacheEpoch' )
743  ];
744  if ( $config->get( 'UseCdn' ) ) {
745  $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, $this->getCdnCacheEpoch(
746  time(),
747  $config->get( 'CdnMaxAge' )
748  ) );
749  }
750  Hooks::run( 'OutputPageCheckLastModified', [ &$modifiedTimes, $this ] );
751 
752  $maxModified = max( $modifiedTimes );
753  $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
754 
755  $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' );
756  if ( $clientHeader === false ) {
757  wfDebug( __METHOD__ . ": client did not send If-Modified-Since header", 'private' );
758  return false;
759  }
760 
761  # IE sends sizes after the date like this:
762  # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
763  # this breaks strtotime().
764  $clientHeader = preg_replace( '/;.*$/', '', $clientHeader );
765 
766  Wikimedia\suppressWarnings(); // E_STRICT system time warnings
767  $clientHeaderTime = strtotime( $clientHeader );
768  Wikimedia\restoreWarnings();
769  if ( !$clientHeaderTime ) {
770  wfDebug( __METHOD__
771  . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
772  return false;
773  }
774  $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
775 
776  # Make debug info
777  $info = '';
778  foreach ( $modifiedTimes as $name => $value ) {
779  if ( $info !== '' ) {
780  $info .= ', ';
781  }
782  $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
783  }
784 
785  wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
786  wfTimestamp( TS_ISO_8601, $clientHeaderTime ), 'private' );
787  wfDebug( __METHOD__ . ": effective Last-Modified: " .
788  wfTimestamp( TS_ISO_8601, $maxModified ), 'private' );
789  if ( $clientHeaderTime < $maxModified ) {
790  wfDebug( __METHOD__ . ": STALE, $info", 'private' );
791  return false;
792  }
793 
794  # Not modified
795  # Give a 304 Not Modified response code and disable body output
796  wfDebug( __METHOD__ . ": NOT MODIFIED, $info", 'private' );
797  ini_set( 'zlib.output_compression', 0 );
798  $this->getRequest()->response()->statusHeader( 304 );
799  $this->sendCacheControl();
800  $this->disable();
801 
802  // Don't output a compressed blob when using ob_gzhandler;
803  // it's technically against HTTP spec and seems to confuse
804  // Firefox when the response gets split over two packets.
806 
807  return true;
808  }
809 
815  private function getCdnCacheEpoch( $reqTime, $maxAge ) {
816  // Ensure Last-Modified is never more than $wgCdnMaxAge in the past,
817  // because even if the wiki page content hasn't changed since, static
818  // resources may have changed (skin HTML, interface messages, urls, etc.)
819  // and must roll-over in a timely manner (T46570)
820  return $reqTime - $maxAge;
821  }
822 
829  public function setLastModified( $timestamp ) {
830  $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp );
831  }
832 
841  public function setRobotPolicy( $policy ) {
842  $policy = Article::formatRobotPolicy( $policy );
843 
844  if ( isset( $policy['index'] ) ) {
845  $this->setIndexPolicy( $policy['index'] );
846  }
847  if ( isset( $policy['follow'] ) ) {
848  $this->setFollowPolicy( $policy['follow'] );
849  }
850  }
851 
859  public function setIndexPolicy( $policy ) {
860  $policy = trim( $policy );
861  if ( in_array( $policy, [ 'index', 'noindex' ] ) ) {
862  $this->mIndexPolicy = $policy;
863  }
864  }
865 
873  public function setFollowPolicy( $policy ) {
874  $policy = trim( $policy );
875  if ( in_array( $policy, [ 'follow', 'nofollow' ] ) ) {
876  $this->mFollowPolicy = $policy;
877  }
878  }
879 
886  public function setHTMLTitle( $name ) {
887  if ( $name instanceof Message ) {
888  $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
889  } else {
890  $this->mHTMLtitle = $name;
891  }
892  }
893 
899  public function getHTMLTitle() {
900  return $this->mHTMLtitle;
901  }
902 
908  public function setRedirectedFrom( $t ) {
909  $this->mRedirectedFrom = $t;
910  }
911 
924  public function setPageTitle( $name ) {
925  if ( $name instanceof Message ) {
926  $name = $name->setContext( $this->getContext() )->text();
927  }
928 
929  # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
930  # but leave "<i>foobar</i>" alone
932  $this->mPageTitle = $nameWithTags;
933 
934  # change "<i>foo&amp;bar</i>" to "foo&bar"
935  $this->setHTMLTitle(
936  $this->msg( 'pagetitle' )->plaintextParams( Sanitizer::stripAllTags( $nameWithTags ) )
937  ->inContentLanguage()
938  );
939  }
940 
946  public function getPageTitle() {
947  return $this->mPageTitle;
948  }
949 
957  public function setDisplayTitle( $html ) {
958  $this->displayTitle = $html;
959  }
960 
969  public function getDisplayTitle() {
970  $html = $this->displayTitle;
971  if ( $html === null ) {
972  $html = $this->getTitle()->getPrefixedText();
973  }
974 
976  }
977 
984  public function getUnprefixedDisplayTitle() {
985  $text = $this->getDisplayTitle();
986  $nsPrefix = $this->getTitle()->getNsText() . ':';
987  $prefix = preg_quote( $nsPrefix, '/' );
988 
989  return preg_replace( "/^$prefix/i", '', $text );
990  }
991 
997  public function setTitle( Title $t ) {
998  // @phan-suppress-next-next-line PhanUndeclaredMethod
999  // @fixme Not all implementations of IContextSource have this method!
1000  $this->getContext()->setTitle( $t );
1001  }
1002 
1008  public function setSubtitle( $str ) {
1009  $this->clearSubtitle();
1010  $this->addSubtitle( $str );
1011  }
1012 
1018  public function addSubtitle( $str ) {
1019  if ( $str instanceof Message ) {
1020  $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
1021  } else {
1022  $this->mSubtitle[] = $str;
1023  }
1024  }
1025 
1034  public static function buildBacklinkSubtitle( Title $title, $query = [] ) {
1035  if ( $title->isRedirect() ) {
1036  $query['redirect'] = 'no';
1037  }
1038  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1039  return wfMessage( 'backlinksubtitle' )
1040  ->rawParams( $linkRenderer->makeLink( $title, null, [], $query ) );
1041  }
1042 
1049  public function addBacklinkSubtitle( Title $title, $query = [] ) {
1050  $this->addSubtitle( self::buildBacklinkSubtitle( $title, $query ) );
1051  }
1052 
1056  public function clearSubtitle() {
1057  $this->mSubtitle = [];
1058  }
1059 
1065  public function getSubtitle() {
1066  return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
1067  }
1068 
1073  public function setPrintable() {
1074  $this->mPrintable = true;
1075  }
1076 
1082  public function isPrintable() {
1083  return $this->mPrintable;
1084  }
1085 
1089  public function disable() {
1090  $this->mDoNothing = true;
1091  }
1092 
1098  public function isDisabled() {
1099  return $this->mDoNothing;
1100  }
1101 
1107  public function showNewSectionLink() {
1108  return $this->mNewSectionLink;
1109  }
1110 
1116  public function forceHideNewSectionLink() {
1118  }
1119 
1128  public function setSyndicated( $show = true ) {
1129  if ( $show ) {
1130  $this->setFeedAppendQuery( false );
1131  } else {
1132  $this->mFeedLinks = [];
1133  }
1134  }
1135 
1142  protected function getAdvertisedFeedTypes() {
1143  if ( $this->getConfig()->get( 'Feed' ) ) {
1144  return $this->getConfig()->get( 'AdvertisedFeedTypes' );
1145  } else {
1146  return [];
1147  }
1148  }
1149 
1159  public function setFeedAppendQuery( $val ) {
1160  $this->mFeedLinks = [];
1161 
1162  foreach ( $this->getAdvertisedFeedTypes() as $type ) {
1163  $query = "feed=$type";
1164  if ( is_string( $val ) ) {
1165  $query .= '&' . $val;
1166  }
1167  $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
1168  }
1169  }
1170 
1177  public function addFeedLink( $format, $href ) {
1178  if ( in_array( $format, $this->getAdvertisedFeedTypes() ) ) {
1179  $this->mFeedLinks[$format] = $href;
1180  }
1181  }
1182 
1187  public function isSyndicated() {
1188  return count( $this->mFeedLinks ) > 0;
1189  }
1190 
1195  public function getSyndicationLinks() {
1196  return $this->mFeedLinks;
1197  }
1198 
1204  public function getFeedAppendQuery() {
1206  }
1207 
1215  public function setArticleFlag( $newVal ) {
1216  $this->mIsArticle = $newVal;
1217  if ( $newVal ) {
1218  $this->mIsArticleRelated = $newVal;
1219  }
1220  }
1221 
1228  public function isArticle() {
1229  return $this->mIsArticle;
1230  }
1231 
1238  public function setArticleRelated( $newVal ) {
1239  $this->mIsArticleRelated = $newVal;
1240  if ( !$newVal ) {
1241  $this->mIsArticle = false;
1242  }
1243  }
1244 
1250  public function isArticleRelated() {
1251  return $this->mIsArticleRelated;
1252  }
1253 
1259  public function setCopyright( $hasCopyright ) {
1260  $this->mHasCopyright = $hasCopyright;
1261  }
1262 
1272  public function showsCopyright() {
1273  return $this->isArticle() || $this->mHasCopyright;
1274  }
1275 
1282  public function addLanguageLinks( array $newLinkArray ) {
1283  $this->mLanguageLinks = array_merge( $this->mLanguageLinks, $newLinkArray );
1284  }
1285 
1292  public function setLanguageLinks( array $newLinkArray ) {
1293  $this->mLanguageLinks = $newLinkArray;
1294  }
1295 
1301  public function getLanguageLinks() {
1302  return $this->mLanguageLinks;
1303  }
1304 
1310  public function addCategoryLinks( array $categories ) {
1311  if ( !$categories ) {
1312  return;
1313  }
1314 
1315  $res = $this->addCategoryLinksToLBAndGetResult( $categories );
1316 
1317  # Set all the values to 'normal'.
1318  $categories = array_fill_keys( array_keys( $categories ), 'normal' );
1319 
1320  # Mark hidden categories
1321  foreach ( $res as $row ) {
1322  if ( isset( $row->pp_value ) ) {
1323  $categories[$row->page_title] = 'hidden';
1324  }
1325  }
1326 
1327  // Avoid PHP 7.1 warning of passing $this by reference
1328  $outputPage = $this;
1329  # Add the remaining categories to the skin
1330  if ( Hooks::run(
1331  'OutputPageMakeCategoryLinks',
1332  [ &$outputPage, $categories, &$this->mCategoryLinks ] )
1333  ) {
1334  $services = MediaWikiServices::getInstance();
1335  $linkRenderer = $services->getLinkRenderer();
1336  foreach ( $categories as $category => $type ) {
1337  // array keys will cast numeric category names to ints, so cast back to string
1338  $category = (string)$category;
1339  $origcategory = $category;
1340  $title = Title::makeTitleSafe( NS_CATEGORY, $category );
1341  if ( !$title ) {
1342  continue;
1343  }
1344  $services->getContentLanguage()->findVariantLink( $category, $title, true );
1345  if ( $category != $origcategory && array_key_exists( $category, $categories ) ) {
1346  continue;
1347  }
1348  $text = $services->getContentLanguage()->convertHtml( $title->getText() );
1349  $this->mCategories[$type][] = $title->getText();
1350  $this->mCategoryLinks[$type][] = $linkRenderer->makeLink( $title, new HtmlArmor( $text ) );
1351  }
1352  }
1353  }
1354 
1359  protected function addCategoryLinksToLBAndGetResult( array $categories ) {
1360  # Add the links to a LinkBatch
1361  $arr = [ NS_CATEGORY => $categories ];
1362  $lb = new LinkBatch;
1363  $lb->setArray( $arr );
1364 
1365  # Fetch existence plus the hiddencat property
1366  $dbr = wfGetDB( DB_REPLICA );
1367  $fields = array_merge(
1369  [ 'page_namespace', 'page_title', 'pp_value' ]
1370  );
1371 
1372  $res = $dbr->select( [ 'page', 'page_props' ],
1373  $fields,
1374  $lb->constructSet( 'page', $dbr ),
1375  __METHOD__,
1376  [],
1377  [ 'page_props' => [ 'LEFT JOIN', [
1378  'pp_propname' => 'hiddencat',
1379  'pp_page = page_id'
1380  ] ] ]
1381  );
1382 
1383  # Add the results to the link cache
1384  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1385  $lb->addResultToCache( $linkCache, $res );
1386 
1387  return $res;
1388  }
1389 
1395  public function setCategoryLinks( array $categories ) {
1396  $this->mCategoryLinks = [];
1397  $this->addCategoryLinks( $categories );
1398  }
1399 
1408  public function getCategoryLinks() {
1409  return $this->mCategoryLinks;
1410  }
1411 
1421  public function getCategories( $type = 'all' ) {
1422  if ( $type === 'all' ) {
1423  $allCategories = [];
1424  foreach ( $this->mCategories as $categories ) {
1425  $allCategories = array_merge( $allCategories, $categories );
1426  }
1427  return $allCategories;
1428  }
1429  if ( !isset( $this->mCategories[$type] ) ) {
1430  throw new InvalidArgumentException( 'Invalid category type given: ' . $type );
1431  }
1432  return $this->mCategories[$type];
1433  }
1434 
1444  public function setIndicators( array $indicators ) {
1445  $this->mIndicators = $indicators + $this->mIndicators;
1446  // Keep ordered by key
1447  ksort( $this->mIndicators );
1448  }
1449 
1458  public function getIndicators() {
1459  return $this->mIndicators;
1460  }
1461 
1470  public function addHelpLink( $to, $overrideBaseUrl = false ) {
1471  $this->addModuleStyles( 'mediawiki.helplink' );
1472  $text = $this->msg( 'helppage-top-gethelp' )->escaped();
1473 
1474  if ( $overrideBaseUrl ) {
1475  $helpUrl = $to;
1476  } else {
1477  $toUrlencoded = wfUrlencode( str_replace( ' ', '_', $to ) );
1478  $helpUrl = "https://www.mediawiki.org/wiki/Special:MyLanguage/$toUrlencoded";
1479  }
1480 
1481  $link = Html::rawElement(
1482  'a',
1483  [
1484  'href' => $helpUrl,
1485  'target' => '_blank',
1486  'class' => 'mw-helplink',
1487  ],
1488  $text
1489  );
1490 
1491  $this->setIndicators( [ 'mw-helplink' => $link ] );
1492  }
1493 
1502  public function disallowUserJs() {
1503  $this->reduceAllowedModules(
1504  ResourceLoaderModule::TYPE_SCRIPTS,
1505  ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
1506  );
1507 
1508  // Site-wide styles are controlled by a config setting, see T73621
1509  // for background on why. User styles are never allowed.
1510  if ( $this->getConfig()->get( 'AllowSiteCSSOnRestrictedPages' ) ) {
1511  $styleOrigin = ResourceLoaderModule::ORIGIN_USER_SITEWIDE;
1512  } else {
1513  $styleOrigin = ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL;
1514  }
1515  $this->reduceAllowedModules(
1516  ResourceLoaderModule::TYPE_STYLES,
1517  $styleOrigin
1518  );
1519  }
1520 
1527  public function getAllowedModules( $type ) {
1528  if ( $type == ResourceLoaderModule::TYPE_COMBINED ) {
1529  return min( array_values( $this->mAllowedModules ) );
1530  } else {
1531  return $this->mAllowedModules[$type] ?? ResourceLoaderModule::ORIGIN_ALL;
1532  }
1533  }
1534 
1544  public function reduceAllowedModules( $type, $level ) {
1545  $this->mAllowedModules[$type] = min( $this->getAllowedModules( $type ), $level );
1546  }
1547 
1553  public function prependHTML( $text ) {
1554  $this->mBodytext = $text . $this->mBodytext;
1555  }
1556 
1562  public function addHTML( $text ) {
1563  $this->mBodytext .= $text;
1564  }
1565 
1575  public function addElement( $element, array $attribs = [], $contents = '' ) {
1576  $this->addHTML( Html::element( $element, $attribs, $contents ) );
1577  }
1578 
1582  public function clearHTML() {
1583  $this->mBodytext = '';
1584  }
1585 
1591  public function getHTML() {
1592  return $this->mBodytext;
1593  }
1594 
1603  public function parserOptions( $options = null ) {
1604  if ( $options !== null ) {
1605  wfDeprecated( __METHOD__ . ' with non-null $options', '1.31' );
1606  }
1607 
1608  if ( $options !== null && !empty( $options->isBogus ) ) {
1609  // Someone is trying to set a bogus pre-$wgUser PO. Check if it has
1610  // been changed somehow, and keep it if so.
1611  $anonPO = ParserOptions::newFromAnon();
1612  $anonPO->setAllowUnsafeRawHtml( false );
1613  if ( !$options->matches( $anonPO ) ) {
1614  wfLogWarning( __METHOD__ . ': Setting a changed bogus ParserOptions: ' . wfGetAllCallers( 5 ) );
1615  $options->isBogus = false;
1616  }
1617  }
1618 
1619  if ( !$this->mParserOptions ) {
1620  if ( !$this->getUser()->isSafeToLoad() ) {
1621  // $wgUser isn't unstubbable yet, so don't try to get a
1622  // ParserOptions for it. And don't cache this ParserOptions
1623  // either.
1625  $po->setAllowUnsafeRawHtml( false );
1626  $po->isBogus = true;
1627  if ( $options !== null ) {
1628  $this->mParserOptions = empty( $options->isBogus ) ? $options : null;
1629  }
1630  return $po;
1631  }
1632 
1633  $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
1634  $this->mParserOptions->setAllowUnsafeRawHtml( false );
1635  }
1636 
1637  if ( $options !== null && !empty( $options->isBogus ) ) {
1638  // They're trying to restore the bogus pre-$wgUser PO. Do the right
1639  // thing.
1640  return wfSetVar( $this->mParserOptions, null, true );
1641  } else {
1642  return wfSetVar( $this->mParserOptions, $options );
1643  }
1644  }
1645 
1653  public function setRevisionId( $revid ) {
1654  $val = is_null( $revid ) ? null : intval( $revid );
1655  return wfSetVar( $this->mRevisionId, $val, true );
1656  }
1657 
1663  public function getRevisionId() {
1664  return $this->mRevisionId;
1665  }
1666 
1673  public function isRevisionCurrent() {
1674  return $this->mRevisionId == 0 || $this->mRevisionId == $this->getTitle()->getLatestRevID();
1675  }
1676 
1684  public function setRevisionTimestamp( $timestamp ) {
1685  return wfSetVar( $this->mRevisionTimestamp, $timestamp, true );
1686  }
1687 
1694  public function getRevisionTimestamp() {
1696  }
1697 
1704  public function setFileVersion( $file ) {
1705  $val = null;
1706  if ( $file instanceof File && $file->exists() ) {
1707  $val = [ 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ];
1708  }
1709  return wfSetVar( $this->mFileVersion, $val, true );
1710  }
1711 
1717  public function getFileVersion() {
1718  return $this->mFileVersion;
1719  }
1720 
1727  public function getTemplateIds() {
1728  return $this->mTemplateIds;
1729  }
1730 
1737  public function getFileSearchOptions() {
1738  return $this->mImageTimeKeys;
1739  }
1740 
1757  public function addWikiTextAsInterface(
1758  $text, $linestart = true, Title $title = null
1759  ) {
1760  if ( $title === null ) {
1761  $title = $this->getTitle();
1762  }
1763  if ( !$title ) {
1764  throw new MWException( 'Title is null' );
1765  }
1766  $this->addWikiTextTitleInternal( $text, $title, $linestart, /*interface*/true );
1767  }
1768 
1782  public function wrapWikiTextAsInterface(
1783  $wrapperClass, $text
1784  ) {
1785  $this->addWikiTextTitleInternal(
1786  $text, $this->getTitle(),
1787  /*linestart*/true, /*interface*/true,
1788  $wrapperClass
1789  );
1790  }
1791 
1807  public function addWikiTextAsContent(
1808  $text, $linestart = true, Title $title = null
1809  ) {
1810  if ( $title === null ) {
1811  $title = $this->getTitle();
1812  }
1813  if ( !$title ) {
1814  throw new MWException( 'Title is null' );
1815  }
1816  $this->addWikiTextTitleInternal( $text, $title, $linestart, /*interface*/false );
1817  }
1818 
1831  private function addWikiTextTitleInternal(
1832  $text, Title $title, $linestart, $interface, $wrapperClass = null
1833  ) {
1834  $parserOutput = $this->parseInternal(
1835  $text, $title, $linestart, true, $interface, /*language*/null
1836  );
1837 
1838  $this->addParserOutput( $parserOutput, [
1839  'enableSectionEditLinks' => false,
1840  'wrapperDivClass' => $wrapperClass ?? '',
1841  ] );
1842  }
1843 
1852  public function addParserOutputMetadata( ParserOutput $parserOutput ) {
1853  $this->mLanguageLinks =
1854  array_merge( $this->mLanguageLinks, $parserOutput->getLanguageLinks() );
1855  $this->addCategoryLinks( $parserOutput->getCategories() );
1856  $this->setIndicators( $parserOutput->getIndicators() );
1857  $this->mNewSectionLink = $parserOutput->getNewSection();
1858  $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
1859 
1860  if ( !$parserOutput->isCacheable() ) {
1861  $this->enableClientCache( false );
1862  }
1863  $this->mNoGallery = $parserOutput->getNoGallery();
1864  $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
1865  $this->addModules( $parserOutput->getModules() );
1866  $this->addModuleStyles( $parserOutput->getModuleStyles() );
1867  $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1868  $this->mPreventClickjacking = $this->mPreventClickjacking
1869  || $parserOutput->preventClickjacking();
1870 
1871  // Template versioning...
1872  foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
1873  if ( isset( $this->mTemplateIds[$ns] ) ) {
1874  $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
1875  } else {
1876  $this->mTemplateIds[$ns] = $dbks;
1877  }
1878  }
1879  // File versioning...
1880  foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) {
1881  $this->mImageTimeKeys[$dbk] = $data;
1882  }
1883 
1884  // Hooks registered in the object
1885  $parserOutputHooks = $this->getConfig()->get( 'ParserOutputHooks' );
1886  foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
1887  list( $hookName, $data ) = $hookInfo;
1888  if ( isset( $parserOutputHooks[$hookName] ) ) {
1889  $parserOutputHooks[$hookName]( $this, $parserOutput, $data );
1890  }
1891  }
1892 
1893  // Enable OOUI if requested via ParserOutput
1894  if ( $parserOutput->getEnableOOUI() ) {
1895  $this->enableOOUI();
1896  }
1897 
1898  // Include parser limit report
1899  if ( !$this->limitReportJSData ) {
1900  $this->limitReportJSData = $parserOutput->getLimitReportJSData();
1901  }
1902 
1903  // Link flags are ignored for now, but may in the future be
1904  // used to mark individual language links.
1905  $linkFlags = [];
1906  // Avoid PHP 7.1 warning of passing $this by reference
1907  $outputPage = $this;
1908  Hooks::run( 'LanguageLinks', [ $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ] );
1909  Hooks::runWithoutAbort( 'OutputPageParserOutput', [ &$outputPage, $parserOutput ] );
1910 
1911  // This check must be after 'OutputPageParserOutput' runs in addParserOutputMetadata
1912  // so that extensions may modify ParserOutput to toggle TOC.
1913  // This cannot be moved to addParserOutputText because that is not
1914  // called by EditPage for Preview.
1915  if ( $parserOutput->getTOCHTML() ) {
1916  $this->mEnableTOC = true;
1917  }
1918  }
1919 
1928  public function addParserOutputContent( ParserOutput $parserOutput, $poOptions = [] ) {
1929  $this->addParserOutputText( $parserOutput, $poOptions );
1930 
1931  $this->addModules( $parserOutput->getModules() );
1932  $this->addModuleStyles( $parserOutput->getModuleStyles() );
1933 
1934  $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1935  }
1936 
1944  public function addParserOutputText( ParserOutput $parserOutput, $poOptions = [] ) {
1945  $text = $parserOutput->getText( $poOptions );
1946  // Avoid PHP 7.1 warning of passing $this by reference
1947  $outputPage = $this;
1948  Hooks::runWithoutAbort( 'OutputPageBeforeHTML', [ &$outputPage, &$text ] );
1949  $this->addHTML( $text );
1950  }
1951 
1958  public function addParserOutput( ParserOutput $parserOutput, $poOptions = [] ) {
1959  $this->addParserOutputMetadata( $parserOutput );
1960  $this->addParserOutputText( $parserOutput, $poOptions );
1961  }
1962 
1968  public function addTemplate( &$template ) {
1969  $this->addHTML( $template->getHTML() );
1970  }
1971 
1990  public function parse( $text, $linestart = true, $interface = false, $language = null ) {
1991  wfDeprecated( __METHOD__, '1.33' );
1992  return $this->parseInternal(
1993  $text, $this->getTitle(), $linestart, /*tidy*/false, $interface, $language
1994  )->getText( [
1995  'enableSectionEditLinks' => false,
1996  ] );
1997  }
1998 
2010  public function parseAsContent( $text, $linestart = true ) {
2011  return $this->parseInternal(
2012  $text, $this->getTitle(), $linestart, /*tidy*/true, /*interface*/false, /*language*/null
2013  )->getText( [
2014  'enableSectionEditLinks' => false,
2015  'wrapperDivClass' => ''
2016  ] );
2017  }
2018 
2031  public function parseAsInterface( $text, $linestart = true ) {
2032  return $this->parseInternal(
2033  $text, $this->getTitle(), $linestart, /*tidy*/true, /*interface*/true, /*language*/null
2034  )->getText( [
2035  'enableSectionEditLinks' => false,
2036  'wrapperDivClass' => ''
2037  ] );
2038  }
2039 
2054  public function parseInlineAsInterface( $text, $linestart = true ) {
2056  $this->parseAsInterface( $text, $linestart )
2057  );
2058  }
2059 
2073  public function parseInline( $text, $linestart = true, $interface = false ) {
2074  wfDeprecated( __METHOD__, '1.33' );
2075  $parsed = $this->parseInternal(
2076  $text, $this->getTitle(), $linestart, /*tidy*/false, $interface, /*language*/null
2077  )->getText( [
2078  'enableSectionEditLinks' => false,
2079  'wrapperDivClass' => '', /* no wrapper div */
2080  ] );
2081  return Parser::stripOuterParagraph( $parsed );
2082  }
2083 
2098  private function parseInternal( $text, $title, $linestart, $tidy, $interface, $language ) {
2099  if ( is_null( $title ) ) {
2100  throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
2101  }
2102 
2103  $popts = $this->parserOptions();
2104  $oldTidy = $popts->setTidy( $tidy );
2105  $oldInterface = $popts->setInterfaceMessage( (bool)$interface );
2106 
2107  if ( $language !== null ) {
2108  $oldLang = $popts->setTargetLanguage( $language );
2109  }
2110 
2111  $parserOutput = MediaWikiServices::getInstance()->getParser()->getFreshParser()->parse(
2112  $text, $title, $popts,
2113  $linestart, true, $this->mRevisionId
2114  );
2115 
2116  $popts->setTidy( $oldTidy );
2117  $popts->setInterfaceMessage( $oldInterface );
2118 
2119  if ( $language !== null ) {
2120  $popts->setTargetLanguage( $oldLang );
2121  }
2122 
2123  return $parserOutput;
2124  }
2125 
2131  public function setCdnMaxage( $maxage ) {
2132  $this->mCdnMaxage = min( $maxage, $this->mCdnMaxageLimit );
2133  }
2134 
2144  public function lowerCdnMaxage( $maxage ) {
2145  $this->mCdnMaxageLimit = min( $maxage, $this->mCdnMaxageLimit );
2146  $this->setCdnMaxage( $this->mCdnMaxage );
2147  }
2148 
2161  public function adaptCdnTTL( $mtime, $minTTL = 0, $maxTTL = 0 ) {
2162  $minTTL = $minTTL ?: IExpiringStore::TTL_MINUTE;
2163  $maxTTL = $maxTTL ?: $this->getConfig()->get( 'CdnMaxAge' );
2164 
2165  if ( $mtime === null || $mtime === false ) {
2166  return $minTTL; // entity does not exist
2167  }
2168 
2169  $age = MWTimestamp::time() - wfTimestamp( TS_UNIX, $mtime );
2170  $adaptiveTTL = max( 0.9 * $age, $minTTL );
2171  $adaptiveTTL = min( $adaptiveTTL, $maxTTL );
2172 
2173  $this->lowerCdnMaxage( (int)$adaptiveTTL );
2174  }
2175 
2183  public function enableClientCache( $state ) {
2184  return wfSetVar( $this->mEnableClientCache, $state );
2185  }
2186 
2192  public function getCacheVaryCookies() {
2193  if ( self::$cacheVaryCookies === null ) {
2194  $config = $this->getConfig();
2195  self::$cacheVaryCookies = array_values( array_unique( array_merge(
2196  SessionManager::singleton()->getVaryCookies(),
2197  [
2198  'forceHTTPS',
2199  ],
2200  $config->get( 'CacheVaryCookies' )
2201  ) ) );
2202  Hooks::run( 'GetCacheVaryCookies', [ $this, &self::$cacheVaryCookies ] );
2203  }
2204  return self::$cacheVaryCookies;
2205  }
2206 
2213  public function haveCacheVaryCookies() {
2214  $request = $this->getRequest();
2215  foreach ( $this->getCacheVaryCookies() as $cookieName ) {
2216  if ( $request->getCookie( $cookieName, '', '' ) !== '' ) {
2217  wfDebug( __METHOD__ . ": found $cookieName\n" );
2218  return true;
2219  }
2220  }
2221  wfDebug( __METHOD__ . ": no cache-varying cookies found\n" );
2222  return false;
2223  }
2224 
2234  public function addVaryHeader( $header, array $option = null ) {
2235  if ( $option !== null && count( $option ) > 0 ) {
2236  wfDeprecated( 'addVaryHeader $option is ignored', '1.34' );
2237  }
2238  if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
2239  $this->mVaryHeader[$header] = null;
2240  }
2241  }
2242 
2249  public function getVaryHeader() {
2250  // If we vary on cookies, let's make sure it's always included here too.
2251  if ( $this->getCacheVaryCookies() ) {
2252  $this->addVaryHeader( 'Cookie' );
2253  }
2254 
2255  foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2256  $this->addVaryHeader( $header, $options );
2257  }
2258  return 'Vary: ' . implode( ', ', array_keys( $this->mVaryHeader ) );
2259  }
2260 
2266  public function addLinkHeader( $header ) {
2267  $this->mLinkHeader[] = $header;
2268  }
2269 
2275  public function getLinkHeader() {
2276  if ( !$this->mLinkHeader ) {
2277  return false;
2278  }
2279 
2280  return 'Link: ' . implode( ',', $this->mLinkHeader );
2281  }
2282 
2290  private function addAcceptLanguage() {
2291  $title = $this->getTitle();
2292  if ( !$title instanceof Title ) {
2293  return;
2294  }
2295 
2296  $lang = $title->getPageLanguage();
2297  if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
2298  $this->addVaryHeader( 'Accept-Language' );
2299  }
2300  }
2301 
2312  public function preventClickjacking( $enable = true ) {
2313  $this->mPreventClickjacking = $enable;
2314  }
2315 
2321  public function allowClickjacking() {
2322  $this->mPreventClickjacking = false;
2323  }
2324 
2331  public function getPreventClickjacking() {
2333  }
2334 
2342  public function getFrameOptions() {
2343  $config = $this->getConfig();
2344  if ( $config->get( 'BreakFrames' ) ) {
2345  return 'DENY';
2346  } elseif ( $this->mPreventClickjacking && $config->get( 'EditPageFrameOptions' ) ) {
2347  return $config->get( 'EditPageFrameOptions' );
2348  }
2349  return false;
2350  }
2351 
2358  private function getOriginTrials() {
2359  $config = $this->getConfig();
2360 
2361  return $config->get( 'OriginTrials' );
2362  }
2363 
2364  private function getReportTo() {
2365  $config = $this->getConfig();
2366 
2367  $expiry = $config->get( 'ReportToExpiry' );
2368 
2369  if ( !$expiry ) {
2370  return false;
2371  }
2372 
2373  $endpoints = $config->get( 'ReportToEndpoints' );
2374 
2375  if ( !$endpoints ) {
2376  return false;
2377  }
2378 
2379  $output = [ 'max_age' => $expiry, 'endpoints' => [] ];
2380 
2381  foreach ( $endpoints as $endpoint ) {
2382  $output['endpoints'][] = [ 'url' => $endpoint ];
2383  }
2384 
2385  return json_encode( $output, JSON_UNESCAPED_SLASHES );
2386  }
2387 
2388  private function getFeaturePolicyReportOnly() {
2389  $config = $this->getConfig();
2390 
2391  $features = $config->get( 'FeaturePolicyReportOnly' );
2392  return implode( ';', $features );
2393  }
2394 
2398  public function sendCacheControl() {
2399  $response = $this->getRequest()->response();
2400  $config = $this->getConfig();
2401 
2402  $this->addVaryHeader( 'Cookie' );
2403  $this->addAcceptLanguage();
2404 
2405  # don't serve compressed data to clients who can't handle it
2406  # maintain different caches for logged-in users and non-logged in ones
2407  $response->header( $this->getVaryHeader() );
2408 
2409  if ( $this->mEnableClientCache ) {
2410  if (
2411  $config->get( 'UseCdn' ) &&
2412  !$response->hasCookies() &&
2413  !SessionManager::getGlobalSession()->isPersistent() &&
2414  !$this->isPrintable() &&
2415  $this->mCdnMaxage != 0 &&
2416  !$this->haveCacheVaryCookies()
2417  ) {
2418  # We'll purge the proxy cache for anons explicitly, but require end user agents
2419  # to revalidate against the proxy on each visit.
2420  # IMPORTANT! The CDN needs to replace the Cache-Control header with
2421  # Cache-Control: s-maxage=0, must-revalidate, max-age=0
2422  wfDebug( __METHOD__ .
2423  ": local proxy caching; {$this->mLastModified} **", 'private' );
2424  # start with a shorter timeout for initial testing
2425  # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
2426  $response->header( "Cache-Control: " .
2427  "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" );
2428  } else {
2429  # We do want clients to cache if they can, but they *must* check for updates
2430  # on revisiting the page.
2431  wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **", 'private' );
2432  $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2433  $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
2434  }
2435  if ( $this->mLastModified ) {
2436  $response->header( "Last-Modified: {$this->mLastModified}" );
2437  }
2438  } else {
2439  wfDebug( __METHOD__ . ": no caching **", 'private' );
2440 
2441  # In general, the absence of a last modified header should be enough to prevent
2442  # the client from using its cache. We send a few other things just to make sure.
2443  $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2444  $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
2445  $response->header( 'Pragma: no-cache' );
2446  }
2447  }
2448 
2454  public function loadSkinModules( $sk ) {
2455  foreach ( $sk->getDefaultModules() as $group => $modules ) {
2456  if ( $group === 'styles' ) {
2457  foreach ( $modules as $key => $moduleMembers ) {
2458  $this->addModuleStyles( $moduleMembers );
2459  }
2460  } else {
2461  $this->addModules( $modules );
2462  }
2463  }
2464  }
2465 
2476  public function output( $return = false ) {
2477  if ( $this->mDoNothing ) {
2478  return $return ? '' : null;
2479  }
2480 
2481  $response = $this->getRequest()->response();
2482  $config = $this->getConfig();
2483 
2484  if ( $this->mRedirect != '' ) {
2485  # Standards require redirect URLs to be absolute
2486  $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT );
2487 
2488  $redirect = $this->mRedirect;
2489  $code = $this->mRedirectCode;
2490 
2491  if ( Hooks::run( "BeforePageRedirect", [ $this, &$redirect, &$code ] ) ) {
2492  if ( $code == '301' || $code == '303' ) {
2493  if ( !$config->get( 'DebugRedirects' ) ) {
2494  $response->statusHeader( $code );
2495  }
2496  $this->mLastModified = wfTimestamp( TS_RFC2822 );
2497  }
2498  if ( $config->get( 'VaryOnXFP' ) ) {
2499  $this->addVaryHeader( 'X-Forwarded-Proto' );
2500  }
2501  $this->sendCacheControl();
2502 
2503  $response->header( "Content-Type: text/html; charset=utf-8" );
2504  if ( $config->get( 'DebugRedirects' ) ) {
2505  $url = htmlspecialchars( $redirect );
2506  print "<!DOCTYPE html>\n<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
2507  print "<p>Location: <a href=\"$url\">$url</a></p>\n";
2508  print "</body>\n</html>\n";
2509  } else {
2510  $response->header( 'Location: ' . $redirect );
2511  }
2512  }
2513 
2514  return $return ? '' : null;
2515  } elseif ( $this->mStatusCode ) {
2516  $response->statusHeader( $this->mStatusCode );
2517  }
2518 
2519  # Buffer output; final headers may depend on later processing
2520  ob_start();
2521 
2522  $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
2523  $response->header( 'Content-language: ' .
2524  MediaWikiServices::getInstance()->getContentLanguage()->getHtmlCode() );
2525 
2526  $linkHeader = $this->getLinkHeader();
2527  if ( $linkHeader ) {
2528  $response->header( $linkHeader );
2529  }
2530 
2531  // Prevent framing, if requested
2532  $frameOptions = $this->getFrameOptions();
2533  if ( $frameOptions ) {
2534  $response->header( "X-Frame-Options: $frameOptions" );
2535  }
2536 
2537  $originTrials = $this->getOriginTrials();
2538  foreach ( $originTrials as $originTrial ) {
2539  $response->header( "Origin-Trial: $originTrial", false );
2540  }
2541 
2542  $reportTo = $this->getReportTo();
2543  if ( $reportTo ) {
2544  $response->header( "Report-To: $reportTo" );
2545  }
2546 
2547  $featurePolicyReportOnly = $this->getFeaturePolicyReportOnly();
2548  if ( $featurePolicyReportOnly ) {
2549  $response->header( "Feature-Policy-Report-Only: $featurePolicyReportOnly" );
2550  }
2551 
2553 
2554  if ( $this->mArticleBodyOnly ) {
2555  echo $this->mBodytext;
2556  } else {
2557  // Enable safe mode if requested (T152169)
2558  if ( $this->getRequest()->getBool( 'safemode' ) ) {
2559  $this->disallowUserJs();
2560  }
2561 
2562  $sk = $this->getSkin();
2563  $this->loadSkinModules( $sk );
2564 
2565  MWDebug::addModules( $this );
2566 
2567  // Avoid PHP 7.1 warning of passing $this by reference
2568  $outputPage = $this;
2569  // Hook that allows last minute changes to the output page, e.g.
2570  // adding of CSS or Javascript by extensions.
2571  Hooks::runWithoutAbort( 'BeforePageDisplay', [ &$outputPage, &$sk ] );
2572 
2573  try {
2574  $sk->outputPage();
2575  } catch ( Exception $e ) {
2576  ob_end_clean(); // bug T129657
2577  throw $e;
2578  }
2579  }
2580 
2581  try {
2582  // This hook allows last minute changes to final overall output by modifying output buffer
2583  Hooks::runWithoutAbort( 'AfterFinalPageOutput', [ $this ] );
2584  } catch ( Exception $e ) {
2585  ob_end_clean(); // bug T129657
2586  throw $e;
2587  }
2588 
2589  $this->sendCacheControl();
2590 
2591  if ( $return ) {
2592  return ob_get_clean();
2593  } else {
2594  ob_end_flush();
2595  return null;
2596  }
2597  }
2598 
2609  public function prepareErrorPage( $pageTitle, $htmlTitle = false ) {
2610  $this->setPageTitle( $pageTitle );
2611  if ( $htmlTitle !== false ) {
2612  $this->setHTMLTitle( $htmlTitle );
2613  }
2614  $this->setRobotPolicy( 'noindex,nofollow' );
2615  $this->setArticleRelated( false );
2616  $this->enableClientCache( false );
2617  $this->mRedirect = '';
2618  $this->clearSubtitle();
2619  $this->clearHTML();
2620  }
2621 
2634  public function showErrorPage( $title, $msg, $params = [] ) {
2635  if ( !$title instanceof Message ) {
2636  $title = $this->msg( $title );
2637  }
2638 
2639  $this->prepareErrorPage( $title );
2640 
2641  if ( $msg instanceof Message ) {
2642  if ( $params !== [] ) {
2643  trigger_error( 'Argument ignored: $params. The message parameters argument '
2644  . 'is discarded when the $msg argument is a Message object instead of '
2645  . 'a string.', E_USER_NOTICE );
2646  }
2647  $this->addHTML( $msg->parseAsBlock() );
2648  } else {
2649  $this->addWikiMsgArray( $msg, $params );
2650  }
2651 
2652  $this->returnToMain();
2653  }
2654 
2661  public function showPermissionsErrorPage( array $errors, $action = null ) {
2662  $services = MediaWikiServices::getInstance();
2663  $permissionManager = $services->getPermissionManager();
2664  foreach ( $errors as $key => $error ) {
2665  $errors[$key] = (array)$error;
2666  }
2667 
2668  // For some action (read, edit, create and upload), display a "login to do this action"
2669  // error if all of the following conditions are met:
2670  // 1. the user is not logged in
2671  // 2. the only error is insufficient permissions (i.e. no block or something else)
2672  // 3. the error can be avoided simply by logging in
2673 
2674  if ( in_array( $action, [ 'read', 'edit', 'createpage', 'createtalk', 'upload' ] )
2675  && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
2676  && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
2677  && ( $permissionManager->groupHasPermission( 'user', $action )
2678  || $permissionManager->groupHasPermission( 'autoconfirmed', $action ) )
2679  ) {
2680  $displayReturnto = null;
2681 
2682  # Due to T34276, if a user does not have read permissions,
2683  # $this->getTitle() will just give Special:Badtitle, which is
2684  # not especially useful as a returnto parameter. Use the title
2685  # from the request instead, if there was one.
2686  $request = $this->getRequest();
2687  $returnto = Title::newFromText( $request->getVal( 'title', '' ) );
2688  if ( $action == 'edit' ) {
2689  $msg = 'whitelistedittext';
2690  $displayReturnto = $returnto;
2691  } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
2692  $msg = 'nocreatetext';
2693  } elseif ( $action == 'upload' ) {
2694  $msg = 'uploadnologintext';
2695  } else { # Read
2696  $msg = 'loginreqpagetext';
2697  $displayReturnto = Title::newMainPage();
2698  }
2699 
2700  $query = [];
2701 
2702  if ( $returnto ) {
2703  $query['returnto'] = $returnto->getPrefixedText();
2704 
2705  if ( !$request->wasPosted() ) {
2706  $returntoquery = $request->getValues();
2707  unset( $returntoquery['title'] );
2708  unset( $returntoquery['returnto'] );
2709  unset( $returntoquery['returntoquery'] );
2710  $query['returntoquery'] = wfArrayToCgi( $returntoquery );
2711  }
2712  }
2713 
2714  $title = SpecialPage::getTitleFor( 'Userlogin' );
2715  $linkRenderer = $services->getLinkRenderer();
2716  $loginUrl = $title->getLinkURL( $query, false, PROTO_RELATIVE );
2717  $loginLink = $linkRenderer->makeKnownLink(
2718  $title,
2719  $this->msg( 'loginreqlink' )->text(),
2720  [],
2721  $query
2722  );
2723 
2724  $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) );
2725  $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->params( $loginUrl )->parse() );
2726 
2727  # Don't return to a page the user can't read otherwise
2728  # we'll end up in a pointless loop
2729  if ( $displayReturnto && $permissionManager->userCan(
2730  'read', $this->getUser(), $displayReturnto
2731  ) ) {
2732  $this->returnToMain( null, $displayReturnto );
2733  }
2734  } else {
2735  $this->prepareErrorPage( $this->msg( 'permissionserrors' ) );
2736  $this->addWikiTextAsInterface( $this->formatPermissionsErrorMessage( $errors, $action ) );
2737  }
2738  }
2739 
2746  public function versionRequired( $version ) {
2747  $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) );
2748 
2749  $this->addWikiMsg( 'versionrequiredtext', $version );
2750  $this->returnToMain();
2751  }
2752 
2760  public function formatPermissionsErrorMessage( array $errors, $action = null ) {
2761  if ( $action == null ) {
2762  $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
2763  } else {
2764  $action_desc = $this->msg( "action-$action" )->plain();
2765  $text = $this->msg(
2766  'permissionserrorstext-withaction',
2767  count( $errors ),
2768  $action_desc
2769  )->plain() . "\n\n";
2770  }
2771 
2772  if ( count( $errors ) > 1 ) {
2773  $text .= '<ul class="permissions-errors">' . "\n";
2774 
2775  foreach ( $errors as $error ) {
2776  $text .= '<li>';
2777  $text .= $this->msg( ...$error )->plain();
2778  $text .= "</li>\n";
2779  }
2780  $text .= '</ul>';
2781  } else {
2782  $text .= "<div class=\"permissions-errors\">\n" .
2783  $this->msg( ...reset( $errors ) )->plain() .
2784  "\n</div>";
2785  }
2786 
2787  return $text;
2788  }
2789 
2799  public function showLagWarning( $lag ) {
2800  $config = $this->getConfig();
2801  if ( $lag >= $config->get( 'SlaveLagWarning' ) ) {
2802  $lag = floor( $lag ); // floor to avoid nano seconds to display
2803  $message = $lag < $config->get( 'SlaveLagCritical' )
2804  ? 'lag-warn-normal'
2805  : 'lag-warn-high';
2806  $wrap = Html::rawElement( 'div', [ 'class' => "mw-{$message}" ], "\n$1\n" );
2807  $this->wrapWikiMsg( "$wrap\n", [ $message, $this->getLanguage()->formatNum( $lag ) ] );
2808  }
2809  }
2810 
2817  public function showFatalError( $message ) {
2818  $this->prepareErrorPage( $this->msg( 'internalerror' ) );
2819 
2820  $this->addHTML( $message );
2821  }
2822 
2831  public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) {
2832  $linkRenderer = MediaWikiServices::getInstance()
2833  ->getLinkRendererFactory()->createFromLegacyOptions( $options );
2834  $link = $this->msg( 'returnto' )->rawParams(
2835  $linkRenderer->makeLink( $title, $text, [], $query ) )->escaped();
2836  $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
2837  }
2838 
2847  public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
2848  if ( $returnto == null ) {
2849  $returnto = $this->getRequest()->getText( 'returnto' );
2850  }
2851 
2852  if ( $returntoquery == null ) {
2853  $returntoquery = $this->getRequest()->getText( 'returntoquery' );
2854  }
2855 
2856  if ( $returnto === '' ) {
2857  $returnto = Title::newMainPage();
2858  }
2859 
2860  if ( is_object( $returnto ) ) {
2861  $titleObj = $returnto;
2862  } else {
2863  $titleObj = Title::newFromText( $returnto );
2864  }
2865  // We don't want people to return to external interwiki. That
2866  // might potentially be used as part of a phishing scheme
2867  if ( !is_object( $titleObj ) || $titleObj->isExternal() ) {
2868  $titleObj = Title::newMainPage();
2869  }
2870 
2871  $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) );
2872  }
2873 
2874  private function getRlClientContext() {
2875  if ( !$this->rlClientContext ) {
2877  [], // modules; not relevant
2878  $this->getLanguage()->getCode(),
2879  $this->getSkin()->getSkinName(),
2880  $this->getUser()->isLoggedIn() ? $this->getUser()->getName() : null,
2881  null, // version; not relevant
2883  null, // only; not relevant
2884  $this->isPrintable(),
2885  $this->getRequest()->getBool( 'handheld' )
2886  );
2887  $this->rlClientContext = new ResourceLoaderContext(
2888  $this->getResourceLoader(),
2889  new FauxRequest( $query )
2890  );
2891  if ( $this->contentOverrideCallbacks ) {
2892  $this->rlClientContext = new DerivativeResourceLoaderContext( $this->rlClientContext );
2893  $this->rlClientContext->setContentOverrideCallback( function ( Title $title ) {
2894  foreach ( $this->contentOverrideCallbacks as $callback ) {
2895  $content = $callback( $title );
2896  if ( $content !== null ) {
2897  $text = ContentHandler::getContentText( $content );
2898  if ( strpos( $text, '</script>' ) !== false ) {
2899  // Proactively replace this so that we can display a message
2900  // to the user, instead of letting it go to Html::inlineScript(),
2901  // where it would be considered a server-side issue.
2902  $titleFormatted = $title->getPrefixedText();
2903  $content = new JavaScriptContent(
2904  Xml::encodeJsCall( 'mw.log.error', [
2905  "Cannot preview $titleFormatted due to script-closing tag."
2906  ] )
2907  );
2908  }
2909  return $content;
2910  }
2911  }
2912  return null;
2913  } );
2914  }
2915  }
2916  return $this->rlClientContext;
2917  }
2918 
2930  public function getRlClient() {
2931  if ( !$this->rlClient ) {
2932  $context = $this->getRlClientContext();
2933  $rl = $this->getResourceLoader();
2934  $this->addModules( [
2935  'user',
2936  'user.options',
2937  'user.tokens',
2938  ] );
2939  $this->addModuleStyles( [
2940  'site.styles',
2941  'noscript',
2942  'user.styles',
2943  ] );
2944  $this->getSkin()->setupSkinUserCss( $this );
2945 
2946  // Prepare exempt modules for buildExemptModules()
2947  $exemptGroups = [ 'site' => [], 'noscript' => [], 'private' => [], 'user' => [] ];
2948  $exemptStates = [];
2949  $moduleStyles = $this->getModuleStyles( /*filter*/ true );
2950 
2951  // Preload getTitleInfo for isKnownEmpty calls below and in ResourceLoaderClientHtml
2952  // Separate user-specific batch for improved cache-hit ratio.
2953  $userBatch = [ 'user.styles', 'user' ];
2954  $siteBatch = array_diff( $moduleStyles, $userBatch );
2955  $dbr = wfGetDB( DB_REPLICA );
2958 
2959  // Filter out modules handled by buildExemptModules()
2960  $moduleStyles = array_filter( $moduleStyles,
2961  function ( $name ) use ( $rl, $context, &$exemptGroups, &$exemptStates ) {
2962  $module = $rl->getModule( $name );
2963  if ( $module ) {
2964  $group = $module->getGroup();
2965  if ( isset( $exemptGroups[$group] ) ) {
2966  $exemptStates[$name] = 'ready';
2967  if ( !$module->isKnownEmpty( $context ) ) {
2968  // E.g. Don't output empty <styles>
2969  $exemptGroups[$group][] = $name;
2970  }
2971  return false;
2972  }
2973  }
2974  return true;
2975  }
2976  );
2977  $this->rlExemptStyleModules = $exemptGroups;
2978 
2980  'target' => $this->getTarget(),
2981  'nonce' => $this->getCSPNonce(),
2982  // When 'safemode', disallowUserJs(), or reduceAllowedModules() is used
2983  // to only restrict modules to ORIGIN_CORE (ie. disallow ORIGIN_USER), the list of
2984  // modules enqueud for loading on this page is filtered to just those.
2985  // However, to make sure we also apply the restriction to dynamic dependencies and
2986  // lazy-loaded modules at run-time on the client-side, pass 'safemode' down to the
2987  // StartupModule so that the client-side registry will not contain any restricted
2988  // modules either. (T152169, T185303)
2989  'safemode' => ( $this->getAllowedModules( ResourceLoaderModule::TYPE_COMBINED )
2990  <= ResourceLoaderModule::ORIGIN_CORE_INDIVIDUAL
2991  ) ? '1' : null,
2992  ] );
2993  $rlClient->setConfig( $this->getJSVars() );
2994  $rlClient->setModules( $this->getModules( /*filter*/ true ) );
2995  $rlClient->setModuleStyles( $moduleStyles );
2996  $rlClient->setExemptStates( $exemptStates );
2997  $this->rlClient = $rlClient;
2998  }
2999  return $this->rlClient;
3000  }
3001 
3007  public function headElement( Skin $sk, $includeStyle = true ) {
3008  $config = $this->getConfig();
3009  $userdir = $this->getLanguage()->getDir();
3010  $sitedir = MediaWikiServices::getInstance()->getContentLanguage()->getDir();
3011 
3012  $pieces = [];
3013  $htmlAttribs = Sanitizer::mergeAttributes(
3014  $this->getRlClient()->getDocumentAttributes(),
3016  );
3017  $pieces[] = Html::htmlHeader( $htmlAttribs );
3018  $pieces[] = Html::openElement( 'head' );
3019 
3020  if ( $this->getHTMLTitle() == '' ) {
3021  $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() );
3022  }
3023 
3024  if ( !Html::isXmlMimeType( $config->get( 'MimeType' ) ) ) {
3025  // Add <meta charset="UTF-8">
3026  // This should be before <title> since it defines the charset used by
3027  // text including the text inside <title>.
3028  // The spec recommends defining XHTML5's charset using the XML declaration
3029  // instead of meta.
3030  // Our XML declaration is output by Html::htmlHeader.
3031  // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type
3032  // https://html.spec.whatwg.org/multipage/semantics.html#charset
3033  $pieces[] = Html::element( 'meta', [ 'charset' => 'UTF-8' ] );
3034  }
3035 
3036  $pieces[] = Html::element( 'title', null, $this->getHTMLTitle() );
3037  $pieces[] = $this->getRlClient()->getHeadHtml( $htmlAttribs['class'] ?? null );
3038  $pieces[] = $this->buildExemptModules();
3039  $pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) );
3040  $pieces = array_merge( $pieces, array_values( $this->mHeadItems ) );
3041 
3042  // This library is intended to run on older browsers that MediaWiki no longer
3043  // supports as Grade A. For these Grade C browsers, we provide an experience
3044  // using only HTML and CSS. But, where standards-compliant browsers are able to
3045  // style unknown HTML elements without issue, old IE ignores these styles.
3046  // The html5shiv library fixes that.
3047  // Use an IE conditional comment to serve the script only to old IE
3048  $shivUrl = $config->get( 'ResourceBasePath' ) . '/resources/lib/html5shiv/html5shiv.js';
3049  $pieces[] = '<!--[if lt IE 9]>' .
3050  Html::linkedScript( $shivUrl, $this->getCSPNonce() ) .
3051  '<![endif]-->';
3052 
3053  $pieces[] = Html::closeElement( 'head' );
3054 
3055  $bodyClasses = $this->mAdditionalBodyClasses;
3056  $bodyClasses[] = 'mediawiki';
3057 
3058  # Classes for LTR/RTL directionality support
3059  $bodyClasses[] = $userdir;
3060  $bodyClasses[] = "sitedir-$sitedir";
3061 
3062  $underline = $this->getUser()->getOption( 'underline' );
3063  if ( $underline < 2 ) {
3064  // The following classes can be used here:
3065  // * mw-underline-always
3066  // * mw-underline-never
3067  $bodyClasses[] = 'mw-underline-' . ( $underline ? 'always' : 'never' );
3068  }
3069 
3070  if ( $this->getLanguage()->capitalizeAllNouns() ) {
3071  # A <body> class is probably not the best way to do this . . .
3072  $bodyClasses[] = 'capitalize-all-nouns';
3073  }
3074 
3075  // Parser feature migration class
3076  // The idea is that this will eventually be removed, after the wikitext
3077  // which requires it is cleaned up.
3078  $bodyClasses[] = 'mw-hide-empty-elt';
3079 
3080  $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
3081  $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
3082  $bodyClasses[] =
3083  'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
3084 
3085  $bodyAttrs = [];
3086  // While the implode() is not strictly needed, it's used for backwards compatibility
3087  // (this used to be built as a string and hooks likely still expect that).
3088  $bodyAttrs['class'] = implode( ' ', $bodyClasses );
3089 
3090  // Allow skins and extensions to add body attributes they need
3091  $sk->addToBodyAttributes( $this, $bodyAttrs );
3092  Hooks::run( 'OutputPageBodyAttributes', [ $this, $sk, &$bodyAttrs ] );
3093 
3094  $pieces[] = Html::openElement( 'body', $bodyAttrs );
3095 
3096  return self::combineWrappedStrings( $pieces );
3097  }
3098 
3104  public function getResourceLoader() {
3105  if ( is_null( $this->mResourceLoader ) ) {
3106  // Lazy-initialise as needed
3107  $this->mResourceLoader = MediaWikiServices::getInstance()->getResourceLoader();
3108  }
3109  return $this->mResourceLoader;
3110  }
3111 
3120  public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) {
3121  // Apply 'target' and 'origin' filters
3122  $modules = $this->filterModules( (array)$modules, null, $only );
3123 
3125  $this->getRlClientContext(),
3126  $modules,
3127  $only,
3128  $extraQuery,
3129  $this->getCSPNonce()
3130  );
3131  }
3132 
3139  protected static function combineWrappedStrings( array $chunks ) {
3140  // Filter out empty values
3141  $chunks = array_filter( $chunks, 'strlen' );
3142  return WrappedString::join( "\n", $chunks );
3143  }
3144 
3151  public function getBottomScripts() {
3152  $chunks = [];
3153  $chunks[] = $this->getRlClient()->getBodyHtml();
3154 
3155  // Legacy non-ResourceLoader scripts
3156  $chunks[] = $this->mScripts;
3157 
3158  if ( $this->limitReportJSData ) {
3161  [ 'wgPageParseReport' => $this->limitReportJSData ]
3162  ),
3163  $this->getCSPNonce()
3164  );
3165  }
3166 
3167  return self::combineWrappedStrings( $chunks );
3168  }
3169 
3176  public function getJsConfigVars() {
3177  return $this->mJsConfigVars;
3178  }
3179 
3186  public function addJsConfigVars( $keys, $value = null ) {
3187  if ( is_array( $keys ) ) {
3188  foreach ( $keys as $key => $value ) {
3189  $this->mJsConfigVars[$key] = $value;
3190  }
3191  return;
3192  }
3193 
3194  $this->mJsConfigVars[$keys] = $value;
3195  }
3196 
3206  public function getJSVars() {
3207  $curRevisionId = 0;
3208  $articleId = 0;
3209  $canonicalSpecialPageName = false; # T23115
3210  $services = MediaWikiServices::getInstance();
3211 
3212  $title = $this->getTitle();
3213  $ns = $title->getNamespace();
3214  $nsInfo = $services->getNamespaceInfo();
3215  $canonicalNamespace = $nsInfo->exists( $ns )
3216  ? $nsInfo->getCanonicalName( $ns )
3217  : $title->getNsText();
3218 
3219  $sk = $this->getSkin();
3220  // Get the relevant title so that AJAX features can use the correct page name
3221  // when making API requests from certain special pages (T36972).
3222  $relevantTitle = $sk->getRelevantTitle();
3223  $relevantUser = $sk->getRelevantUser();
3224 
3225  if ( $ns == NS_SPECIAL ) {
3226  list( $canonicalSpecialPageName, /*...*/ ) =
3227  $services->getSpecialPageFactory()->
3228  resolveAlias( $title->getDBkey() );
3229  } elseif ( $this->canUseWikiPage() ) {
3230  $wikiPage = $this->getWikiPage();
3231  $curRevisionId = $wikiPage->getLatest();
3232  $articleId = $wikiPage->getId();
3233  }
3234 
3235  $lang = $title->getPageViewLanguage();
3236 
3237  // Pre-process information
3238  $separatorTransTable = $lang->separatorTransformTable();
3239  $separatorTransTable = $separatorTransTable ?: [];
3240  $compactSeparatorTransTable = [
3241  implode( "\t", array_keys( $separatorTransTable ) ),
3242  implode( "\t", $separatorTransTable ),
3243  ];
3244  $digitTransTable = $lang->digitTransformTable();
3245  $digitTransTable = $digitTransTable ?: [];
3246  $compactDigitTransTable = [
3247  implode( "\t", array_keys( $digitTransTable ) ),
3248  implode( "\t", $digitTransTable ),
3249  ];
3250 
3251  $user = $this->getUser();
3252 
3253  $vars = [
3254  'wgCanonicalNamespace' => $canonicalNamespace,
3255  'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
3256  'wgNamespaceNumber' => $title->getNamespace(),
3257  'wgPageName' => $title->getPrefixedDBkey(),
3258  'wgTitle' => $title->getText(),
3259  'wgCurRevisionId' => $curRevisionId,
3260  'wgRevisionId' => (int)$this->getRevisionId(),
3261  'wgArticleId' => $articleId,
3262  'wgIsArticle' => $this->isArticle(),
3263  'wgIsRedirect' => $title->isRedirect(),
3264  'wgAction' => Action::getActionName( $this->getContext() ),
3265  'wgUserName' => $user->isAnon() ? null : $user->getName(),
3266  'wgUserGroups' => $user->getEffectiveGroups(),
3267  'wgCategories' => $this->getCategories(),
3268  'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
3269  'wgPageContentLanguage' => $lang->getCode(),
3270  'wgPageContentModel' => $title->getContentModel(),
3271  'wgSeparatorTransformTable' => $compactSeparatorTransTable,
3272  'wgDigitTransformTable' => $compactDigitTransTable,
3273  'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
3274  'wgMonthNames' => $lang->getMonthNamesArray(),
3275  'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
3276  'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
3277  'wgRelevantArticleId' => $relevantTitle->getArticleID(),
3278  'wgRequestId' => WebRequest::getRequestId(),
3279  'wgCSPNonce' => $this->getCSPNonce(),
3280  ];
3281 
3282  if ( $user->isLoggedIn() ) {
3283  $vars['wgUserId'] = $user->getId();
3284  $vars['wgUserEditCount'] = $user->getEditCount();
3285  $userReg = $user->getRegistration();
3286  $vars['wgUserRegistration'] = $userReg ? wfTimestamp( TS_UNIX, $userReg ) * 1000 : null;
3287  // Get the revision ID of the oldest new message on the user's talk
3288  // page. This can be used for constructing new message alerts on
3289  // the client side.
3290  $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId();
3291  }
3292 
3293  $contLang = $services->getContentLanguage();
3294  if ( $contLang->hasVariants() ) {
3295  $vars['wgUserVariant'] = $contLang->getPreferredVariant();
3296  }
3297  // Same test as SkinTemplate
3298  $vars['wgIsProbablyEditable'] = $this->userCanEditOrCreate( $user, $title );
3299 
3300  $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle &&
3301  $this->userCanEditOrCreate( $user, $relevantTitle );
3302 
3303  foreach ( $title->getRestrictionTypes() as $type ) {
3304  // Following keys are set in $vars:
3305  // wgRestrictionCreate, wgRestrictionEdit, wgRestrictionMove, wgRestrictionUpload
3306  $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
3307  }
3308 
3309  if ( $title->isMainPage() ) {
3310  $vars['wgIsMainPage'] = true;
3311  }
3312 
3313  if ( $this->mRedirectedFrom ) {
3314  $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey();
3315  }
3316 
3317  if ( $relevantUser ) {
3318  $vars['wgRelevantUserName'] = $relevantUser->getName();
3319  }
3320 
3321  // Allow extensions to add their custom variables to the mw.config map.
3322  // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
3323  // page-dependant but site-wide (without state).
3324  // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
3325  Hooks::run( 'MakeGlobalVariablesScript', [ &$vars, $this ] );
3326 
3327  // Merge in variables from addJsConfigVars last
3328  return array_merge( $vars, $this->getJsConfigVars() );
3329  }
3330 
3340  public function userCanPreview() {
3341  $request = $this->getRequest();
3342  if (
3343  $request->getVal( 'action' ) !== 'submit' ||
3344  !$request->wasPosted()
3345  ) {
3346  return false;
3347  }
3348 
3349  $user = $this->getUser();
3350 
3351  if ( !$user->isLoggedIn() ) {
3352  // Anons have predictable edit tokens
3353  return false;
3354  }
3355  if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
3356  return false;
3357  }
3358 
3359  $title = $this->getTitle();
3360  $errors = $title->getUserPermissionsErrors( 'edit', $user );
3361  if ( count( $errors ) !== 0 ) {
3362  return false;
3363  }
3364 
3365  return true;
3366  }
3367 
3373  private function userCanEditOrCreate(
3374  User $user,
3376  ) {
3377  $pm = MediaWikiServices::getInstance()->getPermissionManager();
3378  return $pm->quickUserCan( 'edit', $user, $title )
3379  && ( $this->getTitle()->exists() ||
3380  $pm->quickUserCan( 'create', $user, $title ) );
3381  }
3382 
3386  public function getHeadLinksArray() {
3387  global $wgVersion;
3388 
3389  $tags = [];
3390  $config = $this->getConfig();
3391 
3392  $canonicalUrl = $this->mCanonicalUrl;
3393 
3394  $tags['meta-generator'] = Html::element( 'meta', [
3395  'name' => 'generator',
3396  'content' => "MediaWiki $wgVersion",
3397  ] );
3398 
3399  if ( $config->get( 'ReferrerPolicy' ) !== false ) {
3400  // Per https://w3c.github.io/webappsec-referrer-policy/#unknown-policy-values
3401  // fallbacks should come before the primary value so we need to reverse the array.
3402  foreach ( array_reverse( (array)$config->get( 'ReferrerPolicy' ) ) as $i => $policy ) {
3403  $tags["meta-referrer-$i"] = Html::element( 'meta', [
3404  'name' => 'referrer',
3405  'content' => $policy,
3406  ] );
3407  }
3408  }
3409 
3410  $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
3411  if ( $p !== 'index,follow' ) {
3412  // http://www.robotstxt.org/wc/meta-user.html
3413  // Only show if it's different from the default robots policy
3414  $tags['meta-robots'] = Html::element( 'meta', [
3415  'name' => 'robots',
3416  'content' => $p,
3417  ] );
3418  }
3419 
3420  foreach ( $this->mMetatags as $tag ) {
3421  if ( strncasecmp( $tag[0], 'http:', 5 ) === 0 ) {
3422  $a = 'http-equiv';
3423  $tag[0] = substr( $tag[0], 5 );
3424  } elseif ( strncasecmp( $tag[0], 'og:', 3 ) === 0 ) {
3425  $a = 'property';
3426  } else {
3427  $a = 'name';
3428  }
3429  $tagName = "meta-{$tag[0]}";
3430  if ( isset( $tags[$tagName] ) ) {
3431  $tagName .= $tag[1];
3432  }
3433  $tags[$tagName] = Html::element( 'meta',
3434  [
3435  $a => $tag[0],
3436  'content' => $tag[1]
3437  ]
3438  );
3439  }
3440 
3441  foreach ( $this->mLinktags as $tag ) {
3442  $tags[] = Html::element( 'link', $tag );
3443  }
3444 
3445  # Universal edit button
3446  if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) {
3447  if ( $this->userCanEditOrCreate( $this->getUser(), $this->getTitle() ) ) {
3448  // Original UniversalEditButton
3449  $msg = $this->msg( 'edit' )->text();
3450  $tags['universal-edit-button'] = Html::element( 'link', [
3451  'rel' => 'alternate',
3452  'type' => 'application/x-wiki',
3453  'title' => $msg,
3454  'href' => $this->getTitle()->getEditURL(),
3455  ] );
3456  // Alternate edit link
3457  $tags['alternative-edit'] = Html::element( 'link', [
3458  'rel' => 'edit',
3459  'title' => $msg,
3460  'href' => $this->getTitle()->getEditURL(),
3461  ] );
3462  }
3463  }
3464 
3465  # Generally the order of the favicon and apple-touch-icon links
3466  # should not matter, but Konqueror (3.5.9 at least) incorrectly
3467  # uses whichever one appears later in the HTML source. Make sure
3468  # apple-touch-icon is specified first to avoid this.
3469  if ( $config->get( 'AppleTouchIcon' ) !== false ) {
3470  $tags['apple-touch-icon'] = Html::element( 'link', [
3471  'rel' => 'apple-touch-icon',
3472  'href' => $config->get( 'AppleTouchIcon' )
3473  ] );
3474  }
3475 
3476  if ( $config->get( 'Favicon' ) !== false ) {
3477  $tags['favicon'] = Html::element( 'link', [
3478  'rel' => 'shortcut icon',
3479  'href' => $config->get( 'Favicon' )
3480  ] );
3481  }
3482 
3483  # OpenSearch description link
3484  $tags['opensearch'] = Html::element( 'link', [
3485  'rel' => 'search',
3486  'type' => 'application/opensearchdescription+xml',
3487  'href' => wfScript( 'opensearch_desc' ),
3488  'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
3489  ] );
3490 
3491  # Real Simple Discovery link, provides auto-discovery information
3492  # for the MediaWiki API (and potentially additional custom API
3493  # support such as WordPress or Twitter-compatible APIs for a
3494  # blogging extension, etc)
3495  $tags['rsd'] = Html::element( 'link', [
3496  'rel' => 'EditURI',
3497  'type' => 'application/rsd+xml',
3498  // Output a protocol-relative URL here if $wgServer is protocol-relative.
3499  // Whether RSD accepts relative or protocol-relative URLs is completely
3500  // undocumented, though.
3501  'href' => wfExpandUrl( wfAppendQuery(
3502  wfScript( 'api' ),
3503  [ 'action' => 'rsd' ] ),
3505  ),
3506  ] );
3507 
3508  # Language variants
3509  if ( !$config->get( 'DisableLangConversion' ) ) {
3510  $lang = $this->getTitle()->getPageLanguage();
3511  if ( $lang->hasVariants() ) {
3512  $variants = $lang->getVariants();
3513  foreach ( $variants as $variant ) {
3514  $tags["variant-$variant"] = Html::element( 'link', [
3515  'rel' => 'alternate',
3516  'hreflang' => LanguageCode::bcp47( $variant ),
3517  'href' => $this->getTitle()->getLocalURL(
3518  [ 'variant' => $variant ] )
3519  ]
3520  );
3521  }
3522  # x-default link per https://support.google.com/webmasters/answer/189077?hl=en
3523  $tags["variant-x-default"] = Html::element( 'link', [
3524  'rel' => 'alternate',
3525  'hreflang' => 'x-default',
3526  'href' => $this->getTitle()->getLocalURL() ] );
3527  }
3528  }
3529 
3530  # Copyright
3531  if ( $this->copyrightUrl !== null ) {
3532  $copyright = $this->copyrightUrl;
3533  } else {
3534  $copyright = '';
3535  if ( $config->get( 'RightsPage' ) ) {
3536  $copy = Title::newFromText( $config->get( 'RightsPage' ) );
3537 
3538  if ( $copy ) {
3539  $copyright = $copy->getLocalURL();
3540  }
3541  }
3542 
3543  if ( !$copyright && $config->get( 'RightsUrl' ) ) {
3544  $copyright = $config->get( 'RightsUrl' );
3545  }
3546  }
3547 
3548  if ( $copyright ) {
3549  $tags['copyright'] = Html::element( 'link', [
3550  'rel' => 'license',
3551  'href' => $copyright ]
3552  );
3553  }
3554 
3555  # Feeds
3556  if ( $config->get( 'Feed' ) ) {
3557  $feedLinks = [];
3558 
3559  foreach ( $this->getSyndicationLinks() as $format => $link ) {
3560  # Use the page name for the title. In principle, this could
3561  # lead to issues with having the same name for different feeds
3562  # corresponding to the same page, but we can't avoid that at
3563  # this low a level.
3564 
3565  $feedLinks[] = $this->feedLink(
3566  $format,
3567  $link,
3568  # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
3569  $this->msg(
3570  "page-{$format}-feed", $this->getTitle()->getPrefixedText()
3571  )->text()
3572  );
3573  }
3574 
3575  # Recent changes feed should appear on every page (except recentchanges,
3576  # that would be redundant). Put it after the per-page feed to avoid
3577  # changing existing behavior. It's still available, probably via a
3578  # menu in your browser. Some sites might have a different feed they'd
3579  # like to promote instead of the RC feed (maybe like a "Recent New Articles"
3580  # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
3581  # If so, use it instead.
3582  $sitename = $config->get( 'Sitename' );
3583  $overrideSiteFeed = $config->get( 'OverrideSiteFeed' );
3584  if ( $overrideSiteFeed ) {
3585  foreach ( $overrideSiteFeed as $type => $feedUrl ) {
3586  // Note, this->feedLink escapes the url.
3587  $feedLinks[] = $this->feedLink(
3588  $type,
3589  $feedUrl,
3590  $this->msg( "site-{$type}-feed", $sitename )->text()
3591  );
3592  }
3593  } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
3594  $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
3595  foreach ( $this->getAdvertisedFeedTypes() as $format ) {
3596  $feedLinks[] = $this->feedLink(
3597  $format,
3598  $rctitle->getLocalURL( [ 'feed' => $format ] ),
3599  # For grep: 'site-rss-feed', 'site-atom-feed'
3600  $this->msg( "site-{$format}-feed", $sitename )->text()
3601  );
3602  }
3603  }
3604 
3605  # Allow extensions to change the list pf feeds. This hook is primarily for changing,
3606  # manipulating or removing existing feed tags. If you want to add new feeds, you should
3607  # use OutputPage::addFeedLink() instead.
3608  Hooks::run( 'AfterBuildFeedLinks', [ &$feedLinks ] );
3609 
3610  $tags += $feedLinks;
3611  }
3612 
3613  # Canonical URL
3614  if ( $config->get( 'EnableCanonicalServerLink' ) ) {
3615  if ( $canonicalUrl !== false ) {
3616  $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL );
3617  } elseif ( $this->isArticleRelated() ) {
3618  // This affects all requests where "setArticleRelated" is true. This is
3619  // typically all requests that show content (query title, curid, oldid, diff),
3620  // and all wikipage actions (edit, delete, purge, info, history etc.).
3621  // It does not apply to File pages and Special pages.
3622  // 'history' and 'info' actions address page metadata rather than the page
3623  // content itself, so they may not be canonicalized to the view page url.
3624  // TODO: this ought to be better encapsulated in the Action class.
3625  $action = Action::getActionName( $this->getContext() );
3626  if ( in_array( $action, [ 'history', 'info' ] ) ) {
3627  $query = "action={$action}";
3628  } else {
3629  $query = '';
3630  }
3631  $canonicalUrl = $this->getTitle()->getCanonicalURL( $query );
3632  } else {
3633  $reqUrl = $this->getRequest()->getRequestURL();
3634  $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL );
3635  }
3636  }
3637  if ( $canonicalUrl !== false ) {
3638  $tags[] = Html::element( 'link', [
3639  'rel' => 'canonical',
3640  'href' => $canonicalUrl
3641  ] );
3642  }
3643 
3644  // Allow extensions to add, remove and/or otherwise manipulate these links
3645  // If you want only to *add* <head> links, please use the addHeadItem()
3646  // (or addHeadItems() for multiple items) method instead.
3647  // This hook is provided as a last resort for extensions to modify these
3648  // links before the output is sent to client.
3649  Hooks::run( 'OutputPageAfterGetHeadLinksArray', [ &$tags, $this ] );
3650 
3651  return $tags;
3652  }
3653 
3662  private function feedLink( $type, $url, $text ) {
3663  return Html::element( 'link', [
3664  'rel' => 'alternate',
3665  'type' => "application/$type+xml",
3666  'title' => $text,
3667  'href' => $url ]
3668  );
3669  }
3670 
3680  public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
3681  $options = [];
3682  if ( $media ) {
3683  $options['media'] = $media;
3684  }
3685  if ( $condition ) {
3686  $options['condition'] = $condition;
3687  }
3688  if ( $dir ) {
3689  $options['dir'] = $dir;
3690  }
3691  $this->styles[$style] = $options;
3692  }
3693 
3701  public function addInlineStyle( $style_css, $flip = 'noflip' ) {
3702  if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
3703  # If wanted, and the interface is right-to-left, flip the CSS
3704  $style_css = CSSJanus::transform( $style_css, true, false );
3705  }
3706  $this->mInlineStyles .= Html::inlineStyle( $style_css );
3707  }
3708 
3714  protected function buildExemptModules() {
3715  $chunks = [];
3716 
3717  // Requirements:
3718  // - Within modules provided by the software (core, skin, extensions),
3719  // styles from skin stylesheets should be overridden by styles
3720  // from modules dynamically loaded with JavaScript.
3721  // - Styles from site-specific, private, and user modules should override
3722  // both of the above.
3723  //
3724  // The effective order for stylesheets must thus be:
3725  // 1. Page style modules, formatted server-side by ResourceLoaderClientHtml.
3726  // 2. Dynamically-loaded styles, inserted client-side by mw.loader.
3727  // 3. Styles that are site-specific, private or from the user, formatted
3728  // server-side by this function.
3729  //
3730  // The 'ResourceLoaderDynamicStyles' marker helps JavaScript know where
3731  // point #2 is.
3732 
3733  // Add legacy styles added through addStyle()/addInlineStyle() here
3734  $chunks[] = implode( '', $this->buildCssLinksArray() ) . $this->mInlineStyles;
3735 
3736  // Things that go after the ResourceLoaderDynamicStyles marker
3737  $append = [];
3738  $separateReq = [ 'site.styles', 'user.styles' ];
3739  foreach ( $this->rlExemptStyleModules as $group => $moduleNames ) {
3740  if ( $moduleNames ) {
3741  $append[] = $this->makeResourceLoaderLink(
3742  array_diff( $moduleNames, $separateReq ),
3743  ResourceLoaderModule::TYPE_STYLES
3744  );
3745 
3746  foreach ( array_intersect( $moduleNames, $separateReq ) as $name ) {
3747  // These require their own dedicated request in order to support "@import"
3748  // syntax, which is incompatible with concatenation. (T147667, T37562)
3749  $append[] = $this->makeResourceLoaderLink( $name,
3750  ResourceLoaderModule::TYPE_STYLES
3751  );
3752  }
3753  }
3754  }
3755  if ( $append ) {
3756  $chunks[] = Html::element(
3757  'meta',
3758  [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ]
3759  );
3760  $chunks = array_merge( $chunks, $append );
3761  }
3762 
3763  return self::combineWrappedStrings( $chunks );
3764  }
3765 
3769  public function buildCssLinksArray() {
3770  $links = [];
3771 
3772  foreach ( $this->styles as $file => $options ) {
3773  $link = $this->styleLink( $file, $options );
3774  if ( $link ) {
3775  $links[$file] = $link;
3776  }
3777  }
3778  return $links;
3779  }
3780 
3788  protected function styleLink( $style, array $options ) {
3789  if ( isset( $options['dir'] ) && $this->getLanguage()->getDir() != $options['dir'] ) {
3790  return '';
3791  }
3792 
3793  if ( isset( $options['media'] ) ) {
3794  $media = self::transformCssMedia( $options['media'] );
3795  if ( is_null( $media ) ) {
3796  return '';
3797  }
3798  } else {
3799  $media = 'all';
3800  }
3801 
3802  if ( substr( $style, 0, 1 ) == '/' ||
3803  substr( $style, 0, 5 ) == 'http:' ||
3804  substr( $style, 0, 6 ) == 'https:' ) {
3805  $url = $style;
3806  } else {
3807  $config = $this->getConfig();
3808  // Append file hash as query parameter
3809  $url = self::transformResourcePath(
3810  $config,
3811  $config->get( 'StylePath' ) . '/' . $style
3812  );
3813  }
3814 
3815  $link = Html::linkedStyle( $url, $media );
3816 
3817  if ( isset( $options['condition'] ) ) {
3818  $condition = htmlspecialchars( $options['condition'] );
3819  $link = "<!--[if $condition]>$link<![endif]-->";
3820  }
3821  return $link;
3822  }
3823 
3845  public static function transformResourcePath( Config $config, $path ) {
3846  global $IP;
3847 
3848  $localDir = $IP;
3849  $remotePathPrefix = $config->get( 'ResourceBasePath' );
3850  if ( $remotePathPrefix === '' ) {
3851  // The configured base path is required to be empty string for
3852  // wikis in the domain root
3853  $remotePath = '/';
3854  } else {
3855  $remotePath = $remotePathPrefix;
3856  }
3857  if ( strpos( $path, $remotePath ) !== 0 || substr( $path, 0, 2 ) === '//' ) {
3858  // - Path is outside wgResourceBasePath, ignore.
3859  // - Path is protocol-relative. Fixes T155310. Not supported by RelPath lib.
3860  return $path;
3861  }
3862  // For files in resources, extensions/ or skins/, ResourceBasePath is preferred here.
3863  // For other misc files in $IP, we'll fallback to that as well. There is, however, a fourth
3864  // supported dir/path pair in the configuration (wgUploadDirectory, wgUploadPath)
3865  // which is not expected to be in wgResourceBasePath on CDNs. (T155146)
3866  $uploadPath = $config->get( 'UploadPath' );
3867  if ( strpos( $path, $uploadPath ) === 0 ) {
3868  $localDir = $config->get( 'UploadDirectory' );
3869  $remotePathPrefix = $remotePath = $uploadPath;
3870  }
3871 
3872  $path = RelPath::getRelativePath( $path, $remotePath );
3873  return self::transformFilePath( $remotePathPrefix, $localDir, $path );
3874  }
3875 
3887  public static function transformFilePath( $remotePathPrefix, $localPath, $file ) {
3888  $hash = md5_file( "$localPath/$file" );
3889  if ( $hash === false ) {
3890  wfLogWarning( __METHOD__ . ": Failed to hash $localPath/$file" );
3891  $hash = '';
3892  }
3893  return "$remotePathPrefix/$file?" . substr( $hash, 0, 5 );
3894  }
3895 
3903  public static function transformCssMedia( $media ) {
3904  global $wgRequest;
3905 
3906  // https://www.w3.org/TR/css3-mediaqueries/#syntax
3907  $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
3908 
3909  // Switch in on-screen display for media testing
3910  $switches = [
3911  'printable' => 'print',
3912  'handheld' => 'handheld',
3913  ];
3914  foreach ( $switches as $switch => $targetMedia ) {
3915  if ( $wgRequest->getBool( $switch ) ) {
3916  if ( $media == $targetMedia ) {
3917  $media = '';
3918  } elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
3919  /* This regex will not attempt to understand a comma-separated media_query_list
3920  *
3921  * Example supported values for $media:
3922  * 'screen', 'only screen', 'screen and (min-width: 982px)' ),
3923  * Example NOT supported value for $media:
3924  * '3d-glasses, screen, print and resolution > 90dpi'
3925  *
3926  * If it's a print request, we never want any kind of screen stylesheets
3927  * If it's a handheld request (currently the only other choice with a switch),
3928  * we don't want simple 'screen' but we might want screen queries that
3929  * have a max-width or something, so we'll pass all others on and let the
3930  * client do the query.
3931  */
3932  if ( $targetMedia == 'print' || $media == 'screen' ) {
3933  return null;
3934  }
3935  }
3936  }
3937  }
3938 
3939  return $media;
3940  }
3941 
3948  public function addWikiMsg( /*...*/ ) {
3949  $args = func_get_args();
3950  $name = array_shift( $args );
3951  $this->addWikiMsgArray( $name, $args );
3952  }
3953 
3962  public function addWikiMsgArray( $name, $args ) {
3963  $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
3964  }
3965 
3991  public function wrapWikiMsg( $wrap /*, ...*/ ) {
3992  $msgSpecs = func_get_args();
3993  array_shift( $msgSpecs );
3994  $msgSpecs = array_values( $msgSpecs );
3995  $s = $wrap;
3996  foreach ( $msgSpecs as $n => $spec ) {
3997  if ( is_array( $spec ) ) {
3998  $args = $spec;
3999  $name = array_shift( $args );
4000  } else {
4001  $args = [];
4002  $name = $spec;
4003  }
4004  $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
4005  }
4006  $this->addWikiTextAsInterface( $s );
4007  }
4008 
4014  public function isTOCEnabled() {
4015  return $this->mEnableTOC;
4016  }
4017 
4025  public static function setupOOUI( $skinName = 'default', $dir = 'ltr' ) {
4027  $theme = $themes[$skinName] ?? $themes['default'];
4028  // For example, 'OOUI\WikimediaUITheme'.
4029  $themeClass = "OOUI\\{$theme}Theme";
4030  OOUI\Theme::setSingleton( new $themeClass() );
4031  OOUI\Element::setDefaultDir( $dir );
4032  }
4033 
4040  public function enableOOUI() {
4041  self::setupOOUI(
4042  strtolower( $this->getSkin()->getSkinName() ),
4043  $this->getLanguage()->getDir()
4044  );
4045  $this->addModuleStyles( [
4046  'oojs-ui-core.styles',
4047  'oojs-ui.styles.indicators',
4048  'mediawiki.widgets.styles',
4049  'oojs-ui-core.icons',
4050  ] );
4051  }
4052 
4062  public function getCSPNonce() {
4064  return false;
4065  }
4066  if ( $this->CSPNonce === null ) {
4067  // XXX It might be expensive to generate randomness
4068  // on every request, on Windows.
4069  $rand = random_bytes( 15 );
4070  $this->CSPNonce = base64_encode( $rand );
4071  }
4072  return $this->CSPNonce;
4073  }
4074 
4075 }
getPreventClickjacking()
Get the prevent-clickjacking flag.
setContext(IContextSource $context)
isDisabled()
Return whether the output will be completely disabled.
static linkedScript( $url, $nonce=null)
Output a "<script>" tag linking to the given URL, e.g., "<script src=foo.js></script>".
Definition: Html.php:596
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
static static static getSkinThemeMap()
Return a map of skin names (in lowercase) to OOUI theme names, defining which theme a given skin shou...
getModules( $filter=false, $position=null, $param='mModules', $type=ResourceLoaderModule::TYPE_COMBINED)
Get the list of modules to include on this page.
Definition: OutputPage.php:528
$response
static newFromContext(IContextSource $context)
Get a ParserOptions object from a IContextSource object.
ResourceLoader $mResourceLoader
Definition: OutputPage.php:160
setArray( $array)
Set the link list to a given 2-d array First key is the namespace, second is the DB key...
Definition: LinkBatch.php:100
array $rlExemptStyleModules
Definition: OutputPage.php:169
array $mTemplateIds
Definition: OutputPage.php:175
setConfig(array $vars)
Set mw.config variables.
addWikiTextAsContent( $text, $linestart=true, Title $title=null)
Convert wikitext in the page content language to HTML and add it to the buffer.
$resourceLoader
Definition: load.php:44
getHeadItemsArray()
Get an array of head items.
Definition: OutputPage.php:624
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:136
int $mCdnMaxage
Cache stuff.
Definition: OutputPage.php:235
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:231
setFileVersion( $file)
Set the displayed file version.
getHTMLTitle()
Return the "HTML title", i.e.
Definition: OutputPage.php:899
int $mCdnMaxageLimit
Upper limit on mCdnMaxage.
Definition: OutputPage.php:237
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:303
$wgVersion
MediaWiki version number.
setTarget( $target)
Sets ResourceLoader target for load.php links.
Definition: OutputPage.php:584
bool $mEnableTOC
Whether parser output contains a table of contents.
Definition: OutputPage.php:298
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:670
callable [] $contentOverrideCallbacks
Definition: OutputPage.php:312
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
setCdnMaxage( $maxage)
Set the value of the "s-maxage" part of the "Cache-control" HTTP header.
string [][] $mMetatags
Should be private.
Definition: OutputPage.php:48
static newMainPage(MessageLocalizer $localizer=null)
Create a new Title for the Main Page.
Definition: Title.php:649
getCanonicalUrl()
Returns the URL to be used for the <link rel="canonical"> if one is set.
Definition: OutputPage.php:439
int $mContainsNewMagic
Definition: OutputPage.php:200
$IP
Definition: WebStart.php:41
setStatusCode( $statusCode)
Set the HTTP status code to send with the output.
Definition: OutputPage.php:377
setArticleFlag( $newVal)
Set whether the displayed content is related to the source of the corresponding article on the wiki S...
wrapWikiMsg( $wrap)
This function takes a number of message/argument specifications, wraps them in some overall structure...
addModules( $modules)
Load one or more ResourceLoader modules on this page.
Definition: OutputPage.php:542
string $mPageTitle
The contents of.
Definition: OutputPage.php:59
setLanguageLinks(array $newLinkArray)
Reset the language links and add new language links.
The Message class provides methods which fulfil two basic services:
Definition: Message.php:164
setSubtitle( $str)
Replace the subtitle with $str.
string $mInlineStyles
Inline CSS styles.
Definition: OutputPage.php:139
preventClickjacking( $flag=null)
Get or set the prevent-clickjacking flag.
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
addCategoryLinks(array $categories)
Add an array of categories, with names in the keys.
haveCacheVaryCookies()
Check if the request has a cache-varying cookie header If it does, it&#39;s very important that we don&#39;t ...
getPageTitle()
Return the "page title", i.e.
Definition: OutputPage.php:946
setCopyrightUrl( $url)
Set the copyright URL to send with the output.
Definition: OutputPage.php:368
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:504
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:345
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:148
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:154
const NS_SPECIAL
Definition: Defines.php:49
const PROTO_CURRENT
Definition: Defines.php:202
string null $mTarget
ResourceLoader target for load.php links.
Definition: OutputPage.php:293
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:690
array $limitReportJSData
Profiling data.
Definition: OutputPage.php:306
prependHTML( $text)
Prepend $text to the body HTML.
static makeConfigSetScript(array $configuration)
Returns JS code which will set the MediaWiki configuration array to the given value.
setProperty( $name, $value)
Set an additional output property.
Definition: OutputPage.php:701
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:984
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:484
string $mPageLinkTitle
Used by skin template.
Definition: OutputPage.php:145
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:303
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:1820
disable()
Disable output completely, i.e.
Content for JavaScript pages.
formatPermissionsErrorMessage(array $errors, $action=null)
Format a list of error messages.
static inlineScript( $contents, $nonce=null)
Output an HTML script tag with the given contents.
Definition: Html.php:572
addReturnTo( $title, array $query=[], $text=null, $options=[])
Add a "return to" link pointing to a specified title.
checkLastModified( $timestamp)
checkLastModified tells the client to use the client-cached page if possible.
Definition: OutputPage.php:727
string $mRevisionTimestamp
Definition: OutputPage.php:250
array $mLanguageLinks
Array of Interwiki Prefixed (non DB key) Titles (e.g.
Definition: OutputPage.php:128
setHTMLTitle( $name)
"HTML title" means the contents of "<title>".
Definition: OutputPage.php:886
IContextSource $context
array Title $mRedirectedFrom
If the current page was reached through a redirect, $mRedirectedFrom contains the Title of the redire...
Definition: OutputPage.php:283
string $displayTitle
The displayed title of the page.
Definition: OutputPage.php:68
setPageTitle( $name)
"Page title" means the contents of <h1>.
Definition: OutputPage.php:924
getRlClient()
Call this to freeze the module queue and JS config and create a formatter.
getFileSearchOptions()
Get the files used on this page.
setCategoryLinks(array $categories)
Reset the category links (but not the category list) and add $categories.
isSyndicated()
Should we output feed links for this page?
static setupOOUI( $skinName='default', $dir='ltr')
Helper function to setup the PHP implementation of OOUI to use in this request.
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
array $styles
An array of stylesheet filenames (relative from skins path), with options for CSS media...
Definition: OutputPage.php:263
isPrintable()
Return whether the page is "printable".
$mProperties
Additional key => value data.
Definition: OutputPage.php:288
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:195
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:908
bool $mPreventClickjacking
Controls if anti-clickjacking / frame-breaking headers will be sent.
Definition: OutputPage.php:244
if( $line===false) $args
Definition: cdb.php:64
static stripOuterParagraph( $html)
Strip outer.
Definition: Parser.php:6472
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:443
array $mJsConfigVars
Definition: OutputPage.php:172
getJsConfigVars()
Get the javascript config vars to include on this page.
enableOOUI()
Add ResourceLoader module styles for OOUI and set up the PHP implementation of it for use with MediaW...
addToBodyAttributes( $out, &$bodyAttrs)
This will be called by OutputPage::headElement when it is creating the "<body>" tag, skins can override it if they have a need to add in any body attributes or classes of their own.
Definition: Skin.php:497
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
bool $mPrintable
We have to set isPrintable().
Definition: OutputPage.php:95
A mutable version of ResourceLoaderContext.
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e.g.
clearSubtitle()
Clear the subtitles.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition: LinkBatch.php:34
bool $mIsArticleRelated
Stores "article flag" toggle.
Definition: OutputPage.php:86
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:815
array $mAllowedModules
What level of &#39;untrustworthiness&#39; is allowed in CSS/JS modules loaded on this page?
Definition: OutputPage.php:190
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:327
bool $mNoGallery
Comes from the parser.
Definition: OutputPage.php:232
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:873
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:2041
getLanguageLinks()
Get the list of language links.
array $mFileVersion
Definition: OutputPage.php:253
array $mModuleStyles
Definition: OutputPage.php:157
isRevisionCurrent()
Whether the revision displayed is the latest revision of the page.
bool $mHasCopyright
Is the content subject to copyright.
Definition: OutputPage.php:89
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness, which urlencode encodes by default.
makeResourceLoaderLink( $modules, $only, array $extraQuery=[])
Explicily load or embed modules on a page.
enableClientCache( $state)
Use enableClientCache(false) to force it to send nocache headers.
addAcceptLanguage()
T23672: Add Accept-Language to Vary header if there&#39;s no &#39;variant&#39; parameter in GET.
Interface for configuration instances.
Definition: Config.php:28
parse( $text, $linestart=true, $interface=false, $language=null)
Parse wikitext and return the HTML.
wfCgiToArray( $query)
This is the logical opposite of wfArrayToCgi(): it accepts a query string as its argument and returns...
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition: Xml.php:677
setSyndicated( $show=true)
Add or remove feed links in the page header This is mainly kept for backward compatibility, see OutputPage::addFeedLink() for the new version.
addSubtitle( $str)
Add $str to the subtitle.
headElement(Skin $sk, $includeStyle=true)
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:841
array array $mIndicators
Definition: OutputPage.php:125
addMeta( $name, $val)
Add a new "<meta>" tag To add an http-equiv meta tag, precede the name with "http:".
Definition: OutputPage.php:388
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:213
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:660
$mFeedLinksAppendQuery
Definition: OutputPage.php:183
getMetaTags()
Returns the current <meta> tags.
Definition: OutputPage.php:398
$mLinkHeader
Link: header contents.
Definition: OutputPage.php:317
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:219
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:151
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.
showNewSectionLink()
Show an "add new section" link?
const NS_CATEGORY
Definition: Defines.php:74
addJsConfigVars( $keys, $value=null)
Add one or more variables to be set in mw.config in JavaScript.
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:315
getCategories( $type='all')
Get the list of category names this page belongs to.
string $mHTMLtitle
Stores contents of "<title>" tag.
Definition: OutputPage.php:77
bool $mHideNewSectionLink
Definition: OutputPage.php:225
string $mBodytext
Contains all of the "<body>" content.
Definition: OutputPage.php:74
buildCssLinksArray()
getRlClientContext()
getDisplayTitle()
Returns page display title.
Definition: OutputPage.php:969
getAdvertisedFeedTypes()
Return effective list of advertised feed types.
static runWithoutAbort( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:231
addHeadItems( $values)
Add one or more head items to the output.
Definition: OutputPage.php:650
getHtmlElementAttributes()
Return values for <html> element.
Definition: Skin.php:481
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:273
preventClickjacking( $enable=true)
Set a flag which will cause an X-Frame-Options header appropriate for edit pages to be sent...
static addModules(OutputPage $out)
Add ResourceLoader modules to the OutputPage object if debugging is enabled.
Definition: MWDebug.php:120
addLink(array $linkarr)
Add a new <link> tag to the page header.
Definition: OutputPage.php:409
$filter
addModuleStyles( $modules)
Load the styles of one or more ResourceLoader modules on this page.
Definition: OutputPage.php:568
const PROTO_RELATIVE
Definition: Defines.php:201
array $mCategories
Definition: OutputPage.php:119
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:322
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:553
bool $mNewSectionLink
Definition: OutputPage.php:222
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:335
static makeInlineScript( $script, $nonce=null)
Returns an HTML script tag that runs given JS code after startup and base modules.
static makeLoad(ResourceLoaderContext $mainContext, array $modules, $only, array $extraQuery=[], $nonce=null)
Explicily load or embed modules on a page.
static newFromAnon()
Get a ParserOptions object for an anonymous user.
addScriptFile( $file, $unused=null)
Add a JavaScript file to be loaded as <script> on this page.
Definition: OutputPage.php:462
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:450
array $mCategoryLinks
Definition: OutputPage.php:116
static getSelectFields()
Fields that LinkCache needs to select.
Definition: LinkCache.php:219
parseAsContent( $text, $linestart=true)
Parse wikitext in the page content language and return the HTML.
setTitle(Title $t)
Set the Title object to use.
Definition: OutputPage.php:997
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:107
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:613
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:681
setLastModified( $timestamp)
Override the last modified timestamp.
Definition: OutputPage.php:829
addHeadItem( $name, $value)
Add or replace a head item to the output.
Definition: OutputPage.php:640
string $mLastModified
Used for sending cache control.
Definition: OutputPage.php:113
wfSetVar(&$dest, $source, $force=false)
Sets dest to source and returns the original value of dest If source is NULL, it just returns the val...
static inDebugMode()
Determine whether debug mode was requested Order of priority is 1) request param, 2) cookie...
addContentOverrideCallback(callable $callback)
Add a callback for mapping from a Title to a Content object, for things like page preview...
Definition: OutputPage.php:615
const PROTO_CANONICAL
Definition: Defines.php:203
static transformCssMedia( $media)
Transform "media" attribute based on request parameters.
addParserOutputContent(ParserOutput $parserOutput, $poOptions=[])
Add the HTML and enhancements for it (like ResourceLoader modules) associated with a ParserOutput obj...
versionRequired( $version)
Display an error page indicating that a given version of MediaWiki is required to use it...
int $mRevisionId
To include the variable {{REVISIONID}}.
Definition: OutputPage.php:247
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:595
array $contentOverrides
Map Title to Content.
Definition: OutputPage.php:309
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:181
array $mImageTimeKeys
Definition: OutputPage.php:178
getResourceLoader()
Get a ResourceLoader object associated with this OutputPage.
getFeaturePolicyReportOnly()
getRevisionId()
Get the displayed revision ID.
wfClearOutputBuffers()
More legible than passing a &#39;false&#39; parameter to wfResetOutputBuffers():
getFileVersion()
Get the displayed file version.
returnToMain( $unused=null, $returnto=null, $returntoquery=null)
Add a "return to" link pointing to a specified title, or the title indicated in the request...
setArticleRelated( $newVal)
Set whether this page is related an article on the wiki Setting false will cause the change of "artic...
userCanEditOrCreate(User $user, LinkTarget $title)
forceHideNewSectionLink()
Forcibly hide the new section link?
static isXmlMimeType( $mimetype)
Determines if the given MIME type is xml.
Definition: Html.php:997
isRedirect( $flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition: Title.php:3152
getBottomScripts()
JS stuff to put at the bottom of the <body>.
addParserOutputMetadata(ParserOutput $parserOutput)
Add all metadata associated with a ParserOutput object, but without the actual HTML.
static inlineStyle( $contents, $media='all', $attribs=[])
Output a "<style>" tag with the given contents for the given media type (if any). ...
Definition: Html.php:619
addHTML( $text)
Append $text to the body HTML.
getRedirect()
Get the URL to redirect to, or an empty string if not redirect URL set.
Definition: OutputPage.php:356
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:735
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:101
showLagWarning( $lag)
Show a warning about replica DB lag.
static removeHTMLtags( $text, $processCallback=null, $args=[], $extratags=[], $removetags=[], $warnCallback=null)
Cleans up HTML, removes dangerous tags and attributes, and removes HTML comments. ...
Definition: Sanitizer.php:497
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
showPermissionsErrorPage(array $errors, $action=null)
Output a standard permission error page.
getCacheVaryCookies()
Get the list of cookie names that will influence the cache.
ResourceLoaderContext $rlClientContext
Definition: OutputPage.php:166
addElement( $element, array $attribs=[], $contents='')
Shortcut for adding an Html::element via addHTML.
setModules(array $modules)
Ensure one or more modules are loaded.
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:61
getCSPNonce()
Get (and set if not yet set) the CSP nonce.
parseInline( $text, $linestart=true, $interface=false)
Parse wikitext, strip paragraph wrapper, and return the HTML.
parserOptions( $options=null)
Get/set the ParserOptions object to use for wikitext parsing.
setFeedAppendQuery( $val)
Add default feeds to the page header This is mainly kept for backward compatibility, see OutputPage::addFeedLink() for the new version.
getWikiPage()
Get the WikiPage object.
getSkinName()
Definition: Skin.php:158
setIndicators(array $indicators)
Add an array of indicators, with their identifiers as array keys and HTML contents as values...
setCanonicalUrl( $url)
Set the URL to be used for the <link rel="canonical">.
Definition: OutputPage.php:428
bool $mIsArticle
Is the displayed content related to the source of the corresponding wiki article. ...
Definition: OutputPage.php:83
disallowUserJs()
Do not allow scripts which can be modified by wiki users to load on this page; only allow scripts bun...
getOrigin()
Get this module&#39;s origin.
setIndexPolicy( $policy)
Set the index policy for the page, but leave the follow policy un- touched.
Definition: OutputPage.php:859
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:472
string $mRedirect
Definition: OutputPage.php:104
static bcp47( $code)
Get the normalised IETF language tag See unit test for examples.
getRevisionTimestamp()
Get the timestamp of displayed revision.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
getLinkTags()
Returns the current <link> tags.
Definition: OutputPage.php:419
setRevisionTimestamp( $timestamp)
Set the timestamp of the revision which will be displayed.
ResourceLoaderClientHtml $rlClient
Definition: OutputPage.php:163
isArticle()
Return whether the content displayed page is related to the source of the corresponding article on th...
addWikiMsgArray( $name, $args)
Add a wikitext-formatted message to the output.
Context object that contains information about the state of a specific ResourceLoader web request...
ParserOptions $mParserOptions
lazy initialised, use parserOptions()
Definition: OutputPage.php:206
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:712
setDisplayTitle( $html)
Same as page title but only contains name of the page, not any other text.
Definition: OutputPage.php:957
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:316
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition: Article.php:1045