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 
555  public function getModuleScripts() {
556  wfDeprecated( __METHOD__, '1.33' );
557  return [];
558  }
559 
567  public function getModuleStyles( $filter = false, $position = null ) {
568  return $this->getModules( $filter, null, 'mModuleStyles',
570  );
571  }
572 
582  public function addModuleStyles( $modules ) {
583  $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
584  }
585 
589  public function getTarget() {
590  return $this->mTarget;
591  }
592 
598  public function setTarget( $target ) {
599  $this->mTarget = $target;
600  }
601 
609  public function addContentOverride( LinkTarget $target, Content $content ) {
610  if ( !$this->contentOverrides ) {
611  // Register a callback for $this->contentOverrides on the first call
612  $this->addContentOverrideCallback( function ( LinkTarget $target ) {
613  $key = $target->getNamespace() . ':' . $target->getDBkey();
614  return $this->contentOverrides[$key] ?? null;
615  } );
616  }
617 
618  $key = $target->getNamespace() . ':' . $target->getDBkey();
619  $this->contentOverrides[$key] = $content;
620  }
621 
629  public function addContentOverrideCallback( callable $callback ) {
630  $this->contentOverrideCallbacks[] = $callback;
631  }
632 
638  function getHeadItemsArray() {
639  return $this->mHeadItems;
640  }
641 
654  public function addHeadItem( $name, $value ) {
655  $this->mHeadItems[$name] = $value;
656  }
657 
664  public function addHeadItems( $values ) {
665  $this->mHeadItems = array_merge( $this->mHeadItems, (array)$values );
666  }
667 
674  public function hasHeadItem( $name ) {
675  return isset( $this->mHeadItems[$name] );
676  }
677 
684  public function addBodyClasses( $classes ) {
685  $this->mAdditionalBodyClasses = array_merge( $this->mAdditionalBodyClasses, (array)$classes );
686  }
687 
695  public function setArticleBodyOnly( $only ) {
696  $this->mArticleBodyOnly = $only;
697  }
698 
704  public function getArticleBodyOnly() {
706  }
707 
715  public function setProperty( $name, $value ) {
716  $this->mProperties[$name] = $value;
717  }
718 
726  public function getProperty( $name ) {
727  return $this->mProperties[$name] ?? null;
728  }
729 
741  public function checkLastModified( $timestamp ) {
742  if ( !$timestamp || $timestamp == '19700101000000' ) {
743  wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
744  return false;
745  }
746  $config = $this->getConfig();
747  if ( !$config->get( 'CachePages' ) ) {
748  wfDebug( __METHOD__ . ": CACHE DISABLED\n" );
749  return false;
750  }
751 
752  $timestamp = wfTimestamp( TS_MW, $timestamp );
753  $modifiedTimes = [
754  'page' => $timestamp,
755  'user' => $this->getUser()->getTouched(),
756  'epoch' => $config->get( 'CacheEpoch' )
757  ];
758  if ( $config->get( 'UseSquid' ) ) {
759  $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, $this->getCdnCacheEpoch(
760  time(),
761  $config->get( 'SquidMaxage' )
762  ) );
763  }
764  Hooks::run( 'OutputPageCheckLastModified', [ &$modifiedTimes, $this ] );
765 
766  $maxModified = max( $modifiedTimes );
767  $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
768 
769  $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' );
770  if ( $clientHeader === false ) {
771  wfDebug( __METHOD__ . ": client did not send If-Modified-Since header", 'private' );
772  return false;
773  }
774 
775  # IE sends sizes after the date like this:
776  # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
777  # this breaks strtotime().
778  $clientHeader = preg_replace( '/;.*$/', '', $clientHeader );
779 
780  Wikimedia\suppressWarnings(); // E_STRICT system time warnings
781  $clientHeaderTime = strtotime( $clientHeader );
782  Wikimedia\restoreWarnings();
783  if ( !$clientHeaderTime ) {
784  wfDebug( __METHOD__
785  . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
786  return false;
787  }
788  $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
789 
790  # Make debug info
791  $info = '';
792  foreach ( $modifiedTimes as $name => $value ) {
793  if ( $info !== '' ) {
794  $info .= ', ';
795  }
796  $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
797  }
798 
799  wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
800  wfTimestamp( TS_ISO_8601, $clientHeaderTime ), 'private' );
801  wfDebug( __METHOD__ . ": effective Last-Modified: " .
802  wfTimestamp( TS_ISO_8601, $maxModified ), 'private' );
803  if ( $clientHeaderTime < $maxModified ) {
804  wfDebug( __METHOD__ . ": STALE, $info", 'private' );
805  return false;
806  }
807 
808  # Not modified
809  # Give a 304 Not Modified response code and disable body output
810  wfDebug( __METHOD__ . ": NOT MODIFIED, $info", 'private' );
811  ini_set( 'zlib.output_compression', 0 );
812  $this->getRequest()->response()->statusHeader( 304 );
813  $this->sendCacheControl();
814  $this->disable();
815 
816  // Don't output a compressed blob when using ob_gzhandler;
817  // it's technically against HTTP spec and seems to confuse
818  // Firefox when the response gets split over two packets.
820 
821  return true;
822  }
823 
829  private function getCdnCacheEpoch( $reqTime, $maxAge ) {
830  // Ensure Last-Modified is never more than (wgSquidMaxage) in the past,
831  // because even if the wiki page content hasn't changed since, static
832  // resources may have changed (skin HTML, interface messages, urls, etc.)
833  // and must roll-over in a timely manner (T46570)
834  return $reqTime - $maxAge;
835  }
836 
843  public function setLastModified( $timestamp ) {
844  $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp );
845  }
846 
855  public function setRobotPolicy( $policy ) {
856  $policy = Article::formatRobotPolicy( $policy );
857 
858  if ( isset( $policy['index'] ) ) {
859  $this->setIndexPolicy( $policy['index'] );
860  }
861  if ( isset( $policy['follow'] ) ) {
862  $this->setFollowPolicy( $policy['follow'] );
863  }
864  }
865 
873  public function setIndexPolicy( $policy ) {
874  $policy = trim( $policy );
875  if ( in_array( $policy, [ 'index', 'noindex' ] ) ) {
876  $this->mIndexPolicy = $policy;
877  }
878  }
879 
887  public function setFollowPolicy( $policy ) {
888  $policy = trim( $policy );
889  if ( in_array( $policy, [ 'follow', 'nofollow' ] ) ) {
890  $this->mFollowPolicy = $policy;
891  }
892  }
893 
900  public function setHTMLTitle( $name ) {
901  if ( $name instanceof Message ) {
902  $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
903  } else {
904  $this->mHTMLtitle = $name;
905  }
906  }
907 
913  public function getHTMLTitle() {
914  return $this->mHTMLtitle;
915  }
916 
922  public function setRedirectedFrom( $t ) {
923  $this->mRedirectedFrom = $t;
924  }
925 
938  public function setPageTitle( $name ) {
939  if ( $name instanceof Message ) {
940  $name = $name->setContext( $this->getContext() )->text();
941  }
942 
943  # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
944  # but leave "<i>foobar</i>" alone
946  $this->mPageTitle = $nameWithTags;
947 
948  # change "<i>foo&amp;bar</i>" to "foo&bar"
949  $this->setHTMLTitle(
950  $this->msg( 'pagetitle' )->plaintextParams( Sanitizer::stripAllTags( $nameWithTags ) )
951  ->inContentLanguage()
952  );
953  }
954 
960  public function getPageTitle() {
961  return $this->mPageTitle;
962  }
963 
971  public function setDisplayTitle( $html ) {
972  $this->displayTitle = $html;
973  }
974 
983  public function getDisplayTitle() {
985  if ( $html === null ) {
986  $html = $this->getTitle()->getPrefixedText();
987  }
988 
990  }
991 
998  public function getUnprefixedDisplayTitle() {
999  $text = $this->getDisplayTitle();
1000  $nsPrefix = $this->getTitle()->getNsText() . ':';
1001  $prefix = preg_quote( $nsPrefix, '/' );
1002 
1003  return preg_replace( "/^$prefix/i", '', $text );
1004  }
1005 
1011  public function setTitle( Title $t ) {
1012  $this->getContext()->setTitle( $t );
1013  }
1014 
1020  public function setSubtitle( $str ) {
1021  $this->clearSubtitle();
1022  $this->addSubtitle( $str );
1023  }
1024 
1030  public function addSubtitle( $str ) {
1031  if ( $str instanceof Message ) {
1032  $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
1033  } else {
1034  $this->mSubtitle[] = $str;
1035  }
1036  }
1037 
1046  public static function buildBacklinkSubtitle( Title $title, $query = [] ) {
1047  if ( $title->isRedirect() ) {
1048  $query['redirect'] = 'no';
1049  }
1050  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1051  return wfMessage( 'backlinksubtitle' )
1052  ->rawParams( $linkRenderer->makeLink( $title, null, [], $query ) );
1053  }
1054 
1061  public function addBacklinkSubtitle( Title $title, $query = [] ) {
1062  $this->addSubtitle( self::buildBacklinkSubtitle( $title, $query ) );
1063  }
1064 
1068  public function clearSubtitle() {
1069  $this->mSubtitle = [];
1070  }
1071 
1077  public function getSubtitle() {
1078  return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
1079  }
1080 
1085  public function setPrintable() {
1086  $this->mPrintable = true;
1087  }
1088 
1094  public function isPrintable() {
1095  return $this->mPrintable;
1096  }
1097 
1101  public function disable() {
1102  $this->mDoNothing = true;
1103  }
1104 
1110  public function isDisabled() {
1111  return $this->mDoNothing;
1112  }
1113 
1119  public function showNewSectionLink() {
1120  return $this->mNewSectionLink;
1121  }
1122 
1128  public function forceHideNewSectionLink() {
1130  }
1131 
1140  public function setSyndicated( $show = true ) {
1141  if ( $show ) {
1142  $this->setFeedAppendQuery( false );
1143  } else {
1144  $this->mFeedLinks = [];
1145  }
1146  }
1147 
1154  protected function getAdvertisedFeedTypes() {
1155  if ( $this->getConfig()->get( 'Feed' ) ) {
1156  return $this->getConfig()->get( 'AdvertisedFeedTypes' );
1157  } else {
1158  return [];
1159  }
1160  }
1161 
1171  public function setFeedAppendQuery( $val ) {
1172  $this->mFeedLinks = [];
1173 
1174  foreach ( $this->getAdvertisedFeedTypes() as $type ) {
1175  $query = "feed=$type";
1176  if ( is_string( $val ) ) {
1177  $query .= '&' . $val;
1178  }
1179  $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
1180  }
1181  }
1182 
1189  public function addFeedLink( $format, $href ) {
1190  if ( in_array( $format, $this->getAdvertisedFeedTypes() ) ) {
1191  $this->mFeedLinks[$format] = $href;
1192  }
1193  }
1194 
1199  public function isSyndicated() {
1200  return count( $this->mFeedLinks ) > 0;
1201  }
1202 
1207  public function getSyndicationLinks() {
1208  return $this->mFeedLinks;
1209  }
1210 
1216  public function getFeedAppendQuery() {
1218  }
1219 
1227  public function setArticleFlag( $newVal ) {
1228  $this->mIsArticle = $newVal;
1229  if ( $newVal ) {
1230  $this->mIsArticleRelated = $newVal;
1231  }
1232  }
1233 
1240  public function isArticle() {
1241  return $this->mIsArticle;
1242  }
1243 
1250  public function setArticleRelated( $newVal ) {
1251  $this->mIsArticleRelated = $newVal;
1252  if ( !$newVal ) {
1253  $this->mIsArticle = false;
1254  }
1255  }
1256 
1262  public function isArticleRelated() {
1263  return $this->mIsArticleRelated;
1264  }
1265 
1271  public function setCopyright( $hasCopyright ) {
1272  $this->mHasCopyright = $hasCopyright;
1273  }
1274 
1284  public function showsCopyright() {
1285  return $this->isArticle() || $this->mHasCopyright;
1286  }
1287 
1294  public function addLanguageLinks( array $newLinkArray ) {
1295  $this->mLanguageLinks = array_merge( $this->mLanguageLinks, $newLinkArray );
1296  }
1297 
1304  public function setLanguageLinks( array $newLinkArray ) {
1305  $this->mLanguageLinks = $newLinkArray;
1306  }
1307 
1313  public function getLanguageLinks() {
1314  return $this->mLanguageLinks;
1315  }
1316 
1322  public function addCategoryLinks( array $categories ) {
1323  if ( !$categories ) {
1324  return;
1325  }
1326 
1327  $res = $this->addCategoryLinksToLBAndGetResult( $categories );
1328 
1329  # Set all the values to 'normal'.
1330  $categories = array_fill_keys( array_keys( $categories ), 'normal' );
1331 
1332  # Mark hidden categories
1333  foreach ( $res as $row ) {
1334  if ( isset( $row->pp_value ) ) {
1335  $categories[$row->page_title] = 'hidden';
1336  }
1337  }
1338 
1339  // Avoid PHP 7.1 warning of passing $this by reference
1340  $outputPage = $this;
1341  # Add the remaining categories to the skin
1342  if ( Hooks::run(
1343  'OutputPageMakeCategoryLinks',
1344  [ &$outputPage, $categories, &$this->mCategoryLinks ] )
1345  ) {
1346  $services = MediaWikiServices::getInstance();
1347  $linkRenderer = $services->getLinkRenderer();
1348  foreach ( $categories as $category => $type ) {
1349  // array keys will cast numeric category names to ints, so cast back to string
1350  $category = (string)$category;
1351  $origcategory = $category;
1352  $title = Title::makeTitleSafe( NS_CATEGORY, $category );
1353  if ( !$title ) {
1354  continue;
1355  }
1356  $services->getContentLanguage()->findVariantLink( $category, $title, true );
1357  if ( $category != $origcategory && array_key_exists( $category, $categories ) ) {
1358  continue;
1359  }
1360  $text = $services->getContentLanguage()->convertHtml( $title->getText() );
1361  $this->mCategories[$type][] = $title->getText();
1362  $this->mCategoryLinks[$type][] = $linkRenderer->makeLink( $title, new HtmlArmor( $text ) );
1363  }
1364  }
1365  }
1366 
1371  protected function addCategoryLinksToLBAndGetResult( array $categories ) {
1372  # Add the links to a LinkBatch
1373  $arr = [ NS_CATEGORY => $categories ];
1374  $lb = new LinkBatch;
1375  $lb->setArray( $arr );
1376 
1377  # Fetch existence plus the hiddencat property
1378  $dbr = wfGetDB( DB_REPLICA );
1379  $fields = array_merge(
1381  [ 'page_namespace', 'page_title', 'pp_value' ]
1382  );
1383 
1384  $res = $dbr->select( [ 'page', 'page_props' ],
1385  $fields,
1386  $lb->constructSet( 'page', $dbr ),
1387  __METHOD__,
1388  [],
1389  [ 'page_props' => [ 'LEFT JOIN', [
1390  'pp_propname' => 'hiddencat',
1391  'pp_page = page_id'
1392  ] ] ]
1393  );
1394 
1395  # Add the results to the link cache
1396  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1397  $lb->addResultToCache( $linkCache, $res );
1398 
1399  return $res;
1400  }
1401 
1407  public function setCategoryLinks( array $categories ) {
1408  $this->mCategoryLinks = [];
1409  $this->addCategoryLinks( $categories );
1410  }
1411 
1420  public function getCategoryLinks() {
1421  return $this->mCategoryLinks;
1422  }
1423 
1433  public function getCategories( $type = 'all' ) {
1434  if ( $type === 'all' ) {
1435  $allCategories = [];
1436  foreach ( $this->mCategories as $categories ) {
1437  $allCategories = array_merge( $allCategories, $categories );
1438  }
1439  return $allCategories;
1440  }
1441  if ( !isset( $this->mCategories[$type] ) ) {
1442  throw new InvalidArgumentException( 'Invalid category type given: ' . $type );
1443  }
1444  return $this->mCategories[$type];
1445  }
1446 
1456  public function setIndicators( array $indicators ) {
1457  $this->mIndicators = $indicators + $this->mIndicators;
1458  // Keep ordered by key
1459  ksort( $this->mIndicators );
1460  }
1461 
1470  public function getIndicators() {
1471  return $this->mIndicators;
1472  }
1473 
1482  public function addHelpLink( $to, $overrideBaseUrl = false ) {
1483  $this->addModuleStyles( 'mediawiki.helplink' );
1484  $text = $this->msg( 'helppage-top-gethelp' )->escaped();
1485 
1486  if ( $overrideBaseUrl ) {
1487  $helpUrl = $to;
1488  } else {
1489  $toUrlencoded = wfUrlencode( str_replace( ' ', '_', $to ) );
1490  $helpUrl = "//www.mediawiki.org/wiki/Special:MyLanguage/$toUrlencoded";
1491  }
1492 
1494  'a',
1495  [
1496  'href' => $helpUrl,
1497  'target' => '_blank',
1498  'class' => 'mw-helplink',
1499  ],
1500  $text
1501  );
1502 
1503  $this->setIndicators( [ 'mw-helplink' => $link ] );
1504  }
1505 
1514  public function disallowUserJs() {
1515  $this->reduceAllowedModules(
1518  );
1519 
1520  // Site-wide styles are controlled by a config setting, see T73621
1521  // for background on why. User styles are never allowed.
1522  if ( $this->getConfig()->get( 'AllowSiteCSSOnRestrictedPages' ) ) {
1524  } else {
1526  }
1527  $this->reduceAllowedModules(
1529  $styleOrigin
1530  );
1531  }
1532 
1539  public function getAllowedModules( $type ) {
1541  return min( array_values( $this->mAllowedModules ) );
1542  } else {
1543  return $this->mAllowedModules[$type] ?? ResourceLoaderModule::ORIGIN_ALL;
1544  }
1545  }
1546 
1556  public function reduceAllowedModules( $type, $level ) {
1557  $this->mAllowedModules[$type] = min( $this->getAllowedModules( $type ), $level );
1558  }
1559 
1565  public function prependHTML( $text ) {
1566  $this->mBodytext = $text . $this->mBodytext;
1567  }
1568 
1574  public function addHTML( $text ) {
1575  $this->mBodytext .= $text;
1576  }
1577 
1587  public function addElement( $element, array $attribs = [], $contents = '' ) {
1588  $this->addHTML( Html::element( $element, $attribs, $contents ) );
1589  }
1590 
1594  public function clearHTML() {
1595  $this->mBodytext = '';
1596  }
1597 
1603  public function getHTML() {
1604  return $this->mBodytext;
1605  }
1606 
1614  public function parserOptions( $options = null ) {
1615  if ( $options !== null ) {
1616  wfDeprecated( __METHOD__ . ' with non-null $options', '1.31' );
1617  }
1618 
1619  if ( $options !== null && !empty( $options->isBogus ) ) {
1620  // Someone is trying to set a bogus pre-$wgUser PO. Check if it has
1621  // been changed somehow, and keep it if so.
1622  $anonPO = ParserOptions::newFromAnon();
1623  $anonPO->setAllowUnsafeRawHtml( false );
1624  if ( !$options->matches( $anonPO ) ) {
1625  wfLogWarning( __METHOD__ . ': Setting a changed bogus ParserOptions: ' . wfGetAllCallers( 5 ) );
1626  $options->isBogus = false;
1627  }
1628  }
1629 
1630  if ( !$this->mParserOptions ) {
1631  if ( !$this->getUser()->isSafeToLoad() ) {
1632  // $wgUser isn't unstubbable yet, so don't try to get a
1633  // ParserOptions for it. And don't cache this ParserOptions
1634  // either.
1636  $po->setAllowUnsafeRawHtml( false );
1637  $po->isBogus = true;
1638  if ( $options !== null ) {
1639  $this->mParserOptions = empty( $options->isBogus ) ? $options : null;
1640  }
1641  return $po;
1642  }
1643 
1644  $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
1645  $this->mParserOptions->setAllowUnsafeRawHtml( false );
1646  }
1647 
1648  if ( $options !== null && !empty( $options->isBogus ) ) {
1649  // They're trying to restore the bogus pre-$wgUser PO. Do the right
1650  // thing.
1651  return wfSetVar( $this->mParserOptions, null, true );
1652  } else {
1653  return wfSetVar( $this->mParserOptions, $options );
1654  }
1655  }
1656 
1664  public function setRevisionId( $revid ) {
1665  $val = is_null( $revid ) ? null : intval( $revid );
1666  return wfSetVar( $this->mRevisionId, $val, true );
1667  }
1668 
1674  public function getRevisionId() {
1675  return $this->mRevisionId;
1676  }
1677 
1685  public function setRevisionTimestamp( $timestamp ) {
1686  return wfSetVar( $this->mRevisionTimestamp, $timestamp, true );
1687  }
1688 
1695  public function getRevisionTimestamp() {
1697  }
1698 
1705  public function setFileVersion( $file ) {
1706  $val = null;
1707  if ( $file instanceof File && $file->exists() ) {
1708  $val = [ 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ];
1709  }
1710  return wfSetVar( $this->mFileVersion, $val, true );
1711  }
1712 
1718  public function getFileVersion() {
1719  return $this->mFileVersion;
1720  }
1721 
1728  public function getTemplateIds() {
1729  return $this->mTemplateIds;
1730  }
1731 
1738  public function getFileSearchOptions() {
1739  return $this->mImageTimeKeys;
1740  }
1741 
1754  public function addWikiText( $text, $linestart = true, $interface = true ) {
1755  wfDeprecated( __METHOD__, '1.32' );
1756  $title = $this->getTitle();
1757  if ( !$title ) {
1758  throw new MWException( 'Title is null' );
1759  }
1760  $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/false, $interface );
1761  }
1762 
1779  public function addWikiTextAsInterface(
1780  $text, $linestart = true, Title $title = null
1781  ) {
1782  if ( $title === null ) {
1783  $title = $this->getTitle();
1784  }
1785  if ( !$title ) {
1786  throw new MWException( 'Title is null' );
1787  }
1788  $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/true, /*interface*/true );
1789  }
1790 
1804  public function wrapWikiTextAsInterface(
1805  $wrapperClass, $text
1806  ) {
1807  $this->addWikiTextTitleInternal(
1808  $text, $this->getTitle(),
1809  /*linestart*/true, /*tidy*/true, /*interface*/true,
1810  $wrapperClass
1811  );
1812  }
1813 
1829  public function addWikiTextAsContent(
1830  $text, $linestart = true, Title $title = null
1831  ) {
1832  if ( $title === null ) {
1833  $title = $this->getTitle();
1834  }
1835  if ( !$title ) {
1836  throw new MWException( 'Title is null' );
1837  }
1838  $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/true, /*interface*/false );
1839  }
1840 
1850  public function addWikiTextWithTitle( $text, Title $title, $linestart = true ) {
1851  wfDeprecated( __METHOD__, '1.32' );
1852  $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/false, /*interface*/false );
1853  }
1854 
1865  function addWikiTextTitleTidy( $text, Title $title, $linestart = true ) {
1866  wfDeprecated( __METHOD__, '1.32' );
1867  $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/true, /*interface*/false );
1868  }
1869 
1878  public function addWikiTextTidy( $text, $linestart = true ) {
1879  wfDeprecated( __METHOD__, '1.32' );
1880  $title = $this->getTitle();
1881  if ( !$title ) {
1882  throw new MWException( 'Title is null' );
1883  }
1884  $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/true, /*interface*/false );
1885  }
1886 
1906  public function addWikiTextTitle( $text, Title $title, $linestart,
1907  $tidy = false, $interface = false
1908  ) {
1909  wfDeprecated( __METHOD__, '1.32' );
1910  return $this->addWikiTextTitleInternal( $text, $title, $linestart, $tidy, $interface );
1911  }
1912 
1929  private function addWikiTextTitleInternal(
1930  $text, Title $title, $linestart, $tidy, $interface, $wrapperClass = null
1931  ) {
1932  if ( !$tidy ) {
1933  wfDeprecated( 'disabling tidy', '1.32' );
1934  }
1935 
1936  $parserOutput = $this->parseInternal(
1937  $text, $title, $linestart, $tidy, $interface, /*language*/null
1938  );
1939 
1940  $this->addParserOutput( $parserOutput, [
1941  'enableSectionEditLinks' => false,
1942  'wrapperDivClass' => $wrapperClass ?? '',
1943  ] );
1944  }
1945 
1954  public function addParserOutputMetadata( ParserOutput $parserOutput ) {
1955  $this->mLanguageLinks =
1956  array_merge( $this->mLanguageLinks, $parserOutput->getLanguageLinks() );
1957  $this->addCategoryLinks( $parserOutput->getCategories() );
1958  $this->setIndicators( $parserOutput->getIndicators() );
1959  $this->mNewSectionLink = $parserOutput->getNewSection();
1960  $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
1961 
1962  if ( !$parserOutput->isCacheable() ) {
1963  $this->enableClientCache( false );
1964  }
1965  $this->mNoGallery = $parserOutput->getNoGallery();
1966  $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
1967  $this->addModules( $parserOutput->getModules() );
1968  $this->addModuleStyles( $parserOutput->getModuleStyles() );
1969  $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1970  $this->mPreventClickjacking = $this->mPreventClickjacking
1971  || $parserOutput->preventClickjacking();
1972 
1973  // Template versioning...
1974  foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
1975  if ( isset( $this->mTemplateIds[$ns] ) ) {
1976  $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
1977  } else {
1978  $this->mTemplateIds[$ns] = $dbks;
1979  }
1980  }
1981  // File versioning...
1982  foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) {
1983  $this->mImageTimeKeys[$dbk] = $data;
1984  }
1985 
1986  // Hooks registered in the object
1987  $parserOutputHooks = $this->getConfig()->get( 'ParserOutputHooks' );
1988  foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
1989  list( $hookName, $data ) = $hookInfo;
1990  if ( isset( $parserOutputHooks[$hookName] ) ) {
1991  $parserOutputHooks[$hookName]( $this, $parserOutput, $data );
1992  }
1993  }
1994 
1995  // Enable OOUI if requested via ParserOutput
1996  if ( $parserOutput->getEnableOOUI() ) {
1997  $this->enableOOUI();
1998  }
1999 
2000  // Include parser limit report
2001  if ( !$this->limitReportJSData ) {
2002  $this->limitReportJSData = $parserOutput->getLimitReportJSData();
2003  }
2004 
2005  // Link flags are ignored for now, but may in the future be
2006  // used to mark individual language links.
2007  $linkFlags = [];
2008  // Avoid PHP 7.1 warning of passing $this by reference
2009  $outputPage = $this;
2010  Hooks::run( 'LanguageLinks', [ $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ] );
2011  Hooks::runWithoutAbort( 'OutputPageParserOutput', [ &$outputPage, $parserOutput ] );
2012 
2013  // This check must be after 'OutputPageParserOutput' runs in addParserOutputMetadata
2014  // so that extensions may modify ParserOutput to toggle TOC.
2015  // This cannot be moved to addParserOutputText because that is not
2016  // called by EditPage for Preview.
2017  if ( $parserOutput->getTOCHTML() ) {
2018  $this->mEnableTOC = true;
2019  }
2020  }
2021 
2030  public function addParserOutputContent( ParserOutput $parserOutput, $poOptions = [] ) {
2031  $this->addParserOutputText( $parserOutput, $poOptions );
2032 
2033  $this->addModules( $parserOutput->getModules() );
2034  $this->addModuleStyles( $parserOutput->getModuleStyles() );
2035 
2036  $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
2037  }
2038 
2046  public function addParserOutputText( ParserOutput $parserOutput, $poOptions = [] ) {
2047  $text = $parserOutput->getText( $poOptions );
2048  // Avoid PHP 7.1 warning of passing $this by reference
2049  $outputPage = $this;
2050  Hooks::runWithoutAbort( 'OutputPageBeforeHTML', [ &$outputPage, &$text ] );
2051  $this->addHTML( $text );
2052  }
2053 
2060  function addParserOutput( ParserOutput $parserOutput, $poOptions = [] ) {
2061  $this->addParserOutputMetadata( $parserOutput );
2062  $this->addParserOutputText( $parserOutput, $poOptions );
2063  }
2064 
2070  public function addTemplate( &$template ) {
2071  $this->addHTML( $template->getHTML() );
2072  }
2073 
2092  public function parse( $text, $linestart = true, $interface = false, $language = null ) {
2093  wfDeprecated( __METHOD__, '1.33' );
2094  return $this->parseInternal(
2095  $text, $this->getTitle(), $linestart, /*tidy*/false, $interface, $language
2096  )->getText( [
2097  'enableSectionEditLinks' => false,
2098  ] );
2099  }
2100 
2112  public function parseAsContent( $text, $linestart = true ) {
2113  return $this->parseInternal(
2114  $text, $this->getTitle(), $linestart, /*tidy*/true, /*interface*/false, /*language*/null
2115  )->getText( [
2116  'enableSectionEditLinks' => false,
2117  'wrapperDivClass' => ''
2118  ] );
2119  }
2120 
2133  public function parseAsInterface( $text, $linestart = true ) {
2134  return $this->parseInternal(
2135  $text, $this->getTitle(), $linestart, /*tidy*/true, /*interface*/true, /*language*/null
2136  )->getText( [
2137  'enableSectionEditLinks' => false,
2138  'wrapperDivClass' => ''
2139  ] );
2140  }
2141 
2156  public function parseInlineAsInterface( $text, $linestart = true ) {
2158  $this->parseAsInterface( $text, $linestart )
2159  );
2160  }
2161 
2175  public function parseInline( $text, $linestart = true, $interface = false ) {
2176  wfDeprecated( __METHOD__, '1.33' );
2177  $parsed = $this->parseInternal(
2178  $text, $this->getTitle(), $linestart, /*tidy*/false, $interface, /*language*/null
2179  )->getText( [
2180  'enableSectionEditLinks' => false,
2181  'wrapperDivClass' => '', /* no wrapper div */
2182  ] );
2183  return Parser::stripOuterParagraph( $parsed );
2184  }
2185 
2200  private function parseInternal( $text, $title, $linestart, $tidy, $interface, $language ) {
2201  global $wgParser;
2202 
2203  if ( is_null( $title ) ) {
2204  throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
2205  }
2206 
2207  $popts = $this->parserOptions();
2208  $oldTidy = $popts->setTidy( $tidy );
2209  $oldInterface = $popts->setInterfaceMessage( (bool)$interface );
2210 
2211  if ( $language !== null ) {
2212  $oldLang = $popts->setTargetLanguage( $language );
2213  }
2214 
2215  $parserOutput = $wgParser->getFreshParser()->parse(
2216  $text, $title, $popts,
2217  $linestart, true, $this->mRevisionId
2218  );
2219 
2220  $popts->setTidy( $oldTidy );
2221  $popts->setInterfaceMessage( $oldInterface );
2222 
2223  if ( $language !== null ) {
2224  $popts->setTargetLanguage( $oldLang );
2225  }
2226 
2227  return $parserOutput;
2228  }
2229 
2235  public function setCdnMaxage( $maxage ) {
2236  $this->mCdnMaxage = min( $maxage, $this->mCdnMaxageLimit );
2237  }
2238 
2248  public function lowerCdnMaxage( $maxage ) {
2249  $this->mCdnMaxageLimit = min( $maxage, $this->mCdnMaxageLimit );
2250  $this->setCdnMaxage( $this->mCdnMaxage );
2251  }
2252 
2265  public function adaptCdnTTL( $mtime, $minTTL = 0, $maxTTL = 0 ) {
2266  $minTTL = $minTTL ?: IExpiringStore::TTL_MINUTE;
2267  $maxTTL = $maxTTL ?: $this->getConfig()->get( 'SquidMaxage' );
2268 
2269  if ( $mtime === null || $mtime === false ) {
2270  return $minTTL; // entity does not exist
2271  }
2272 
2273  $age = MWTimestamp::time() - wfTimestamp( TS_UNIX, $mtime );
2274  $adaptiveTTL = max( 0.9 * $age, $minTTL );
2275  $adaptiveTTL = min( $adaptiveTTL, $maxTTL );
2276 
2277  $this->lowerCdnMaxage( (int)$adaptiveTTL );
2278  }
2279 
2287  public function enableClientCache( $state ) {
2288  return wfSetVar( $this->mEnableClientCache, $state );
2289  }
2290 
2296  function getCacheVaryCookies() {
2297  if ( self::$cacheVaryCookies === null ) {
2298  $config = $this->getConfig();
2299  self::$cacheVaryCookies = array_values( array_unique( array_merge(
2300  SessionManager::singleton()->getVaryCookies(),
2301  [
2302  'forceHTTPS',
2303  ],
2304  $config->get( 'CacheVaryCookies' )
2305  ) ) );
2306  Hooks::run( 'GetCacheVaryCookies', [ $this, &self::$cacheVaryCookies ] );
2307  }
2308  return self::$cacheVaryCookies;
2309  }
2310 
2318  $request = $this->getRequest();
2319  foreach ( $this->getCacheVaryCookies() as $cookieName ) {
2320  if ( $request->getCookie( $cookieName, '', '' ) !== '' ) {
2321  wfDebug( __METHOD__ . ": found $cookieName\n" );
2322  return true;
2323  }
2324  }
2325  wfDebug( __METHOD__ . ": no cache-varying cookies found\n" );
2326  return false;
2327  }
2328 
2337  public function addVaryHeader( $header, array $option = null ) {
2338  if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
2339  $this->mVaryHeader[$header] = [];
2340  }
2341  if ( !is_array( $option ) ) {
2342  $option = [];
2343  }
2344  $this->mVaryHeader[$header] =
2345  array_unique( array_merge( $this->mVaryHeader[$header], $option ) );
2346  }
2347 
2354  public function getVaryHeader() {
2355  // If we vary on cookies, let's make sure it's always included here too.
2356  if ( $this->getCacheVaryCookies() ) {
2357  $this->addVaryHeader( 'Cookie' );
2358  }
2359 
2360  foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2361  $this->addVaryHeader( $header, $options );
2362  }
2363  return 'Vary: ' . implode( ', ', array_keys( $this->mVaryHeader ) );
2364  }
2365 
2371  public function addLinkHeader( $header ) {
2372  $this->mLinkHeader[] = $header;
2373  }
2374 
2380  public function getLinkHeader() {
2381  if ( !$this->mLinkHeader ) {
2382  return false;
2383  }
2384 
2385  return 'Link: ' . implode( ',', $this->mLinkHeader );
2386  }
2387 
2395  public function getKeyHeader() {
2396  wfDeprecated( '$wgUseKeyHeader', '1.32' );
2397 
2398  $cvCookies = $this->getCacheVaryCookies();
2399 
2400  $cookiesOption = [];
2401  foreach ( $cvCookies as $cookieName ) {
2402  $cookiesOption[] = 'param=' . $cookieName;
2403  }
2404  $this->addVaryHeader( 'Cookie', $cookiesOption );
2405 
2406  foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2407  $this->addVaryHeader( $header, $options );
2408  }
2409 
2410  $headers = [];
2411  foreach ( $this->mVaryHeader as $header => $option ) {
2412  $newheader = $header;
2413  if ( is_array( $option ) && count( $option ) > 0 ) {
2414  $newheader .= ';' . implode( ';', $option );
2415  }
2416  $headers[] = $newheader;
2417  }
2418  $key = 'Key: ' . implode( ',', $headers );
2419 
2420  return $key;
2421  }
2422 
2430  private function addAcceptLanguage() {
2431  $title = $this->getTitle();
2432  if ( !$title instanceof Title ) {
2433  return;
2434  }
2435 
2436  $lang = $title->getPageLanguage();
2437  if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
2438  $variants = $lang->getVariants();
2439  $aloption = [];
2440  foreach ( $variants as $variant ) {
2441  if ( $variant === $lang->getCode() ) {
2442  continue;
2443  }
2444 
2445  // XXX Note that this code is not strictly correct: we
2446  // do a case-insensitive match in
2447  // LanguageConverter::getHeaderVariant() while the
2448  // (abandoned, draft) spec for the `Key` header only
2449  // allows case-sensitive matches. To match the logic
2450  // in LanguageConverter::getHeaderVariant() we should
2451  // also be looking at fallback variants and deprecated
2452  // mediawiki-internal codes, as well as BCP 47
2453  // normalized forms.
2454 
2455  $aloption[] = "substr=$variant";
2456 
2457  // IE and some other browsers use BCP 47 standards in their Accept-Language header,
2458  // like "zh-CN" or "zh-Hant". We should handle these too.
2459  $variantBCP47 = LanguageCode::bcp47( $variant );
2460  if ( $variantBCP47 !== $variant ) {
2461  $aloption[] = "substr=$variantBCP47";
2462  }
2463  }
2464  $this->addVaryHeader( 'Accept-Language', $aloption );
2465  }
2466  }
2467 
2478  public function preventClickjacking( $enable = true ) {
2479  $this->mPreventClickjacking = $enable;
2480  }
2481 
2487  public function allowClickjacking() {
2488  $this->mPreventClickjacking = false;
2489  }
2490 
2497  public function getPreventClickjacking() {
2499  }
2500 
2508  public function getFrameOptions() {
2509  $config = $this->getConfig();
2510  if ( $config->get( 'BreakFrames' ) ) {
2511  return 'DENY';
2512  } elseif ( $this->mPreventClickjacking && $config->get( 'EditPageFrameOptions' ) ) {
2513  return $config->get( 'EditPageFrameOptions' );
2514  }
2515  return false;
2516  }
2517 
2524  private function getOriginTrials() {
2525  $config = $this->getConfig();
2526 
2527  return $config->get( 'OriginTrials' );
2528  }
2529 
2533  public function sendCacheControl() {
2534  $response = $this->getRequest()->response();
2535  $config = $this->getConfig();
2536 
2537  $this->addVaryHeader( 'Cookie' );
2538  $this->addAcceptLanguage();
2539 
2540  # don't serve compressed data to clients who can't handle it
2541  # maintain different caches for logged-in users and non-logged in ones
2542  $response->header( $this->getVaryHeader() );
2543 
2544  if ( $config->get( 'UseKeyHeader' ) ) {
2545  $response->header( $this->getKeyHeader() );
2546  }
2547 
2548  if ( $this->mEnableClientCache ) {
2549  if (
2550  $config->get( 'UseSquid' ) &&
2551  !$response->hasCookies() &&
2552  !SessionManager::getGlobalSession()->isPersistent() &&
2553  !$this->isPrintable() &&
2554  $this->mCdnMaxage != 0 &&
2555  !$this->haveCacheVaryCookies()
2556  ) {
2557  if ( $config->get( 'UseESI' ) ) {
2558  wfDeprecated( '$wgUseESI = true', '1.33' );
2559  # We'll purge the proxy cache explicitly, but require end user agents
2560  # to revalidate against the proxy on each visit.
2561  # Surrogate-Control controls our CDN, Cache-Control downstream caches
2562  wfDebug( __METHOD__ .
2563  ": proxy caching with ESI; {$this->mLastModified} **", 'private' );
2564  # start with a shorter timeout for initial testing
2565  # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
2566  $response->header(
2567  "Surrogate-Control: max-age={$config->get( 'SquidMaxage' )}" .
2568  "+{$this->mCdnMaxage}, content=\"ESI/1.0\""
2569  );
2570  $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
2571  } else {
2572  # We'll purge the proxy cache for anons explicitly, but require end user agents
2573  # to revalidate against the proxy on each visit.
2574  # IMPORTANT! The CDN needs to replace the Cache-Control header with
2575  # Cache-Control: s-maxage=0, must-revalidate, max-age=0
2576  wfDebug( __METHOD__ .
2577  ": local proxy caching; {$this->mLastModified} **", 'private' );
2578  # start with a shorter timeout for initial testing
2579  # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
2580  $response->header( "Cache-Control: " .
2581  "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" );
2582  }
2583  } else {
2584  # We do want clients to cache if they can, but they *must* check for updates
2585  # on revisiting the page.
2586  wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **", 'private' );
2587  $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2588  $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
2589  }
2590  if ( $this->mLastModified ) {
2591  $response->header( "Last-Modified: {$this->mLastModified}" );
2592  }
2593  } else {
2594  wfDebug( __METHOD__ . ": no caching **", 'private' );
2595 
2596  # In general, the absence of a last modified header should be enough to prevent
2597  # the client from using its cache. We send a few other things just to make sure.
2598  $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2599  $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
2600  $response->header( 'Pragma: no-cache' );
2601  }
2602  }
2603 
2609  public function loadSkinModules( $sk ) {
2610  foreach ( $sk->getDefaultModules() as $group => $modules ) {
2611  if ( $group === 'styles' ) {
2612  foreach ( $modules as $key => $moduleMembers ) {
2613  $this->addModuleStyles( $moduleMembers );
2614  }
2615  } else {
2616  $this->addModules( $modules );
2617  }
2618  }
2619  }
2620 
2631  public function output( $return = false ) {
2632  if ( $this->mDoNothing ) {
2633  return $return ? '' : null;
2634  }
2635 
2636  $response = $this->getRequest()->response();
2637  $config = $this->getConfig();
2638 
2639  if ( $this->mRedirect != '' ) {
2640  # Standards require redirect URLs to be absolute
2641  $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT );
2642 
2643  $redirect = $this->mRedirect;
2645 
2646  if ( Hooks::run( "BeforePageRedirect", [ $this, &$redirect, &$code ] ) ) {
2647  if ( $code == '301' || $code == '303' ) {
2648  if ( !$config->get( 'DebugRedirects' ) ) {
2649  $response->statusHeader( $code );
2650  }
2651  $this->mLastModified = wfTimestamp( TS_RFC2822 );
2652  }
2653  if ( $config->get( 'VaryOnXFP' ) ) {
2654  $this->addVaryHeader( 'X-Forwarded-Proto' );
2655  }
2656  $this->sendCacheControl();
2657 
2658  $response->header( "Content-Type: text/html; charset=utf-8" );
2659  if ( $config->get( 'DebugRedirects' ) ) {
2660  $url = htmlspecialchars( $redirect );
2661  print "<!DOCTYPE html>\n<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
2662  print "<p>Location: <a href=\"$url\">$url</a></p>\n";
2663  print "</body>\n</html>\n";
2664  } else {
2665  $response->header( 'Location: ' . $redirect );
2666  }
2667  }
2668 
2669  return $return ? '' : null;
2670  } elseif ( $this->mStatusCode ) {
2671  $response->statusHeader( $this->mStatusCode );
2672  }
2673 
2674  # Buffer output; final headers may depend on later processing
2675  ob_start();
2676 
2677  $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
2678  $response->header( 'Content-language: ' .
2679  MediaWikiServices::getInstance()->getContentLanguage()->getHtmlCode() );
2680 
2681  if ( !$this->mArticleBodyOnly ) {
2682  $sk = $this->getSkin();
2683  }
2684 
2685  $linkHeader = $this->getLinkHeader();
2686  if ( $linkHeader ) {
2687  $response->header( $linkHeader );
2688  }
2689 
2690  // Prevent framing, if requested
2691  $frameOptions = $this->getFrameOptions();
2692  if ( $frameOptions ) {
2693  $response->header( "X-Frame-Options: $frameOptions" );
2694  }
2695 
2696  $originTrials = $this->getOriginTrials();
2697  foreach ( $originTrials as $originTrial ) {
2698  $response->header( "Origin-Trial: $originTrial", false );
2699  }
2700 
2702 
2703  if ( $this->mArticleBodyOnly ) {
2704  echo $this->mBodytext;
2705  } else {
2706  // Enable safe mode if requested (T152169)
2707  if ( $this->getRequest()->getBool( 'safemode' ) ) {
2708  $this->disallowUserJs();
2709  }
2710 
2711  $sk = $this->getSkin();
2712  $this->loadSkinModules( $sk );
2713 
2714  MWDebug::addModules( $this );
2715 
2716  // Avoid PHP 7.1 warning of passing $this by reference
2717  $outputPage = $this;
2718  // Hook that allows last minute changes to the output page, e.g.
2719  // adding of CSS or Javascript by extensions.
2720  Hooks::runWithoutAbort( 'BeforePageDisplay', [ &$outputPage, &$sk ] );
2721 
2722  try {
2723  $sk->outputPage();
2724  } catch ( Exception $e ) {
2725  ob_end_clean(); // bug T129657
2726  throw $e;
2727  }
2728  }
2729 
2730  try {
2731  // This hook allows last minute changes to final overall output by modifying output buffer
2732  Hooks::runWithoutAbort( 'AfterFinalPageOutput', [ $this ] );
2733  } catch ( Exception $e ) {
2734  ob_end_clean(); // bug T129657
2735  throw $e;
2736  }
2737 
2738  $this->sendCacheControl();
2739 
2740  if ( $return ) {
2741  return ob_get_clean();
2742  } else {
2743  ob_end_flush();
2744  return null;
2745  }
2746  }
2747 
2758  public function prepareErrorPage( $pageTitle, $htmlTitle = false ) {
2759  $this->setPageTitle( $pageTitle );
2760  if ( $htmlTitle !== false ) {
2761  $this->setHTMLTitle( $htmlTitle );
2762  }
2763  $this->setRobotPolicy( 'noindex,nofollow' );
2764  $this->setArticleRelated( false );
2765  $this->enableClientCache( false );
2766  $this->mRedirect = '';
2767  $this->clearSubtitle();
2768  $this->clearHTML();
2769  }
2770 
2783  public function showErrorPage( $title, $msg, $params = [] ) {
2784  if ( !$title instanceof Message ) {
2785  $title = $this->msg( $title );
2786  }
2787 
2788  $this->prepareErrorPage( $title );
2789 
2790  if ( $msg instanceof Message ) {
2791  if ( $params !== [] ) {
2792  trigger_error( 'Argument ignored: $params. The message parameters argument '
2793  . 'is discarded when the $msg argument is a Message object instead of '
2794  . 'a string.', E_USER_NOTICE );
2795  }
2796  $this->addHTML( $msg->parseAsBlock() );
2797  } else {
2798  $this->addWikiMsgArray( $msg, $params );
2799  }
2800 
2801  $this->returnToMain();
2802  }
2803 
2810  public function showPermissionsErrorPage( array $errors, $action = null ) {
2811  foreach ( $errors as $key => $error ) {
2812  $errors[$key] = (array)$error;
2813  }
2814 
2815  // For some action (read, edit, create and upload), display a "login to do this action"
2816  // error if all of the following conditions are met:
2817  // 1. the user is not logged in
2818  // 2. the only error is insufficient permissions (i.e. no block or something else)
2819  // 3. the error can be avoided simply by logging in
2820  if ( in_array( $action, [ 'read', 'edit', 'createpage', 'createtalk', 'upload' ] )
2821  && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
2822  && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
2823  && ( User::groupHasPermission( 'user', $action )
2824  || User::groupHasPermission( 'autoconfirmed', $action ) )
2825  ) {
2826  $displayReturnto = null;
2827 
2828  # Due to T34276, if a user does not have read permissions,
2829  # $this->getTitle() will just give Special:Badtitle, which is
2830  # not especially useful as a returnto parameter. Use the title
2831  # from the request instead, if there was one.
2832  $request = $this->getRequest();
2833  $returnto = Title::newFromText( $request->getVal( 'title', '' ) );
2834  if ( $action == 'edit' ) {
2835  $msg = 'whitelistedittext';
2836  $displayReturnto = $returnto;
2837  } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
2838  $msg = 'nocreatetext';
2839  } elseif ( $action == 'upload' ) {
2840  $msg = 'uploadnologintext';
2841  } else { # Read
2842  $msg = 'loginreqpagetext';
2843  $displayReturnto = Title::newMainPage();
2844  }
2845 
2846  $query = [];
2847 
2848  if ( $returnto ) {
2849  $query['returnto'] = $returnto->getPrefixedText();
2850 
2851  if ( !$request->wasPosted() ) {
2852  $returntoquery = $request->getValues();
2853  unset( $returntoquery['title'] );
2854  unset( $returntoquery['returnto'] );
2855  unset( $returntoquery['returntoquery'] );
2856  $query['returntoquery'] = wfArrayToCgi( $returntoquery );
2857  }
2858  }
2859  $title = SpecialPage::getTitleFor( 'Userlogin' );
2860  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
2861  $loginUrl = $title->getLinkURL( $query, false, PROTO_RELATIVE );
2862  $loginLink = $linkRenderer->makeKnownLink(
2863  $title,
2864  $this->msg( 'loginreqlink' )->text(),
2865  [],
2866  $query
2867  );
2868 
2869  $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) );
2870  $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->params( $loginUrl )->parse() );
2871 
2872  # Don't return to a page the user can't read otherwise
2873  # we'll end up in a pointless loop
2874  if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) {
2875  $this->returnToMain( null, $displayReturnto );
2876  }
2877  } else {
2878  $this->prepareErrorPage( $this->msg( 'permissionserrors' ) );
2879  $this->addWikiTextAsInterface( $this->formatPermissionsErrorMessage( $errors, $action ) );
2880  }
2881  }
2882 
2889  public function versionRequired( $version ) {
2890  $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) );
2891 
2892  $this->addWikiMsg( 'versionrequiredtext', $version );
2893  $this->returnToMain();
2894  }
2895 
2903  public function formatPermissionsErrorMessage( array $errors, $action = null ) {
2904  if ( $action == null ) {
2905  $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
2906  } else {
2907  $action_desc = $this->msg( "action-$action" )->plain();
2908  $text = $this->msg(
2909  'permissionserrorstext-withaction',
2910  count( $errors ),
2911  $action_desc
2912  )->plain() . "\n\n";
2913  }
2914 
2915  if ( count( $errors ) > 1 ) {
2916  $text .= '<ul class="permissions-errors">' . "\n";
2917 
2918  foreach ( $errors as $error ) {
2919  $text .= '<li>';
2920  $text .= $this->msg( ...$error )->plain();
2921  $text .= "</li>\n";
2922  }
2923  $text .= '</ul>';
2924  } else {
2925  $text .= "<div class=\"permissions-errors\">\n" .
2926  $this->msg( ...reset( $errors ) )->plain() .
2927  "\n</div>";
2928  }
2929 
2930  return $text;
2931  }
2932 
2942  public function showLagWarning( $lag ) {
2943  $config = $this->getConfig();
2944  if ( $lag >= $config->get( 'SlaveLagWarning' ) ) {
2945  $lag = floor( $lag ); // floor to avoid nano seconds to display
2946  $message = $lag < $config->get( 'SlaveLagCritical' )
2947  ? 'lag-warn-normal'
2948  : 'lag-warn-high';
2949  $wrap = Html::rawElement( 'div', [ 'class' => "mw-{$message}" ], "\n$1\n" );
2950  $this->wrapWikiMsg( "$wrap\n", [ $message, $this->getLanguage()->formatNum( $lag ) ] );
2951  }
2952  }
2953 
2960  public function showFatalError( $message ) {
2961  $this->prepareErrorPage( $this->msg( 'internalerror' ) );
2962 
2963  $this->addHTML( $message );
2964  }
2965 
2969  public function showUnexpectedValueError( $name, $val ) {
2970  wfDeprecated( __METHOD__, '1.32' );
2971  $this->showFatalError( $this->msg( 'unexpected', $name, $val )->escaped() );
2972  }
2973 
2977  public function showFileCopyError( $old, $new ) {
2978  wfDeprecated( __METHOD__, '1.32' );
2979  $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->escaped() );
2980  }
2981 
2985  public function showFileRenameError( $old, $new ) {
2986  wfDeprecated( __METHOD__, '1.32' );
2987  $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->escpaed() );
2988  }
2989 
2993  public function showFileDeleteError( $name ) {
2994  wfDeprecated( __METHOD__, '1.32' );
2995  $this->showFatalError( $this->msg( 'filedeleteerror', $name )->escaped() );
2996  }
2997 
3001  public function showFileNotFoundError( $name ) {
3002  wfDeprecated( __METHOD__, '1.32' );
3003  $this->showFatalError( $this->msg( 'filenotfound', $name )->escaped() );
3004  }
3005 
3014  public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) {
3015  $linkRenderer = MediaWikiServices::getInstance()
3016  ->getLinkRendererFactory()->createFromLegacyOptions( $options );
3017  $link = $this->msg( 'returnto' )->rawParams(
3018  $linkRenderer->makeLink( $title, $text, [], $query ) )->escaped();
3019  $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
3020  }
3021 
3030  public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
3031  if ( $returnto == null ) {
3032  $returnto = $this->getRequest()->getText( 'returnto' );
3033  }
3034 
3035  if ( $returntoquery == null ) {
3036  $returntoquery = $this->getRequest()->getText( 'returntoquery' );
3037  }
3038 
3039  if ( $returnto === '' ) {
3040  $returnto = Title::newMainPage();
3041  }
3042 
3043  if ( is_object( $returnto ) ) {
3044  $titleObj = $returnto;
3045  } else {
3046  $titleObj = Title::newFromText( $returnto );
3047  }
3048  // We don't want people to return to external interwiki. That
3049  // might potentially be used as part of a phishing scheme
3050  if ( !is_object( $titleObj ) || $titleObj->isExternal() ) {
3051  $titleObj = Title::newMainPage();
3052  }
3053 
3054  $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) );
3055  }
3056 
3057  private function getRlClientContext() {
3058  if ( !$this->rlClientContext ) {
3060  [], // modules; not relevant
3061  $this->getLanguage()->getCode(),
3062  $this->getSkin()->getSkinName(),
3063  $this->getUser()->isLoggedIn() ? $this->getUser()->getName() : null,
3064  null, // version; not relevant
3066  null, // only; not relevant
3067  $this->isPrintable(),
3068  $this->getRequest()->getBool( 'handheld' )
3069  );
3070  $this->rlClientContext = new ResourceLoaderContext(
3071  $this->getResourceLoader(),
3072  new FauxRequest( $query )
3073  );
3074  if ( $this->contentOverrideCallbacks ) {
3075  $this->rlClientContext = new DerivativeResourceLoaderContext( $this->rlClientContext );
3076  $this->rlClientContext->setContentOverrideCallback( function ( Title $title ) {
3077  foreach ( $this->contentOverrideCallbacks as $callback ) {
3078  $content = $callback( $title );
3079  if ( $content !== null ) {
3081  if ( strpos( $text, '</script>' ) !== false ) {
3082  // Proactively replace this so that we can display a message
3083  // to the user, instead of letting it go to Html::inlineScript(),
3084  // where it would be considered a server-side issue.
3085  $titleFormatted = $title->getPrefixedText();
3087  Xml::encodeJsCall( 'mw.log.error', [
3088  "Cannot preview $titleFormatted due to script-closing tag."
3089  ] )
3090  );
3091  }
3092  return $content;
3093  }
3094  }
3095  return null;
3096  } );
3097  }
3098  }
3099  return $this->rlClientContext;
3100  }
3101 
3113  public function getRlClient() {
3114  if ( !$this->rlClient ) {
3115  $context = $this->getRlClientContext();
3116  $rl = $this->getResourceLoader();
3117  $this->addModules( [
3118  'user',
3119  'user.options',
3120  'user.tokens',
3121  ] );
3122  $this->addModuleStyles( [
3123  'site.styles',
3124  'noscript',
3125  'user.styles',
3126  ] );
3127  $this->getSkin()->setupSkinUserCss( $this );
3128 
3129  // Prepare exempt modules for buildExemptModules()
3130  $exemptGroups = [ 'site' => [], 'noscript' => [], 'private' => [], 'user' => [] ];
3131  $exemptStates = [];
3132  $moduleStyles = $this->getModuleStyles( /*filter*/ true );
3133 
3134  // Preload getTitleInfo for isKnownEmpty calls below and in ResourceLoaderClientHtml
3135  // Separate user-specific batch for improved cache-hit ratio.
3136  $userBatch = [ 'user.styles', 'user' ];
3137  $siteBatch = array_diff( $moduleStyles, $userBatch );
3138  $dbr = wfGetDB( DB_REPLICA );
3141 
3142  // Filter out modules handled by buildExemptModules()
3143  $moduleStyles = array_filter( $moduleStyles,
3144  function ( $name ) use ( $rl, $context, &$exemptGroups, &$exemptStates ) {
3145  $module = $rl->getModule( $name );
3146  if ( $module ) {
3147  $group = $module->getGroup();
3148  if ( isset( $exemptGroups[$group] ) ) {
3149  $exemptStates[$name] = 'ready';
3150  if ( !$module->isKnownEmpty( $context ) ) {
3151  // E.g. Don't output empty <styles>
3152  $exemptGroups[$group][] = $name;
3153  }
3154  return false;
3155  }
3156  }
3157  return true;
3158  }
3159  );
3160  $this->rlExemptStyleModules = $exemptGroups;
3161 
3163  'target' => $this->getTarget(),
3164  'nonce' => $this->getCSPNonce(),
3165  // When 'safemode', disallowUserJs(), or reduceAllowedModules() is used
3166  // to only restrict modules to ORIGIN_CORE (ie. disallow ORIGIN_USER), the list of
3167  // modules enqueud for loading on this page is filtered to just those.
3168  // However, to make sure we also apply the restriction to dynamic dependencies and
3169  // lazy-loaded modules at run-time on the client-side, pass 'safemode' down to the
3170  // StartupModule so that the client-side registry will not contain any restricted
3171  // modules either. (T152169, T185303)
3172  'safemode' => ( $this->getAllowedModules( ResourceLoaderModule::TYPE_COMBINED )
3174  ) ? '1' : null,
3175  ] );
3176  $rlClient->setConfig( $this->getJSVars() );
3177  $rlClient->setModules( $this->getModules( /*filter*/ true ) );
3178  $rlClient->setModuleStyles( $moduleStyles );
3179  $rlClient->setExemptStates( $exemptStates );
3180  $this->rlClient = $rlClient;
3181  }
3182  return $this->rlClient;
3183  }
3184 
3190  public function headElement( Skin $sk, $includeStyle = true ) {
3191  $userdir = $this->getLanguage()->getDir();
3192  $sitedir = MediaWikiServices::getInstance()->getContentLanguage()->getDir();
3193 
3194  $pieces = [];
3196  $this->getRlClient()->getDocumentAttributes(),
3198  ) );
3199  $pieces[] = Html::openElement( 'head' );
3200 
3201  if ( $this->getHTMLTitle() == '' ) {
3202  $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() );
3203  }
3204 
3205  if ( !Html::isXmlMimeType( $this->getConfig()->get( 'MimeType' ) ) ) {
3206  // Add <meta charset="UTF-8">
3207  // This should be before <title> since it defines the charset used by
3208  // text including the text inside <title>.
3209  // The spec recommends defining XHTML5's charset using the XML declaration
3210  // instead of meta.
3211  // Our XML declaration is output by Html::htmlHeader.
3212  // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type
3213  // https://html.spec.whatwg.org/multipage/semantics.html#charset
3214  $pieces[] = Html::element( 'meta', [ 'charset' => 'UTF-8' ] );
3215  }
3216 
3217  $pieces[] = Html::element( 'title', null, $this->getHTMLTitle() );
3218  $pieces[] = $this->getRlClient()->getHeadHtml();
3219  $pieces[] = $this->buildExemptModules();
3220  $pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) );
3221  $pieces = array_merge( $pieces, array_values( $this->mHeadItems ) );
3222 
3223  // Use an IE conditional comment to serve the script only to old IE
3224  $pieces[] = '<!--[if lt IE 9]>' .
3227  [ 'html5shiv' ],
3229  [ 'sync' => true ],
3230  $this->getCSPNonce()
3231  ) .
3232  '<![endif]-->';
3233 
3234  $pieces[] = Html::closeElement( 'head' );
3235 
3236  $bodyClasses = $this->mAdditionalBodyClasses;
3237  $bodyClasses[] = 'mediawiki';
3238 
3239  # Classes for LTR/RTL directionality support
3240  $bodyClasses[] = $userdir;
3241  $bodyClasses[] = "sitedir-$sitedir";
3242 
3243  $underline = $this->getUser()->getOption( 'underline' );
3244  if ( $underline < 2 ) {
3245  // The following classes can be used here:
3246  // * mw-underline-always
3247  // * mw-underline-never
3248  $bodyClasses[] = 'mw-underline-' . ( $underline ? 'always' : 'never' );
3249  }
3250 
3251  if ( $this->getLanguage()->capitalizeAllNouns() ) {
3252  # A <body> class is probably not the best way to do this . . .
3253  $bodyClasses[] = 'capitalize-all-nouns';
3254  }
3255 
3256  // Parser feature migration class
3257  // The idea is that this will eventually be removed, after the wikitext
3258  // which requires it is cleaned up.
3259  $bodyClasses[] = 'mw-hide-empty-elt';
3260 
3261  $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
3262  $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
3263  $bodyClasses[] =
3264  'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
3265 
3266  $bodyAttrs = [];
3267  // While the implode() is not strictly needed, it's used for backwards compatibility
3268  // (this used to be built as a string and hooks likely still expect that).
3269  $bodyAttrs['class'] = implode( ' ', $bodyClasses );
3270 
3271  // Allow skins and extensions to add body attributes they need
3272  $sk->addToBodyAttributes( $this, $bodyAttrs );
3273  Hooks::run( 'OutputPageBodyAttributes', [ $this, $sk, &$bodyAttrs ] );
3274 
3275  $pieces[] = Html::openElement( 'body', $bodyAttrs );
3276 
3277  return self::combineWrappedStrings( $pieces );
3278  }
3279 
3285  public function getResourceLoader() {
3286  if ( is_null( $this->mResourceLoader ) ) {
3287  // Lazy-initialise as needed
3288  $this->mResourceLoader = MediaWikiServices::getInstance()->getResourceLoader();
3289  }
3290  return $this->mResourceLoader;
3291  }
3292 
3301  public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) {
3302  // Apply 'target' and 'origin' filters
3303  $modules = $this->filterModules( (array)$modules, null, $only );
3304 
3306  $this->getRlClientContext(),
3307  $modules,
3308  $only,
3309  $extraQuery,
3310  $this->getCSPNonce()
3311  );
3312  }
3313 
3320  protected static function combineWrappedStrings( array $chunks ) {
3321  // Filter out empty values
3322  $chunks = array_filter( $chunks, 'strlen' );
3323  return WrappedString::join( "\n", $chunks );
3324  }
3325 
3332  public function getBottomScripts() {
3333  $chunks = [];
3334  $chunks[] = $this->getRlClient()->getBodyHtml();
3335 
3336  // Legacy non-ResourceLoader scripts
3337  $chunks[] = $this->mScripts;
3338 
3339  if ( $this->limitReportJSData ) {
3342  [ 'wgPageParseReport' => $this->limitReportJSData ]
3343  ),
3344  $this->getCSPNonce()
3345  );
3346  }
3347 
3348  return self::combineWrappedStrings( $chunks );
3349  }
3350 
3357  public function getJsConfigVars() {
3358  return $this->mJsConfigVars;
3359  }
3360 
3367  public function addJsConfigVars( $keys, $value = null ) {
3368  if ( is_array( $keys ) ) {
3369  foreach ( $keys as $key => $value ) {
3370  $this->mJsConfigVars[$key] = $value;
3371  }
3372  return;
3373  }
3374 
3375  $this->mJsConfigVars[$keys] = $value;
3376  }
3377 
3387  public function getJSVars() {
3388  $curRevisionId = 0;
3389  $articleId = 0;
3390  $canonicalSpecialPageName = false; # T23115
3391  $services = MediaWikiServices::getInstance();
3392 
3393  $title = $this->getTitle();
3394  $ns = $title->getNamespace();
3395  $canonicalNamespace = MWNamespace::exists( $ns )
3397  : $title->getNsText();
3398 
3399  $sk = $this->getSkin();
3400  // Get the relevant title so that AJAX features can use the correct page name
3401  // when making API requests from certain special pages (T36972).
3402  $relevantTitle = $sk->getRelevantTitle();
3403  $relevantUser = $sk->getRelevantUser();
3404 
3405  if ( $ns == NS_SPECIAL ) {
3406  list( $canonicalSpecialPageName, /*...*/ ) =
3407  $services->getSpecialPageFactory()->
3408  resolveAlias( $title->getDBkey() );
3409  } elseif ( $this->canUseWikiPage() ) {
3410  $wikiPage = $this->getWikiPage();
3411  $curRevisionId = $wikiPage->getLatest();
3412  $articleId = $wikiPage->getId();
3413  }
3414 
3415  $lang = $title->getPageViewLanguage();
3416 
3417  // Pre-process information
3418  $separatorTransTable = $lang->separatorTransformTable();
3419  $separatorTransTable = $separatorTransTable ?: [];
3420  $compactSeparatorTransTable = [
3421  implode( "\t", array_keys( $separatorTransTable ) ),
3422  implode( "\t", $separatorTransTable ),
3423  ];
3424  $digitTransTable = $lang->digitTransformTable();
3425  $digitTransTable = $digitTransTable ?: [];
3426  $compactDigitTransTable = [
3427  implode( "\t", array_keys( $digitTransTable ) ),
3428  implode( "\t", $digitTransTable ),
3429  ];
3430 
3431  $user = $this->getUser();
3432 
3433  $vars = [
3434  'wgCanonicalNamespace' => $canonicalNamespace,
3435  'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
3436  'wgNamespaceNumber' => $title->getNamespace(),
3437  'wgPageName' => $title->getPrefixedDBkey(),
3438  'wgTitle' => $title->getText(),
3439  'wgCurRevisionId' => $curRevisionId,
3440  'wgRevisionId' => (int)$this->getRevisionId(),
3441  'wgArticleId' => $articleId,
3442  'wgIsArticle' => $this->isArticle(),
3443  'wgIsRedirect' => $title->isRedirect(),
3444  'wgAction' => Action::getActionName( $this->getContext() ),
3445  'wgUserName' => $user->isAnon() ? null : $user->getName(),
3446  'wgUserGroups' => $user->getEffectiveGroups(),
3447  'wgCategories' => $this->getCategories(),
3448  'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
3449  'wgPageContentLanguage' => $lang->getCode(),
3450  'wgPageContentModel' => $title->getContentModel(),
3451  'wgSeparatorTransformTable' => $compactSeparatorTransTable,
3452  'wgDigitTransformTable' => $compactDigitTransTable,
3453  'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
3454  'wgMonthNames' => $lang->getMonthNamesArray(),
3455  'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
3456  'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
3457  'wgRelevantArticleId' => $relevantTitle->getArticleID(),
3458  'wgRequestId' => WebRequest::getRequestId(),
3459  'wgCSPNonce' => $this->getCSPNonce(),
3460  ];
3461 
3462  if ( $user->isLoggedIn() ) {
3463  $vars['wgUserId'] = $user->getId();
3464  $vars['wgUserEditCount'] = $user->getEditCount();
3465  $userReg = $user->getRegistration();
3466  $vars['wgUserRegistration'] = $userReg ? wfTimestamp( TS_UNIX, $userReg ) * 1000 : null;
3467  // Get the revision ID of the oldest new message on the user's talk
3468  // page. This can be used for constructing new message alerts on
3469  // the client side.
3470  $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId();
3471  }
3472 
3473  $contLang = $services->getContentLanguage();
3474  if ( $contLang->hasVariants() ) {
3475  $vars['wgUserVariant'] = $contLang->getPreferredVariant();
3476  }
3477  // Same test as SkinTemplate
3478  $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user )
3479  && ( $title->exists() || $title->quickUserCan( 'create', $user ) );
3480 
3481  $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle
3482  && $relevantTitle->quickUserCan( 'edit', $user )
3483  && ( $relevantTitle->exists() || $relevantTitle->quickUserCan( 'create', $user ) );
3484 
3485  foreach ( $title->getRestrictionTypes() as $type ) {
3486  // Following keys are set in $vars:
3487  // wgRestrictionCreate, wgRestrictionEdit, wgRestrictionMove, wgRestrictionUpload
3488  $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
3489  }
3490 
3491  if ( $title->isMainPage() ) {
3492  $vars['wgIsMainPage'] = true;
3493  }
3494 
3495  if ( $this->mRedirectedFrom ) {
3496  $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey();
3497  }
3498 
3499  if ( $relevantUser ) {
3500  $vars['wgRelevantUserName'] = $relevantUser->getName();
3501  }
3502 
3503  // Allow extensions to add their custom variables to the mw.config map.
3504  // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
3505  // page-dependant but site-wide (without state).
3506  // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
3507  Hooks::run( 'MakeGlobalVariablesScript', [ &$vars, $this ] );
3508 
3509  // Merge in variables from addJsConfigVars last
3510  return array_merge( $vars, $this->getJsConfigVars() );
3511  }
3512 
3522  public function userCanPreview() {
3523  $request = $this->getRequest();
3524  if (
3525  $request->getVal( 'action' ) !== 'submit' ||
3526  !$request->wasPosted()
3527  ) {
3528  return false;
3529  }
3530 
3531  $user = $this->getUser();
3532 
3533  if ( !$user->isLoggedIn() ) {
3534  // Anons have predictable edit tokens
3535  return false;
3536  }
3537  if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
3538  return false;
3539  }
3540 
3541  $title = $this->getTitle();
3542  $errors = $title->getUserPermissionsErrors( 'edit', $user );
3543  if ( count( $errors ) !== 0 ) {
3544  return false;
3545  }
3546 
3547  return true;
3548  }
3549 
3553  public function getHeadLinksArray() {
3554  global $wgVersion;
3555 
3556  $tags = [];
3557  $config = $this->getConfig();
3558 
3559  $canonicalUrl = $this->mCanonicalUrl;
3560 
3561  $tags['meta-generator'] = Html::element( 'meta', [
3562  'name' => 'generator',
3563  'content' => "MediaWiki $wgVersion",
3564  ] );
3565 
3566  if ( $config->get( 'ReferrerPolicy' ) !== false ) {
3567  // Per https://w3c.github.io/webappsec-referrer-policy/#unknown-policy-values
3568  // fallbacks should come before the primary value so we need to reverse the array.
3569  foreach ( array_reverse( (array)$config->get( 'ReferrerPolicy' ) ) as $i => $policy ) {
3570  $tags["meta-referrer-$i"] = Html::element( 'meta', [
3571  'name' => 'referrer',
3572  'content' => $policy,
3573  ] );
3574  }
3575  }
3576 
3577  $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
3578  if ( $p !== 'index,follow' ) {
3579  // http://www.robotstxt.org/wc/meta-user.html
3580  // Only show if it's different from the default robots policy
3581  $tags['meta-robots'] = Html::element( 'meta', [
3582  'name' => 'robots',
3583  'content' => $p,
3584  ] );
3585  }
3586 
3587  foreach ( $this->mMetatags as $tag ) {
3588  if ( strncasecmp( $tag[0], 'http:', 5 ) === 0 ) {
3589  $a = 'http-equiv';
3590  $tag[0] = substr( $tag[0], 5 );
3591  } elseif ( strncasecmp( $tag[0], 'og:', 3 ) === 0 ) {
3592  $a = 'property';
3593  } else {
3594  $a = 'name';
3595  }
3596  $tagName = "meta-{$tag[0]}";
3597  if ( isset( $tags[$tagName] ) ) {
3598  $tagName .= $tag[1];
3599  }
3600  $tags[$tagName] = Html::element( 'meta',
3601  [
3602  $a => $tag[0],
3603  'content' => $tag[1]
3604  ]
3605  );
3606  }
3607 
3608  foreach ( $this->mLinktags as $tag ) {
3609  $tags[] = Html::element( 'link', $tag );
3610  }
3611 
3612  # Universal edit button
3613  if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) {
3614  $user = $this->getUser();
3615  if ( $this->getTitle()->quickUserCan( 'edit', $user )
3616  && ( $this->getTitle()->exists() ||
3617  $this->getTitle()->quickUserCan( 'create', $user ) )
3618  ) {
3619  // Original UniversalEditButton
3620  $msg = $this->msg( 'edit' )->text();
3621  $tags['universal-edit-button'] = Html::element( 'link', [
3622  'rel' => 'alternate',
3623  'type' => 'application/x-wiki',
3624  'title' => $msg,
3625  'href' => $this->getTitle()->getEditURL(),
3626  ] );
3627  // Alternate edit link
3628  $tags['alternative-edit'] = Html::element( 'link', [
3629  'rel' => 'edit',
3630  'title' => $msg,
3631  'href' => $this->getTitle()->getEditURL(),
3632  ] );
3633  }
3634  }
3635 
3636  # Generally the order of the favicon and apple-touch-icon links
3637  # should not matter, but Konqueror (3.5.9 at least) incorrectly
3638  # uses whichever one appears later in the HTML source. Make sure
3639  # apple-touch-icon is specified first to avoid this.
3640  if ( $config->get( 'AppleTouchIcon' ) !== false ) {
3641  $tags['apple-touch-icon'] = Html::element( 'link', [
3642  'rel' => 'apple-touch-icon',
3643  'href' => $config->get( 'AppleTouchIcon' )
3644  ] );
3645  }
3646 
3647  if ( $config->get( 'Favicon' ) !== false ) {
3648  $tags['favicon'] = Html::element( 'link', [
3649  'rel' => 'shortcut icon',
3650  'href' => $config->get( 'Favicon' )
3651  ] );
3652  }
3653 
3654  # OpenSearch description link
3655  $tags['opensearch'] = Html::element( 'link', [
3656  'rel' => 'search',
3657  'type' => 'application/opensearchdescription+xml',
3658  'href' => wfScript( 'opensearch_desc' ),
3659  'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
3660  ] );
3661 
3662  # Real Simple Discovery link, provides auto-discovery information
3663  # for the MediaWiki API (and potentially additional custom API
3664  # support such as WordPress or Twitter-compatible APIs for a
3665  # blogging extension, etc)
3666  $tags['rsd'] = Html::element( 'link', [
3667  'rel' => 'EditURI',
3668  'type' => 'application/rsd+xml',
3669  // Output a protocol-relative URL here if $wgServer is protocol-relative.
3670  // Whether RSD accepts relative or protocol-relative URLs is completely
3671  // undocumented, though.
3672  'href' => wfExpandUrl( wfAppendQuery(
3673  wfScript( 'api' ),
3674  [ 'action' => 'rsd' ] ),
3676  ),
3677  ] );
3678 
3679  # Language variants
3680  if ( !$config->get( 'DisableLangConversion' ) ) {
3681  $lang = $this->getTitle()->getPageLanguage();
3682  if ( $lang->hasVariants() ) {
3683  $variants = $lang->getVariants();
3684  foreach ( $variants as $variant ) {
3685  $tags["variant-$variant"] = Html::element( 'link', [
3686  'rel' => 'alternate',
3687  'hreflang' => LanguageCode::bcp47( $variant ),
3688  'href' => $this->getTitle()->getLocalURL(
3689  [ 'variant' => $variant ] )
3690  ]
3691  );
3692  }
3693  # x-default link per https://support.google.com/webmasters/answer/189077?hl=en
3694  $tags["variant-x-default"] = Html::element( 'link', [
3695  'rel' => 'alternate',
3696  'hreflang' => 'x-default',
3697  'href' => $this->getTitle()->getLocalURL() ] );
3698  }
3699  }
3700 
3701  # Copyright
3702  if ( $this->copyrightUrl !== null ) {
3703  $copyright = $this->copyrightUrl;
3704  } else {
3705  $copyright = '';
3706  if ( $config->get( 'RightsPage' ) ) {
3707  $copy = Title::newFromText( $config->get( 'RightsPage' ) );
3708 
3709  if ( $copy ) {
3710  $copyright = $copy->getLocalURL();
3711  }
3712  }
3713 
3714  if ( !$copyright && $config->get( 'RightsUrl' ) ) {
3715  $copyright = $config->get( 'RightsUrl' );
3716  }
3717  }
3718 
3719  if ( $copyright ) {
3720  $tags['copyright'] = Html::element( 'link', [
3721  'rel' => 'license',
3722  'href' => $copyright ]
3723  );
3724  }
3725 
3726  # Feeds
3727  if ( $config->get( 'Feed' ) ) {
3728  $feedLinks = [];
3729 
3730  foreach ( $this->getSyndicationLinks() as $format => $link ) {
3731  # Use the page name for the title. In principle, this could
3732  # lead to issues with having the same name for different feeds
3733  # corresponding to the same page, but we can't avoid that at
3734  # this low a level.
3735 
3736  $feedLinks[] = $this->feedLink(
3737  $format,
3738  $link,
3739  # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
3740  $this->msg(
3741  "page-{$format}-feed", $this->getTitle()->getPrefixedText()
3742  )->text()
3743  );
3744  }
3745 
3746  # Recent changes feed should appear on every page (except recentchanges,
3747  # that would be redundant). Put it after the per-page feed to avoid
3748  # changing existing behavior. It's still available, probably via a
3749  # menu in your browser. Some sites might have a different feed they'd
3750  # like to promote instead of the RC feed (maybe like a "Recent New Articles"
3751  # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
3752  # If so, use it instead.
3753  $sitename = $config->get( 'Sitename' );
3754  $overrideSiteFeed = $config->get( 'OverrideSiteFeed' );
3755  if ( $overrideSiteFeed ) {
3756  foreach ( $overrideSiteFeed as $type => $feedUrl ) {
3757  // Note, this->feedLink escapes the url.
3758  $feedLinks[] = $this->feedLink(
3759  $type,
3760  $feedUrl,
3761  $this->msg( "site-{$type}-feed", $sitename )->text()
3762  );
3763  }
3764  } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
3765  $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
3766  foreach ( $this->getAdvertisedFeedTypes() as $format ) {
3767  $feedLinks[] = $this->feedLink(
3768  $format,
3769  $rctitle->getLocalURL( [ 'feed' => $format ] ),
3770  # For grep: 'site-rss-feed', 'site-atom-feed'
3771  $this->msg( "site-{$format}-feed", $sitename )->text()
3772  );
3773  }
3774  }
3775 
3776  # Allow extensions to change the list pf feeds. This hook is primarily for changing,
3777  # manipulating or removing existing feed tags. If you want to add new feeds, you should
3778  # use OutputPage::addFeedLink() instead.
3779  Hooks::run( 'AfterBuildFeedLinks', [ &$feedLinks ] );
3780 
3781  $tags += $feedLinks;
3782  }
3783 
3784  # Canonical URL
3785  if ( $config->get( 'EnableCanonicalServerLink' ) ) {
3786  if ( $canonicalUrl !== false ) {
3787  $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL );
3788  } else {
3789  if ( $this->isArticleRelated() ) {
3790  // This affects all requests where "setArticleRelated" is true. This is
3791  // typically all requests that show content (query title, curid, oldid, diff),
3792  // and all wikipage actions (edit, delete, purge, info, history etc.).
3793  // It does not apply to File pages and Special pages.
3794  // 'history' and 'info' actions address page metadata rather than the page
3795  // content itself, so they may not be canonicalized to the view page url.
3796  // TODO: this ought to be better encapsulated in the Action class.
3797  $action = Action::getActionName( $this->getContext() );
3798  if ( in_array( $action, [ 'history', 'info' ] ) ) {
3799  $query = "action={$action}";
3800  } else {
3801  $query = '';
3802  }
3803  $canonicalUrl = $this->getTitle()->getCanonicalURL( $query );
3804  } else {
3805  $reqUrl = $this->getRequest()->getRequestURL();
3806  $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL );
3807  }
3808  }
3809  }
3810  if ( $canonicalUrl !== false ) {
3811  $tags[] = Html::element( 'link', [
3812  'rel' => 'canonical',
3813  'href' => $canonicalUrl
3814  ] );
3815  }
3816 
3817  // Allow extensions to add, remove and/or otherwise manipulate these links
3818  // If you want only to *add* <head> links, please use the addHeadItem()
3819  // (or addHeadItems() for multiple items) method instead.
3820  // This hook is provided as a last resort for extensions to modify these
3821  // links before the output is sent to client.
3822  Hooks::run( 'OutputPageAfterGetHeadLinksArray', [ &$tags, $this ] );
3823 
3824  return $tags;
3825  }
3826 
3835  private function feedLink( $type, $url, $text ) {
3836  return Html::element( 'link', [
3837  'rel' => 'alternate',
3838  'type' => "application/$type+xml",
3839  'title' => $text,
3840  'href' => $url ]
3841  );
3842  }
3843 
3853  public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
3854  $options = [];
3855  if ( $media ) {
3856  $options['media'] = $media;
3857  }
3858  if ( $condition ) {
3859  $options['condition'] = $condition;
3860  }
3861  if ( $dir ) {
3862  $options['dir'] = $dir;
3863  }
3864  $this->styles[$style] = $options;
3865  }
3866 
3874  public function addInlineStyle( $style_css, $flip = 'noflip' ) {
3875  if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
3876  # If wanted, and the interface is right-to-left, flip the CSS
3877  $style_css = CSSJanus::transform( $style_css, true, false );
3878  }
3879  $this->mInlineStyles .= Html::inlineStyle( $style_css );
3880  }
3881 
3887  protected function buildExemptModules() {
3888  $chunks = [];
3889  // Things that go after the ResourceLoaderDynamicStyles marker
3890  $append = [];
3891 
3892  // We want site, private and user styles to override dynamically added styles from
3893  // general modules, but we want dynamically added styles to override statically added
3894  // style modules. So the order has to be:
3895  // - page style modules (formatted by ResourceLoaderClientHtml::getHeadHtml())
3896  // - dynamically loaded styles (added by mw.loader before ResourceLoaderDynamicStyles)
3897  // - ResourceLoaderDynamicStyles marker
3898  // - site/private/user styles
3899 
3900  // Add legacy styles added through addStyle()/addInlineStyle() here
3901  $chunks[] = implode( '', $this->buildCssLinksArray() ) . $this->mInlineStyles;
3902 
3903  $chunks[] = Html::element(
3904  'meta',
3905  [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ]
3906  );
3907 
3908  $separateReq = [ 'site.styles', 'user.styles' ];
3909  foreach ( $this->rlExemptStyleModules as $group => $moduleNames ) {
3910  // Combinable modules
3911  $chunks[] = $this->makeResourceLoaderLink(
3912  array_diff( $moduleNames, $separateReq ),
3914  );
3915 
3916  foreach ( array_intersect( $moduleNames, $separateReq ) as $name ) {
3917  // These require their own dedicated request in order to support "@import"
3918  // syntax, which is incompatible with concatenation. (T147667, T37562)
3919  $chunks[] = $this->makeResourceLoaderLink( $name,
3921  );
3922  }
3923  }
3924 
3925  return self::combineWrappedStrings( array_merge( $chunks, $append ) );
3926  }
3927 
3931  public function buildCssLinksArray() {
3932  $links = [];
3933 
3934  foreach ( $this->styles as $file => $options ) {
3935  $link = $this->styleLink( $file, $options );
3936  if ( $link ) {
3937  $links[$file] = $link;
3938  }
3939  }
3940  return $links;
3941  }
3942 
3950  protected function styleLink( $style, array $options ) {
3951  if ( isset( $options['dir'] ) ) {
3952  if ( $this->getLanguage()->getDir() != $options['dir'] ) {
3953  return '';
3954  }
3955  }
3956 
3957  if ( isset( $options['media'] ) ) {
3958  $media = self::transformCssMedia( $options['media'] );
3959  if ( is_null( $media ) ) {
3960  return '';
3961  }
3962  } else {
3963  $media = 'all';
3964  }
3965 
3966  if ( substr( $style, 0, 1 ) == '/' ||
3967  substr( $style, 0, 5 ) == 'http:' ||
3968  substr( $style, 0, 6 ) == 'https:' ) {
3969  $url = $style;
3970  } else {
3971  $config = $this->getConfig();
3972  // Append file hash as query parameter
3973  $url = self::transformResourcePath(
3974  $config,
3975  $config->get( 'StylePath' ) . '/' . $style
3976  );
3977  }
3978 
3979  $link = Html::linkedStyle( $url, $media );
3980 
3981  if ( isset( $options['condition'] ) ) {
3982  $condition = htmlspecialchars( $options['condition'] );
3983  $link = "<!--[if $condition]>$link<![endif]-->";
3984  }
3985  return $link;
3986  }
3987 
4009  public static function transformResourcePath( Config $config, $path ) {
4010  global $IP;
4011 
4012  $localDir = $IP;
4013  $remotePathPrefix = $config->get( 'ResourceBasePath' );
4014  if ( $remotePathPrefix === '' ) {
4015  // The configured base path is required to be empty string for
4016  // wikis in the domain root
4017  $remotePath = '/';
4018  } else {
4019  $remotePath = $remotePathPrefix;
4020  }
4021  if ( strpos( $path, $remotePath ) !== 0 || substr( $path, 0, 2 ) === '//' ) {
4022  // - Path is outside wgResourceBasePath, ignore.
4023  // - Path is protocol-relative. Fixes T155310. Not supported by RelPath lib.
4024  return $path;
4025  }
4026  // For files in resources, extensions/ or skins/, ResourceBasePath is preferred here.
4027  // For other misc files in $IP, we'll fallback to that as well. There is, however, a fourth
4028  // supported dir/path pair in the configuration (wgUploadDirectory, wgUploadPath)
4029  // which is not expected to be in wgResourceBasePath on CDNs. (T155146)
4030  $uploadPath = $config->get( 'UploadPath' );
4031  if ( strpos( $path, $uploadPath ) === 0 ) {
4032  $localDir = $config->get( 'UploadDirectory' );
4033  $remotePathPrefix = $remotePath = $uploadPath;
4034  }
4035 
4036  $path = RelPath::getRelativePath( $path, $remotePath );
4037  return self::transformFilePath( $remotePathPrefix, $localDir, $path );
4038  }
4039 
4051  public static function transformFilePath( $remotePathPrefix, $localPath, $file ) {
4052  $hash = md5_file( "$localPath/$file" );
4053  if ( $hash === false ) {
4054  wfLogWarning( __METHOD__ . ": Failed to hash $localPath/$file" );
4055  $hash = '';
4056  }
4057  return "$remotePathPrefix/$file?" . substr( $hash, 0, 5 );
4058  }
4059 
4067  public static function transformCssMedia( $media ) {
4068  global $wgRequest;
4069 
4070  // https://www.w3.org/TR/css3-mediaqueries/#syntax
4071  $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
4072 
4073  // Switch in on-screen display for media testing
4074  $switches = [
4075  'printable' => 'print',
4076  'handheld' => 'handheld',
4077  ];
4078  foreach ( $switches as $switch => $targetMedia ) {
4079  if ( $wgRequest->getBool( $switch ) ) {
4080  if ( $media == $targetMedia ) {
4081  $media = '';
4082  } elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
4083  /* This regex will not attempt to understand a comma-separated media_query_list
4084  *
4085  * Example supported values for $media:
4086  * 'screen', 'only screen', 'screen and (min-width: 982px)' ),
4087  * Example NOT supported value for $media:
4088  * '3d-glasses, screen, print and resolution > 90dpi'
4089  *
4090  * If it's a print request, we never want any kind of screen stylesheets
4091  * If it's a handheld request (currently the only other choice with a switch),
4092  * we don't want simple 'screen' but we might want screen queries that
4093  * have a max-width or something, so we'll pass all others on and let the
4094  * client do the query.
4095  */
4096  if ( $targetMedia == 'print' || $media == 'screen' ) {
4097  return null;
4098  }
4099  }
4100  }
4101  }
4102 
4103  return $media;
4104  }
4105 
4112  public function addWikiMsg( /*...*/ ) {
4113  $args = func_get_args();
4114  $name = array_shift( $args );
4115  $this->addWikiMsgArray( $name, $args );
4116  }
4117 
4126  public function addWikiMsgArray( $name, $args ) {
4127  $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
4128  }
4129 
4155  public function wrapWikiMsg( $wrap /*, ...*/ ) {
4156  $msgSpecs = func_get_args();
4157  array_shift( $msgSpecs );
4158  $msgSpecs = array_values( $msgSpecs );
4159  $s = $wrap;
4160  foreach ( $msgSpecs as $n => $spec ) {
4161  if ( is_array( $spec ) ) {
4162  $args = $spec;
4163  $name = array_shift( $args );
4164  if ( isset( $args['options'] ) ) {
4165  unset( $args['options'] );
4166  wfDeprecated(
4167  'Adding "options" to ' . __METHOD__ . ' is no longer supported',
4168  '1.20'
4169  );
4170  }
4171  } else {
4172  $args = [];
4173  $name = $spec;
4174  }
4175  $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
4176  }
4177  $this->addWikiTextAsInterface( $s );
4178  }
4179 
4185  public function isTOCEnabled() {
4186  return $this->mEnableTOC;
4187  }
4188 
4195  public function enableSectionEditLinks( $flag = true ) {
4196  wfDeprecated( __METHOD__, '1.31' );
4197  }
4198 
4204  public function sectionEditLinksEnabled() {
4205  wfDeprecated( __METHOD__, '1.31' );
4206  return true;
4207  }
4208 
4216  public static function setupOOUI( $skinName = 'default', $dir = 'ltr' ) {
4218  $theme = $themes[$skinName] ?? $themes['default'];
4219  // For example, 'OOUI\WikimediaUITheme'.
4220  $themeClass = "OOUI\\{$theme}Theme";
4221  OOUI\Theme::setSingleton( new $themeClass() );
4222  OOUI\Element::setDefaultDir( $dir );
4223  }
4224 
4231  public function enableOOUI() {
4232  self::setupOOUI(
4233  strtolower( $this->getSkin()->getSkinName() ),
4234  $this->getLanguage()->getDir()
4235  );
4236  $this->addModuleStyles( [
4237  'oojs-ui-core.styles',
4238  'oojs-ui.styles.indicators',
4239  'oojs-ui.styles.textures',
4240  'mediawiki.widgets.styles',
4241  'oojs-ui.styles.icons-content',
4242  'oojs-ui.styles.icons-alerts',
4243  'oojs-ui.styles.icons-interactions',
4244  ] );
4245  }
4246 
4256  public function getCSPNonce() {
4258  return false;
4259  }
4260  if ( $this->CSPNonce === null ) {
4261  // XXX It might be expensive to generate randomness
4262  // on every request, on Windows.
4263  $rand = random_bytes( 15 );
4264  $this->CSPNonce = base64_encode( $rand );
4265  }
4266  return $this->CSPNonce;
4267  }
4268 
4269 }
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:599
static static static getSkinThemeMap()
Return a map of skin names (in lowercase) to OOUI theme names, defining which theme a given skin shou...
getModuleScripts()
Definition: OutputPage.php:555
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:1985
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:638
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:653
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:1588
showUnexpectedValueError( $name, $val)
static element( $element, $attribs=[], $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:232
showFileCopyError( $old, $new)
setFileVersion( $file)
Set the displayed file version.
either a plain
Definition: hooks.txt:2046
getHTMLTitle()
Return the "HTML title", i.e.
Definition: OutputPage.php:913
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:598
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:684
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()
Create a new Title for the Main Page.
Definition: Title.php:598
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
static newDummyContext()
Return a dummy ResourceLoaderContext object suitable for passing into things that don&#39;t "really" need...
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:2621
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:1985
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.
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2162
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
$wgParser
Definition: Setup.php:886
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:960
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.
sectionEditLinksEnabled()
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:704
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:715
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:998
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:1625
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:741
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:900
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:938
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:922
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:6328
lowerCdnMaxage( $maxage)
Set the value of the "s-maxage" part of the "Cache-control" HTTP header to $maxage if that is lower t...
addInlineStyle( $style_css, $flip='noflip')
Adds inline CSS styles Internal use only.
getPageClasses( $title)
TODO: document.
Definition: Skin.php:438
array $mJsConfigVars
Definition: OutputPage.php:171
getJsConfigVars()
Get the javascript config vars to include on this page.
enableOOUI()
Add ResourceLoader module styles for OOUI and set up the PHP implementation of it for use with MediaW...
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:3038
addToBodyAttributes( $out, &$bodyAttrs)
This will be called by OutputPage::headElement when it is creating the "<body>" tag, skins can override it if they have a need to add in any body attributes or classes of their own.
Definition: Skin.php:490
passed in as a query string parameter to the various URLs constructed here(i.e. $prevlink) $ldel you ll need to handle error messages
Definition: hooks.txt:1290
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:829
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:887
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:679
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:2210
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:855
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:674
$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:931
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...
Bootstrap 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:1985
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:1985
static exists( $index)
Returns whether the specified namespace exists.
showFileRenameError( $old, $new)
buildCssLinksArray()
getRlClientContext()
getDisplayTitle()
Returns page display title.
Definition: OutputPage.php:983
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:664
getHtmlElementAttributes()
Return values for <html> element.
Definition: Skin.php:474
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt: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:1412
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:582
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:1563
getModuleStyles( $filter=false, $position=null)
Get the list of style-only modules to load on this page.
Definition: OutputPage.php:567
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:966
__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:5073
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:206
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:574
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:695
setLastModified( $timestamp)
Override the last modified timestamp.
Definition: OutputPage.php:843
addHeadItem( $name, $value)
Add or replace a head item to the output.
Definition: OutputPage.php:654
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:629
const PROTO_CANONICAL
Definition: Defines.php:223
static getCanonicalName( $index)
Returns the canonical (English) name for a given index.
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:609
showFileNotFoundError( $name)
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.
exists()
Returns true if file exists in the repository.
Definition: File.php:896
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.
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:1004
isRedirect( $flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition: Title.php:3572
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:624
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:728
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
showFileDeleteError( $name)
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:51
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:155
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:2621
setIndexPolicy( $policy)
Set the index policy for the page, but leave the follow policy un- touched.
Definition: OutputPage.php:873
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:2210
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:1476
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:726
setDisplayTitle( $html)
Same as page title but only contains name of the page, not any other text.
Definition: OutputPage.php:971
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:280
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition: Article.php:1027