MediaWiki  master
OutputPage.php
Go to the documentation of this file.
1 <?php
30 
46 class OutputPage extends ContextSource {
48  protected $mMetatags = [];
49 
51  protected $mLinktags = [];
52 
54  protected $mCanonicalUrl = false;
55 
58  private $mPageTitle = '';
59 
67  private $displayTitle;
68 
73  public $mBodytext = '';
74 
76  private $mHTMLtitle = '';
77 
82  private $mIsArticle = false;
83 
85  private $mIsArticleRelated = true;
86 
88  private $mHasCopyright = false;
89 
94  private $mPrintable = false;
95 
100  private $mSubtitle = [];
101 
103  public $mRedirect = '';
104 
106  protected $mStatusCode;
107 
112  protected $mLastModified = '';
113 
115  protected $mCategoryLinks = [];
116 
118  protected $mCategories = [
119  'hidden' => [],
120  'normal' => [],
121  ];
122 
124  protected $mIndicators = [];
125 
127  private $mLanguageLinks = [];
128 
135  private $mScripts = '';
136 
138  protected $mInlineStyles = '';
139 
144  public $mPageLinkTitle = '';
145 
147  protected $mHeadItems = [];
148 
150  protected $mAdditionalBodyClasses = [];
151 
153  protected $mModules = [];
154 
156  protected $mModuleStyles = [];
157 
159  protected $mResourceLoader;
160 
162  private $rlClient;
163 
166 
169 
171  protected $mJsConfigVars = [];
172 
174  protected $mTemplateIds = [];
175 
177  protected $mImageTimeKeys = [];
178 
180  public $mRedirectCode = '';
181 
183 
189  protected $mAllowedModules = [
191  ];
192 
194  protected $mDoNothing = false;
195 
196  // Parser related.
197 
199  protected $mContainsNewMagic = 0;
200 
205  protected $mParserOptions = null;
206 
212  private $mFeedLinks = [];
213 
214  // Gwicke work on squid caching? Roughly from 2003.
215  protected $mEnableClientCache = true;
216 
218  private $mArticleBodyOnly = false;
219 
221  protected $mNewSectionLink = false;
222 
224  protected $mHideNewSectionLink = false;
225 
231  public $mNoGallery = false;
232 
234  protected $mCdnMaxage = 0;
236  protected $mCdnMaxageLimit = INF;
237 
243  protected $mPreventClickjacking = true;
244 
246  private $mRevisionId = null;
247 
250 
252  protected $mFileVersion = null;
253 
262  protected $styles = [];
263 
264  private $mIndexPolicy = 'index';
265  private $mFollowPolicy = 'follow';
266 
271  private $mVaryHeader = [
272  'Accept-Encoding' => [ 'match=gzip' ],
273  ];
274 
282 
286  private $mProperties = [];
287 
291  private $mTarget = null;
292 
296  private $mEnableTOC = false;
297 
301  private $copyrightUrl;
302 
304  private $limitReportJSData = [];
305 
307  private $contentOverrides = [];
308 
311 
315  private $mLinkHeader = [];
316 
320  private $CSPNonce;
321 
325  private static $cacheVaryCookies = null;
326 
334  $this->setContext( $context );
335  }
336 
343  public function redirect( $url, $responsecode = '302' ) {
344  # Strip newlines as a paranoia check for header injection in PHP<5.1.2
345  $this->mRedirect = str_replace( "\n", '', $url );
346  $this->mRedirectCode = $responsecode;
347  }
348 
354  public function getRedirect() {
355  return $this->mRedirect;
356  }
357 
366  public function setCopyrightUrl( $url ) {
367  $this->copyrightUrl = $url;
368  }
369 
375  public function setStatusCode( $statusCode ) {
376  $this->mStatusCode = $statusCode;
377  }
378 
386  function addMeta( $name, $val ) {
387  array_push( $this->mMetatags, [ $name, $val ] );
388  }
389 
396  public function getMetaTags() {
397  return $this->mMetatags;
398  }
399 
407  function addLink( array $linkarr ) {
408  array_push( $this->mLinktags, $linkarr );
409  }
410 
417  public function getLinkTags() {
418  return $this->mLinktags;
419  }
420 
426  function setCanonicalUrl( $url ) {
427  $this->mCanonicalUrl = $url;
428  }
429 
437  public function getCanonicalUrl() {
438  return $this->mCanonicalUrl;
439  }
440 
448  function addScript( $script ) {
449  $this->mScripts .= $script;
450  }
451 
460  public function addScriptFile( $file, $unused = null ) {
461  if ( substr( $file, 0, 1 ) !== '/' && !preg_match( '#^[a-z]*://#i', $file ) ) {
462  // This is not an absolute path, protocol-relative url, or full scheme url,
463  // presumed to be an old call intended to include a file from /w/skins/common,
464  // which doesn't exist anymore as of MediaWiki 1.24 per T71277. Ignore.
465  wfDeprecated( __METHOD__, '1.24' );
466  return;
467  }
468  $this->addScript( Html::linkedScript( $file, $this->getCSPNonce() ) );
469  }
470 
477  public function addInlineScript( $script ) {
478  $this->mScripts .= Html::inlineScript( "\n$script\n", $this->getCSPNonce() ) . "\n";
479  }
480 
489  protected function filterModules( array $modules, $position = null,
491  ) {
493  $filteredModules = [];
494  foreach ( $modules as $val ) {
495  $module = $resourceLoader->getModule( $val );
496  if ( $module instanceof ResourceLoaderModule
497  && $module->getOrigin() <= $this->getAllowedModules( $type )
498  ) {
499  if ( $this->mTarget && !in_array( $this->mTarget, $module->getTargets() ) ) {
500  $this->warnModuleTargetFilter( $module->getName() );
501  continue;
502  }
503  $filteredModules[] = $val;
504  }
505  }
506  return $filteredModules;
507  }
508 
509  private function warnModuleTargetFilter( $moduleName ) {
510  static $warnings = [];
511  if ( isset( $warnings[$this->mTarget][$moduleName] ) ) {
512  return;
513  }
514  $warnings[$this->mTarget][$moduleName] = true;
515  $this->getResourceLoader()->getLogger()->debug(
516  'Module "{module}" not loadable on target "{target}".',
517  [
518  'module' => $moduleName,
519  'target' => $this->mTarget,
520  ]
521  );
522  }
523 
533  public function getModules( $filter = false, $position = null, $param = 'mModules',
535  ) {
536  $modules = array_values( array_unique( $this->$param ) );
537  return $filter
538  ? $this->filterModules( $modules, null, $type )
539  : $modules;
540  }
541 
547  public function addModules( $modules ) {
548  $this->mModules = array_merge( $this->mModules, (array)$modules );
549  }
550 
558  public function getModuleStyles( $filter = false, $position = null ) {
559  return $this->getModules( $filter, null, 'mModuleStyles',
561  );
562  }
563 
573  public function addModuleStyles( $modules ) {
574  $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
575  }
576 
580  public function getTarget() {
581  return $this->mTarget;
582  }
583 
589  public function setTarget( $target ) {
590  $this->mTarget = $target;
591  }
592 
600  public function addContentOverride( LinkTarget $target, Content $content ) {
601  if ( !$this->contentOverrides ) {
602  // Register a callback for $this->contentOverrides on the first call
603  $this->addContentOverrideCallback( function ( LinkTarget $target ) {
604  $key = $target->getNamespace() . ':' . $target->getDBkey();
605  return $this->contentOverrides[$key] ?? null;
606  } );
607  }
608 
609  $key = $target->getNamespace() . ':' . $target->getDBkey();
610  $this->contentOverrides[$key] = $content;
611  }
612 
620  public function addContentOverrideCallback( callable $callback ) {
621  $this->contentOverrideCallbacks[] = $callback;
622  }
623 
629  function getHeadItemsArray() {
630  return $this->mHeadItems;
631  }
632 
645  public function addHeadItem( $name, $value ) {
646  $this->mHeadItems[$name] = $value;
647  }
648 
655  public function addHeadItems( $values ) {
656  $this->mHeadItems = array_merge( $this->mHeadItems, (array)$values );
657  }
658 
665  public function hasHeadItem( $name ) {
666  return isset( $this->mHeadItems[$name] );
667  }
668 
675  public function addBodyClasses( $classes ) {
676  $this->mAdditionalBodyClasses = array_merge( $this->mAdditionalBodyClasses, (array)$classes );
677  }
678 
686  public function setArticleBodyOnly( $only ) {
687  $this->mArticleBodyOnly = $only;
688  }
689 
695  public function getArticleBodyOnly() {
697  }
698 
706  public function setProperty( $name, $value ) {
707  $this->mProperties[$name] = $value;
708  }
709 
717  public function getProperty( $name ) {
718  return $this->mProperties[$name] ?? null;
719  }
720 
732  public function checkLastModified( $timestamp ) {
733  if ( !$timestamp || $timestamp == '19700101000000' ) {
734  wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
735  return false;
736  }
737  $config = $this->getConfig();
738  if ( !$config->get( 'CachePages' ) ) {
739  wfDebug( __METHOD__ . ": CACHE DISABLED\n" );
740  return false;
741  }
742 
743  $timestamp = wfTimestamp( TS_MW, $timestamp );
744  $modifiedTimes = [
745  'page' => $timestamp,
746  'user' => $this->getUser()->getTouched(),
747  'epoch' => $config->get( 'CacheEpoch' )
748  ];
749  if ( $config->get( 'UseCdn' ) ) {
750  $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, $this->getCdnCacheEpoch(
751  time(),
752  $config->get( 'CdnMaxAge' )
753  ) );
754  }
755  Hooks::run( 'OutputPageCheckLastModified', [ &$modifiedTimes, $this ] );
756 
757  $maxModified = max( $modifiedTimes );
758  $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
759 
760  $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' );
761  if ( $clientHeader === false ) {
762  wfDebug( __METHOD__ . ": client did not send If-Modified-Since header", 'private' );
763  return false;
764  }
765 
766  # IE sends sizes after the date like this:
767  # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
768  # this breaks strtotime().
769  $clientHeader = preg_replace( '/;.*$/', '', $clientHeader );
770 
771  Wikimedia\suppressWarnings(); // E_STRICT system time warnings
772  $clientHeaderTime = strtotime( $clientHeader );
773  Wikimedia\restoreWarnings();
774  if ( !$clientHeaderTime ) {
775  wfDebug( __METHOD__
776  . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
777  return false;
778  }
779  $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
780 
781  # Make debug info
782  $info = '';
783  foreach ( $modifiedTimes as $name => $value ) {
784  if ( $info !== '' ) {
785  $info .= ', ';
786  }
787  $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
788  }
789 
790  wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
791  wfTimestamp( TS_ISO_8601, $clientHeaderTime ), 'private' );
792  wfDebug( __METHOD__ . ": effective Last-Modified: " .
793  wfTimestamp( TS_ISO_8601, $maxModified ), 'private' );
794  if ( $clientHeaderTime < $maxModified ) {
795  wfDebug( __METHOD__ . ": STALE, $info", 'private' );
796  return false;
797  }
798 
799  # Not modified
800  # Give a 304 Not Modified response code and disable body output
801  wfDebug( __METHOD__ . ": NOT MODIFIED, $info", 'private' );
802  ini_set( 'zlib.output_compression', 0 );
803  $this->getRequest()->response()->statusHeader( 304 );
804  $this->sendCacheControl();
805  $this->disable();
806 
807  // Don't output a compressed blob when using ob_gzhandler;
808  // it's technically against HTTP spec and seems to confuse
809  // Firefox when the response gets split over two packets.
811 
812  return true;
813  }
814 
820  private function getCdnCacheEpoch( $reqTime, $maxAge ) {
821  // Ensure Last-Modified is never more than $wgCdnMaxAge in the past,
822  // because even if the wiki page content hasn't changed since, static
823  // resources may have changed (skin HTML, interface messages, urls, etc.)
824  // and must roll-over in a timely manner (T46570)
825  return $reqTime - $maxAge;
826  }
827 
834  public function setLastModified( $timestamp ) {
835  $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp );
836  }
837 
846  public function setRobotPolicy( $policy ) {
847  $policy = Article::formatRobotPolicy( $policy );
848 
849  if ( isset( $policy['index'] ) ) {
850  $this->setIndexPolicy( $policy['index'] );
851  }
852  if ( isset( $policy['follow'] ) ) {
853  $this->setFollowPolicy( $policy['follow'] );
854  }
855  }
856 
864  public function setIndexPolicy( $policy ) {
865  $policy = trim( $policy );
866  if ( in_array( $policy, [ 'index', 'noindex' ] ) ) {
867  $this->mIndexPolicy = $policy;
868  }
869  }
870 
878  public function setFollowPolicy( $policy ) {
879  $policy = trim( $policy );
880  if ( in_array( $policy, [ 'follow', 'nofollow' ] ) ) {
881  $this->mFollowPolicy = $policy;
882  }
883  }
884 
891  public function setHTMLTitle( $name ) {
892  if ( $name instanceof Message ) {
893  $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
894  } else {
895  $this->mHTMLtitle = $name;
896  }
897  }
898 
904  public function getHTMLTitle() {
905  return $this->mHTMLtitle;
906  }
907 
913  public function setRedirectedFrom( $t ) {
914  $this->mRedirectedFrom = $t;
915  }
916 
929  public function setPageTitle( $name ) {
930  if ( $name instanceof Message ) {
931  $name = $name->setContext( $this->getContext() )->text();
932  }
933 
934  # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
935  # but leave "<i>foobar</i>" alone
937  $this->mPageTitle = $nameWithTags;
938 
939  # change "<i>foo&amp;bar</i>" to "foo&bar"
940  $this->setHTMLTitle(
941  $this->msg( 'pagetitle' )->plaintextParams( Sanitizer::stripAllTags( $nameWithTags ) )
942  ->inContentLanguage()
943  );
944  }
945 
951  public function getPageTitle() {
952  return $this->mPageTitle;
953  }
954 
962  public function setDisplayTitle( $html ) {
963  $this->displayTitle = $html;
964  }
965 
974  public function getDisplayTitle() {
976  if ( $html === null ) {
977  $html = $this->getTitle()->getPrefixedText();
978  }
979 
981  }
982 
989  public function getUnprefixedDisplayTitle() {
990  $text = $this->getDisplayTitle();
991  $nsPrefix = $this->getTitle()->getNsText() . ':';
992  $prefix = preg_quote( $nsPrefix, '/' );
993 
994  return preg_replace( "/^$prefix/i", '', $text );
995  }
996 
1002  public function setTitle( Title $t ) {
1003  $this->getContext()->setTitle( $t );
1004  }
1005 
1011  public function setSubtitle( $str ) {
1012  $this->clearSubtitle();
1013  $this->addSubtitle( $str );
1014  }
1015 
1021  public function addSubtitle( $str ) {
1022  if ( $str instanceof Message ) {
1023  $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
1024  } else {
1025  $this->mSubtitle[] = $str;
1026  }
1027  }
1028 
1037  public static function buildBacklinkSubtitle( Title $title, $query = [] ) {
1038  if ( $title->isRedirect() ) {
1039  $query['redirect'] = 'no';
1040  }
1041  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1042  return wfMessage( 'backlinksubtitle' )
1043  ->rawParams( $linkRenderer->makeLink( $title, null, [], $query ) );
1044  }
1045 
1052  public function addBacklinkSubtitle( Title $title, $query = [] ) {
1053  $this->addSubtitle( self::buildBacklinkSubtitle( $title, $query ) );
1054  }
1055 
1059  public function clearSubtitle() {
1060  $this->mSubtitle = [];
1061  }
1062 
1068  public function getSubtitle() {
1069  return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
1070  }
1071 
1076  public function setPrintable() {
1077  $this->mPrintable = true;
1078  }
1079 
1085  public function isPrintable() {
1086  return $this->mPrintable;
1087  }
1088 
1092  public function disable() {
1093  $this->mDoNothing = true;
1094  }
1095 
1101  public function isDisabled() {
1102  return $this->mDoNothing;
1103  }
1104 
1110  public function showNewSectionLink() {
1111  return $this->mNewSectionLink;
1112  }
1113 
1119  public function forceHideNewSectionLink() {
1121  }
1122 
1131  public function setSyndicated( $show = true ) {
1132  if ( $show ) {
1133  $this->setFeedAppendQuery( false );
1134  } else {
1135  $this->mFeedLinks = [];
1136  }
1137  }
1138 
1145  protected function getAdvertisedFeedTypes() {
1146  if ( $this->getConfig()->get( 'Feed' ) ) {
1147  return $this->getConfig()->get( 'AdvertisedFeedTypes' );
1148  } else {
1149  return [];
1150  }
1151  }
1152 
1162  public function setFeedAppendQuery( $val ) {
1163  $this->mFeedLinks = [];
1164 
1165  foreach ( $this->getAdvertisedFeedTypes() as $type ) {
1166  $query = "feed=$type";
1167  if ( is_string( $val ) ) {
1168  $query .= '&' . $val;
1169  }
1170  $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
1171  }
1172  }
1173 
1180  public function addFeedLink( $format, $href ) {
1181  if ( in_array( $format, $this->getAdvertisedFeedTypes() ) ) {
1182  $this->mFeedLinks[$format] = $href;
1183  }
1184  }
1185 
1190  public function isSyndicated() {
1191  return count( $this->mFeedLinks ) > 0;
1192  }
1193 
1198  public function getSyndicationLinks() {
1199  return $this->mFeedLinks;
1200  }
1201 
1207  public function getFeedAppendQuery() {
1209  }
1210 
1218  public function setArticleFlag( $newVal ) {
1219  $this->mIsArticle = $newVal;
1220  if ( $newVal ) {
1221  $this->mIsArticleRelated = $newVal;
1222  }
1223  }
1224 
1231  public function isArticle() {
1232  return $this->mIsArticle;
1233  }
1234 
1241  public function setArticleRelated( $newVal ) {
1242  $this->mIsArticleRelated = $newVal;
1243  if ( !$newVal ) {
1244  $this->mIsArticle = false;
1245  }
1246  }
1247 
1253  public function isArticleRelated() {
1254  return $this->mIsArticleRelated;
1255  }
1256 
1262  public function setCopyright( $hasCopyright ) {
1263  $this->mHasCopyright = $hasCopyright;
1264  }
1265 
1275  public function showsCopyright() {
1276  return $this->isArticle() || $this->mHasCopyright;
1277  }
1278 
1285  public function addLanguageLinks( array $newLinkArray ) {
1286  $this->mLanguageLinks = array_merge( $this->mLanguageLinks, $newLinkArray );
1287  }
1288 
1295  public function setLanguageLinks( array $newLinkArray ) {
1296  $this->mLanguageLinks = $newLinkArray;
1297  }
1298 
1304  public function getLanguageLinks() {
1305  return $this->mLanguageLinks;
1306  }
1307 
1313  public function addCategoryLinks( array $categories ) {
1314  if ( !$categories ) {
1315  return;
1316  }
1317 
1318  $res = $this->addCategoryLinksToLBAndGetResult( $categories );
1319 
1320  # Set all the values to 'normal'.
1321  $categories = array_fill_keys( array_keys( $categories ), 'normal' );
1322 
1323  # Mark hidden categories
1324  foreach ( $res as $row ) {
1325  if ( isset( $row->pp_value ) ) {
1326  $categories[$row->page_title] = 'hidden';
1327  }
1328  }
1329 
1330  // Avoid PHP 7.1 warning of passing $this by reference
1331  $outputPage = $this;
1332  # Add the remaining categories to the skin
1333  if ( Hooks::run(
1334  'OutputPageMakeCategoryLinks',
1335  [ &$outputPage, $categories, &$this->mCategoryLinks ] )
1336  ) {
1337  $services = MediaWikiServices::getInstance();
1338  $linkRenderer = $services->getLinkRenderer();
1339  foreach ( $categories as $category => $type ) {
1340  // array keys will cast numeric category names to ints, so cast back to string
1341  $category = (string)$category;
1342  $origcategory = $category;
1343  $title = Title::makeTitleSafe( NS_CATEGORY, $category );
1344  if ( !$title ) {
1345  continue;
1346  }
1347  $services->getContentLanguage()->findVariantLink( $category, $title, true );
1348  if ( $category != $origcategory && array_key_exists( $category, $categories ) ) {
1349  continue;
1350  }
1351  $text = $services->getContentLanguage()->convertHtml( $title->getText() );
1352  $this->mCategories[$type][] = $title->getText();
1353  $this->mCategoryLinks[$type][] = $linkRenderer->makeLink( $title, new HtmlArmor( $text ) );
1354  }
1355  }
1356  }
1357 
1362  protected function addCategoryLinksToLBAndGetResult( array $categories ) {
1363  # Add the links to a LinkBatch
1364  $arr = [ NS_CATEGORY => $categories ];
1365  $lb = new LinkBatch;
1366  $lb->setArray( $arr );
1367 
1368  # Fetch existence plus the hiddencat property
1369  $dbr = wfGetDB( DB_REPLICA );
1370  $fields = array_merge(
1372  [ 'page_namespace', 'page_title', 'pp_value' ]
1373  );
1374 
1375  $res = $dbr->select( [ 'page', 'page_props' ],
1376  $fields,
1377  $lb->constructSet( 'page', $dbr ),
1378  __METHOD__,
1379  [],
1380  [ 'page_props' => [ 'LEFT JOIN', [
1381  'pp_propname' => 'hiddencat',
1382  'pp_page = page_id'
1383  ] ] ]
1384  );
1385 
1386  # Add the results to the link cache
1387  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1388  $lb->addResultToCache( $linkCache, $res );
1389 
1390  return $res;
1391  }
1392 
1398  public function setCategoryLinks( array $categories ) {
1399  $this->mCategoryLinks = [];
1400  $this->addCategoryLinks( $categories );
1401  }
1402 
1411  public function getCategoryLinks() {
1412  return $this->mCategoryLinks;
1413  }
1414 
1424  public function getCategories( $type = 'all' ) {
1425  if ( $type === 'all' ) {
1426  $allCategories = [];
1427  foreach ( $this->mCategories as $categories ) {
1428  $allCategories = array_merge( $allCategories, $categories );
1429  }
1430  return $allCategories;
1431  }
1432  if ( !isset( $this->mCategories[$type] ) ) {
1433  throw new InvalidArgumentException( 'Invalid category type given: ' . $type );
1434  }
1435  return $this->mCategories[$type];
1436  }
1437 
1447  public function setIndicators( array $indicators ) {
1448  $this->mIndicators = $indicators + $this->mIndicators;
1449  // Keep ordered by key
1450  ksort( $this->mIndicators );
1451  }
1452 
1461  public function getIndicators() {
1462  return $this->mIndicators;
1463  }
1464 
1473  public function addHelpLink( $to, $overrideBaseUrl = false ) {
1474  $this->addModuleStyles( 'mediawiki.helplink' );
1475  $text = $this->msg( 'helppage-top-gethelp' )->escaped();
1476 
1477  if ( $overrideBaseUrl ) {
1478  $helpUrl = $to;
1479  } else {
1480  $toUrlencoded = wfUrlencode( str_replace( ' ', '_', $to ) );
1481  $helpUrl = "https://www.mediawiki.org/wiki/Special:MyLanguage/$toUrlencoded";
1482  }
1483 
1485  'a',
1486  [
1487  'href' => $helpUrl,
1488  'target' => '_blank',
1489  'class' => 'mw-helplink',
1490  ],
1491  $text
1492  );
1493 
1494  $this->setIndicators( [ 'mw-helplink' => $link ] );
1495  }
1496 
1505  public function disallowUserJs() {
1506  $this->reduceAllowedModules(
1509  );
1510 
1511  // Site-wide styles are controlled by a config setting, see T73621
1512  // for background on why. User styles are never allowed.
1513  if ( $this->getConfig()->get( 'AllowSiteCSSOnRestrictedPages' ) ) {
1515  } else {
1517  }
1518  $this->reduceAllowedModules(
1520  $styleOrigin
1521  );
1522  }
1523 
1530  public function getAllowedModules( $type ) {
1532  return min( array_values( $this->mAllowedModules ) );
1533  } else {
1534  return $this->mAllowedModules[$type] ?? ResourceLoaderModule::ORIGIN_ALL;
1535  }
1536  }
1537 
1547  public function reduceAllowedModules( $type, $level ) {
1548  $this->mAllowedModules[$type] = min( $this->getAllowedModules( $type ), $level );
1549  }
1550 
1556  public function prependHTML( $text ) {
1557  $this->mBodytext = $text . $this->mBodytext;
1558  }
1559 
1565  public function addHTML( $text ) {
1566  $this->mBodytext .= $text;
1567  }
1568 
1578  public function addElement( $element, array $attribs = [], $contents = '' ) {
1579  $this->addHTML( Html::element( $element, $attribs, $contents ) );
1580  }
1581 
1585  public function clearHTML() {
1586  $this->mBodytext = '';
1587  }
1588 
1594  public function getHTML() {
1595  return $this->mBodytext;
1596  }
1597 
1605  public function parserOptions( $options = null ) {
1606  if ( $options !== null ) {
1607  wfDeprecated( __METHOD__ . ' with non-null $options', '1.31' );
1608  }
1609 
1610  if ( $options !== null && !empty( $options->isBogus ) ) {
1611  // Someone is trying to set a bogus pre-$wgUser PO. Check if it has
1612  // been changed somehow, and keep it if so.
1613  $anonPO = ParserOptions::newFromAnon();
1614  $anonPO->setAllowUnsafeRawHtml( false );
1615  if ( !$options->matches( $anonPO ) ) {
1616  wfLogWarning( __METHOD__ . ': Setting a changed bogus ParserOptions: ' . wfGetAllCallers( 5 ) );
1617  $options->isBogus = false;
1618  }
1619  }
1620 
1621  if ( !$this->mParserOptions ) {
1622  if ( !$this->getUser()->isSafeToLoad() ) {
1623  // $wgUser isn't unstubbable yet, so don't try to get a
1624  // ParserOptions for it. And don't cache this ParserOptions
1625  // either.
1627  $po->setAllowUnsafeRawHtml( false );
1628  $po->isBogus = true;
1629  if ( $options !== null ) {
1630  $this->mParserOptions = empty( $options->isBogus ) ? $options : null;
1631  }
1632  return $po;
1633  }
1634 
1635  $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
1636  $this->mParserOptions->setAllowUnsafeRawHtml( false );
1637  }
1638 
1639  if ( $options !== null && !empty( $options->isBogus ) ) {
1640  // They're trying to restore the bogus pre-$wgUser PO. Do the right
1641  // thing.
1642  return wfSetVar( $this->mParserOptions, null, true );
1643  } else {
1644  return wfSetVar( $this->mParserOptions, $options );
1645  }
1646  }
1647 
1655  public function setRevisionId( $revid ) {
1656  $val = is_null( $revid ) ? null : intval( $revid );
1657  return wfSetVar( $this->mRevisionId, $val, true );
1658  }
1659 
1665  public function getRevisionId() {
1666  return $this->mRevisionId;
1667  }
1668 
1676  public function setRevisionTimestamp( $timestamp ) {
1677  return wfSetVar( $this->mRevisionTimestamp, $timestamp, true );
1678  }
1679 
1686  public function getRevisionTimestamp() {
1688  }
1689 
1696  public function setFileVersion( $file ) {
1697  $val = null;
1698  if ( $file instanceof File && $file->exists() ) {
1699  $val = [ 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ];
1700  }
1701  return wfSetVar( $this->mFileVersion, $val, true );
1702  }
1703 
1709  public function getFileVersion() {
1710  return $this->mFileVersion;
1711  }
1712 
1719  public function getTemplateIds() {
1720  return $this->mTemplateIds;
1721  }
1722 
1729  public function getFileSearchOptions() {
1730  return $this->mImageTimeKeys;
1731  }
1732 
1745  public function addWikiText( $text, $linestart = true, $interface = true ) {
1746  wfDeprecated( __METHOD__, '1.32' );
1747  $title = $this->getTitle();
1748  if ( !$title ) {
1749  throw new MWException( 'Title is null' );
1750  }
1751  $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/false, $interface );
1752  }
1753 
1770  public function addWikiTextAsInterface(
1771  $text, $linestart = true, Title $title = null
1772  ) {
1773  if ( $title === null ) {
1774  $title = $this->getTitle();
1775  }
1776  if ( !$title ) {
1777  throw new MWException( 'Title is null' );
1778  }
1779  $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/true, /*interface*/true );
1780  }
1781 
1795  public function wrapWikiTextAsInterface(
1796  $wrapperClass, $text
1797  ) {
1798  $this->addWikiTextTitleInternal(
1799  $text, $this->getTitle(),
1800  /*linestart*/true, /*tidy*/true, /*interface*/true,
1801  $wrapperClass
1802  );
1803  }
1804 
1820  public function addWikiTextAsContent(
1821  $text, $linestart = true, Title $title = null
1822  ) {
1823  if ( $title === null ) {
1824  $title = $this->getTitle();
1825  }
1826  if ( !$title ) {
1827  throw new MWException( 'Title is null' );
1828  }
1829  $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/true, /*interface*/false );
1830  }
1831 
1841  public function addWikiTextWithTitle( $text, Title $title, $linestart = true ) {
1842  wfDeprecated( __METHOD__, '1.32' );
1843  $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/false, /*interface*/false );
1844  }
1845 
1856  function addWikiTextTitleTidy( $text, Title $title, $linestart = true ) {
1857  wfDeprecated( __METHOD__, '1.32' );
1858  $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/true, /*interface*/false );
1859  }
1860 
1869  public function addWikiTextTidy( $text, $linestart = true ) {
1870  wfDeprecated( __METHOD__, '1.32' );
1871  $title = $this->getTitle();
1872  if ( !$title ) {
1873  throw new MWException( 'Title is null' );
1874  }
1875  $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/true, /*interface*/false );
1876  }
1877 
1897  public function addWikiTextTitle( $text, Title $title, $linestart,
1898  $tidy = false, $interface = false
1899  ) {
1900  wfDeprecated( __METHOD__, '1.32' );
1901  return $this->addWikiTextTitleInternal( $text, $title, $linestart, $tidy, $interface );
1902  }
1903 
1920  private function addWikiTextTitleInternal(
1921  $text, Title $title, $linestart, $tidy, $interface, $wrapperClass = null
1922  ) {
1923  if ( !$tidy ) {
1924  wfDeprecated( 'disabling tidy', '1.32' );
1925  }
1926 
1927  $parserOutput = $this->parseInternal(
1928  $text, $title, $linestart, $tidy, $interface, /*language*/null
1929  );
1930 
1931  $this->addParserOutput( $parserOutput, [
1932  'enableSectionEditLinks' => false,
1933  'wrapperDivClass' => $wrapperClass ?? '',
1934  ] );
1935  }
1936 
1945  public function addParserOutputMetadata( ParserOutput $parserOutput ) {
1946  $this->mLanguageLinks =
1947  array_merge( $this->mLanguageLinks, $parserOutput->getLanguageLinks() );
1948  $this->addCategoryLinks( $parserOutput->getCategories() );
1949  $this->setIndicators( $parserOutput->getIndicators() );
1950  $this->mNewSectionLink = $parserOutput->getNewSection();
1951  $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
1952 
1953  if ( !$parserOutput->isCacheable() ) {
1954  $this->enableClientCache( false );
1955  }
1956  $this->mNoGallery = $parserOutput->getNoGallery();
1957  $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
1958  $this->addModules( $parserOutput->getModules() );
1959  $this->addModuleStyles( $parserOutput->getModuleStyles() );
1960  $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1961  $this->mPreventClickjacking = $this->mPreventClickjacking
1962  || $parserOutput->preventClickjacking();
1963 
1964  // Template versioning...
1965  foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
1966  if ( isset( $this->mTemplateIds[$ns] ) ) {
1967  $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
1968  } else {
1969  $this->mTemplateIds[$ns] = $dbks;
1970  }
1971  }
1972  // File versioning...
1973  foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) {
1974  $this->mImageTimeKeys[$dbk] = $data;
1975  }
1976 
1977  // Hooks registered in the object
1978  $parserOutputHooks = $this->getConfig()->get( 'ParserOutputHooks' );
1979  foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
1980  list( $hookName, $data ) = $hookInfo;
1981  if ( isset( $parserOutputHooks[$hookName] ) ) {
1982  $parserOutputHooks[$hookName]( $this, $parserOutput, $data );
1983  }
1984  }
1985 
1986  // Enable OOUI if requested via ParserOutput
1987  if ( $parserOutput->getEnableOOUI() ) {
1988  $this->enableOOUI();
1989  }
1990 
1991  // Include parser limit report
1992  if ( !$this->limitReportJSData ) {
1993  $this->limitReportJSData = $parserOutput->getLimitReportJSData();
1994  }
1995 
1996  // Link flags are ignored for now, but may in the future be
1997  // used to mark individual language links.
1998  $linkFlags = [];
1999  // Avoid PHP 7.1 warning of passing $this by reference
2000  $outputPage = $this;
2001  Hooks::run( 'LanguageLinks', [ $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ] );
2002  Hooks::runWithoutAbort( 'OutputPageParserOutput', [ &$outputPage, $parserOutput ] );
2003 
2004  // This check must be after 'OutputPageParserOutput' runs in addParserOutputMetadata
2005  // so that extensions may modify ParserOutput to toggle TOC.
2006  // This cannot be moved to addParserOutputText because that is not
2007  // called by EditPage for Preview.
2008  if ( $parserOutput->getTOCHTML() ) {
2009  $this->mEnableTOC = true;
2010  }
2011  }
2012 
2021  public function addParserOutputContent( ParserOutput $parserOutput, $poOptions = [] ) {
2022  $this->addParserOutputText( $parserOutput, $poOptions );
2023 
2024  $this->addModules( $parserOutput->getModules() );
2025  $this->addModuleStyles( $parserOutput->getModuleStyles() );
2026 
2027  $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
2028  }
2029 
2037  public function addParserOutputText( ParserOutput $parserOutput, $poOptions = [] ) {
2038  $text = $parserOutput->getText( $poOptions );
2039  // Avoid PHP 7.1 warning of passing $this by reference
2040  $outputPage = $this;
2041  Hooks::runWithoutAbort( 'OutputPageBeforeHTML', [ &$outputPage, &$text ] );
2042  $this->addHTML( $text );
2043  }
2044 
2051  function addParserOutput( ParserOutput $parserOutput, $poOptions = [] ) {
2052  $this->addParserOutputMetadata( $parserOutput );
2053  $this->addParserOutputText( $parserOutput, $poOptions );
2054  }
2055 
2061  public function addTemplate( &$template ) {
2062  $this->addHTML( $template->getHTML() );
2063  }
2064 
2083  public function parse( $text, $linestart = true, $interface = false, $language = null ) {
2084  wfDeprecated( __METHOD__, '1.33' );
2085  return $this->parseInternal(
2086  $text, $this->getTitle(), $linestart, /*tidy*/false, $interface, $language
2087  )->getText( [
2088  'enableSectionEditLinks' => false,
2089  ] );
2090  }
2091 
2103  public function parseAsContent( $text, $linestart = true ) {
2104  return $this->parseInternal(
2105  $text, $this->getTitle(), $linestart, /*tidy*/true, /*interface*/false, /*language*/null
2106  )->getText( [
2107  'enableSectionEditLinks' => false,
2108  'wrapperDivClass' => ''
2109  ] );
2110  }
2111 
2124  public function parseAsInterface( $text, $linestart = true ) {
2125  return $this->parseInternal(
2126  $text, $this->getTitle(), $linestart, /*tidy*/true, /*interface*/true, /*language*/null
2127  )->getText( [
2128  'enableSectionEditLinks' => false,
2129  'wrapperDivClass' => ''
2130  ] );
2131  }
2132 
2147  public function parseInlineAsInterface( $text, $linestart = true ) {
2149  $this->parseAsInterface( $text, $linestart )
2150  );
2151  }
2152 
2166  public function parseInline( $text, $linestart = true, $interface = false ) {
2167  wfDeprecated( __METHOD__, '1.33' );
2168  $parsed = $this->parseInternal(
2169  $text, $this->getTitle(), $linestart, /*tidy*/false, $interface, /*language*/null
2170  )->getText( [
2171  'enableSectionEditLinks' => false,
2172  'wrapperDivClass' => '', /* no wrapper div */
2173  ] );
2174  return Parser::stripOuterParagraph( $parsed );
2175  }
2176 
2191  private function parseInternal( $text, $title, $linestart, $tidy, $interface, $language ) {
2192  if ( is_null( $title ) ) {
2193  throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
2194  }
2195 
2196  $popts = $this->parserOptions();
2197  $oldTidy = $popts->setTidy( $tidy );
2198  $oldInterface = $popts->setInterfaceMessage( (bool)$interface );
2199 
2200  if ( $language !== null ) {
2201  $oldLang = $popts->setTargetLanguage( $language );
2202  }
2203 
2204  $parserOutput = MediaWikiServices::getInstance()->getParser()->getFreshParser()->parse(
2205  $text, $title, $popts,
2206  $linestart, true, $this->mRevisionId
2207  );
2208 
2209  $popts->setTidy( $oldTidy );
2210  $popts->setInterfaceMessage( $oldInterface );
2211 
2212  if ( $language !== null ) {
2213  $popts->setTargetLanguage( $oldLang );
2214  }
2215 
2216  return $parserOutput;
2217  }
2218 
2224  public function setCdnMaxage( $maxage ) {
2225  $this->mCdnMaxage = min( $maxage, $this->mCdnMaxageLimit );
2226  }
2227 
2237  public function lowerCdnMaxage( $maxage ) {
2238  $this->mCdnMaxageLimit = min( $maxage, $this->mCdnMaxageLimit );
2239  $this->setCdnMaxage( $this->mCdnMaxage );
2240  }
2241 
2254  public function adaptCdnTTL( $mtime, $minTTL = 0, $maxTTL = 0 ) {
2255  $minTTL = $minTTL ?: IExpiringStore::TTL_MINUTE;
2256  $maxTTL = $maxTTL ?: $this->getConfig()->get( 'CdnMaxAge' );
2257 
2258  if ( $mtime === null || $mtime === false ) {
2259  return $minTTL; // entity does not exist
2260  }
2261 
2262  $age = MWTimestamp::time() - wfTimestamp( TS_UNIX, $mtime );
2263  $adaptiveTTL = max( 0.9 * $age, $minTTL );
2264  $adaptiveTTL = min( $adaptiveTTL, $maxTTL );
2265 
2266  $this->lowerCdnMaxage( (int)$adaptiveTTL );
2267  }
2268 
2276  public function enableClientCache( $state ) {
2277  return wfSetVar( $this->mEnableClientCache, $state );
2278  }
2279 
2285  function getCacheVaryCookies() {
2286  if ( self::$cacheVaryCookies === null ) {
2287  $config = $this->getConfig();
2288  self::$cacheVaryCookies = array_values( array_unique( array_merge(
2289  SessionManager::singleton()->getVaryCookies(),
2290  [
2291  'forceHTTPS',
2292  ],
2293  $config->get( 'CacheVaryCookies' )
2294  ) ) );
2295  Hooks::run( 'GetCacheVaryCookies', [ $this, &self::$cacheVaryCookies ] );
2296  }
2297  return self::$cacheVaryCookies;
2298  }
2299 
2307  $request = $this->getRequest();
2308  foreach ( $this->getCacheVaryCookies() as $cookieName ) {
2309  if ( $request->getCookie( $cookieName, '', '' ) !== '' ) {
2310  wfDebug( __METHOD__ . ": found $cookieName\n" );
2311  return true;
2312  }
2313  }
2314  wfDebug( __METHOD__ . ": no cache-varying cookies found\n" );
2315  return false;
2316  }
2317 
2326  public function addVaryHeader( $header, array $option = null ) {
2327  if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
2328  $this->mVaryHeader[$header] = [];
2329  }
2330  if ( !is_array( $option ) ) {
2331  $option = [];
2332  }
2333  $this->mVaryHeader[$header] =
2334  array_unique( array_merge( $this->mVaryHeader[$header], $option ) );
2335  }
2336 
2343  public function getVaryHeader() {
2344  // If we vary on cookies, let's make sure it's always included here too.
2345  if ( $this->getCacheVaryCookies() ) {
2346  $this->addVaryHeader( 'Cookie' );
2347  }
2348 
2349  foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2350  $this->addVaryHeader( $header, $options );
2351  }
2352  return 'Vary: ' . implode( ', ', array_keys( $this->mVaryHeader ) );
2353  }
2354 
2360  public function addLinkHeader( $header ) {
2361  $this->mLinkHeader[] = $header;
2362  }
2363 
2369  public function getLinkHeader() {
2370  if ( !$this->mLinkHeader ) {
2371  return false;
2372  }
2373 
2374  return 'Link: ' . implode( ',', $this->mLinkHeader );
2375  }
2376 
2384  public function getKeyHeader() {
2385  wfDeprecated( '$wgUseKeyHeader', '1.32' );
2386 
2387  $cvCookies = $this->getCacheVaryCookies();
2388 
2389  $cookiesOption = [];
2390  foreach ( $cvCookies as $cookieName ) {
2391  $cookiesOption[] = 'param=' . $cookieName;
2392  }
2393  $this->addVaryHeader( 'Cookie', $cookiesOption );
2394 
2395  foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2396  $this->addVaryHeader( $header, $options );
2397  }
2398 
2399  $headers = [];
2400  foreach ( $this->mVaryHeader as $header => $option ) {
2401  $newheader = $header;
2402  if ( is_array( $option ) && count( $option ) > 0 ) {
2403  $newheader .= ';' . implode( ';', $option );
2404  }
2405  $headers[] = $newheader;
2406  }
2407  $key = 'Key: ' . implode( ',', $headers );
2408 
2409  return $key;
2410  }
2411 
2419  private function addAcceptLanguage() {
2420  $title = $this->getTitle();
2421  if ( !$title instanceof Title ) {
2422  return;
2423  }
2424 
2425  $lang = $title->getPageLanguage();
2426  if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
2427  $variants = $lang->getVariants();
2428  $aloption = [];
2429  foreach ( $variants as $variant ) {
2430  if ( $variant === $lang->getCode() ) {
2431  continue;
2432  }
2433 
2434  // XXX Note that this code is not strictly correct: we
2435  // do a case-insensitive match in
2436  // LanguageConverter::getHeaderVariant() while the
2437  // (abandoned, draft) spec for the `Key` header only
2438  // allows case-sensitive matches. To match the logic
2439  // in LanguageConverter::getHeaderVariant() we should
2440  // also be looking at fallback variants and deprecated
2441  // mediawiki-internal codes, as well as BCP 47
2442  // normalized forms.
2443 
2444  $aloption[] = "substr=$variant";
2445 
2446  // IE and some other browsers use BCP 47 standards in their Accept-Language header,
2447  // like "zh-CN" or "zh-Hant". We should handle these too.
2448  $variantBCP47 = LanguageCode::bcp47( $variant );
2449  if ( $variantBCP47 !== $variant ) {
2450  $aloption[] = "substr=$variantBCP47";
2451  }
2452  }
2453  $this->addVaryHeader( 'Accept-Language', $aloption );
2454  }
2455  }
2456 
2467  public function preventClickjacking( $enable = true ) {
2468  $this->mPreventClickjacking = $enable;
2469  }
2470 
2476  public function allowClickjacking() {
2477  $this->mPreventClickjacking = false;
2478  }
2479 
2486  public function getPreventClickjacking() {
2488  }
2489 
2497  public function getFrameOptions() {
2498  $config = $this->getConfig();
2499  if ( $config->get( 'BreakFrames' ) ) {
2500  return 'DENY';
2501  } elseif ( $this->mPreventClickjacking && $config->get( 'EditPageFrameOptions' ) ) {
2502  return $config->get( 'EditPageFrameOptions' );
2503  }
2504  return false;
2505  }
2506 
2513  private function getOriginTrials() {
2514  $config = $this->getConfig();
2515 
2516  return $config->get( 'OriginTrials' );
2517  }
2518 
2519  private function getReportTo() {
2520  $config = $this->getConfig();
2521 
2522  $expiry = $config->get( 'ReportToExpiry' );
2523 
2524  if ( !$expiry ) {
2525  return false;
2526  }
2527 
2528  $endpoints = $config->get( 'ReportToEndpoints' );
2529 
2530  if ( !$endpoints ) {
2531  return false;
2532  }
2533 
2534  $output = [ 'max_age' => $expiry, 'endpoints' => [] ];
2535 
2536  foreach ( $endpoints as $endpoint ) {
2537  $output['endpoints'][] = [ 'url' => $endpoint ];
2538  }
2539 
2540  return json_encode( $output, JSON_UNESCAPED_SLASHES );
2541  }
2542 
2543  private function getFeaturePolicyReportOnly() {
2544  $config = $this->getConfig();
2545 
2546  $features = $config->get( 'FeaturePolicyReportOnly' );
2547  return implode( ';', $features );
2548  }
2549 
2553  public function sendCacheControl() {
2554  $response = $this->getRequest()->response();
2555  $config = $this->getConfig();
2556 
2557  $this->addVaryHeader( 'Cookie' );
2558  $this->addAcceptLanguage();
2559 
2560  # don't serve compressed data to clients who can't handle it
2561  # maintain different caches for logged-in users and non-logged in ones
2562  $response->header( $this->getVaryHeader() );
2563 
2564  if ( $config->get( 'UseKeyHeader' ) ) {
2565  $response->header( $this->getKeyHeader() );
2566  }
2567 
2568  if ( $this->mEnableClientCache ) {
2569  if (
2570  $config->get( 'UseCdn' ) &&
2571  !$response->hasCookies() &&
2572  !SessionManager::getGlobalSession()->isPersistent() &&
2573  !$this->isPrintable() &&
2574  $this->mCdnMaxage != 0 &&
2575  !$this->haveCacheVaryCookies()
2576  ) {
2577  if ( $config->get( 'UseESI' ) ) {
2578  wfDeprecated( '$wgUseESI = true', '1.33' );
2579  # We'll purge the proxy cache explicitly, but require end user agents
2580  # to revalidate against the proxy on each visit.
2581  # Surrogate-Control controls our CDN, Cache-Control downstream caches
2582  wfDebug( __METHOD__ .
2583  ": proxy caching with ESI; {$this->mLastModified} **", 'private' );
2584  # start with a shorter timeout for initial testing
2585  # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
2586  $response->header(
2587  "Surrogate-Control: max-age={$config->get( 'CdnMaxAge' )}" .
2588  "+{$this->mCdnMaxage}, content=\"ESI/1.0\""
2589  );
2590  $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
2591  } else {
2592  # We'll purge the proxy cache for anons explicitly, but require end user agents
2593  # to revalidate against the proxy on each visit.
2594  # IMPORTANT! The CDN needs to replace the Cache-Control header with
2595  # Cache-Control: s-maxage=0, must-revalidate, max-age=0
2596  wfDebug( __METHOD__ .
2597  ": local proxy caching; {$this->mLastModified} **", 'private' );
2598  # start with a shorter timeout for initial testing
2599  # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
2600  $response->header( "Cache-Control: " .
2601  "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" );
2602  }
2603  } else {
2604  # We do want clients to cache if they can, but they *must* check for updates
2605  # on revisiting the page.
2606  wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **", 'private' );
2607  $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2608  $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
2609  }
2610  if ( $this->mLastModified ) {
2611  $response->header( "Last-Modified: {$this->mLastModified}" );
2612  }
2613  } else {
2614  wfDebug( __METHOD__ . ": no caching **", 'private' );
2615 
2616  # In general, the absence of a last modified header should be enough to prevent
2617  # the client from using its cache. We send a few other things just to make sure.
2618  $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2619  $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
2620  $response->header( 'Pragma: no-cache' );
2621  }
2622  }
2623 
2629  public function loadSkinModules( $sk ) {
2630  foreach ( $sk->getDefaultModules() as $group => $modules ) {
2631  if ( $group === 'styles' ) {
2632  foreach ( $modules as $key => $moduleMembers ) {
2633  $this->addModuleStyles( $moduleMembers );
2634  }
2635  } else {
2636  $this->addModules( $modules );
2637  }
2638  }
2639  }
2640 
2651  public function output( $return = false ) {
2652  if ( $this->mDoNothing ) {
2653  return $return ? '' : null;
2654  }
2655 
2656  $response = $this->getRequest()->response();
2657  $config = $this->getConfig();
2658 
2659  if ( $this->mRedirect != '' ) {
2660  # Standards require redirect URLs to be absolute
2661  $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT );
2662 
2663  $redirect = $this->mRedirect;
2665 
2666  if ( Hooks::run( "BeforePageRedirect", [ $this, &$redirect, &$code ] ) ) {
2667  if ( $code == '301' || $code == '303' ) {
2668  if ( !$config->get( 'DebugRedirects' ) ) {
2669  $response->statusHeader( $code );
2670  }
2671  $this->mLastModified = wfTimestamp( TS_RFC2822 );
2672  }
2673  if ( $config->get( 'VaryOnXFP' ) ) {
2674  $this->addVaryHeader( 'X-Forwarded-Proto' );
2675  }
2676  $this->sendCacheControl();
2677 
2678  $response->header( "Content-Type: text/html; charset=utf-8" );
2679  if ( $config->get( 'DebugRedirects' ) ) {
2680  $url = htmlspecialchars( $redirect );
2681  print "<!DOCTYPE html>\n<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
2682  print "<p>Location: <a href=\"$url\">$url</a></p>\n";
2683  print "</body>\n</html>\n";
2684  } else {
2685  $response->header( 'Location: ' . $redirect );
2686  }
2687  }
2688 
2689  return $return ? '' : null;
2690  } elseif ( $this->mStatusCode ) {
2691  $response->statusHeader( $this->mStatusCode );
2692  }
2693 
2694  # Buffer output; final headers may depend on later processing
2695  ob_start();
2696 
2697  $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
2698  $response->header( 'Content-language: ' .
2699  MediaWikiServices::getInstance()->getContentLanguage()->getHtmlCode() );
2700 
2701  $linkHeader = $this->getLinkHeader();
2702  if ( $linkHeader ) {
2703  $response->header( $linkHeader );
2704  }
2705 
2706  // Prevent framing, if requested
2707  $frameOptions = $this->getFrameOptions();
2708  if ( $frameOptions ) {
2709  $response->header( "X-Frame-Options: $frameOptions" );
2710  }
2711 
2712  $originTrials = $this->getOriginTrials();
2713  foreach ( $originTrials as $originTrial ) {
2714  $response->header( "Origin-Trial: $originTrial", false );
2715  }
2716 
2717  $reportTo = $this->getReportTo();
2718  if ( $reportTo ) {
2719  $response->header( "Report-To: $reportTo" );
2720  }
2721 
2722  $featurePolicyReportOnly = $this->getFeaturePolicyReportOnly();
2723  if ( $featurePolicyReportOnly ) {
2724  $response->header( "Feature-Policy-Report-Only: $featurePolicyReportOnly" );
2725  }
2726 
2728 
2729  if ( $this->mArticleBodyOnly ) {
2730  echo $this->mBodytext;
2731  } else {
2732  // Enable safe mode if requested (T152169)
2733  if ( $this->getRequest()->getBool( 'safemode' ) ) {
2734  $this->disallowUserJs();
2735  }
2736 
2737  $sk = $this->getSkin();
2738  $this->loadSkinModules( $sk );
2739 
2740  MWDebug::addModules( $this );
2741 
2742  // Avoid PHP 7.1 warning of passing $this by reference
2743  $outputPage = $this;
2744  // Hook that allows last minute changes to the output page, e.g.
2745  // adding of CSS or Javascript by extensions.
2746  Hooks::runWithoutAbort( 'BeforePageDisplay', [ &$outputPage, &$sk ] );
2747 
2748  try {
2749  $sk->outputPage();
2750  } catch ( Exception $e ) {
2751  ob_end_clean(); // bug T129657
2752  throw $e;
2753  }
2754  }
2755 
2756  try {
2757  // This hook allows last minute changes to final overall output by modifying output buffer
2758  Hooks::runWithoutAbort( 'AfterFinalPageOutput', [ $this ] );
2759  } catch ( Exception $e ) {
2760  ob_end_clean(); // bug T129657
2761  throw $e;
2762  }
2763 
2764  $this->sendCacheControl();
2765 
2766  if ( $return ) {
2767  return ob_get_clean();
2768  } else {
2769  ob_end_flush();
2770  return null;
2771  }
2772  }
2773 
2784  public function prepareErrorPage( $pageTitle, $htmlTitle = false ) {
2785  $this->setPageTitle( $pageTitle );
2786  if ( $htmlTitle !== false ) {
2787  $this->setHTMLTitle( $htmlTitle );
2788  }
2789  $this->setRobotPolicy( 'noindex,nofollow' );
2790  $this->setArticleRelated( false );
2791  $this->enableClientCache( false );
2792  $this->mRedirect = '';
2793  $this->clearSubtitle();
2794  $this->clearHTML();
2795  }
2796 
2809  public function showErrorPage( $title, $msg, $params = [] ) {
2810  if ( !$title instanceof Message ) {
2811  $title = $this->msg( $title );
2812  }
2813 
2814  $this->prepareErrorPage( $title );
2815 
2816  if ( $msg instanceof Message ) {
2817  if ( $params !== [] ) {
2818  trigger_error( 'Argument ignored: $params. The message parameters argument '
2819  . 'is discarded when the $msg argument is a Message object instead of '
2820  . 'a string.', E_USER_NOTICE );
2821  }
2822  $this->addHTML( $msg->parseAsBlock() );
2823  } else {
2824  $this->addWikiMsgArray( $msg, $params );
2825  }
2826 
2827  $this->returnToMain();
2828  }
2829 
2836  public function showPermissionsErrorPage( array $errors, $action = null ) {
2837  foreach ( $errors as $key => $error ) {
2838  $errors[$key] = (array)$error;
2839  }
2840 
2841  // For some action (read, edit, create and upload), display a "login to do this action"
2842  // error if all of the following conditions are met:
2843  // 1. the user is not logged in
2844  // 2. the only error is insufficient permissions (i.e. no block or something else)
2845  // 3. the error can be avoided simply by logging in
2846  if ( in_array( $action, [ 'read', 'edit', 'createpage', 'createtalk', 'upload' ] )
2847  && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
2848  && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
2849  && ( User::groupHasPermission( 'user', $action )
2850  || User::groupHasPermission( 'autoconfirmed', $action ) )
2851  ) {
2852  $displayReturnto = null;
2853 
2854  # Due to T34276, if a user does not have read permissions,
2855  # $this->getTitle() will just give Special:Badtitle, which is
2856  # not especially useful as a returnto parameter. Use the title
2857  # from the request instead, if there was one.
2858  $request = $this->getRequest();
2859  $returnto = Title::newFromText( $request->getVal( 'title', '' ) );
2860  if ( $action == 'edit' ) {
2861  $msg = 'whitelistedittext';
2862  $displayReturnto = $returnto;
2863  } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
2864  $msg = 'nocreatetext';
2865  } elseif ( $action == 'upload' ) {
2866  $msg = 'uploadnologintext';
2867  } else { # Read
2868  $msg = 'loginreqpagetext';
2869  $displayReturnto = Title::newMainPage();
2870  }
2871 
2872  $query = [];
2873 
2874  if ( $returnto ) {
2875  $query['returnto'] = $returnto->getPrefixedText();
2876 
2877  if ( !$request->wasPosted() ) {
2878  $returntoquery = $request->getValues();
2879  unset( $returntoquery['title'] );
2880  unset( $returntoquery['returnto'] );
2881  unset( $returntoquery['returntoquery'] );
2882  $query['returntoquery'] = wfArrayToCgi( $returntoquery );
2883  }
2884  }
2885  $title = SpecialPage::getTitleFor( 'Userlogin' );
2886  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
2887  $loginUrl = $title->getLinkURL( $query, false, PROTO_RELATIVE );
2888  $loginLink = $linkRenderer->makeKnownLink(
2889  $title,
2890  $this->msg( 'loginreqlink' )->text(),
2891  [],
2892  $query
2893  );
2894 
2895  $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) );
2896  $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->params( $loginUrl )->parse() );
2897 
2898  # Don't return to a page the user can't read otherwise
2899  # we'll end up in a pointless loop
2900  if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) {
2901  $this->returnToMain( null, $displayReturnto );
2902  }
2903  } else {
2904  $this->prepareErrorPage( $this->msg( 'permissionserrors' ) );
2905  $this->addWikiTextAsInterface( $this->formatPermissionsErrorMessage( $errors, $action ) );
2906  }
2907  }
2908 
2915  public function versionRequired( $version ) {
2916  $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) );
2917 
2918  $this->addWikiMsg( 'versionrequiredtext', $version );
2919  $this->returnToMain();
2920  }
2921 
2929  public function formatPermissionsErrorMessage( array $errors, $action = null ) {
2930  if ( $action == null ) {
2931  $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
2932  } else {
2933  $action_desc = $this->msg( "action-$action" )->plain();
2934  $text = $this->msg(
2935  'permissionserrorstext-withaction',
2936  count( $errors ),
2937  $action_desc
2938  )->plain() . "\n\n";
2939  }
2940 
2941  if ( count( $errors ) > 1 ) {
2942  $text .= '<ul class="permissions-errors">' . "\n";
2943 
2944  foreach ( $errors as $error ) {
2945  $text .= '<li>';
2946  $text .= $this->msg( ...$error )->plain();
2947  $text .= "</li>\n";
2948  }
2949  $text .= '</ul>';
2950  } else {
2951  $text .= "<div class=\"permissions-errors\">\n" .
2952  $this->msg( ...reset( $errors ) )->plain() .
2953  "\n</div>";
2954  }
2955 
2956  return $text;
2957  }
2958 
2968  public function showLagWarning( $lag ) {
2969  $config = $this->getConfig();
2970  if ( $lag >= $config->get( 'SlaveLagWarning' ) ) {
2971  $lag = floor( $lag ); // floor to avoid nano seconds to display
2972  $message = $lag < $config->get( 'SlaveLagCritical' )
2973  ? 'lag-warn-normal'
2974  : 'lag-warn-high';
2975  $wrap = Html::rawElement( 'div', [ 'class' => "mw-{$message}" ], "\n$1\n" );
2976  $this->wrapWikiMsg( "$wrap\n", [ $message, $this->getLanguage()->formatNum( $lag ) ] );
2977  }
2978  }
2979 
2986  public function showFatalError( $message ) {
2987  $this->prepareErrorPage( $this->msg( 'internalerror' ) );
2988 
2989  $this->addHTML( $message );
2990  }
2991 
3000  public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) {
3001  $linkRenderer = MediaWikiServices::getInstance()
3002  ->getLinkRendererFactory()->createFromLegacyOptions( $options );
3003  $link = $this->msg( 'returnto' )->rawParams(
3004  $linkRenderer->makeLink( $title, $text, [], $query ) )->escaped();
3005  $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
3006  }
3007 
3016  public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
3017  if ( $returnto == null ) {
3018  $returnto = $this->getRequest()->getText( 'returnto' );
3019  }
3020 
3021  if ( $returntoquery == null ) {
3022  $returntoquery = $this->getRequest()->getText( 'returntoquery' );
3023  }
3024 
3025  if ( $returnto === '' ) {
3026  $returnto = Title::newMainPage();
3027  }
3028 
3029  if ( is_object( $returnto ) ) {
3030  $titleObj = $returnto;
3031  } else {
3032  $titleObj = Title::newFromText( $returnto );
3033  }
3034  // We don't want people to return to external interwiki. That
3035  // might potentially be used as part of a phishing scheme
3036  if ( !is_object( $titleObj ) || $titleObj->isExternal() ) {
3037  $titleObj = Title::newMainPage();
3038  }
3039 
3040  $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) );
3041  }
3042 
3043  private function getRlClientContext() {
3044  if ( !$this->rlClientContext ) {
3046  [], // modules; not relevant
3047  $this->getLanguage()->getCode(),
3048  $this->getSkin()->getSkinName(),
3049  $this->getUser()->isLoggedIn() ? $this->getUser()->getName() : null,
3050  null, // version; not relevant
3052  null, // only; not relevant
3053  $this->isPrintable(),
3054  $this->getRequest()->getBool( 'handheld' )
3055  );
3056  $this->rlClientContext = new ResourceLoaderContext(
3057  $this->getResourceLoader(),
3058  new FauxRequest( $query )
3059  );
3060  if ( $this->contentOverrideCallbacks ) {
3061  $this->rlClientContext = new DerivativeResourceLoaderContext( $this->rlClientContext );
3062  $this->rlClientContext->setContentOverrideCallback( function ( Title $title ) {
3063  foreach ( $this->contentOverrideCallbacks as $callback ) {
3064  $content = $callback( $title );
3065  if ( $content !== null ) {
3067  if ( strpos( $text, '</script>' ) !== false ) {
3068  // Proactively replace this so that we can display a message
3069  // to the user, instead of letting it go to Html::inlineScript(),
3070  // where it would be considered a server-side issue.
3071  $titleFormatted = $title->getPrefixedText();
3073  Xml::encodeJsCall( 'mw.log.error', [
3074  "Cannot preview $titleFormatted due to script-closing tag."
3075  ] )
3076  );
3077  }
3078  return $content;
3079  }
3080  }
3081  return null;
3082  } );
3083  }
3084  }
3085  return $this->rlClientContext;
3086  }
3087 
3099  public function getRlClient() {
3100  if ( !$this->rlClient ) {
3101  $context = $this->getRlClientContext();
3102  $rl = $this->getResourceLoader();
3103  $this->addModules( [
3104  'user',
3105  'user.options',
3106  'user.tokens',
3107  ] );
3108  $this->addModuleStyles( [
3109  'site.styles',
3110  'noscript',
3111  'user.styles',
3112  ] );
3113  $this->getSkin()->setupSkinUserCss( $this );
3114 
3115  // Prepare exempt modules for buildExemptModules()
3116  $exemptGroups = [ 'site' => [], 'noscript' => [], 'private' => [], 'user' => [] ];
3117  $exemptStates = [];
3118  $moduleStyles = $this->getModuleStyles( /*filter*/ true );
3119 
3120  // Preload getTitleInfo for isKnownEmpty calls below and in ResourceLoaderClientHtml
3121  // Separate user-specific batch for improved cache-hit ratio.
3122  $userBatch = [ 'user.styles', 'user' ];
3123  $siteBatch = array_diff( $moduleStyles, $userBatch );
3124  $dbr = wfGetDB( DB_REPLICA );
3127 
3128  // Filter out modules handled by buildExemptModules()
3129  $moduleStyles = array_filter( $moduleStyles,
3130  function ( $name ) use ( $rl, $context, &$exemptGroups, &$exemptStates ) {
3131  $module = $rl->getModule( $name );
3132  if ( $module ) {
3133  $group = $module->getGroup();
3134  if ( isset( $exemptGroups[$group] ) ) {
3135  $exemptStates[$name] = 'ready';
3136  if ( !$module->isKnownEmpty( $context ) ) {
3137  // E.g. Don't output empty <styles>
3138  $exemptGroups[$group][] = $name;
3139  }
3140  return false;
3141  }
3142  }
3143  return true;
3144  }
3145  );
3146  $this->rlExemptStyleModules = $exemptGroups;
3147 
3149  'target' => $this->getTarget(),
3150  'nonce' => $this->getCSPNonce(),
3151  // When 'safemode', disallowUserJs(), or reduceAllowedModules() is used
3152  // to only restrict modules to ORIGIN_CORE (ie. disallow ORIGIN_USER), the list of
3153  // modules enqueud for loading on this page is filtered to just those.
3154  // However, to make sure we also apply the restriction to dynamic dependencies and
3155  // lazy-loaded modules at run-time on the client-side, pass 'safemode' down to the
3156  // StartupModule so that the client-side registry will not contain any restricted
3157  // modules either. (T152169, T185303)
3158  'safemode' => ( $this->getAllowedModules( ResourceLoaderModule::TYPE_COMBINED )
3160  ) ? '1' : null,
3161  ] );
3162  $rlClient->setConfig( $this->getJSVars() );
3163  $rlClient->setModules( $this->getModules( /*filter*/ true ) );
3164  $rlClient->setModuleStyles( $moduleStyles );
3165  $rlClient->setExemptStates( $exemptStates );
3166  $this->rlClient = $rlClient;
3167  }
3168  return $this->rlClient;
3169  }
3170 
3176  public function headElement( Skin $sk, $includeStyle = true ) {
3177  $userdir = $this->getLanguage()->getDir();
3178  $sitedir = MediaWikiServices::getInstance()->getContentLanguage()->getDir();
3179 
3180  $pieces = [];
3182  $this->getRlClient()->getDocumentAttributes(),
3184  ) );
3185  $pieces[] = Html::openElement( 'head' );
3186 
3187  if ( $this->getHTMLTitle() == '' ) {
3188  $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() );
3189  }
3190 
3191  if ( !Html::isXmlMimeType( $this->getConfig()->get( 'MimeType' ) ) ) {
3192  // Add <meta charset="UTF-8">
3193  // This should be before <title> since it defines the charset used by
3194  // text including the text inside <title>.
3195  // The spec recommends defining XHTML5's charset using the XML declaration
3196  // instead of meta.
3197  // Our XML declaration is output by Html::htmlHeader.
3198  // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type
3199  // https://html.spec.whatwg.org/multipage/semantics.html#charset
3200  $pieces[] = Html::element( 'meta', [ 'charset' => 'UTF-8' ] );
3201  }
3202 
3203  $pieces[] = Html::element( 'title', null, $this->getHTMLTitle() );
3204  $pieces[] = $this->getRlClient()->getHeadHtml();
3205  $pieces[] = $this->buildExemptModules();
3206  $pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) );
3207  $pieces = array_merge( $pieces, array_values( $this->mHeadItems ) );
3208 
3209  // Use an IE conditional comment to serve the script only to old IE
3210  $pieces[] = '<!--[if lt IE 9]>' .
3213  $this->getResourceLoader(),
3214  new FauxRequest( [] )
3215  ),
3216  [ 'html5shiv' ],
3218  [ 'sync' => true ],
3219  $this->getCSPNonce()
3220  ) .
3221  '<![endif]-->';
3222 
3223  $pieces[] = Html::closeElement( 'head' );
3224 
3225  $bodyClasses = $this->mAdditionalBodyClasses;
3226  $bodyClasses[] = 'mediawiki';
3227 
3228  # Classes for LTR/RTL directionality support
3229  $bodyClasses[] = $userdir;
3230  $bodyClasses[] = "sitedir-$sitedir";
3231 
3232  $underline = $this->getUser()->getOption( 'underline' );
3233  if ( $underline < 2 ) {
3234  // The following classes can be used here:
3235  // * mw-underline-always
3236  // * mw-underline-never
3237  $bodyClasses[] = 'mw-underline-' . ( $underline ? 'always' : 'never' );
3238  }
3239 
3240  if ( $this->getLanguage()->capitalizeAllNouns() ) {
3241  # A <body> class is probably not the best way to do this . . .
3242  $bodyClasses[] = 'capitalize-all-nouns';
3243  }
3244 
3245  // Parser feature migration class
3246  // The idea is that this will eventually be removed, after the wikitext
3247  // which requires it is cleaned up.
3248  $bodyClasses[] = 'mw-hide-empty-elt';
3249 
3250  $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
3251  $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
3252  $bodyClasses[] =
3253  'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
3254 
3255  $bodyAttrs = [];
3256  // While the implode() is not strictly needed, it's used for backwards compatibility
3257  // (this used to be built as a string and hooks likely still expect that).
3258  $bodyAttrs['class'] = implode( ' ', $bodyClasses );
3259 
3260  // Allow skins and extensions to add body attributes they need
3261  $sk->addToBodyAttributes( $this, $bodyAttrs );
3262  Hooks::run( 'OutputPageBodyAttributes', [ $this, $sk, &$bodyAttrs ] );
3263 
3264  $pieces[] = Html::openElement( 'body', $bodyAttrs );
3265 
3266  return self::combineWrappedStrings( $pieces );
3267  }
3268 
3274  public function getResourceLoader() {
3275  if ( is_null( $this->mResourceLoader ) ) {
3276  // Lazy-initialise as needed
3277  $this->mResourceLoader = MediaWikiServices::getInstance()->getResourceLoader();
3278  }
3279  return $this->mResourceLoader;
3280  }
3281 
3290  public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) {
3291  // Apply 'target' and 'origin' filters
3292  $modules = $this->filterModules( (array)$modules, null, $only );
3293 
3295  $this->getRlClientContext(),
3296  $modules,
3297  $only,
3298  $extraQuery,
3299  $this->getCSPNonce()
3300  );
3301  }
3302 
3309  protected static function combineWrappedStrings( array $chunks ) {
3310  // Filter out empty values
3311  $chunks = array_filter( $chunks, 'strlen' );
3312  return WrappedString::join( "\n", $chunks );
3313  }
3314 
3321  public function getBottomScripts() {
3322  $chunks = [];
3323  $chunks[] = $this->getRlClient()->getBodyHtml();
3324 
3325  // Legacy non-ResourceLoader scripts
3326  $chunks[] = $this->mScripts;
3327 
3328  if ( $this->limitReportJSData ) {
3331  [ 'wgPageParseReport' => $this->limitReportJSData ]
3332  ),
3333  $this->getCSPNonce()
3334  );
3335  }
3336 
3337  return self::combineWrappedStrings( $chunks );
3338  }
3339 
3346  public function getJsConfigVars() {
3347  return $this->mJsConfigVars;
3348  }
3349 
3356  public function addJsConfigVars( $keys, $value = null ) {
3357  if ( is_array( $keys ) ) {
3358  foreach ( $keys as $key => $value ) {
3359  $this->mJsConfigVars[$key] = $value;
3360  }
3361  return;
3362  }
3363 
3364  $this->mJsConfigVars[$keys] = $value;
3365  }
3366 
3376  public function getJSVars() {
3377  $curRevisionId = 0;
3378  $articleId = 0;
3379  $canonicalSpecialPageName = false; # T23115
3380  $services = MediaWikiServices::getInstance();
3381 
3382  $title = $this->getTitle();
3383  $ns = $title->getNamespace();
3384  $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
3385  $canonicalNamespace = $nsInfo->exists( $ns )
3386  ? $nsInfo->getCanonicalName( $ns )
3387  : $title->getNsText();
3388 
3389  $sk = $this->getSkin();
3390  // Get the relevant title so that AJAX features can use the correct page name
3391  // when making API requests from certain special pages (T36972).
3392  $relevantTitle = $sk->getRelevantTitle();
3393  $relevantUser = $sk->getRelevantUser();
3394 
3395  if ( $ns == NS_SPECIAL ) {
3396  list( $canonicalSpecialPageName, /*...*/ ) =
3397  $services->getSpecialPageFactory()->
3398  resolveAlias( $title->getDBkey() );
3399  } elseif ( $this->canUseWikiPage() ) {
3400  $wikiPage = $this->getWikiPage();
3401  $curRevisionId = $wikiPage->getLatest();
3402  $articleId = $wikiPage->getId();
3403  }
3404 
3405  $lang = $title->getPageViewLanguage();
3406 
3407  // Pre-process information
3408  $separatorTransTable = $lang->separatorTransformTable();
3409  $separatorTransTable = $separatorTransTable ?: [];
3410  $compactSeparatorTransTable = [
3411  implode( "\t", array_keys( $separatorTransTable ) ),
3412  implode( "\t", $separatorTransTable ),
3413  ];
3414  $digitTransTable = $lang->digitTransformTable();
3415  $digitTransTable = $digitTransTable ?: [];
3416  $compactDigitTransTable = [
3417  implode( "\t", array_keys( $digitTransTable ) ),
3418  implode( "\t", $digitTransTable ),
3419  ];
3420 
3421  $user = $this->getUser();
3422 
3423  $vars = [
3424  'wgCanonicalNamespace' => $canonicalNamespace,
3425  'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
3426  'wgNamespaceNumber' => $title->getNamespace(),
3427  'wgPageName' => $title->getPrefixedDBkey(),
3428  'wgTitle' => $title->getText(),
3429  'wgCurRevisionId' => $curRevisionId,
3430  'wgRevisionId' => (int)$this->getRevisionId(),
3431  'wgArticleId' => $articleId,
3432  'wgIsArticle' => $this->isArticle(),
3433  'wgIsRedirect' => $title->isRedirect(),
3434  'wgAction' => Action::getActionName( $this->getContext() ),
3435  'wgUserName' => $user->isAnon() ? null : $user->getName(),
3436  'wgUserGroups' => $user->getEffectiveGroups(),
3437  'wgCategories' => $this->getCategories(),
3438  'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
3439  'wgPageContentLanguage' => $lang->getCode(),
3440  'wgPageContentModel' => $title->getContentModel(),
3441  'wgSeparatorTransformTable' => $compactSeparatorTransTable,
3442  'wgDigitTransformTable' => $compactDigitTransTable,
3443  'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
3444  'wgMonthNames' => $lang->getMonthNamesArray(),
3445  'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
3446  'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
3447  'wgRelevantArticleId' => $relevantTitle->getArticleID(),
3448  'wgRequestId' => WebRequest::getRequestId(),
3449  'wgCSPNonce' => $this->getCSPNonce(),
3450  ];
3451 
3452  if ( $user->isLoggedIn() ) {
3453  $vars['wgUserId'] = $user->getId();
3454  $vars['wgUserEditCount'] = $user->getEditCount();
3455  $userReg = $user->getRegistration();
3456  $vars['wgUserRegistration'] = $userReg ? wfTimestamp( TS_UNIX, $userReg ) * 1000 : null;
3457  // Get the revision ID of the oldest new message on the user's talk
3458  // page. This can be used for constructing new message alerts on
3459  // the client side.
3460  $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId();
3461  }
3462 
3463  $contLang = $services->getContentLanguage();
3464  if ( $contLang->hasVariants() ) {
3465  $vars['wgUserVariant'] = $contLang->getPreferredVariant();
3466  }
3467  // Same test as SkinTemplate
3468  $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user )
3469  && ( $title->exists() || $title->quickUserCan( 'create', $user ) );
3470 
3471  $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle
3472  && $relevantTitle->quickUserCan( 'edit', $user )
3473  && ( $relevantTitle->exists() || $relevantTitle->quickUserCan( 'create', $user ) );
3474 
3475  foreach ( $title->getRestrictionTypes() as $type ) {
3476  // Following keys are set in $vars:
3477  // wgRestrictionCreate, wgRestrictionEdit, wgRestrictionMove, wgRestrictionUpload
3478  $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
3479  }
3480 
3481  if ( $title->isMainPage() ) {
3482  $vars['wgIsMainPage'] = true;
3483  }
3484 
3485  if ( $this->mRedirectedFrom ) {
3486  $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey();
3487  }
3488 
3489  if ( $relevantUser ) {
3490  $vars['wgRelevantUserName'] = $relevantUser->getName();
3491  }
3492 
3493  // Allow extensions to add their custom variables to the mw.config map.
3494  // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
3495  // page-dependant but site-wide (without state).
3496  // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
3497  Hooks::run( 'MakeGlobalVariablesScript', [ &$vars, $this ] );
3498 
3499  // Merge in variables from addJsConfigVars last
3500  return array_merge( $vars, $this->getJsConfigVars() );
3501  }
3502 
3512  public function userCanPreview() {
3513  $request = $this->getRequest();
3514  if (
3515  $request->getVal( 'action' ) !== 'submit' ||
3516  !$request->wasPosted()
3517  ) {
3518  return false;
3519  }
3520 
3521  $user = $this->getUser();
3522 
3523  if ( !$user->isLoggedIn() ) {
3524  // Anons have predictable edit tokens
3525  return false;
3526  }
3527  if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
3528  return false;
3529  }
3530 
3531  $title = $this->getTitle();
3532  $errors = $title->getUserPermissionsErrors( 'edit', $user );
3533  if ( count( $errors ) !== 0 ) {
3534  return false;
3535  }
3536 
3537  return true;
3538  }
3539 
3543  public function getHeadLinksArray() {
3544  global $wgVersion;
3545 
3546  $tags = [];
3547  $config = $this->getConfig();
3548 
3549  $canonicalUrl = $this->mCanonicalUrl;
3550 
3551  $tags['meta-generator'] = Html::element( 'meta', [
3552  'name' => 'generator',
3553  'content' => "MediaWiki $wgVersion",
3554  ] );
3555 
3556  if ( $config->get( 'ReferrerPolicy' ) !== false ) {
3557  // Per https://w3c.github.io/webappsec-referrer-policy/#unknown-policy-values
3558  // fallbacks should come before the primary value so we need to reverse the array.
3559  foreach ( array_reverse( (array)$config->get( 'ReferrerPolicy' ) ) as $i => $policy ) {
3560  $tags["meta-referrer-$i"] = Html::element( 'meta', [
3561  'name' => 'referrer',
3562  'content' => $policy,
3563  ] );
3564  }
3565  }
3566 
3567  $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
3568  if ( $p !== 'index,follow' ) {
3569  // http://www.robotstxt.org/wc/meta-user.html
3570  // Only show if it's different from the default robots policy
3571  $tags['meta-robots'] = Html::element( 'meta', [
3572  'name' => 'robots',
3573  'content' => $p,
3574  ] );
3575  }
3576 
3577  foreach ( $this->mMetatags as $tag ) {
3578  if ( strncasecmp( $tag[0], 'http:', 5 ) === 0 ) {
3579  $a = 'http-equiv';
3580  $tag[0] = substr( $tag[0], 5 );
3581  } elseif ( strncasecmp( $tag[0], 'og:', 3 ) === 0 ) {
3582  $a = 'property';
3583  } else {
3584  $a = 'name';
3585  }
3586  $tagName = "meta-{$tag[0]}";
3587  if ( isset( $tags[$tagName] ) ) {
3588  $tagName .= $tag[1];
3589  }
3590  $tags[$tagName] = Html::element( 'meta',
3591  [
3592  $a => $tag[0],
3593  'content' => $tag[1]
3594  ]
3595  );
3596  }
3597 
3598  foreach ( $this->mLinktags as $tag ) {
3599  $tags[] = Html::element( 'link', $tag );
3600  }
3601 
3602  # Universal edit button
3603  if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) {
3604  $user = $this->getUser();
3605  if ( $this->getTitle()->quickUserCan( 'edit', $user )
3606  && ( $this->getTitle()->exists() ||
3607  $this->getTitle()->quickUserCan( 'create', $user ) )
3608  ) {
3609  // Original UniversalEditButton
3610  $msg = $this->msg( 'edit' )->text();
3611  $tags['universal-edit-button'] = Html::element( 'link', [
3612  'rel' => 'alternate',
3613  'type' => 'application/x-wiki',
3614  'title' => $msg,
3615  'href' => $this->getTitle()->getEditURL(),
3616  ] );
3617  // Alternate edit link
3618  $tags['alternative-edit'] = Html::element( 'link', [
3619  'rel' => 'edit',
3620  'title' => $msg,
3621  'href' => $this->getTitle()->getEditURL(),
3622  ] );
3623  }
3624  }
3625 
3626  # Generally the order of the favicon and apple-touch-icon links
3627  # should not matter, but Konqueror (3.5.9 at least) incorrectly
3628  # uses whichever one appears later in the HTML source. Make sure
3629  # apple-touch-icon is specified first to avoid this.
3630  if ( $config->get( 'AppleTouchIcon' ) !== false ) {
3631  $tags['apple-touch-icon'] = Html::element( 'link', [
3632  'rel' => 'apple-touch-icon',
3633  'href' => $config->get( 'AppleTouchIcon' )
3634  ] );
3635  }
3636 
3637  if ( $config->get( 'Favicon' ) !== false ) {
3638  $tags['favicon'] = Html::element( 'link', [
3639  'rel' => 'shortcut icon',
3640  'href' => $config->get( 'Favicon' )
3641  ] );
3642  }
3643 
3644  # OpenSearch description link
3645  $tags['opensearch'] = Html::element( 'link', [
3646  'rel' => 'search',
3647  'type' => 'application/opensearchdescription+xml',
3648  'href' => wfScript( 'opensearch_desc' ),
3649  'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
3650  ] );
3651 
3652  # Real Simple Discovery link, provides auto-discovery information
3653  # for the MediaWiki API (and potentially additional custom API
3654  # support such as WordPress or Twitter-compatible APIs for a
3655  # blogging extension, etc)
3656  $tags['rsd'] = Html::element( 'link', [
3657  'rel' => 'EditURI',
3658  'type' => 'application/rsd+xml',
3659  // Output a protocol-relative URL here if $wgServer is protocol-relative.
3660  // Whether RSD accepts relative or protocol-relative URLs is completely
3661  // undocumented, though.
3662  'href' => wfExpandUrl( wfAppendQuery(
3663  wfScript( 'api' ),
3664  [ 'action' => 'rsd' ] ),
3666  ),
3667  ] );
3668 
3669  # Language variants
3670  if ( !$config->get( 'DisableLangConversion' ) ) {
3671  $lang = $this->getTitle()->getPageLanguage();
3672  if ( $lang->hasVariants() ) {
3673  $variants = $lang->getVariants();
3674  foreach ( $variants as $variant ) {
3675  $tags["variant-$variant"] = Html::element( 'link', [
3676  'rel' => 'alternate',
3677  'hreflang' => LanguageCode::bcp47( $variant ),
3678  'href' => $this->getTitle()->getLocalURL(
3679  [ 'variant' => $variant ] )
3680  ]
3681  );
3682  }
3683  # x-default link per https://support.google.com/webmasters/answer/189077?hl=en
3684  $tags["variant-x-default"] = Html::element( 'link', [
3685  'rel' => 'alternate',
3686  'hreflang' => 'x-default',
3687  'href' => $this->getTitle()->getLocalURL() ] );
3688  }
3689  }
3690 
3691  # Copyright
3692  if ( $this->copyrightUrl !== null ) {
3693  $copyright = $this->copyrightUrl;
3694  } else {
3695  $copyright = '';
3696  if ( $config->get( 'RightsPage' ) ) {
3697  $copy = Title::newFromText( $config->get( 'RightsPage' ) );
3698 
3699  if ( $copy ) {
3700  $copyright = $copy->getLocalURL();
3701  }
3702  }
3703 
3704  if ( !$copyright && $config->get( 'RightsUrl' ) ) {
3705  $copyright = $config->get( 'RightsUrl' );
3706  }
3707  }
3708 
3709  if ( $copyright ) {
3710  $tags['copyright'] = Html::element( 'link', [
3711  'rel' => 'license',
3712  'href' => $copyright ]
3713  );
3714  }
3715 
3716  # Feeds
3717  if ( $config->get( 'Feed' ) ) {
3718  $feedLinks = [];
3719 
3720  foreach ( $this->getSyndicationLinks() as $format => $link ) {
3721  # Use the page name for the title. In principle, this could
3722  # lead to issues with having the same name for different feeds
3723  # corresponding to the same page, but we can't avoid that at
3724  # this low a level.
3725 
3726  $feedLinks[] = $this->feedLink(
3727  $format,
3728  $link,
3729  # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
3730  $this->msg(
3731  "page-{$format}-feed", $this->getTitle()->getPrefixedText()
3732  )->text()
3733  );
3734  }
3735 
3736  # Recent changes feed should appear on every page (except recentchanges,
3737  # that would be redundant). Put it after the per-page feed to avoid
3738  # changing existing behavior. It's still available, probably via a
3739  # menu in your browser. Some sites might have a different feed they'd
3740  # like to promote instead of the RC feed (maybe like a "Recent New Articles"
3741  # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
3742  # If so, use it instead.
3743  $sitename = $config->get( 'Sitename' );
3744  $overrideSiteFeed = $config->get( 'OverrideSiteFeed' );
3745  if ( $overrideSiteFeed ) {
3746  foreach ( $overrideSiteFeed as $type => $feedUrl ) {
3747  // Note, this->feedLink escapes the url.
3748  $feedLinks[] = $this->feedLink(
3749  $type,
3750  $feedUrl,
3751  $this->msg( "site-{$type}-feed", $sitename )->text()
3752  );
3753  }
3754  } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
3755  $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
3756  foreach ( $this->getAdvertisedFeedTypes() as $format ) {
3757  $feedLinks[] = $this->feedLink(
3758  $format,
3759  $rctitle->getLocalURL( [ 'feed' => $format ] ),
3760  # For grep: 'site-rss-feed', 'site-atom-feed'
3761  $this->msg( "site-{$format}-feed", $sitename )->text()
3762  );
3763  }
3764  }
3765 
3766  # Allow extensions to change the list pf feeds. This hook is primarily for changing,
3767  # manipulating or removing existing feed tags. If you want to add new feeds, you should
3768  # use OutputPage::addFeedLink() instead.
3769  Hooks::run( 'AfterBuildFeedLinks', [ &$feedLinks ] );
3770 
3771  $tags += $feedLinks;
3772  }
3773 
3774  # Canonical URL
3775  if ( $config->get( 'EnableCanonicalServerLink' ) ) {
3776  if ( $canonicalUrl !== false ) {
3777  $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL );
3778  } elseif ( $this->isArticleRelated() ) {
3779  // This affects all requests where "setArticleRelated" is true. This is
3780  // typically all requests that show content (query title, curid, oldid, diff),
3781  // and all wikipage actions (edit, delete, purge, info, history etc.).
3782  // It does not apply to File pages and Special pages.
3783  // 'history' and 'info' actions address page metadata rather than the page
3784  // content itself, so they may not be canonicalized to the view page url.
3785  // TODO: this ought to be better encapsulated in the Action class.
3786  $action = Action::getActionName( $this->getContext() );
3787  if ( in_array( $action, [ 'history', 'info' ] ) ) {
3788  $query = "action={$action}";
3789  } else {
3790  $query = '';
3791  }
3792  $canonicalUrl = $this->getTitle()->getCanonicalURL( $query );
3793  } else {
3794  $reqUrl = $this->getRequest()->getRequestURL();
3795  $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL );
3796  }
3797  }
3798  if ( $canonicalUrl !== false ) {
3799  $tags[] = Html::element( 'link', [
3800  'rel' => 'canonical',
3801  'href' => $canonicalUrl
3802  ] );
3803  }
3804 
3805  // Allow extensions to add, remove and/or otherwise manipulate these links
3806  // If you want only to *add* <head> links, please use the addHeadItem()
3807  // (or addHeadItems() for multiple items) method instead.
3808  // This hook is provided as a last resort for extensions to modify these
3809  // links before the output is sent to client.
3810  Hooks::run( 'OutputPageAfterGetHeadLinksArray', [ &$tags, $this ] );
3811 
3812  return $tags;
3813  }
3814 
3823  private function feedLink( $type, $url, $text ) {
3824  return Html::element( 'link', [
3825  'rel' => 'alternate',
3826  'type' => "application/$type+xml",
3827  'title' => $text,
3828  'href' => $url ]
3829  );
3830  }
3831 
3841  public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
3842  $options = [];
3843  if ( $media ) {
3844  $options['media'] = $media;
3845  }
3846  if ( $condition ) {
3847  $options['condition'] = $condition;
3848  }
3849  if ( $dir ) {
3850  $options['dir'] = $dir;
3851  }
3852  $this->styles[$style] = $options;
3853  }
3854 
3862  public function addInlineStyle( $style_css, $flip = 'noflip' ) {
3863  if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
3864  # If wanted, and the interface is right-to-left, flip the CSS
3865  $style_css = CSSJanus::transform( $style_css, true, false );
3866  }
3867  $this->mInlineStyles .= Html::inlineStyle( $style_css );
3868  }
3869 
3875  protected function buildExemptModules() {
3876  $chunks = [];
3877  // Things that go after the ResourceLoaderDynamicStyles marker
3878  $append = [];
3879 
3880  // We want site, private and user styles to override dynamically added styles from
3881  // general modules, but we want dynamically added styles to override statically added
3882  // style modules. So the order has to be:
3883  // - page style modules (formatted by ResourceLoaderClientHtml::getHeadHtml())
3884  // - dynamically loaded styles (added by mw.loader before ResourceLoaderDynamicStyles)
3885  // - ResourceLoaderDynamicStyles marker
3886  // - site/private/user styles
3887 
3888  // Add legacy styles added through addStyle()/addInlineStyle() here
3889  $chunks[] = implode( '', $this->buildCssLinksArray() ) . $this->mInlineStyles;
3890 
3891  $chunks[] = Html::element(
3892  'meta',
3893  [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ]
3894  );
3895 
3896  $separateReq = [ 'site.styles', 'user.styles' ];
3897  foreach ( $this->rlExemptStyleModules as $group => $moduleNames ) {
3898  // Combinable modules
3899  $chunks[] = $this->makeResourceLoaderLink(
3900  array_diff( $moduleNames, $separateReq ),
3902  );
3903 
3904  foreach ( array_intersect( $moduleNames, $separateReq ) as $name ) {
3905  // These require their own dedicated request in order to support "@import"
3906  // syntax, which is incompatible with concatenation. (T147667, T37562)
3907  $chunks[] = $this->makeResourceLoaderLink( $name,
3909  );
3910  }
3911  }
3912 
3913  return self::combineWrappedStrings( array_merge( $chunks, $append ) );
3914  }
3915 
3919  public function buildCssLinksArray() {
3920  $links = [];
3921 
3922  foreach ( $this->styles as $file => $options ) {
3923  $link = $this->styleLink( $file, $options );
3924  if ( $link ) {
3925  $links[$file] = $link;
3926  }
3927  }
3928  return $links;
3929  }
3930 
3938  protected function styleLink( $style, array $options ) {
3939  if ( isset( $options['dir'] ) && $this->getLanguage()->getDir() != $options['dir'] ) {
3940  return '';
3941  }
3942 
3943  if ( isset( $options['media'] ) ) {
3944  $media = self::transformCssMedia( $options['media'] );
3945  if ( is_null( $media ) ) {
3946  return '';
3947  }
3948  } else {
3949  $media = 'all';
3950  }
3951 
3952  if ( substr( $style, 0, 1 ) == '/' ||
3953  substr( $style, 0, 5 ) == 'http:' ||
3954  substr( $style, 0, 6 ) == 'https:' ) {
3955  $url = $style;
3956  } else {
3957  $config = $this->getConfig();
3958  // Append file hash as query parameter
3959  $url = self::transformResourcePath(
3960  $config,
3961  $config->get( 'StylePath' ) . '/' . $style
3962  );
3963  }
3964 
3965  $link = Html::linkedStyle( $url, $media );
3966 
3967  if ( isset( $options['condition'] ) ) {
3968  $condition = htmlspecialchars( $options['condition'] );
3969  $link = "<!--[if $condition]>$link<![endif]-->";
3970  }
3971  return $link;
3972  }
3973 
3995  public static function transformResourcePath( Config $config, $path ) {
3996  global $IP;
3997 
3998  $localDir = $IP;
3999  $remotePathPrefix = $config->get( 'ResourceBasePath' );
4000  if ( $remotePathPrefix === '' ) {
4001  // The configured base path is required to be empty string for
4002  // wikis in the domain root
4003  $remotePath = '/';
4004  } else {
4005  $remotePath = $remotePathPrefix;
4006  }
4007  if ( strpos( $path, $remotePath ) !== 0 || substr( $path, 0, 2 ) === '//' ) {
4008  // - Path is outside wgResourceBasePath, ignore.
4009  // - Path is protocol-relative. Fixes T155310. Not supported by RelPath lib.
4010  return $path;
4011  }
4012  // For files in resources, extensions/ or skins/, ResourceBasePath is preferred here.
4013  // For other misc files in $IP, we'll fallback to that as well. There is, however, a fourth
4014  // supported dir/path pair in the configuration (wgUploadDirectory, wgUploadPath)
4015  // which is not expected to be in wgResourceBasePath on CDNs. (T155146)
4016  $uploadPath = $config->get( 'UploadPath' );
4017  if ( strpos( $path, $uploadPath ) === 0 ) {
4018  $localDir = $config->get( 'UploadDirectory' );
4019  $remotePathPrefix = $remotePath = $uploadPath;
4020  }
4021 
4022  $path = RelPath::getRelativePath( $path, $remotePath );
4023  return self::transformFilePath( $remotePathPrefix, $localDir, $path );
4024  }
4025 
4037  public static function transformFilePath( $remotePathPrefix, $localPath, $file ) {
4038  $hash = md5_file( "$localPath/$file" );
4039  if ( $hash === false ) {
4040  wfLogWarning( __METHOD__ . ": Failed to hash $localPath/$file" );
4041  $hash = '';
4042  }
4043  return "$remotePathPrefix/$file?" . substr( $hash, 0, 5 );
4044  }
4045 
4053  public static function transformCssMedia( $media ) {
4054  global $wgRequest;
4055 
4056  // https://www.w3.org/TR/css3-mediaqueries/#syntax
4057  $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
4058 
4059  // Switch in on-screen display for media testing
4060  $switches = [
4061  'printable' => 'print',
4062  'handheld' => 'handheld',
4063  ];
4064  foreach ( $switches as $switch => $targetMedia ) {
4065  if ( $wgRequest->getBool( $switch ) ) {
4066  if ( $media == $targetMedia ) {
4067  $media = '';
4068  } elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
4069  /* This regex will not attempt to understand a comma-separated media_query_list
4070  *
4071  * Example supported values for $media:
4072  * 'screen', 'only screen', 'screen and (min-width: 982px)' ),
4073  * Example NOT supported value for $media:
4074  * '3d-glasses, screen, print and resolution > 90dpi'
4075  *
4076  * If it's a print request, we never want any kind of screen stylesheets
4077  * If it's a handheld request (currently the only other choice with a switch),
4078  * we don't want simple 'screen' but we might want screen queries that
4079  * have a max-width or something, so we'll pass all others on and let the
4080  * client do the query.
4081  */
4082  if ( $targetMedia == 'print' || $media == 'screen' ) {
4083  return null;
4084  }
4085  }
4086  }
4087  }
4088 
4089  return $media;
4090  }
4091 
4098  public function addWikiMsg( /*...*/ ) {
4099  $args = func_get_args();
4100  $name = array_shift( $args );
4101  $this->addWikiMsgArray( $name, $args );
4102  }
4103 
4112  public function addWikiMsgArray( $name, $args ) {
4113  $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
4114  }
4115 
4141  public function wrapWikiMsg( $wrap /*, ...*/ ) {
4142  $msgSpecs = func_get_args();
4143  array_shift( $msgSpecs );
4144  $msgSpecs = array_values( $msgSpecs );
4145  $s = $wrap;
4146  foreach ( $msgSpecs as $n => $spec ) {
4147  if ( is_array( $spec ) ) {
4148  $args = $spec;
4149  $name = array_shift( $args );
4150  if ( isset( $args['options'] ) ) {
4151  unset( $args['options'] );
4152  wfDeprecated(
4153  'Adding "options" to ' . __METHOD__ . ' is no longer supported',
4154  '1.20'
4155  );
4156  }
4157  } else {
4158  $args = [];
4159  $name = $spec;
4160  }
4161  $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
4162  }
4163  $this->addWikiTextAsInterface( $s );
4164  }
4165 
4171  public function isTOCEnabled() {
4172  return $this->mEnableTOC;
4173  }
4174 
4181  public function enableSectionEditLinks( $flag = true ) {
4182  wfDeprecated( __METHOD__, '1.31' );
4183  }
4184 
4192  public static function setupOOUI( $skinName = 'default', $dir = 'ltr' ) {
4194  $theme = $themes[$skinName] ?? $themes['default'];
4195  // For example, 'OOUI\WikimediaUITheme'.
4196  $themeClass = "OOUI\\{$theme}Theme";
4197  OOUI\Theme::setSingleton( new $themeClass() );
4198  OOUI\Element::setDefaultDir( $dir );
4199  }
4200 
4207  public function enableOOUI() {
4208  self::setupOOUI(
4209  strtolower( $this->getSkin()->getSkinName() ),
4210  $this->getLanguage()->getDir()
4211  );
4212  $this->addModuleStyles( [
4213  'oojs-ui-core.styles',
4214  'oojs-ui.styles.indicators',
4215  'oojs-ui.styles.textures',
4216  'mediawiki.widgets.styles',
4217  'oojs-ui.styles.icons-content',
4218  'oojs-ui.styles.icons-alerts',
4219  'oojs-ui.styles.icons-interactions',
4220  ] );
4221  }
4222 
4232  public function getCSPNonce() {
4234  return false;
4235  }
4236  if ( $this->CSPNonce === null ) {
4237  // XXX It might be expensive to generate randomness
4238  // on every request, on Windows.
4239  $rand = random_bytes( 15 );
4240  $this->CSPNonce = base64_encode( $rand );
4241  }
4242  return $this->CSPNonce;
4243  }
4244 
4245 }
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
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:597
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:533
static newFromContext(IContextSource $context)
Get a ParserOptions object from a IContextSource object.
ResourceLoader $mResourceLoader
Definition: OutputPage.php:159
setArray( $array)
Set the link list to a given 2-d array First key is the namespace, second is the DB key...
Definition: LinkBatch.php:100
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
Definition: hooks.txt:1982
array $rlExemptStyleModules
Definition: OutputPage.php:168
array $mTemplateIds
Definition: OutputPage.php:174
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
setConfig(array $vars)
Set mw.config variables.
addWikiTextAsContent( $text, $linestart=true, Title $title=null)
Convert wikitext in the page content language to HTML and add it to the buffer.
getHeadItemsArray()
Get an array of head items.
Definition: OutputPage.php:629
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:649
buildExemptModules()
Build exempt modules and legacy non-ResourceLoader styles.
$mScripts
Used for JavaScript (predates ResourceLoader)
Definition: OutputPage.php:135
int $mCdnMaxage
Cache stuff.
Definition: OutputPage.php:234
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1585
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:232
setFileVersion( $file)
Set the displayed file version.
either a plain
Definition: hooks.txt:2043
getHTMLTitle()
Return the "HTML title", i.e.
Definition: OutputPage.php:904
addWikiTextWithTitle( $text, Title $title, $linestart=true)
Add wikitext with a custom Title object.
int $mCdnMaxageLimit
Upper limit on mCdnMaxage.
Definition: OutputPage.php:236
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
styleLink( $style, array $options)
Generate <link> tags for stylesheets.
isTOCEnabled()
Whether the output has a table of contents.
static getRequestId()
Get the unique request ID.
Definition: WebRequest.php:275
$wgVersion
MediaWiki version number.
setTarget( $target)
Sets ResourceLoader target for load.php links.
Definition: OutputPage.php:589
bool $mEnableTOC
Whether parser output contains a table of contents.
Definition: OutputPage.php:296
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:675
callable [] $contentOverrideCallbacks
Definition: OutputPage.php:310
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
setCdnMaxage( $maxage)
Set the value of the "s-maxage" part of the "Cache-control" HTTP header.
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
static newMainPage(MessageLocalizer $localizer=null)
Create a new Title for the Main Page.
Definition: Title.php:653
getCanonicalUrl()
Returns the URL to be used for the <link rel="canonical"> if one is set.
Definition: OutputPage.php:437
int $mContainsNewMagic
Definition: OutputPage.php:199
$IP
Definition: WebStart.php:41
setStatusCode( $statusCode)
Set the HTTP status code to send with the output.
Definition: OutputPage.php:375
setArticleFlag( $newVal)
Set whether the displayed content is related to the source of the corresponding article on the wiki S...
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext such as when responding to a resource loader request or generating HTML output & $resourceLoader
Definition: hooks.txt:2633
wrapWikiMsg( $wrap)
This function takes a number of message/argument specifications, wraps them in some overall structure...
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing after in associative array form before processing starts Return false to skip default processing and return $ret $linkRenderer
Definition: hooks.txt:1982
addModules( $modules)
Load one or more ResourceLoader modules on this page.
Definition: OutputPage.php:547
string $mPageTitle
The contents of.
Definition: OutputPage.php:58
setLanguageLinks(array $newLinkArray)
Reset the language links and add new language links.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Definition: router.php:42
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2159
setSubtitle( $str)
Replace the subtitle with $str.
string $mInlineStyles
Inline CSS styles.
Definition: OutputPage.php:138
preventClickjacking( $flag=null)
Get or set the prevent-clickjacking flag.
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
array $mMetatags
Should be private.
Definition: OutputPage.php:48
addCategoryLinks(array $categories)
Add an array of categories, with names in the keys.
haveCacheVaryCookies()
Check if the request has a cache-varying cookie header If it does, it&#39;s very important that we don&#39;t ...
getPageTitle()
Return the "page title", i.e.
Definition: OutputPage.php:951
setCopyrightUrl( $url)
Set the copyright URL to send with the output.
Definition: OutputPage.php:366
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:509
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:252
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:210
redirect( $url, $responsecode='302')
Redirect to $url rather than displaying the normal page.
Definition: OutputPage.php:343
static isNonceRequired(Config $config)
Should we set nonce attribute.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
& getFileSearchOptions()
array $mHeadItems
Array of elements in "<head>".
Definition: OutputPage.php:147
showFatalError( $message)
Output an error page.
allowClickjacking()
Turn off frame-breaking.
loadSkinModules( $sk)
Transfer styles and JavaScript modules from skin.
parseInlineAsInterface( $text, $linestart=true)
Parse wikitext in the user interface language, strip paragraph wrapper, and return the HTML...
array $mModules
Definition: OutputPage.php:153
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition: hooks.txt:175
$value
const NS_SPECIAL
Definition: Defines.php:53
const PROTO_CURRENT
Definition: Defines.php:222
string null $mTarget
ResourceLoader target for load.php links.
Definition: OutputPage.php:291
getArticleBodyOnly()
Return whether the output will contain only the body of the article.
Definition: OutputPage.php:695
array $limitReportJSData
Profiling data.
Definition: OutputPage.php:304
prependHTML( $text)
Prepend $text to the body HTML.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
static makeConfigSetScript(array $configuration)
Returns JS code which will set the MediaWiki configuration array to the given value.
setProperty( $name, $value)
Set an additional output property.
Definition: OutputPage.php:706
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:989
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:489
string $mPageLinkTitle
Used by skin template.
Definition: OutputPage.php:144
userCanPreview()
To make it harder for someone to slip a user a fake JavaScript or CSS preview, a random token is asso...
setPrintable()
Set the page as printable, i.e.
string null $copyrightUrl
The URL to send in a <link> element with rel=license.
Definition: OutputPage.php:301
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:1696
disable()
Disable output completely, i.e.
Content for JavaScript pages.
this hook is for auditing only $response
Definition: hooks.txt:780
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:573
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:732
string $mRevisionTimestamp
Definition: OutputPage.php:249
array $mLanguageLinks
Array of Interwiki Prefixed (non DB key) Titles (e.g.
Definition: OutputPage.php:127
setHTMLTitle( $name)
"HTML title" means the contents of "<title>".
Definition: OutputPage.php:891
IContextSource $context
array Title $mRedirectedFrom
If the current page was reached through a redirect, $mRedirectedFrom contains the Title of the redire...
Definition: OutputPage.php:281
string $displayTitle
The displayed title of the page.
Definition: OutputPage.php:67
setPageTitle( $name)
"Page title" means the contents of <h1>.
Definition: OutputPage.php:929
getRlClient()
Call this to freeze the module queue and JS config and create a formatter.
getFileSearchOptions()
Get the files used on this page.
setCategoryLinks(array $categories)
Reset the category links (but not the category list) and add $categories.
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
isSyndicated()
Should we output feed links for this page?
static setupOOUI( $skinName='default', $dir='ltr')
Helper function to setup the PHP implementation of OOUI to use in this request.
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
array $styles
An array of stylesheet filenames (relative from skins path), with options for CSS media...
Definition: OutputPage.php:262
isPrintable()
Return whether the page is "printable".
$mProperties
Additional key => value data.
Definition: OutputPage.php:286
getNamespace()
Get the namespace index.
parseInternal( $text, $title, $linestart, $tidy, $interface, $language)
Parse wikitext and return the HTML (internal implementation helper)
array bool $mDoNothing
Whether output is disabled.
Definition: OutputPage.php:194
getAllowedModules( $type)
Show what level of JavaScript / CSS untrustworthiness is allowed on this page.
setRedirectedFrom( $t)
Set $mRedirectedFrom, the Title of the page which redirected us to the current page.
Definition: OutputPage.php:913
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title e g db for database replication lag or jobqueue for job queue size converted to pseudo seconds It is possible to add more fields and they will be returned to the user in the API response after the basic globals have been set but before ordinary actions take place $output
Definition: hooks.txt:2217
bool $mPreventClickjacking
Controls if anti-clickjacking / frame-breaking headers will be sent.
Definition: OutputPage.php:243
if( $line===false) $args
Definition: cdb.php:64
static stripOuterParagraph( $html)
Strip outer.
Definition: Parser.php:6418
lowerCdnMaxage( $maxage)
Set the value of the "s-maxage" part of the "Cache-control" HTTP header to $maxage if that is lower t...
addInlineStyle( $style_css, $flip='noflip')
Adds inline CSS styles Internal use only.
getPageClasses( $title)
TODO: document.
Definition: Skin.php:439
array $mJsConfigVars
Definition: OutputPage.php:171
getJsConfigVars()
Get the javascript config vars to include on this page.
enableOOUI()
Add ResourceLoader module styles for OOUI and set up the PHP implementation of it for use with MediaW...
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:3050
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:491
passed in as a query string parameter to the various URLs constructed here(i.e. $prevlink) $ldel you ll need to handle error messages
Definition: hooks.txt:1287
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
bool $mPrintable
We have to set isPrintable().
Definition: OutputPage.php:94
Allows changing specific properties of a context object, without changing the main one...
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e.g.
clearSubtitle()
Clear the subtitles.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition: LinkBatch.php:34
addWikiTextTitle( $text, Title $title, $linestart, $tidy=false, $interface=false)
Add wikitext with a custom Title object.
bool $mIsArticleRelated
Stores "article flag" toggle.
Definition: OutputPage.php:85
wfGetAllCallers( $limit=3)
Return a string consisting of callers in the stack.
canUseWikiPage()
Check whether a WikiPage object can be get with getWikiPage().
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
getCdnCacheEpoch( $reqTime, $maxAge)
Definition: OutputPage.php:820
array $mAllowedModules
What level of &#39;untrustworthiness&#39; is allowed in CSS/JS modules loaded on this page?
Definition: OutputPage.php:189
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
static array $cacheVaryCookies
A cache of the names of the cookies that will influence the cache.
Definition: OutputPage.php:325
bool $mNoGallery
Comes from the parser.
Definition: OutputPage.php:231
output( $return=false)
Finally, all the text has been munged and accumulated into the object, let&#39;s actually output it: ...
setCopyright( $hasCopyright)
Set whether the standard copyright should be shown for the current page.
getTemplateIds()
Get the templates used on this page.
addParserOutput(ParserOutput $parserOutput, $poOptions=[])
Add everything from a ParserOutput object.
setFollowPolicy( $policy)
Set the follow policy for the page, but leave the index policy un- touched.
Definition: OutputPage.php:878
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:1994
getLanguageLinks()
Get the list of language links.
array $mFileVersion
Definition: OutputPage.php:252
array $mModuleStyles
Definition: OutputPage.php:156
bool $mHasCopyright
Is the content subject to copyright.
Definition: OutputPage.php:88
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness, which urlencode encodes by default.
makeResourceLoaderLink( $modules, $only, array $extraQuery=[])
Explicily load or embed modules on a page.
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
enableClientCache( $state)
Use enableClientCache(false) to force it to send nocache headers.
addAcceptLanguage()
T23672: Add Accept-Language to Vary and Key headers if there&#39;s no &#39;variant&#39; parameter in GET...
Interface for configuration instances.
Definition: Config.php:28
parse( $text, $linestart=true, $interface=false, $language=null)
Parse wikitext and return the HTML.
wfCgiToArray( $query)
This is the logical opposite of wfArrayToCgi(): it accepts a query string as its argument and returns...
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition: Xml.php:677
setSyndicated( $show=true)
Add or remove feed links in the page header This is mainly kept for backward compatibility, see OutputPage::addFeedLink() for the new version.
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title e g db for database replication lag or jobqueue for job queue size converted to pseudo seconds It is possible to add more fields and they will be returned to the user in the API response after the basic globals have been set but before ordinary actions take place or wrap services the preferred way to define a new service is the $wgServiceWiringFiles array $services
Definition: hooks.txt:2217
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:846
array array $mIndicators
Definition: OutputPage.php:124
addMeta( $name, $val)
Add a new "<meta>" tag To add an http-equiv meta tag, precede the name with "http:".
Definition: OutputPage.php:386
showsCopyright()
Return whether the standard copyright should be shown for the current page.
addLinkHeader( $header)
Add an HTTP Link: header.
$mFeedLinks
Handles the Atom / RSS links.
Definition: OutputPage.php:212
$res
Definition: database.txt:21
static getContentText(Content $content=null)
Convenience function for getting flat text from a Content object.
getSubtitle()
Get the subtitle.
isArticleRelated()
Return whether this page is related an article on the wiki.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
static preloadTitleInfo(ResourceLoaderContext $context, IDatabase $db, array $moduleNames)
hasHeadItem( $name)
Check if the header item $name is already set.
Definition: OutputPage.php:665
$mFeedLinksAppendQuery
Definition: OutputPage.php:182
getMetaTags()
Returns the current <meta> tags.
Definition: OutputPage.php:396
$mLinkHeader
Link: header contents.
Definition: OutputPage.php:315
getCategoryLinks()
Get the list of category links, in a 2-D array with the following format: $arr[$type][] = $link...
bool $mArticleBodyOnly
Flag if output should only contain the body of the article.
Definition: OutputPage.php:218
static mergeAttributes( $a, $b)
Merge two sets of HTML attributes.
Definition: Sanitizer.php:929
prepareErrorPage( $pageTitle, $htmlTitle=false)
Prepare this object to display an error page; disable caching and indexing, clear the current text an...
addTemplate(&$template)
Add the output of a QuickTemplate to the output buffer.
clearHTML()
Clear the body HTML.
getFrameOptions()
Get the X-Frame-Options header value (without the name part), or false if there isn&#39;t one...
Load and configure a ResourceLoader client on an HTML page.
array $mAdditionalBodyClasses
Additional <body> classes; there are also <body> classes from other sources.
Definition: OutputPage.php:150
parseAsInterface( $text, $linestart=true)
Parse wikitext in the user interface language and return the HTML.
addFeedLink( $format, $href)
Add a feed link to the page header.
getIndicators()
Get the indicators associated with this page.
getContext()
Get the base IContextSource object.
getDBkey()
Get the main part with underscores.
addWikiMsg()
Add a wikitext-formatted message to the output.
static combineWrappedStrings(array $chunks)
Combine WrappedString chunks and filter out empty ones.
$params
showNewSectionLink()
Show an "add new section" link?
const NS_CATEGORY
Definition: Defines.php:78
addJsConfigVars( $keys, $value=null)
Add one or more variables to be set in mw.config in JavaScript.
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1982
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:316
and(b) You must cause any modified files to carry prominent notices stating that You changed the files
getCategories( $type='all')
Get the list of category names this page belongs to.
string $mHTMLtitle
Stores contents of "<title>" tag.
Definition: OutputPage.php:76
bool $mHideNewSectionLink
Definition: OutputPage.php:224
string $mBodytext
Contains all of the "<body>" content.
Definition: OutputPage.php:73
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Definition: hooks.txt:1982
buildCssLinksArray()
getRlClientContext()
getDisplayTitle()
Returns page display title.
Definition: OutputPage.php:974
getAdvertisedFeedTypes()
Return effective list of advertised feed types.
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:780
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:655
getHtmlElementAttributes()
Return values for <html> element.
Definition: Skin.php:475
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:925
static escapeClass( $class)
Given a value, escape it so that it can be used as a CSS class and return it.
Definition: Sanitizer.php:1410
array $mVaryHeader
Headers that cause the cache to vary.
Definition: OutputPage.php:271
preventClickjacking( $enable=true)
Set a flag which will cause an X-Frame-Options header appropriate for edit pages to be sent...
static addModules(OutputPage $out)
Add ResourceLoader modules to the OutputPage object if debugging is enabled.
Definition: MWDebug.php:96
addLink(array $linkarr)
Add a new <link> tag to the page header.
Definition: OutputPage.php:407
$filter
addModuleStyles( $modules)
Load the styles of one or more ResourceLoader modules on this page.
Definition: OutputPage.php:573
const PROTO_RELATIVE
Definition: Defines.php:221
array $mCategories
Definition: OutputPage.php:118
static getActionName(IContextSource $context)
Get the action that will be executed, not necessarily the one passed passed through the "action" requ...
Definition: Action.php:123
string $CSPNonce
The nonce for Content-Security-Policy.
Definition: OutputPage.php:320
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:82
$header
static normalizeCharReferences( $text)
Ensure that any entities and character references are legal for XML and XHTML specifically.
Definition: Sanitizer.php:1561
getModuleStyles( $filter=false, $position=null)
Get the list of style-only modules to load on this page.
Definition: OutputPage.php:558
bool $mNewSectionLink
Definition: OutputPage.php:221
reduceAllowedModules( $type, $level)
Limit the highest level of CSS/JS untrustworthiness allowed.
static htmlHeader(array $attribs=[])
Constructs the opening html-tag with necessary doctypes depending on global variables.
Definition: Html.php:962
__construct(IContextSource $context)
Constructor for OutputPage.
Definition: OutputPage.php:333
enableSectionEditLinks( $flag=true)
Enables/disables section edit links, doesn&#39;t override NOEDITSECTION
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition: hooks.txt:780
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
static makeInlineScript( $script, $nonce=null)
Returns an HTML script tag that runs given JS code after startup and base modules.
static makeLoad(ResourceLoaderContext $mainContext, array $modules, $only, array $extraQuery=[], $nonce=null)
Explicily load or embed modules on a page.
static newFromAnon()
Get a ParserOptions object for an anonymous user.
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:4936
addScriptFile( $file, $unused=null)
Add a JavaScript file to be loaded as <script> on this page.
Definition: OutputPage.php:460
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:448
array $mCategoryLinks
Definition: OutputPage.php:115
static getSelectFields()
Fields that LinkCache needs to select.
Definition: LinkCache.php:218
parseAsContent( $text, $linestart=true)
Parse wikitext in the page content language and return the HTML.
setTitle(Title $t)
Set the Title object to use.
addBacklinkSubtitle(Title $title, $query=[])
Add a subtitle containing a backlink to a page.
static transformFilePath( $remotePathPrefix, $localPath, $file)
Utility method for transformResourceFilePath().
int $mStatusCode
Definition: OutputPage.php:106
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:617
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:686
setLastModified( $timestamp)
Override the last modified timestamp.
Definition: OutputPage.php:834
addHeadItem( $name, $value)
Add or replace a head item to the output.
Definition: OutputPage.php:645
string $mLastModified
Used for sending cache control.
Definition: OutputPage.php:112
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
wfSetVar(&$dest, $source, $force=false)
Sets dest to source and returns the original value of dest If source is NULL, it just returns the val...
addWikiTextTidy( $text, $linestart=true)
Add wikitext in content language.
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping $template
Definition: hooks.txt:780
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:620
const PROTO_CANONICAL
Definition: Defines.php:223
static transformCssMedia( $media)
Transform "media" attribute based on request parameters.
addParserOutputContent(ParserOutput $parserOutput, $poOptions=[])
Add the HTML and enhancements for it (like ResourceLoader modules) associated with a ParserOutput obj...
versionRequired( $version)
Display an error page indicating that a given version of MediaWiki is required to use it...
int $mRevisionId
To include the variable {{REVISIONID}}.
Definition: OutputPage.php:246
getSyndicationLinks()
Return URLs for each supported syndication format for this page.
getText( $options=[])
Get the output HTML.
addContentOverride(LinkTarget $target, Content $content)
Add a mapping from a LinkTarget to a Content, for things like page preview.
Definition: OutputPage.php:600
array $contentOverrides
Map Title to Content.
Definition: OutputPage.php:307
feedLink( $type, $url, $text)
Generate a "<link rel/>" for a feed.
getOriginTrials()
Get the Origin-Trial header values.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
string $mRedirectCode
Definition: OutputPage.php:180
array $mImageTimeKeys
Definition: OutputPage.php:177
getResourceLoader()
Get a ResourceLoader object associated with this OutputPage.
getFeaturePolicyReportOnly()
getRevisionId()
Get the displayed revision ID.
wfClearOutputBuffers()
More legible than passing a &#39;false&#39; parameter to wfResetOutputBuffers():
getFileVersion()
Get the displayed file version.
addWikiTextTitleInternal( $text, Title $title, $linestart, $tidy, $interface, $wrapperClass=null)
Add wikitext with a custom Title object.
returnToMain( $unused=null, $returnto=null, $returntoquery=null)
Add a "return to" link pointing to a specified title, or the title indicated in the request...
addWikiTextTitleTidy( $text, Title $title, $linestart=true)
Add wikitext in content language with a custom Title object.
setArticleRelated( $newVal)
Set whether this page is related an article on the wiki Setting false will cause the change of "artic...
forceHideNewSectionLink()
Forcibly hide the new section link?
static isXmlMimeType( $mimetype)
Determines if the given MIME type is xml.
Definition: Html.php:1000
isRedirect( $flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition: Title.php:3029
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:620
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
addHTML( $text)
Append $text to the body HTML.
getRedirect()
Get the URL to redirect to, or an empty string if not redirect URL set.
Definition: OutputPage.php:354
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:780
getKeyHeader()
Get a complete Key header.
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
addWikiText( $text, $linestart=true, $interface=true)
Convert wikitext to HTML and add it to the buffer Default assumes that the current page title will be...
array $mSubtitle
Contains the page subtitle.
Definition: OutputPage.php:100
showLagWarning( $lag)
Show a warning about replica DB lag.
static removeHTMLtags( $text, $processCallback=null, $args=[], $extratags=[], $removetags=[], $warnCallback=null)
Cleans up HTML, removes dangerous tags and attributes, and removes HTML comments. ...
Definition: Sanitizer.php:497
showPermissionsErrorPage(array $errors, $action=null)
Output a standard permission error page.
getCacheVaryCookies()
Get the list of cookie names that will influence the cache.
ResourceLoaderContext $rlClientContext
Definition: OutputPage.php:165
addElement( $element, array $attribs=[], $contents='')
Shortcut for adding an Html::element via addHTML.
setModules(array $modules)
Ensure one or more modules are loaded.
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:52
getCSPNonce()
Get (and set if not yet set) the CSP nonce.
parseInline( $text, $linestart=true, $interface=false)
Parse wikitext, strip paragraph wrapper, and return the HTML.
parserOptions( $options=null)
Get/set the ParserOptions object to use for wikitext parsing.
$content
Definition: pageupdater.txt:72
setFeedAppendQuery( $val)
Add default feeds to the page header This is mainly kept for backward compatibility, see OutputPage::addFeedLink() for the new version.
getWikiPage()
Get the WikiPage object.
getSkinName()
Definition: Skin.php:156
setIndicators(array $indicators)
Add an array of indicators, with their identifiers as array keys and HTML contents as values...
setCanonicalUrl( $url)
Set the URL to be used for the <link rel="canonical">.
Definition: OutputPage.php:426
bool $mIsArticle
Is the displayed content related to the source of the corresponding wiki article. ...
Definition: OutputPage.php:82
disallowUserJs()
Do not allow scripts which can be modified by wiki users to load on this page; only allow scripts bun...
getOrigin()
Get this module&#39;s origin.
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2633
setIndexPolicy( $policy)
Set the index policy for the page, but leave the follow policy un- touched.
Definition: OutputPage.php:864
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:477
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition: hooks.txt:2217
string $mRedirect
Definition: OutputPage.php:103
static bcp47( $code)
Get the normalised IETF language tag See unit test for examples.
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1473
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:417
setRevisionTimestamp( $timestamp)
Set the timestamp of the revision which will be displayed.
ResourceLoaderClientHtml $rlClient
Definition: OutputPage.php:162
isArticle()
Return whether the content displayed page is related to the source of the corresponding article on th...
addWikiMsgArray( $name, $args)
Add a wikitext-formatted message to the output.
Object passed around to modules which contains information about the state of a specific loader reque...
ParserOptions $mParserOptions
lazy initialised, use parserOptions()
Definition: OutputPage.php:205
showErrorPage( $title, $msg, $params=[])
Output a standard error page.
getLinkHeader()
Return a Link: header.
addParserOutputText(ParserOutput $parserOutput, $poOptions=[])
Add the HTML associated with a ParserOutput object, without any metadata.
getProperty( $name)
Get an additional output property.
Definition: OutputPage.php:717
setDisplayTitle( $html)
Same as page title but only contains name of the page, not any other text.
Definition: OutputPage.php:962
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:319
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition: Article.php:1027