MediaWiki REL1_31
OutputPage.php
Go to the documentation of this file.
1<?php
26use Wikimedia\RelPath;
27use Wikimedia\WrappedString;
28use Wikimedia\WrappedStringList;
29
47 protected $mMetatags = [];
48
50 protected $mLinktags = [];
51
53 protected $mCanonicalUrl = false;
54
58 public $mPagetitle = '';
59
64 public $mBodytext = '';
65
67 private $mHTMLtitle = '';
68
73 private $mIsarticle = false;
74
76 private $mIsArticleRelated = true;
77
82 private $mPrintable = false;
83
88 private $mSubtitle = [];
89
91 public $mRedirect = '';
92
94 protected $mStatusCode;
95
100 protected $mLastModified = '';
101
103 protected $mCategoryLinks = [];
104
106 protected $mCategories = [
107 'hidden' => [],
108 'normal' => [],
109 ];
110
112 protected $mIndicators = [];
113
115 private $mLanguageLinks = [];
116
123 private $mScripts = '';
124
126 protected $mInlineStyles = '';
127
132 public $mPageLinkTitle = '';
133
135 protected $mHeadItems = [];
136
139
141 protected $mModules = [];
142
144 protected $mModuleScripts = [];
145
147 protected $mModuleStyles = [];
148
151
153 private $rlClient;
154
157
160
163
165 protected $mJsConfigVars = [];
166
168 protected $mTemplateIds = [];
169
171 protected $mImageTimeKeys = [];
172
174 public $mRedirectCode = '';
175
176 protected $mFeedLinksAppendQuery = null;
177
183 protected $mAllowedModules = [
185 ];
186
188 protected $mDoNothing = false;
189
190 // Parser related.
191
193 protected $mContainsNewMagic = 0;
194
199 protected $mParserOptions = null;
200
206 private $mFeedLinks = [];
207
208 // Gwicke work on squid caching? Roughly from 2003.
209 protected $mEnableClientCache = true;
210
212 private $mArticleBodyOnly = false;
213
215 protected $mNewSectionLink = false;
216
218 protected $mHideNewSectionLink = false;
219
225 public $mNoGallery = false;
226
229
231 protected $mCdnMaxage = 0;
233 protected $mCdnMaxageLimit = INF;
234
240 protected $mPreventClickjacking = true;
241
243 private $mRevisionId = null;
244
246 private $mRevisionTimestamp = null;
247
249 protected $mFileVersion = null;
250
259 protected $styles = [];
260
261 private $mIndexPolicy = 'index';
262 private $mFollowPolicy = 'follow';
263 private $mVaryHeader = [
264 'Accept-Encoding' => [ 'match=gzip' ],
265 ];
266
273 private $mRedirectedFrom = null;
274
278 private $mProperties = [];
279
283 private $mTarget = null;
284
288 private $mEnableTOC = false;
289
294
296 private $limitReportJSData = [];
297
301 private $mLinkHeader = [];
302
310 $this->setContext( $context );
311 }
312
319 public function redirect( $url, $responsecode = '302' ) {
320 # Strip newlines as a paranoia check for header injection in PHP<5.1.2
321 $this->mRedirect = str_replace( "\n", '', $url );
322 $this->mRedirectCode = $responsecode;
323 }
324
330 public function getRedirect() {
331 return $this->mRedirect;
332 }
333
342 public function setCopyrightUrl( $url ) {
343 $this->copyrightUrl = $url;
344 }
345
351 public function setStatusCode( $statusCode ) {
352 $this->mStatusCode = $statusCode;
353 }
354
362 function addMeta( $name, $val ) {
363 array_push( $this->mMetatags, [ $name, $val ] );
364 }
365
372 public function getMetaTags() {
373 return $this->mMetatags;
374 }
375
383 function addLink( array $linkarr ) {
384 array_push( $this->mLinktags, $linkarr );
385 }
386
393 public function getLinkTags() {
394 return $this->mLinktags;
395 }
396
404 function addMetadataLink( array $linkarr ) {
405 $linkarr['rel'] = $this->getMetadataAttribute();
406 $this->addLink( $linkarr );
407 }
408
414 function setCanonicalUrl( $url ) {
415 $this->mCanonicalUrl = $url;
416 }
417
425 public function getCanonicalUrl() {
426 return $this->mCanonicalUrl;
427 }
428
434 public function getMetadataAttribute() {
435 # note: buggy CC software only reads first "meta" link
436 static $haveMeta = false;
437 if ( $haveMeta ) {
438 return 'alternate meta';
439 } else {
440 $haveMeta = true;
441 return 'meta';
442 }
443 }
444
452 function addScript( $script ) {
453 $this->mScripts .= $script;
454 }
455
464 public function addScriptFile( $file, $version = null ) {
465 // See if $file parameter is an absolute URL or begins with a slash
466 if ( substr( $file, 0, 1 ) == '/' || preg_match( '#^[a-z]*://#i', $file ) ) {
467 $path = $file;
468 } else {
469 $path = $this->getConfig()->get( 'StylePath' ) . "/common/{$file}";
470 }
471 if ( is_null( $version ) ) {
472 $version = $this->getConfig()->get( 'StyleVersion' );
473 }
474 $this->addScript( Html::linkedScript( wfAppendQuery( $path, $version ) ) );
475 }
476
483 public function addInlineScript( $script ) {
484 $this->mScripts .= Html::inlineScript( $script );
485 }
486
495 protected function filterModules( array $modules, $position = null,
497 ) {
499 $filteredModules = [];
500 foreach ( $modules as $val ) {
501 $module = $resourceLoader->getModule( $val );
502 if ( $module instanceof ResourceLoaderModule
503 && $module->getOrigin() <= $this->getAllowedModules( $type )
504 ) {
505 if ( $this->mTarget && !in_array( $this->mTarget, $module->getTargets() ) ) {
506 $this->warnModuleTargetFilter( $module->getName() );
507 continue;
508 }
509 $filteredModules[] = $val;
510 }
511 }
512 return $filteredModules;
513 }
514
515 private function warnModuleTargetFilter( $moduleName ) {
516 static $warnings = [];
517 if ( isset( $warnings[$this->mTarget][$moduleName] ) ) {
518 return;
519 }
520 $warnings[$this->mTarget][$moduleName] = true;
521 $this->getResourceLoader()->getLogger()->debug(
522 'Module "{module}" not loadable on target "{target}".',
523 [
524 'module' => $moduleName,
525 'target' => $this->mTarget,
526 ]
527 );
528 }
529
539 public function getModules( $filter = false, $position = null, $param = 'mModules',
541 ) {
542 $modules = array_values( array_unique( $this->$param ) );
543 return $filter
544 ? $this->filterModules( $modules, null, $type )
545 : $modules;
546 }
547
555 public function addModules( $modules ) {
556 $this->mModules = array_merge( $this->mModules, (array)$modules );
557 }
558
566 public function getModuleScripts( $filter = false, $position = null ) {
567 return $this->getModules( $filter, null, 'mModuleScripts',
569 );
570 }
571
579 public function addModuleScripts( $modules ) {
580 $this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules );
581 }
582
590 public function getModuleStyles( $filter = false, $position = null ) {
591 return $this->getModules( $filter, null, 'mModuleStyles',
593 );
594 }
595
605 public function addModuleStyles( $modules ) {
606 $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
607 }
608
612 public function getTarget() {
613 return $this->mTarget;
614 }
615
621 public function setTarget( $target ) {
622 $this->mTarget = $target;
623 }
624
630 function getHeadItemsArray() {
631 return $this->mHeadItems;
632 }
633
646 public function addHeadItem( $name, $value ) {
647 $this->mHeadItems[$name] = $value;
648 }
649
656 public function addHeadItems( $values ) {
657 $this->mHeadItems = array_merge( $this->mHeadItems, (array)$values );
658 }
659
666 public function hasHeadItem( $name ) {
667 return isset( $this->mHeadItems[$name] );
668 }
669
676 public function addBodyClasses( $classes ) {
677 $this->mAdditionalBodyClasses = array_merge( $this->mAdditionalBodyClasses, (array)$classes );
678 }
679
687 public function setArticleBodyOnly( $only ) {
688 $this->mArticleBodyOnly = $only;
689 }
690
696 public function getArticleBodyOnly() {
697 return $this->mArticleBodyOnly;
698 }
699
707 public function setProperty( $name, $value ) {
708 $this->mProperties[$name] = $value;
709 }
710
718 public function getProperty( $name ) {
719 if ( isset( $this->mProperties[$name] ) ) {
720 return $this->mProperties[$name];
721 } else {
722 return null;
723 }
724 }
725
737 public function checkLastModified( $timestamp ) {
738 if ( !$timestamp || $timestamp == '19700101000000' ) {
739 wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
740 return false;
741 }
742 $config = $this->getConfig();
743 if ( !$config->get( 'CachePages' ) ) {
744 wfDebug( __METHOD__ . ": CACHE DISABLED\n" );
745 return false;
746 }
747
748 $timestamp = wfTimestamp( TS_MW, $timestamp );
749 $modifiedTimes = [
750 'page' => $timestamp,
751 'user' => $this->getUser()->getTouched(),
752 'epoch' => $config->get( 'CacheEpoch' )
753 ];
754 if ( $config->get( 'UseSquid' ) ) {
755 // T46570: the core page itself may not change, but resources might
756 $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, time() - $config->get( 'SquidMaxage' ) );
757 }
758 Hooks::run( 'OutputPageCheckLastModified', [ &$modifiedTimes, $this ] );
759
760 $maxModified = max( $modifiedTimes );
761 $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
762
763 $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' );
764 if ( $clientHeader === false ) {
765 wfDebug( __METHOD__ . ": client did not send If-Modified-Since header", 'private' );
766 return false;
767 }
768
769 # IE sends sizes after the date like this:
770 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
771 # this breaks strtotime().
772 $clientHeader = preg_replace( '/;.*$/', '', $clientHeader );
773
774 Wikimedia\suppressWarnings(); // E_STRICT system time bitching
775 $clientHeaderTime = strtotime( $clientHeader );
776 Wikimedia\restoreWarnings();
777 if ( !$clientHeaderTime ) {
778 wfDebug( __METHOD__
779 . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
780 return false;
781 }
782 $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
783
784 # Make debug info
785 $info = '';
786 foreach ( $modifiedTimes as $name => $value ) {
787 if ( $info !== '' ) {
788 $info .= ', ';
789 }
790 $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
791 }
792
793 wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
794 wfTimestamp( TS_ISO_8601, $clientHeaderTime ), 'private' );
795 wfDebug( __METHOD__ . ": effective Last-Modified: " .
796 wfTimestamp( TS_ISO_8601, $maxModified ), 'private' );
797 if ( $clientHeaderTime < $maxModified ) {
798 wfDebug( __METHOD__ . ": STALE, $info", 'private' );
799 return false;
800 }
801
802 # Not modified
803 # Give a 304 Not Modified response code and disable body output
804 wfDebug( __METHOD__ . ": NOT MODIFIED, $info", 'private' );
805 ini_set( 'zlib.output_compression', 0 );
806 $this->getRequest()->response()->statusHeader( 304 );
807 $this->sendCacheControl();
808 $this->disable();
809
810 // Don't output a compressed blob when using ob_gzhandler;
811 // it's technically against HTTP spec and seems to confuse
812 // Firefox when the response gets split over two packets.
814
815 return true;
816 }
817
824 public function setLastModified( $timestamp ) {
825 $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp );
826 }
827
836 public function setRobotPolicy( $policy ) {
837 $policy = Article::formatRobotPolicy( $policy );
838
839 if ( isset( $policy['index'] ) ) {
840 $this->setIndexPolicy( $policy['index'] );
841 }
842 if ( isset( $policy['follow'] ) ) {
843 $this->setFollowPolicy( $policy['follow'] );
844 }
845 }
846
854 public function setIndexPolicy( $policy ) {
855 $policy = trim( $policy );
856 if ( in_array( $policy, [ 'index', 'noindex' ] ) ) {
857 $this->mIndexPolicy = $policy;
858 }
859 }
860
868 public function setFollowPolicy( $policy ) {
869 $policy = trim( $policy );
870 if ( in_array( $policy, [ 'follow', 'nofollow' ] ) ) {
871 $this->mFollowPolicy = $policy;
872 }
873 }
874
881 public function setPageTitleActionText( $text ) {
882 $this->mPageTitleActionText = $text;
883 }
884
890 public function getPageTitleActionText() {
891 return $this->mPageTitleActionText;
892 }
893
900 public function setHTMLTitle( $name ) {
901 if ( $name instanceof Message ) {
902 $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
903 } else {
904 $this->mHTMLtitle = $name;
905 }
906 }
907
913 public function getHTMLTitle() {
914 return $this->mHTMLtitle;
915 }
916
922 public function setRedirectedFrom( $t ) {
923 $this->mRedirectedFrom = $t;
924 }
925
936 public function setPageTitle( $name ) {
937 if ( $name instanceof Message ) {
938 $name = $name->setContext( $this->getContext() )->text();
939 }
940
941 # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
942 # but leave "<i>foobar</i>" alone
943 $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) );
944 $this->mPagetitle = $nameWithTags;
945
946 # change "<i>foo&amp;bar</i>" to "foo&bar"
947 $this->setHTMLTitle(
948 $this->msg( 'pagetitle' )->rawParams( Sanitizer::stripAllTags( $nameWithTags ) )
949 ->inContentLanguage()
950 );
951 }
952
958 public function getPageTitle() {
959 return $this->mPagetitle;
960 }
961
967 public function setTitle( Title $t ) {
968 $this->getContext()->setTitle( $t );
969 }
970
976 public function setSubtitle( $str ) {
977 $this->clearSubtitle();
978 $this->addSubtitle( $str );
979 }
980
986 public function addSubtitle( $str ) {
987 if ( $str instanceof Message ) {
988 $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
989 } else {
990 $this->mSubtitle[] = $str;
991 }
992 }
993
1002 public static function buildBacklinkSubtitle( Title $title, $query = [] ) {
1003 if ( $title->isRedirect() ) {
1004 $query['redirect'] = 'no';
1005 }
1006 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1007 return wfMessage( 'backlinksubtitle' )
1008 ->rawParams( $linkRenderer->makeLink( $title, null, [], $query ) );
1009 }
1010
1017 public function addBacklinkSubtitle( Title $title, $query = [] ) {
1018 $this->addSubtitle( self::buildBacklinkSubtitle( $title, $query ) );
1019 }
1020
1024 public function clearSubtitle() {
1025 $this->mSubtitle = [];
1026 }
1027
1033 public function getSubtitle() {
1034 return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
1035 }
1036
1041 public function setPrintable() {
1042 $this->mPrintable = true;
1043 }
1044
1050 public function isPrintable() {
1051 return $this->mPrintable;
1052 }
1053
1057 public function disable() {
1058 $this->mDoNothing = true;
1059 }
1060
1066 public function isDisabled() {
1067 return $this->mDoNothing;
1068 }
1069
1075 public function showNewSectionLink() {
1076 return $this->mNewSectionLink;
1077 }
1078
1084 public function forceHideNewSectionLink() {
1085 return $this->mHideNewSectionLink;
1086 }
1087
1096 public function setSyndicated( $show = true ) {
1097 if ( $show ) {
1098 $this->setFeedAppendQuery( false );
1099 } else {
1100 $this->mFeedLinks = [];
1101 }
1102 }
1103
1113 public function setFeedAppendQuery( $val ) {
1114 $this->mFeedLinks = [];
1115
1116 foreach ( $this->getConfig()->get( 'AdvertisedFeedTypes' ) as $type ) {
1117 $query = "feed=$type";
1118 if ( is_string( $val ) ) {
1119 $query .= '&' . $val;
1120 }
1121 $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
1122 }
1123 }
1124
1131 public function addFeedLink( $format, $href ) {
1132 if ( in_array( $format, $this->getConfig()->get( 'AdvertisedFeedTypes' ) ) ) {
1133 $this->mFeedLinks[$format] = $href;
1134 }
1135 }
1136
1141 public function isSyndicated() {
1142 return count( $this->mFeedLinks ) > 0;
1143 }
1144
1149 public function getSyndicationLinks() {
1150 return $this->mFeedLinks;
1151 }
1152
1158 public function getFeedAppendQuery() {
1159 return $this->mFeedLinksAppendQuery;
1160 }
1161
1169 public function setArticleFlag( $v ) {
1170 $this->mIsarticle = $v;
1171 if ( $v ) {
1172 $this->mIsArticleRelated = $v;
1173 }
1174 }
1175
1182 public function isArticle() {
1183 return $this->mIsarticle;
1184 }
1185
1192 public function setArticleRelated( $v ) {
1193 $this->mIsArticleRelated = $v;
1194 if ( !$v ) {
1195 $this->mIsarticle = false;
1196 }
1197 }
1198
1204 public function isArticleRelated() {
1205 return $this->mIsArticleRelated;
1206 }
1207
1214 public function addLanguageLinks( array $newLinkArray ) {
1215 $this->mLanguageLinks += $newLinkArray;
1216 }
1217
1224 public function setLanguageLinks( array $newLinkArray ) {
1225 $this->mLanguageLinks = $newLinkArray;
1226 }
1227
1233 public function getLanguageLinks() {
1234 return $this->mLanguageLinks;
1235 }
1236
1242 public function addCategoryLinks( array $categories ) {
1244
1245 if ( !is_array( $categories ) || count( $categories ) == 0 ) {
1246 return;
1247 }
1248
1249 $res = $this->addCategoryLinksToLBAndGetResult( $categories );
1250
1251 # Set all the values to 'normal'.
1252 $categories = array_fill_keys( array_keys( $categories ), 'normal' );
1253
1254 # Mark hidden categories
1255 foreach ( $res as $row ) {
1256 if ( isset( $row->pp_value ) ) {
1257 $categories[$row->page_title] = 'hidden';
1258 }
1259 }
1260
1261 // Avoid PHP 7.1 warning of passing $this by reference
1262 $outputPage = $this;
1263 # Add the remaining categories to the skin
1264 if ( Hooks::run(
1265 'OutputPageMakeCategoryLinks',
1266 [ &$outputPage, $categories, &$this->mCategoryLinks ] )
1267 ) {
1268 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1269 foreach ( $categories as $category => $type ) {
1270 // array keys will cast numeric category names to ints, so cast back to string
1271 $category = (string)$category;
1272 $origcategory = $category;
1273 $title = Title::makeTitleSafe( NS_CATEGORY, $category );
1274 if ( !$title ) {
1275 continue;
1276 }
1277 $wgContLang->findVariantLink( $category, $title, true );
1278 if ( $category != $origcategory && array_key_exists( $category, $categories ) ) {
1279 continue;
1280 }
1281 $text = $wgContLang->convertHtml( $title->getText() );
1282 $this->mCategories[$type][] = $title->getText();
1283 $this->mCategoryLinks[$type][] = $linkRenderer->makeLink( $title, new HtmlArmor( $text ) );
1284 }
1285 }
1286 }
1287
1292 protected function addCategoryLinksToLBAndGetResult( array $categories ) {
1293 # Add the links to a LinkBatch
1294 $arr = [ NS_CATEGORY => $categories ];
1295 $lb = new LinkBatch;
1296 $lb->setArray( $arr );
1297
1298 # Fetch existence plus the hiddencat property
1299 $dbr = wfGetDB( DB_REPLICA );
1300 $fields = array_merge(
1301 LinkCache::getSelectFields(),
1302 [ 'page_namespace', 'page_title', 'pp_value' ]
1303 );
1304
1305 $res = $dbr->select( [ 'page', 'page_props' ],
1306 $fields,
1307 $lb->constructSet( 'page', $dbr ),
1308 __METHOD__,
1309 [],
1310 [ 'page_props' => [ 'LEFT JOIN', [
1311 'pp_propname' => 'hiddencat',
1312 'pp_page = page_id'
1313 ] ] ]
1314 );
1315
1316 # Add the results to the link cache
1317 $lb->addResultToCache( LinkCache::singleton(), $res );
1318
1319 return $res;
1320 }
1321
1327 public function setCategoryLinks( array $categories ) {
1328 $this->mCategoryLinks = [];
1329 $this->addCategoryLinks( $categories );
1330 }
1331
1340 public function getCategoryLinks() {
1341 return $this->mCategoryLinks;
1342 }
1343
1353 public function getCategories( $type = 'all' ) {
1354 if ( $type === 'all' ) {
1355 $allCategories = [];
1356 foreach ( $this->mCategories as $categories ) {
1357 $allCategories = array_merge( $allCategories, $categories );
1358 }
1359 return $allCategories;
1360 }
1361 if ( !isset( $this->mCategories[$type] ) ) {
1362 throw new InvalidArgumentException( 'Invalid category type given: ' . $type );
1363 }
1364 return $this->mCategories[$type];
1365 }
1366
1376 public function setIndicators( array $indicators ) {
1377 $this->mIndicators = $indicators + $this->mIndicators;
1378 // Keep ordered by key
1379 ksort( $this->mIndicators );
1380 }
1381
1390 public function getIndicators() {
1391 return $this->mIndicators;
1392 }
1393
1402 public function addHelpLink( $to, $overrideBaseUrl = false ) {
1403 $this->addModuleStyles( 'mediawiki.helplink' );
1404 $text = $this->msg( 'helppage-top-gethelp' )->escaped();
1405
1406 if ( $overrideBaseUrl ) {
1407 $helpUrl = $to;
1408 } else {
1409 $toUrlencoded = wfUrlencode( str_replace( ' ', '_', $to ) );
1410 $helpUrl = "//www.mediawiki.org/wiki/Special:MyLanguage/$toUrlencoded";
1411 }
1412
1413 $link = Html::rawElement(
1414 'a',
1415 [
1416 'href' => $helpUrl,
1417 'target' => '_blank',
1418 'class' => 'mw-helplink',
1419 ],
1420 $text
1421 );
1422
1423 $this->setIndicators( [ 'mw-helplink' => $link ] );
1424 }
1425
1434 public function disallowUserJs() {
1435 $this->reduceAllowedModules(
1438 );
1439
1440 // Site-wide styles are controlled by a config setting, see T73621
1441 // for background on why. User styles are never allowed.
1442 if ( $this->getConfig()->get( 'AllowSiteCSSOnRestrictedPages' ) ) {
1444 } else {
1446 }
1447 $this->reduceAllowedModules(
1449 $styleOrigin
1450 );
1451 }
1452
1459 public function getAllowedModules( $type ) {
1461 return min( array_values( $this->mAllowedModules ) );
1462 } else {
1463 return isset( $this->mAllowedModules[$type] )
1464 ? $this->mAllowedModules[$type]
1466 }
1467 }
1468
1478 public function reduceAllowedModules( $type, $level ) {
1479 $this->mAllowedModules[$type] = min( $this->getAllowedModules( $type ), $level );
1480 }
1481
1487 public function prependHTML( $text ) {
1488 $this->mBodytext = $text . $this->mBodytext;
1489 }
1490
1496 public function addHTML( $text ) {
1497 $this->mBodytext .= $text;
1498 }
1499
1509 public function addElement( $element, array $attribs = [], $contents = '' ) {
1510 $this->addHTML( Html::element( $element, $attribs, $contents ) );
1511 }
1512
1516 public function clearHTML() {
1517 $this->mBodytext = '';
1518 }
1519
1525 public function getHTML() {
1526 return $this->mBodytext;
1527 }
1528
1536 public function parserOptions( $options = null ) {
1537 if ( $options !== null ) {
1538 wfDeprecated( __METHOD__ . ' with non-null $options', '1.31' );
1539 }
1540
1541 if ( $options !== null && !empty( $options->isBogus ) ) {
1542 // Someone is trying to set a bogus pre-$wgUser PO. Check if it has
1543 // been changed somehow, and keep it if so.
1544 $anonPO = ParserOptions::newFromAnon();
1545 $anonPO->setAllowUnsafeRawHtml( false );
1546 if ( !$options->matches( $anonPO ) ) {
1547 wfLogWarning( __METHOD__ . ': Setting a changed bogus ParserOptions: ' . wfGetAllCallers( 5 ) );
1548 $options->isBogus = false;
1549 }
1550 }
1551
1552 if ( !$this->mParserOptions ) {
1553 if ( !$this->getContext()->getUser()->isSafeToLoad() ) {
1554 // $wgUser isn't unstubbable yet, so don't try to get a
1555 // ParserOptions for it. And don't cache this ParserOptions
1556 // either.
1557 $po = ParserOptions::newFromAnon();
1558 $po->setAllowUnsafeRawHtml( false );
1559 $po->isBogus = true;
1560 if ( $options !== null ) {
1561 $this->mParserOptions = empty( $options->isBogus ) ? $options : null;
1562 }
1563 return $po;
1564 }
1565
1566 $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
1567 $this->mParserOptions->setAllowUnsafeRawHtml( false );
1568 }
1569
1570 if ( $options !== null && !empty( $options->isBogus ) ) {
1571 // They're trying to restore the bogus pre-$wgUser PO. Do the right
1572 // thing.
1573 return wfSetVar( $this->mParserOptions, null, true );
1574 } else {
1575 return wfSetVar( $this->mParserOptions, $options );
1576 }
1577 }
1578
1586 public function setRevisionId( $revid ) {
1587 $val = is_null( $revid ) ? null : intval( $revid );
1588 return wfSetVar( $this->mRevisionId, $val );
1589 }
1590
1596 public function getRevisionId() {
1597 return $this->mRevisionId;
1598 }
1599
1607 public function setRevisionTimestamp( $timestamp ) {
1608 return wfSetVar( $this->mRevisionTimestamp, $timestamp );
1609 }
1610
1617 public function getRevisionTimestamp() {
1618 return $this->mRevisionTimestamp;
1619 }
1620
1627 public function setFileVersion( $file ) {
1628 $val = null;
1629 if ( $file instanceof File && $file->exists() ) {
1630 $val = [ 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ];
1631 }
1632 return wfSetVar( $this->mFileVersion, $val, true );
1633 }
1634
1640 public function getFileVersion() {
1641 return $this->mFileVersion;
1642 }
1643
1650 public function getTemplateIds() {
1651 return $this->mTemplateIds;
1652 }
1653
1660 public function getFileSearchOptions() {
1661 return $this->mImageTimeKeys;
1662 }
1663
1673 public function addWikiText( $text, $linestart = true, $interface = true ) {
1674 $title = $this->getTitle(); // Work around E_STRICT
1675 if ( !$title ) {
1676 throw new MWException( 'Title is null' );
1677 }
1678 $this->addWikiTextTitle( $text, $title, $linestart, /*tidy*/false, $interface );
1679 }
1680
1688 public function addWikiTextWithTitle( $text, &$title, $linestart = true ) {
1689 $this->addWikiTextTitle( $text, $title, $linestart );
1690 }
1691
1699 function addWikiTextTitleTidy( $text, &$title, $linestart = true ) {
1700 $this->addWikiTextTitle( $text, $title, $linestart, true );
1701 }
1702
1709 public function addWikiTextTidy( $text, $linestart = true ) {
1710 $title = $this->getTitle();
1711 $this->addWikiTextTitleTidy( $text, $title, $linestart );
1712 }
1713
1724 public function addWikiTextTitle( $text, Title $title, $linestart,
1725 $tidy = false, $interface = false
1726 ) {
1728
1729 $popts = $this->parserOptions();
1730 $oldTidy = $popts->setTidy( $tidy );
1731 $popts->setInterfaceMessage( (bool)$interface );
1732
1733 $parserOutput = $wgParser->getFreshParser()->parse(
1734 $text, $title, $popts,
1735 $linestart, true, $this->mRevisionId
1736 );
1737
1738 $popts->setTidy( $oldTidy );
1739
1740 $this->addParserOutput( $parserOutput, [
1741 'enableSectionEditLinks' => false,
1742 ] );
1743 }
1744
1753 public function addParserOutputMetadata( $parserOutput ) {
1754 $this->mLanguageLinks += $parserOutput->getLanguageLinks();
1755 $this->addCategoryLinks( $parserOutput->getCategories() );
1756 $this->setIndicators( $parserOutput->getIndicators() );
1757 $this->mNewSectionLink = $parserOutput->getNewSection();
1758 $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
1759
1760 if ( !$parserOutput->isCacheable() ) {
1761 $this->enableClientCache( false );
1762 }
1763 $this->mNoGallery = $parserOutput->getNoGallery();
1764 $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
1765 $this->addModules( $parserOutput->getModules() );
1766 $this->addModuleScripts( $parserOutput->getModuleScripts() );
1767 $this->addModuleStyles( $parserOutput->getModuleStyles() );
1768 $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1769 $this->mPreventClickjacking = $this->mPreventClickjacking
1770 || $parserOutput->preventClickjacking();
1771
1772 // Template versioning...
1773 foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
1774 if ( isset( $this->mTemplateIds[$ns] ) ) {
1775 $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
1776 } else {
1777 $this->mTemplateIds[$ns] = $dbks;
1778 }
1779 }
1780 // File versioning...
1781 foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) {
1782 $this->mImageTimeKeys[$dbk] = $data;
1783 }
1784
1785 // Hooks registered in the object
1786 $parserOutputHooks = $this->getConfig()->get( 'ParserOutputHooks' );
1787 foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
1788 list( $hookName, $data ) = $hookInfo;
1789 if ( isset( $parserOutputHooks[$hookName] ) ) {
1790 call_user_func( $parserOutputHooks[$hookName], $this, $parserOutput, $data );
1791 }
1792 }
1793
1794 // Enable OOUI if requested via ParserOutput
1795 if ( $parserOutput->getEnableOOUI() ) {
1796 $this->enableOOUI();
1797 }
1798
1799 // Include parser limit report
1800 if ( !$this->limitReportJSData ) {
1801 $this->limitReportJSData = $parserOutput->getLimitReportJSData();
1802 }
1803
1804 // Link flags are ignored for now, but may in the future be
1805 // used to mark individual language links.
1806 $linkFlags = [];
1807 // Avoid PHP 7.1 warning of passing $this by reference
1808 $outputPage = $this;
1809 Hooks::run( 'LanguageLinks', [ $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ] );
1810 Hooks::runWithoutAbort( 'OutputPageParserOutput', [ &$outputPage, $parserOutput ] );
1811
1812 // This check must be after 'OutputPageParserOutput' runs in addParserOutputMetadata
1813 // so that extensions may modify ParserOutput to toggle TOC.
1814 // This cannot be moved to addParserOutputText because that is not
1815 // called by EditPage for Preview.
1816 if ( $parserOutput->getTOCHTML() ) {
1817 $this->mEnableTOC = true;
1818 }
1819 }
1820
1829 public function addParserOutputContent( $parserOutput, $poOptions = [] ) {
1830 $this->addParserOutputText( $parserOutput, $poOptions );
1831
1832 $this->addModules( $parserOutput->getModules() );
1833 $this->addModuleScripts( $parserOutput->getModuleScripts() );
1834 $this->addModuleStyles( $parserOutput->getModuleStyles() );
1835
1836 $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1837 }
1838
1846 public function addParserOutputText( $parserOutput, $poOptions = [] ) {
1847 $text = $parserOutput->getText( $poOptions );
1848 // Avoid PHP 7.1 warning of passing $this by reference
1849 $outputPage = $this;
1850 Hooks::runWithoutAbort( 'OutputPageBeforeHTML', [ &$outputPage, &$text ] );
1851 $this->addHTML( $text );
1852 }
1853
1860 function addParserOutput( $parserOutput, $poOptions = [] ) {
1861 $this->addParserOutputMetadata( $parserOutput );
1862 $this->addParserOutputText( $parserOutput, $poOptions );
1863 }
1864
1870 public function addTemplate( &$template ) {
1871 $this->addHTML( $template->getHTML() );
1872 }
1873
1886 public function parse( $text, $linestart = true, $interface = false, $language = null ) {
1888
1889 if ( is_null( $this->getTitle() ) ) {
1890 throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
1891 }
1892
1893 $popts = $this->parserOptions();
1894 if ( $interface ) {
1895 $popts->setInterfaceMessage( true );
1896 }
1897 if ( $language !== null ) {
1898 $oldLang = $popts->setTargetLanguage( $language );
1899 }
1900
1901 $parserOutput = $wgParser->getFreshParser()->parse(
1902 $text, $this->getTitle(), $popts,
1903 $linestart, true, $this->mRevisionId
1904 );
1905
1906 if ( $interface ) {
1907 $popts->setInterfaceMessage( false );
1908 }
1909 if ( $language !== null ) {
1910 $popts->setTargetLanguage( $oldLang );
1911 }
1912
1913 return $parserOutput->getText( [
1914 'enableSectionEditLinks' => false,
1915 ] );
1916 }
1917
1928 public function parseInline( $text, $linestart = true, $interface = false ) {
1929 $parsed = $this->parse( $text, $linestart, $interface );
1930 return Parser::stripOuterParagraph( $parsed );
1931 }
1932
1938 public function setCdnMaxage( $maxage ) {
1939 $this->mCdnMaxage = min( $maxage, $this->mCdnMaxageLimit );
1940 }
1941
1948 public function lowerCdnMaxage( $maxage ) {
1949 $this->mCdnMaxageLimit = min( $maxage, $this->mCdnMaxageLimit );
1950 $this->setCdnMaxage( $this->mCdnMaxage );
1951 }
1952
1966 public function adaptCdnTTL( $mtime, $minTTL = 0, $maxTTL = 0 ) {
1967 $minTTL = $minTTL ?: IExpiringStore::TTL_MINUTE;
1968 $maxTTL = $maxTTL ?: $this->getConfig()->get( 'SquidMaxage' );
1969
1970 if ( $mtime === null || $mtime === false ) {
1971 return $minTTL; // entity does not exist
1972 }
1973
1974 $age = time() - wfTimestamp( TS_UNIX, $mtime );
1975 $adaptiveTTL = max( 0.9 * $age, $minTTL );
1976 $adaptiveTTL = min( $adaptiveTTL, $maxTTL );
1977
1978 $this->lowerCdnMaxage( (int)$adaptiveTTL );
1979
1980 return $adaptiveTTL;
1981 }
1982
1990 public function enableClientCache( $state ) {
1991 return wfSetVar( $this->mEnableClientCache, $state );
1992 }
1993
2000 static $cookies;
2001 if ( $cookies === null ) {
2002 $config = $this->getConfig();
2003 $cookies = array_merge(
2004 SessionManager::singleton()->getVaryCookies(),
2005 [
2006 'forceHTTPS',
2007 ],
2008 $config->get( 'CacheVaryCookies' )
2009 );
2010 Hooks::run( 'GetCacheVaryCookies', [ $this, &$cookies ] );
2011 }
2012 return $cookies;
2013 }
2014
2022 $request = $this->getRequest();
2023 foreach ( $this->getCacheVaryCookies() as $cookieName ) {
2024 if ( $request->getCookie( $cookieName, '', '' ) !== '' ) {
2025 wfDebug( __METHOD__ . ": found $cookieName\n" );
2026 return true;
2027 }
2028 }
2029 wfDebug( __METHOD__ . ": no cache-varying cookies found\n" );
2030 return false;
2031 }
2032
2041 public function addVaryHeader( $header, array $option = null ) {
2042 if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
2043 $this->mVaryHeader[$header] = [];
2044 }
2045 if ( !is_array( $option ) ) {
2046 $option = [];
2047 }
2048 $this->mVaryHeader[$header] = array_unique( array_merge( $this->mVaryHeader[$header], $option ) );
2049 }
2050
2057 public function getVaryHeader() {
2058 // If we vary on cookies, let's make sure it's always included here too.
2059 if ( $this->getCacheVaryCookies() ) {
2060 $this->addVaryHeader( 'Cookie' );
2061 }
2062
2063 foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2064 $this->addVaryHeader( $header, $options );
2065 }
2066 return 'Vary: ' . implode( ', ', array_keys( $this->mVaryHeader ) );
2067 }
2068
2074 public function addLinkHeader( $header ) {
2075 $this->mLinkHeader[] = $header;
2076 }
2077
2083 public function getLinkHeader() {
2084 if ( !$this->mLinkHeader ) {
2085 return false;
2086 }
2087
2088 return 'Link: ' . implode( ',', $this->mLinkHeader );
2089 }
2090
2096 public function getKeyHeader() {
2097 $cvCookies = $this->getCacheVaryCookies();
2098
2099 $cookiesOption = [];
2100 foreach ( $cvCookies as $cookieName ) {
2101 $cookiesOption[] = 'param=' . $cookieName;
2102 }
2103 $this->addVaryHeader( 'Cookie', $cookiesOption );
2104
2105 foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2106 $this->addVaryHeader( $header, $options );
2107 }
2108
2109 $headers = [];
2110 foreach ( $this->mVaryHeader as $header => $option ) {
2111 $newheader = $header;
2112 if ( is_array( $option ) && count( $option ) > 0 ) {
2113 $newheader .= ';' . implode( ';', $option );
2114 }
2115 $headers[] = $newheader;
2116 }
2117 $key = 'Key: ' . implode( ',', $headers );
2118
2119 return $key;
2120 }
2121
2131 $title = $this->getTitle();
2132 if ( !$title instanceof Title ) {
2133 return;
2134 }
2135
2136 $lang = $title->getPageLanguage();
2137 if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
2138 $variants = $lang->getVariants();
2139 $aloption = [];
2140 foreach ( $variants as $variant ) {
2141 if ( $variant === $lang->getCode() ) {
2142 continue;
2143 } else {
2144 $aloption[] = 'substr=' . $variant;
2145
2146 // IE and some other browsers use BCP 47 standards in
2147 // their Accept-Language header, like "zh-CN" or "zh-Hant".
2148 // We should handle these too.
2149 $variantBCP47 = LanguageCode::bcp47( $variant );
2150 if ( $variantBCP47 !== $variant ) {
2151 $aloption[] = 'substr=' . $variantBCP47;
2152 }
2153 }
2154 }
2155 $this->addVaryHeader( 'Accept-Language', $aloption );
2156 }
2157 }
2158
2169 public function preventClickjacking( $enable = true ) {
2170 $this->mPreventClickjacking = $enable;
2171 }
2172
2178 public function allowClickjacking() {
2179 $this->mPreventClickjacking = false;
2180 }
2181
2188 public function getPreventClickjacking() {
2189 return $this->mPreventClickjacking;
2190 }
2191
2199 public function getFrameOptions() {
2200 $config = $this->getConfig();
2201 if ( $config->get( 'BreakFrames' ) ) {
2202 return 'DENY';
2203 } elseif ( $this->mPreventClickjacking && $config->get( 'EditPageFrameOptions' ) ) {
2204 return $config->get( 'EditPageFrameOptions' );
2205 }
2206 return false;
2207 }
2208
2212 public function sendCacheControl() {
2213 $response = $this->getRequest()->response();
2214 $config = $this->getConfig();
2215
2216 $this->addVaryHeader( 'Cookie' );
2217 $this->addAcceptLanguage();
2218
2219 # don't serve compressed data to clients who can't handle it
2220 # maintain different caches for logged-in users and non-logged in ones
2221 $response->header( $this->getVaryHeader() );
2222
2223 if ( $config->get( 'UseKeyHeader' ) ) {
2224 $response->header( $this->getKeyHeader() );
2225 }
2226
2227 if ( $this->mEnableClientCache ) {
2228 if (
2229 $config->get( 'UseSquid' ) &&
2230 !$response->hasCookies() &&
2231 !SessionManager::getGlobalSession()->isPersistent() &&
2232 !$this->isPrintable() &&
2233 $this->mCdnMaxage != 0 &&
2234 !$this->haveCacheVaryCookies()
2235 ) {
2236 if ( $config->get( 'UseESI' ) ) {
2237 # We'll purge the proxy cache explicitly, but require end user agents
2238 # to revalidate against the proxy on each visit.
2239 # Surrogate-Control controls our CDN, Cache-Control downstream caches
2240 wfDebug( __METHOD__ .
2241 ": proxy caching with ESI; {$this->mLastModified} **", 'private' );
2242 # start with a shorter timeout for initial testing
2243 # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
2244 $response->header(
2245 "Surrogate-Control: max-age={$config->get( 'SquidMaxage' )}" .
2246 "+{$this->mCdnMaxage}, content=\"ESI/1.0\""
2247 );
2248 $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
2249 } else {
2250 # We'll purge the proxy cache for anons explicitly, but require end user agents
2251 # to revalidate against the proxy on each visit.
2252 # IMPORTANT! The CDN needs to replace the Cache-Control header with
2253 # Cache-Control: s-maxage=0, must-revalidate, max-age=0
2254 wfDebug( __METHOD__ .
2255 ": local proxy caching; {$this->mLastModified} **", 'private' );
2256 # start with a shorter timeout for initial testing
2257 # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
2258 $response->header( "Cache-Control: " .
2259 "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" );
2260 }
2261 } else {
2262 # We do want clients to cache if they can, but they *must* check for updates
2263 # on revisiting the page.
2264 wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **", 'private' );
2265 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2266 $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
2267 }
2268 if ( $this->mLastModified ) {
2269 $response->header( "Last-Modified: {$this->mLastModified}" );
2270 }
2271 } else {
2272 wfDebug( __METHOD__ . ": no caching **", 'private' );
2273
2274 # In general, the absence of a last modified header should be enough to prevent
2275 # the client from using its cache. We send a few other things just to make sure.
2276 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2277 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
2278 $response->header( 'Pragma: no-cache' );
2279 }
2280 }
2281
2292 public function output( $return = false ) {
2294
2295 if ( $this->mDoNothing ) {
2296 return $return ? '' : null;
2297 }
2298
2299 $response = $this->getRequest()->response();
2300 $config = $this->getConfig();
2301
2302 if ( $this->mRedirect != '' ) {
2303 # Standards require redirect URLs to be absolute
2304 $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT );
2305
2306 $redirect = $this->mRedirect;
2307 $code = $this->mRedirectCode;
2308
2309 if ( Hooks::run( "BeforePageRedirect", [ $this, &$redirect, &$code ] ) ) {
2310 if ( $code == '301' || $code == '303' ) {
2311 if ( !$config->get( 'DebugRedirects' ) ) {
2312 $response->statusHeader( $code );
2313 }
2314 $this->mLastModified = wfTimestamp( TS_RFC2822 );
2315 }
2316 if ( $config->get( 'VaryOnXFP' ) ) {
2317 $this->addVaryHeader( 'X-Forwarded-Proto' );
2318 }
2319 $this->sendCacheControl();
2320
2321 $response->header( "Content-Type: text/html; charset=utf-8" );
2322 if ( $config->get( 'DebugRedirects' ) ) {
2323 $url = htmlspecialchars( $redirect );
2324 print "<!DOCTYPE html>\n<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
2325 print "<p>Location: <a href=\"$url\">$url</a></p>\n";
2326 print "</body>\n</html>\n";
2327 } else {
2328 $response->header( 'Location: ' . $redirect );
2329 }
2330 }
2331
2332 return $return ? '' : null;
2333 } elseif ( $this->mStatusCode ) {
2334 $response->statusHeader( $this->mStatusCode );
2335 }
2336
2337 # Buffer output; final headers may depend on later processing
2338 ob_start();
2339
2340 $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
2341 $response->header( 'Content-language: ' . $wgContLang->getHtmlCode() );
2342
2343 // Avoid Internet Explorer "compatibility view" in IE 8-10, so that
2344 // jQuery etc. can work correctly.
2345 $response->header( 'X-UA-Compatible: IE=Edge' );
2346
2347 if ( !$this->mArticleBodyOnly ) {
2348 $sk = $this->getSkin();
2349
2350 if ( $sk->shouldPreloadLogo() ) {
2352 }
2353 }
2354
2355 $linkHeader = $this->getLinkHeader();
2356 if ( $linkHeader ) {
2357 $response->header( $linkHeader );
2358 }
2359
2360 // Prevent framing, if requested
2361 $frameOptions = $this->getFrameOptions();
2362 if ( $frameOptions ) {
2363 $response->header( "X-Frame-Options: $frameOptions" );
2364 }
2365
2366 if ( $this->mArticleBodyOnly ) {
2367 echo $this->mBodytext;
2368 } else {
2369 // Enable safe mode if requested
2370 if ( $this->getRequest()->getBool( 'safemode' ) ) {
2371 $this->disallowUserJs();
2372 }
2373
2374 $sk = $this->getSkin();
2375 foreach ( $sk->getDefaultModules() as $group ) {
2376 $this->addModules( $group );
2377 }
2378
2379 MWDebug::addModules( $this );
2380
2381 // Avoid PHP 7.1 warning of passing $this by reference
2382 $outputPage = $this;
2383 // Hook that allows last minute changes to the output page, e.g.
2384 // adding of CSS or Javascript by extensions.
2385 Hooks::runWithoutAbort( 'BeforePageDisplay', [ &$outputPage, &$sk ] );
2386
2387 try {
2388 $sk->outputPage();
2389 } catch ( Exception $e ) {
2390 ob_end_clean(); // bug T129657
2391 throw $e;
2392 }
2393 }
2394
2395 try {
2396 // This hook allows last minute changes to final overall output by modifying output buffer
2397 Hooks::runWithoutAbort( 'AfterFinalPageOutput', [ $this ] );
2398 } catch ( Exception $e ) {
2399 ob_end_clean(); // bug T129657
2400 throw $e;
2401 }
2402
2403 $this->sendCacheControl();
2404
2405 if ( $return ) {
2406 return ob_get_clean();
2407 } else {
2408 ob_end_flush();
2409 return null;
2410 }
2411 }
2412
2423 public function prepareErrorPage( $pageTitle, $htmlTitle = false ) {
2424 $this->setPageTitle( $pageTitle );
2425 if ( $htmlTitle !== false ) {
2426 $this->setHTMLTitle( $htmlTitle );
2427 }
2428 $this->setRobotPolicy( 'noindex,nofollow' );
2429 $this->setArticleRelated( false );
2430 $this->enableClientCache( false );
2431 $this->mRedirect = '';
2432 $this->clearSubtitle();
2433 $this->clearHTML();
2434 }
2435
2448 public function showErrorPage( $title, $msg, $params = [] ) {
2449 if ( !$title instanceof Message ) {
2450 $title = $this->msg( $title );
2451 }
2452
2453 $this->prepareErrorPage( $title );
2454
2455 if ( $msg instanceof Message ) {
2456 if ( $params !== [] ) {
2457 trigger_error( 'Argument ignored: $params. The message parameters argument '
2458 . 'is discarded when the $msg argument is a Message object instead of '
2459 . 'a string.', E_USER_NOTICE );
2460 }
2461 $this->addHTML( $msg->parseAsBlock() );
2462 } else {
2463 $this->addWikiMsgArray( $msg, $params );
2464 }
2465
2466 $this->returnToMain();
2467 }
2468
2475 public function showPermissionsErrorPage( array $errors, $action = null ) {
2476 foreach ( $errors as $key => $error ) {
2477 $errors[$key] = (array)$error;
2478 }
2479
2480 // For some action (read, edit, create and upload), display a "login to do this action"
2481 // error if all of the following conditions are met:
2482 // 1. the user is not logged in
2483 // 2. the only error is insufficient permissions (i.e. no block or something else)
2484 // 3. the error can be avoided simply by logging in
2485 if ( in_array( $action, [ 'read', 'edit', 'createpage', 'createtalk', 'upload' ] )
2486 && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
2487 && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
2488 && ( User::groupHasPermission( 'user', $action )
2489 || User::groupHasPermission( 'autoconfirmed', $action ) )
2490 ) {
2491 $displayReturnto = null;
2492
2493 # Due to T34276, if a user does not have read permissions,
2494 # $this->getTitle() will just give Special:Badtitle, which is
2495 # not especially useful as a returnto parameter. Use the title
2496 # from the request instead, if there was one.
2497 $request = $this->getRequest();
2498 $returnto = Title::newFromText( $request->getVal( 'title', '' ) );
2499 if ( $action == 'edit' ) {
2500 $msg = 'whitelistedittext';
2501 $displayReturnto = $returnto;
2502 } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
2503 $msg = 'nocreatetext';
2504 } elseif ( $action == 'upload' ) {
2505 $msg = 'uploadnologintext';
2506 } else { # Read
2507 $msg = 'loginreqpagetext';
2508 $displayReturnto = Title::newMainPage();
2509 }
2510
2511 $query = [];
2512
2513 if ( $returnto ) {
2514 $query['returnto'] = $returnto->getPrefixedText();
2515
2516 if ( !$request->wasPosted() ) {
2517 $returntoquery = $request->getValues();
2518 unset( $returntoquery['title'] );
2519 unset( $returntoquery['returnto'] );
2520 unset( $returntoquery['returntoquery'] );
2521 $query['returntoquery'] = wfArrayToCgi( $returntoquery );
2522 }
2523 }
2524 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
2525 $loginLink = $linkRenderer->makeKnownLink(
2526 SpecialPage::getTitleFor( 'Userlogin' ),
2527 $this->msg( 'loginreqlink' )->text(),
2528 [],
2529 $query
2530 );
2531
2532 $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) );
2533 $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->parse() );
2534
2535 # Don't return to a page the user can't read otherwise
2536 # we'll end up in a pointless loop
2537 if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) {
2538 $this->returnToMain( null, $displayReturnto );
2539 }
2540 } else {
2541 $this->prepareErrorPage( $this->msg( 'permissionserrors' ) );
2542 $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) );
2543 }
2544 }
2545
2552 public function versionRequired( $version ) {
2553 $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) );
2554
2555 $this->addWikiMsg( 'versionrequiredtext', $version );
2556 $this->returnToMain();
2557 }
2558
2566 public function formatPermissionsErrorMessage( array $errors, $action = null ) {
2567 if ( $action == null ) {
2568 $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
2569 } else {
2570 $action_desc = $this->msg( "action-$action" )->plain();
2571 $text = $this->msg(
2572 'permissionserrorstext-withaction',
2573 count( $errors ),
2574 $action_desc
2575 )->plain() . "\n\n";
2576 }
2577
2578 if ( count( $errors ) > 1 ) {
2579 $text .= '<ul class="permissions-errors">' . "\n";
2580
2581 foreach ( $errors as $error ) {
2582 $text .= '<li>';
2583 $text .= call_user_func_array( [ $this, 'msg' ], $error )->plain();
2584 $text .= "</li>\n";
2585 }
2586 $text .= '</ul>';
2587 } else {
2588 $text .= "<div class=\"permissions-errors\">\n" .
2589 call_user_func_array( [ $this, 'msg' ], reset( $errors ) )->plain() .
2590 "\n</div>";
2591 }
2592
2593 return $text;
2594 }
2595
2605 public function showLagWarning( $lag ) {
2606 $config = $this->getConfig();
2607 if ( $lag >= $config->get( 'SlaveLagWarning' ) ) {
2608 $lag = floor( $lag ); // floor to avoid nano seconds to display
2609 $message = $lag < $config->get( 'SlaveLagCritical' )
2610 ? 'lag-warn-normal'
2611 : 'lag-warn-high';
2612 $wrap = Html::rawElement( 'div', [ 'class' => "mw-{$message}" ], "\n$1\n" );
2613 $this->wrapWikiMsg( "$wrap\n", [ $message, $this->getLanguage()->formatNum( $lag ) ] );
2614 }
2615 }
2616
2617 public function showFatalError( $message ) {
2618 $this->prepareErrorPage( $this->msg( 'internalerror' ) );
2619
2620 $this->addHTML( $message );
2621 }
2622
2623 public function showUnexpectedValueError( $name, $val ) {
2624 $this->showFatalError( $this->msg( 'unexpected', $name, $val )->text() );
2625 }
2626
2627 public function showFileCopyError( $old, $new ) {
2628 $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->text() );
2629 }
2630
2631 public function showFileRenameError( $old, $new ) {
2632 $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->text() );
2633 }
2634
2635 public function showFileDeleteError( $name ) {
2636 $this->showFatalError( $this->msg( 'filedeleteerror', $name )->text() );
2637 }
2638
2639 public function showFileNotFoundError( $name ) {
2640 $this->showFatalError( $this->msg( 'filenotfound', $name )->text() );
2641 }
2642
2651 public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) {
2652 $linkRenderer = MediaWikiServices::getInstance()
2653 ->getLinkRendererFactory()->createFromLegacyOptions( $options );
2654 $link = $this->msg( 'returnto' )->rawParams(
2655 $linkRenderer->makeLink( $title, $text, [], $query ) )->escaped();
2656 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
2657 }
2658
2667 public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
2668 if ( $returnto == null ) {
2669 $returnto = $this->getRequest()->getText( 'returnto' );
2670 }
2671
2672 if ( $returntoquery == null ) {
2673 $returntoquery = $this->getRequest()->getText( 'returntoquery' );
2674 }
2675
2676 if ( $returnto === '' ) {
2677 $returnto = Title::newMainPage();
2678 }
2679
2680 if ( is_object( $returnto ) ) {
2681 $titleObj = $returnto;
2682 } else {
2683 $titleObj = Title::newFromText( $returnto );
2684 }
2685 // We don't want people to return to external interwiki. That
2686 // might potentially be used as part of a phishing scheme
2687 if ( !is_object( $titleObj ) || $titleObj->isExternal() ) {
2688 $titleObj = Title::newMainPage();
2689 }
2690
2691 $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) );
2692 }
2693
2694 private function getRlClientContext() {
2695 if ( !$this->rlClientContext ) {
2697 [], // modules; not relevant
2698 $this->getLanguage()->getCode(),
2699 $this->getSkin()->getSkinName(),
2700 $this->getUser()->isLoggedIn() ? $this->getUser()->getName() : null,
2701 null, // version; not relevant
2703 null, // only; not relevant
2704 $this->isPrintable(),
2705 $this->getRequest()->getBool( 'handheld' )
2706 );
2707 $this->rlClientContext = new ResourceLoaderContext(
2708 $this->getResourceLoader(),
2709 new FauxRequest( $query )
2710 );
2711 }
2712 return $this->rlClientContext;
2713 }
2714
2726 public function getRlClient() {
2727 if ( !$this->rlClient ) {
2728 $context = $this->getRlClientContext();
2729 $rl = $this->getResourceLoader();
2730 $this->addModules( [
2731 'user.options',
2732 'user.tokens',
2733 ] );
2734 $this->addModuleStyles( [
2735 'site.styles',
2736 'noscript',
2737 'user.styles',
2738 ] );
2739 $this->getSkin()->setupSkinUserCss( $this );
2740
2741 // Prepare exempt modules for buildExemptModules()
2742 $exemptGroups = [ 'site' => [], 'noscript' => [], 'private' => [], 'user' => [] ];
2743 $exemptStates = [];
2744 $moduleStyles = $this->getModuleStyles( /*filter*/ true );
2745
2746 // Preload getTitleInfo for isKnownEmpty calls below and in ResourceLoaderClientHtml
2747 // Separate user-specific batch for improved cache-hit ratio.
2748 $userBatch = [ 'user.styles', 'user' ];
2749 $siteBatch = array_diff( $moduleStyles, $userBatch );
2750 $dbr = wfGetDB( DB_REPLICA );
2753
2754 // Filter out modules handled by buildExemptModules()
2755 $moduleStyles = array_filter( $moduleStyles,
2756 function ( $name ) use ( $rl, $context, &$exemptGroups, &$exemptStates ) {
2757 $module = $rl->getModule( $name );
2758 if ( $module ) {
2759 if ( $name === 'user.styles' && $this->isUserCssPreview() ) {
2760 $exemptStates[$name] = 'ready';
2761 // Special case in buildExemptModules()
2762 return false;
2763 }
2764 $group = $module->getGroup();
2765 if ( isset( $exemptGroups[$group] ) ) {
2766 $exemptStates[$name] = 'ready';
2767 if ( !$module->isKnownEmpty( $context ) ) {
2768 // E.g. Don't output empty <styles>
2769 $exemptGroups[$group][] = $name;
2770 }
2771 return false;
2772 }
2773 }
2774 return true;
2775 }
2776 );
2777 $this->rlExemptStyleModules = $exemptGroups;
2778
2779 $isUserModuleFiltered = !$this->filterModules( [ 'user' ] );
2780 // If this page filters out 'user', makeResourceLoaderLink will drop it.
2781 // Avoid indefinite "loading" state or untrue "ready" state (T145368).
2782 if ( !$isUserModuleFiltered ) {
2783 // Manually handled by getBottomScripts()
2784 $userModule = $rl->getModule( 'user' );
2785 $userState = $userModule->isKnownEmpty( $context ) && !$this->isUserJsPreview()
2786 ? 'ready'
2787 : 'loading';
2788 $this->rlUserModuleState = $exemptStates['user'] = $userState;
2789 }
2790
2792 'target' => $this->getTarget(),
2793 ] );
2794 $rlClient->setConfig( $this->getJSVars() );
2795 $rlClient->setModules( $this->getModules( /*filter*/ true ) );
2796 $rlClient->setModuleStyles( $moduleStyles );
2797 $rlClient->setModuleScripts( $this->getModuleScripts( /*filter*/ true ) );
2798 $rlClient->setExemptStates( $exemptStates );
2799 $this->rlClient = $rlClient;
2800 }
2801 return $this->rlClient;
2802 }
2803
2809 public function headElement( Skin $sk, $includeStyle = true ) {
2811
2812 $userdir = $this->getLanguage()->getDir();
2813 $sitedir = $wgContLang->getDir();
2814
2815 $pieces = [];
2816 $pieces[] = Html::htmlHeader( Sanitizer::mergeAttributes(
2817 $this->getRlClient()->getDocumentAttributes(),
2819 ) );
2820 $pieces[] = Html::openElement( 'head' );
2821
2822 if ( $this->getHTMLTitle() == '' ) {
2823 $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() );
2824 }
2825
2826 if ( !Html::isXmlMimeType( $this->getConfig()->get( 'MimeType' ) ) ) {
2827 // Add <meta charset="UTF-8">
2828 // This should be before <title> since it defines the charset used by
2829 // text including the text inside <title>.
2830 // The spec recommends defining XHTML5's charset using the XML declaration
2831 // instead of meta.
2832 // Our XML declaration is output by Html::htmlHeader.
2833 // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type
2834 // https://html.spec.whatwg.org/multipage/semantics.html#charset
2835 $pieces[] = Html::element( 'meta', [ 'charset' => 'UTF-8' ] );
2836 }
2837
2838 $pieces[] = Html::element( 'title', null, $this->getHTMLTitle() );
2839 $pieces[] = $this->getRlClient()->getHeadHtml();
2840 $pieces[] = $this->buildExemptModules();
2841 $pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) );
2842 $pieces = array_merge( $pieces, array_values( $this->mHeadItems ) );
2843
2844 // Use an IE conditional comment to serve the script only to old IE
2845 $pieces[] = '<!--[if lt IE 9]>' .
2848 [ 'html5shiv' ],
2850 [ 'sync' => true ]
2851 ) .
2852 '<![endif]-->';
2853
2854 $pieces[] = Html::closeElement( 'head' );
2855
2856 $bodyClasses = $this->mAdditionalBodyClasses;
2857 $bodyClasses[] = 'mediawiki';
2858
2859 # Classes for LTR/RTL directionality support
2860 $bodyClasses[] = $userdir;
2861 $bodyClasses[] = "sitedir-$sitedir";
2862
2863 $underline = $this->getUser()->getOption( 'underline' );
2864 if ( $underline < 2 ) {
2865 // The following classes can be used here:
2866 // * mw-underline-always
2867 // * mw-underline-never
2868 $bodyClasses[] = 'mw-underline-' . ( $underline ? 'always' : 'never' );
2869 }
2870
2871 if ( $this->getLanguage()->capitalizeAllNouns() ) {
2872 # A <body> class is probably not the best way to do this . . .
2873 $bodyClasses[] = 'capitalize-all-nouns';
2874 }
2875
2876 // Parser feature migration class
2877 // The idea is that this will eventually be removed, after the wikitext
2878 // which requires it is cleaned up.
2879 $bodyClasses[] = 'mw-hide-empty-elt';
2880
2881 $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
2882 $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
2883 $bodyClasses[] =
2884 'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
2885
2886 $bodyAttrs = [];
2887 // While the implode() is not strictly needed, it's used for backwards compatibility
2888 // (this used to be built as a string and hooks likely still expect that).
2889 $bodyAttrs['class'] = implode( ' ', $bodyClasses );
2890
2891 // Allow skins and extensions to add body attributes they need
2892 $sk->addToBodyAttributes( $this, $bodyAttrs );
2893 Hooks::run( 'OutputPageBodyAttributes', [ $this, $sk, &$bodyAttrs ] );
2894
2895 $pieces[] = Html::openElement( 'body', $bodyAttrs );
2896
2897 return self::combineWrappedStrings( $pieces );
2898 }
2899
2905 public function getResourceLoader() {
2906 if ( is_null( $this->mResourceLoader ) ) {
2907 $this->mResourceLoader = new ResourceLoader(
2908 $this->getConfig(),
2909 LoggerFactory::getInstance( 'resourceloader' )
2910 );
2911 }
2912 return $this->mResourceLoader;
2913 }
2914
2923 public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) {
2924 // Apply 'target' and 'origin' filters
2925 $modules = $this->filterModules( (array)$modules, null, $only );
2926
2928 $this->getRlClientContext(),
2929 $modules,
2930 $only,
2931 $extraQuery
2932 );
2933 }
2934
2941 protected static function combineWrappedStrings( array $chunks ) {
2942 // Filter out empty values
2943 $chunks = array_filter( $chunks, 'strlen' );
2944 return WrappedString::join( "\n", $chunks );
2945 }
2946
2947 private function isUserJsPreview() {
2948 return $this->getConfig()->get( 'AllowUserJs' )
2949 && $this->getTitle()
2950 && $this->getTitle()->isUserJsConfigPage()
2951 && $this->userCanPreview();
2952 }
2953
2954 protected function isUserCssPreview() {
2955 return $this->getConfig()->get( 'AllowUserCss' )
2956 && $this->getTitle()
2957 && $this->getTitle()->isUserCssConfigPage()
2958 && $this->userCanPreview();
2959 }
2960
2967 public function getBottomScripts() {
2968 $chunks = [];
2969 $chunks[] = $this->getRlClient()->getBodyHtml();
2970
2971 // Legacy non-ResourceLoader scripts
2972 $chunks[] = $this->mScripts;
2973
2974 // Exempt 'user' module
2975 // - May need excludepages for live preview. (T28283)
2976 // - Must use TYPE_COMBINED so its response is handled by mw.loader.implement() which
2977 // ensures execution is scheduled after the "site" module.
2978 // - Don't load if module state is already resolved as "ready".
2979 if ( $this->rlUserModuleState === 'loading' ) {
2980 if ( $this->isUserJsPreview() ) {
2982 [ 'excludepage' => $this->getTitle()->getPrefixedDBkey() ]
2983 );
2985 Xml::encodeJsCall( 'mw.loader.using', [
2986 [ 'user', 'site' ],
2987 new XmlJsCode(
2988 'function () {'
2989 . Xml::encodeJsCall( '$.globalEval', [
2990 $this->getRequest()->getText( 'wpTextbox1' )
2991 ] )
2992 . '}'
2993 )
2994 ] )
2995 );
2996 // FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded
2997 // asynchronously and may arrive *after* the inline script here. So the previewed code
2998 // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js.
2999 // Similarly, when previewing ./common.js and the user module does arrive first,
3000 // it will arrive without common.js and the inline script runs after.
3001 // Thus running common after the excluded subpage.
3002 } else {
3003 // Load normally
3004 $chunks[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_COMBINED );
3005 }
3006 }
3007
3008 if ( $this->limitReportJSData ) {
3011 [ 'wgPageParseReport' => $this->limitReportJSData ]
3012 )
3013 );
3014 }
3015
3016 return self::combineWrappedStrings( $chunks );
3017 }
3018
3025 public function getJsConfigVars() {
3026 return $this->mJsConfigVars;
3027 }
3028
3035 public function addJsConfigVars( $keys, $value = null ) {
3036 if ( is_array( $keys ) ) {
3037 foreach ( $keys as $key => $value ) {
3038 $this->mJsConfigVars[$key] = $value;
3039 }
3040 return;
3041 }
3042
3043 $this->mJsConfigVars[$keys] = $value;
3044 }
3045
3055 public function getJSVars() {
3057
3058 $curRevisionId = 0;
3059 $articleId = 0;
3060 $canonicalSpecialPageName = false; # T23115
3061
3062 $title = $this->getTitle();
3063 $ns = $title->getNamespace();
3064 $canonicalNamespace = MWNamespace::exists( $ns )
3065 ? MWNamespace::getCanonicalName( $ns )
3066 : $title->getNsText();
3067
3068 $sk = $this->getSkin();
3069 // Get the relevant title so that AJAX features can use the correct page name
3070 // when making API requests from certain special pages (T36972).
3071 $relevantTitle = $sk->getRelevantTitle();
3072 $relevantUser = $sk->getRelevantUser();
3073
3074 if ( $ns == NS_SPECIAL ) {
3075 list( $canonicalSpecialPageName, /*...*/ ) =
3077 } elseif ( $this->canUseWikiPage() ) {
3078 $wikiPage = $this->getWikiPage();
3079 $curRevisionId = $wikiPage->getLatest();
3080 $articleId = $wikiPage->getId();
3081 }
3082
3083 $lang = $title->getPageViewLanguage();
3084
3085 // Pre-process information
3086 $separatorTransTable = $lang->separatorTransformTable();
3087 $separatorTransTable = $separatorTransTable ? $separatorTransTable : [];
3088 $compactSeparatorTransTable = [
3089 implode( "\t", array_keys( $separatorTransTable ) ),
3090 implode( "\t", $separatorTransTable ),
3091 ];
3092 $digitTransTable = $lang->digitTransformTable();
3093 $digitTransTable = $digitTransTable ? $digitTransTable : [];
3094 $compactDigitTransTable = [
3095 implode( "\t", array_keys( $digitTransTable ) ),
3096 implode( "\t", $digitTransTable ),
3097 ];
3098
3099 $user = $this->getUser();
3100
3101 $vars = [
3102 'wgCanonicalNamespace' => $canonicalNamespace,
3103 'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
3104 'wgNamespaceNumber' => $title->getNamespace(),
3105 'wgPageName' => $title->getPrefixedDBkey(),
3106 'wgTitle' => $title->getText(),
3107 'wgCurRevisionId' => $curRevisionId,
3108 'wgRevisionId' => (int)$this->getRevisionId(),
3109 'wgArticleId' => $articleId,
3110 'wgIsArticle' => $this->isArticle(),
3111 'wgIsRedirect' => $title->isRedirect(),
3112 'wgAction' => Action::getActionName( $this->getContext() ),
3113 'wgUserName' => $user->isAnon() ? null : $user->getName(),
3114 'wgUserGroups' => $user->getEffectiveGroups(),
3115 'wgCategories' => $this->getCategories(),
3116 'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
3117 'wgPageContentLanguage' => $lang->getCode(),
3118 'wgPageContentModel' => $title->getContentModel(),
3119 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
3120 'wgDigitTransformTable' => $compactDigitTransTable,
3121 'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
3122 'wgMonthNames' => $lang->getMonthNamesArray(),
3123 'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
3124 'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
3125 'wgRelevantArticleId' => $relevantTitle->getArticleID(),
3126 'wgRequestId' => WebRequest::getRequestId(),
3127 ];
3128
3129 if ( $user->isLoggedIn() ) {
3130 $vars['wgUserId'] = $user->getId();
3131 $vars['wgUserEditCount'] = $user->getEditCount();
3132 $userReg = $user->getRegistration();
3133 $vars['wgUserRegistration'] = $userReg ? wfTimestamp( TS_UNIX, $userReg ) * 1000 : null;
3134 // Get the revision ID of the oldest new message on the user's talk
3135 // page. This can be used for constructing new message alerts on
3136 // the client side.
3137 $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId();
3138 }
3139
3140 if ( $wgContLang->hasVariants() ) {
3141 $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
3142 }
3143 // Same test as SkinTemplate
3144 $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user )
3145 && ( $title->exists() || $title->quickUserCan( 'create', $user ) );
3146
3147 $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle
3148 && $relevantTitle->quickUserCan( 'edit', $user )
3149 && ( $relevantTitle->exists() || $relevantTitle->quickUserCan( 'create', $user ) );
3150
3151 foreach ( $title->getRestrictionTypes() as $type ) {
3152 // Following keys are set in $vars:
3153 // wgRestrictionCreate, wgRestrictionEdit, wgRestrictionMove, wgRestrictionUpload
3154 $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
3155 }
3156
3157 if ( $title->isMainPage() ) {
3158 $vars['wgIsMainPage'] = true;
3159 }
3160
3161 if ( $this->mRedirectedFrom ) {
3162 $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey();
3163 }
3164
3165 if ( $relevantUser && ( !$relevantUser->isHidden() || $user->isAllowed( 'hideuser' ) ) ) {
3166 // T120883 if the user is hidden and the viewer cannot see
3167 // hidden users, pretend like it does not exist at all.
3168 $vars['wgRelevantUserName'] = $relevantUser->getName();
3169 }
3170
3171 // Allow extensions to add their custom variables to the mw.config map.
3172 // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
3173 // page-dependant but site-wide (without state).
3174 // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
3175 Hooks::run( 'MakeGlobalVariablesScript', [ &$vars, $this ] );
3176
3177 // Merge in variables from addJsConfigVars last
3178 return array_merge( $vars, $this->getJsConfigVars() );
3179 }
3180
3190 public function userCanPreview() {
3191 $request = $this->getRequest();
3192 if (
3193 $request->getVal( 'action' ) !== 'submit' ||
3194 !$request->getCheck( 'wpPreview' ) ||
3195 !$request->wasPosted()
3196 ) {
3197 return false;
3198 }
3199
3200 $user = $this->getUser();
3201
3202 if ( !$user->isLoggedIn() ) {
3203 // Anons have predictable edit tokens
3204 return false;
3205 }
3206 if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
3207 return false;
3208 }
3209
3210 $title = $this->getTitle();
3211 if (
3212 !$title->isUserJsConfigPage()
3213 && !$title->isUserCssConfigPage()
3214 ) {
3215 return false;
3216 }
3217 if ( !$title->isSubpageOf( $user->getUserPage() ) ) {
3218 // Don't execute another user's CSS or JS on preview (T85855)
3219 return false;
3220 }
3221
3222 $errors = $title->getUserPermissionsErrors( 'edit', $user );
3223 if ( count( $errors ) !== 0 ) {
3224 return false;
3225 }
3226
3227 return true;
3228 }
3229
3233 public function getHeadLinksArray() {
3235
3236 $tags = [];
3237 $config = $this->getConfig();
3238
3239 $canonicalUrl = $this->mCanonicalUrl;
3240
3241 $tags['meta-generator'] = Html::element( 'meta', [
3242 'name' => 'generator',
3243 'content' => "MediaWiki $wgVersion",
3244 ] );
3245
3246 if ( $config->get( 'ReferrerPolicy' ) !== false ) {
3247 // Per https://w3c.github.io/webappsec-referrer-policy/#unknown-policy-values
3248 // fallbacks should come before the primary value so we need to reverse the array.
3249 foreach ( array_reverse( (array)$config->get( 'ReferrerPolicy' ) ) as $i => $policy ) {
3250 $tags["meta-referrer-$i"] = Html::element( 'meta', [
3251 'name' => 'referrer',
3252 'content' => $policy,
3253 ] );
3254 }
3255 }
3256
3257 $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
3258 if ( $p !== 'index,follow' ) {
3259 // http://www.robotstxt.org/wc/meta-user.html
3260 // Only show if it's different from the default robots policy
3261 $tags['meta-robots'] = Html::element( 'meta', [
3262 'name' => 'robots',
3263 'content' => $p,
3264 ] );
3265 }
3266
3267 foreach ( $this->mMetatags as $tag ) {
3268 if ( strncasecmp( $tag[0], 'http:', 5 ) === 0 ) {
3269 $a = 'http-equiv';
3270 $tag[0] = substr( $tag[0], 5 );
3271 } elseif ( strncasecmp( $tag[0], 'og:', 3 ) === 0 ) {
3272 $a = 'property';
3273 } else {
3274 $a = 'name';
3275 }
3276 $tagName = "meta-{$tag[0]}";
3277 if ( isset( $tags[$tagName] ) ) {
3278 $tagName .= $tag[1];
3279 }
3280 $tags[$tagName] = Html::element( 'meta',
3281 [
3282 $a => $tag[0],
3283 'content' => $tag[1]
3284 ]
3285 );
3286 }
3287
3288 foreach ( $this->mLinktags as $tag ) {
3289 $tags[] = Html::element( 'link', $tag );
3290 }
3291
3292 # Universal edit button
3293 if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) {
3294 $user = $this->getUser();
3295 if ( $this->getTitle()->quickUserCan( 'edit', $user )
3296 && ( $this->getTitle()->exists() ||
3297 $this->getTitle()->quickUserCan( 'create', $user ) )
3298 ) {
3299 // Original UniversalEditButton
3300 $msg = $this->msg( 'edit' )->text();
3301 $tags['universal-edit-button'] = Html::element( 'link', [
3302 'rel' => 'alternate',
3303 'type' => 'application/x-wiki',
3304 'title' => $msg,
3305 'href' => $this->getTitle()->getEditURL(),
3306 ] );
3307 // Alternate edit link
3308 $tags['alternative-edit'] = Html::element( 'link', [
3309 'rel' => 'edit',
3310 'title' => $msg,
3311 'href' => $this->getTitle()->getEditURL(),
3312 ] );
3313 }
3314 }
3315
3316 # Generally the order of the favicon and apple-touch-icon links
3317 # should not matter, but Konqueror (3.5.9 at least) incorrectly
3318 # uses whichever one appears later in the HTML source. Make sure
3319 # apple-touch-icon is specified first to avoid this.
3320 if ( $config->get( 'AppleTouchIcon' ) !== false ) {
3321 $tags['apple-touch-icon'] = Html::element( 'link', [
3322 'rel' => 'apple-touch-icon',
3323 'href' => $config->get( 'AppleTouchIcon' )
3324 ] );
3325 }
3326
3327 if ( $config->get( 'Favicon' ) !== false ) {
3328 $tags['favicon'] = Html::element( 'link', [
3329 'rel' => 'shortcut icon',
3330 'href' => $config->get( 'Favicon' )
3331 ] );
3332 }
3333
3334 # OpenSearch description link
3335 $tags['opensearch'] = Html::element( 'link', [
3336 'rel' => 'search',
3337 'type' => 'application/opensearchdescription+xml',
3338 'href' => wfScript( 'opensearch_desc' ),
3339 'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
3340 ] );
3341
3342 if ( $config->get( 'EnableAPI' ) ) {
3343 # Real Simple Discovery link, provides auto-discovery information
3344 # for the MediaWiki API (and potentially additional custom API
3345 # support such as WordPress or Twitter-compatible APIs for a
3346 # blogging extension, etc)
3347 $tags['rsd'] = Html::element( 'link', [
3348 'rel' => 'EditURI',
3349 'type' => 'application/rsd+xml',
3350 // Output a protocol-relative URL here if $wgServer is protocol-relative.
3351 // Whether RSD accepts relative or protocol-relative URLs is completely
3352 // undocumented, though.
3353 'href' => wfExpandUrl( wfAppendQuery(
3354 wfScript( 'api' ),
3355 [ 'action' => 'rsd' ] ),
3357 ),
3358 ] );
3359 }
3360
3361 # Language variants
3362 if ( !$config->get( 'DisableLangConversion' ) ) {
3363 $lang = $this->getTitle()->getPageLanguage();
3364 if ( $lang->hasVariants() ) {
3365 $variants = $lang->getVariants();
3366 foreach ( $variants as $variant ) {
3367 $tags["variant-$variant"] = Html::element( 'link', [
3368 'rel' => 'alternate',
3369 'hreflang' => LanguageCode::bcp47( $variant ),
3370 'href' => $this->getTitle()->getLocalURL(
3371 [ 'variant' => $variant ] )
3372 ]
3373 );
3374 }
3375 # x-default link per https://support.google.com/webmasters/answer/189077?hl=en
3376 $tags["variant-x-default"] = Html::element( 'link', [
3377 'rel' => 'alternate',
3378 'hreflang' => 'x-default',
3379 'href' => $this->getTitle()->getLocalURL() ] );
3380 }
3381 }
3382
3383 # Copyright
3384 if ( $this->copyrightUrl !== null ) {
3385 $copyright = $this->copyrightUrl;
3386 } else {
3387 $copyright = '';
3388 if ( $config->get( 'RightsPage' ) ) {
3389 $copy = Title::newFromText( $config->get( 'RightsPage' ) );
3390
3391 if ( $copy ) {
3392 $copyright = $copy->getLocalURL();
3393 }
3394 }
3395
3396 if ( !$copyright && $config->get( 'RightsUrl' ) ) {
3397 $copyright = $config->get( 'RightsUrl' );
3398 }
3399 }
3400
3401 if ( $copyright ) {
3402 $tags['copyright'] = Html::element( 'link', [
3403 'rel' => 'license',
3404 'href' => $copyright ]
3405 );
3406 }
3407
3408 # Feeds
3409 if ( $config->get( 'Feed' ) ) {
3410 $feedLinks = [];
3411
3412 foreach ( $this->getSyndicationLinks() as $format => $link ) {
3413 # Use the page name for the title. In principle, this could
3414 # lead to issues with having the same name for different feeds
3415 # corresponding to the same page, but we can't avoid that at
3416 # this low a level.
3417
3418 $feedLinks[] = $this->feedLink(
3419 $format,
3420 $link,
3421 # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
3422 $this->msg(
3423 "page-{$format}-feed", $this->getTitle()->getPrefixedText()
3424 )->text()
3425 );
3426 }
3427
3428 # Recent changes feed should appear on every page (except recentchanges,
3429 # that would be redundant). Put it after the per-page feed to avoid
3430 # changing existing behavior. It's still available, probably via a
3431 # menu in your browser. Some sites might have a different feed they'd
3432 # like to promote instead of the RC feed (maybe like a "Recent New Articles"
3433 # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
3434 # If so, use it instead.
3435 $sitename = $config->get( 'Sitename' );
3436 if ( $config->get( 'OverrideSiteFeed' ) ) {
3437 foreach ( $config->get( 'OverrideSiteFeed' ) as $type => $feedUrl ) {
3438 // Note, this->feedLink escapes the url.
3439 $feedLinks[] = $this->feedLink(
3440 $type,
3441 $feedUrl,
3442 $this->msg( "site-{$type}-feed", $sitename )->text()
3443 );
3444 }
3445 } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
3446 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
3447 foreach ( $config->get( 'AdvertisedFeedTypes' ) as $format ) {
3448 $feedLinks[] = $this->feedLink(
3449 $format,
3450 $rctitle->getLocalURL( [ 'feed' => $format ] ),
3451 # For grep: 'site-rss-feed', 'site-atom-feed'
3452 $this->msg( "site-{$format}-feed", $sitename )->text()
3453 );
3454 }
3455 }
3456
3457 # Allow extensions to change the list pf feeds. This hook is primarily for changing,
3458 # manipulating or removing existing feed tags. If you want to add new feeds, you should
3459 # use OutputPage::addFeedLink() instead.
3460 Hooks::run( 'AfterBuildFeedLinks', [ &$feedLinks ] );
3461
3462 $tags += $feedLinks;
3463 }
3464
3465 # Canonical URL
3466 if ( $config->get( 'EnableCanonicalServerLink' ) ) {
3467 if ( $canonicalUrl !== false ) {
3468 $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL );
3469 } else {
3470 if ( $this->isArticleRelated() ) {
3471 // This affects all requests where "setArticleRelated" is true. This is
3472 // typically all requests that show content (query title, curid, oldid, diff),
3473 // and all wikipage actions (edit, delete, purge, info, history etc.).
3474 // It does not apply to File pages and Special pages.
3475 // 'history' and 'info' actions address page metadata rather than the page
3476 // content itself, so they may not be canonicalized to the view page url.
3477 // TODO: this ought to be better encapsulated in the Action class.
3478 $action = Action::getActionName( $this->getContext() );
3479 if ( in_array( $action, [ 'history', 'info' ] ) ) {
3480 $query = "action={$action}";
3481 } else {
3482 $query = '';
3483 }
3484 $canonicalUrl = $this->getTitle()->getCanonicalURL( $query );
3485 } else {
3486 $reqUrl = $this->getRequest()->getRequestURL();
3487 $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL );
3488 }
3489 }
3490 }
3491 if ( $canonicalUrl !== false ) {
3492 $tags[] = Html::element( 'link', [
3493 'rel' => 'canonical',
3494 'href' => $canonicalUrl
3495 ] );
3496 }
3497
3498 return $tags;
3499 }
3500
3509 private function feedLink( $type, $url, $text ) {
3510 return Html::element( 'link', [
3511 'rel' => 'alternate',
3512 'type' => "application/$type+xml",
3513 'title' => $text,
3514 'href' => $url ]
3515 );
3516 }
3517
3527 public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
3528 $options = [];
3529 if ( $media ) {
3530 $options['media'] = $media;
3531 }
3532 if ( $condition ) {
3533 $options['condition'] = $condition;
3534 }
3535 if ( $dir ) {
3536 $options['dir'] = $dir;
3537 }
3538 $this->styles[$style] = $options;
3539 }
3540
3548 public function addInlineStyle( $style_css, $flip = 'noflip' ) {
3549 if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
3550 # If wanted, and the interface is right-to-left, flip the CSS
3551 $style_css = CSSJanus::transform( $style_css, true, false );
3552 }
3553 $this->mInlineStyles .= Html::inlineStyle( $style_css );
3554 }
3555
3561 protected function buildExemptModules() {
3563
3564 $chunks = [];
3565 // Things that go after the ResourceLoaderDynamicStyles marker
3566 $append = [];
3567
3568 // Exempt 'user' styles module (may need 'excludepages' for live preview)
3569 if ( $this->isUserCssPreview() ) {
3570 $append[] = $this->makeResourceLoaderLink(
3571 'user.styles',
3573 [ 'excludepage' => $this->getTitle()->getPrefixedDBkey() ]
3574 );
3575
3576 // Load the previewed CSS. Janus it if needed.
3577 // User-supplied CSS is assumed to in the wiki's content language.
3578 $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' );
3579 if ( $this->getLanguage()->getDir() !== $wgContLang->getDir() ) {
3580 $previewedCSS = CSSJanus::transform( $previewedCSS, true, false );
3581 }
3582 $append[] = Html::inlineStyle( $previewedCSS );
3583 }
3584
3585 // We want site, private and user styles to override dynamically added styles from
3586 // general modules, but we want dynamically added styles to override statically added
3587 // style modules. So the order has to be:
3588 // - page style modules (formatted by ResourceLoaderClientHtml::getHeadHtml())
3589 // - dynamically loaded styles (added by mw.loader before ResourceLoaderDynamicStyles)
3590 // - ResourceLoaderDynamicStyles marker
3591 // - site/private/user styles
3592
3593 // Add legacy styles added through addStyle()/addInlineStyle() here
3594 $chunks[] = implode( '', $this->buildCssLinksArray() ) . $this->mInlineStyles;
3595
3596 $chunks[] = Html::element(
3597 'meta',
3598 [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ]
3599 );
3600
3601 $separateReq = [ 'site.styles', 'user.styles' ];
3602 foreach ( $this->rlExemptStyleModules as $group => $moduleNames ) {
3603 // Combinable modules
3604 $chunks[] = $this->makeResourceLoaderLink(
3605 array_diff( $moduleNames, $separateReq ),
3607 );
3608
3609 foreach ( array_intersect( $moduleNames, $separateReq ) as $name ) {
3610 // These require their own dedicated request in order to support "@import"
3611 // syntax, which is incompatible with concatenation. (T147667, T37562)
3612 $chunks[] = $this->makeResourceLoaderLink( $name,
3614 );
3615 }
3616 }
3617
3618 return self::combineWrappedStrings( array_merge( $chunks, $append ) );
3619 }
3620
3624 public function buildCssLinksArray() {
3625 $links = [];
3626
3627 foreach ( $this->styles as $file => $options ) {
3628 $link = $this->styleLink( $file, $options );
3629 if ( $link ) {
3630 $links[$file] = $link;
3631 }
3632 }
3633 return $links;
3634 }
3635
3643 protected function styleLink( $style, array $options ) {
3644 if ( isset( $options['dir'] ) ) {
3645 if ( $this->getLanguage()->getDir() != $options['dir'] ) {
3646 return '';
3647 }
3648 }
3649
3650 if ( isset( $options['media'] ) ) {
3651 $media = self::transformCssMedia( $options['media'] );
3652 if ( is_null( $media ) ) {
3653 return '';
3654 }
3655 } else {
3656 $media = 'all';
3657 }
3658
3659 if ( substr( $style, 0, 1 ) == '/' ||
3660 substr( $style, 0, 5 ) == 'http:' ||
3661 substr( $style, 0, 6 ) == 'https:' ) {
3662 $url = $style;
3663 } else {
3664 $config = $this->getConfig();
3665 $url = $config->get( 'StylePath' ) . '/' . $style . '?' .
3666 $config->get( 'StyleVersion' );
3667 }
3668
3669 $link = Html::linkedStyle( $url, $media );
3670
3671 if ( isset( $options['condition'] ) ) {
3672 $condition = htmlspecialchars( $options['condition'] );
3673 $link = "<!--[if $condition]>$link<![endif]-->";
3674 }
3675 return $link;
3676 }
3677
3699 public static function transformResourcePath( Config $config, $path ) {
3700 global $IP;
3701
3702 $localDir = $IP;
3703 $remotePathPrefix = $config->get( 'ResourceBasePath' );
3704 if ( $remotePathPrefix === '' ) {
3705 // The configured base path is required to be empty string for
3706 // wikis in the domain root
3707 $remotePath = '/';
3708 } else {
3709 $remotePath = $remotePathPrefix;
3710 }
3711 if ( strpos( $path, $remotePath ) !== 0 || substr( $path, 0, 2 ) === '//' ) {
3712 // - Path is outside wgResourceBasePath, ignore.
3713 // - Path is protocol-relative. Fixes T155310. Not supported by RelPath lib.
3714 return $path;
3715 }
3716 // For files in resources, extensions/ or skins/, ResourceBasePath is preferred here.
3717 // For other misc files in $IP, we'll fallback to that as well. There is, however, a fourth
3718 // supported dir/path pair in the configuration (wgUploadDirectory, wgUploadPath)
3719 // which is not expected to be in wgResourceBasePath on CDNs. (T155146)
3720 $uploadPath = $config->get( 'UploadPath' );
3721 if ( strpos( $path, $uploadPath ) === 0 ) {
3722 $localDir = $config->get( 'UploadDirectory' );
3723 $remotePathPrefix = $remotePath = $uploadPath;
3724 }
3725
3726 $path = RelPath::getRelativePath( $path, $remotePath );
3727 return self::transformFilePath( $remotePathPrefix, $localDir, $path );
3728 }
3729
3741 public static function transformFilePath( $remotePathPrefix, $localPath, $file ) {
3742 $hash = md5_file( "$localPath/$file" );
3743 if ( $hash === false ) {
3744 wfLogWarning( __METHOD__ . ": Failed to hash $localPath/$file" );
3745 $hash = '';
3746 }
3747 return "$remotePathPrefix/$file?" . substr( $hash, 0, 5 );
3748 }
3749
3757 public static function transformCssMedia( $media ) {
3759
3760 // https://www.w3.org/TR/css3-mediaqueries/#syntax
3761 $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
3762
3763 // Switch in on-screen display for media testing
3764 $switches = [
3765 'printable' => 'print',
3766 'handheld' => 'handheld',
3767 ];
3768 foreach ( $switches as $switch => $targetMedia ) {
3769 if ( $wgRequest->getBool( $switch ) ) {
3770 if ( $media == $targetMedia ) {
3771 $media = '';
3772 } elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
3773 /* This regex will not attempt to understand a comma-separated media_query_list
3774 *
3775 * Example supported values for $media:
3776 * 'screen', 'only screen', 'screen and (min-width: 982px)' ),
3777 * Example NOT supported value for $media:
3778 * '3d-glasses, screen, print and resolution > 90dpi'
3779 *
3780 * If it's a print request, we never want any kind of screen stylesheets
3781 * If it's a handheld request (currently the only other choice with a switch),
3782 * we don't want simple 'screen' but we might want screen queries that
3783 * have a max-width or something, so we'll pass all others on and let the
3784 * client do the query.
3785 */
3786 if ( $targetMedia == 'print' || $media == 'screen' ) {
3787 return null;
3788 }
3789 }
3790 }
3791 }
3792
3793 return $media;
3794 }
3795
3802 public function addWikiMsg( /*...*/ ) {
3803 $args = func_get_args();
3804 $name = array_shift( $args );
3805 $this->addWikiMsgArray( $name, $args );
3806 }
3807
3816 public function addWikiMsgArray( $name, $args ) {
3817 $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
3818 }
3819
3845 public function wrapWikiMsg( $wrap /*, ...*/ ) {
3846 $msgSpecs = func_get_args();
3847 array_shift( $msgSpecs );
3848 $msgSpecs = array_values( $msgSpecs );
3849 $s = $wrap;
3850 foreach ( $msgSpecs as $n => $spec ) {
3851 if ( is_array( $spec ) ) {
3852 $args = $spec;
3853 $name = array_shift( $args );
3854 if ( isset( $args['options'] ) ) {
3855 unset( $args['options'] );
3857 'Adding "options" to ' . __METHOD__ . ' is no longer supported',
3858 '1.20'
3859 );
3860 }
3861 } else {
3862 $args = [];
3863 $name = $spec;
3864 }
3865 $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
3866 }
3867 $this->addWikiText( $s );
3868 }
3869
3875 public function isTOCEnabled() {
3876 return $this->mEnableTOC;
3877 }
3878
3885 public function enableSectionEditLinks( $flag = true ) {
3886 wfDeprecated( __METHOD__, '1.31' );
3887 }
3888
3894 public function sectionEditLinksEnabled() {
3895 wfDeprecated( __METHOD__, '1.31' );
3896 return true;
3897 }
3898
3906 public static function setupOOUI( $skinName = 'default', $dir = 'ltr' ) {
3907 $themes = ResourceLoaderOOUIModule::getSkinThemeMap();
3908 $theme = isset( $themes[$skinName] ) ? $themes[$skinName] : $themes['default'];
3909 // For example, 'OOUI\WikimediaUITheme'.
3910 $themeClass = "OOUI\\{$theme}Theme";
3911 OOUI\Theme::setSingleton( new $themeClass() );
3912 OOUI\Element::setDefaultDir( $dir );
3913 }
3914
3921 public function enableOOUI() {
3922 self::setupOOUI(
3923 strtolower( $this->getSkin()->getSkinName() ),
3924 $this->getLanguage()->getDir()
3925 );
3926 $this->addModuleStyles( [
3927 'oojs-ui-core.styles',
3928 'oojs-ui.styles.indicators',
3929 'oojs-ui.styles.textures',
3930 'mediawiki.widgets.styles',
3931 'oojs-ui.styles.icons-content',
3932 'oojs-ui.styles.icons-alerts',
3933 'oojs-ui.styles.icons-interactions',
3934 ] );
3935 }
3936
3942 protected function addLogoPreloadLinkHeaders() {
3943 $logo = ResourceLoaderSkinModule::getLogo( $this->getConfig() );
3944
3945 $tags = [];
3946 $logosPerDppx = [];
3947 $logos = [];
3948
3949 if ( !is_array( $logo ) ) {
3950 // No media queries required if we only have one variant
3951 $this->addLinkHeader( '<' . $logo . '>;rel=preload;as=image' );
3952 return;
3953 }
3954
3955 if ( isset( $logo['svg'] ) ) {
3956 // No media queries required if we only have a 1x and svg variant
3957 // because all preload-capable browsers support SVGs
3958 $this->addLinkHeader( '<' . $logo['svg'] . '>;rel=preload;as=image' );
3959 return;
3960 }
3961
3962 foreach ( $logo as $dppx => $src ) {
3963 // Keys are in this format: "1.5x"
3964 $dppx = substr( $dppx, 0, -1 );
3965 $logosPerDppx[$dppx] = $src;
3966 }
3967
3968 // Because PHP can't have floats as array keys
3969 uksort( $logosPerDppx, function ( $a , $b ) {
3970 $a = floatval( $a );
3971 $b = floatval( $b );
3972
3973 if ( $a == $b ) {
3974 return 0;
3975 }
3976 // Sort from smallest to largest (e.g. 1x, 1.5x, 2x)
3977 return ( $a < $b ) ? -1 : 1;
3978 } );
3979
3980 foreach ( $logosPerDppx as $dppx => $src ) {
3981 $logos[] = [ 'dppx' => $dppx, 'src' => $src ];
3982 }
3983
3984 $logosCount = count( $logos );
3985 // Logic must match ResourceLoaderSkinModule:
3986 // - 1x applies to resolution < 1.5dppx
3987 // - 1.5x applies to resolution >= 1.5dppx && < 2dppx
3988 // - 2x applies to resolution >= 2dppx
3989 // Note that min-resolution and max-resolution are both inclusive.
3990 for ( $i = 0; $i < $logosCount; $i++ ) {
3991 if ( $i === 0 ) {
3992 // Smallest dppx
3993 // min-resolution is ">=" (larger than or equal to)
3994 // "not min-resolution" is essentially "<"
3995 $media_query = 'not all and (min-resolution: ' . $logos[ 1 ]['dppx'] . 'dppx)';
3996 } elseif ( $i !== $logosCount - 1 ) {
3997 // In between
3998 // Media query expressions can only apply "not" to the entire expression
3999 // (e.g. can't express ">= 1.5 and not >= 2).
4000 // Workaround: Use <= 1.9999 in place of < 2.
4001 $upper_bound = floatval( $logos[ $i + 1 ]['dppx'] ) - 0.000001;
4002 $media_query = '(min-resolution: ' . $logos[ $i ]['dppx'] .
4003 'dppx) and (max-resolution: ' . $upper_bound . 'dppx)';
4004 } else {
4005 // Largest dppx
4006 $media_query = '(min-resolution: ' . $logos[ $i ]['dppx'] . 'dppx)';
4007 }
4008
4009 $this->addLinkHeader(
4010 '<' . $logos[$i]['src'] . '>;rel=preload;as=image;media=' . $media_query
4011 );
4012 }
4013 }
4014}
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
$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:917
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:737
$IP
Definition WebStart.php:52
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:122
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition Article.php:833
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)
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
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,...
Definition LinkBatch.php:97
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:159
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
addWikiTextWithTitle( $text, &$title, $linestart=true)
Add wikitext with a custom Title object.
addScriptFile( $file, $version=null)
Add a JavaScript file out of skins/common, or a given relative path.
disable()
Disable output completely, i.e.
addLink(array $linkarr)
Add a new <link> tag to the page header.
ResourceLoader $mResourceLoader
showFileCopyError( $old, $new)
getCanonicalUrl()
Returns the URL to be used for the <link rel=canonical>> if one is set.
getPageTitle()
Return the "page title", i.e.
int $mStatusCode
getModuleScripts( $filter=false, $position=null)
Get the list of module JS to include 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
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...
getSyndicationLinks()
Return URLs for each supported syndication format for this page.
addMeta( $name, $val)
Add a new "<meta>" tag To add an http-equiv meta tag, precede the name with "http:".
showFileNotFoundError( $name)
bool $mIsarticle
Is the displayed content related to the source of the corresponding wiki article.
getMetadataAttribute()
Get the value of the "rel" attribute for metadata links.
parseInline( $text, $linestart=true, $interface=false)
Parse wikitext, strip paragraphs, 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.
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
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.
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.
addParserOutputText( $parserOutput, $poOptions=[])
Add the HTML associated with a ParserOutput object, without any metadata.
setSyndicated( $show=true)
Add or remove feed links in the page header This is mainly kept for backward compatibility,...
bool $mHideNewSectionLink
adaptCdnTTL( $mtime, $minTTL=0, $maxTTL=0)
Get TTL in [$minTTL,$maxTTL] in pass it to lowerCdnMaxage()
sendCacheControl()
Send cache control HTTP headers.
string $mPageTitleActionText
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)
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.
array $mMetatags
Should be private.
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?
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.
addParserOutputContent( $parserOutput, $poOptions=[])
Add the HTML and enhancements for it (like ResourceLoader modules) associated with a ParserOutput obj...
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.
string $rlUserModuleState
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.
setArticleFlag( $v)
Set whether the displayed content is related to the source of the corresponding article on the wiki S...
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)
Add only CSS of one or more modules recognized by ResourceLoader.
setHTMLTitle( $name)
"HTML title" means the contents of "<title>".
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.
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.
addWikiTextTitleTidy( $text, &$title, $linestart=true)
Add wikitext with a custom Title object and tidy enabled.
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...
getPreventClickjacking()
Get the prevent-clickjacking flag.
string $mPagetitle
Should be private - has getter and setter.
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.
addWikiMsgArray( $name, $args)
Add a wikitext-formatted message to the output.
addModuleScripts( $modules)
Add only JS of one or more modules recognized by ResourceLoader.
addBodyClasses( $classes)
Add a class to the <body> element.
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.
addParserOutput( $parserOutput, $poOptions=[])
Add everything from a ParserOutput object.
bool $mNoGallery
Comes from the parser.
getModuleStyles( $filter=false, $position=null)
Get the list of module CSS to include 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 user-JavaScript or user-CSS preview,...
array $limitReportJSData
Profiling data.
setPageTitleActionText( $text)
Set the new value of the "action text", this will be added to the "HTML title", separated from it wit...
getFileSearchOptions()
Get the files used on this page.
setArticleRelated( $v)
Set whether this page is related an article on the wiki Setting false will cause the change of "artic...
addWikiTextTidy( $text, $linestart=true)
Add wikitext with tidy enabled.
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.
getRlClient()
Call this to freeze the module queue and JS config and create a formatter.
static transformFilePath( $remotePathPrefix, $localPath, $file)
Utility method for transformResourceFilePath().
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 existed in GET.
setStatusCode( $statusCode)
Set the HTTP status code to send with the output.
getCacheVaryCookies()
Get the list of cookies that will influence on 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.
showNewSectionLink()
Show an "add new section" link?
bool $mDoNothing
Whether output is disabled.
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)
Lower the value of the "s-maxage" part of the "Cache-control" HTTP header.
forceHideNewSectionLink()
Forcibly hide the new section link?
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.
addMetadataLink(array $linkarr)
Add a new <link> with "rel" attribute set to "meta".
parserOptions( $options=null)
Get/set the ParserOptions object to use for wikitext parsing.
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...
string $mInlineStyles
Inline CSS styles.
array $mFileVersion
addModules( $modules)
Add one or more modules recognized by ResourceLoader.
buildExemptModules()
Build exempt modules and legacy non-ResourceLoader styles.
string $mRedirectCode
getProperty( $name)
Get an additional output property.
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
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.
addParserOutputMetadata( $parserOutput)
Add all metadata associated with a ParserOutput object, but without the actual HTML.
getMetaTags()
Returns the current <meta> tags.
bool $mArticleBodyOnly
Flag if output should only contain the body of the article.
addLogoPreloadLinkHeaders()
Add Link headers for preloading the wiki's logo.
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.
getPageTitleActionText()
Get the value of the "action text".
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.
Bootstrap a ResourceLoader client on an HTML page.
setModules(array $modules)
Ensure one or more modules are loaded.
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.
static makeLoad(ResourceLoaderContext $mainContext, array $modules, $only, array $extraQuery=[])
Explicily load or embed modules on a page.
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.
static preloadTitleInfo(ResourceLoaderContext $context, IDatabase $db, array $moduleNames)
Dynamic JavaScript and CSS resource loading system.
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 makeInlineScript( $script)
Returns an HTML script tag that runs given JS code after startup and base modules.
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:36
getHtmlElementAttributes()
Return values for <html> element.
Definition Skin.php:452
getSkinName()
Definition Skin.php:153
addToBodyAttributes( $out, &$bodyAttrs)
This will be called by OutputPage::headElement when it is creating the "<body>" tag,...
Definition Skin.php:468
getPageClasses( $title)
TODO: document.
Definition Skin.php:424
setupSkinUserCss(OutputPage $out)
Add skin specific stylesheets Calling this method with an $out of anything but the same OutputPage in...
static resolveAlias( $alias)
Given a special page name with a possible subpage, return an array where the first element is the spe...
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:5005
A wrapper class which causes Xml::encodeJsVar() and Xml::encodeJsCall() to interpret a given string a...
Definition XmlJsCode.php:39
$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 class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the local content language as $wgContLang
Definition design.txt:57
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser getSkin(). See also skin.txt. Language Represents the language used for incidental text
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition design.txt:18
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:233
const PROTO_CURRENT
Definition Defines.php:232
const NS_SPECIAL
Definition Defines.php:63
const PROTO_RELATIVE
Definition Defines.php:231
const NS_CATEGORY
Definition Defines.php:88
the array() calling protocol came about after MediaWiki 1.4rc1.
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition hooks.txt:2228
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:2806
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:831
either a plain
Definition hooks.txt:2056
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:2001
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:865
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:2811
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:964
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 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
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition hooks.txt:3021
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:2014
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:2818
this hook is for auditing only $response
Definition hooks.txt:783
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:1620
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:2056
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:1275
returning false will NOT prevent logging $e
Definition hooks.txt:2176
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.".
Interface for objects which can provide a MediaWiki context on request.
const DB_REPLICA
Definition defines.php:25
$params
if(!isset( $args[0])) $lang
$header