MediaWiki  1.33.0
OutputPage.php
Go to the documentation of this file.
1 <?php
27 use Wikimedia\RelPath;
28 use Wikimedia\WrappedString;
29 use Wikimedia\WrappedStringList;
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 
165  private $rlClientContext;
166 
168  private $rlExemptStyleModules;
169 
171  protected $mJsConfigVars = [];
172 
174  protected $mTemplateIds = [];
175 
177  protected $mImageTimeKeys = [];
178 
180  public $mRedirectCode = '';
181 
182  protected $mFeedLinksAppendQuery = null;
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 
249  private $mRevisionTimestamp = null;
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 
281  private $mRedirectedFrom = null;
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 
310  private $contentOverrideCallbacks = [];
311 
315  private $mLinkHeader = [];
316 
320  private $CSPNonce;
321 
325  private static $cacheVaryCookies = null;
326 
333  function __construct( IContextSource $context ) {
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  ) {
492  $resourceLoader = $this->getResourceLoader();
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() {
705  return $this->mArticleBodyOnly;
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
945  $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) );
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() {
984  $html = $this->displayTitle;
985  if ( $html === null ) {
986  $html = $this->getTitle()->getPrefixedText();
987  }
988 
989  return Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $html ) );
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() {
1129  return $this->mHideNewSectionLink;
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() {
1217  return $this->mFeedLinksAppendQuery;
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 
1493  $link = Html::rawElement(
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() {
1696  return $this->mRevisionTimestamp;
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 ) {
2157  return Parser::stripOuterParagraph(
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 
2317  function haveCacheVaryCookies() {
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() {
2498  return $this->mPreventClickjacking;
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;
2644  $code = $this->mRedirectCode;
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 )->escaped() );
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 ) {
3059  $query = ResourceLoader::makeLoaderQuery(
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
3065  ResourceLoader::inDebugMode(),
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 
3162  $rlClient = new ResourceLoaderClientHtml( $context, [
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 = [];
3195  $pieces[] = Html::htmlHeader( Sanitizer::mergeAttributes(
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  $this->getResourceLoader(),
3228  new FauxRequest( [] )
3229  ),
3230  [ 'html5shiv' ],
3232  [ 'sync' => true ],
3233  $this->getCSPNonce()
3234  ) .
3235  '<![endif]-->';
3236 
3237  $pieces[] = Html::closeElement( 'head' );
3238 
3239  $bodyClasses = $this->mAdditionalBodyClasses;
3240  $bodyClasses[] = 'mediawiki';
3241 
3242  # Classes for LTR/RTL directionality support
3243  $bodyClasses[] = $userdir;
3244  $bodyClasses[] = "sitedir-$sitedir";
3245 
3246  $underline = $this->getUser()->getOption( 'underline' );
3247  if ( $underline < 2 ) {
3248  // The following classes can be used here:
3249  // * mw-underline-always
3250  // * mw-underline-never
3251  $bodyClasses[] = 'mw-underline-' . ( $underline ? 'always' : 'never' );
3252  }
3253 
3254  if ( $this->getLanguage()->capitalizeAllNouns() ) {
3255  # A <body> class is probably not the best way to do this . . .
3256  $bodyClasses[] = 'capitalize-all-nouns';
3257  }
3258 
3259  // Parser feature migration class
3260  // The idea is that this will eventually be removed, after the wikitext
3261  // which requires it is cleaned up.
3262  $bodyClasses[] = 'mw-hide-empty-elt';
3263 
3264  $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
3265  $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
3266  $bodyClasses[] =
3267  'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
3268 
3269  $bodyAttrs = [];
3270  // While the implode() is not strictly needed, it's used for backwards compatibility
3271  // (this used to be built as a string and hooks likely still expect that).
3272  $bodyAttrs['class'] = implode( ' ', $bodyClasses );
3273 
3274  // Allow skins and extensions to add body attributes they need
3275  $sk->addToBodyAttributes( $this, $bodyAttrs );
3276  Hooks::run( 'OutputPageBodyAttributes', [ $this, $sk, &$bodyAttrs ] );
3277 
3278  $pieces[] = Html::openElement( 'body', $bodyAttrs );
3279 
3280  return self::combineWrappedStrings( $pieces );
3281  }
3282 
3288  public function getResourceLoader() {
3289  if ( is_null( $this->mResourceLoader ) ) {
3290  // Lazy-initialise as needed
3291  $this->mResourceLoader = MediaWikiServices::getInstance()->getResourceLoader();
3292  }
3293  return $this->mResourceLoader;
3294  }
3295 
3304  public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) {
3305  // Apply 'target' and 'origin' filters
3306  $modules = $this->filterModules( (array)$modules, null, $only );
3307 
3309  $this->getRlClientContext(),
3310  $modules,
3311  $only,
3312  $extraQuery,
3313  $this->getCSPNonce()
3314  );
3315  }
3316 
3323  protected static function combineWrappedStrings( array $chunks ) {
3324  // Filter out empty values
3325  $chunks = array_filter( $chunks, 'strlen' );
3326  return WrappedString::join( "\n", $chunks );
3327  }
3328 
3335  public function getBottomScripts() {
3336  $chunks = [];
3337  $chunks[] = $this->getRlClient()->getBodyHtml();
3338 
3339  // Legacy non-ResourceLoader scripts
3340  $chunks[] = $this->mScripts;
3341 
3342  if ( $this->limitReportJSData ) {
3343  $chunks[] = ResourceLoader::makeInlineScript(
3344  ResourceLoader::makeConfigSetScript(
3345  [ 'wgPageParseReport' => $this->limitReportJSData ]
3346  ),
3347  $this->getCSPNonce()
3348  );
3349  }
3350 
3351  return self::combineWrappedStrings( $chunks );
3352  }
3353 
3360  public function getJsConfigVars() {
3361  return $this->mJsConfigVars;
3362  }
3363 
3370  public function addJsConfigVars( $keys, $value = null ) {
3371  if ( is_array( $keys ) ) {
3372  foreach ( $keys as $key => $value ) {
3373  $this->mJsConfigVars[$key] = $value;
3374  }
3375  return;
3376  }
3377 
3378  $this->mJsConfigVars[$keys] = $value;
3379  }
3380 
3390  public function getJSVars() {
3391  $curRevisionId = 0;
3392  $articleId = 0;
3393  $canonicalSpecialPageName = false; # T23115
3394  $services = MediaWikiServices::getInstance();
3395 
3396  $title = $this->getTitle();
3397  $ns = $title->getNamespace();
3398  $canonicalNamespace = MWNamespace::exists( $ns )
3400  : $title->getNsText();
3401 
3402  $sk = $this->getSkin();
3403  // Get the relevant title so that AJAX features can use the correct page name
3404  // when making API requests from certain special pages (T36972).
3405  $relevantTitle = $sk->getRelevantTitle();
3406  $relevantUser = $sk->getRelevantUser();
3407 
3408  if ( $ns == NS_SPECIAL ) {
3409  list( $canonicalSpecialPageName, /*...*/ ) =
3410  $services->getSpecialPageFactory()->
3411  resolveAlias( $title->getDBkey() );
3412  } elseif ( $this->canUseWikiPage() ) {
3413  $wikiPage = $this->getWikiPage();
3414  $curRevisionId = $wikiPage->getLatest();
3415  $articleId = $wikiPage->getId();
3416  }
3417 
3418  $lang = $title->getPageViewLanguage();
3419 
3420  // Pre-process information
3421  $separatorTransTable = $lang->separatorTransformTable();
3422  $separatorTransTable = $separatorTransTable ?: [];
3423  $compactSeparatorTransTable = [
3424  implode( "\t", array_keys( $separatorTransTable ) ),
3425  implode( "\t", $separatorTransTable ),
3426  ];
3427  $digitTransTable = $lang->digitTransformTable();
3428  $digitTransTable = $digitTransTable ?: [];
3429  $compactDigitTransTable = [
3430  implode( "\t", array_keys( $digitTransTable ) ),
3431  implode( "\t", $digitTransTable ),
3432  ];
3433 
3434  $user = $this->getUser();
3435 
3436  $vars = [
3437  'wgCanonicalNamespace' => $canonicalNamespace,
3438  'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
3439  'wgNamespaceNumber' => $title->getNamespace(),
3440  'wgPageName' => $title->getPrefixedDBkey(),
3441  'wgTitle' => $title->getText(),
3442  'wgCurRevisionId' => $curRevisionId,
3443  'wgRevisionId' => (int)$this->getRevisionId(),
3444  'wgArticleId' => $articleId,
3445  'wgIsArticle' => $this->isArticle(),
3446  'wgIsRedirect' => $title->isRedirect(),
3447  'wgAction' => Action::getActionName( $this->getContext() ),
3448  'wgUserName' => $user->isAnon() ? null : $user->getName(),
3449  'wgUserGroups' => $user->getEffectiveGroups(),
3450  'wgCategories' => $this->getCategories(),
3451  'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
3452  'wgPageContentLanguage' => $lang->getCode(),
3453  'wgPageContentModel' => $title->getContentModel(),
3454  'wgSeparatorTransformTable' => $compactSeparatorTransTable,
3455  'wgDigitTransformTable' => $compactDigitTransTable,
3456  'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
3457  'wgMonthNames' => $lang->getMonthNamesArray(),
3458  'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
3459  'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
3460  'wgRelevantArticleId' => $relevantTitle->getArticleID(),
3461  'wgRequestId' => WebRequest::getRequestId(),
3462  'wgCSPNonce' => $this->getCSPNonce(),
3463  ];
3464 
3465  if ( $user->isLoggedIn() ) {
3466  $vars['wgUserId'] = $user->getId();
3467  $vars['wgUserEditCount'] = $user->getEditCount();
3468  $userReg = $user->getRegistration();
3469  $vars['wgUserRegistration'] = $userReg ? wfTimestamp( TS_UNIX, $userReg ) * 1000 : null;
3470  // Get the revision ID of the oldest new message on the user's talk
3471  // page. This can be used for constructing new message alerts on
3472  // the client side.
3473  $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId();
3474  }
3475 
3476  $contLang = $services->getContentLanguage();
3477  if ( $contLang->hasVariants() ) {
3478  $vars['wgUserVariant'] = $contLang->getPreferredVariant();
3479  }
3480  // Same test as SkinTemplate
3481  $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user )
3482  && ( $title->exists() || $title->quickUserCan( 'create', $user ) );
3483 
3484  $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle
3485  && $relevantTitle->quickUserCan( 'edit', $user )
3486  && ( $relevantTitle->exists() || $relevantTitle->quickUserCan( 'create', $user ) );
3487 
3488  foreach ( $title->getRestrictionTypes() as $type ) {
3489  // Following keys are set in $vars:
3490  // wgRestrictionCreate, wgRestrictionEdit, wgRestrictionMove, wgRestrictionUpload
3491  $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
3492  }
3493 
3494  if ( $title->isMainPage() ) {
3495  $vars['wgIsMainPage'] = true;
3496  }
3497 
3498  if ( $this->mRedirectedFrom ) {
3499  $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey();
3500  }
3501 
3502  if ( $relevantUser ) {
3503  $vars['wgRelevantUserName'] = $relevantUser->getName();
3504  }
3505 
3506  // Allow extensions to add their custom variables to the mw.config map.
3507  // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
3508  // page-dependant but site-wide (without state).
3509  // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
3510  Hooks::run( 'MakeGlobalVariablesScript', [ &$vars, $this ] );
3511 
3512  // Merge in variables from addJsConfigVars last
3513  return array_merge( $vars, $this->getJsConfigVars() );
3514  }
3515 
3525  public function userCanPreview() {
3526  $request = $this->getRequest();
3527  if (
3528  $request->getVal( 'action' ) !== 'submit' ||
3529  !$request->wasPosted()
3530  ) {
3531  return false;
3532  }
3533 
3534  $user = $this->getUser();
3535 
3536  if ( !$user->isLoggedIn() ) {
3537  // Anons have predictable edit tokens
3538  return false;
3539  }
3540  if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
3541  return false;
3542  }
3543 
3544  $title = $this->getTitle();
3545  $errors = $title->getUserPermissionsErrors( 'edit', $user );
3546  if ( count( $errors ) !== 0 ) {
3547  return false;
3548  }
3549 
3550  return true;
3551  }
3552 
3556  public function getHeadLinksArray() {
3557  global $wgVersion;
3558 
3559  $tags = [];
3560  $config = $this->getConfig();
3561 
3562  $canonicalUrl = $this->mCanonicalUrl;
3563 
3564  $tags['meta-generator'] = Html::element( 'meta', [
3565  'name' => 'generator',
3566  'content' => "MediaWiki $wgVersion",
3567  ] );
3568 
3569  if ( $config->get( 'ReferrerPolicy' ) !== false ) {
3570  // Per https://w3c.github.io/webappsec-referrer-policy/#unknown-policy-values
3571  // fallbacks should come before the primary value so we need to reverse the array.
3572  foreach ( array_reverse( (array)$config->get( 'ReferrerPolicy' ) ) as $i => $policy ) {
3573  $tags["meta-referrer-$i"] = Html::element( 'meta', [
3574  'name' => 'referrer',
3575  'content' => $policy,
3576  ] );
3577  }
3578  }
3579 
3580  $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
3581  if ( $p !== 'index,follow' ) {
3582  // http://www.robotstxt.org/wc/meta-user.html
3583  // Only show if it's different from the default robots policy
3584  $tags['meta-robots'] = Html::element( 'meta', [
3585  'name' => 'robots',
3586  'content' => $p,
3587  ] );
3588  }
3589 
3590  foreach ( $this->mMetatags as $tag ) {
3591  if ( strncasecmp( $tag[0], 'http:', 5 ) === 0 ) {
3592  $a = 'http-equiv';
3593  $tag[0] = substr( $tag[0], 5 );
3594  } elseif ( strncasecmp( $tag[0], 'og:', 3 ) === 0 ) {
3595  $a = 'property';
3596  } else {
3597  $a = 'name';
3598  }
3599  $tagName = "meta-{$tag[0]}";
3600  if ( isset( $tags[$tagName] ) ) {
3601  $tagName .= $tag[1];
3602  }
3603  $tags[$tagName] = Html::element( 'meta',
3604  [
3605  $a => $tag[0],
3606  'content' => $tag[1]
3607  ]
3608  );
3609  }
3610 
3611  foreach ( $this->mLinktags as $tag ) {
3612  $tags[] = Html::element( 'link', $tag );
3613  }
3614 
3615  # Universal edit button
3616  if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) {
3617  $user = $this->getUser();
3618  if ( $this->getTitle()->quickUserCan( 'edit', $user )
3619  && ( $this->getTitle()->exists() ||
3620  $this->getTitle()->quickUserCan( 'create', $user ) )
3621  ) {
3622  // Original UniversalEditButton
3623  $msg = $this->msg( 'edit' )->text();
3624  $tags['universal-edit-button'] = Html::element( 'link', [
3625  'rel' => 'alternate',
3626  'type' => 'application/x-wiki',
3627  'title' => $msg,
3628  'href' => $this->getTitle()->getEditURL(),
3629  ] );
3630  // Alternate edit link
3631  $tags['alternative-edit'] = Html::element( 'link', [
3632  'rel' => 'edit',
3633  'title' => $msg,
3634  'href' => $this->getTitle()->getEditURL(),
3635  ] );
3636  }
3637  }
3638 
3639  # Generally the order of the favicon and apple-touch-icon links
3640  # should not matter, but Konqueror (3.5.9 at least) incorrectly
3641  # uses whichever one appears later in the HTML source. Make sure
3642  # apple-touch-icon is specified first to avoid this.
3643  if ( $config->get( 'AppleTouchIcon' ) !== false ) {
3644  $tags['apple-touch-icon'] = Html::element( 'link', [
3645  'rel' => 'apple-touch-icon',
3646  'href' => $config->get( 'AppleTouchIcon' )
3647  ] );
3648  }
3649 
3650  if ( $config->get( 'Favicon' ) !== false ) {
3651  $tags['favicon'] = Html::element( 'link', [
3652  'rel' => 'shortcut icon',
3653  'href' => $config->get( 'Favicon' )
3654  ] );
3655  }
3656 
3657  # OpenSearch description link
3658  $tags['opensearch'] = Html::element( 'link', [
3659  'rel' => 'search',
3660  'type' => 'application/opensearchdescription+xml',
3661  'href' => wfScript( 'opensearch_desc' ),
3662  'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
3663  ] );
3664 
3665  # Real Simple Discovery link, provides auto-discovery information
3666  # for the MediaWiki API (and potentially additional custom API
3667  # support such as WordPress or Twitter-compatible APIs for a
3668  # blogging extension, etc)
3669  $tags['rsd'] = Html::element( 'link', [
3670  'rel' => 'EditURI',
3671  'type' => 'application/rsd+xml',
3672  // Output a protocol-relative URL here if $wgServer is protocol-relative.
3673  // Whether RSD accepts relative or protocol-relative URLs is completely
3674  // undocumented, though.
3675  'href' => wfExpandUrl( wfAppendQuery(
3676  wfScript( 'api' ),
3677  [ 'action' => 'rsd' ] ),
3679  ),
3680  ] );
3681 
3682  # Language variants
3683  if ( !$config->get( 'DisableLangConversion' ) ) {
3684  $lang = $this->getTitle()->getPageLanguage();
3685  if ( $lang->hasVariants() ) {
3686  $variants = $lang->getVariants();
3687  foreach ( $variants as $variant ) {
3688  $tags["variant-$variant"] = Html::element( 'link', [
3689  'rel' => 'alternate',
3690  'hreflang' => LanguageCode::bcp47( $variant ),
3691  'href' => $this->getTitle()->getLocalURL(
3692  [ 'variant' => $variant ] )
3693  ]
3694  );
3695  }
3696  # x-default link per https://support.google.com/webmasters/answer/189077?hl=en
3697  $tags["variant-x-default"] = Html::element( 'link', [
3698  'rel' => 'alternate',
3699  'hreflang' => 'x-default',
3700  'href' => $this->getTitle()->getLocalURL() ] );
3701  }
3702  }
3703 
3704  # Copyright
3705  if ( $this->copyrightUrl !== null ) {
3706  $copyright = $this->copyrightUrl;
3707  } else {
3708  $copyright = '';
3709  if ( $config->get( 'RightsPage' ) ) {
3710  $copy = Title::newFromText( $config->get( 'RightsPage' ) );
3711 
3712  if ( $copy ) {
3713  $copyright = $copy->getLocalURL();
3714  }
3715  }
3716 
3717  if ( !$copyright && $config->get( 'RightsUrl' ) ) {
3718  $copyright = $config->get( 'RightsUrl' );
3719  }
3720  }
3721 
3722  if ( $copyright ) {
3723  $tags['copyright'] = Html::element( 'link', [
3724  'rel' => 'license',
3725  'href' => $copyright ]
3726  );
3727  }
3728 
3729  # Feeds
3730  if ( $config->get( 'Feed' ) ) {
3731  $feedLinks = [];
3732 
3733  foreach ( $this->getSyndicationLinks() as $format => $link ) {
3734  # Use the page name for the title. In principle, this could
3735  # lead to issues with having the same name for different feeds
3736  # corresponding to the same page, but we can't avoid that at
3737  # this low a level.
3738 
3739  $feedLinks[] = $this->feedLink(
3740  $format,
3741  $link,
3742  # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
3743  $this->msg(
3744  "page-{$format}-feed", $this->getTitle()->getPrefixedText()
3745  )->text()
3746  );
3747  }
3748 
3749  # Recent changes feed should appear on every page (except recentchanges,
3750  # that would be redundant). Put it after the per-page feed to avoid
3751  # changing existing behavior. It's still available, probably via a
3752  # menu in your browser. Some sites might have a different feed they'd
3753  # like to promote instead of the RC feed (maybe like a "Recent New Articles"
3754  # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
3755  # If so, use it instead.
3756  $sitename = $config->get( 'Sitename' );
3757  $overrideSiteFeed = $config->get( 'OverrideSiteFeed' );
3758  if ( $overrideSiteFeed ) {
3759  foreach ( $overrideSiteFeed as $type => $feedUrl ) {
3760  // Note, this->feedLink escapes the url.
3761  $feedLinks[] = $this->feedLink(
3762  $type,
3763  $feedUrl,
3764  $this->msg( "site-{$type}-feed", $sitename )->text()
3765  );
3766  }
3767  } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
3768  $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
3769  foreach ( $this->getAdvertisedFeedTypes() as $format ) {
3770  $feedLinks[] = $this->feedLink(
3771  $format,
3772  $rctitle->getLocalURL( [ 'feed' => $format ] ),
3773  # For grep: 'site-rss-feed', 'site-atom-feed'
3774  $this->msg( "site-{$format}-feed", $sitename )->text()
3775  );
3776  }
3777  }
3778 
3779  # Allow extensions to change the list pf feeds. This hook is primarily for changing,
3780  # manipulating or removing existing feed tags. If you want to add new feeds, you should
3781  # use OutputPage::addFeedLink() instead.
3782  Hooks::run( 'AfterBuildFeedLinks', [ &$feedLinks ] );
3783 
3784  $tags += $feedLinks;
3785  }
3786 
3787  # Canonical URL
3788  if ( $config->get( 'EnableCanonicalServerLink' ) ) {
3789  if ( $canonicalUrl !== false ) {
3790  $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL );
3791  } elseif ( $this->isArticleRelated() ) {
3792  // This affects all requests where "setArticleRelated" is true. This is
3793  // typically all requests that show content (query title, curid, oldid, diff),
3794  // and all wikipage actions (edit, delete, purge, info, history etc.).
3795  // It does not apply to File pages and Special pages.
3796  // 'history' and 'info' actions address page metadata rather than the page
3797  // content itself, so they may not be canonicalized to the view page url.
3798  // TODO: this ought to be better encapsulated in the Action class.
3799  $action = Action::getActionName( $this->getContext() );
3800  if ( in_array( $action, [ 'history', 'info' ] ) ) {
3801  $query = "action={$action}";
3802  } else {
3803  $query = '';
3804  }
3805  $canonicalUrl = $this->getTitle()->getCanonicalURL( $query );
3806  } else {
3807  $reqUrl = $this->getRequest()->getRequestURL();
3808  $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL );
3809  }
3810  }
3811  if ( $canonicalUrl !== false ) {
3812  $tags[] = Html::element( 'link', [
3813  'rel' => 'canonical',
3814  'href' => $canonicalUrl
3815  ] );
3816  }
3817 
3818  // Allow extensions to add, remove and/or otherwise manipulate these links
3819  // If you want only to *add* <head> links, please use the addHeadItem()
3820  // (or addHeadItems() for multiple items) method instead.
3821  // This hook is provided as a last resort for extensions to modify these
3822  // links before the output is sent to client.
3823  Hooks::run( 'OutputPageAfterGetHeadLinksArray', [ &$tags, $this ] );
3824 
3825  return $tags;
3826  }
3827 
3836  private function feedLink( $type, $url, $text ) {
3837  return Html::element( 'link', [
3838  'rel' => 'alternate',
3839  'type' => "application/$type+xml",
3840  'title' => $text,
3841  'href' => $url ]
3842  );
3843  }
3844 
3854  public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
3855  $options = [];
3856  if ( $media ) {
3857  $options['media'] = $media;
3858  }
3859  if ( $condition ) {
3860  $options['condition'] = $condition;
3861  }
3862  if ( $dir ) {
3863  $options['dir'] = $dir;
3864  }
3865  $this->styles[$style] = $options;
3866  }
3867 
3875  public function addInlineStyle( $style_css, $flip = 'noflip' ) {
3876  if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
3877  # If wanted, and the interface is right-to-left, flip the CSS
3878  $style_css = CSSJanus::transform( $style_css, true, false );
3879  }
3880  $this->mInlineStyles .= Html::inlineStyle( $style_css );
3881  }
3882 
3888  protected function buildExemptModules() {
3889  $chunks = [];
3890  // Things that go after the ResourceLoaderDynamicStyles marker
3891  $append = [];
3892 
3893  // We want site, private and user styles to override dynamically added styles from
3894  // general modules, but we want dynamically added styles to override statically added
3895  // style modules. So the order has to be:
3896  // - page style modules (formatted by ResourceLoaderClientHtml::getHeadHtml())
3897  // - dynamically loaded styles (added by mw.loader before ResourceLoaderDynamicStyles)
3898  // - ResourceLoaderDynamicStyles marker
3899  // - site/private/user styles
3900 
3901  // Add legacy styles added through addStyle()/addInlineStyle() here
3902  $chunks[] = implode( '', $this->buildCssLinksArray() ) . $this->mInlineStyles;
3903 
3904  $chunks[] = Html::element(
3905  'meta',
3906  [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ]
3907  );
3908 
3909  $separateReq = [ 'site.styles', 'user.styles' ];
3910  foreach ( $this->rlExemptStyleModules as $group => $moduleNames ) {
3911  // Combinable modules
3912  $chunks[] = $this->makeResourceLoaderLink(
3913  array_diff( $moduleNames, $separateReq ),
3915  );
3916 
3917  foreach ( array_intersect( $moduleNames, $separateReq ) as $name ) {
3918  // These require their own dedicated request in order to support "@import"
3919  // syntax, which is incompatible with concatenation. (T147667, T37562)
3920  $chunks[] = $this->makeResourceLoaderLink( $name,
3922  );
3923  }
3924  }
3925 
3926  return self::combineWrappedStrings( array_merge( $chunks, $append ) );
3927  }
3928 
3932  public function buildCssLinksArray() {
3933  $links = [];
3934 
3935  foreach ( $this->styles as $file => $options ) {
3936  $link = $this->styleLink( $file, $options );
3937  if ( $link ) {
3938  $links[$file] = $link;
3939  }
3940  }
3941  return $links;
3942  }
3943 
3951  protected function styleLink( $style, array $options ) {
3952  if ( isset( $options['dir'] ) && $this->getLanguage()->getDir() != $options['dir'] ) {
3953  return '';
3954  }
3955 
3956  if ( isset( $options['media'] ) ) {
3957  $media = self::transformCssMedia( $options['media'] );
3958  if ( is_null( $media ) ) {
3959  return '';
3960  }
3961  } else {
3962  $media = 'all';
3963  }
3964 
3965  if ( substr( $style, 0, 1 ) == '/' ||
3966  substr( $style, 0, 5 ) == 'http:' ||
3967  substr( $style, 0, 6 ) == 'https:' ) {
3968  $url = $style;
3969  } else {
3970  $config = $this->getConfig();
3971  // Append file hash as query parameter
3972  $url = self::transformResourcePath(
3973  $config,
3974  $config->get( 'StylePath' ) . '/' . $style
3975  );
3976  }
3977 
3978  $link = Html::linkedStyle( $url, $media );
3979 
3980  if ( isset( $options['condition'] ) ) {
3981  $condition = htmlspecialchars( $options['condition'] );
3982  $link = "<!--[if $condition]>$link<![endif]-->";
3983  }
3984  return $link;
3985  }
3986 
4008  public static function transformResourcePath( Config $config, $path ) {
4009  global $IP;
4010 
4011  $localDir = $IP;
4012  $remotePathPrefix = $config->get( 'ResourceBasePath' );
4013  if ( $remotePathPrefix === '' ) {
4014  // The configured base path is required to be empty string for
4015  // wikis in the domain root
4016  $remotePath = '/';
4017  } else {
4018  $remotePath = $remotePathPrefix;
4019  }
4020  if ( strpos( $path, $remotePath ) !== 0 || substr( $path, 0, 2 ) === '//' ) {
4021  // - Path is outside wgResourceBasePath, ignore.
4022  // - Path is protocol-relative. Fixes T155310. Not supported by RelPath lib.
4023  return $path;
4024  }
4025  // For files in resources, extensions/ or skins/, ResourceBasePath is preferred here.
4026  // For other misc files in $IP, we'll fallback to that as well. There is, however, a fourth
4027  // supported dir/path pair in the configuration (wgUploadDirectory, wgUploadPath)
4028  // which is not expected to be in wgResourceBasePath on CDNs. (T155146)
4029  $uploadPath = $config->get( 'UploadPath' );
4030  if ( strpos( $path, $uploadPath ) === 0 ) {
4031  $localDir = $config->get( 'UploadDirectory' );
4032  $remotePathPrefix = $remotePath = $uploadPath;
4033  }
4034 
4035  $path = RelPath::getRelativePath( $path, $remotePath );
4036  return self::transformFilePath( $remotePathPrefix, $localDir, $path );
4037  }
4038 
4050  public static function transformFilePath( $remotePathPrefix, $localPath, $file ) {
4051  $hash = md5_file( "$localPath/$file" );
4052  if ( $hash === false ) {
4053  wfLogWarning( __METHOD__ . ": Failed to hash $localPath/$file" );
4054  $hash = '';
4055  }
4056  return "$remotePathPrefix/$file?" . substr( $hash, 0, 5 );
4057  }
4058 
4066  public static function transformCssMedia( $media ) {
4067  global $wgRequest;
4068 
4069  // https://www.w3.org/TR/css3-mediaqueries/#syntax
4070  $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
4071 
4072  // Switch in on-screen display for media testing
4073  $switches = [
4074  'printable' => 'print',
4075  'handheld' => 'handheld',
4076  ];
4077  foreach ( $switches as $switch => $targetMedia ) {
4078  if ( $wgRequest->getBool( $switch ) ) {
4079  if ( $media == $targetMedia ) {
4080  $media = '';
4081  } elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
4082  /* This regex will not attempt to understand a comma-separated media_query_list
4083  *
4084  * Example supported values for $media:
4085  * 'screen', 'only screen', 'screen and (min-width: 982px)' ),
4086  * Example NOT supported value for $media:
4087  * '3d-glasses, screen, print and resolution > 90dpi'
4088  *
4089  * If it's a print request, we never want any kind of screen stylesheets
4090  * If it's a handheld request (currently the only other choice with a switch),
4091  * we don't want simple 'screen' but we might want screen queries that
4092  * have a max-width or something, so we'll pass all others on and let the
4093  * client do the query.
4094  */
4095  if ( $targetMedia == 'print' || $media == 'screen' ) {
4096  return null;
4097  }
4098  }
4099  }
4100  }
4101 
4102  return $media;
4103  }
4104 
4111  public function addWikiMsg( /*...*/ ) {
4112  $args = func_get_args();
4113  $name = array_shift( $args );
4114  $this->addWikiMsgArray( $name, $args );
4115  }
4116 
4125  public function addWikiMsgArray( $name, $args ) {
4126  $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
4127  }
4128 
4154  public function wrapWikiMsg( $wrap /*, ...*/ ) {
4155  $msgSpecs = func_get_args();
4156  array_shift( $msgSpecs );
4157  $msgSpecs = array_values( $msgSpecs );
4158  $s = $wrap;
4159  foreach ( $msgSpecs as $n => $spec ) {
4160  if ( is_array( $spec ) ) {
4161  $args = $spec;
4162  $name = array_shift( $args );
4163  if ( isset( $args['options'] ) ) {
4164  unset( $args['options'] );
4165  wfDeprecated(
4166  'Adding "options" to ' . __METHOD__ . ' is no longer supported',
4167  '1.20'
4168  );
4169  }
4170  } else {
4171  $args = [];
4172  $name = $spec;
4173  }
4174  $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
4175  }
4176  $this->addWikiTextAsInterface( $s );
4177  }
4178 
4184  public function isTOCEnabled() {
4185  return $this->mEnableTOC;
4186  }
4187 
4194  public function enableSectionEditLinks( $flag = true ) {
4195  wfDeprecated( __METHOD__, '1.31' );
4196  }
4197 
4203  public function sectionEditLinksEnabled() {
4204  wfDeprecated( __METHOD__, '1.31' );
4205  return true;
4206  }
4207 
4215  public static function setupOOUI( $skinName = 'default', $dir = 'ltr' ) {
4217  $theme = $themes[$skinName] ?? $themes['default'];
4218  // For example, 'OOUI\WikimediaUITheme'.
4219  $themeClass = "OOUI\\{$theme}Theme";
4220  OOUI\Theme::setSingleton( new $themeClass() );
4221  OOUI\Element::setDefaultDir( $dir );
4222  }
4223 
4230  public function enableOOUI() {
4231  self::setupOOUI(
4232  strtolower( $this->getSkin()->getSkinName() ),
4233  $this->getLanguage()->getDir()
4234  );
4235  $this->addModuleStyles( [
4236  'oojs-ui-core.styles',
4237  'oojs-ui.styles.indicators',
4238  'oojs-ui.styles.textures',
4239  'mediawiki.widgets.styles',
4240  'oojs-ui.styles.icons-content',
4241  'oojs-ui.styles.icons-alerts',
4242  'oojs-ui.styles.icons-interactions',
4243  ] );
4244  }
4245 
4255  public function getCSPNonce() {
4257  return false;
4258  }
4259  if ( $this->CSPNonce === null ) {
4260  // XXX It might be expensive to generate randomness
4261  // on every request, on Windows.
4262  $rand = random_bytes( 15 );
4263  $this->CSPNonce = base64_encode( $rand );
4264  }
4265  return $this->CSPNonce;
4266  }
4267 
4268 }
$filter
$filter
Definition: profileinfo.php:341
Action\getActionName
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
ParserOutput\getEnableOOUI
getEnableOOUI()
Definition: ParserOutput.php:577
ParserOptions
Set options of the Parser.
Definition: ParserOptions.php:42
ContextSource\getConfig
getConfig()
Definition: ContextSource.php:63
ResourceLoaderContext
Object passed around to modules which contains information about the state of a specific loader reque...
Definition: ResourceLoaderContext.php:32
$user
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1476
FauxRequest
WebRequest clone which takes values from a provided array.
Definition: FauxRequest.php:33
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:306
ContextSource\getContext
getContext()
Get the base IContextSource object.
Definition: ContextSource.php:40
HtmlArmor
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:28
ResourceLoaderClientHtml
Load and configure a ResourceLoader client on an HTML page.
Definition: ResourceLoaderClientHtml.php:29
PROTO_CANONICAL
const PROTO_CANONICAL
Definition: Defines.php:223
$file
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Definition: router.php:42
ParserOutput
Definition: ParserOutput.php:25
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
Article\formatRobotPolicy
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition: Article.php:1027
$context
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 $context
Definition: hooks.txt:2636
$wgParser
$wgParser
Definition: Setup.php:886
ResourceLoaderModule\TYPE_COMBINED
const TYPE_COMBINED
Definition: ResourceLoaderModule.php:39
LinkBatch
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition: LinkBatch.php:34
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:33
wfSetVar
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...
Definition: GlobalFunctions.php:1633
captcha-old.count
count
Definition: captcha-old.py:249
ResourceLoaderModule\ORIGIN_USER_SITEWIDE
const ORIGIN_USER_SITEWIDE
Definition: ResourceLoaderModule.php:55
ContextSource\msg
msg( $key)
Get a Message object with context set Parameters are the same as wfMessage()
Definition: ContextSource.php:168
Title\newMainPage
static newMainPage()
Create a new Title for the Main Page.
Definition: Title.php:632
ParserOutput\getModules
getModules()
Definition: ParserOutput.php:517
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1912
$wgVersion
$wgVersion
MediaWiki version number.
Definition: DefaultSettings.php:75
ParserOutput\getJsConfigVars
getJsConfigVars()
Definition: ParserOutput.php:534
wfUrlencode
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness,...
Definition: GlobalFunctions.php:333
ParserOptions\newFromAnon
static newFromAnon()
Get a ParserOptions object for an anonymous user.
Definition: ParserOptions.php:1004
$params
$params
Definition: styleTest.css.php:44
Skin\addToBodyAttributes
addToBodyAttributes( $out, &$bodyAttrs)
This will be called by OutputPage::headElement when it is creating the "<body>" tag,...
Definition: Skin.php:489
ResourceLoaderClientHtml\setExemptStates
setExemptStates(array $states)
Set state of special modules that are handled by the caller manually.
Definition: ResourceLoaderClientHtml.php:108
$s
$s
Definition: mergeMessageFileList.php:186
$linkRenderer
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
SpecialPage\getTitleFor
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Definition: SpecialPage.php:82
ContextSource\canUseWikiPage
canUseWikiPage()
Check whether a WikiPage object can be get with getWikiPage().
Definition: ContextSource.php:91
wfLogWarning
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
Definition: GlobalFunctions.php:1105
$res
$res
Definition: database.txt:21
ContextSource\getRequest
getRequest()
Definition: ContextSource.php:71
$resourceLoader
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:2636
IExpiringStore\TTL_MINUTE
const TTL_MINUTE
Definition: IExpiringStore.php:34
User\groupHasPermission
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition: User.php:5065
ParserOutput\getHeadItems
getHeadItems()
Definition: ParserOutput.php:513
ContextSource\getUser
getUser()
Definition: ContextSource.php:120
ContextSource\getTitle
getTitle()
Definition: ContextSource.php:79
Skin\getHtmlElementAttributes
getHtmlElementAttributes()
Return values for <html> element.
Definition: Skin.php:473
LinkBatch\setArray
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
LinkCache\getSelectFields
static getSelectFields()
Fields that LinkCache needs to select.
Definition: LinkCache.php:206
php
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
$dbr
$dbr
Definition: testCompression.php:50
ResourceLoaderClientHtml\setConfig
setConfig(array $vars)
Set mw.config variables.
Definition: ResourceLoaderClientHtml.php:77
ContextSource\getLanguage
getLanguage()
Definition: ContextSource.php:128
wfAppendQuery
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
Definition: GlobalFunctions.php:463
ParserOutput\getModuleStyles
getModuleStyles()
Definition: ParserOutput.php:526
Xml\encodeJsCall
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition: Xml.php:677
ContentSecurityPolicy\sendHeaders
static sendHeaders(IContextSource $context)
Send CSP headers based on wiki config.
Definition: ContentSecurityPolicy.php:73
$query
null for the 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
Config
Interface for configuration instances.
Definition: Config.php:28
NS_SPECIAL
const NS_SPECIAL
Definition: Defines.php:53
MediaWiki\Linker\LinkTarget\getNamespace
getNamespace()
Get the namespace index.
$data
$data
Utility to generate mapping file used in mw.Title (phpCharToUpper.json)
Definition: generatePhpCharToUpperMappings.php:13
File
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:52
ParserOutput\getLimitReportJSData
getLimitReportJSData()
Definition: ParserOutput.php:565
$html
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
MWException
MediaWiki exception.
Definition: MWException.php:26
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:925
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1078
wfScript
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
Definition: GlobalFunctions.php:2714
Wikimedia\Rdbms\IResultWrapper
Result wrapper for grabbing data queried from an IDatabase object.
Definition: IResultWrapper.php:24
ResourceLoaderWikiModule\preloadTitleInfo
static preloadTitleInfo(ResourceLoaderContext $context, IDatabase $db, array $moduleNames)
Definition: ResourceLoaderWikiModule.php:452
Config\get
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2636
ContextSource
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
Definition: ContextSource.php:29
ContextSource\getWikiPage
getWikiPage()
Get the WikiPage object.
Definition: ContextSource.php:104
$attribs
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
$modules
$modules
Definition: HTMLFormElement.php:12
$IP
$IP
Definition: update.php:3
PROTO_CURRENT
const PROTO_CURRENT
Definition: Defines.php:222
ContextSource\getSkin
getSkin()
Definition: ContextSource.php:136
ResourceLoaderModule\TYPE_SCRIPTS
const TYPE_SCRIPTS
Definition: ResourceLoaderModule.php:37
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
$code
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
wfCgiToArray
wfCgiToArray( $query)
This is the logical opposite of wfArrayToCgi(): it accepts a query string as its argument and returns...
Definition: GlobalFunctions.php:416
$vars
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition: hooks.txt:2220
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:78
array
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))
ParserOutput\getIndicators
getIndicators()
Definition: ParserOutput.php:462
ResourceLoaderModule\getOrigin
getOrigin()
Get this module's origin.
Definition: ResourceLoaderModule.php:123
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:949
string
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
ContextSource\setContext
setContext(IContextSource $context)
Definition: ContextSource.php:55
ParserOutput\getLanguageLinks
& getLanguageLinks()
Definition: ParserOutput.php:442
list
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
Skin\outputPage
outputPage(OutputPage $out=null)
Outputs the HTML generated by other functions.
JavaScriptContent
Content for JavaScript pages.
Definition: JavaScriptContent.php:33
ParserOutput\getTemplateIds
& getTemplateIds()
Definition: ParserOutput.php:490
ParserOutput\getTOCHTML
getTOCHTML()
Definition: ParserOutput.php:550
null
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
$request
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:2636
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:604
Skin\getRelevantTitle
getRelevantTitle()
Return the "relevant" title.
Definition: Skin.php:342
ParserOutput\getNewSection
getNewSection()
Definition: ParserOutput.php:674
Hooks\runWithoutAbort
static runWithoutAbort( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:231
Skin\getPageClasses
getPageClasses( $title)
TODO: document.
Definition: Skin.php:437
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2162
wfClearOutputBuffers
wfClearOutputBuffers()
More legible than passing a 'false' parameter to wfResetOutputBuffers():
Definition: GlobalFunctions.php:1774
$value
$value
Definition: styleTest.css.php:49
Skin\getDefaultModules
getDefaultModules()
Defines the ResourceLoader modules that should be added to the skin It is recommended that skins wish...
Definition: Skin.php:176
ParserOptions\newFromContext
static newFromContext(IContextSource $context)
Get a ParserOptions object from a IContextSource object.
Definition: ParserOptions.php:1043
$header
$header
Definition: updateCredits.php:41
ParserOutput\getOutputHooks
getOutputHooks()
Definition: ParserOutput.php:538
ParserOutput\getHideNewSection
getHideNewSection()
Definition: ParserOutput.php:671
MediaWiki\Session\SessionManager
This serves as the entry point to the MediaWiki session handling system.
Definition: SessionManager.php:50
DerivativeResourceLoaderContext
Allows changing specific properties of a context object, without changing the main one.
Definition: DerivativeResourceLoaderContext.php:30
MediaWiki\Linker\LinkTarget\getDBkey
getDBkey()
Get the main part with underscores.
PROTO_RELATIVE
const PROTO_RELATIVE
Definition: Defines.php:221
MWNamespace\exists
static exists( $index)
Returns whether the specified namespace exists.
Definition: MWNamespace.php:183
ParserOutput\getNoGallery
getNoGallery()
Definition: ParserOutput.php:509
ResourceLoaderModule\ORIGIN_CORE_INDIVIDUAL
const ORIGIN_CORE_INDIVIDUAL
Definition: ResourceLoaderModule.php:51
plain
either a plain
Definition: hooks.txt:2046
getSkinThemeMap
static getSkinThemeMap()
Return a map of skin names (in lowercase) to OOUI theme names, defining which theme a given skin shou...
Definition: ResourceLoaderOOUIModule.php:75
$response
this hook is for auditing only $response
Definition: hooks.txt:780
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:53
ResourceLoaderModule\ORIGIN_ALL
const ORIGIN_ALL
Definition: ResourceLoaderModule.php:61
wfGetAllCallers
wfGetAllCallers( $limit=3)
Return a string consisting of callers in the stack.
Definition: GlobalFunctions.php:1496
Content
Base interface for content objects.
Definition: Content.php:34
ContentSecurityPolicy\isNonceRequired
static isNonceRequired(Config $config)
Should we set nonce attribute.
Definition: ContentSecurityPolicy.php:480
ResourceLoaderClientHtml\makeLoad
static makeLoad(ResourceLoaderContext $mainContext, array $modules, $only, array $extraQuery=[], $nonce=null)
Explicily load or embed modules on a page.
Definition: ResourceLoaderClientHtml.php:372
text
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
Definition: All_system_messages.txt:1267
$args
if( $line===false) $args
Definition: cdb.php:64
ParserOutput\getFileSearchOptions
& getFileSearchOptions()
Definition: ParserOutput.php:498
Title
Represents a title within MediaWiki.
Definition: Title.php:40
ResourceLoaderModule
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
Definition: ResourceLoaderModule.php:35
and
and that you know you can do these things To protect your we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights These restrictions translate to certain responsibilities for you if you distribute copies of the or if you modify it For if you distribute copies of such a whether gratis or for a you must give the recipients all the rights that you have You must make sure that receive or can get the source code And you must show them these terms so they know their rights We protect your rights with two and(2) offer you this license which gives you legal permission to copy
ContentHandler\getContentText
static getContentText(Content $content=null)
Convenience function for getting flat text from a Content object.
Definition: ContentHandler.php:83
$options
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
ResourceLoaderClientHtml\setModules
setModules(array $modules)
Ensure one or more modules are loaded.
Definition: ResourceLoaderClientHtml.php:88
ResourceLoaderClientHtml\setModuleStyles
setModuleStyles(array $modules)
Ensure the styles of one or more modules are loaded.
Definition: ResourceLoaderClientHtml.php:97
WebRequest\getRequestId
static getRequestId()
Get the unique request ID.
Definition: WebRequest.php:275
$path
$path
Definition: NoLocalSettings.php:25
ParserOutput\getCategories
& getCategories()
Definition: ParserOutput.php:454
as
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
LanguageCode\bcp47
static bcp47( $code)
Get the normalised IETF language tag See unit test for examples.
Definition: LanguageCode.php:179
messages
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
Skin\getRelevantUser
getRelevantUser()
Return the "relevant" user.
Definition: Skin.php:363
$keys
$keys
Definition: testCompression.php:67
$link
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:3053
ResourceLoaderModule\TYPE_STYLES
const TYPE_STYLES
Definition: ResourceLoaderModule.php:38
MWDebug\addModules
static addModules(OutputPage $out)
Add ResourceLoader modules to the OutputPage object if debugging is enabled.
Definition: MWDebug.php:96
$content
$content
Definition: pageupdater.txt:72
CacheTime\isCacheable
isCacheable()
Definition: CacheTime.php:155
ParserOutput\getText
getText( $options=[])
Get the output HTML.
Definition: ParserOutput.php:303
$t
$t
Definition: testCompression.php:69
$services
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:2220
$wgRequest
if(! $wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:728
captcha-old.output
output
Definition: captcha-old.py:240
Skin\getSkinName
getSkinName()
Definition: Skin.php:155
MediaWikiServices
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
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:26
Skin
The main skin class which provides methods and properties for all other skins.
Definition: Skin.php:38
wfMessage
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
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
$template
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
redirect
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 redirect
Definition: All_system_messages.txt:1267
MWNamespace\getCanonicalName
static getCanonicalName( $index)
Returns the canonical (English) name for a given index.
Definition: MWNamespace.php:256
wfExpandUrl
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
Definition: GlobalFunctions.php:515
ParserOutput\preventClickjacking
preventClickjacking( $flag=null)
Get or set the prevent-clickjacking flag.
Definition: ParserOutput.php:1229
wfArrayToCgi
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
Definition: GlobalFunctions.php:371
$type
$type
Definition: testCompression.php:48