MediaWiki  master
OutputPage.php
Go to the documentation of this file.
1 <?php
25 use WrappedString\WrappedString;
26 use WrappedString\WrappedStringList;
27 
43 class OutputPage extends ContextSource {
45  protected $mMetatags = [];
46 
48  protected $mLinktags = [];
49 
51  protected $mCanonicalUrl = false;
52 
57  protected $mExtStyles = [];
58 
62  public $mPagetitle = '';
63 
68  public $mBodytext = '';
69 
75  public $mDebugtext = '';
76 
78  private $mHTMLtitle = '';
79 
84  private $mIsarticle = false;
85 
87  private $mIsArticleRelated = true;
88 
93  private $mPrintable = false;
94 
99  private $mSubtitle = [];
100 
102  public $mRedirect = '';
103 
105  protected $mStatusCode;
106 
111  protected $mLastModified = '';
112 
114  protected $mCategoryLinks = [];
115 
117  protected $mCategories = [];
118 
120  protected $mIndicators = [];
121 
123  private $mLanguageLinks = [];
124 
131  private $mScripts = '';
132 
134  protected $mInlineStyles = '';
135 
140  public $mPageLinkTitle = '';
141 
143  protected $mHeadItems = [];
144 
146  protected $mModules = [];
147 
149  protected $mModuleScripts = [];
150 
152  protected $mModuleStyles = [];
153 
155  protected $mResourceLoader;
156 
158  protected $mJsConfigVars = [];
159 
161  protected $mTemplateIds = [];
162 
164  protected $mImageTimeKeys = [];
165 
167  public $mRedirectCode = '';
168 
169  protected $mFeedLinksAppendQuery = null;
170 
176  protected $mAllowedModules = [
178  ];
179 
181  protected $mDoNothing = false;
182 
183  // Parser related.
184 
186  protected $mContainsNewMagic = 0;
187 
192  protected $mParserOptions = null;
193 
199  private $mFeedLinks = [];
200 
201  // Gwicke work on squid caching? Roughly from 2003.
202  protected $mEnableClientCache = true;
203 
205  private $mArticleBodyOnly = false;
206 
208  protected $mNewSectionLink = false;
209 
211  protected $mHideNewSectionLink = false;
212 
218  public $mNoGallery = false;
219 
221  private $mPageTitleActionText = '';
222 
224  protected $mCdnMaxage = 0;
226  protected $mCdnMaxageLimit = INF;
227 
233  protected $mPreventClickjacking = true;
234 
236  private $mRevisionId = null;
237 
239  private $mRevisionTimestamp = null;
240 
242  protected $mFileVersion = null;
243 
252  protected $styles = [];
253 
254  private $mIndexPolicy = 'index';
255  private $mFollowPolicy = 'follow';
256  private $mVaryHeader = [
257  'Accept-Encoding' => [ 'match=gzip' ],
258  ];
259 
266  private $mRedirectedFrom = null;
267 
271  private $mProperties = [];
272 
276  private $mTarget = null;
277 
281  private $mEnableTOC = true;
282 
286  private $mEnableSectionEditLinks = true;
287 
291  private $copyrightUrl;
292 
294  private $limitReportData = [];
295 
302  function __construct( IContextSource $context = null ) {
303  if ( $context === null ) {
304  # Extensions should use `new RequestContext` instead of `new OutputPage` now.
305  wfDeprecated( __METHOD__, '1.18' );
306  } else {
307  $this->setContext( $context );
308  }
309  }
310 
317  public function redirect( $url, $responsecode = '302' ) {
318  # Strip newlines as a paranoia check for header injection in PHP<5.1.2
319  $this->mRedirect = str_replace( "\n", '', $url );
320  $this->mRedirectCode = $responsecode;
321  }
322 
328  public function getRedirect() {
329  return $this->mRedirect;
330  }
331 
340  public function setCopyrightUrl( $url ) {
341  $this->copyrightUrl = $url;
342  }
343 
349  public function setStatusCode( $statusCode ) {
350  $this->mStatusCode = $statusCode;
351  }
352 
360  function addMeta( $name, $val ) {
361  array_push( $this->mMetatags, [ $name, $val ] );
362  }
363 
370  public function getMetaTags() {
371  return $this->mMetatags;
372  }
373 
381  function addLink( array $linkarr ) {
382  array_push( $this->mLinktags, $linkarr );
383  }
384 
391  public function getLinkTags() {
392  return $this->mLinktags;
393  }
394 
402  function addMetadataLink( array $linkarr ) {
403  $linkarr['rel'] = $this->getMetadataAttribute();
404  $this->addLink( $linkarr );
405  }
406 
412  function setCanonicalUrl( $url ) {
413  $this->mCanonicalUrl = $url;
414  }
415 
423  public function getCanonicalUrl() {
424  return $this->mCanonicalUrl;
425  }
426 
432  public function getMetadataAttribute() {
433  # note: buggy CC software only reads first "meta" link
434  static $haveMeta = false;
435  if ( $haveMeta ) {
436  return 'alternate meta';
437  } else {
438  $haveMeta = true;
439  return 'meta';
440  }
441  }
442 
450  function addScript( $script ) {
451  $this->mScripts .= $script;
452  }
453 
463  public function addExtensionStyle( $url ) {
464  wfDeprecated( __METHOD__, '1.27' );
465  array_push( $this->mExtStyles, $url );
466  }
467 
474  function getExtStyle() {
475  wfDeprecated( __METHOD__, '1.27' );
476  return $this->mExtStyles;
477  }
478 
487  public function addScriptFile( $file, $version = null ) {
488  // See if $file parameter is an absolute URL or begins with a slash
489  if ( substr( $file, 0, 1 ) == '/' || preg_match( '#^[a-z]*://#i', $file ) ) {
490  $path = $file;
491  } else {
492  $path = $this->getConfig()->get( 'StylePath' ) . "/common/{$file}";
493  }
494  if ( is_null( $version ) ) {
495  $version = $this->getConfig()->get( 'StyleVersion' );
496  }
498  }
499 
506  public function addInlineScript( $script ) {
507  $this->mScripts .= Html::inlineScript( $script );
508  }
509 
518  protected function filterModules( array $modules, $position = null,
520  ) {
522  $filteredModules = [];
523  foreach ( $modules as $val ) {
524  $module = $resourceLoader->getModule( $val );
525  if ( $module instanceof ResourceLoaderModule
526  && $module->getOrigin() <= $this->getAllowedModules( $type )
527  && ( is_null( $position ) || $module->getPosition() == $position )
528  && ( !$this->mTarget || in_array( $this->mTarget, $module->getTargets() ) )
529  ) {
530  $filteredModules[] = $val;
531  }
532  }
533  return $filteredModules;
534  }
535 
544  public function getModules( $filter = false, $position = null, $param = 'mModules' ) {
545  $modules = array_values( array_unique( $this->$param ) );
546  return $filter
547  ? $this->filterModules( $modules, $position )
548  : $modules;
549  }
550 
558  public function addModules( $modules ) {
559  $this->mModules = array_merge( $this->mModules, (array)$modules );
560  }
561 
570  public function getModuleScripts( $filter = false, $position = null ) {
571  return $this->getModules( $filter, $position, 'mModuleScripts' );
572  }
573 
581  public function addModuleScripts( $modules ) {
582  $this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules );
583  }
584 
593  public function getModuleStyles( $filter = false, $position = null ) {
594  return $this->getModules( $filter, $position, 'mModuleStyles' );
595  }
596 
606  public function addModuleStyles( $modules ) {
607  $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
608  }
609 
613  public function getTarget() {
614  return $this->mTarget;
615  }
616 
622  public function setTarget( $target ) {
623  $this->mTarget = $target;
624  }
625 
631  function getHeadItemsArray() {
632  return $this->mHeadItems;
633  }
634 
647  public function addHeadItem( $name, $value ) {
648  $this->mHeadItems[$name] = $value;
649  }
650 
657  public function hasHeadItem( $name ) {
658  return isset( $this->mHeadItems[$name] );
659  }
660 
665  public function setETag( $tag ) {
666  }
667 
675  public function setArticleBodyOnly( $only ) {
676  $this->mArticleBodyOnly = $only;
677  }
678 
684  public function getArticleBodyOnly() {
686  }
687 
695  public function setProperty( $name, $value ) {
696  $this->mProperties[$name] = $value;
697  }
698 
706  public function getProperty( $name ) {
707  if ( isset( $this->mProperties[$name] ) ) {
708  return $this->mProperties[$name];
709  } else {
710  return null;
711  }
712  }
713 
725  public function checkLastModified( $timestamp ) {
726  if ( !$timestamp || $timestamp == '19700101000000' ) {
727  wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
728  return false;
729  }
730  $config = $this->getConfig();
731  if ( !$config->get( 'CachePages' ) ) {
732  wfDebug( __METHOD__ . ": CACHE DISABLED\n" );
733  return false;
734  }
735 
737  $modifiedTimes = [
738  'page' => $timestamp,
739  'user' => $this->getUser()->getTouched(),
740  'epoch' => $config->get( 'CacheEpoch' )
741  ];
742  if ( $config->get( 'UseSquid' ) ) {
743  // bug 44570: the core page itself may not change, but resources might
744  $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, time() - $config->get( 'SquidMaxage' ) );
745  }
746  Hooks::run( 'OutputPageCheckLastModified', [ &$modifiedTimes, $this ] );
747 
748  $maxModified = max( $modifiedTimes );
749  $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
750 
751  $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' );
752  if ( $clientHeader === false ) {
753  wfDebug( __METHOD__ . ": client did not send If-Modified-Since header", 'private' );
754  return false;
755  }
756 
757  # IE sends sizes after the date like this:
758  # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
759  # this breaks strtotime().
760  $clientHeader = preg_replace( '/;.*$/', '', $clientHeader );
761 
762  MediaWiki\suppressWarnings(); // E_STRICT system time bitching
763  $clientHeaderTime = strtotime( $clientHeader );
764  MediaWiki\restoreWarnings();
765  if ( !$clientHeaderTime ) {
766  wfDebug( __METHOD__
767  . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
768  return false;
769  }
770  $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
771 
772  # Make debug info
773  $info = '';
774  foreach ( $modifiedTimes as $name => $value ) {
775  if ( $info !== '' ) {
776  $info .= ', ';
777  }
778  $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
779  }
780 
781  wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
782  wfTimestamp( TS_ISO_8601, $clientHeaderTime ), 'private' );
783  wfDebug( __METHOD__ . ": effective Last-Modified: " .
784  wfTimestamp( TS_ISO_8601, $maxModified ), 'private' );
785  if ( $clientHeaderTime < $maxModified ) {
786  wfDebug( __METHOD__ . ": STALE, $info", 'private' );
787  return false;
788  }
789 
790  # Not modified
791  # Give a 304 Not Modified response code and disable body output
792  wfDebug( __METHOD__ . ": NOT MODIFIED, $info", 'private' );
793  ini_set( 'zlib.output_compression', 0 );
794  $this->getRequest()->response()->statusHeader( 304 );
795  $this->sendCacheControl();
796  $this->disable();
797 
798  // Don't output a compressed blob when using ob_gzhandler;
799  // it's technically against HTTP spec and seems to confuse
800  // Firefox when the response gets split over two packets.
802 
803  return true;
804  }
805 
812  public function setLastModified( $timestamp ) {
813  $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp );
814  }
815 
824  public function setRobotPolicy( $policy ) {
825  $policy = Article::formatRobotPolicy( $policy );
826 
827  if ( isset( $policy['index'] ) ) {
828  $this->setIndexPolicy( $policy['index'] );
829  }
830  if ( isset( $policy['follow'] ) ) {
831  $this->setFollowPolicy( $policy['follow'] );
832  }
833  }
834 
842  public function setIndexPolicy( $policy ) {
843  $policy = trim( $policy );
844  if ( in_array( $policy, [ 'index', 'noindex' ] ) ) {
845  $this->mIndexPolicy = $policy;
846  }
847  }
848 
856  public function setFollowPolicy( $policy ) {
857  $policy = trim( $policy );
858  if ( in_array( $policy, [ 'follow', 'nofollow' ] ) ) {
859  $this->mFollowPolicy = $policy;
860  }
861  }
862 
869  public function setPageTitleActionText( $text ) {
870  $this->mPageTitleActionText = $text;
871  }
872 
878  public function getPageTitleActionText() {
880  }
881 
888  public function setHTMLTitle( $name ) {
889  if ( $name instanceof Message ) {
890  $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
891  } else {
892  $this->mHTMLtitle = $name;
893  }
894  }
895 
901  public function getHTMLTitle() {
902  return $this->mHTMLtitle;
903  }
904 
910  public function setRedirectedFrom( $t ) {
911  $this->mRedirectedFrom = $t;
912  }
913 
924  public function setPageTitle( $name ) {
925  if ( $name instanceof Message ) {
926  $name = $name->setContext( $this->getContext() )->text();
927  }
928 
929  # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
930  # but leave "<i>foobar</i>" alone
932  $this->mPagetitle = $nameWithTags;
933 
934  # change "<i>foo&amp;bar</i>" to "foo&bar"
935  $this->setHTMLTitle(
936  $this->msg( 'pagetitle' )->rawParams( Sanitizer::stripAllTags( $nameWithTags ) )
937  ->inContentLanguage()
938  );
939  }
940 
946  public function getPageTitle() {
947  return $this->mPagetitle;
948  }
949 
955  public function setTitle( Title $t ) {
956  $this->getContext()->setTitle( $t );
957  }
958 
964  public function setSubtitle( $str ) {
965  $this->clearSubtitle();
966  $this->addSubtitle( $str );
967  }
968 
974  public function addSubtitle( $str ) {
975  if ( $str instanceof Message ) {
976  $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
977  } else {
978  $this->mSubtitle[] = $str;
979  }
980  }
981 
990  public static function buildBacklinkSubtitle( Title $title, $query = [] ) {
991  if ( $title->isRedirect() ) {
992  $query['redirect'] = 'no';
993  }
994  return wfMessage( 'backlinksubtitle' )
995  ->rawParams( Linker::link( $title, null, [], $query ) );
996  }
997 
1004  public function addBacklinkSubtitle( Title $title, $query = [] ) {
1005  $this->addSubtitle( self::buildBacklinkSubtitle( $title, $query ) );
1006  }
1007 
1011  public function clearSubtitle() {
1012  $this->mSubtitle = [];
1013  }
1014 
1020  public function getSubtitle() {
1021  return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
1022  }
1023 
1028  public function setPrintable() {
1029  $this->mPrintable = true;
1030  }
1031 
1037  public function isPrintable() {
1038  return $this->mPrintable;
1039  }
1040 
1044  public function disable() {
1045  $this->mDoNothing = true;
1046  }
1047 
1053  public function isDisabled() {
1054  return $this->mDoNothing;
1055  }
1056 
1062  public function showNewSectionLink() {
1063  return $this->mNewSectionLink;
1064  }
1065 
1071  public function forceHideNewSectionLink() {
1073  }
1074 
1083  public function setSyndicated( $show = true ) {
1084  if ( $show ) {
1085  $this->setFeedAppendQuery( false );
1086  } else {
1087  $this->mFeedLinks = [];
1088  }
1089  }
1090 
1100  public function setFeedAppendQuery( $val ) {
1101  $this->mFeedLinks = [];
1102 
1103  foreach ( $this->getConfig()->get( 'AdvertisedFeedTypes' ) as $type ) {
1104  $query = "feed=$type";
1105  if ( is_string( $val ) ) {
1106  $query .= '&' . $val;
1107  }
1108  $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
1109  }
1110  }
1111 
1118  public function addFeedLink( $format, $href ) {
1119  if ( in_array( $format, $this->getConfig()->get( 'AdvertisedFeedTypes' ) ) ) {
1120  $this->mFeedLinks[$format] = $href;
1121  }
1122  }
1123 
1128  public function isSyndicated() {
1129  return count( $this->mFeedLinks ) > 0;
1130  }
1131 
1136  public function getSyndicationLinks() {
1137  return $this->mFeedLinks;
1138  }
1139 
1145  public function getFeedAppendQuery() {
1147  }
1148 
1156  public function setArticleFlag( $v ) {
1157  $this->mIsarticle = $v;
1158  if ( $v ) {
1159  $this->mIsArticleRelated = $v;
1160  }
1161  }
1162 
1169  public function isArticle() {
1170  return $this->mIsarticle;
1171  }
1172 
1179  public function setArticleRelated( $v ) {
1180  $this->mIsArticleRelated = $v;
1181  if ( !$v ) {
1182  $this->mIsarticle = false;
1183  }
1184  }
1185 
1191  public function isArticleRelated() {
1192  return $this->mIsArticleRelated;
1193  }
1194 
1201  public function addLanguageLinks( array $newLinkArray ) {
1202  $this->mLanguageLinks += $newLinkArray;
1203  }
1204 
1211  public function setLanguageLinks( array $newLinkArray ) {
1212  $this->mLanguageLinks = $newLinkArray;
1213  }
1214 
1220  public function getLanguageLinks() {
1221  return $this->mLanguageLinks;
1222  }
1223 
1229  public function addCategoryLinks( array $categories ) {
1231 
1232  if ( !is_array( $categories ) || count( $categories ) == 0 ) {
1233  return;
1234  }
1235 
1236  # Add the links to a LinkBatch
1237  $arr = [ NS_CATEGORY => $categories ];
1238  $lb = new LinkBatch;
1239  $lb->setArray( $arr );
1240 
1241  # Fetch existence plus the hiddencat property
1242  $dbr = wfGetDB( DB_SLAVE );
1243  $fields = array_merge(
1245  [ 'page_namespace', 'page_title', 'pp_value' ]
1246  );
1247 
1248  $res = $dbr->select( [ 'page', 'page_props' ],
1249  $fields,
1250  $lb->constructSet( 'page', $dbr ),
1251  __METHOD__,
1252  [],
1253  [ 'page_props' => [ 'LEFT JOIN', [
1254  'pp_propname' => 'hiddencat',
1255  'pp_page = page_id'
1256  ] ] ]
1257  );
1258 
1259  # Add the results to the link cache
1260  $lb->addResultToCache( LinkCache::singleton(), $res );
1261 
1262  # Set all the values to 'normal'.
1263  $categories = array_fill_keys( array_keys( $categories ), 'normal' );
1264 
1265  # Mark hidden categories
1266  foreach ( $res as $row ) {
1267  if ( isset( $row->pp_value ) ) {
1268  $categories[$row->page_title] = 'hidden';
1269  }
1270  }
1271 
1272  # Add the remaining categories to the skin
1273  if ( Hooks::run(
1274  'OutputPageMakeCategoryLinks',
1275  [ &$this, $categories, &$this->mCategoryLinks ] )
1276  ) {
1277  foreach ( $categories as $category => $type ) {
1278  // array keys will cast numeric category names to ints, so cast back to string
1279  $category = (string)$category;
1280  $origcategory = $category;
1281  $title = Title::makeTitleSafe( NS_CATEGORY, $category );
1282  if ( !$title ) {
1283  continue;
1284  }
1285  $wgContLang->findVariantLink( $category, $title, true );
1286  if ( $category != $origcategory && array_key_exists( $category, $categories ) ) {
1287  continue;
1288  }
1289  $text = $wgContLang->convertHtml( $title->getText() );
1290  $this->mCategories[] = $title->getText();
1291  $this->mCategoryLinks[$type][] = Linker::link( $title, $text );
1292  }
1293  }
1294  }
1295 
1301  public function setCategoryLinks( array $categories ) {
1302  $this->mCategoryLinks = [];
1303  $this->addCategoryLinks( $categories );
1304  }
1305 
1314  public function getCategoryLinks() {
1315  return $this->mCategoryLinks;
1316  }
1317 
1323  public function getCategories() {
1324  return $this->mCategories;
1325  }
1326 
1336  public function setIndicators( array $indicators ) {
1337  $this->mIndicators = $indicators + $this->mIndicators;
1338  // Keep ordered by key
1339  ksort( $this->mIndicators );
1340  }
1341 
1350  public function getIndicators() {
1351  return $this->mIndicators;
1352  }
1353 
1362  public function addHelpLink( $to, $overrideBaseUrl = false ) {
1363  $this->addModuleStyles( 'mediawiki.helplink' );
1364  $text = $this->msg( 'helppage-top-gethelp' )->escaped();
1365 
1366  if ( $overrideBaseUrl ) {
1367  $helpUrl = $to;
1368  } else {
1369  $toUrlencoded = wfUrlencode( str_replace( ' ', '_', $to ) );
1370  $helpUrl = "//www.mediawiki.org/wiki/Special:MyLanguage/$toUrlencoded";
1371  }
1372 
1374  'a',
1375  [
1376  'href' => $helpUrl,
1377  'target' => '_blank',
1378  'class' => 'mw-helplink',
1379  ],
1380  $text
1381  );
1382 
1383  $this->setIndicators( [ 'mw-helplink' => $link ] );
1384  }
1385 
1394  public function disallowUserJs() {
1395  $this->reduceAllowedModules(
1398  );
1399 
1400  // Site-wide styles are controlled by a config setting, see bug 71621
1401  // for background on why. User styles are never allowed.
1402  if ( $this->getConfig()->get( 'AllowSiteCSSOnRestrictedPages' ) ) {
1404  } else {
1406  }
1407  $this->reduceAllowedModules(
1409  $styleOrigin
1410  );
1411  }
1412 
1419  public function getAllowedModules( $type ) {
1421  return min( array_values( $this->mAllowedModules ) );
1422  } else {
1423  return isset( $this->mAllowedModules[$type] )
1424  ? $this->mAllowedModules[$type]
1426  }
1427  }
1428 
1438  public function reduceAllowedModules( $type, $level ) {
1439  $this->mAllowedModules[$type] = min( $this->getAllowedModules( $type ), $level );
1440  }
1441 
1447  public function prependHTML( $text ) {
1448  $this->mBodytext = $text . $this->mBodytext;
1449  }
1450 
1456  public function addHTML( $text ) {
1457  $this->mBodytext .= $text;
1458  }
1459 
1469  public function addElement( $element, array $attribs = [], $contents = '' ) {
1470  $this->addHTML( Html::element( $element, $attribs, $contents ) );
1471  }
1472 
1476  public function clearHTML() {
1477  $this->mBodytext = '';
1478  }
1479 
1485  public function getHTML() {
1486  return $this->mBodytext;
1487  }
1488 
1496  public function parserOptions( $options = null ) {
1497  if ( $options !== null && !empty( $options->isBogus ) ) {
1498  // Someone is trying to set a bogus pre-$wgUser PO. Check if it has
1499  // been changed somehow, and keep it if so.
1500  $anonPO = ParserOptions::newFromAnon();
1501  $anonPO->setEditSection( false );
1502  if ( !$options->matches( $anonPO ) ) {
1503  wfLogWarning( __METHOD__ . ': Setting a changed bogus ParserOptions: ' . wfGetAllCallers( 5 ) );
1504  $options->isBogus = false;
1505  }
1506  }
1507 
1508  if ( !$this->mParserOptions ) {
1509  if ( !$this->getContext()->getUser()->isSafeToLoad() ) {
1510  // $wgUser isn't unstubbable yet, so don't try to get a
1511  // ParserOptions for it. And don't cache this ParserOptions
1512  // either.
1514  $po->setEditSection( false );
1515  $po->isBogus = true;
1516  if ( $options !== null ) {
1517  $this->mParserOptions = empty( $options->isBogus ) ? $options : null;
1518  }
1519  return $po;
1520  }
1521 
1522  $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
1523  $this->mParserOptions->setEditSection( false );
1524  }
1525 
1526  if ( $options !== null && !empty( $options->isBogus ) ) {
1527  // They're trying to restore the bogus pre-$wgUser PO. Do the right
1528  // thing.
1529  return wfSetVar( $this->mParserOptions, null, true );
1530  } else {
1531  return wfSetVar( $this->mParserOptions, $options );
1532  }
1533  }
1534 
1542  public function setRevisionId( $revid ) {
1543  $val = is_null( $revid ) ? null : intval( $revid );
1544  return wfSetVar( $this->mRevisionId, $val );
1545  }
1546 
1552  public function getRevisionId() {
1553  return $this->mRevisionId;
1554  }
1555 
1563  public function setRevisionTimestamp( $timestamp ) {
1564  return wfSetVar( $this->mRevisionTimestamp, $timestamp );
1565  }
1566 
1573  public function getRevisionTimestamp() {
1575  }
1576 
1583  public function setFileVersion( $file ) {
1584  $val = null;
1585  if ( $file instanceof File && $file->exists() ) {
1586  $val = [ 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ];
1587  }
1588  return wfSetVar( $this->mFileVersion, $val, true );
1589  }
1590 
1596  public function getFileVersion() {
1597  return $this->mFileVersion;
1598  }
1599 
1606  public function getTemplateIds() {
1607  return $this->mTemplateIds;
1608  }
1609 
1616  public function getFileSearchOptions() {
1617  return $this->mImageTimeKeys;
1618  }
1619 
1629  public function addWikiText( $text, $linestart = true, $interface = true ) {
1630  $title = $this->getTitle(); // Work around E_STRICT
1631  if ( !$title ) {
1632  throw new MWException( 'Title is null' );
1633  }
1634  $this->addWikiTextTitle( $text, $title, $linestart, /*tidy*/false, $interface );
1635  }
1636 
1644  public function addWikiTextWithTitle( $text, &$title, $linestart = true ) {
1645  $this->addWikiTextTitle( $text, $title, $linestart );
1646  }
1647 
1655  function addWikiTextTitleTidy( $text, &$title, $linestart = true ) {
1656  $this->addWikiTextTitle( $text, $title, $linestart, true );
1657  }
1658 
1665  public function addWikiTextTidy( $text, $linestart = true ) {
1666  $title = $this->getTitle();
1667  $this->addWikiTextTitleTidy( $text, $title, $linestart );
1668  }
1669 
1680  public function addWikiTextTitle( $text, Title $title, $linestart,
1681  $tidy = false, $interface = false
1682  ) {
1683  global $wgParser;
1684 
1685  $popts = $this->parserOptions();
1686  $oldTidy = $popts->setTidy( $tidy );
1687  $popts->setInterfaceMessage( (bool)$interface );
1688 
1689  $parserOutput = $wgParser->getFreshParser()->parse(
1690  $text, $title, $popts,
1691  $linestart, true, $this->mRevisionId
1692  );
1693 
1694  $popts->setTidy( $oldTidy );
1695 
1696  $this->addParserOutput( $parserOutput );
1697 
1698  }
1699 
1707  wfDeprecated( __METHOD__, '1.24' );
1709  }
1710 
1720  $this->mLanguageLinks += $parserOutput->getLanguageLinks();
1721  $this->addCategoryLinks( $parserOutput->getCategories() );
1722  $this->setIndicators( $parserOutput->getIndicators() );
1723  $this->mNewSectionLink = $parserOutput->getNewSection();
1724  $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
1725 
1726  if ( !$parserOutput->isCacheable() ) {
1727  $this->enableClientCache( false );
1728  }
1729  $this->mNoGallery = $parserOutput->getNoGallery();
1730  $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
1731  $this->addModules( $parserOutput->getModules() );
1732  $this->addModuleScripts( $parserOutput->getModuleScripts() );
1733  $this->addModuleStyles( $parserOutput->getModuleStyles() );
1734  $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1735  $this->mPreventClickjacking = $this->mPreventClickjacking
1736  || $parserOutput->preventClickjacking();
1737 
1738  // Template versioning...
1739  foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
1740  if ( isset( $this->mTemplateIds[$ns] ) ) {
1741  $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
1742  } else {
1743  $this->mTemplateIds[$ns] = $dbks;
1744  }
1745  }
1746  // File versioning...
1747  foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) {
1748  $this->mImageTimeKeys[$dbk] = $data;
1749  }
1750 
1751  // Hooks registered in the object
1752  $parserOutputHooks = $this->getConfig()->get( 'ParserOutputHooks' );
1753  foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
1754  list( $hookName, $data ) = $hookInfo;
1755  if ( isset( $parserOutputHooks[$hookName] ) ) {
1756  call_user_func( $parserOutputHooks[$hookName], $this, $parserOutput, $data );
1757  }
1758  }
1759 
1760  // Enable OOUI if requested via ParserOutput
1761  if ( $parserOutput->getEnableOOUI() ) {
1762  $this->enableOOUI();
1763  }
1764 
1765  // Include profiling data
1766  $this->limitReportData = $parserOutput->getLimitReportData();
1767 
1768  // Link flags are ignored for now, but may in the future be
1769  // used to mark individual language links.
1770  $linkFlags = [];
1771  Hooks::run( 'LanguageLinks', [ $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ] );
1772  Hooks::run( 'OutputPageParserOutput', [ &$this, $parserOutput ] );
1773  }
1774 
1784 
1785  $this->addModules( $parserOutput->getModules() );
1786  $this->addModuleScripts( $parserOutput->getModuleScripts() );
1787  $this->addModuleStyles( $parserOutput->getModuleStyles() );
1788 
1789  $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1790  }
1791 
1798  public function addParserOutputText( $parserOutput ) {
1799  $text = $parserOutput->getText();
1800  Hooks::run( 'OutputPageBeforeHTML', [ &$this, &$text ] );
1801  $this->addHTML( $text );
1802  }
1803 
1811  $parserOutput->setTOCEnabled( $this->mEnableTOC );
1812 
1813  // Touch section edit links only if not previously disabled
1814  if ( $parserOutput->getEditSectionTokens() ) {
1815  $parserOutput->setEditSectionTokens( $this->mEnableSectionEditLinks );
1816  }
1817 
1819  }
1820 
1826  public function addTemplate( &$template ) {
1827  $this->addHTML( $template->getHTML() );
1828  }
1829 
1842  public function parse( $text, $linestart = true, $interface = false, $language = null ) {
1843  global $wgParser;
1844 
1845  if ( is_null( $this->getTitle() ) ) {
1846  throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
1847  }
1848 
1849  $popts = $this->parserOptions();
1850  if ( $interface ) {
1851  $popts->setInterfaceMessage( true );
1852  }
1853  if ( $language !== null ) {
1854  $oldLang = $popts->setTargetLanguage( $language );
1855  }
1856 
1857  $parserOutput = $wgParser->getFreshParser()->parse(
1858  $text, $this->getTitle(), $popts,
1859  $linestart, true, $this->mRevisionId
1860  );
1861 
1862  if ( $interface ) {
1863  $popts->setInterfaceMessage( false );
1864  }
1865  if ( $language !== null ) {
1866  $popts->setTargetLanguage( $oldLang );
1867  }
1868 
1869  return $parserOutput->getText();
1870  }
1871 
1882  public function parseInline( $text, $linestart = true, $interface = false ) {
1883  $parsed = $this->parse( $text, $linestart, $interface );
1884  return Parser::stripOuterParagraph( $parsed );
1885  }
1886 
1891  public function setSquidMaxage( $maxage ) {
1892  $this->setCdnMaxage( $maxage );
1893  }
1894 
1900  public function setCdnMaxage( $maxage ) {
1901  $this->mCdnMaxage = min( $maxage, $this->mCdnMaxageLimit );
1902  }
1903 
1910  public function lowerCdnMaxage( $maxage ) {
1911  $this->mCdnMaxageLimit = min( $maxage, $this->mCdnMaxageLimit );
1912  $this->setCdnMaxage( $this->mCdnMaxage );
1913  }
1914 
1922  public function enableClientCache( $state ) {
1923  return wfSetVar( $this->mEnableClientCache, $state );
1924  }
1925 
1931  function getCacheVaryCookies() {
1932  static $cookies;
1933  if ( $cookies === null ) {
1934  $config = $this->getConfig();
1935  $cookies = array_merge(
1936  SessionManager::singleton()->getVaryCookies(),
1937  [
1938  'forceHTTPS',
1939  ],
1940  $config->get( 'CacheVaryCookies' )
1941  );
1942  Hooks::run( 'GetCacheVaryCookies', [ $this, &$cookies ] );
1943  }
1944  return $cookies;
1945  }
1946 
1954  $request = $this->getRequest();
1955  foreach ( $this->getCacheVaryCookies() as $cookieName ) {
1956  if ( $request->getCookie( $cookieName, '', '' ) !== '' ) {
1957  wfDebug( __METHOD__ . ": found $cookieName\n" );
1958  return true;
1959  }
1960  }
1961  wfDebug( __METHOD__ . ": no cache-varying cookies found\n" );
1962  return false;
1963  }
1964 
1973  public function addVaryHeader( $header, array $option = null ) {
1974  if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
1975  $this->mVaryHeader[$header] = [];
1976  }
1977  if ( !is_array( $option ) ) {
1978  $option = [];
1979  }
1980  $this->mVaryHeader[$header] = array_unique( array_merge( $this->mVaryHeader[$header], $option ) );
1981  }
1982 
1989  public function getVaryHeader() {
1990  // If we vary on cookies, let's make sure it's always included here too.
1991  if ( $this->getCacheVaryCookies() ) {
1992  $this->addVaryHeader( 'Cookie' );
1993  }
1994 
1995  foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
1996  $this->addVaryHeader( $header, $options );
1997  }
1998  return 'Vary: ' . implode( ', ', array_keys( $this->mVaryHeader ) );
1999  }
2000 
2006  public function getKeyHeader() {
2007  $cvCookies = $this->getCacheVaryCookies();
2008 
2009  $cookiesOption = [];
2010  foreach ( $cvCookies as $cookieName ) {
2011  $cookiesOption[] = 'param=' . $cookieName;
2012  }
2013  $this->addVaryHeader( 'Cookie', $cookiesOption );
2014 
2015  foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2016  $this->addVaryHeader( $header, $options );
2017  }
2018 
2019  $headers = [];
2020  foreach ( $this->mVaryHeader as $header => $option ) {
2021  $newheader = $header;
2022  if ( is_array( $option ) && count( $option ) > 0 ) {
2023  $newheader .= ';' . implode( ';', $option );
2024  }
2025  $headers[] = $newheader;
2026  }
2027  $key = 'Key: ' . implode( ',', $headers );
2028 
2029  return $key;
2030  }
2031 
2040  function addAcceptLanguage() {
2041  $title = $this->getTitle();
2042  if ( !$title instanceof Title ) {
2043  return;
2044  }
2045 
2046  $lang = $title->getPageLanguage();
2047  if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
2048  $variants = $lang->getVariants();
2049  $aloption = [];
2050  foreach ( $variants as $variant ) {
2051  if ( $variant === $lang->getCode() ) {
2052  continue;
2053  } else {
2054  $aloption[] = 'substr=' . $variant;
2055 
2056  // IE and some other browsers use BCP 47 standards in
2057  // their Accept-Language header, like "zh-CN" or "zh-Hant".
2058  // We should handle these too.
2059  $variantBCP47 = wfBCP47( $variant );
2060  if ( $variantBCP47 !== $variant ) {
2061  $aloption[] = 'substr=' . $variantBCP47;
2062  }
2063  }
2064  }
2065  $this->addVaryHeader( 'Accept-Language', $aloption );
2066  }
2067  }
2068 
2079  public function preventClickjacking( $enable = true ) {
2080  $this->mPreventClickjacking = $enable;
2081  }
2082 
2088  public function allowClickjacking() {
2089  $this->mPreventClickjacking = false;
2090  }
2091 
2098  public function getPreventClickjacking() {
2100  }
2101 
2109  public function getFrameOptions() {
2110  $config = $this->getConfig();
2111  if ( $config->get( 'BreakFrames' ) ) {
2112  return 'DENY';
2113  } elseif ( $this->mPreventClickjacking && $config->get( 'EditPageFrameOptions' ) ) {
2114  return $config->get( 'EditPageFrameOptions' );
2115  }
2116  return false;
2117  }
2118 
2122  public function sendCacheControl() {
2123  $response = $this->getRequest()->response();
2124  $config = $this->getConfig();
2125 
2126  $this->addVaryHeader( 'Cookie' );
2127  $this->addAcceptLanguage();
2128 
2129  # don't serve compressed data to clients who can't handle it
2130  # maintain different caches for logged-in users and non-logged in ones
2131  $response->header( $this->getVaryHeader() );
2132 
2133  if ( $config->get( 'UseKeyHeader' ) ) {
2134  $response->header( $this->getKeyHeader() );
2135  }
2136 
2137  if ( $this->mEnableClientCache ) {
2138  if (
2139  $config->get( 'UseSquid' ) &&
2140  !$response->hasCookies() &&
2141  !SessionManager::getGlobalSession()->isPersistent() &&
2142  !$this->isPrintable() &&
2143  $this->mCdnMaxage != 0 &&
2144  !$this->haveCacheVaryCookies()
2145  ) {
2146  if ( $config->get( 'UseESI' ) ) {
2147  # We'll purge the proxy cache explicitly, but require end user agents
2148  # to revalidate against the proxy on each visit.
2149  # Surrogate-Control controls our CDN, Cache-Control downstream caches
2150  wfDebug( __METHOD__ . ": proxy caching with ESI; {$this->mLastModified} **", 'private' );
2151  # start with a shorter timeout for initial testing
2152  # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
2153  $response->header( 'Surrogate-Control: max-age=' . $config->get( 'SquidMaxage' )
2154  . '+' . $this->mCdnMaxage . ', content="ESI/1.0"' );
2155  $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
2156  } else {
2157  # We'll purge the proxy cache for anons explicitly, but require end user agents
2158  # to revalidate against the proxy on each visit.
2159  # IMPORTANT! The CDN needs to replace the Cache-Control header with
2160  # Cache-Control: s-maxage=0, must-revalidate, max-age=0
2161  wfDebug( __METHOD__ . ": local proxy caching; {$this->mLastModified} **", 'private' );
2162  # start with a shorter timeout for initial testing
2163  # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
2164  $response->header( 'Cache-Control: s-maxage=' . $this->mCdnMaxage
2165  . ', must-revalidate, max-age=0' );
2166  }
2167  } else {
2168  # We do want clients to cache if they can, but they *must* check for updates
2169  # on revisiting the page.
2170  wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **", 'private' );
2171  $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2172  $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
2173  }
2174  if ( $this->mLastModified ) {
2175  $response->header( "Last-Modified: {$this->mLastModified}" );
2176  }
2177  } else {
2178  wfDebug( __METHOD__ . ": no caching **", 'private' );
2179 
2180  # In general, the absence of a last modified header should be enough to prevent
2181  # the client from using its cache. We send a few other things just to make sure.
2182  $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2183  $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
2184  $response->header( 'Pragma: no-cache' );
2185  }
2186  }
2187 
2192  public function output() {
2193  if ( $this->mDoNothing ) {
2194  return;
2195  }
2196 
2197  $response = $this->getRequest()->response();
2198  $config = $this->getConfig();
2199 
2200  if ( $this->mRedirect != '' ) {
2201  # Standards require redirect URLs to be absolute
2202  $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT );
2203 
2204  $redirect = $this->mRedirect;
2206 
2207  if ( Hooks::run( "BeforePageRedirect", [ $this, &$redirect, &$code ] ) ) {
2208  if ( $code == '301' || $code == '303' ) {
2209  if ( !$config->get( 'DebugRedirects' ) ) {
2210  $response->statusHeader( $code );
2211  }
2212  $this->mLastModified = wfTimestamp( TS_RFC2822 );
2213  }
2214  if ( $config->get( 'VaryOnXFP' ) ) {
2215  $this->addVaryHeader( 'X-Forwarded-Proto' );
2216  }
2217  $this->sendCacheControl();
2218 
2219  $response->header( "Content-Type: text/html; charset=utf-8" );
2220  if ( $config->get( 'DebugRedirects' ) ) {
2221  $url = htmlspecialchars( $redirect );
2222  print "<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
2223  print "<p>Location: <a href=\"$url\">$url</a></p>\n";
2224  print "</body>\n</html>\n";
2225  } else {
2226  $response->header( 'Location: ' . $redirect );
2227  }
2228  }
2229 
2230  return;
2231  } elseif ( $this->mStatusCode ) {
2232  $response->statusHeader( $this->mStatusCode );
2233  }
2234 
2235  # Buffer output; final headers may depend on later processing
2236  ob_start();
2237 
2238  $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
2239  $response->header( 'Content-language: ' . $config->get( 'LanguageCode' ) );
2240 
2241  // Avoid Internet Explorer "compatibility view" in IE 8-10, so that
2242  // jQuery etc. can work correctly.
2243  $response->header( 'X-UA-Compatible: IE=Edge' );
2244 
2245  // Prevent framing, if requested
2246  $frameOptions = $this->getFrameOptions();
2247  if ( $frameOptions ) {
2248  $response->header( "X-Frame-Options: $frameOptions" );
2249  }
2250 
2251  if ( $this->mArticleBodyOnly ) {
2252  echo $this->mBodytext;
2253  } else {
2254  $sk = $this->getSkin();
2255  // add skin specific modules
2256  $modules = $sk->getDefaultModules();
2257 
2258  // Enforce various default modules for all skins
2259  $coreModules = [
2260  // Keep this list as small as possible
2261  'site',
2262  'mediawiki.page.startup',
2263  'mediawiki.user',
2264  ];
2265 
2266  // Support for high-density display images if enabled
2267  if ( $config->get( 'ResponsiveImages' ) ) {
2268  $coreModules[] = 'mediawiki.hidpi';
2269  }
2270 
2271  $this->addModules( $coreModules );
2272  foreach ( $modules as $group ) {
2273  $this->addModules( $group );
2274  }
2275  MWDebug::addModules( $this );
2276 
2277  // Hook that allows last minute changes to the output page, e.g.
2278  // adding of CSS or Javascript by extensions.
2279  Hooks::run( 'BeforePageDisplay', [ &$this, &$sk ] );
2280 
2281  try {
2282  $sk->outputPage();
2283  } catch ( Exception $e ) {
2284  ob_end_clean(); // bug T129657
2285  throw $e;
2286  }
2287  }
2288 
2289  try {
2290  // This hook allows last minute changes to final overall output by modifying output buffer
2291  Hooks::run( 'AfterFinalPageOutput', [ $this ] );
2292  } catch ( Exception $e ) {
2293  ob_end_clean(); // bug T129657
2294  throw $e;
2295  }
2296 
2297  $this->sendCacheControl();
2298 
2299  ob_end_flush();
2300 
2301  }
2302 
2313  public function prepareErrorPage( $pageTitle, $htmlTitle = false ) {
2314  $this->setPageTitle( $pageTitle );
2315  if ( $htmlTitle !== false ) {
2316  $this->setHTMLTitle( $htmlTitle );
2317  }
2318  $this->setRobotPolicy( 'noindex,nofollow' );
2319  $this->setArticleRelated( false );
2320  $this->enableClientCache( false );
2321  $this->mRedirect = '';
2322  $this->clearSubtitle();
2323  $this->clearHTML();
2324  }
2325 
2338  public function showErrorPage( $title, $msg, $params = [] ) {
2339  if ( !$title instanceof Message ) {
2340  $title = $this->msg( $title );
2341  }
2342 
2343  $this->prepareErrorPage( $title );
2344 
2345  if ( $msg instanceof Message ) {
2346  if ( $params !== [] ) {
2347  trigger_error( 'Argument ignored: $params. The message parameters argument '
2348  . 'is discarded when the $msg argument is a Message object instead of '
2349  . 'a string.', E_USER_NOTICE );
2350  }
2351  $this->addHTML( $msg->parseAsBlock() );
2352  } else {
2353  $this->addWikiMsgArray( $msg, $params );
2354  }
2355 
2356  $this->returnToMain();
2357  }
2358 
2365  public function showPermissionsErrorPage( array $errors, $action = null ) {
2366  // For some action (read, edit, create and upload), display a "login to do this action"
2367  // error if all of the following conditions are met:
2368  // 1. the user is not logged in
2369  // 2. the only error is insufficient permissions (i.e. no block or something else)
2370  // 3. the error can be avoided simply by logging in
2371  if ( in_array( $action, [ 'read', 'edit', 'createpage', 'createtalk', 'upload' ] )
2372  && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
2373  && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
2374  && ( User::groupHasPermission( 'user', $action )
2375  || User::groupHasPermission( 'autoconfirmed', $action ) )
2376  ) {
2377  $displayReturnto = null;
2378 
2379  # Due to bug 32276, if a user does not have read permissions,
2380  # $this->getTitle() will just give Special:Badtitle, which is
2381  # not especially useful as a returnto parameter. Use the title
2382  # from the request instead, if there was one.
2383  $request = $this->getRequest();
2384  $returnto = Title::newFromText( $request->getVal( 'title', '' ) );
2385  if ( $action == 'edit' ) {
2386  $msg = 'whitelistedittext';
2387  $displayReturnto = $returnto;
2388  } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
2389  $msg = 'nocreatetext';
2390  } elseif ( $action == 'upload' ) {
2391  $msg = 'uploadnologintext';
2392  } else { # Read
2393  $msg = 'loginreqpagetext';
2394  $displayReturnto = Title::newMainPage();
2395  }
2396 
2397  $query = [];
2398 
2399  if ( $returnto ) {
2400  $query['returnto'] = $returnto->getPrefixedText();
2401 
2402  if ( !$request->wasPosted() ) {
2403  $returntoquery = $request->getValues();
2404  unset( $returntoquery['title'] );
2405  unset( $returntoquery['returnto'] );
2406  unset( $returntoquery['returntoquery'] );
2407  $query['returntoquery'] = wfArrayToCgi( $returntoquery );
2408  }
2409  }
2410  $loginLink = Linker::linkKnown(
2411  SpecialPage::getTitleFor( 'Userlogin' ),
2412  $this->msg( 'loginreqlink' )->escaped(),
2413  [],
2414  $query
2415  );
2416 
2417  $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) );
2418  $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->parse() );
2419 
2420  # Don't return to a page the user can't read otherwise
2421  # we'll end up in a pointless loop
2422  if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) {
2423  $this->returnToMain( null, $displayReturnto );
2424  }
2425  } else {
2426  $this->prepareErrorPage( $this->msg( 'permissionserrors' ) );
2427  $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) );
2428  }
2429  }
2430 
2437  public function versionRequired( $version ) {
2438  $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) );
2439 
2440  $this->addWikiMsg( 'versionrequiredtext', $version );
2441  $this->returnToMain();
2442  }
2443 
2451  public function formatPermissionsErrorMessage( array $errors, $action = null ) {
2452  if ( $action == null ) {
2453  $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
2454  } else {
2455  $action_desc = $this->msg( "action-$action" )->plain();
2456  $text = $this->msg(
2457  'permissionserrorstext-withaction',
2458  count( $errors ),
2459  $action_desc
2460  )->plain() . "\n\n";
2461  }
2462 
2463  if ( count( $errors ) > 1 ) {
2464  $text .= '<ul class="permissions-errors">' . "\n";
2465 
2466  foreach ( $errors as $error ) {
2467  $text .= '<li>';
2468  $text .= call_user_func_array( [ $this, 'msg' ], $error )->plain();
2469  $text .= "</li>\n";
2470  }
2471  $text .= '</ul>';
2472  } else {
2473  $text .= "<div class=\"permissions-errors\">\n" .
2474  call_user_func_array( [ $this, 'msg' ], reset( $errors ) )->plain() .
2475  "\n</div>";
2476  }
2477 
2478  return $text;
2479  }
2480 
2492  public function readOnlyPage() {
2493  if ( func_num_args() > 0 ) {
2494  throw new MWException( __METHOD__ . ' no longer accepts arguments since 1.25.' );
2495  }
2496 
2497  throw new ReadOnlyError;
2498  }
2499 
2506  public function rateLimited() {
2507  wfDeprecated( __METHOD__, '1.25' );
2508  throw new ThrottledError;
2509  }
2510 
2520  public function showLagWarning( $lag ) {
2521  $config = $this->getConfig();
2522  if ( $lag >= $config->get( 'SlaveLagWarning' ) ) {
2523  $message = $lag < $config->get( 'SlaveLagCritical' )
2524  ? 'lag-warn-normal'
2525  : 'lag-warn-high';
2526  $wrap = Html::rawElement( 'div', [ 'class' => "mw-{$message}" ], "\n$1\n" );
2527  $this->wrapWikiMsg( "$wrap\n", [ $message, $this->getLanguage()->formatNum( $lag ) ] );
2528  }
2529  }
2530 
2531  public function showFatalError( $message ) {
2532  $this->prepareErrorPage( $this->msg( 'internalerror' ) );
2533 
2534  $this->addHTML( $message );
2535  }
2536 
2537  public function showUnexpectedValueError( $name, $val ) {
2538  $this->showFatalError( $this->msg( 'unexpected', $name, $val )->text() );
2539  }
2540 
2541  public function showFileCopyError( $old, $new ) {
2542  $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->text() );
2543  }
2544 
2545  public function showFileRenameError( $old, $new ) {
2546  $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->text() );
2547  }
2548 
2549  public function showFileDeleteError( $name ) {
2550  $this->showFatalError( $this->msg( 'filedeleteerror', $name )->text() );
2551  }
2552 
2553  public function showFileNotFoundError( $name ) {
2554  $this->showFatalError( $this->msg( 'filenotfound', $name )->text() );
2555  }
2556 
2565  public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) {
2566  $link = $this->msg( 'returnto' )->rawParams(
2567  Linker::link( $title, $text, [], $query, $options ) )->escaped();
2568  $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
2569  }
2570 
2579  public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
2580  if ( $returnto == null ) {
2581  $returnto = $this->getRequest()->getText( 'returnto' );
2582  }
2583 
2584  if ( $returntoquery == null ) {
2585  $returntoquery = $this->getRequest()->getText( 'returntoquery' );
2586  }
2587 
2588  if ( $returnto === '' ) {
2589  $returnto = Title::newMainPage();
2590  }
2591 
2592  if ( is_object( $returnto ) ) {
2593  $titleObj = $returnto;
2594  } else {
2595  $titleObj = Title::newFromText( $returnto );
2596  }
2597  if ( !is_object( $titleObj ) ) {
2598  $titleObj = Title::newMainPage();
2599  }
2600 
2601  $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) );
2602  }
2603 
2609  public function headElement( Skin $sk, $includeStyle = true ) {
2611 
2612  $userdir = $this->getLanguage()->getDir();
2613  $sitedir = $wgContLang->getDir();
2614 
2615  $pieces = [];
2616  $pieces[] = Html::htmlHeader( $sk->getHtmlElementAttributes() );
2617 
2618  if ( $this->getHTMLTitle() == '' ) {
2619  $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() );
2620  }
2621 
2622  $openHead = Html::openElement( 'head' );
2623  if ( $openHead ) {
2624  $pieces[] = $openHead;
2625  }
2626 
2627  if ( !Html::isXmlMimeType( $this->getConfig()->get( 'MimeType' ) ) ) {
2628  // Add <meta charset="UTF-8">
2629  // This should be before <title> since it defines the charset used by
2630  // text including the text inside <title>.
2631  // The spec recommends defining XHTML5's charset using the XML declaration
2632  // instead of meta.
2633  // Our XML declaration is output by Html::htmlHeader.
2634  // http://www.whatwg.org/html/semantics.html#attr-meta-http-equiv-content-type
2635  // http://www.whatwg.org/html/semantics.html#charset
2636  $pieces[] = Html::element( 'meta', [ 'charset' => 'UTF-8' ] );
2637  }
2638 
2639  $pieces[] = Html::element( 'title', null, $this->getHTMLTitle() );
2640  $pieces[] = $this->getInlineHeadScripts();
2641  $pieces[] = $this->buildCssLinks();
2642  $pieces[] = $this->getExternalHeadScripts();
2643 
2644  foreach ( $this->getHeadLinksArray() as $item ) {
2645  $pieces[] = $item;
2646  }
2647 
2648  foreach ( $this->mHeadItems as $item ) {
2649  $pieces[] = $item;
2650  }
2651 
2652  $closeHead = Html::closeElement( 'head' );
2653  if ( $closeHead ) {
2654  $pieces[] = $closeHead;
2655  }
2656 
2657  $bodyClasses = [];
2658  $bodyClasses[] = 'mediawiki';
2659 
2660  # Classes for LTR/RTL directionality support
2661  $bodyClasses[] = $userdir;
2662  $bodyClasses[] = "sitedir-$sitedir";
2663 
2664  if ( $this->getLanguage()->capitalizeAllNouns() ) {
2665  # A <body> class is probably not the best way to do this . . .
2666  $bodyClasses[] = 'capitalize-all-nouns';
2667  }
2668 
2669  // Parser feature migration class
2670  // The idea is that this will eventually be removed, after the wikitext
2671  // which requires it is cleaned up.
2672  $bodyClasses[] = 'mw-hide-empty-elt';
2673 
2674  $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
2675  $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
2676  $bodyClasses[] =
2677  'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
2678 
2679  $bodyAttrs = [];
2680  // While the implode() is not strictly needed, it's used for backwards compatibility
2681  // (this used to be built as a string and hooks likely still expect that).
2682  $bodyAttrs['class'] = implode( ' ', $bodyClasses );
2683 
2684  // Allow skins and extensions to add body attributes they need
2685  $sk->addToBodyAttributes( $this, $bodyAttrs );
2686  Hooks::run( 'OutputPageBodyAttributes', [ $this, $sk, &$bodyAttrs ] );
2687 
2688  $pieces[] = Html::openElement( 'body', $bodyAttrs );
2689 
2690  return WrappedStringList::join( "\n", $pieces );
2691  }
2692 
2698  public function getResourceLoader() {
2699  if ( is_null( $this->mResourceLoader ) ) {
2700  $this->mResourceLoader = new ResourceLoader(
2701  $this->getConfig(),
2702  LoggerFactory::getInstance( 'resourceloader' )
2703  );
2704  }
2705  return $this->mResourceLoader;
2706  }
2707 
2718  public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) {
2719  $modules = (array)$modules;
2720 
2721  $links = [
2722  // List of html strings
2723  'html' => [],
2724  // Associative array of module names and their states
2725  'states' => [],
2726  ];
2727 
2728  if ( !count( $modules ) ) {
2729  return $links;
2730  }
2731 
2732  if ( count( $modules ) > 1 ) {
2733  // Remove duplicate module requests
2734  $modules = array_unique( $modules );
2735  // Sort module names so requests are more uniform
2736  sort( $modules );
2737 
2738  if ( ResourceLoader::inDebugMode() ) {
2739  // Recursively call us for every item
2740  foreach ( $modules as $name ) {
2741  $link = $this->makeResourceLoaderLink( $name, $only, $extraQuery );
2742  $links['html'] = array_merge( $links['html'], $link['html'] );
2743  $links['states'] += $link['states'];
2744  }
2745  return $links;
2746  }
2747  }
2748 
2749  if ( !is_null( $this->mTarget ) ) {
2750  $extraQuery['target'] = $this->mTarget;
2751  }
2752 
2753  // Create keyed-by-source and then keyed-by-group list of module objects from modules list
2754  $sortedModules = [];
2756  foreach ( $modules as $name ) {
2757  $module = $resourceLoader->getModule( $name );
2758  # Check that we're allowed to include this module on this page
2759  if ( !$module
2760  || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_SCRIPTS )
2762  || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_STYLES )
2763  && $only == ResourceLoaderModule::TYPE_STYLES )
2764  || ( $module->getOrigin() > $this->getAllowedModules( ResourceLoaderModule::TYPE_COMBINED )
2766  || ( $this->mTarget && !in_array( $this->mTarget, $module->getTargets() ) )
2767  ) {
2768  continue;
2769  }
2770 
2771  if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
2772  if ( $module->getType() !== ResourceLoaderModule::LOAD_STYLES ) {
2773  $logger = $resourceLoader->getLogger();
2774  $logger->debug( 'Unexpected general module "{module}" in styles queue.', [
2775  'module' => $name,
2776  ] );
2777  } else {
2778  $links['states'][$name] = 'ready';
2779  }
2780  }
2781 
2782  $sortedModules[$module->getSource()][$module->getGroup()][$name] = $module;
2783  }
2784 
2785  foreach ( $sortedModules as $source => $groups ) {
2786  foreach ( $groups as $group => $grpModules ) {
2787  // Special handling for user-specific groups
2788  $user = null;
2789  if ( ( $group === 'user' || $group === 'private' ) && $this->getUser()->isLoggedIn() ) {
2790  $user = $this->getUser()->getName();
2791  }
2792 
2793  // Create a fake request based on the one we are about to make so modules return
2794  // correct timestamp and emptiness data
2796  [], // modules; not determined yet
2797  $this->getLanguage()->getCode(),
2798  $this->getSkin()->getSkinName(),
2799  $user,
2800  null, // version; not determined yet
2802  $only === ResourceLoaderModule::TYPE_COMBINED ? null : $only,
2803  $this->isPrintable(),
2804  $this->getRequest()->getBool( 'handheld' ),
2805  $extraQuery
2806  );
2808 
2809  // Extract modules that know they're empty and see if we have one or more
2810  // raw modules
2811  $isRaw = false;
2812  foreach ( $grpModules as $key => $module ) {
2813  // Inline empty modules: since they're empty, just mark them as 'ready' (bug 46857)
2814  // If we're only getting the styles, we don't need to do anything for empty modules.
2815  if ( $module->isKnownEmpty( $context ) ) {
2816  unset( $grpModules[$key] );
2817  if ( $only !== ResourceLoaderModule::TYPE_STYLES ) {
2818  $links['states'][$key] = 'ready';
2819  }
2820  }
2821 
2822  $isRaw |= $module->isRaw();
2823  }
2824 
2825  // If there are no non-empty modules, skip this group
2826  if ( count( $grpModules ) === 0 ) {
2827  continue;
2828  }
2829 
2830  // Inline private modules. These can't be loaded through load.php for security
2831  // reasons, see bug 34907. Note that these modules should be loaded from
2832  // getExternalHeadScripts() before the first loader call. Otherwise other modules can't
2833  // properly use them as dependencies (bug 30914)
2834  if ( $group === 'private' ) {
2835  if ( $only == ResourceLoaderModule::TYPE_STYLES ) {
2836  $links['html'][] = Html::inlineStyle(
2837  $resourceLoader->makeModuleResponse( $context, $grpModules )
2838  );
2839  } else {
2840  $links['html'][] = ResourceLoader::makeInlineScript(
2841  $resourceLoader->makeModuleResponse( $context, $grpModules )
2842  );
2843  }
2844  continue;
2845  }
2846 
2847  // Special handling for the user group; because users might change their stuff
2848  // on-wiki like user pages, or user preferences; we need to find the highest
2849  // timestamp of these user-changeable modules so we can ensure cache misses on change
2850  // This should NOT be done for the site group (bug 27564) because anons get that too
2851  // and we shouldn't be putting timestamps in CDN-cached HTML
2852  $version = null;
2853  if ( $group === 'user' ) {
2854  $query['version'] = $resourceLoader->getCombinedVersion( $context, array_keys( $grpModules ) );
2855  }
2856 
2857  $query['modules'] = ResourceLoader::makePackedModulesString( array_keys( $grpModules ) );
2858  $moduleContext = new ResourceLoaderContext( $resourceLoader, new FauxRequest( $query ) );
2859  $url = $resourceLoader->createLoaderURL( $source, $moduleContext, $extraQuery );
2860 
2861  // Automatically select style/script elements
2862  if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
2863  $link = Html::linkedStyle( $url );
2864  } else {
2865  if ( $context->getRaw() || $isRaw ) {
2866  // Startup module can't load itself, needs to use <script> instead of mw.loader.load
2867  $link = Html::element( 'script', [
2868  // In SpecialJavaScriptTest, QUnit must load synchronous
2869  'async' => !isset( $extraQuery['sync'] ),
2870  'src' => $url
2871  ] );
2872  } else {
2874  Xml::encodeJsCall( 'mw.loader.load', [ $url ] )
2875  );
2876  }
2877 
2878  // For modules requested directly in the html via <script> or mw.loader.load
2879  // tell mw.loader they are being loading to prevent duplicate requests.
2880  foreach ( $grpModules as $key => $module ) {
2881  // Don't output state=loading for the startup module.
2882  if ( $key !== 'startup' ) {
2883  $links['states'][$key] = 'loading';
2884  }
2885  }
2886  }
2887 
2888  if ( $group == 'noscript' ) {
2889  $links['html'][] = Html::rawElement( 'noscript', [], $link );
2890  } else {
2891  $links['html'][] = $link;
2892  }
2893  }
2894  }
2895 
2896  return $links;
2897  }
2898 
2904  protected static function getHtmlFromLoaderLinks( array $links ) {
2905  $html = [];
2906  $states = [];
2907  foreach ( $links as $link ) {
2908  if ( !is_array( $link ) ) {
2909  $html[] = $link;
2910  } else {
2911  $html = array_merge( $html, $link['html'] );
2912  $states += $link['states'];
2913  }
2914  }
2915  // Filter out empty values
2916  $html = array_filter( $html, 'strlen' );
2917 
2918  if ( $states ) {
2919  array_unshift( $html, ResourceLoader::makeInlineScript(
2921  ) );
2922  }
2923 
2924  return WrappedString::join( "\n", $html );
2925  }
2926 
2933  function getHeadScripts() {
2934  return $this->getInlineHeadScripts() . $this->getExternalHeadScripts();
2935  }
2936 
2944  // Startup - this provides the client with the module
2945  // manifest and loads jquery and mediawiki base modules
2946  $links = [];
2947  $links[] = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS );
2948  return self::getHtmlFromLoaderLinks( $links );
2949  }
2950 
2957  $links = [];
2958 
2959  // Client profile classes for <html>. Allows for easy hiding/showing of UI components.
2960  // Must be done synchronously on every page to avoid flashes of wrong content.
2961  // Note: This class distinguishes MediaWiki-supported JavaScript from the rest.
2962  // The "rest" includes browsers that support JavaScript but not supported by our runtime.
2963  // For the performance benefit of the majority, this is added unconditionally here and is
2964  // then fixed up by the startup module for unsupported browsers.
2965  $links[] = Html::inlineScript(
2966  'document.documentElement.className = document.documentElement.className'
2967  . '.replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" );'
2968  );
2969 
2970  // Load config before anything else
2973  );
2974 
2975  // Load embeddable private modules before any loader links
2976  // This needs to be TYPE_COMBINED so these modules are properly wrapped
2977  // in mw.loader.implement() calls and deferred until mw.user is available
2978  $embedScripts = [ 'user.options' ];
2979  $links[] = $this->makeResourceLoaderLink(
2980  $embedScripts,
2982  );
2983  // Separate user.tokens as otherwise caching will be allowed (T84960)
2984  $links[] = $this->makeResourceLoaderLink(
2985  'user.tokens',
2987  );
2988 
2989  // Modules requests - let the client calculate dependencies and batch requests as it likes
2990  // Only load modules that have marked themselves for loading at the top
2991  $modules = $this->getModules( true, 'top' );
2992  if ( $modules ) {
2994  Xml::encodeJsCall( 'mw.loader.load', [ $modules ] )
2995  );
2996  }
2997 
2998  // "Scripts only" modules marked for top inclusion
2999  $links[] = $this->makeResourceLoaderLink(
3000  $this->getModuleScripts( true, 'top' ),
3002  );
3003 
3004  return self::getHtmlFromLoaderLinks( $links );
3005  }
3006 
3016  function getScriptsForBottomQueue( $unused = null ) {
3017  // Scripts "only" requests marked for bottom inclusion
3018  // If we're in the <head>, use load() calls rather than <script src="..."> tags
3019  $links = [];
3020 
3021  $links[] = $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ),
3023  );
3024 
3025  // Modules requests - let the client calculate dependencies and batch requests as it likes
3026  // Only load modules that have marked themselves for loading at the bottom
3027  $modules = $this->getModules( true, 'bottom' );
3028  if ( $modules ) {
3030  Xml::encodeJsCall( 'mw.loader.load', [ $modules ] )
3031  );
3032  }
3033 
3034  // Legacy Scripts
3035  $links[] = $this->mScripts;
3036 
3037  // Add user JS if enabled
3038  // This must use TYPE_COMBINED instead of only=scripts so that its request is handled by
3039  // mw.loader.implement() which ensures that execution is scheduled after the "site" module.
3040  if ( $this->getConfig()->get( 'AllowUserJs' )
3041  && $this->getUser()->isLoggedIn()
3042  && $this->getTitle()
3043  && $this->getTitle()->isJsSubpage()
3044  && $this->userCanPreview()
3045  ) {
3046  // We're on a preview of a JS subpage. Exclude this page from the user module (T28283)
3047  // and include the draft contents as a raw script instead.
3049  [ 'excludepage' => $this->getTitle()->getPrefixedDBkey() ]
3050  );
3051  // Load the previewed JS
3053  Xml::encodeJsCall( 'mw.loader.using', [
3054  [ 'user', 'site' ],
3055  new XmlJsCode(
3056  'function () {'
3057  . Xml::encodeJsCall( '$.globalEval', [
3058  $this->getRequest()->getText( 'wpTextbox1' )
3059  ] )
3060  . '}'
3061  )
3062  ] )
3063  );
3064 
3065  // FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded
3066  // asynchronously and may arrive *after* the inline script here. So the previewed code
3067  // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js.
3068  // Similarly, when previewing ./common.js and the user module does arrive first,
3069  // it will arrive without common.js and the inline script runs after.
3070  // Thus running common after the excluded subpage.
3071  } else {
3072  // Include the user module normally, i.e., raw to avoid it being wrapped in a closure.
3073  $links[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_COMBINED );
3074  }
3075 
3076  return self::getHtmlFromLoaderLinks( $links );
3077  }
3078 
3083  function getBottomScripts() {
3084  return $this->getScriptsForBottomQueue() .
3087  [ 'wgPageParseReport' => $this->limitReportData ],
3088  true
3089  )
3090  );
3091  }
3092 
3099  public function getJsConfigVars() {
3100  return $this->mJsConfigVars;
3101  }
3102 
3109  public function addJsConfigVars( $keys, $value = null ) {
3110  if ( is_array( $keys ) ) {
3111  foreach ( $keys as $key => $value ) {
3112  $this->mJsConfigVars[$key] = $value;
3113  }
3114  return;
3115  }
3116 
3117  $this->mJsConfigVars[$keys] = $value;
3118  }
3119 
3129  public function getJSVars() {
3131 
3132  $curRevisionId = 0;
3133  $articleId = 0;
3134  $canonicalSpecialPageName = false; # bug 21115
3135 
3136  $title = $this->getTitle();
3137  $ns = $title->getNamespace();
3138  $canonicalNamespace = MWNamespace::exists( $ns )
3140  : $title->getNsText();
3141 
3142  $sk = $this->getSkin();
3143  // Get the relevant title so that AJAX features can use the correct page name
3144  // when making API requests from certain special pages (bug 34972).
3145  $relevantTitle = $sk->getRelevantTitle();
3146  $relevantUser = $sk->getRelevantUser();
3147 
3148  if ( $ns == NS_SPECIAL ) {
3149  list( $canonicalSpecialPageName, /*...*/ ) =
3150  SpecialPageFactory::resolveAlias( $title->getDBkey() );
3151  } elseif ( $this->canUseWikiPage() ) {
3152  $wikiPage = $this->getWikiPage();
3153  $curRevisionId = $wikiPage->getLatest();
3154  $articleId = $wikiPage->getId();
3155  }
3156 
3157  $lang = $title->getPageViewLanguage();
3158 
3159  // Pre-process information
3160  $separatorTransTable = $lang->separatorTransformTable();
3161  $separatorTransTable = $separatorTransTable ? $separatorTransTable : [];
3162  $compactSeparatorTransTable = [
3163  implode( "\t", array_keys( $separatorTransTable ) ),
3164  implode( "\t", $separatorTransTable ),
3165  ];
3166  $digitTransTable = $lang->digitTransformTable();
3167  $digitTransTable = $digitTransTable ? $digitTransTable : [];
3168  $compactDigitTransTable = [
3169  implode( "\t", array_keys( $digitTransTable ) ),
3170  implode( "\t", $digitTransTable ),
3171  ];
3172 
3173  $user = $this->getUser();
3174 
3175  $vars = [
3176  'wgCanonicalNamespace' => $canonicalNamespace,
3177  'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
3178  'wgNamespaceNumber' => $title->getNamespace(),
3179  'wgPageName' => $title->getPrefixedDBkey(),
3180  'wgTitle' => $title->getText(),
3181  'wgCurRevisionId' => $curRevisionId,
3182  'wgRevisionId' => (int)$this->getRevisionId(),
3183  'wgArticleId' => $articleId,
3184  'wgIsArticle' => $this->isArticle(),
3185  'wgIsRedirect' => $title->isRedirect(),
3186  'wgAction' => Action::getActionName( $this->getContext() ),
3187  'wgUserName' => $user->isAnon() ? null : $user->getName(),
3188  'wgUserGroups' => $user->getEffectiveGroups(),
3189  'wgCategories' => $this->getCategories(),
3190  'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
3191  'wgPageContentLanguage' => $lang->getCode(),
3192  'wgPageContentModel' => $title->getContentModel(),
3193  'wgSeparatorTransformTable' => $compactSeparatorTransTable,
3194  'wgDigitTransformTable' => $compactDigitTransTable,
3195  'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
3196  'wgMonthNames' => $lang->getMonthNamesArray(),
3197  'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
3198  'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
3199  'wgRelevantArticleId' => $relevantTitle->getArticleID(),
3200  'wgRequestId' => WebRequest::getRequestId(),
3201  ];
3202 
3203  if ( $user->isLoggedIn() ) {
3204  $vars['wgUserId'] = $user->getId();
3205  $vars['wgUserEditCount'] = $user->getEditCount();
3206  $userReg = wfTimestampOrNull( TS_UNIX, $user->getRegistration() );
3207  $vars['wgUserRegistration'] = $userReg !== null ? ( $userReg * 1000 ) : null;
3208  // Get the revision ID of the oldest new message on the user's talk
3209  // page. This can be used for constructing new message alerts on
3210  // the client side.
3211  $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId();
3212  }
3213 
3214  if ( $wgContLang->hasVariants() ) {
3215  $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
3216  }
3217  // Same test as SkinTemplate
3218  $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user )
3219  && ( $title->exists() || $title->quickUserCan( 'create', $user ) );
3220 
3221  foreach ( $title->getRestrictionTypes() as $type ) {
3222  $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
3223  }
3224 
3225  if ( $title->isMainPage() ) {
3226  $vars['wgIsMainPage'] = true;
3227  }
3228 
3229  if ( $this->mRedirectedFrom ) {
3230  $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey();
3231  }
3232 
3233  if ( $relevantUser ) {
3234  $vars['wgRelevantUserName'] = $relevantUser->getName();
3235  }
3236 
3237  // Allow extensions to add their custom variables to the mw.config map.
3238  // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
3239  // page-dependant but site-wide (without state).
3240  // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
3241  Hooks::run( 'MakeGlobalVariablesScript', [ &$vars, $this ] );
3242 
3243  // Merge in variables from addJsConfigVars last
3244  return array_merge( $vars, $this->getJsConfigVars() );
3245  }
3246 
3256  public function userCanPreview() {
3257  $request = $this->getRequest();
3258  if (
3259  $request->getVal( 'action' ) !== 'submit' ||
3260  !$request->getCheck( 'wpPreview' ) ||
3261  !$request->wasPosted()
3262  ) {
3263  return false;
3264  }
3265 
3266  $user = $this->getUser();
3267  if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
3268  return false;
3269  }
3270 
3271  $title = $this->getTitle();
3272  if ( !$title->isJsSubpage() && !$title->isCssSubpage() ) {
3273  return false;
3274  }
3275  if ( !$title->isSubpageOf( $user->getUserPage() ) ) {
3276  // Don't execute another user's CSS or JS on preview (T85855)
3277  return false;
3278  }
3279 
3280  $errors = $title->getUserPermissionsErrors( 'edit', $user );
3281  if ( count( $errors ) !== 0 ) {
3282  return false;
3283  }
3284 
3285  return true;
3286  }
3287 
3291  public function getHeadLinksArray() {
3293 
3294  $tags = [];
3295  $config = $this->getConfig();
3296 
3297  $canonicalUrl = $this->mCanonicalUrl;
3298 
3299  $tags['meta-generator'] = Html::element( 'meta', [
3300  'name' => 'generator',
3301  'content' => "MediaWiki $wgVersion",
3302  ] );
3303 
3304  if ( $config->get( 'ReferrerPolicy' ) !== false ) {
3305  $tags['meta-referrer'] = Html::element( 'meta', [
3306  'name' => 'referrer',
3307  'content' => $config->get( 'ReferrerPolicy' )
3308  ] );
3309  }
3310 
3311  $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
3312  if ( $p !== 'index,follow' ) {
3313  // http://www.robotstxt.org/wc/meta-user.html
3314  // Only show if it's different from the default robots policy
3315  $tags['meta-robots'] = Html::element( 'meta', [
3316  'name' => 'robots',
3317  'content' => $p,
3318  ] );
3319  }
3320 
3321  foreach ( $this->mMetatags as $tag ) {
3322  if ( 0 == strcasecmp( 'http:', substr( $tag[0], 0, 5 ) ) ) {
3323  $a = 'http-equiv';
3324  $tag[0] = substr( $tag[0], 5 );
3325  } else {
3326  $a = 'name';
3327  }
3328  $tagName = "meta-{$tag[0]}";
3329  if ( isset( $tags[$tagName] ) ) {
3330  $tagName .= $tag[1];
3331  }
3332  $tags[$tagName] = Html::element( 'meta',
3333  [
3334  $a => $tag[0],
3335  'content' => $tag[1]
3336  ]
3337  );
3338  }
3339 
3340  foreach ( $this->mLinktags as $tag ) {
3341  $tags[] = Html::element( 'link', $tag );
3342  }
3343 
3344  # Universal edit button
3345  if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) {
3346  $user = $this->getUser();
3347  if ( $this->getTitle()->quickUserCan( 'edit', $user )
3348  && ( $this->getTitle()->exists() ||
3349  $this->getTitle()->quickUserCan( 'create', $user ) )
3350  ) {
3351  // Original UniversalEditButton
3352  $msg = $this->msg( 'edit' )->text();
3353  $tags['universal-edit-button'] = Html::element( 'link', [
3354  'rel' => 'alternate',
3355  'type' => 'application/x-wiki',
3356  'title' => $msg,
3357  'href' => $this->getTitle()->getEditURL(),
3358  ] );
3359  // Alternate edit link
3360  $tags['alternative-edit'] = Html::element( 'link', [
3361  'rel' => 'edit',
3362  'title' => $msg,
3363  'href' => $this->getTitle()->getEditURL(),
3364  ] );
3365  }
3366  }
3367 
3368  # Generally the order of the favicon and apple-touch-icon links
3369  # should not matter, but Konqueror (3.5.9 at least) incorrectly
3370  # uses whichever one appears later in the HTML source. Make sure
3371  # apple-touch-icon is specified first to avoid this.
3372  if ( $config->get( 'AppleTouchIcon' ) !== false ) {
3373  $tags['apple-touch-icon'] = Html::element( 'link', [
3374  'rel' => 'apple-touch-icon',
3375  'href' => $config->get( 'AppleTouchIcon' )
3376  ] );
3377  }
3378 
3379  if ( $config->get( 'Favicon' ) !== false ) {
3380  $tags['favicon'] = Html::element( 'link', [
3381  'rel' => 'shortcut icon',
3382  'href' => $config->get( 'Favicon' )
3383  ] );
3384  }
3385 
3386  # OpenSearch description link
3387  $tags['opensearch'] = Html::element( 'link', [
3388  'rel' => 'search',
3389  'type' => 'application/opensearchdescription+xml',
3390  'href' => wfScript( 'opensearch_desc' ),
3391  'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
3392  ] );
3393 
3394  if ( $config->get( 'EnableAPI' ) ) {
3395  # Real Simple Discovery link, provides auto-discovery information
3396  # for the MediaWiki API (and potentially additional custom API
3397  # support such as WordPress or Twitter-compatible APIs for a
3398  # blogging extension, etc)
3399  $tags['rsd'] = Html::element( 'link', [
3400  'rel' => 'EditURI',
3401  'type' => 'application/rsd+xml',
3402  // Output a protocol-relative URL here if $wgServer is protocol-relative.
3403  // Whether RSD accepts relative or protocol-relative URLs is completely
3404  // undocumented, though.
3405  'href' => wfExpandUrl( wfAppendQuery(
3406  wfScript( 'api' ),
3407  [ 'action' => 'rsd' ] ),
3409  ),
3410  ] );
3411  }
3412 
3413  # Language variants
3414  if ( !$config->get( 'DisableLangConversion' ) ) {
3415  $lang = $this->getTitle()->getPageLanguage();
3416  if ( $lang->hasVariants() ) {
3417  $variants = $lang->getVariants();
3418  foreach ( $variants as $variant ) {
3419  $tags["variant-$variant"] = Html::element( 'link', [
3420  'rel' => 'alternate',
3421  'hreflang' => wfBCP47( $variant ),
3422  'href' => $this->getTitle()->getLocalURL(
3423  [ 'variant' => $variant ] )
3424  ]
3425  );
3426  }
3427  # x-default link per https://support.google.com/webmasters/answer/189077?hl=en
3428  $tags["variant-x-default"] = Html::element( 'link', [
3429  'rel' => 'alternate',
3430  'hreflang' => 'x-default',
3431  'href' => $this->getTitle()->getLocalURL() ] );
3432  }
3433  }
3434 
3435  # Copyright
3436  if ( $this->copyrightUrl !== null ) {
3437  $copyright = $this->copyrightUrl;
3438  } else {
3439  $copyright = '';
3440  if ( $config->get( 'RightsPage' ) ) {
3441  $copy = Title::newFromText( $config->get( 'RightsPage' ) );
3442 
3443  if ( $copy ) {
3444  $copyright = $copy->getLocalURL();
3445  }
3446  }
3447 
3448  if ( !$copyright && $config->get( 'RightsUrl' ) ) {
3449  $copyright = $config->get( 'RightsUrl' );
3450  }
3451  }
3452 
3453  if ( $copyright ) {
3454  $tags['copyright'] = Html::element( 'link', [
3455  'rel' => 'copyright',
3456  'href' => $copyright ]
3457  );
3458  }
3459 
3460  # Feeds
3461  if ( $config->get( 'Feed' ) ) {
3462  $feedLinks = [];
3463 
3464  foreach ( $this->getSyndicationLinks() as $format => $link ) {
3465  # Use the page name for the title. In principle, this could
3466  # lead to issues with having the same name for different feeds
3467  # corresponding to the same page, but we can't avoid that at
3468  # this low a level.
3469 
3470  $feedLinks[] = $this->feedLink(
3471  $format,
3472  $link,
3473  # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
3474  $this->msg(
3475  "page-{$format}-feed", $this->getTitle()->getPrefixedText()
3476  )->text()
3477  );
3478  }
3479 
3480  # Recent changes feed should appear on every page (except recentchanges,
3481  # that would be redundant). Put it after the per-page feed to avoid
3482  # changing existing behavior. It's still available, probably via a
3483  # menu in your browser. Some sites might have a different feed they'd
3484  # like to promote instead of the RC feed (maybe like a "Recent New Articles"
3485  # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
3486  # If so, use it instead.
3487  $sitename = $config->get( 'Sitename' );
3488  if ( $config->get( 'OverrideSiteFeed' ) ) {
3489  foreach ( $config->get( 'OverrideSiteFeed' ) as $type => $feedUrl ) {
3490  // Note, this->feedLink escapes the url.
3491  $feedLinks[] = $this->feedLink(
3492  $type,
3493  $feedUrl,
3494  $this->msg( "site-{$type}-feed", $sitename )->text()
3495  );
3496  }
3497  } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
3498  $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
3499  foreach ( $config->get( 'AdvertisedFeedTypes' ) as $format ) {
3500  $feedLinks[] = $this->feedLink(
3501  $format,
3502  $rctitle->getLocalURL( [ 'feed' => $format ] ),
3503  # For grep: 'site-rss-feed', 'site-atom-feed'
3504  $this->msg( "site-{$format}-feed", $sitename )->text()
3505  );
3506  }
3507  }
3508 
3509  # Allow extensions to change the list pf feeds. This hook is primarily for changing,
3510  # manipulating or removing existing feed tags. If you want to add new feeds, you should
3511  # use OutputPage::addFeedLink() instead.
3512  Hooks::run( 'AfterBuildFeedLinks', [ &$feedLinks ] );
3513 
3514  $tags += $feedLinks;
3515  }
3516 
3517  # Canonical URL
3518  if ( $config->get( 'EnableCanonicalServerLink' ) ) {
3519  if ( $canonicalUrl !== false ) {
3520  $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL );
3521  } else {
3522  if ( $this->isArticleRelated() ) {
3523  // This affects all requests where "setArticleRelated" is true. This is
3524  // typically all requests that show content (query title, curid, oldid, diff),
3525  // and all wikipage actions (edit, delete, purge, info, history etc.).
3526  // It does not apply to File pages and Special pages.
3527  // 'history' and 'info' actions address page metadata rather than the page
3528  // content itself, so they may not be canonicalized to the view page url.
3529  // TODO: this ought to be better encapsulated in the Action class.
3530  $action = Action::getActionName( $this->getContext() );
3531  if ( in_array( $action, [ 'history', 'info' ] ) ) {
3532  $query = "action={$action}";
3533  } else {
3534  $query = '';
3535  }
3536  $canonicalUrl = $this->getTitle()->getCanonicalURL( $query );
3537  } else {
3538  $reqUrl = $this->getRequest()->getRequestURL();
3539  $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL );
3540  }
3541  }
3542  }
3543  if ( $canonicalUrl !== false ) {
3544  $tags[] = Html::element( 'link', [
3545  'rel' => 'canonical',
3546  'href' => $canonicalUrl
3547  ] );
3548  }
3549 
3550  return $tags;
3551  }
3552 
3558  public function getHeadLinks() {
3559  wfDeprecated( __METHOD__, '1.24' );
3560  return implode( "\n", $this->getHeadLinksArray() );
3561  }
3562 
3571  private function feedLink( $type, $url, $text ) {
3572  return Html::element( 'link', [
3573  'rel' => 'alternate',
3574  'type' => "application/$type+xml",
3575  'title' => $text,
3576  'href' => $url ]
3577  );
3578  }
3579 
3589  public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
3590  $options = [];
3591  if ( $media ) {
3592  $options['media'] = $media;
3593  }
3594  if ( $condition ) {
3595  $options['condition'] = $condition;
3596  }
3597  if ( $dir ) {
3598  $options['dir'] = $dir;
3599  }
3600  $this->styles[$style] = $options;
3601  }
3602 
3610  public function addInlineStyle( $style_css, $flip = 'noflip' ) {
3611  if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
3612  # If wanted, and the interface is right-to-left, flip the CSS
3613  $style_css = CSSJanus::transform( $style_css, true, false );
3614  }
3615  $this->mInlineStyles .= Html::inlineStyle( $style_css );
3616  }
3617 
3623  public function buildCssLinks() {
3625 
3626  $this->getSkin()->setupSkinUserCss( $this );
3627 
3628  // Add ResourceLoader styles
3629  // Split the styles into these groups
3630  $styles = [
3631  'other' => [],
3632  'user' => [],
3633  'site' => [],
3634  'private' => [],
3635  'noscript' => []
3636  ];
3637  $links = [];
3638  $otherTags = []; // Tags to append after the normal <link> tags
3640 
3641  $moduleStyles = $this->getModuleStyles();
3642 
3643  // Per-site custom styles
3644  $moduleStyles[] = 'site.styles';
3645  $moduleStyles[] = 'noscript';
3646 
3647  // Per-user custom styles
3648  if ( $this->getConfig()->get( 'AllowUserCss' ) && $this->getTitle()->isCssSubpage()
3649  && $this->userCanPreview()
3650  ) {
3651  // We're on a preview of a CSS subpage
3652  // Exclude this page from the user module in case it's in there (bug 26283)
3654  [ 'excludepage' => $this->getTitle()->getPrefixedDBkey() ]
3655  );
3656  $otherTags = array_merge( $otherTags, $link['html'] );
3657 
3658  // Load the previewed CSS
3659  // If needed, Janus it first. This is user-supplied CSS, so it's
3660  // assumed to be right for the content language directionality.
3661  $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' );
3662  if ( $this->getLanguage()->getDir() !== $wgContLang->getDir() ) {
3663  $previewedCSS = CSSJanus::transform( $previewedCSS, true, false );
3664  }
3665  $otherTags[] = Html::inlineStyle( $previewedCSS );
3666  } else {
3667  // Load the user styles normally
3668  $moduleStyles[] = 'user';
3669  }
3670 
3671  // Per-user preference styles
3672  $moduleStyles[] = 'user.cssprefs';
3673 
3674  foreach ( $moduleStyles as $name ) {
3675  $module = $resourceLoader->getModule( $name );
3676  if ( !$module ) {
3677  continue;
3678  }
3679  if ( $name === 'site.styles' ) {
3680  // HACK: The site module shouldn't be fragmented with a cache group and
3681  // http request. But in order to ensure its styles are separated and after the
3682  // ResourceLoaderDynamicStyles marker, pretend it is in a group called 'site'.
3683  // The scripts remain ungrouped and rides the bottom queue.
3684  $styles['site'][] = $name;
3685  continue;
3686  }
3687  $group = $module->getGroup();
3688  // Modules in groups other than the ones needing special treatment
3689  // (see $styles assignment)
3690  // will be placed in the "other" style category.
3691  $styles[isset( $styles[$group] ) ? $group : 'other'][] = $name;
3692  }
3693 
3694  // We want site, private and user styles to override dynamically added
3695  // styles from modules, but we want dynamically added styles to override
3696  // statically added styles from other modules. So the order has to be
3697  // other, dynamic, site, private, user. Add statically added styles for
3698  // other modules
3699  $links[] = $this->makeResourceLoaderLink(
3700  $styles['other'],
3702  );
3703  // Add normal styles added through addStyle()/addInlineStyle() here
3704  $links[] = implode( '', $this->buildCssLinksArray() ) . $this->mInlineStyles;
3705  // Add marker tag to mark the place where the client-side
3706  // loader should inject dynamic styles
3707  // We use a <meta> tag with a made-up name for this because that's valid HTML
3708  $links[] = Html::element(
3709  'meta',
3710  [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ]
3711  );
3712 
3713  // Add site-specific and user-specific styles
3714  // 'private' at present only contains user.options, so put that before 'user'
3715  // Any future private modules will likely have a similar user-specific character
3716  foreach ( [ 'site', 'noscript', 'private', 'user' ] as $group ) {
3717  $links[] = $this->makeResourceLoaderLink( $styles[$group],
3719  );
3720  }
3721 
3722  // Add stuff in $otherTags (previewed user CSS if applicable)
3723  $links[] = implode( '', $otherTags );
3724 
3725  return self::getHtmlFromLoaderLinks( $links );
3726  }
3727 
3731  public function buildCssLinksArray() {
3732  $links = [];
3733 
3734  // Add any extension CSS
3735  foreach ( $this->mExtStyles as $url ) {
3736  $this->addStyle( $url );
3737  }
3738  $this->mExtStyles = [];
3739 
3740  foreach ( $this->styles as $file => $options ) {
3741  $link = $this->styleLink( $file, $options );
3742  if ( $link ) {
3743  $links[$file] = $link;
3744  }
3745  }
3746  return $links;
3747  }
3748 
3756  protected function styleLink( $style, array $options ) {
3757  if ( isset( $options['dir'] ) ) {
3758  if ( $this->getLanguage()->getDir() != $options['dir'] ) {
3759  return '';
3760  }
3761  }
3762 
3763  if ( isset( $options['media'] ) ) {
3764  $media = self::transformCssMedia( $options['media'] );
3765  if ( is_null( $media ) ) {
3766  return '';
3767  }
3768  } else {
3769  $media = 'all';
3770  }
3771 
3772  if ( substr( $style, 0, 1 ) == '/' ||
3773  substr( $style, 0, 5 ) == 'http:' ||
3774  substr( $style, 0, 6 ) == 'https:' ) {
3775  $url = $style;
3776  } else {
3777  $config = $this->getConfig();
3778  $url = $config->get( 'StylePath' ) . '/' . $style . '?' .
3779  $config->get( 'StyleVersion' );
3780  }
3781 
3782  $link = Html::linkedStyle( $url, $media );
3783 
3784  if ( isset( $options['condition'] ) ) {
3785  $condition = htmlspecialchars( $options['condition'] );
3786  $link = "<!--[if $condition]>$link<![endif]-->";
3787  }
3788  return $link;
3789  }
3790 
3812  public static function transformResourcePath( Config $config, $path ) {
3813  global $IP;
3814  $remotePathPrefix = $config->get( 'ResourceBasePath' );
3815  if ( $remotePathPrefix === '' ) {
3816  // The configured base path is required to be empty string for
3817  // wikis in the domain root
3818  $remotePath = '/';
3819  } else {
3820  $remotePath = $remotePathPrefix;
3821  }
3822  if ( strpos( $path, $remotePath ) !== 0 ) {
3823  // Path is outside wgResourceBasePath, ignore.
3824  return $path;
3825  }
3826  $path = RelPath\getRelativePath( $path, $remotePath );
3827  return self::transformFilePath( $remotePathPrefix, $IP, $path );
3828  }
3829 
3841  public static function transformFilePath( $remotePathPrefix, $localPath, $file ) {
3842  $hash = md5_file( "$localPath/$file" );
3843  if ( $hash === false ) {
3844  wfLogWarning( __METHOD__ . ": Failed to hash $localPath/$file" );
3845  $hash = '';
3846  }
3847  return "$remotePathPrefix/$file?" . substr( $hash, 0, 5 );
3848  }
3849 
3857  public static function transformCssMedia( $media ) {
3859 
3860  // http://www.w3.org/TR/css3-mediaqueries/#syntax
3861  $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
3862 
3863  // Switch in on-screen display for media testing
3864  $switches = [
3865  'printable' => 'print',
3866  'handheld' => 'handheld',
3867  ];
3868  foreach ( $switches as $switch => $targetMedia ) {
3869  if ( $wgRequest->getBool( $switch ) ) {
3870  if ( $media == $targetMedia ) {
3871  $media = '';
3872  } elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
3873  /* This regex will not attempt to understand a comma-separated media_query_list
3874  *
3875  * Example supported values for $media:
3876  * 'screen', 'only screen', 'screen and (min-width: 982px)' ),
3877  * Example NOT supported value for $media:
3878  * '3d-glasses, screen, print and resolution > 90dpi'
3879  *
3880  * If it's a print request, we never want any kind of screen stylesheets
3881  * If it's a handheld request (currently the only other choice with a switch),
3882  * we don't want simple 'screen' but we might want screen queries that
3883  * have a max-width or something, so we'll pass all others on and let the
3884  * client do the query.
3885  */
3886  if ( $targetMedia == 'print' || $media == 'screen' ) {
3887  return null;
3888  }
3889  }
3890  }
3891  }
3892 
3893  return $media;
3894  }
3895 
3902  public function addWikiMsg( /*...*/ ) {
3903  $args = func_get_args();
3904  $name = array_shift( $args );
3905  $this->addWikiMsgArray( $name, $args );
3906  }
3907 
3916  public function addWikiMsgArray( $name, $args ) {
3917  $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
3918  }
3919 
3945  public function wrapWikiMsg( $wrap /*, ...*/ ) {
3946  $msgSpecs = func_get_args();
3947  array_shift( $msgSpecs );
3948  $msgSpecs = array_values( $msgSpecs );
3949  $s = $wrap;
3950  foreach ( $msgSpecs as $n => $spec ) {
3951  if ( is_array( $spec ) ) {
3952  $args = $spec;
3953  $name = array_shift( $args );
3954  if ( isset( $args['options'] ) ) {
3955  unset( $args['options'] );
3956  wfDeprecated(
3957  'Adding "options" to ' . __METHOD__ . ' is no longer supported',
3958  '1.20'
3959  );
3960  }
3961  } else {
3962  $args = [];
3963  $name = $spec;
3964  }
3965  $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
3966  }
3967  $this->addWikiText( $s );
3968  }
3969 
3975  public function enableTOC( $flag = true ) {
3976  $this->mEnableTOC = $flag;
3977  }
3978 
3983  public function isTOCEnabled() {
3984  return $this->mEnableTOC;
3985  }
3986 
3992  public function enableSectionEditLinks( $flag = true ) {
3993  $this->mEnableSectionEditLinks = $flag;
3994  }
3995 
4000  public function sectionEditLinksEnabled() {
4002  }
4003 
4011  public static function setupOOUI( $skinName = '', $dir = 'ltr' ) {
4012  $themes = ExtensionRegistry::getInstance()->getAttribute( 'SkinOOUIThemes' );
4013  // Make keys (skin names) lowercase for case-insensitive matching.
4014  $themes = array_change_key_case( $themes, CASE_LOWER );
4015  $theme = isset( $themes[$skinName] ) ? $themes[$skinName] : 'MediaWiki';
4016  // For example, 'OOUI\MediaWikiTheme'.
4017  $themeClass = "OOUI\\{$theme}Theme";
4018  OOUI\Theme::setSingleton( new $themeClass() );
4019  OOUI\Element::setDefaultDir( $dir );
4020  }
4021 
4028  public function enableOOUI() {
4029  self::setupOOUI(
4030  strtolower( $this->getSkin()->getSkinName() ),
4031  $this->getLanguage()->getDir()
4032  );
4033  $this->addModuleStyles( [
4034  'oojs-ui-core.styles',
4035  'oojs-ui.styles.icons',
4036  'oojs-ui.styles.indicators',
4037  'oojs-ui.styles.textures',
4038  'mediawiki.widgets.styles',
4039  ] );
4040  // Used by 'skipFunction' of the four 'oojs-ui.styles.*' modules. Please don't treat this as a
4041  // public API or you'll be severely disappointed when T87871 is fixed and it disappears.
4042  $this->addMeta( 'X-OOUI-PHP', '1' );
4043  }
4044 }
getPreventClickjacking()
Get the prevent-clickjacking flag.
setContext(IContextSource $context)
Set the IContextSource object.
addInlineStyle($style_css, $flip= 'noflip')
Adds inline CSS styles Internal use only.
const TS_RFC2822
RFC 2822 format, for E-mail and HTTP headers.
prependHTML($text)
Prepend $text to the body HTML.
isDisabled()
Return whether the output will be completely disabled.
static closeElement($element)
Returns "</$element>".
Definition: Html.php:306
showFileRenameError($old, $new)
static newFromContext(IContextSource $context)
Get a ParserOptions object from a IContextSource object.
ResourceLoader $mResourceLoader
Definition: OutputPage.php:155
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:1816
array $mTemplateIds
Definition: OutputPage.php:161
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
Interface for objects which can provide a MediaWiki context on request.
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
getPageTitleActionText()
Get the value of the "action text".
Definition: OutputPage.php:878
getMetadataAttribute()
Get the value of the "rel" attribute for metadata links.
Definition: OutputPage.php:432
getHeadItemsArray()
Get an array of head items.
Definition: OutputPage.php:631
static inlineScript($contents)
Output a "<script>" tag with the given contents.
Definition: Html.php:597
addHTML($text)
Append $text to the body HTML.
sendCacheControl()
Send cache control HTTP headers.
$mScripts
Used for JavaScript (predates ResourceLoader)
Definition: OutputPage.php:131
int $mCdnMaxage
Cache stuff.
Definition: OutputPage.php:224
the array() calling protocol came about after MediaWiki 1.4rc1.
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1435
addInlineScript($script)
Add a self-contained script tag with the given contents Internal use only.
Definition: OutputPage.php:506
addScript($script)
Add raw HTML to the list of scripts (including \<script\> tag, etc.) Internal use only...
Definition: OutputPage.php:450
showLagWarning($lag)
Show a warning about slave lag.
getLanguage()
Get the Language object.
getHTMLTitle()
Return the "HTML title", i.e.
Definition: OutputPage.php:901
int $mCdnMaxageLimit
Upper limit on mCdnMaxage.
Definition: OutputPage.php:226
addSubtitle($str)
Add $str to the subtitle.
Definition: OutputPage.php:974
static getRequestId()
Get the unique request ID.
Definition: WebRequest.php:268
The main skin class which provides methods and properties for all other skins.
Definition: Skin.php:34
$wgVersion
MediaWiki version number.
$mDebugtext
Holds the debug lines that will be output as comments in page source if $wgDebugComments is enabled...
Definition: OutputPage.php:75
addToBodyAttributes($out, &$bodyAttrs)
This will be called by OutputPage::headElement when it is creating the "<body>" tag, skins can override it if they have a need to add in any body attributes or classes of their own.
Definition: Skin.php:412
bool $mEnableTOC
Whether parser output should contain table of contents.
Definition: OutputPage.php:281
makeResourceLoaderLink($modules, $only, array $extraQuery=[])
Construct neccecary html and loader preset states to load modules on a page.
rateLimited()
Turn off regular page output and return an error response for when rate limiting has triggered...
wfScript($script= 'index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
array $mModuleScripts
Definition: OutputPage.php:149
if(count($args)==0) $dir
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
setHTMLTitle($name)
"HTML title" means the contents of "<title>".
Definition: OutputPage.php:888
addWikiTextTitle($text, Title $title, $linestart, $tidy=false, $interface=false)
Add wikitext with a custom Title object.
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:189
static newMainPage()
Create a new Title for the Main Page.
Definition: Title.php:548
getCanonicalUrl()
Returns the URL to be used for the <link rel="canonical"> if one is set.
Definition: OutputPage.php:423
int $mContainsNewMagic
Definition: OutputPage.php:186
$IP
Definition: WebStart.php:58
parserOptions($options=null)
Get/set the ParserOptions object to use for wikitext parsing.
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
addParserOutputMetadata($parserOutput)
Add all metadata associated with a ParserOutput object, but without the actual HTML.
error also a ContextSource you ll probably need to make sure the header is varied on such as when responding to a resource loader request or generating HTML output & $resourceLoader
Definition: hooks.txt:2458
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
buildCssLinks()
Build a set of "<link>" elements for stylesheets specified in the $this->styles array.
static getTitleFor($name, $subpage=false, $fragment= '')
Get a localised Title object for a specified special page name.
Definition: SpecialPage.php:80
hasHeadItem($name)
Check if the header item $name is already set.
Definition: OutputPage.php:657
setLanguageLinks(array $newLinkArray)
Reset the language links and add new language links.
The Message class provides methods which fulfil two basic services:
Definition: Message.php:159
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException'returning false will NOT prevent logging $e
Definition: hooks.txt:1980
string $mInlineStyles
Inline CSS styles.
Definition: OutputPage.php:134
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
array $mMetatags
Should be private.
Definition: OutputPage.php:45
$wgParser
Definition: Setup.php:816
addCategoryLinks(array $categories)
Add an array of categories, with names in the keys.
haveCacheVaryCookies()
Check if the request has a cache-varying cookie header If it does, it's very important that we don't ...
getPageTitle()
Return the "page title", i.e.
Definition: OutputPage.php:946
static rawElement($element, $attribs=[], $contents= '')
Returns an HTML element in a string.
Definition: Html.php:210
setSubtitle($str)
Replace the subtitle with $str.
Definition: OutputPage.php:964
addWikiMsgArray($name, $args)
Add a wikitext-formatted message to the output.
addFeedLink($format, $href)
Add a feed link to the page header.
sectionEditLinksEnabled()
if(!isset($args[0])) $lang
setArticleRelated($v)
Set whether this page is related an article on the wiki Setting false will cause the change of "artic...
array $mIndicators
Definition: OutputPage.php:120
readOnlyPage()
Display a page stating that the Wiki is in read-only mode.
checkLastModified($timestamp)
checkLastModified tells the client to use the client-cached page if possible.
Definition: OutputPage.php:725
addScriptFile($file, $version=null)
Add a JavaScript file out of skins/common, or a given relative path.
Definition: OutputPage.php:487
array $mHeadItems
Array of elements in "<head>".
Definition: OutputPage.php:143
allowClickjacking()
Turn off frame-breaking.
setFeedAppendQuery($val)
Add default feeds to the page header This is mainly kept for backward compatibility, see OutputPage::addFeedLink() for the new version.
isRedirect($flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition: Title.php:3167
array $mModules
Definition: OutputPage.php:146
array $mExtStyles
Additional stylesheets.
Definition: OutputPage.php:57
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:177
$source
$value
showFatalError($message)
setETag($tag)
Definition: OutputPage.php:665
getExtStyle()
Get all styles added by extensions.
Definition: OutputPage.php:474
returnToMain($unused=null, $returnto=null, $returntoquery=null)
Add a "return to" link pointing to a specified title, or the title indicated in the request...
const NS_SPECIAL
Definition: Defines.php:58
const PROTO_CURRENT
Definition: Defines.php:265
addWikiTextTitleTidy($text, &$title, $linestart=true)
Add wikitext with a custom Title object and tidy enabled.
static linkedScript($url)
Output a "<script>" tag linking to the given URL, e.g., "<script src=foo.js></script>".
Definition: Html.php:614
string null $mTarget
ResourceLoader target for load.php links.
Definition: OutputPage.php:276
getArticleBodyOnly()
Return whether the output will contain only the body of the article.
Definition: OutputPage.php:684
static escapeClass($class)
Given a value, escape it so that it can be used as a CSS class and return it.
Definition: Sanitizer.php:1246
addHelpLink($to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
filterModules(array $modules, $position=null, $type=ResourceLoaderModule::TYPE_COMBINED)
Filter an array of modules to remove insufficiently trustworthy members, and modules which are no lon...
Definition: OutputPage.php:518
string $mPageLinkTitle
Used by skin template.
Definition: OutputPage.php:140
addParserOutputContent($parserOutput)
Add the HTML and enhancements for it (like ResourceLoader modules) associated with a ParserOutput obj...
userCanPreview()
To make it harder for someone to slip a user a fake user-JavaScript or user-CSS preview, a random token is associated with the login session.
addParserOutputText($parserOutput)
Add the HTML associated with a ParserOutput object, without any metadata.
setPrintable()
Set the page as printable, i.e.
string null $copyrightUrl
The URL to send in a <link> element with rel=copyright.
Definition: OutputPage.php:291
disable()
Disable output completely, i.e.
static exists($index)
Returns whether the specified namespace exists.
wfUrlencode($s)
We want some things to be included as literal characters in our title URLs for prettiness, which urlencode encodes by default.
setStatusCode($statusCode)
Set the HTTP status code to send with the output.
Definition: OutputPage.php:349
static newFromText($text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:256
this hook is for auditing only $response
Definition: hooks.txt:776
Represents a title within MediaWiki.
Definition: Title.php:36
addExtensionStyle($url)
Register and add a stylesheet from an extension directory.
Definition: OutputPage.php:463
static stripAllTags($text)
Take a fragment of (potentially invalid) HTML and return a version with any tags removed, encoded as plain text.
Definition: Sanitizer.php:1822
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
wfExpandUrl($url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
formatPermissionsErrorMessage(array $errors, $action=null)
Format a list of error messages.
string $mRevisionTimestamp
Definition: OutputPage.php:239
array $mLanguageLinks
Array of Interwiki Prefixed (non DB key) Titles (e.g.
Definition: OutputPage.php:123
IContextSource $context
getFileSearchOptions()
Get the files used on this page.
setCategoryLinks(array $categories)
Reset the category links (but not the category list) and add $categories.
isSyndicated()
Should we output feed links for this page?
array $styles
An array of stylesheet filenames (relative from skins path), with options for CSS media...
Definition: OutputPage.php:252
isPrintable()
Return whether the page is "printable".
getTitle()
Get the Title object.
prepareErrorPage($pageTitle, $htmlTitle=false)
Prepare this object to display an error page; disable caching and indexing, clear the current text an...
$mProperties
Additional key => value data.
Definition: OutputPage.php:271
get($name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
getAllowedModules($type)
Show what level of JavaScript / CSS untrustworthiness is allowed on this page.
addElement($element, array $attribs=[], $contents= '')
Shortcut for adding an Html::element via addHTML.
array bool $mDoNothing
Whether output is disabled.
Definition: OutputPage.php:181
addModules($modules)
Add one or more modules recognized by ResourceLoader.
Definition: OutputPage.php:558
wfDebug($text, $dest= 'all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
reduceAllowedModules($type, $level)
Limit the highest level of CSS/JS untrustworthiness allowed.
setPageTitleActionText($text)
Set the new value of the "action text", this will be added to the "HTML title", separated from it wit...
Definition: OutputPage.php:869
bool $mPreventClickjacking
Controls if anti-clickjacking / frame-breaking headers will be sent.
Definition: OutputPage.php:233
addMetadataLink(array $linkarr)
Add a new \<link\> with "rel" attribute set to "meta".
Definition: OutputPage.php:402
if($line===false) $args
Definition: cdb.php:64
the value to return A Title object or null for latest to be modified or replaced by the hook handler or if authentication is not possible after cache objects are set for highlighting & $link
Definition: hooks.txt:2621
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub 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:776
preventClickjacking($enable=true)
Set a flag which will cause an X-Frame-Options header appropriate for edit pages to be sent...
enableClientCache($state)
Use enableClientCache(false) to force it to send nocache headers.
getCategories()
Get the list of category names this page belongs to.
array $mJsConfigVars
Definition: OutputPage.php:158
getJsConfigVars()
Get the javascript config vars to include on this page.
enableOOUI()
Add ResourceLoader module styles for OOUI and set up the PHP implementation of it for use with MediaW...
addParserOutput($parserOutput)
Add everything from a ParserOutput object.
static makeConfigSetScript(array $configuration, $pretty=null)
Returns JS code which will set the MediaWiki configuration array to the given value.
bool $mPrintable
We have to set isPrintable().
Definition: OutputPage.php:93
static makeLoaderStateScript($name, $state=null)
Returns a JS call to mw.loader.state, which sets the state of a module or modules to a given value...
clearSubtitle()
Clear the subtitles.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition: LinkBatch.php:32
wfTimestamp($outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
getRequest()
Get the WebRequest object.
addWikiTextWithTitle($text, &$title, $linestart=true)
Add wikitext with a custom Title object.
getHeadScripts()
JS stuff to put in the "<head>".
bool $mIsArticleRelated
Stores "article flag" toggle.
Definition: OutputPage.php:87
canUseWikiPage()
Check whether a WikiPage object can be get with getWikiPage().
getExternalHeadScripts()
<script src="..."> tags for "<head>".This is the startup module and other modules marked with pos...
array $mAllowedModules
What level of 'untrustworthiness' is allowed in CSS/JS modules loaded on this page?
Definition: OutputPage.php:176
bool $mNoGallery
Comes from the parser.
Definition: OutputPage.php:218
static transformFilePath($remotePathPrefix, $localPath, $file)
Utility method for transformResourceFilePath().
getTemplateIds()
Get the templates used on this page.
wfCgiToArray($query)
This is the logical opposite of wfArrayToCgi(): it accepts a query string as its argument and returns...
setCopyrightUrl($url)
Set the copyright URL to send with the output.
Definition: OutputPage.php:340
msg()
Get a Message object with context set Parameters are the same as wfMessage()
getHTML()
Get the body HTML.
static buildBacklinkSubtitle(Title $title, $query=[])
Build message object for a subtitle containing a backlink to a page.
Definition: OutputPage.php:990
feedLink($type, $url, $text)
Generate a "<link rel/>" for a feed.
static openElement($element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition: Html.php:248
getLanguageLinks()
Get the list of language links.
array $mFileVersion
Definition: OutputPage.php:242
array $mModuleStyles
Definition: OutputPage.php:152
addAcceptLanguage()
T23672: Add Accept-Language to Vary and Key headers if there's no 'variant' parameter existed in GET...
static stripOuterParagraph($html)
Strip outer.
Definition: Parser.php:5970
static groupHasPermission($group, $role)
Check, if the given group has the given permission.
Definition: User.php:4822
Interface for configuration instances.
Definition: Config.php:28
static singleton()
Get an instance of this class.
Definition: LinkCache.php:65
static getCanonicalName($index)
Returns the canonical (English) name for a given index.
addWikiText($text, $linestart=true, $interface=true)
Convert wikitext to HTML and add it to the buffer Default assumes that the current page title will be...
wfAppendQuery($url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
headElement(Skin $sk, $includeStyle=true)
getJSVars()
Get an array containing the variables to be set in mw.config in JavaScript.
static transformResourcePath(Config $config, $path)
Transform path to web-accessible static resource.
getModuleScripts($filter=false, $position=null)
Get the list of module JS to include on this page.
Definition: OutputPage.php:570
if($limit) $timestamp
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context $parserOutput
Definition: hooks.txt:1020
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1020
const TS_ISO_8601
ISO 8601 format with no timezone: 1986-02-09T20:00:00Z.
$mFeedLinks
Handles the Atom / RSS links.
Definition: OutputPage.php:199
$res
Definition: database.txt:21
getSubtitle()
Get the subtitle.
isArticleRelated()
Return whether this page is related an article on the wiki.
setRedirectedFrom($t)
Set $mRedirectedFrom, the Title of the page which redirected us to the current page.
Definition: OutputPage.php:910
showErrorPage($title, $msg, $params=[])
Output a standard error page.
getConfig()
Get the Config object.
$mFeedLinksAppendQuery
Definition: OutputPage.php:169
getMetaTags()
Returns the current <meta> tags.
Definition: OutputPage.php:370
lowerCdnMaxage($maxage)
Lower the value of the "s-maxage" part of the "Cache-control" HTTP header.
getCategoryLinks()
Get the list of category links, in a 2-D array with the following format: $arr[$type][] = $link...
MediaWiki exception.
Definition: MWException.php:26
bool $mArticleBodyOnly
Flag if output should only contain the body of the article.
Definition: OutputPage.php:205
setPageTitle($name)
"Page title" means the contents of \<h1\>.
Definition: OutputPage.php:924
setRobotPolicy($policy)
Set the robot policy for the page: http://www.robotstxt.org/meta.html
Definition: OutputPage.php:824
bool $mEnableSectionEditLinks
Whether parser output should contain section edit links.
Definition: OutputPage.php:286
getInlineHeadScripts()
Inline "<script>" tags to put in "<head>".
addTemplate(&$template)
Add the output of a QuickTemplate to the output buffer.
clearHTML()
Clear the body HTML.
getFrameOptions()
Get the X-Frame-Options header value (without the name part), or false if there isn't one...
A wrapper class which causes Xml::encodeJsVar() and Xml::encodeJsCall() to interpret a given string a...
Definition: Xml.php:884
setCdnMaxage($maxage)
Set the value of the "s-maxage" part of the "Cache-control" HTTP header.
enableSectionEditLinks($flag=true)
Enables/disables section edit links, doesn't override NOEDITSECTION
getIndicators()
Get the indicators associated with this page.
getContext()
Get the base IContextSource object.
addWikiMsg()
Add a wikitext-formatted message to the output.
static makePackedModulesString($modules)
Convert an array of module names to a packed query string.
$params
showNewSectionLink()
Show an "add new section" link?
const NS_CATEGORY
Definition: Defines.php:83
Title $mRedirectedFrom
If the current page was reached through a redirect, $mRedirectedFrom contains the Title of the redire...
Definition: OutputPage.php:266
and(b) You must cause any modified files to carry prominent notices stating that You changed the files
wfDeprecated($function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
string $mHTMLtitle
Stores contents of "<title>" tag.
Definition: OutputPage.php:78
bool $mHideNewSectionLink
Definition: OutputPage.php:211
string $mBodytext
Contains all of the "<body>" content.
Definition: OutputPage.php:68
static makeTitleSafe($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:527
buildCssLinksArray()
getModuleStyles($filter=false, $position=null)
Get the list of module CSS to include on this page.
Definition: OutputPage.php:593
parseInline($text, $linestart=true, $interface=false)
Parse wikitext, strip paragraphs, and return the HTML.
const DB_SLAVE
Definition: Defines.php:46
styleLink($style, array $options)
Generate \<link\> tags for stylesheets.
static makeInlineScript($script)
Construct an inline script tag with given JS code.
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 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 after processing after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation 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 unsetoffset-wrap String Wrap the message in html(usually something like"&lt
getHtmlElementAttributes()
Return values for <html> element.
Definition: Skin.php:396
wfBCP47($code)
Get the normalised IETF language tag See unit test for examples.
Allows to change the fields on the form that will be generated are created Can be used to omit specific feeds from being outputted You must not use this hook to add use OutputPage::addFeedLink() instead.&$feedLinks conditions will AND in the final query as a Content object as a Content object $title
Definition: hooks.txt:312
static linkKnown($target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to 'known'.
Definition: Linker.php:255
static addModules(OutputPage $out)
Add ResourceLoader modules to the OutputPage object if debugging is enabled.
Definition: MWDebug.php:94
addLink(array $linkarr)
Add a new \<link\> tag to the page header.
Definition: OutputPage.php:381
addStyle($style, $media= '', $condition= '', $dir= '')
Add a local or specified stylesheet, with the given media options.
static inlineStyle($contents, $media= 'all')
Output a "<style>" tag with the given contents for the given media type (if any). ...
Definition: Html.php:629
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
const PROTO_RELATIVE
Definition: Defines.php:264
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:12
array $mCategories
Definition: OutputPage.php:117
static getActionName(IContextSource $context)
Get the action that will be executed, not necessarily the one passed passed through the "action" requ...
Definition: Action.php:122
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books $tag
Definition: hooks.txt:981
showFileNotFoundError($name)
setFollowPolicy($policy)
Set the follow policy for the page, but leave the index policy un- touched.
Definition: OutputPage.php:856
bool $mNewSectionLink
Definition: OutputPage.php:208
static htmlHeader(array $attribs=[])
Constructs the opening html-tag with necessary doctypes depending on global variables.
Definition: Html.php:910
getPageClasses($title)
TODO: document.
Definition: Skin.php:368
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
addVaryHeader($header, array $option=null)
Add an HTTP header that will influence on the cache.
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub 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:776
This class should be covered by a general architecture document which does not exist as of January 20...
Definition: OutputPage.php:43
parse($text, $linestart=true, $interface=false, $language=null)
Parse wikitext and return the HTML.
static newFromAnon()
Get a ParserOptions object for an anonymous user.
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:242
static link($target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition: Linker.php:203
getFeedAppendQuery()
Will currently always return null.
array $mCategoryLinks
Definition: OutputPage.php:114
static getSelectFields()
Fields that LinkCache needs to select.
Definition: LinkCache.php:211
setTarget($target)
Sets ResourceLoader target for load.php links.
Definition: OutputPage.php:622
setTitle(Title $t)
Set the Title object to use.
Definition: OutputPage.php:955
addBacklinkSubtitle(Title $title, $query=[])
Add a subtitle containing a backlink to a page.
int $mStatusCode
Definition: OutputPage.php:105
bool $mCanonicalUrl
Definition: OutputPage.php:51
array $mLinktags
Definition: OutputPage.php:48
addMeta($name, $val)
Add a new "<meta>" tag To add an http-equiv meta tag, precede the name with "http:".
Definition: OutputPage.php:360
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
addLanguageLinks(array $newLinkArray)
Add new language links.
wfGetAllCallers($limit=3)
Return a string consisting of callers in the stack.
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired whether it is OK to use $contentModel on $title Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok inclusive false for true for descending in case the handler function wants to provide a converted Content object Note that $result getContentModel() must return $toModel. 'CustomEditor'you ll need to handle error messages
Definition: hooks.txt:1115
string $mLastModified
Used for sending cache control.
Definition: OutputPage.php:111
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
wfSetVar(&$dest, $source, $force=false)
Sets dest to source and returns the original value of dest If source is NULL, it just returns the val...
static inDebugMode()
Determine whether debug mode was requested Order of priority is 1) request param, 2) cookie...
addParserOutputNoText($parserOutput)
Add a ParserOutput object, but without Html.
const PROTO_CANONICAL
Definition: Defines.php:266
static removeHTMLtags($text, $processCallback=null, $args=[], $extratags=[], $removetags=[], $warnCallback=null)
Cleans up HTML, removes dangerous tags and attributes, and removes HTML comments. ...
Definition: Sanitizer.php:462
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2458
int $mRevisionId
To include the variable {{REVISIONID}}.
Definition: OutputPage.php:236
setSquidMaxage($maxage)
setRevisionTimestamp($timestamp)
Set the timestamp of the revision which will be displayed.
showFileCopyError($old, $new)
getSyndicationLinks()
Return URLs for each supported syndication format for this page.
versionRequired($version)
Display an error page indicating that a given version of MediaWiki is required to use it...
static resolveAlias($alias)
Given a special page name with a possible subpage, return an array where the first element is the spe...
enableTOC($flag=true)
Enables/disables TOC, doesn't override NOTOC
static transformCssMedia($media)
Transform "media" attribute based on request parameters.
static normalizeCharReferences($text)
Ensure that any entities and character references are legal for XML and XHTML specifically.
Definition: Sanitizer.php:1399
setArticleBodyOnly($only)
Set whether the output should only contain the body of the article, without any skin, sidebar, etc.
Definition: OutputPage.php:675
getProperty($name)
Get an additional output property.
Definition: OutputPage.php:706
WebRequest clone which takes values from a provided array.
Definition: FauxRequest.php:33
addHeadItem($name, $value)
Add or replace an header item to the output.
Definition: OutputPage.php:647
output()
Finally, all the text has been munged and accumulated into the object, let's actually output it: ...
static isXmlMimeType($mimetype)
Determines if the given MIME type is xml.
Definition: Html.php:949
exists()
Returns true if file exists in the repository.
Definition: File.php:876
wfArrayToCgi($array1, $array2=null, $prefix= '')
This function takes one or two arrays as input, and returns a CGI-style string, e.g.
array $limitReportData
Profiling data.
Definition: OutputPage.php:294
string $mRedirectCode
Definition: OutputPage.php:167
__construct(IContextSource $context=null)
Constructor for OutputPage.
Definition: OutputPage.php:302
array $mImageTimeKeys
Definition: OutputPage.php:164
getResourceLoader()
Get a ResourceLoader object associated with this OutputPage.
getRevisionId()
Get the displayed revision ID.
static setupOOUI($skinName= '', $dir= 'ltr')
Helper function to setup the PHP implementation of OOUI to use in this request.
wfClearOutputBuffers()
More legible than passing a 'false' parameter to wfResetOutputBuffers():
getFileVersion()
Get the displayed file version.
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the local content language as $wgContLang
Definition: design.txt:56
setIndexPolicy($policy)
Set the index policy for the page, but leave the follow policy un- touched.
Definition: OutputPage.php:842
getScriptsForBottomQueue($unused=null)
JS stuff to put at the 'bottom', which goes at the bottom of the <body>.
setProperty($name, $value)
Set an additional output property.
Definition: OutputPage.php:695
forceHideNewSectionLink()
Forcibly hide the new section link?
MediaWiki Logger LoggerFactory implements a PSR[0] compatible message logging system Named Psr Log LoggerInterface instances can be obtained from the MediaWiki Logger LoggerFactory::getInstance() static method.MediaWiki\Logger\LoggerFactory expects a class implementing the MediaWiki\Logger\Spi interface to act as a factory for new Psr\Log\LoggerInterface instances.The"Spi"in MediaWiki\Logger\Spi stands for"service provider interface".An SPI is an API intended to be implemented or extended by a third party.This software design pattern is intended to enable framework extension and replaceable components.It is specifically used in the MediaWiki\Logger\LoggerFactory service to allow alternate PSR-3 logging implementations to be easily integrated with MediaWiki.The service provider interface allows the backend logging library to be implemented in multiple ways.The $wgMWLoggerDefaultSpi global provides the classname of the default MediaWiki\Logger\Spi implementation to be loaded at runtime.This can either be the name of a class implementing the MediaWiki\Logger\Spi with a zero argument const ructor or a callable that will return an MediaWiki\Logger\Spi instance.Alternately the MediaWiki\Logger\LoggerFactory MediaWiki Logger LoggerFactory
Definition: logger.txt:5
wrapWikiMsg($wrap)
This function takes a number of message/argument specifications, wraps them in some overall structure...
bool $mIsarticle
Is the displayed content related to the source of the corresponding wiki article. ...
Definition: OutputPage.php:84
getBottomScripts()
JS stuff to put at the bottom of the "<body>".
setFileVersion($file)
Set the displayed file version.
getRedirect()
Get the URL to redirect to, or an empty string if not redirect URL set.
Definition: OutputPage.php:328
addReturnTo($title, array $query=[], $text=null, $options=[])
Add a "return to" link pointing to a specified title.
static makeLoaderQuery($modules, $lang, $skin, $user=null, $version=null, $debug=false, $only=null, $printable=false, $handheld=false, $extraQuery=[])
Build a query array (array representation of query string) for load.php.
setRevisionId($revid)
Set the revision ID which will be seen by the wiki text parser for things such as embedded {{REVISION...
getKeyHeader()
Get a complete Key header.
$version
Definition: parserTests.php:94
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:92
showUnexpectedValueError($name, $val)
wfLogWarning($msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
const TS_UNIX
Unix time - the number of seconds since 1970-01-01 00:00:00 UTC.
array $mSubtitle
Contains the page subtitle.
Definition: OutputPage.php:99
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:1816
This serves as the entry point to the MediaWiki session handling system.
redirect($url, $responsecode= '302')
Redirect to $url rather than displaying the normal page.
Definition: OutputPage.php:317
string $mPagetitle
Should be private - has getter and setter.
Definition: OutputPage.php:62
static getHtmlFromLoaderLinks(array $links)
Build html output from an array of links from makeResourceLoaderLink.
showPermissionsErrorPage(array $errors, $action=null)
Output a standard permission error page.
getCacheVaryCookies()
Get the list of cookies that will influence on the cache.
wfTimestampOrNull($outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
setArticleFlag($v)
Set whether the displayed content is related to the source of the corresponding article on the wiki S...
showFileDeleteError($name)
static encodeJsCall($name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition: Xml.php:682
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:50
addWikiTextTidy($text, $linestart=true)
Add wikitext with tidy enabled.
string $mPageTitleActionText
Definition: OutputPage.php:221
Dynamic JavaScript and CSS resource loading system.
getWikiPage()
Get the WikiPage object.
getSkinName()
Definition: Skin.php:137
static linkedStyle($url, $media= 'all')
Output a "<link rel=stylesheet>" linking to the given URL for the given media type (if any)...
Definition: Html.php:647
static element($element, $attribs=[], $contents= '')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:230
setIndicators(array $indicators)
Add an array of indicators, with their identifiers as array keys and HTML contents as values...
disallowUserJs()
Do not allow scripts which can be modified by wiki users to load on this page; only allow scripts bun...
getOrigin()
Get this module's origin.
getUser()
Get the User object.
getVaryHeader()
Return a Vary: header on which to vary caches.
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition: hooks.txt:2044
getModules($filter=false, $position=null, $param= 'mModules')
Get the list of modules to include on this page.
Definition: OutputPage.php:544
if(is_null($wgLocalTZoffset)) if(!$wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:663
Show an error when the user hits a rate limit.
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2376
addModuleStyles($modules)
Add only CSS of one or more modules recognized by ResourceLoader.
Definition: OutputPage.php:606
string $mRedirect
Definition: OutputPage.php:102
addModuleScripts($modules)
Add only JS of one or more modules recognized by ResourceLoader.
Definition: OutputPage.php:581
setCanonicalUrl($url)
Set the URL to be used for the <link rel="canonical">.
Definition: OutputPage.php:412
setLastModified($timestamp)
Override the last modified timestamp.
Definition: OutputPage.php:812
setSyndicated($show=true)
Add or remove feed links in the page header This is mainly kept for backward compatibility, see OutputPage::addFeedLink() for the new version.
getRevisionTimestamp()
Get the timestamp of displayed revision.
getLinkTags()
Returns the current <link> tags.
Definition: OutputPage.php:391
isArticle()
Return whether the content displayed page is related to the source of the corresponding article on th...
Object passed around to modules which contains information about the state of a specific loader reque...
ParserOptions $mParserOptions
lazy initialised, use parserOptions()
Definition: OutputPage.php:192
addJsConfigVars($keys, $value=null)
Add one or more variables to be set in mw.config in JavaScript.
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310
getSkin()
Get the Skin object.
static formatRobotPolicy($policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition: Article.php:919