MediaWiki REL1_32
OutputPage.php
Go to the documentation of this file.
1<?php
28use Wikimedia\RelPath;
29use Wikimedia\WrappedString;
30use Wikimedia\WrappedStringList;
31
49 protected $mMetatags = [];
50
52 protected $mLinktags = [];
53
55 protected $mCanonicalUrl = false;
56
59 private $mPageTitle = '';
60
69
74 public $mBodytext = '';
75
77 private $mHTMLtitle = '';
78
83 private $mIsArticle = false;
84
86 private $mIsArticleRelated = true;
87
89 private $mHasCopyright = false;
90
95 private $mPrintable = false;
96
101 private $mSubtitle = [];
102
104 public $mRedirect = '';
105
107 protected $mStatusCode;
108
113 protected $mLastModified = '';
114
116 protected $mCategoryLinks = [];
117
119 protected $mCategories = [
120 'hidden' => [],
121 'normal' => [],
122 ];
123
125 protected $mIndicators = [];
126
128 private $mLanguageLinks = [];
129
136 private $mScripts = '';
137
139 protected $mInlineStyles = '';
140
145 public $mPageLinkTitle = '';
146
148 protected $mHeadItems = [];
149
152
154 protected $mModules = [];
155
157 protected $mModuleScripts = [];
158
160 protected $mModuleStyles = [];
161
164
166 private $rlClient;
167
170
173
175 protected $mJsConfigVars = [];
176
178 protected $mTemplateIds = [];
179
181 protected $mImageTimeKeys = [];
182
184 public $mRedirectCode = '';
185
186 protected $mFeedLinksAppendQuery = null;
187
193 protected $mAllowedModules = [
195 ];
196
198 protected $mDoNothing = false;
199
200 // Parser related.
201
203 protected $mContainsNewMagic = 0;
204
209 protected $mParserOptions = null;
210
216 private $mFeedLinks = [];
217
218 // Gwicke work on squid caching? Roughly from 2003.
219 protected $mEnableClientCache = true;
220
222 private $mArticleBodyOnly = false;
223
225 protected $mNewSectionLink = false;
226
228 protected $mHideNewSectionLink = false;
229
235 public $mNoGallery = false;
236
238 protected $mCdnMaxage = 0;
240 protected $mCdnMaxageLimit = INF;
241
247 protected $mPreventClickjacking = true;
248
250 private $mRevisionId = null;
251
253 private $mRevisionTimestamp = null;
254
256 protected $mFileVersion = null;
257
266 protected $styles = [];
267
268 private $mIndexPolicy = 'index';
269 private $mFollowPolicy = 'follow';
270
275 private $mVaryHeader = [
276 'Accept-Encoding' => [ 'match=gzip' ],
277 ];
278
285 private $mRedirectedFrom = null;
286
290 private $mProperties = [];
291
295 private $mTarget = null;
296
300 private $mEnableTOC = false;
301
306
308 private $limitReportJSData = [];
309
311 private $contentOverrides = [];
312
315
319 private $mLinkHeader = [];
320
324 private $CSPNonce;
325
329 private static $cacheVaryCookies = null;
330
338 $this->setContext( $context );
339 }
340
347 public function redirect( $url, $responsecode = '302' ) {
348 # Strip newlines as a paranoia check for header injection in PHP<5.1.2
349 $this->mRedirect = str_replace( "\n", '', $url );
350 $this->mRedirectCode = $responsecode;
351 }
352
358 public function getRedirect() {
359 return $this->mRedirect;
360 }
361
370 public function setCopyrightUrl( $url ) {
371 $this->copyrightUrl = $url;
372 }
373
379 public function setStatusCode( $statusCode ) {
380 $this->mStatusCode = $statusCode;
381 }
382
390 function addMeta( $name, $val ) {
391 array_push( $this->mMetatags, [ $name, $val ] );
392 }
393
400 public function getMetaTags() {
401 return $this->mMetatags;
402 }
403
411 function addLink( array $linkarr ) {
412 array_push( $this->mLinktags, $linkarr );
413 }
414
421 public function getLinkTags() {
422 return $this->mLinktags;
423 }
424
430 function setCanonicalUrl( $url ) {
431 $this->mCanonicalUrl = $url;
432 }
433
441 public function getCanonicalUrl() {
442 return $this->mCanonicalUrl;
443 }
444
452 function addScript( $script ) {
453 $this->mScripts .= $script;
454 }
455
464 public function addScriptFile( $file, $unused = null ) {
465 if ( substr( $file, 0, 1 ) !== '/' && !preg_match( '#^[a-z]*://#i', $file ) ) {
466 // This is not an absolute path, protocol-relative url, or full scheme url,
467 // presumed to be an old call intended to include a file from /w/skins/common,
468 // which doesn't exist anymore as of MediaWiki 1.24 per T71277. Ignore.
469 wfDeprecated( __METHOD__, '1.24' );
470 return;
471 }
472 $this->addScript( Html::linkedScript( $file, $this->getCSPNonce() ) );
473 }
474
481 public function addInlineScript( $script ) {
482 $this->mScripts .= Html::inlineScript( "\n$script\n", $this->getCSPNonce() ) . "\n";
483 }
484
493 protected function filterModules( array $modules, $position = null,
495 ) {
497 $filteredModules = [];
498 foreach ( $modules as $val ) {
499 $module = $resourceLoader->getModule( $val );
500 if ( $module instanceof ResourceLoaderModule
501 && $module->getOrigin() <= $this->getAllowedModules( $type )
502 ) {
503 if ( $this->mTarget && !in_array( $this->mTarget, $module->getTargets() ) ) {
504 $this->warnModuleTargetFilter( $module->getName() );
505 continue;
506 }
507 $filteredModules[] = $val;
508 }
509 }
510 return $filteredModules;
511 }
512
513 private function warnModuleTargetFilter( $moduleName ) {
514 static $warnings = [];
515 if ( isset( $warnings[$this->mTarget][$moduleName] ) ) {
516 return;
517 }
518 $warnings[$this->mTarget][$moduleName] = true;
519 $this->getResourceLoader()->getLogger()->debug(
520 'Module "{module}" not loadable on target "{target}".',
521 [
522 'module' => $moduleName,
523 'target' => $this->mTarget,
524 ]
525 );
526 }
527
537 public function getModules( $filter = false, $position = null, $param = 'mModules',
539 ) {
540 $modules = array_values( array_unique( $this->$param ) );
541 return $filter
542 ? $this->filterModules( $modules, null, $type )
543 : $modules;
544 }
545
551 public function addModules( $modules ) {
552 $this->mModules = array_merge( $this->mModules, (array)$modules );
553 }
554
562 public function getModuleScripts( $filter = false, $position = null ) {
563 return $this->getModules( $filter, null, 'mModuleScripts',
565 );
566 }
567
578 public function addModuleScripts( $modules ) {
579 $this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules );
580 }
581
589 public function getModuleStyles( $filter = false, $position = null ) {
590 return $this->getModules( $filter, null, 'mModuleStyles',
592 );
593 }
594
604 public function addModuleStyles( $modules ) {
605 $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
606 }
607
611 public function getTarget() {
612 return $this->mTarget;
613 }
614
620 public function setTarget( $target ) {
621 $this->mTarget = $target;
622 }
623
631 public function addContentOverride( LinkTarget $target, Content $content ) {
632 if ( !$this->contentOverrides ) {
633 // Register a callback for $this->contentOverrides on the first call
634 $this->addContentOverrideCallback( function ( LinkTarget $target ) {
635 $key = $target->getNamespace() . ':' . $target->getDBkey();
636 return $this->contentOverrides[$key] ?? null;
637 } );
638 }
639
640 $key = $target->getNamespace() . ':' . $target->getDBkey();
641 $this->contentOverrides[$key] = $content;
642 }
643
651 public function addContentOverrideCallback( callable $callback ) {
652 $this->contentOverrideCallbacks[] = $callback;
653 }
654
660 function getHeadItemsArray() {
661 return $this->mHeadItems;
662 }
663
676 public function addHeadItem( $name, $value ) {
677 $this->mHeadItems[$name] = $value;
678 }
679
686 public function addHeadItems( $values ) {
687 $this->mHeadItems = array_merge( $this->mHeadItems, (array)$values );
688 }
689
696 public function hasHeadItem( $name ) {
697 return isset( $this->mHeadItems[$name] );
698 }
699
706 public function addBodyClasses( $classes ) {
707 $this->mAdditionalBodyClasses = array_merge( $this->mAdditionalBodyClasses, (array)$classes );
708 }
709
717 public function setArticleBodyOnly( $only ) {
718 $this->mArticleBodyOnly = $only;
719 }
720
726 public function getArticleBodyOnly() {
727 return $this->mArticleBodyOnly;
728 }
729
737 public function setProperty( $name, $value ) {
738 $this->mProperties[$name] = $value;
739 }
740
748 public function getProperty( $name ) {
749 return $this->mProperties[$name] ?? null;
750 }
751
763 public function checkLastModified( $timestamp ) {
764 if ( !$timestamp || $timestamp == '19700101000000' ) {
765 wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
766 return false;
767 }
768 $config = $this->getConfig();
769 if ( !$config->get( 'CachePages' ) ) {
770 wfDebug( __METHOD__ . ": CACHE DISABLED\n" );
771 return false;
772 }
773
774 $timestamp = wfTimestamp( TS_MW, $timestamp );
775 $modifiedTimes = [
776 'page' => $timestamp,
777 'user' => $this->getUser()->getTouched(),
778 'epoch' => $config->get( 'CacheEpoch' )
779 ];
780 if ( $config->get( 'UseSquid' ) ) {
781 $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, $this->getCdnCacheEpoch(
782 time(),
783 $config->get( 'SquidMaxage' )
784 ) );
785 }
786 Hooks::run( 'OutputPageCheckLastModified', [ &$modifiedTimes, $this ] );
787
788 $maxModified = max( $modifiedTimes );
789 $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
790
791 $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' );
792 if ( $clientHeader === false ) {
793 wfDebug( __METHOD__ . ": client did not send If-Modified-Since header", 'private' );
794 return false;
795 }
796
797 # IE sends sizes after the date like this:
798 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
799 # this breaks strtotime().
800 $clientHeader = preg_replace( '/;.*$/', '', $clientHeader );
801
802 Wikimedia\suppressWarnings(); // E_STRICT system time warnings
803 $clientHeaderTime = strtotime( $clientHeader );
804 Wikimedia\restoreWarnings();
805 if ( !$clientHeaderTime ) {
806 wfDebug( __METHOD__
807 . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
808 return false;
809 }
810 $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
811
812 # Make debug info
813 $info = '';
814 foreach ( $modifiedTimes as $name => $value ) {
815 if ( $info !== '' ) {
816 $info .= ', ';
817 }
818 $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
819 }
820
821 wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
822 wfTimestamp( TS_ISO_8601, $clientHeaderTime ), 'private' );
823 wfDebug( __METHOD__ . ": effective Last-Modified: " .
824 wfTimestamp( TS_ISO_8601, $maxModified ), 'private' );
825 if ( $clientHeaderTime < $maxModified ) {
826 wfDebug( __METHOD__ . ": STALE, $info", 'private' );
827 return false;
828 }
829
830 # Not modified
831 # Give a 304 Not Modified response code and disable body output
832 wfDebug( __METHOD__ . ": NOT MODIFIED, $info", 'private' );
833 ini_set( 'zlib.output_compression', 0 );
834 $this->getRequest()->response()->statusHeader( 304 );
835 $this->sendCacheControl();
836 $this->disable();
837
838 // Don't output a compressed blob when using ob_gzhandler;
839 // it's technically against HTTP spec and seems to confuse
840 // Firefox when the response gets split over two packets.
842
843 return true;
844 }
845
851 private function getCdnCacheEpoch( $reqTime, $maxAge ) {
852 // Ensure Last-Modified is never more than (wgSquidMaxage) in the past,
853 // because even if the wiki page content hasn't changed since, static
854 // resources may have changed (skin HTML, interface messages, urls, etc.)
855 // and must roll-over in a timely manner (T46570)
856 return $reqTime - $maxAge;
857 }
858
865 public function setLastModified( $timestamp ) {
866 $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp );
867 }
868
877 public function setRobotPolicy( $policy ) {
878 $policy = Article::formatRobotPolicy( $policy );
879
880 if ( isset( $policy['index'] ) ) {
881 $this->setIndexPolicy( $policy['index'] );
882 }
883 if ( isset( $policy['follow'] ) ) {
884 $this->setFollowPolicy( $policy['follow'] );
885 }
886 }
887
895 public function setIndexPolicy( $policy ) {
896 $policy = trim( $policy );
897 if ( in_array( $policy, [ 'index', 'noindex' ] ) ) {
898 $this->mIndexPolicy = $policy;
899 }
900 }
901
909 public function setFollowPolicy( $policy ) {
910 $policy = trim( $policy );
911 if ( in_array( $policy, [ 'follow', 'nofollow' ] ) ) {
912 $this->mFollowPolicy = $policy;
913 }
914 }
915
922 public function setHTMLTitle( $name ) {
923 if ( $name instanceof Message ) {
924 $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
925 } else {
926 $this->mHTMLtitle = $name;
927 }
928 }
929
935 public function getHTMLTitle() {
936 return $this->mHTMLtitle;
937 }
938
944 public function setRedirectedFrom( $t ) {
945 $this->mRedirectedFrom = $t;
946 }
947
958 public function setPageTitle( $name ) {
959 if ( $name instanceof Message ) {
960 $name = $name->setContext( $this->getContext() )->text();
961 }
962
963 # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
964 # but leave "<i>foobar</i>" alone
965 $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) );
966 $this->mPageTitle = $nameWithTags;
967
968 # change "<i>foo&amp;bar</i>" to "foo&bar"
969 $this->setHTMLTitle(
970 $this->msg( 'pagetitle' )->rawParams( Sanitizer::stripAllTags( $nameWithTags ) )
971 ->inContentLanguage()
972 );
973 }
974
980 public function getPageTitle() {
981 return $this->mPageTitle;
982 }
983
991 public function setDisplayTitle( $html ) {
992 $this->displayTitle = $html;
993 }
994
1003 public function getDisplayTitle() {
1004 $html = $this->displayTitle;
1005 if ( $html === null ) {
1006 $html = $this->getTitle()->getPrefixedText();
1007 }
1008
1009 return Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $html ) );
1010 }
1011
1018 public function getUnprefixedDisplayTitle() {
1019 $text = $this->getDisplayTitle();
1020 $nsPrefix = $this->getTitle()->getNsText() . ':';
1021 $prefix = preg_quote( $nsPrefix, '/' );
1022
1023 return preg_replace( "/^$prefix/i", '', $text );
1024 }
1025
1031 public function setTitle( Title $t ) {
1032 $this->getContext()->setTitle( $t );
1033 }
1034
1040 public function setSubtitle( $str ) {
1041 $this->clearSubtitle();
1042 $this->addSubtitle( $str );
1043 }
1044
1050 public function addSubtitle( $str ) {
1051 if ( $str instanceof Message ) {
1052 $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
1053 } else {
1054 $this->mSubtitle[] = $str;
1055 }
1056 }
1057
1066 public static function buildBacklinkSubtitle( Title $title, $query = [] ) {
1067 if ( $title->isRedirect() ) {
1068 $query['redirect'] = 'no';
1069 }
1070 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1071 return wfMessage( 'backlinksubtitle' )
1072 ->rawParams( $linkRenderer->makeLink( $title, null, [], $query ) );
1073 }
1074
1081 public function addBacklinkSubtitle( Title $title, $query = [] ) {
1082 $this->addSubtitle( self::buildBacklinkSubtitle( $title, $query ) );
1083 }
1084
1088 public function clearSubtitle() {
1089 $this->mSubtitle = [];
1090 }
1091
1097 public function getSubtitle() {
1098 return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
1099 }
1100
1105 public function setPrintable() {
1106 $this->mPrintable = true;
1107 }
1108
1114 public function isPrintable() {
1115 return $this->mPrintable;
1116 }
1117
1121 public function disable() {
1122 $this->mDoNothing = true;
1123 }
1124
1130 public function isDisabled() {
1131 return $this->mDoNothing;
1132 }
1133
1139 public function showNewSectionLink() {
1140 return $this->mNewSectionLink;
1141 }
1142
1148 public function forceHideNewSectionLink() {
1149 return $this->mHideNewSectionLink;
1150 }
1151
1160 public function setSyndicated( $show = true ) {
1161 if ( $show ) {
1162 $this->setFeedAppendQuery( false );
1163 } else {
1164 $this->mFeedLinks = [];
1165 }
1166 }
1167
1177 public function setFeedAppendQuery( $val ) {
1178 $this->mFeedLinks = [];
1179
1180 foreach ( $this->getConfig()->get( 'AdvertisedFeedTypes' ) as $type ) {
1181 $query = "feed=$type";
1182 if ( is_string( $val ) ) {
1183 $query .= '&' . $val;
1184 }
1185 $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
1186 }
1187 }
1188
1195 public function addFeedLink( $format, $href ) {
1196 if ( in_array( $format, $this->getConfig()->get( 'AdvertisedFeedTypes' ) ) ) {
1197 $this->mFeedLinks[$format] = $href;
1198 }
1199 }
1200
1205 public function isSyndicated() {
1206 return count( $this->mFeedLinks ) > 0;
1207 }
1208
1213 public function getSyndicationLinks() {
1214 return $this->mFeedLinks;
1215 }
1216
1222 public function getFeedAppendQuery() {
1223 return $this->mFeedLinksAppendQuery;
1224 }
1225
1233 public function setArticleFlag( $newVal ) {
1234 $this->mIsArticle = $newVal;
1235 if ( $newVal ) {
1236 $this->mIsArticleRelated = $newVal;
1237 }
1238 }
1239
1246 public function isArticle() {
1247 return $this->mIsArticle;
1248 }
1249
1256 public function setArticleRelated( $newVal ) {
1257 $this->mIsArticleRelated = $newVal;
1258 if ( !$newVal ) {
1259 $this->mIsArticle = false;
1260 }
1261 }
1262
1268 public function isArticleRelated() {
1269 return $this->mIsArticleRelated;
1270 }
1271
1277 public function setCopyright( $hasCopyright ) {
1278 $this->mHasCopyright = $hasCopyright;
1279 }
1280
1290 public function showsCopyright() {
1291 return $this->isArticle() || $this->mHasCopyright;
1292 }
1293
1300 public function addLanguageLinks( array $newLinkArray ) {
1301 $this->mLanguageLinks = array_merge( $this->mLanguageLinks, $newLinkArray );
1302 }
1303
1310 public function setLanguageLinks( array $newLinkArray ) {
1311 $this->mLanguageLinks = $newLinkArray;
1312 }
1313
1319 public function getLanguageLinks() {
1320 return $this->mLanguageLinks;
1321 }
1322
1328 public function addCategoryLinks( array $categories ) {
1329 if ( !$categories ) {
1330 return;
1331 }
1332
1333 $res = $this->addCategoryLinksToLBAndGetResult( $categories );
1334
1335 # Set all the values to 'normal'.
1336 $categories = array_fill_keys( array_keys( $categories ), 'normal' );
1337
1338 # Mark hidden categories
1339 foreach ( $res as $row ) {
1340 if ( isset( $row->pp_value ) ) {
1341 $categories[$row->page_title] = 'hidden';
1342 }
1343 }
1344
1345 // Avoid PHP 7.1 warning of passing $this by reference
1346 $outputPage = $this;
1347 # Add the remaining categories to the skin
1348 if ( Hooks::run(
1349 'OutputPageMakeCategoryLinks',
1350 [ &$outputPage, $categories, &$this->mCategoryLinks ] )
1351 ) {
1352 $services = MediaWikiServices::getInstance();
1353 $linkRenderer = $services->getLinkRenderer();
1354 foreach ( $categories as $category => $type ) {
1355 // array keys will cast numeric category names to ints, so cast back to string
1356 $category = (string)$category;
1357 $origcategory = $category;
1358 $title = Title::makeTitleSafe( NS_CATEGORY, $category );
1359 if ( !$title ) {
1360 continue;
1361 }
1362 $services->getContentLanguage()->findVariantLink( $category, $title, true );
1363 if ( $category != $origcategory && array_key_exists( $category, $categories ) ) {
1364 continue;
1365 }
1366 $text = $services->getContentLanguage()->convertHtml( $title->getText() );
1367 $this->mCategories[$type][] = $title->getText();
1368 $this->mCategoryLinks[$type][] = $linkRenderer->makeLink( $title, new HtmlArmor( $text ) );
1369 }
1370 }
1371 }
1372
1377 protected function addCategoryLinksToLBAndGetResult( array $categories ) {
1378 # Add the links to a LinkBatch
1379 $arr = [ NS_CATEGORY => $categories ];
1380 $lb = new LinkBatch;
1381 $lb->setArray( $arr );
1382
1383 # Fetch existence plus the hiddencat property
1384 $dbr = wfGetDB( DB_REPLICA );
1385 $fields = array_merge(
1386 LinkCache::getSelectFields(),
1387 [ 'page_namespace', 'page_title', 'pp_value' ]
1388 );
1389
1390 $res = $dbr->select( [ 'page', 'page_props' ],
1391 $fields,
1392 $lb->constructSet( 'page', $dbr ),
1393 __METHOD__,
1394 [],
1395 [ 'page_props' => [ 'LEFT JOIN', [
1396 'pp_propname' => 'hiddencat',
1397 'pp_page = page_id'
1398 ] ] ]
1399 );
1400
1401 # Add the results to the link cache
1402 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1403 $lb->addResultToCache( $linkCache, $res );
1404
1405 return $res;
1406 }
1407
1413 public function setCategoryLinks( array $categories ) {
1414 $this->mCategoryLinks = [];
1415 $this->addCategoryLinks( $categories );
1416 }
1417
1426 public function getCategoryLinks() {
1427 return $this->mCategoryLinks;
1428 }
1429
1439 public function getCategories( $type = 'all' ) {
1440 if ( $type === 'all' ) {
1441 $allCategories = [];
1442 foreach ( $this->mCategories as $categories ) {
1443 $allCategories = array_merge( $allCategories, $categories );
1444 }
1445 return $allCategories;
1446 }
1447 if ( !isset( $this->mCategories[$type] ) ) {
1448 throw new InvalidArgumentException( 'Invalid category type given: ' . $type );
1449 }
1450 return $this->mCategories[$type];
1451 }
1452
1462 public function setIndicators( array $indicators ) {
1463 $this->mIndicators = $indicators + $this->mIndicators;
1464 // Keep ordered by key
1465 ksort( $this->mIndicators );
1466 }
1467
1476 public function getIndicators() {
1477 return $this->mIndicators;
1478 }
1479
1488 public function addHelpLink( $to, $overrideBaseUrl = false ) {
1489 $this->addModuleStyles( 'mediawiki.helplink' );
1490 $text = $this->msg( 'helppage-top-gethelp' )->escaped();
1491
1492 if ( $overrideBaseUrl ) {
1493 $helpUrl = $to;
1494 } else {
1495 $toUrlencoded = wfUrlencode( str_replace( ' ', '_', $to ) );
1496 $helpUrl = "//www.mediawiki.org/wiki/Special:MyLanguage/$toUrlencoded";
1497 }
1498
1499 $link = Html::rawElement(
1500 'a',
1501 [
1502 'href' => $helpUrl,
1503 'target' => '_blank',
1504 'class' => 'mw-helplink',
1505 ],
1506 $text
1507 );
1508
1509 $this->setIndicators( [ 'mw-helplink' => $link ] );
1510 }
1511
1520 public function disallowUserJs() {
1521 $this->reduceAllowedModules(
1524 );
1525
1526 // Site-wide styles are controlled by a config setting, see T73621
1527 // for background on why. User styles are never allowed.
1528 if ( $this->getConfig()->get( 'AllowSiteCSSOnRestrictedPages' ) ) {
1530 } else {
1532 }
1533 $this->reduceAllowedModules(
1535 $styleOrigin
1536 );
1537 }
1538
1545 public function getAllowedModules( $type ) {
1547 return min( array_values( $this->mAllowedModules ) );
1548 } else {
1549 return $this->mAllowedModules[$type] ?? ResourceLoaderModule::ORIGIN_ALL;
1550 }
1551 }
1552
1562 public function reduceAllowedModules( $type, $level ) {
1563 $this->mAllowedModules[$type] = min( $this->getAllowedModules( $type ), $level );
1564 }
1565
1571 public function prependHTML( $text ) {
1572 $this->mBodytext = $text . $this->mBodytext;
1573 }
1574
1580 public function addHTML( $text ) {
1581 $this->mBodytext .= $text;
1582 }
1583
1593 public function addElement( $element, array $attribs = [], $contents = '' ) {
1594 $this->addHTML( Html::element( $element, $attribs, $contents ) );
1595 }
1596
1600 public function clearHTML() {
1601 $this->mBodytext = '';
1602 }
1603
1609 public function getHTML() {
1610 return $this->mBodytext;
1611 }
1612
1620 public function parserOptions( $options = null ) {
1621 if ( $options !== null ) {
1622 wfDeprecated( __METHOD__ . ' with non-null $options', '1.31' );
1623 }
1624
1625 if ( $options !== null && !empty( $options->isBogus ) ) {
1626 // Someone is trying to set a bogus pre-$wgUser PO. Check if it has
1627 // been changed somehow, and keep it if so.
1628 $anonPO = ParserOptions::newFromAnon();
1629 $anonPO->setAllowUnsafeRawHtml( false );
1630 if ( !$options->matches( $anonPO ) ) {
1631 wfLogWarning( __METHOD__ . ': Setting a changed bogus ParserOptions: ' . wfGetAllCallers( 5 ) );
1632 $options->isBogus = false;
1633 }
1634 }
1635
1636 if ( !$this->mParserOptions ) {
1637 if ( !$this->getContext()->getUser()->isSafeToLoad() ) {
1638 // $wgUser isn't unstubbable yet, so don't try to get a
1639 // ParserOptions for it. And don't cache this ParserOptions
1640 // either.
1641 $po = ParserOptions::newFromAnon();
1642 $po->setAllowUnsafeRawHtml( false );
1643 $po->isBogus = true;
1644 if ( $options !== null ) {
1645 $this->mParserOptions = empty( $options->isBogus ) ? $options : null;
1646 }
1647 return $po;
1648 }
1649
1650 $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
1651 $this->mParserOptions->setAllowUnsafeRawHtml( false );
1652 }
1653
1654 if ( $options !== null && !empty( $options->isBogus ) ) {
1655 // They're trying to restore the bogus pre-$wgUser PO. Do the right
1656 // thing.
1657 return wfSetVar( $this->mParserOptions, null, true );
1658 } else {
1659 return wfSetVar( $this->mParserOptions, $options );
1660 }
1661 }
1662
1670 public function setRevisionId( $revid ) {
1671 $val = is_null( $revid ) ? null : intval( $revid );
1672 return wfSetVar( $this->mRevisionId, $val, true );
1673 }
1674
1680 public function getRevisionId() {
1681 return $this->mRevisionId;
1682 }
1683
1691 public function setRevisionTimestamp( $timestamp ) {
1692 return wfSetVar( $this->mRevisionTimestamp, $timestamp, true );
1693 }
1694
1701 public function getRevisionTimestamp() {
1702 return $this->mRevisionTimestamp;
1703 }
1704
1711 public function setFileVersion( $file ) {
1712 $val = null;
1713 if ( $file instanceof File && $file->exists() ) {
1714 $val = [ 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ];
1715 }
1716 return wfSetVar( $this->mFileVersion, $val, true );
1717 }
1718
1724 public function getFileVersion() {
1725 return $this->mFileVersion;
1726 }
1727
1734 public function getTemplateIds() {
1735 return $this->mTemplateIds;
1736 }
1737
1744 public function getFileSearchOptions() {
1745 return $this->mImageTimeKeys;
1746 }
1747
1760 public function addWikiText( $text, $linestart = true, $interface = true ) {
1761 $title = $this->getTitle();
1762 if ( !$title ) {
1763 throw new MWException( 'Title is null' );
1764 }
1765 $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/false, $interface );
1766 }
1767
1784 public function addWikiTextAsInterface(
1785 $text, $linestart = true, Title $title = null
1786 ) {
1787 if ( $title === null ) {
1788 $title = $this->getTitle();
1789 }
1790 if ( !$title ) {
1791 throw new MWException( 'Title is null' );
1792 }
1793 $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/true, /*interface*/true );
1794 }
1795
1810 $wrapperClass, $text
1811 ) {
1813 $text, $this->getTitle(),
1814 /*linestart*/true, /*tidy*/true, /*interface*/true,
1815 $wrapperClass
1816 );
1817 }
1818
1834 public function addWikiTextAsContent(
1835 $text, $linestart = true, Title $title = null
1836 ) {
1837 if ( $title === null ) {
1838 $title = $this->getTitle();
1839 }
1840 if ( !$title ) {
1841 throw new MWException( 'Title is null' );
1842 }
1843 $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/true, /*interface*/false );
1844 }
1845
1855 public function addWikiTextWithTitle( $text, Title $title, $linestart = true ) {
1856 wfDeprecated( __METHOD__, '1.32' );
1857 $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/false, /*interface*/false );
1858 }
1859
1870 function addWikiTextTitleTidy( $text, Title $title, $linestart = true ) {
1871 wfDeprecated( __METHOD__, '1.32' );
1872 $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/true, /*interface*/false );
1873 }
1874
1883 public function addWikiTextTidy( $text, $linestart = true ) {
1884 wfDeprecated( __METHOD__, '1.32' );
1885 $title = $this->getTitle();
1886 if ( !$title ) {
1887 throw new MWException( 'Title is null' );
1888 }
1889 $this->addWikiTextTitleInternal( $text, $title, $linestart, /*tidy*/true, /*interface*/false );
1890 }
1891
1911 public function addWikiTextTitle( $text, Title $title, $linestart,
1912 $tidy = false, $interface = false
1913 ) {
1914 wfDeprecated( __METHOD__, '1.32' );
1915 return $this->addWikiTextTitleInternal( $text, $title, $linestart, $tidy, $interface );
1916 }
1917
1935 $text, Title $title, $linestart, $tidy, $interface, $wrapperClass = null
1936 ) {
1937 $parserOutput = $this->parseInternal(
1938 $text, $title, $linestart, $tidy, $interface, /*language*/null
1939 );
1940
1941 $this->addParserOutput( $parserOutput, [
1942 'enableSectionEditLinks' => false,
1943 'wrapperDivClass' => $wrapperClass ?? '',
1944 ] );
1945 }
1946
1955 public function addParserOutputMetadata( ParserOutput $parserOutput ) {
1956 $this->mLanguageLinks =
1957 array_merge( $this->mLanguageLinks, $parserOutput->getLanguageLinks() );
1958 $this->addCategoryLinks( $parserOutput->getCategories() );
1959 $this->setIndicators( $parserOutput->getIndicators() );
1960 $this->mNewSectionLink = $parserOutput->getNewSection();
1961 $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
1962
1963 if ( !$parserOutput->isCacheable() ) {
1964 $this->enableClientCache( false );
1965 }
1966 $this->mNoGallery = $parserOutput->getNoGallery();
1967 $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
1968 $this->addModules( $parserOutput->getModules() );
1969 $this->addModuleScripts( $parserOutput->getModuleScripts() );
1970 $this->addModuleStyles( $parserOutput->getModuleStyles() );
1971 $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1972 $this->mPreventClickjacking = $this->mPreventClickjacking
1973 || $parserOutput->preventClickjacking();
1974
1975 // Template versioning...
1976 foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
1977 if ( isset( $this->mTemplateIds[$ns] ) ) {
1978 $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
1979 } else {
1980 $this->mTemplateIds[$ns] = $dbks;
1981 }
1982 }
1983 // File versioning...
1984 foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) {
1985 $this->mImageTimeKeys[$dbk] = $data;
1986 }
1987
1988 // Hooks registered in the object
1989 $parserOutputHooks = $this->getConfig()->get( 'ParserOutputHooks' );
1990 foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
1991 list( $hookName, $data ) = $hookInfo;
1992 if ( isset( $parserOutputHooks[$hookName] ) ) {
1993 $parserOutputHooks[$hookName]( $this, $parserOutput, $data );
1994 }
1995 }
1996
1997 // Enable OOUI if requested via ParserOutput
1998 if ( $parserOutput->getEnableOOUI() ) {
1999 $this->enableOOUI();
2000 }
2001
2002 // Include parser limit report
2003 if ( !$this->limitReportJSData ) {
2004 $this->limitReportJSData = $parserOutput->getLimitReportJSData();
2005 }
2006
2007 // Link flags are ignored for now, but may in the future be
2008 // used to mark individual language links.
2009 $linkFlags = [];
2010 // Avoid PHP 7.1 warning of passing $this by reference
2011 $outputPage = $this;
2012 Hooks::run( 'LanguageLinks', [ $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ] );
2013 Hooks::runWithoutAbort( 'OutputPageParserOutput', [ &$outputPage, $parserOutput ] );
2014
2015 // This check must be after 'OutputPageParserOutput' runs in addParserOutputMetadata
2016 // so that extensions may modify ParserOutput to toggle TOC.
2017 // This cannot be moved to addParserOutputText because that is not
2018 // called by EditPage for Preview.
2019 if ( $parserOutput->getTOCHTML() ) {
2020 $this->mEnableTOC = true;
2021 }
2022 }
2023
2032 public function addParserOutputContent( ParserOutput $parserOutput, $poOptions = [] ) {
2033 $this->addParserOutputText( $parserOutput, $poOptions );
2034
2035 $this->addModules( $parserOutput->getModules() );
2036 $this->addModuleScripts( $parserOutput->getModuleScripts() );
2037 $this->addModuleStyles( $parserOutput->getModuleStyles() );
2038
2039 $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
2040 }
2041
2049 public function addParserOutputText( ParserOutput $parserOutput, $poOptions = [] ) {
2050 $text = $parserOutput->getText( $poOptions );
2051 // Avoid PHP 7.1 warning of passing $this by reference
2052 $outputPage = $this;
2053 Hooks::runWithoutAbort( 'OutputPageBeforeHTML', [ &$outputPage, &$text ] );
2054 $this->addHTML( $text );
2055 }
2056
2063 function addParserOutput( ParserOutput $parserOutput, $poOptions = [] ) {
2064 $this->addParserOutputMetadata( $parserOutput );
2065 $this->addParserOutputText( $parserOutput, $poOptions );
2066 }
2067
2073 public function addTemplate( &$template ) {
2074 $this->addHTML( $template->getHTML() );
2075 }
2076
2095 public function parse( $text, $linestart = true, $interface = false, $language = null ) {
2096 return $this->parseInternal(
2097 $text, $this->getTitle(), $linestart, /*tidy*/false, $interface, $language
2098 )->getText( [
2099 'enableSectionEditLinks' => false,
2100 ] );
2101 }
2102
2114 public function parseAsContent( $text, $linestart = true ) {
2115 return $this->parseInternal(
2116 $text, $this->getTitle(), $linestart, /*tidy*/true, /*interface*/false, /*language*/null
2117 )->getText( [
2118 'enableSectionEditLinks' => false,
2119 'wrapperDivClass' => ''
2120 ] );
2121 }
2122
2135 public function parseAsInterface( $text, $linestart = true ) {
2136 return $this->parseInternal(
2137 $text, $this->getTitle(), $linestart, /*tidy*/true, /*interface*/true, /*language*/null
2138 )->getText( [
2139 'enableSectionEditLinks' => false,
2140 'wrapperDivClass' => ''
2141 ] );
2142 }
2143
2158 public function parseInlineAsInterface( $text, $linestart = true ) {
2159 return Parser::stripOuterParagraph(
2160 $this->parseAsInterface( $text, $linestart )
2161 );
2162 }
2163
2177 public function parseInline( $text, $linestart = true, $interface = false ) {
2178 $parsed = $this->parseInternal(
2179 $text, $this->getTitle(), $linestart, /*tidy*/false, $interface, /*language*/null
2180 )->getText( [
2181 'enableSectionEditLinks' => false,
2182 'wrapperDivClass' => '', /* no wrapper div */
2183 ] );
2184 return Parser::stripOuterParagraph( $parsed );
2185 }
2186
2201 private function parseInternal( $text, $title, $linestart, $tidy, $interface, $language ) {
2202 global $wgParser;
2203
2204 if ( is_null( $title ) ) {
2205 throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
2206 }
2207
2208 $popts = $this->parserOptions();
2209 $oldTidy = $popts->setTidy( $tidy );
2210 $oldInterface = $popts->setInterfaceMessage( (bool)$interface );
2211
2212 if ( $language !== null ) {
2213 $oldLang = $popts->setTargetLanguage( $language );
2214 }
2215
2216 $parserOutput = $wgParser->getFreshParser()->parse(
2217 $text, $title, $popts,
2218 $linestart, true, $this->mRevisionId
2219 );
2220
2221 $popts->setTidy( $oldTidy );
2222 $popts->setInterfaceMessage( $oldInterface );
2223
2224 if ( $language !== null ) {
2225 $popts->setTargetLanguage( $oldLang );
2226 }
2227
2228 return $parserOutput;
2229 }
2230
2236 public function setCdnMaxage( $maxage ) {
2237 $this->mCdnMaxage = min( $maxage, $this->mCdnMaxageLimit );
2238 }
2239
2249 public function lowerCdnMaxage( $maxage ) {
2250 $this->mCdnMaxageLimit = min( $maxage, $this->mCdnMaxageLimit );
2251 $this->setCdnMaxage( $this->mCdnMaxage );
2252 }
2253
2266 public function adaptCdnTTL( $mtime, $minTTL = 0, $maxTTL = 0 ) {
2267 $minTTL = $minTTL ?: IExpiringStore::TTL_MINUTE;
2268 $maxTTL = $maxTTL ?: $this->getConfig()->get( 'SquidMaxage' );
2269
2270 if ( $mtime === null || $mtime === false ) {
2271 return $minTTL; // entity does not exist
2272 }
2273
2274 $age = MWTimestamp::time() - wfTimestamp( TS_UNIX, $mtime );
2275 $adaptiveTTL = max( 0.9 * $age, $minTTL );
2276 $adaptiveTTL = min( $adaptiveTTL, $maxTTL );
2277
2278 $this->lowerCdnMaxage( (int)$adaptiveTTL );
2279 }
2280
2288 public function enableClientCache( $state ) {
2289 return wfSetVar( $this->mEnableClientCache, $state );
2290 }
2291
2298 if ( self::$cacheVaryCookies === null ) {
2299 $config = $this->getConfig();
2300 self::$cacheVaryCookies = array_values( array_unique( array_merge(
2301 SessionManager::singleton()->getVaryCookies(),
2302 [
2303 'forceHTTPS',
2304 ],
2305 $config->get( 'CacheVaryCookies' )
2306 ) ) );
2307 Hooks::run( 'GetCacheVaryCookies', [ $this, &self::$cacheVaryCookies ] );
2308 }
2309 return self::$cacheVaryCookies;
2310 }
2311
2319 $request = $this->getRequest();
2320 foreach ( $this->getCacheVaryCookies() as $cookieName ) {
2321 if ( $request->getCookie( $cookieName, '', '' ) !== '' ) {
2322 wfDebug( __METHOD__ . ": found $cookieName\n" );
2323 return true;
2324 }
2325 }
2326 wfDebug( __METHOD__ . ": no cache-varying cookies found\n" );
2327 return false;
2328 }
2329
2338 public function addVaryHeader( $header, array $option = null ) {
2339 if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
2340 $this->mVaryHeader[$header] = [];
2341 }
2342 if ( !is_array( $option ) ) {
2343 $option = [];
2344 }
2345 $this->mVaryHeader[$header] =
2346 array_unique( array_merge( $this->mVaryHeader[$header], $option ) );
2347 }
2348
2355 public function getVaryHeader() {
2356 // If we vary on cookies, let's make sure it's always included here too.
2357 if ( $this->getCacheVaryCookies() ) {
2358 $this->addVaryHeader( 'Cookie' );
2359 }
2360
2361 foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2362 $this->addVaryHeader( $header, $options );
2363 }
2364 return 'Vary: ' . implode( ', ', array_keys( $this->mVaryHeader ) );
2365 }
2366
2372 public function addLinkHeader( $header ) {
2373 $this->mLinkHeader[] = $header;
2374 }
2375
2381 public function getLinkHeader() {
2382 if ( !$this->mLinkHeader ) {
2383 return false;
2384 }
2385
2386 return 'Link: ' . implode( ',', $this->mLinkHeader );
2387 }
2388
2396 public function getKeyHeader() {
2397 wfDeprecated( '$wgUseKeyHeader', '1.32' );
2398
2399 $cvCookies = $this->getCacheVaryCookies();
2400
2401 $cookiesOption = [];
2402 foreach ( $cvCookies as $cookieName ) {
2403 $cookiesOption[] = 'param=' . $cookieName;
2404 }
2405 $this->addVaryHeader( 'Cookie', $cookiesOption );
2406
2407 foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2408 $this->addVaryHeader( $header, $options );
2409 }
2410
2411 $headers = [];
2412 foreach ( $this->mVaryHeader as $header => $option ) {
2413 $newheader = $header;
2414 if ( is_array( $option ) && count( $option ) > 0 ) {
2415 $newheader .= ';' . implode( ';', $option );
2416 }
2417 $headers[] = $newheader;
2418 }
2419 $key = 'Key: ' . implode( ',', $headers );
2420
2421 return $key;
2422 }
2423
2431 private function addAcceptLanguage() {
2432 $title = $this->getTitle();
2433 if ( !$title instanceof Title ) {
2434 return;
2435 }
2436
2437 $lang = $title->getPageLanguage();
2438 if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
2439 $variants = $lang->getVariants();
2440 $aloption = [];
2441 foreach ( $variants as $variant ) {
2442 if ( $variant === $lang->getCode() ) {
2443 continue;
2444 }
2445
2446 // XXX Note that this code is not strictly correct: we
2447 // do a case-insensitive match in
2448 // LanguageConverter::getHeaderVariant() while the
2449 // (abandoned, draft) spec for the `Key` header only
2450 // allows case-sensitive matches. To match the logic
2451 // in LanguageConverter::getHeaderVariant() we should
2452 // also be looking at fallback variants and deprecated
2453 // mediawiki-internal codes, as well as BCP 47
2454 // normalized forms.
2455
2456 $aloption[] = "substr=$variant";
2457
2458 // IE and some other browsers use BCP 47 standards in their Accept-Language header,
2459 // like "zh-CN" or "zh-Hant". We should handle these too.
2460 $variantBCP47 = LanguageCode::bcp47( $variant );
2461 if ( $variantBCP47 !== $variant ) {
2462 $aloption[] = "substr=$variantBCP47";
2463 }
2464 }
2465 $this->addVaryHeader( 'Accept-Language', $aloption );
2466 }
2467 }
2468
2479 public function preventClickjacking( $enable = true ) {
2480 $this->mPreventClickjacking = $enable;
2481 }
2482
2488 public function allowClickjacking() {
2489 $this->mPreventClickjacking = false;
2490 }
2491
2498 public function getPreventClickjacking() {
2499 return $this->mPreventClickjacking;
2500 }
2501
2509 public function getFrameOptions() {
2510 $config = $this->getConfig();
2511 if ( $config->get( 'BreakFrames' ) ) {
2512 return 'DENY';
2513 } elseif ( $this->mPreventClickjacking && $config->get( 'EditPageFrameOptions' ) ) {
2514 return $config->get( 'EditPageFrameOptions' );
2515 }
2516 return false;
2517 }
2518
2522 public function sendCacheControl() {
2523 $response = $this->getRequest()->response();
2524 $config = $this->getConfig();
2525
2526 $this->addVaryHeader( 'Cookie' );
2527 $this->addAcceptLanguage();
2528
2529 # don't serve compressed data to clients who can't handle it
2530 # maintain different caches for logged-in users and non-logged in ones
2531 $response->header( $this->getVaryHeader() );
2532
2533 if ( $config->get( 'UseKeyHeader' ) ) {
2534 $response->header( $this->getKeyHeader() );
2535 }
2536
2537 if ( $this->mEnableClientCache ) {
2538 if (
2539 $config->get( 'UseSquid' ) &&
2540 !$response->hasCookies() &&
2541 !SessionManager::getGlobalSession()->isPersistent() &&
2542 !$this->isPrintable() &&
2543 $this->mCdnMaxage != 0 &&
2544 !$this->haveCacheVaryCookies()
2545 ) {
2546 if ( $config->get( 'UseESI' ) ) {
2547 # We'll purge the proxy cache explicitly, but require end user agents
2548 # to revalidate against the proxy on each visit.
2549 # Surrogate-Control controls our CDN, Cache-Control downstream caches
2550 wfDebug( __METHOD__ .
2551 ": proxy caching with ESI; {$this->mLastModified} **", 'private' );
2552 # start with a shorter timeout for initial testing
2553 # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
2554 $response->header(
2555 "Surrogate-Control: max-age={$config->get( 'SquidMaxage' )}" .
2556 "+{$this->mCdnMaxage}, content=\"ESI/1.0\""
2557 );
2558 $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
2559 } else {
2560 # We'll purge the proxy cache for anons explicitly, but require end user agents
2561 # to revalidate against the proxy on each visit.
2562 # IMPORTANT! The CDN needs to replace the Cache-Control header with
2563 # Cache-Control: s-maxage=0, must-revalidate, max-age=0
2564 wfDebug( __METHOD__ .
2565 ": local proxy caching; {$this->mLastModified} **", 'private' );
2566 # start with a shorter timeout for initial testing
2567 # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
2568 $response->header( "Cache-Control: " .
2569 "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" );
2570 }
2571 } else {
2572 # We do want clients to cache if they can, but they *must* check for updates
2573 # on revisiting the page.
2574 wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **", 'private' );
2575 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2576 $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
2577 }
2578 if ( $this->mLastModified ) {
2579 $response->header( "Last-Modified: {$this->mLastModified}" );
2580 }
2581 } else {
2582 wfDebug( __METHOD__ . ": no caching **", 'private' );
2583
2584 # In general, the absence of a last modified header should be enough to prevent
2585 # the client from using its cache. We send a few other things just to make sure.
2586 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2587 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
2588 $response->header( 'Pragma: no-cache' );
2589 }
2590 }
2591
2597 public function loadSkinModules( $sk ) {
2598 foreach ( $sk->getDefaultModules() as $group => $modules ) {
2599 if ( $group === 'styles' ) {
2600 foreach ( $modules as $key => $moduleMembers ) {
2601 $this->addModuleStyles( $moduleMembers );
2602 }
2603 } else {
2604 $this->addModules( $modules );
2605 }
2606 }
2607 }
2608
2619 public function output( $return = false ) {
2620 if ( $this->mDoNothing ) {
2621 return $return ? '' : null;
2622 }
2623
2624 $response = $this->getRequest()->response();
2625 $config = $this->getConfig();
2626
2627 if ( $this->mRedirect != '' ) {
2628 # Standards require redirect URLs to be absolute
2629 $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT );
2630
2631 $redirect = $this->mRedirect;
2632 $code = $this->mRedirectCode;
2633
2634 if ( Hooks::run( "BeforePageRedirect", [ $this, &$redirect, &$code ] ) ) {
2635 if ( $code == '301' || $code == '303' ) {
2636 if ( !$config->get( 'DebugRedirects' ) ) {
2637 $response->statusHeader( $code );
2638 }
2639 $this->mLastModified = wfTimestamp( TS_RFC2822 );
2640 }
2641 if ( $config->get( 'VaryOnXFP' ) ) {
2642 $this->addVaryHeader( 'X-Forwarded-Proto' );
2643 }
2644 $this->sendCacheControl();
2645
2646 $response->header( "Content-Type: text/html; charset=utf-8" );
2647 if ( $config->get( 'DebugRedirects' ) ) {
2648 $url = htmlspecialchars( $redirect );
2649 print "<!DOCTYPE html>\n<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
2650 print "<p>Location: <a href=\"$url\">$url</a></p>\n";
2651 print "</body>\n</html>\n";
2652 } else {
2653 $response->header( 'Location: ' . $redirect );
2654 }
2655 }
2656
2657 return $return ? '' : null;
2658 } elseif ( $this->mStatusCode ) {
2659 $response->statusHeader( $this->mStatusCode );
2660 }
2661
2662 # Buffer output; final headers may depend on later processing
2663 ob_start();
2664
2665 $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
2666 $response->header( 'Content-language: ' .
2667 MediaWikiServices::getInstance()->getContentLanguage()->getHtmlCode() );
2668
2669 if ( !$this->mArticleBodyOnly ) {
2670 $sk = $this->getSkin();
2671 }
2672
2673 $linkHeader = $this->getLinkHeader();
2674 if ( $linkHeader ) {
2675 $response->header( $linkHeader );
2676 }
2677
2678 // Prevent framing, if requested
2679 $frameOptions = $this->getFrameOptions();
2680 if ( $frameOptions ) {
2681 $response->header( "X-Frame-Options: $frameOptions" );
2682 }
2683
2685
2686 if ( $this->mArticleBodyOnly ) {
2687 echo $this->mBodytext;
2688 } else {
2689 // Enable safe mode if requested (T152169)
2690 if ( $this->getRequest()->getBool( 'safemode' ) ) {
2691 $this->disallowUserJs();
2692 }
2693
2694 $sk = $this->getSkin();
2695 $this->loadSkinModules( $sk );
2696
2697 MWDebug::addModules( $this );
2698
2699 // Avoid PHP 7.1 warning of passing $this by reference
2700 $outputPage = $this;
2701 // Hook that allows last minute changes to the output page, e.g.
2702 // adding of CSS or Javascript by extensions.
2703 Hooks::runWithoutAbort( 'BeforePageDisplay', [ &$outputPage, &$sk ] );
2704
2705 try {
2706 $sk->outputPage();
2707 } catch ( Exception $e ) {
2708 ob_end_clean(); // bug T129657
2709 throw $e;
2710 }
2711 }
2712
2713 try {
2714 // This hook allows last minute changes to final overall output by modifying output buffer
2715 Hooks::runWithoutAbort( 'AfterFinalPageOutput', [ $this ] );
2716 } catch ( Exception $e ) {
2717 ob_end_clean(); // bug T129657
2718 throw $e;
2719 }
2720
2721 $this->sendCacheControl();
2722
2723 if ( $return ) {
2724 return ob_get_clean();
2725 } else {
2726 ob_end_flush();
2727 return null;
2728 }
2729 }
2730
2741 public function prepareErrorPage( $pageTitle, $htmlTitle = false ) {
2742 $this->setPageTitle( $pageTitle );
2743 if ( $htmlTitle !== false ) {
2744 $this->setHTMLTitle( $htmlTitle );
2745 }
2746 $this->setRobotPolicy( 'noindex,nofollow' );
2747 $this->setArticleRelated( false );
2748 $this->enableClientCache( false );
2749 $this->mRedirect = '';
2750 $this->clearSubtitle();
2751 $this->clearHTML();
2752 }
2753
2766 public function showErrorPage( $title, $msg, $params = [] ) {
2767 if ( !$title instanceof Message ) {
2768 $title = $this->msg( $title );
2769 }
2770
2771 $this->prepareErrorPage( $title );
2772
2773 if ( $msg instanceof Message ) {
2774 if ( $params !== [] ) {
2775 trigger_error( 'Argument ignored: $params. The message parameters argument '
2776 . 'is discarded when the $msg argument is a Message object instead of '
2777 . 'a string.', E_USER_NOTICE );
2778 }
2779 $this->addHTML( $msg->parseAsBlock() );
2780 } else {
2781 $this->addWikiMsgArray( $msg, $params );
2782 }
2783
2784 $this->returnToMain();
2785 }
2786
2793 public function showPermissionsErrorPage( array $errors, $action = null ) {
2794 foreach ( $errors as $key => $error ) {
2795 $errors[$key] = (array)$error;
2796 }
2797
2798 // For some action (read, edit, create and upload), display a "login to do this action"
2799 // error if all of the following conditions are met:
2800 // 1. the user is not logged in
2801 // 2. the only error is insufficient permissions (i.e. no block or something else)
2802 // 3. the error can be avoided simply by logging in
2803 if ( in_array( $action, [ 'read', 'edit', 'createpage', 'createtalk', 'upload' ] )
2804 && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
2805 && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
2806 && ( User::groupHasPermission( 'user', $action )
2807 || User::groupHasPermission( 'autoconfirmed', $action ) )
2808 ) {
2809 $displayReturnto = null;
2810
2811 # Due to T34276, if a user does not have read permissions,
2812 # $this->getTitle() will just give Special:Badtitle, which is
2813 # not especially useful as a returnto parameter. Use the title
2814 # from the request instead, if there was one.
2815 $request = $this->getRequest();
2816 $returnto = Title::newFromText( $request->getVal( 'title', '' ) );
2817 if ( $action == 'edit' ) {
2818 $msg = 'whitelistedittext';
2819 $displayReturnto = $returnto;
2820 } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
2821 $msg = 'nocreatetext';
2822 } elseif ( $action == 'upload' ) {
2823 $msg = 'uploadnologintext';
2824 } else { # Read
2825 $msg = 'loginreqpagetext';
2826 $displayReturnto = Title::newMainPage();
2827 }
2828
2829 $query = [];
2830
2831 if ( $returnto ) {
2832 $query['returnto'] = $returnto->getPrefixedText();
2833
2834 if ( !$request->wasPosted() ) {
2835 $returntoquery = $request->getValues();
2836 unset( $returntoquery['title'] );
2837 unset( $returntoquery['returnto'] );
2838 unset( $returntoquery['returntoquery'] );
2839 $query['returntoquery'] = wfArrayToCgi( $returntoquery );
2840 }
2841 }
2842 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
2843 $loginLink = $linkRenderer->makeKnownLink(
2844 SpecialPage::getTitleFor( 'Userlogin' ),
2845 $this->msg( 'loginreqlink' )->text(),
2846 [],
2847 $query
2848 );
2849
2850 $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) );
2851 $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->parse() );
2852
2853 # Don't return to a page the user can't read otherwise
2854 # we'll end up in a pointless loop
2855 if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) {
2856 $this->returnToMain( null, $displayReturnto );
2857 }
2858 } else {
2859 $this->prepareErrorPage( $this->msg( 'permissionserrors' ) );
2860 $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) );
2861 }
2862 }
2863
2870 public function versionRequired( $version ) {
2871 $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) );
2872
2873 $this->addWikiMsg( 'versionrequiredtext', $version );
2874 $this->returnToMain();
2875 }
2876
2884 public function formatPermissionsErrorMessage( array $errors, $action = null ) {
2885 if ( $action == null ) {
2886 $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
2887 } else {
2888 $action_desc = $this->msg( "action-$action" )->plain();
2889 $text = $this->msg(
2890 'permissionserrorstext-withaction',
2891 count( $errors ),
2892 $action_desc
2893 )->plain() . "\n\n";
2894 }
2895
2896 if ( count( $errors ) > 1 ) {
2897 $text .= '<ul class="permissions-errors">' . "\n";
2898
2899 foreach ( $errors as $error ) {
2900 $text .= '<li>';
2901 $text .= $this->msg( ...$error )->plain();
2902 $text .= "</li>\n";
2903 }
2904 $text .= '</ul>';
2905 } else {
2906 $text .= "<div class=\"permissions-errors\">\n" .
2907 $this->msg( ...reset( $errors ) )->plain() .
2908 "\n</div>";
2909 }
2910
2911 return $text;
2912 }
2913
2923 public function showLagWarning( $lag ) {
2924 $config = $this->getConfig();
2925 if ( $lag >= $config->get( 'SlaveLagWarning' ) ) {
2926 $lag = floor( $lag ); // floor to avoid nano seconds to display
2927 $message = $lag < $config->get( 'SlaveLagCritical' )
2928 ? 'lag-warn-normal'
2929 : 'lag-warn-high';
2930 $wrap = Html::rawElement( 'div', [ 'class' => "mw-{$message}" ], "\n$1\n" );
2931 $this->wrapWikiMsg( "$wrap\n", [ $message, $this->getLanguage()->formatNum( $lag ) ] );
2932 }
2933 }
2934
2941 public function showFatalError( $message ) {
2942 $this->prepareErrorPage( $this->msg( 'internalerror' ) );
2943
2944 $this->addHTML( $message );
2945 }
2946
2950 public function showUnexpectedValueError( $name, $val ) {
2951 wfDeprecated( __METHOD__, '1.32' );
2952 $this->showFatalError( $this->msg( 'unexpected', $name, $val )->escaped() );
2953 }
2954
2958 public function showFileCopyError( $old, $new ) {
2959 wfDeprecated( __METHOD__, '1.32' );
2960 $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->escaped() );
2961 }
2962
2966 public function showFileRenameError( $old, $new ) {
2967 wfDeprecated( __METHOD__, '1.32' );
2968 $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->escpaed() );
2969 }
2970
2974 public function showFileDeleteError( $name ) {
2975 wfDeprecated( __METHOD__, '1.32' );
2976 $this->showFatalError( $this->msg( 'filedeleteerror', $name )->escaped() );
2977 }
2978
2982 public function showFileNotFoundError( $name ) {
2983 wfDeprecated( __METHOD__, '1.32' );
2984 $this->showFatalError( $this->msg( 'filenotfound', $name )->escaped() );
2985 }
2986
2995 public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) {
2996 $linkRenderer = MediaWikiServices::getInstance()
2997 ->getLinkRendererFactory()->createFromLegacyOptions( $options );
2998 $link = $this->msg( 'returnto' )->rawParams(
2999 $linkRenderer->makeLink( $title, $text, [], $query ) )->escaped();
3000 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
3001 }
3002
3011 public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
3012 if ( $returnto == null ) {
3013 $returnto = $this->getRequest()->getText( 'returnto' );
3014 }
3015
3016 if ( $returntoquery == null ) {
3017 $returntoquery = $this->getRequest()->getText( 'returntoquery' );
3018 }
3019
3020 if ( $returnto === '' ) {
3021 $returnto = Title::newMainPage();
3022 }
3023
3024 if ( is_object( $returnto ) ) {
3025 $titleObj = $returnto;
3026 } else {
3027 $titleObj = Title::newFromText( $returnto );
3028 }
3029 // We don't want people to return to external interwiki. That
3030 // might potentially be used as part of a phishing scheme
3031 if ( !is_object( $titleObj ) || $titleObj->isExternal() ) {
3032 $titleObj = Title::newMainPage();
3033 }
3034
3035 $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) );
3036 }
3037
3038 private function getRlClientContext() {
3039 if ( !$this->rlClientContext ) {
3041 [], // modules; not relevant
3042 $this->getLanguage()->getCode(),
3043 $this->getSkin()->getSkinName(),
3044 $this->getUser()->isLoggedIn() ? $this->getUser()->getName() : null,
3045 null, // version; not relevant
3047 null, // only; not relevant
3048 $this->isPrintable(),
3049 $this->getRequest()->getBool( 'handheld' )
3050 );
3051 $this->rlClientContext = new ResourceLoaderContext(
3052 $this->getResourceLoader(),
3053 new FauxRequest( $query )
3054 );
3055 if ( $this->contentOverrideCallbacks ) {
3056 $this->rlClientContext = new DerivativeResourceLoaderContext( $this->rlClientContext );
3057 $this->rlClientContext->setContentOverrideCallback( function ( Title $title ) {
3058 foreach ( $this->contentOverrideCallbacks as $callback ) {
3059 $content = $callback( $title );
3060 if ( $content !== null ) {
3061 $text = ContentHandler::getContentText( $content );
3062 if ( strpos( $text, '</script>' ) !== false ) {
3063 // Proactively replace this so that we can display a message
3064 // to the user, instead of letting it go to Html::inlineScript(),
3065 // where it would be considered a server-side issue.
3066 $titleFormatted = $title->getPrefixedText();
3068 Xml::encodeJsCall( 'mw.log.error', [
3069 "Cannot preview $titleFormatted due to script-closing tag."
3070 ] )
3071 );
3072 }
3073 return $content;
3074 }
3075 }
3076 return null;
3077 } );
3078 }
3079 }
3080 return $this->rlClientContext;
3081 }
3082
3094 public function getRlClient() {
3095 if ( !$this->rlClient ) {
3096 $context = $this->getRlClientContext();
3097 $rl = $this->getResourceLoader();
3098 $this->addModules( [
3099 'user',
3100 'user.options',
3101 'user.tokens',
3102 ] );
3103 $this->addModuleStyles( [
3104 'site.styles',
3105 'noscript',
3106 'user.styles',
3107 ] );
3108 $this->getSkin()->setupSkinUserCss( $this );
3109
3110 // Prepare exempt modules for buildExemptModules()
3111 $exemptGroups = [ 'site' => [], 'noscript' => [], 'private' => [], 'user' => [] ];
3112 $exemptStates = [];
3113 $moduleStyles = $this->getModuleStyles( /*filter*/ true );
3114
3115 // Preload getTitleInfo for isKnownEmpty calls below and in ResourceLoaderClientHtml
3116 // Separate user-specific batch for improved cache-hit ratio.
3117 $userBatch = [ 'user.styles', 'user' ];
3118 $siteBatch = array_diff( $moduleStyles, $userBatch );
3119 $dbr = wfGetDB( DB_REPLICA );
3120 ResourceLoaderWikiModule::preloadTitleInfo( $context, $dbr, $siteBatch );
3121 ResourceLoaderWikiModule::preloadTitleInfo( $context, $dbr, $userBatch );
3122
3123 // Filter out modules handled by buildExemptModules()
3124 $moduleStyles = array_filter( $moduleStyles,
3125 function ( $name ) use ( $rl, $context, &$exemptGroups, &$exemptStates ) {
3126 $module = $rl->getModule( $name );
3127 if ( $module ) {
3128 $group = $module->getGroup();
3129 if ( isset( $exemptGroups[$group] ) ) {
3130 $exemptStates[$name] = 'ready';
3131 if ( !$module->isKnownEmpty( $context ) ) {
3132 // E.g. Don't output empty <styles>
3133 $exemptGroups[$group][] = $name;
3134 }
3135 return false;
3136 }
3137 }
3138 return true;
3139 }
3140 );
3141 $this->rlExemptStyleModules = $exemptGroups;
3142
3144 'target' => $this->getTarget(),
3145 'nonce' => $this->getCSPNonce(),
3146 // When 'safemode', disallowUserJs(), or reduceAllowedModules() is used
3147 // to only restrict modules to ORIGIN_CORE (ie. disallow ORIGIN_USER), the list of
3148 // modules enqueud for loading on this page is filtered to just those.
3149 // However, to make sure we also apply the restriction to dynamic dependencies and
3150 // lazy-loaded modules at run-time on the client-side, pass 'safemode' down to the
3151 // StartupModule so that the client-side registry will not contain any restricted
3152 // modules either. (T152169, T185303)
3155 ) ? '1' : null,
3156 ] );
3157 $rlClient->setConfig( $this->getJSVars() );
3158 $rlClient->setModules( $this->getModules( /*filter*/ true ) );
3159 $rlClient->setModuleStyles( $moduleStyles );
3160 $rlClient->setModuleScripts( $this->getModuleScripts( /*filter*/ true ) );
3161 $rlClient->setExemptStates( $exemptStates );
3162 $this->rlClient = $rlClient;
3163 }
3164 return $this->rlClient;
3165 }
3166
3172 public function headElement( Skin $sk, $includeStyle = true ) {
3173 $userdir = $this->getLanguage()->getDir();
3174 $sitedir = MediaWikiServices::getInstance()->getContentLanguage()->getDir();
3175
3176 $pieces = [];
3177 $pieces[] = Html::htmlHeader( Sanitizer::mergeAttributes(
3178 $this->getRlClient()->getDocumentAttributes(),
3180 ) );
3181 $pieces[] = Html::openElement( 'head' );
3182
3183 if ( $this->getHTMLTitle() == '' ) {
3184 $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() );
3185 }
3186
3187 if ( !Html::isXmlMimeType( $this->getConfig()->get( 'MimeType' ) ) ) {
3188 // Add <meta charset="UTF-8">
3189 // This should be before <title> since it defines the charset used by
3190 // text including the text inside <title>.
3191 // The spec recommends defining XHTML5's charset using the XML declaration
3192 // instead of meta.
3193 // Our XML declaration is output by Html::htmlHeader.
3194 // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type
3195 // https://html.spec.whatwg.org/multipage/semantics.html#charset
3196 $pieces[] = Html::element( 'meta', [ 'charset' => 'UTF-8' ] );
3197 }
3198
3199 $pieces[] = Html::element( 'title', null, $this->getHTMLTitle() );
3200 $pieces[] = $this->getRlClient()->getHeadHtml();
3201 $pieces[] = $this->buildExemptModules();
3202 $pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) );
3203 $pieces = array_merge( $pieces, array_values( $this->mHeadItems ) );
3204
3205 // Use an IE conditional comment to serve the script only to old IE
3206 $pieces[] = '<!--[if lt IE 9]>' .
3209 [ 'html5shiv' ],
3211 [ 'sync' => true ],
3212 $this->getCSPNonce()
3213 ) .
3214 '<![endif]-->';
3215
3216 $pieces[] = Html::closeElement( 'head' );
3217
3218 $bodyClasses = $this->mAdditionalBodyClasses;
3219 $bodyClasses[] = 'mediawiki';
3220
3221 # Classes for LTR/RTL directionality support
3222 $bodyClasses[] = $userdir;
3223 $bodyClasses[] = "sitedir-$sitedir";
3224
3225 $underline = $this->getUser()->getOption( 'underline' );
3226 if ( $underline < 2 ) {
3227 // The following classes can be used here:
3228 // * mw-underline-always
3229 // * mw-underline-never
3230 $bodyClasses[] = 'mw-underline-' . ( $underline ? 'always' : 'never' );
3231 }
3232
3233 if ( $this->getLanguage()->capitalizeAllNouns() ) {
3234 # A <body> class is probably not the best way to do this . . .
3235 $bodyClasses[] = 'capitalize-all-nouns';
3236 }
3237
3238 // Parser feature migration class
3239 // The idea is that this will eventually be removed, after the wikitext
3240 // which requires it is cleaned up.
3241 $bodyClasses[] = 'mw-hide-empty-elt';
3242
3243 $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
3244 $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
3245 $bodyClasses[] =
3246 'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
3247
3248 $bodyAttrs = [];
3249 // While the implode() is not strictly needed, it's used for backwards compatibility
3250 // (this used to be built as a string and hooks likely still expect that).
3251 $bodyAttrs['class'] = implode( ' ', $bodyClasses );
3252
3253 // Allow skins and extensions to add body attributes they need
3254 $sk->addToBodyAttributes( $this, $bodyAttrs );
3255 Hooks::run( 'OutputPageBodyAttributes', [ $this, $sk, &$bodyAttrs ] );
3256
3257 $pieces[] = Html::openElement( 'body', $bodyAttrs );
3258
3259 return self::combineWrappedStrings( $pieces );
3260 }
3261
3267 public function getResourceLoader() {
3268 if ( is_null( $this->mResourceLoader ) ) {
3269 $this->mResourceLoader = new ResourceLoader(
3270 $this->getConfig(),
3271 LoggerFactory::getInstance( 'resourceloader' )
3272 );
3273 }
3274 return $this->mResourceLoader;
3275 }
3276
3285 public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) {
3286 // Apply 'target' and 'origin' filters
3287 $modules = $this->filterModules( (array)$modules, null, $only );
3288
3290 $this->getRlClientContext(),
3291 $modules,
3292 $only,
3293 $extraQuery,
3294 $this->getCSPNonce()
3295 );
3296 }
3297
3304 protected static function combineWrappedStrings( array $chunks ) {
3305 // Filter out empty values
3306 $chunks = array_filter( $chunks, 'strlen' );
3307 return WrappedString::join( "\n", $chunks );
3308 }
3309
3316 public function getBottomScripts() {
3317 $chunks = [];
3318 $chunks[] = $this->getRlClient()->getBodyHtml();
3319
3320 // Legacy non-ResourceLoader scripts
3321 $chunks[] = $this->mScripts;
3322
3323 if ( $this->limitReportJSData ) {
3326 [ 'wgPageParseReport' => $this->limitReportJSData ]
3327 ),
3328 $this->getCSPNonce()
3329 );
3330 }
3331
3332 return self::combineWrappedStrings( $chunks );
3333 }
3334
3341 public function getJsConfigVars() {
3342 return $this->mJsConfigVars;
3343 }
3344
3351 public function addJsConfigVars( $keys, $value = null ) {
3352 if ( is_array( $keys ) ) {
3353 foreach ( $keys as $key => $value ) {
3354 $this->mJsConfigVars[$key] = $value;
3355 }
3356 return;
3357 }
3358
3359 $this->mJsConfigVars[$keys] = $value;
3360 }
3361
3371 public function getJSVars() {
3372 $curRevisionId = 0;
3373 $articleId = 0;
3374 $canonicalSpecialPageName = false; # T23115
3375 $services = MediaWikiServices::getInstance();
3376
3377 $title = $this->getTitle();
3378 $ns = $title->getNamespace();
3379 $canonicalNamespace = MWNamespace::exists( $ns )
3380 ? MWNamespace::getCanonicalName( $ns )
3381 : $title->getNsText();
3382
3383 $sk = $this->getSkin();
3384 // Get the relevant title so that AJAX features can use the correct page name
3385 // when making API requests from certain special pages (T36972).
3386 $relevantTitle = $sk->getRelevantTitle();
3387 $relevantUser = $sk->getRelevantUser();
3388
3389 if ( $ns == NS_SPECIAL ) {
3390 list( $canonicalSpecialPageName, /*...*/ ) =
3391 $services->getSpecialPageFactory()->
3392 resolveAlias( $title->getDBkey() );
3393 } elseif ( $this->canUseWikiPage() ) {
3394 $wikiPage = $this->getWikiPage();
3395 $curRevisionId = $wikiPage->getLatest();
3396 $articleId = $wikiPage->getId();
3397 }
3398
3399 $lang = $title->getPageViewLanguage();
3400
3401 // Pre-process information
3402 $separatorTransTable = $lang->separatorTransformTable();
3403 $separatorTransTable = $separatorTransTable ?: [];
3404 $compactSeparatorTransTable = [
3405 implode( "\t", array_keys( $separatorTransTable ) ),
3406 implode( "\t", $separatorTransTable ),
3407 ];
3408 $digitTransTable = $lang->digitTransformTable();
3409 $digitTransTable = $digitTransTable ?: [];
3410 $compactDigitTransTable = [
3411 implode( "\t", array_keys( $digitTransTable ) ),
3412 implode( "\t", $digitTransTable ),
3413 ];
3414
3415 $user = $this->getUser();
3416
3417 $vars = [
3418 'wgCanonicalNamespace' => $canonicalNamespace,
3419 'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
3420 'wgNamespaceNumber' => $title->getNamespace(),
3421 'wgPageName' => $title->getPrefixedDBkey(),
3422 'wgTitle' => $title->getText(),
3423 'wgCurRevisionId' => $curRevisionId,
3424 'wgRevisionId' => (int)$this->getRevisionId(),
3425 'wgArticleId' => $articleId,
3426 'wgIsArticle' => $this->isArticle(),
3427 'wgIsRedirect' => $title->isRedirect(),
3428 'wgAction' => Action::getActionName( $this->getContext() ),
3429 'wgUserName' => $user->isAnon() ? null : $user->getName(),
3430 'wgUserGroups' => $user->getEffectiveGroups(),
3431 'wgCategories' => $this->getCategories(),
3432 'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
3433 'wgPageContentLanguage' => $lang->getCode(),
3434 'wgPageContentModel' => $title->getContentModel(),
3435 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
3436 'wgDigitTransformTable' => $compactDigitTransTable,
3437 'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
3438 'wgMonthNames' => $lang->getMonthNamesArray(),
3439 'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
3440 'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
3441 'wgRelevantArticleId' => $relevantTitle->getArticleID(),
3442 'wgRequestId' => WebRequest::getRequestId(),
3443 'wgCSPNonce' => $this->getCSPNonce(),
3444 ];
3445
3446 if ( $user->isLoggedIn() ) {
3447 $vars['wgUserId'] = $user->getId();
3448 $vars['wgUserEditCount'] = $user->getEditCount();
3449 $userReg = $user->getRegistration();
3450 $vars['wgUserRegistration'] = $userReg ? wfTimestamp( TS_UNIX, $userReg ) * 1000 : null;
3451 // Get the revision ID of the oldest new message on the user's talk
3452 // page. This can be used for constructing new message alerts on
3453 // the client side.
3454 $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId();
3455 }
3456
3457 $contLang = $services->getContentLanguage();
3458 if ( $contLang->hasVariants() ) {
3459 $vars['wgUserVariant'] = $contLang->getPreferredVariant();
3460 }
3461 // Same test as SkinTemplate
3462 $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user )
3463 && ( $title->exists() || $title->quickUserCan( 'create', $user ) );
3464
3465 $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle
3466 && $relevantTitle->quickUserCan( 'edit', $user )
3467 && ( $relevantTitle->exists() || $relevantTitle->quickUserCan( 'create', $user ) );
3468
3469 foreach ( $title->getRestrictionTypes() as $type ) {
3470 // Following keys are set in $vars:
3471 // wgRestrictionCreate, wgRestrictionEdit, wgRestrictionMove, wgRestrictionUpload
3472 $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
3473 }
3474
3475 if ( $title->isMainPage() ) {
3476 $vars['wgIsMainPage'] = true;
3477 }
3478
3479 if ( $this->mRedirectedFrom ) {
3480 $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey();
3481 }
3482
3483 if ( $relevantUser ) {
3484 $vars['wgRelevantUserName'] = $relevantUser->getName();
3485 }
3486
3487 // Allow extensions to add their custom variables to the mw.config map.
3488 // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
3489 // page-dependant but site-wide (without state).
3490 // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
3491 Hooks::run( 'MakeGlobalVariablesScript', [ &$vars, $this ] );
3492
3493 // Merge in variables from addJsConfigVars last
3494 return array_merge( $vars, $this->getJsConfigVars() );
3495 }
3496
3506 public function userCanPreview() {
3507 $request = $this->getRequest();
3508 if (
3509 $request->getVal( 'action' ) !== 'submit' ||
3510 !$request->wasPosted()
3511 ) {
3512 return false;
3513 }
3514
3515 $user = $this->getUser();
3516
3517 if ( !$user->isLoggedIn() ) {
3518 // Anons have predictable edit tokens
3519 return false;
3520 }
3521 if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
3522 return false;
3523 }
3524
3525 $title = $this->getTitle();
3526 $errors = $title->getUserPermissionsErrors( 'edit', $user );
3527 if ( count( $errors ) !== 0 ) {
3528 return false;
3529 }
3530
3531 return true;
3532 }
3533
3537 public function getHeadLinksArray() {
3538 global $wgVersion;
3539
3540 $tags = [];
3541 $config = $this->getConfig();
3542
3543 $canonicalUrl = $this->mCanonicalUrl;
3544
3545 $tags['meta-generator'] = Html::element( 'meta', [
3546 'name' => 'generator',
3547 'content' => "MediaWiki $wgVersion",
3548 ] );
3549
3550 if ( $config->get( 'ReferrerPolicy' ) !== false ) {
3551 // Per https://w3c.github.io/webappsec-referrer-policy/#unknown-policy-values
3552 // fallbacks should come before the primary value so we need to reverse the array.
3553 foreach ( array_reverse( (array)$config->get( 'ReferrerPolicy' ) ) as $i => $policy ) {
3554 $tags["meta-referrer-$i"] = Html::element( 'meta', [
3555 'name' => 'referrer',
3556 'content' => $policy,
3557 ] );
3558 }
3559 }
3560
3561 $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
3562 if ( $p !== 'index,follow' ) {
3563 // http://www.robotstxt.org/wc/meta-user.html
3564 // Only show if it's different from the default robots policy
3565 $tags['meta-robots'] = Html::element( 'meta', [
3566 'name' => 'robots',
3567 'content' => $p,
3568 ] );
3569 }
3570
3571 foreach ( $this->mMetatags as $tag ) {
3572 if ( strncasecmp( $tag[0], 'http:', 5 ) === 0 ) {
3573 $a = 'http-equiv';
3574 $tag[0] = substr( $tag[0], 5 );
3575 } elseif ( strncasecmp( $tag[0], 'og:', 3 ) === 0 ) {
3576 $a = 'property';
3577 } else {
3578 $a = 'name';
3579 }
3580 $tagName = "meta-{$tag[0]}";
3581 if ( isset( $tags[$tagName] ) ) {
3582 $tagName .= $tag[1];
3583 }
3584 $tags[$tagName] = Html::element( 'meta',
3585 [
3586 $a => $tag[0],
3587 'content' => $tag[1]
3588 ]
3589 );
3590 }
3591
3592 foreach ( $this->mLinktags as $tag ) {
3593 $tags[] = Html::element( 'link', $tag );
3594 }
3595
3596 # Universal edit button
3597 if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) {
3598 $user = $this->getUser();
3599 if ( $this->getTitle()->quickUserCan( 'edit', $user )
3600 && ( $this->getTitle()->exists() ||
3601 $this->getTitle()->quickUserCan( 'create', $user ) )
3602 ) {
3603 // Original UniversalEditButton
3604 $msg = $this->msg( 'edit' )->text();
3605 $tags['universal-edit-button'] = Html::element( 'link', [
3606 'rel' => 'alternate',
3607 'type' => 'application/x-wiki',
3608 'title' => $msg,
3609 'href' => $this->getTitle()->getEditURL(),
3610 ] );
3611 // Alternate edit link
3612 $tags['alternative-edit'] = Html::element( 'link', [
3613 'rel' => 'edit',
3614 'title' => $msg,
3615 'href' => $this->getTitle()->getEditURL(),
3616 ] );
3617 }
3618 }
3619
3620 # Generally the order of the favicon and apple-touch-icon links
3621 # should not matter, but Konqueror (3.5.9 at least) incorrectly
3622 # uses whichever one appears later in the HTML source. Make sure
3623 # apple-touch-icon is specified first to avoid this.
3624 if ( $config->get( 'AppleTouchIcon' ) !== false ) {
3625 $tags['apple-touch-icon'] = Html::element( 'link', [
3626 'rel' => 'apple-touch-icon',
3627 'href' => $config->get( 'AppleTouchIcon' )
3628 ] );
3629 }
3630
3631 if ( $config->get( 'Favicon' ) !== false ) {
3632 $tags['favicon'] = Html::element( 'link', [
3633 'rel' => 'shortcut icon',
3634 'href' => $config->get( 'Favicon' )
3635 ] );
3636 }
3637
3638 # OpenSearch description link
3639 $tags['opensearch'] = Html::element( 'link', [
3640 'rel' => 'search',
3641 'type' => 'application/opensearchdescription+xml',
3642 'href' => wfScript( 'opensearch_desc' ),
3643 'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
3644 ] );
3645
3646 # Real Simple Discovery link, provides auto-discovery information
3647 # for the MediaWiki API (and potentially additional custom API
3648 # support such as WordPress or Twitter-compatible APIs for a
3649 # blogging extension, etc)
3650 $tags['rsd'] = Html::element( 'link', [
3651 'rel' => 'EditURI',
3652 'type' => 'application/rsd+xml',
3653 // Output a protocol-relative URL here if $wgServer is protocol-relative.
3654 // Whether RSD accepts relative or protocol-relative URLs is completely
3655 // undocumented, though.
3656 'href' => wfExpandUrl( wfAppendQuery(
3657 wfScript( 'api' ),
3658 [ 'action' => 'rsd' ] ),
3660 ),
3661 ] );
3662
3663 # Language variants
3664 if ( !$config->get( 'DisableLangConversion' ) ) {
3665 $lang = $this->getTitle()->getPageLanguage();
3666 if ( $lang->hasVariants() ) {
3667 $variants = $lang->getVariants();
3668 foreach ( $variants as $variant ) {
3669 $tags["variant-$variant"] = Html::element( 'link', [
3670 'rel' => 'alternate',
3671 'hreflang' => LanguageCode::bcp47( $variant ),
3672 'href' => $this->getTitle()->getLocalURL(
3673 [ 'variant' => $variant ] )
3674 ]
3675 );
3676 }
3677 # x-default link per https://support.google.com/webmasters/answer/189077?hl=en
3678 $tags["variant-x-default"] = Html::element( 'link', [
3679 'rel' => 'alternate',
3680 'hreflang' => 'x-default',
3681 'href' => $this->getTitle()->getLocalURL() ] );
3682 }
3683 }
3684
3685 # Copyright
3686 if ( $this->copyrightUrl !== null ) {
3687 $copyright = $this->copyrightUrl;
3688 } else {
3689 $copyright = '';
3690 if ( $config->get( 'RightsPage' ) ) {
3691 $copy = Title::newFromText( $config->get( 'RightsPage' ) );
3692
3693 if ( $copy ) {
3694 $copyright = $copy->getLocalURL();
3695 }
3696 }
3697
3698 if ( !$copyright && $config->get( 'RightsUrl' ) ) {
3699 $copyright = $config->get( 'RightsUrl' );
3700 }
3701 }
3702
3703 if ( $copyright ) {
3704 $tags['copyright'] = Html::element( 'link', [
3705 'rel' => 'license',
3706 'href' => $copyright ]
3707 );
3708 }
3709
3710 # Feeds
3711 if ( $config->get( 'Feed' ) ) {
3712 $feedLinks = [];
3713
3714 foreach ( $this->getSyndicationLinks() as $format => $link ) {
3715 # Use the page name for the title. In principle, this could
3716 # lead to issues with having the same name for different feeds
3717 # corresponding to the same page, but we can't avoid that at
3718 # this low a level.
3719
3720 $feedLinks[] = $this->feedLink(
3721 $format,
3722 $link,
3723 # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
3724 $this->msg(
3725 "page-{$format}-feed", $this->getTitle()->getPrefixedText()
3726 )->text()
3727 );
3728 }
3729
3730 # Recent changes feed should appear on every page (except recentchanges,
3731 # that would be redundant). Put it after the per-page feed to avoid
3732 # changing existing behavior. It's still available, probably via a
3733 # menu in your browser. Some sites might have a different feed they'd
3734 # like to promote instead of the RC feed (maybe like a "Recent New Articles"
3735 # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
3736 # If so, use it instead.
3737 $sitename = $config->get( 'Sitename' );
3738 if ( $config->get( 'OverrideSiteFeed' ) ) {
3739 foreach ( $config->get( 'OverrideSiteFeed' ) as $type => $feedUrl ) {
3740 // Note, this->feedLink escapes the url.
3741 $feedLinks[] = $this->feedLink(
3742 $type,
3743 $feedUrl,
3744 $this->msg( "site-{$type}-feed", $sitename )->text()
3745 );
3746 }
3747 } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
3748 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
3749 foreach ( $config->get( 'AdvertisedFeedTypes' ) as $format ) {
3750 $feedLinks[] = $this->feedLink(
3751 $format,
3752 $rctitle->getLocalURL( [ 'feed' => $format ] ),
3753 # For grep: 'site-rss-feed', 'site-atom-feed'
3754 $this->msg( "site-{$format}-feed", $sitename )->text()
3755 );
3756 }
3757 }
3758
3759 # Allow extensions to change the list pf feeds. This hook is primarily for changing,
3760 # manipulating or removing existing feed tags. If you want to add new feeds, you should
3761 # use OutputPage::addFeedLink() instead.
3762 Hooks::run( 'AfterBuildFeedLinks', [ &$feedLinks ] );
3763
3764 $tags += $feedLinks;
3765 }
3766
3767 # Canonical URL
3768 if ( $config->get( 'EnableCanonicalServerLink' ) ) {
3769 if ( $canonicalUrl !== false ) {
3770 $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL );
3771 } else {
3772 if ( $this->isArticleRelated() ) {
3773 // This affects all requests where "setArticleRelated" is true. This is
3774 // typically all requests that show content (query title, curid, oldid, diff),
3775 // and all wikipage actions (edit, delete, purge, info, history etc.).
3776 // It does not apply to File pages and Special pages.
3777 // 'history' and 'info' actions address page metadata rather than the page
3778 // content itself, so they may not be canonicalized to the view page url.
3779 // TODO: this ought to be better encapsulated in the Action class.
3780 $action = Action::getActionName( $this->getContext() );
3781 if ( in_array( $action, [ 'history', 'info' ] ) ) {
3782 $query = "action={$action}";
3783 } else {
3784 $query = '';
3785 }
3786 $canonicalUrl = $this->getTitle()->getCanonicalURL( $query );
3787 } else {
3788 $reqUrl = $this->getRequest()->getRequestURL();
3789 $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL );
3790 }
3791 }
3792 }
3793 if ( $canonicalUrl !== false ) {
3794 $tags[] = Html::element( 'link', [
3795 'rel' => 'canonical',
3796 'href' => $canonicalUrl
3797 ] );
3798 }
3799
3800 // Allow extensions to add, remove and/or otherwise manipulate these links
3801 // If you want only to *add* <head> links, please use the addHeadItem()
3802 // (or addHeadItems() for multiple items) method instead.
3803 // This hook is provided as a last resort for extensions to modify these
3804 // links before the output is sent to client.
3805 Hooks::run( 'OutputPageAfterGetHeadLinksArray', [ &$tags, $this ] );
3806
3807 return $tags;
3808 }
3809
3818 private function feedLink( $type, $url, $text ) {
3819 return Html::element( 'link', [
3820 'rel' => 'alternate',
3821 'type' => "application/$type+xml",
3822 'title' => $text,
3823 'href' => $url ]
3824 );
3825 }
3826
3836 public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
3837 $options = [];
3838 if ( $media ) {
3839 $options['media'] = $media;
3840 }
3841 if ( $condition ) {
3842 $options['condition'] = $condition;
3843 }
3844 if ( $dir ) {
3845 $options['dir'] = $dir;
3846 }
3847 $this->styles[$style] = $options;
3848 }
3849
3857 public function addInlineStyle( $style_css, $flip = 'noflip' ) {
3858 if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
3859 # If wanted, and the interface is right-to-left, flip the CSS
3860 $style_css = CSSJanus::transform( $style_css, true, false );
3861 }
3862 $this->mInlineStyles .= Html::inlineStyle( $style_css );
3863 }
3864
3870 protected function buildExemptModules() {
3871 $chunks = [];
3872 // Things that go after the ResourceLoaderDynamicStyles marker
3873 $append = [];
3874
3875 // We want site, private and user styles to override dynamically added styles from
3876 // general modules, but we want dynamically added styles to override statically added
3877 // style modules. So the order has to be:
3878 // - page style modules (formatted by ResourceLoaderClientHtml::getHeadHtml())
3879 // - dynamically loaded styles (added by mw.loader before ResourceLoaderDynamicStyles)
3880 // - ResourceLoaderDynamicStyles marker
3881 // - site/private/user styles
3882
3883 // Add legacy styles added through addStyle()/addInlineStyle() here
3884 $chunks[] = implode( '', $this->buildCssLinksArray() ) . $this->mInlineStyles;
3885
3886 $chunks[] = Html::element(
3887 'meta',
3888 [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ]
3889 );
3890
3891 $separateReq = [ 'site.styles', 'user.styles' ];
3892 foreach ( $this->rlExemptStyleModules as $group => $moduleNames ) {
3893 // Combinable modules
3894 $chunks[] = $this->makeResourceLoaderLink(
3895 array_diff( $moduleNames, $separateReq ),
3897 );
3898
3899 foreach ( array_intersect( $moduleNames, $separateReq ) as $name ) {
3900 // These require their own dedicated request in order to support "@import"
3901 // syntax, which is incompatible with concatenation. (T147667, T37562)
3902 $chunks[] = $this->makeResourceLoaderLink( $name,
3904 );
3905 }
3906 }
3907
3908 return self::combineWrappedStrings( array_merge( $chunks, $append ) );
3909 }
3910
3914 public function buildCssLinksArray() {
3915 $links = [];
3916
3917 foreach ( $this->styles as $file => $options ) {
3918 $link = $this->styleLink( $file, $options );
3919 if ( $link ) {
3920 $links[$file] = $link;
3921 }
3922 }
3923 return $links;
3924 }
3925
3933 protected function styleLink( $style, array $options ) {
3934 if ( isset( $options['dir'] ) ) {
3935 if ( $this->getLanguage()->getDir() != $options['dir'] ) {
3936 return '';
3937 }
3938 }
3939
3940 if ( isset( $options['media'] ) ) {
3941 $media = self::transformCssMedia( $options['media'] );
3942 if ( is_null( $media ) ) {
3943 return '';
3944 }
3945 } else {
3946 $media = 'all';
3947 }
3948
3949 if ( substr( $style, 0, 1 ) == '/' ||
3950 substr( $style, 0, 5 ) == 'http:' ||
3951 substr( $style, 0, 6 ) == 'https:' ) {
3952 $url = $style;
3953 } else {
3954 $config = $this->getConfig();
3955 // Append file hash as query parameter
3956 $url = self::transformResourcePath(
3957 $config,
3958 $config->get( 'StylePath' ) . '/' . $style
3959 );
3960 }
3961
3962 $link = Html::linkedStyle( $url, $media );
3963
3964 if ( isset( $options['condition'] ) ) {
3965 $condition = htmlspecialchars( $options['condition'] );
3966 $link = "<!--[if $condition]>$link<![endif]-->";
3967 }
3968 return $link;
3969 }
3970
3992 public static function transformResourcePath( Config $config, $path ) {
3993 global $IP;
3994
3995 $localDir = $IP;
3996 $remotePathPrefix = $config->get( 'ResourceBasePath' );
3997 if ( $remotePathPrefix === '' ) {
3998 // The configured base path is required to be empty string for
3999 // wikis in the domain root
4000 $remotePath = '/';
4001 } else {
4002 $remotePath = $remotePathPrefix;
4003 }
4004 if ( strpos( $path, $remotePath ) !== 0 || substr( $path, 0, 2 ) === '//' ) {
4005 // - Path is outside wgResourceBasePath, ignore.
4006 // - Path is protocol-relative. Fixes T155310. Not supported by RelPath lib.
4007 return $path;
4008 }
4009 // For files in resources, extensions/ or skins/, ResourceBasePath is preferred here.
4010 // For other misc files in $IP, we'll fallback to that as well. There is, however, a fourth
4011 // supported dir/path pair in the configuration (wgUploadDirectory, wgUploadPath)
4012 // which is not expected to be in wgResourceBasePath on CDNs. (T155146)
4013 $uploadPath = $config->get( 'UploadPath' );
4014 if ( strpos( $path, $uploadPath ) === 0 ) {
4015 $localDir = $config->get( 'UploadDirectory' );
4016 $remotePathPrefix = $remotePath = $uploadPath;
4017 }
4018
4019 $path = RelPath::getRelativePath( $path, $remotePath );
4020 return self::transformFilePath( $remotePathPrefix, $localDir, $path );
4021 }
4022
4034 public static function transformFilePath( $remotePathPrefix, $localPath, $file ) {
4035 $hash = md5_file( "$localPath/$file" );
4036 if ( $hash === false ) {
4037 wfLogWarning( __METHOD__ . ": Failed to hash $localPath/$file" );
4038 $hash = '';
4039 }
4040 return "$remotePathPrefix/$file?" . substr( $hash, 0, 5 );
4041 }
4042
4050 public static function transformCssMedia( $media ) {
4051 global $wgRequest;
4052
4053 // https://www.w3.org/TR/css3-mediaqueries/#syntax
4054 $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
4055
4056 // Switch in on-screen display for media testing
4057 $switches = [
4058 'printable' => 'print',
4059 'handheld' => 'handheld',
4060 ];
4061 foreach ( $switches as $switch => $targetMedia ) {
4062 if ( $wgRequest->getBool( $switch ) ) {
4063 if ( $media == $targetMedia ) {
4064 $media = '';
4065 } elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
4066 /* This regex will not attempt to understand a comma-separated media_query_list
4067 *
4068 * Example supported values for $media:
4069 * 'screen', 'only screen', 'screen and (min-width: 982px)' ),
4070 * Example NOT supported value for $media:
4071 * '3d-glasses, screen, print and resolution > 90dpi'
4072 *
4073 * If it's a print request, we never want any kind of screen stylesheets
4074 * If it's a handheld request (currently the only other choice with a switch),
4075 * we don't want simple 'screen' but we might want screen queries that
4076 * have a max-width or something, so we'll pass all others on and let the
4077 * client do the query.
4078 */
4079 if ( $targetMedia == 'print' || $media == 'screen' ) {
4080 return null;
4081 }
4082 }
4083 }
4084 }
4085
4086 return $media;
4087 }
4088
4095 public function addWikiMsg( /*...*/ ) {
4096 $args = func_get_args();
4097 $name = array_shift( $args );
4098 $this->addWikiMsgArray( $name, $args );
4099 }
4100
4109 public function addWikiMsgArray( $name, $args ) {
4110 $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
4111 }
4112
4138 public function wrapWikiMsg( $wrap /*, ...*/ ) {
4139 $msgSpecs = func_get_args();
4140 array_shift( $msgSpecs );
4141 $msgSpecs = array_values( $msgSpecs );
4142 $s = $wrap;
4143 foreach ( $msgSpecs as $n => $spec ) {
4144 if ( is_array( $spec ) ) {
4145 $args = $spec;
4146 $name = array_shift( $args );
4147 if ( isset( $args['options'] ) ) {
4148 unset( $args['options'] );
4150 'Adding "options" to ' . __METHOD__ . ' is no longer supported',
4151 '1.20'
4152 );
4153 }
4154 } else {
4155 $args = [];
4156 $name = $spec;
4157 }
4158 $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
4159 }
4160 $this->addWikiText( $s );
4161 }
4162
4168 public function isTOCEnabled() {
4169 return $this->mEnableTOC;
4170 }
4171
4178 public function enableSectionEditLinks( $flag = true ) {
4179 wfDeprecated( __METHOD__, '1.31' );
4180 }
4181
4187 public function sectionEditLinksEnabled() {
4188 wfDeprecated( __METHOD__, '1.31' );
4189 return true;
4190 }
4191
4199 public static function setupOOUI( $skinName = 'default', $dir = 'ltr' ) {
4200 $themes = ResourceLoaderOOUIModule::getSkinThemeMap();
4201 $theme = $themes[$skinName] ?? $themes['default'];
4202 // For example, 'OOUI\WikimediaUITheme'.
4203 $themeClass = "OOUI\\{$theme}Theme";
4204 OOUI\Theme::setSingleton( new $themeClass() );
4205 OOUI\Element::setDefaultDir( $dir );
4206 }
4207
4214 public function enableOOUI() {
4215 self::setupOOUI(
4216 strtolower( $this->getSkin()->getSkinName() ),
4217 $this->getLanguage()->getDir()
4218 );
4219 $this->addModuleStyles( [
4220 'oojs-ui-core.styles',
4221 'oojs-ui.styles.indicators',
4222 'oojs-ui.styles.textures',
4223 'mediawiki.widgets.styles',
4224 'oojs-ui.styles.icons-content',
4225 'oojs-ui.styles.icons-alerts',
4226 'oojs-ui.styles.icons-interactions',
4227 ] );
4228 }
4229
4239 public function getCSPNonce() {
4241 return false;
4242 }
4243 if ( $this->CSPNonce === null ) {
4244 // XXX It might be expensive to generate randomness
4245 // on every request, on Windows.
4246 $rand = random_bytes( 15 );
4247 $this->CSPNonce = base64_encode( $rand );
4248 }
4249 return $this->CSPNonce;
4250 }
4251
4252}
and(b) You must cause any modified files to carry prominent notices stating that You changed the files
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
$wgVersion
MediaWiki version number.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness,...
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
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...
wfGetAllCallers( $limit=3)
Return a string consisting of callers in the stack.
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
wfClearOutputBuffers()
More legible than passing a 'false' parameter to wfResetOutputBuffers():
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
wfCgiToArray( $query)
This is the logical opposite of wfArrayToCgi(): it accepts a query string as its argument and returns...
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
getContext()
$wgParser
Definition Setup.php:921
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:747
$IP
Definition WebStart.php:41
if( $line===false) $args
Definition cdb.php:64
static getActionName(IContextSource $context)
Get the action that will be executed, not necessarily the one passed passed through the "action" requ...
Definition Action.php:124
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition Article.php:1029
static isNonceRequired(Config $config)
Should we set nonce attribute.
static sendHeaders(IContextSource $context)
Send CSP headers based on wiki config.
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
msg( $key)
Get a Message object with context set Parameters are the same as wfMessage()
canUseWikiPage()
Check whether a WikiPage object can be get with getWikiPage().
getWikiPage()
Get the WikiPage object.
setContext(IContextSource $context)
Allows changing specific properties of a context object, without changing the main one.
WebRequest clone which takes values from a provided array.
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition File.php:51
exists()
Returns true if file exists in the repository.
Definition File.php:896
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:28
Content for JavaScript pages.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition LinkBatch.php:34
setArray( $array)
Set the link list to a given 2-d array First key is the namespace, second is the DB key,...
MediaWiki exception.
PSR-3 logger instance factory.
MediaWikiServices is the service locator for the application scope of MediaWiki.
This serves as the entry point to the MediaWiki session handling system.
The Message class provides methods which fulfil two basic services:
Definition Message.php:160
This class should be covered by a general architecture document which does not exist as of January 20...
isArticle()
Return whether the content displayed page is related to the source of the corresponding article on th...
addWikiMsg()
Add a wikitext-formatted message to the output.
addLinkHeader( $header)
Add an HTTP Link: header.
setFileVersion( $file)
Set the displayed file version.
$mScripts
Used for JavaScript (predates ResourceLoader)
bool $mCanonicalUrl
getDisplayTitle()
Returns page display title.
string $CSPNonce
The nonce for Content-Security-Policy.
getCdnCacheEpoch( $reqTime, $maxAge)
disable()
Disable output completely, i.e.
addLink(array $linkarr)
Add a new <link> tag to the page header.
ResourceLoader $mResourceLoader
showFileCopyError( $old, $new)
static array $cacheVaryCookies
A cache of the names of the cookies that will influence the cache.
getCanonicalUrl()
Returns the URL to be used for the <link rel=canonical>> if one is set.
getPageTitle()
Return the "page title", i.e.
getModuleScripts( $filter=false, $position=null)
Get the list of script-only modules to load on this page.
isArticleRelated()
Return whether this page is related an article on the wiki.
addCategoryLinksToLBAndGetResult(array $categories)
getLanguageLinks()
Get the list of language links.
addWikiText( $text, $linestart=true, $interface=true)
Convert wikitext to HTML and add it to the buffer Default assumes that the current page title will be...
array $mModules
addScriptFile( $file, $unused=null)
Add a JavaScript file to be loaded as <script> on this page.
array $mJsConfigVars
array $mImageTimeKeys
allowClickjacking()
Turn off frame-breaking.
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...
wrapWikiTextAsInterface( $wrapperClass, $text)
Convert wikitext in the user interface language to HTML and add it to the buffer with a <div class="$...
getSyndicationLinks()
Return URLs for each supported syndication format for this page.
setArticleFlag( $newVal)
Set whether the displayed content is related to the source of the corresponding article on the wiki S...
addMeta( $name, $val)
Add a new "<meta>" tag To add an http-equiv meta tag, precede the name with "http:".
showFileNotFoundError( $name)
parseInline( $text, $linestart=true, $interface=false)
Parse wikitext, strip paragraph wrapper, and return the HTML.
showFileRenameError( $old, $new)
$mFeedLinks
Handles the Atom / RSS links.
bool $mNewSectionLink
showUnexpectedValueError( $name, $val)
setCopyrightUrl( $url)
Set the copyright URL to send with the output.
parseInternal( $text, $title, $linestart, $tidy, $interface, $language)
Parse wikitext and return the HTML (internal implementation helper)
setRobotPolicy( $policy)
Set the robot policy for the page: http://www.robotstxt.org/meta.html
__construct(IContextSource $context)
Constructor for OutputPage.
setIndexPolicy( $policy)
Set the index policy for the page, but leave the follow policy un- touched.
getTemplateIds()
Get the templates used on this page.
array $mLinktags
setDisplayTitle( $html)
Same as page title but only contains name of the page, not any other text.
setPageTitle( $name)
"Page title" means the contents of <h1>.
output( $return=false)
Finally, all the text has been munged and accumulated into the object, let's actually output it:
array $mIndicators
redirect( $url, $responsecode='302')
Redirect to $url rather than displaying the normal page.
getRedirect()
Get the URL to redirect to, or an empty string if not redirect URL set.
setLastModified( $timestamp)
Override the last modified timestamp.
addWikiTextTitleTidy( $text, Title $title, $linestart=true)
Add wikitext in content language with a custom Title object.
setLanguageLinks(array $newLinkArray)
Reset the language links and add new language links.
addFeedLink( $format, $href)
Add a feed link to the page header.
setTitle(Title $t)
Set the Title object to use.
setCategoryLinks(array $categories)
Reset the category links (but not the category list) and add $categories.
getFileVersion()
Get the displayed file version.
setSyndicated( $show=true)
Add or remove feed links in the page header This is mainly kept for backward compatibility,...
bool $mHideNewSectionLink
bool $mIsArticle
Is the displayed content related to the source of the corresponding wiki article.
adaptCdnTTL( $mtime, $minTTL=0, $maxTTL=0)
Get TTL in [$minTTL,$maxTTL] in pass it to lowerCdnMaxage()
showsCopyright()
Return whether the standard copyright should be shown for the current page.
sendCacheControl()
Send cache control HTTP headers.
setTarget( $target)
Sets ResourceLoader target for load.php links.
addCategoryLinks(array $categories)
Add an array of categories, with names in the keys.
int $mRevisionId
To include the variable {{REVISIONID}}.
showLagWarning( $lag)
Show a warning about replica DB lag.
clearSubtitle()
Clear the subtitles.
showFatalError( $message)
Output an error page.
setCdnMaxage( $maxage)
Set the value of the "s-maxage" part of the "Cache-control" HTTP header.
getJsConfigVars()
Get the javascript config vars to include on this page.
getHTML()
Get the body HTML.
addWikiTextWithTitle( $text, Title $title, $linestart=true)
Add wikitext with a custom Title object.
addContentOverride(LinkTarget $target, Content $content)
Add a mapping from a LinkTarget to a Content, for things like page preview.
array $mMetatags
Should be private.
parseAsContent( $text, $linestart=true)
Parse wikitext in the page content language and return the HTML.
setCopyright( $hasCopyright)
Set whether the standard copyright should be shown for the current page.
addWikiTextTitle( $text, Title $title, $linestart, $tidy=false, $interface=false)
Add wikitext with a custom Title object.
array $mAllowedModules
What level of 'untrustworthiness' is allowed in CSS/JS modules loaded on this page?
addParserOutput(ParserOutput $parserOutput, $poOptions=[])
Add everything from a ParserOutput object.
getJSVars()
Get an array containing the variables to be set in mw.config in JavaScript.
bool $mPreventClickjacking
Controls if anti-clickjacking / frame-breaking headers will be sent.
haveCacheVaryCookies()
Check if the request has a cache-varying cookie header If it does, it's very important that we don't ...
isTOCEnabled()
Whether the output has a table of contents.
versionRequired( $version)
Display an error page indicating that a given version of MediaWiki is required to use it.
reduceAllowedModules( $type, $level)
Limit the highest level of CSS/JS untrustworthiness allowed.
addInlineStyle( $style_css, $flip='noflip')
Adds inline CSS styles Internal use only.
getRevisionTimestamp()
Get the timestamp of displayed revision.
preventClickjacking( $enable=true)
Set a flag which will cause an X-Frame-Options header appropriate for edit pages to be sent.
bool $mPrintable
We have to set isPrintable().
setFollowPolicy( $policy)
Set the follow policy for the page, but leave the index policy un- touched.
getHeadItemsArray()
Get an array of head items.
isPrintable()
Return whether the page is "printable".
getModules( $filter=false, $position=null, $param='mModules', $type=ResourceLoaderModule::TYPE_COMBINED)
Get the list of modules to include on this page.
setRedirectedFrom( $t)
Set $mRedirectedFrom, the Title of the page which redirected us to the current page.
getFeedAppendQuery()
Will currently always return null.
addModuleStyles( $modules)
Load the styles of one or more ResourceLoader modules on this page.
setHTMLTitle( $name)
"HTML title" means the contents of "<title>".
loadSkinModules( $sk)
Transfer styles and JavaScript modules from skin.
array $mAdditionalBodyClasses
Additional <body> classes; there are also <body> classes from other sources.
returnToMain( $unused=null, $returnto=null, $returntoquery=null)
Add a "return to" link pointing to a specified title, or the title indicated in the request,...
array $mHeadItems
Array of elements in "<head>".
isSyndicated()
Should we output feed links for this page?
$mLinkHeader
Link: header contents.
styleLink( $style, array $options)
Generate <link> tags for stylesheets.
parseAsInterface( $text, $linestart=true)
Parse wikitext in the user interface language and return the HTML.
addParserOutputMetadata(ParserOutput $parserOutput)
Add all metadata associated with a ParserOutput object, but without the actual HTML.
static transformResourcePath(Config $config, $path)
Transform path to web-accessible static resource.
string $mRedirect
array $mModuleScripts
addElement( $element, array $attribs=[], $contents='')
Shortcut for adding an Html::element via addHTML.
array $mModuleStyles
makeResourceLoaderLink( $modules, $only, array $extraQuery=[])
Explicily load or embed modules on a page.
getSubtitle()
Get the subtitle.
showPermissionsErrorPage(array $errors, $action=null)
Output a standard permission error page.
addSubtitle( $str)
Add $str to the subtitle.
static setupOOUI( $skinName='default', $dir='ltr')
Helper function to setup the PHP implementation of OOUI to use in this request.
setRevisionId( $revid)
Set the revision ID which will be seen by the wiki text parser for things such as embedded {{REVISION...
addParserOutputContent(ParserOutput $parserOutput, $poOptions=[])
Add the HTML and enhancements for it (like ResourceLoader modules) associated with a ParserOutput obj...
getPreventClickjacking()
Get the prevent-clickjacking flag.
enableOOUI()
Add ResourceLoader module styles for OOUI and set up the PHP implementation of it for use with MediaW...
getCategories( $type='all')
Get the list of category names this page belongs to.
sectionEditLinksEnabled()
getIndicators()
Get the indicators associated with this page.
$mProperties
Additional key => value data.
parseInlineAsInterface( $text, $linestart=true)
Parse wikitext in the user interface language, strip paragraph wrapper, and return the HTML.
addWikiMsgArray( $name, $args)
Add a wikitext-formatted message to the output.
addModuleScripts( $modules)
Load the scripts of one or more ResourceLoader modules, on this page.
array $contentOverrides
Map Title to Content.
addBodyClasses( $classes)
Add a class to the <body> element.
addParserOutputText(ParserOutput $parserOutput, $poOptions=[])
Add the HTML associated with a ParserOutput object, without any metadata.
warnModuleTargetFilter( $moduleName)
string $mBodytext
Contains all of the "<body>" content.
int $mContainsNewMagic
addScript( $script)
Add raw HTML to the list of scripts (including <script> tag, etc.) Internal use only.
clearHTML()
Clear the body HTML.
array $mSubtitle
Contains the page subtitle.
addVaryHeader( $header, array $option=null)
Add an HTTP header that will influence on the cache.
addStyle( $style, $media='', $condition='', $dir='')
Add a local or specified stylesheet, with the given media options.
getLinkHeader()
Return a Link: header.
setPrintable()
Set the page as printable, i.e.
getResourceLoader()
Get a ResourceLoader object associated with this OutputPage.
feedLink( $type, $url, $text)
Generate a "<link rel/>" for a feed.
bool $mNoGallery
Comes from the parser.
getModuleStyles( $filter=false, $position=null)
Get the list of style-only modules to load on this page.
enableClientCache( $state)
Use enableClientCache(false) to force it to send nocache headers.
userCanPreview()
To make it harder for someone to slip a user a fake JavaScript or CSS preview, a random token is asso...
array $limitReportJSData
Profiling data.
getFileSearchOptions()
Get the files used on this page.
addWikiTextTidy( $text, $linestart=true)
Add wikitext in content language.
addHTML( $text)
Append $text to the body HTML.
formatPermissionsErrorMessage(array $errors, $action=null)
Format a list of error messages.
string $mRevisionTimestamp
string null $copyrightUrl
The URL to send in a <link> element with rel=license.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
getHTMLTitle()
Return the "HTML title", i.e.
disallowUserJs()
Do not allow scripts which can be modified by wiki users to load on this page; only allow scripts bun...
string $mHTMLtitle
Stores contents of "<title>" tag.
addJsConfigVars( $keys, $value=null)
Add one or more variables to be set in mw.config in JavaScript.
static transformCssMedia( $media)
Transform "media" attribute based on request parameters.
enableSectionEditLinks( $flag=true)
Enables/disables section edit links, doesn't override NOEDITSECTION
setCanonicalUrl( $url)
Set the URL to be used for the <link rel=canonical>>.
ParserOptions $mParserOptions
lazy initialised, use parserOptions()
addTemplate(&$template)
Add the output of a QuickTemplate to the output buffer.
addWikiTextTitleInternal( $text, Title $title, $linestart, $tidy, $interface, $wrapperClass=null)
Add wikitext with a custom Title object.
getRlClient()
Call this to freeze the module queue and JS config and create a formatter.
static transformFilePath( $remotePathPrefix, $localPath, $file)
Utility method for transformResourceFilePath().
callable[] $contentOverrideCallbacks
int $mCdnMaxageLimit
Upper limit on mCdnMaxage.
ResourceLoaderClientHtml $rlClient
setArticleBodyOnly( $only)
Set whether the output should only contain the body of the article, without any skin,...
showFileDeleteError( $name)
addInlineScript( $script)
Add a self-contained script tag with the given contents Internal use only.
addAcceptLanguage()
T23672: Add Accept-Language to Vary and Key headers if there's no 'variant' parameter in GET.
setStatusCode( $statusCode)
Set the HTTP status code to send with the output.
getCacheVaryCookies()
Get the list of cookie names that will influence the cache.
ResourceLoaderContext $rlClientContext
showErrorPage( $title, $msg, $params=[])
Output a standard error page.
getLinkTags()
Returns the current <link> tags.
getArticleBodyOnly()
Return whether the output will contain only the body of the article.
setProperty( $name, $value)
Set an additional output property.
setRevisionTimestamp( $timestamp)
Set the timestamp of the revision which will be displayed.
checkLastModified( $timestamp)
checkLastModified tells the client to use the client-cached page if possible.
getVaryHeader()
Return a Vary: header on which to vary caches.
string $mLastModified
Used for sending cache control.
addWikiTextAsContent( $text, $linestart=true, Title $title=null)
Convert wikitext in the page content language to HTML and add it to the buffer.
showNewSectionLink()
Show an "add new section" link?
bool $mDoNothing
Whether output is disabled.
string $mPageTitle
The contents of.
string $mPageLinkTitle
Used by skin template.
addHeadItem( $name, $value)
Add or replace a head item to the output.
getRevisionId()
Get the displayed revision ID.
setIndicators(array $indicators)
Add an array of indicators, with their identifiers as array keys and HTML contents as values.
bool $mEnableTOC
Whether parser output contains a table of contents.
Title $mRedirectedFrom
If the current page was reached through a redirect, $mRedirectedFrom contains the Title of the redire...
lowerCdnMaxage( $maxage)
Set the value of the "s-maxage" part of the "Cache-control" HTTP header to $maxage if that is lower t...
forceHideNewSectionLink()
Forcibly hide the new section link?
bool $mHasCopyright
Is the content subject to copyright.
string null $mTarget
ResourceLoader target for load.php links.
int $mCdnMaxage
Cache stuff.
addBacklinkSubtitle(Title $title, $query=[])
Add a subtitle containing a backlink to a page.
array $mTemplateIds
getAllowedModules( $type)
Show what level of JavaScript / CSS untrustworthiness is allowed on this page.
parserOptions( $options=null)
Get/set the ParserOptions object to use for wikitext parsing.
string $displayTitle
The displayed title of the page.
prependHTML( $text)
Prepend $text to the body HTML.
addLanguageLinks(array $newLinkArray)
Add new language links.
array $mLanguageLinks
Array of Interwiki Prefixed (non DB key) Titles (e.g.
wrapWikiMsg( $wrap)
This function takes a number of message/argument specifications, wraps them in some overall structure...
setArticleRelated( $newVal)
Set whether this page is related an article on the wiki Setting false will cause the change of "artic...
string $mInlineStyles
Inline CSS styles.
array $mFileVersion
addModules( $modules)
Load one or more ResourceLoader modules on this page.
buildExemptModules()
Build exempt modules and legacy non-ResourceLoader styles.
string $mRedirectCode
getProperty( $name)
Get an additional output property.
addWikiTextAsInterface( $text, $linestart=true, Title $title=null)
Convert wikitext in the user interface language to HTML and add it to the buffer.
headElement(Skin $sk, $includeStyle=true)
prepareErrorPage( $pageTitle, $htmlTitle=false)
Prepare this object to display an error page; disable caching and indexing, clear the current text an...
array $mCategoryLinks
addContentOverrideCallback(callable $callback)
Add a callback for mapping from a Title to a Content object, for things like page preview.
static buildBacklinkSubtitle(Title $title, $query=[])
Build message object for a subtitle containing a backlink to a page.
setFeedAppendQuery( $val)
Add default feeds to the page header This is mainly kept for backward compatibility,...
getBottomScripts()
JS stuff to put at the bottom of the <body>.
addReturnTo( $title, array $query=[], $text=null, $options=[])
Add a "return to" link pointing to a specified title.
getMetaTags()
Returns the current <meta> tags.
bool $mArticleBodyOnly
Flag if output should only contain the body of the article.
setSubtitle( $str)
Replace the subtitle with $str.
array $rlExemptStyleModules
getKeyHeader()
Get a complete Key header.
getCategoryLinks()
Get the list of category links, in a 2-D array with the following format: $arr[$type][] = $link,...
getFrameOptions()
Get the X-Frame-Options header value (without the name part), or false if there isn't one.
addHeadItems( $values)
Add one or more head items to the output.
static combineWrappedStrings(array $chunks)
Combine WrappedString chunks and filter out empty ones.
bool $mIsArticleRelated
Stores "article flag" toggle.
array $mCategories
parse( $text, $linestart=true, $interface=false, $language=null)
Parse wikitext and return the HTML.
getUnprefixedDisplayTitle()
Returns page display title without namespace prefix if possible.
getCSPNonce()
Get (and set if not yet set) the CSP nonce.
array $mVaryHeader
Headers that cause the cache to vary.
hasHeadItem( $name)
Check if the header item $name is already set.
array $styles
An array of stylesheet filenames (relative from skins path), with options for CSS media,...
isDisabled()
Return whether the output will be completely disabled.
Set options of the Parser.
preventClickjacking( $flag=null)
Get or set the prevent-clickjacking flag.
getText( $options=[])
Get the output HTML.
Bootstrap a ResourceLoader client on an HTML page.
setModules(array $modules)
Ensure one or more modules are loaded.
static makeLoad(ResourceLoaderContext $mainContext, array $modules, $only, array $extraQuery=[], $nonce=null)
Explicily load or embed modules on a page.
setModuleScripts(array $modules)
Ensure the scripts of one or more modules are loaded.
setConfig(array $vars)
Set mw.config variables.
setExemptStates(array $states)
Set state of special modules that are handled by the caller manually.
setModuleStyles(array $modules)
Ensure the styles of one or more modules are loaded.
Object passed around to modules which contains information about the state of a specific loader reque...
static newDummyContext()
Return a dummy ResourceLoaderContext object suitable for passing into things that don't "really" need...
Abstraction for ResourceLoader modules, with name registration and maxage functionality.
getOrigin()
Get this module's origin.
Dynamic JavaScript and CSS resource loading system.
static makeInlineScript( $script, $nonce=null)
Returns an HTML script tag that runs given JS code after startup and base modules.
static inDebugMode()
Determine whether debug mode was requested Order of priority is 1) request param, 2) cookie,...
static makeConfigSetScript(array $configuration)
Returns JS code which will set the MediaWiki configuration array to the given value.
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.
The main skin class which provides methods and properties for all other skins.
Definition Skin.php:38
getHtmlElementAttributes()
Return values for <html> element.
Definition Skin.php:471
getSkinName()
Definition Skin.php:155
addToBodyAttributes( $out, &$bodyAttrs)
This will be called by OutputPage::headElement when it is creating the "<body>" tag,...
Definition Skin.php:487
getPageClasses( $title)
TODO: document.
Definition Skin.php:442
Represents a title within MediaWiki.
Definition Title.php:39
static groupHasPermission( $group, $role)
Check, if the given group has the given permission.
Definition User.php:5013
$res
Definition database.txt:21
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
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
while(( $__line=Maintenance::readconsole()) !==false) print
Definition eval.php:64
const PROTO_CANONICAL
Definition Defines.php:223
const PROTO_CURRENT
Definition Defines.php:222
const NS_SPECIAL
Definition Defines.php:53
const PROTO_RELATIVE
Definition Defines.php:221
const NS_CATEGORY
Definition Defines.php:78
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition hooks.txt:2278
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
Definition hooks.txt:2880
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:181
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:861
either a plain
Definition hooks.txt:2105
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition hooks.txt:2050
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition hooks.txt:2885
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:994
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:895
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title e g db for database replication lag or jobqueue for job queue size converted to pseudo seconds It is possible to add more fields and they will be returned to the user in the API response after the basic globals have been set but before ordinary actions take place or wrap services the preferred way to define a new service is the $wgServiceWiringFiles array $services
Definition hooks.txt:2335
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt;div ...>$1&lt;/div>"). - flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException':Called before an exception(or PHP error) is logged. This is meant for integration with external error aggregation services
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:2062
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition hooks.txt:3106
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:302
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:2063
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext such as when responding to a resource loader request or generating HTML output & $resourceLoader
Definition hooks.txt:2892
this hook is for auditing only $response
Definition hooks.txt:813
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:1656
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing after in associative array form before processing starts Return false to skip default processing and return $ret $linkRenderer
Definition hooks.txt:2105
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:247
passed in as a query string parameter to the various URLs constructed here(i.e. $prevlink) $ldel you ll need to handle error messages
Definition hooks.txt:1329
returning false will NOT prevent logging $e
Definition hooks.txt:2226
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:37
Interface for configuration instances.
Definition Config.php:28
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
Base interface for content objects.
Definition Content.php:34
Interface for objects which can provide a MediaWiki context on request.
getNamespace()
Get the namespace index.
getDBkey()
Get the main part with underscores.
Result wrapper for grabbing data queried from an IDatabase object.
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
$content
const DB_REPLICA
Definition defines.php:25
$params
if(!isset( $args[0])) $lang
$header