MediaWiki REL1_30
OutputPage.php
Go to the documentation of this file.
1<?php
26use WrappedString\WrappedString;
27use WrappedString\WrappedStringList;
28
46 protected $mMetatags = [];
47
49 protected $mLinktags = [];
50
52 protected $mCanonicalUrl = false;
53
58 protected $mExtStyles = [];
59
63 public $mPagetitle = '';
64
69 public $mBodytext = '';
70
72 private $mHTMLtitle = '';
73
78 private $mIsarticle = false;
79
81 private $mIsArticleRelated = true;
82
87 private $mPrintable = false;
88
93 private $mSubtitle = [];
94
96 public $mRedirect = '';
97
99 protected $mStatusCode;
100
105 protected $mLastModified = '';
106
108 protected $mCategoryLinks = [];
109
111 protected $mCategories = [
112 'hidden' => [],
113 'normal' => [],
114 ];
115
117 protected $mIndicators = [];
118
120 private $mLanguageLinks = [];
121
128 private $mScripts = '';
129
131 protected $mInlineStyles = '';
132
137 public $mPageLinkTitle = '';
138
140 protected $mHeadItems = [];
141
144
146 protected $mModules = [];
147
149 protected $mModuleScripts = [];
150
152 protected $mModuleStyles = [];
153
156
158 private $rlClient;
159
162
165
168
170 protected $mJsConfigVars = [];
171
173 protected $mTemplateIds = [];
174
176 protected $mImageTimeKeys = [];
177
179 public $mRedirectCode = '';
180
181 protected $mFeedLinksAppendQuery = null;
182
188 protected $mAllowedModules = [
190 ];
191
193 protected $mDoNothing = false;
194
195 // Parser related.
196
198 protected $mContainsNewMagic = 0;
199
204 protected $mParserOptions = null;
205
211 private $mFeedLinks = [];
212
213 // Gwicke work on squid caching? Roughly from 2003.
214 protected $mEnableClientCache = true;
215
217 private $mArticleBodyOnly = false;
218
220 protected $mNewSectionLink = false;
221
223 protected $mHideNewSectionLink = false;
224
230 public $mNoGallery = false;
231
234
236 protected $mCdnMaxage = 0;
238 protected $mCdnMaxageLimit = INF;
239
245 protected $mPreventClickjacking = true;
246
248 private $mRevisionId = null;
249
251 private $mRevisionTimestamp = null;
252
254 protected $mFileVersion = null;
255
264 protected $styles = [];
265
266 private $mIndexPolicy = 'index';
267 private $mFollowPolicy = 'follow';
268 private $mVaryHeader = [
269 'Accept-Encoding' => [ 'match=gzip' ],
270 ];
271
278 private $mRedirectedFrom = null;
279
283 private $mProperties = [];
284
288 private $mTarget = null;
289
293 private $mEnableTOC = false;
294
299
304
306 private $limitReportJSData = [];
307
311 private $mLinkHeader = [];
312
320 if ( $context === null ) {
321 # Extensions should use `new RequestContext` instead of `new OutputPage` now.
322 wfDeprecated( __METHOD__, '1.18' );
323 } else {
324 $this->setContext( $context );
325 }
326 }
327
334 public function redirect( $url, $responsecode = '302' ) {
335 # Strip newlines as a paranoia check for header injection in PHP<5.1.2
336 $this->mRedirect = str_replace( "\n", '', $url );
337 $this->mRedirectCode = $responsecode;
338 }
339
345 public function getRedirect() {
346 return $this->mRedirect;
347 }
348
357 public function setCopyrightUrl( $url ) {
358 $this->copyrightUrl = $url;
359 }
360
366 public function setStatusCode( $statusCode ) {
367 $this->mStatusCode = $statusCode;
368 }
369
377 function addMeta( $name, $val ) {
378 array_push( $this->mMetatags, [ $name, $val ] );
379 }
380
387 public function getMetaTags() {
388 return $this->mMetatags;
389 }
390
398 function addLink( array $linkarr ) {
399 array_push( $this->mLinktags, $linkarr );
400 }
401
408 public function getLinkTags() {
409 return $this->mLinktags;
410 }
411
419 function addMetadataLink( array $linkarr ) {
420 $linkarr['rel'] = $this->getMetadataAttribute();
421 $this->addLink( $linkarr );
422 }
423
429 function setCanonicalUrl( $url ) {
430 $this->mCanonicalUrl = $url;
431 }
432
440 public function getCanonicalUrl() {
442 }
443
449 public function getMetadataAttribute() {
450 # note: buggy CC software only reads first "meta" link
451 static $haveMeta = false;
452 if ( $haveMeta ) {
453 return 'alternate meta';
454 } else {
455 $haveMeta = true;
456 return 'meta';
457 }
458 }
459
467 function addScript( $script ) {
468 $this->mScripts .= $script;
469 }
470
480 public function addExtensionStyle( $url ) {
481 wfDeprecated( __METHOD__, '1.27' );
482 array_push( $this->mExtStyles, $url );
483 }
484
491 function getExtStyle() {
492 wfDeprecated( __METHOD__, '1.27' );
493 return $this->mExtStyles;
494 }
495
504 public function addScriptFile( $file, $version = null ) {
505 // See if $file parameter is an absolute URL or begins with a slash
506 if ( substr( $file, 0, 1 ) == '/' || preg_match( '#^[a-z]*://#i', $file ) ) {
507 $path = $file;
508 } else {
509 $path = $this->getConfig()->get( 'StylePath' ) . "/common/{$file}";
510 }
511 if ( is_null( $version ) ) {
512 $version = $this->getConfig()->get( 'StyleVersion' );
513 }
514 $this->addScript( Html::linkedScript( wfAppendQuery( $path, $version ) ) );
515 }
516
523 public function addInlineScript( $script ) {
524 $this->mScripts .= Html::inlineScript( $script );
525 }
526
535 protected function filterModules( array $modules, $position = null,
537 ) {
539 $filteredModules = [];
540 foreach ( $modules as $val ) {
541 $module = $resourceLoader->getModule( $val );
542 if ( $module instanceof ResourceLoaderModule
543 && $module->getOrigin() <= $this->getAllowedModules( $type )
544 && ( is_null( $position ) || $module->getPosition() == $position )
545 ) {
546 if ( $this->mTarget && !in_array( $this->mTarget, $module->getTargets() ) ) {
547 $this->warnModuleTargetFilter( $module->getName() );
548 continue;
549 }
550 $filteredModules[] = $val;
551 }
552 }
553 return $filteredModules;
554 }
555
556 private function warnModuleTargetFilter( $moduleName ) {
557 static $warnings = [];
558 if ( isset( $warnings[$this->mTarget][$moduleName] ) ) {
559 return;
560 }
561 $warnings[$this->mTarget][$moduleName] = true;
562 $this->getResourceLoader()->getLogger()->debug(
563 'Module "{module}" not loadable on target "{target}".',
564 [
565 'module' => $moduleName,
566 'target' => $this->mTarget,
567 ]
568 );
569 }
570
580 public function getModules( $filter = false, $position = null, $param = 'mModules',
582 ) {
583 $modules = array_values( array_unique( $this->$param ) );
584 return $filter
585 ? $this->filterModules( $modules, $position, $type )
586 : $modules;
587 }
588
596 public function addModules( $modules ) {
597 $this->mModules = array_merge( $this->mModules, (array)$modules );
598 }
599
607 public function getModuleScripts( $filter = false, $position = null ) {
608 return $this->getModules( $filter, $position, 'mModuleScripts',
610 );
611 }
612
620 public function addModuleScripts( $modules ) {
621 $this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules );
622 }
623
631 public function getModuleStyles( $filter = false, $position = null ) {
632 return $this->getModules( $filter, $position, 'mModuleStyles',
634 );
635 }
636
646 public function addModuleStyles( $modules ) {
647 $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
648 }
649
653 public function getTarget() {
654 return $this->mTarget;
655 }
656
662 public function setTarget( $target ) {
663 $this->mTarget = $target;
664 }
665
671 function getHeadItemsArray() {
672 return $this->mHeadItems;
673 }
674
687 public function addHeadItem( $name, $value ) {
688 $this->mHeadItems[$name] = $value;
689 }
690
697 public function addHeadItems( $values ) {
698 $this->mHeadItems = array_merge( $this->mHeadItems, (array)$values );
699 }
700
707 public function hasHeadItem( $name ) {
708 return isset( $this->mHeadItems[$name] );
709 }
710
717 public function addBodyClasses( $classes ) {
718 $this->mAdditionalBodyClasses = array_merge( $this->mAdditionalBodyClasses, (array)$classes );
719 }
720
725 public function setETag( $tag ) {
726 }
727
735 public function setArticleBodyOnly( $only ) {
736 $this->mArticleBodyOnly = $only;
737 }
738
744 public function getArticleBodyOnly() {
746 }
747
755 public function setProperty( $name, $value ) {
756 $this->mProperties[$name] = $value;
757 }
758
766 public function getProperty( $name ) {
767 if ( isset( $this->mProperties[$name] ) ) {
768 return $this->mProperties[$name];
769 } else {
770 return null;
771 }
772 }
773
785 public function checkLastModified( $timestamp ) {
786 if ( !$timestamp || $timestamp == '19700101000000' ) {
787 wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
788 return false;
789 }
790 $config = $this->getConfig();
791 if ( !$config->get( 'CachePages' ) ) {
792 wfDebug( __METHOD__ . ": CACHE DISABLED\n" );
793 return false;
794 }
795
796 $timestamp = wfTimestamp( TS_MW, $timestamp );
797 $modifiedTimes = [
798 'page' => $timestamp,
799 'user' => $this->getUser()->getTouched(),
800 'epoch' => $config->get( 'CacheEpoch' )
801 ];
802 if ( $config->get( 'UseSquid' ) ) {
803 // T46570: the core page itself may not change, but resources might
804 $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, time() - $config->get( 'SquidMaxage' ) );
805 }
806 Hooks::run( 'OutputPageCheckLastModified', [ &$modifiedTimes, $this ] );
807
808 $maxModified = max( $modifiedTimes );
809 $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
810
811 $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' );
812 if ( $clientHeader === false ) {
813 wfDebug( __METHOD__ . ": client did not send If-Modified-Since header", 'private' );
814 return false;
815 }
816
817 # IE sends sizes after the date like this:
818 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
819 # this breaks strtotime().
820 $clientHeader = preg_replace( '/;.*$/', '', $clientHeader );
821
822 MediaWiki\suppressWarnings(); // E_STRICT system time bitching
823 $clientHeaderTime = strtotime( $clientHeader );
824 MediaWiki\restoreWarnings();
825 if ( !$clientHeaderTime ) {
826 wfDebug( __METHOD__
827 . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
828 return false;
829 }
830 $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
831
832 # Make debug info
833 $info = '';
834 foreach ( $modifiedTimes as $name => $value ) {
835 if ( $info !== '' ) {
836 $info .= ', ';
837 }
838 $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
839 }
840
841 wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
842 wfTimestamp( TS_ISO_8601, $clientHeaderTime ), 'private' );
843 wfDebug( __METHOD__ . ": effective Last-Modified: " .
844 wfTimestamp( TS_ISO_8601, $maxModified ), 'private' );
845 if ( $clientHeaderTime < $maxModified ) {
846 wfDebug( __METHOD__ . ": STALE, $info", 'private' );
847 return false;
848 }
849
850 # Not modified
851 # Give a 304 Not Modified response code and disable body output
852 wfDebug( __METHOD__ . ": NOT MODIFIED, $info", 'private' );
853 ini_set( 'zlib.output_compression', 0 );
854 $this->getRequest()->response()->statusHeader( 304 );
855 $this->sendCacheControl();
856 $this->disable();
857
858 // Don't output a compressed blob when using ob_gzhandler;
859 // it's technically against HTTP spec and seems to confuse
860 // Firefox when the response gets split over two packets.
862
863 return true;
864 }
865
872 public function setLastModified( $timestamp ) {
873 $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp );
874 }
875
884 public function setRobotPolicy( $policy ) {
885 $policy = Article::formatRobotPolicy( $policy );
886
887 if ( isset( $policy['index'] ) ) {
888 $this->setIndexPolicy( $policy['index'] );
889 }
890 if ( isset( $policy['follow'] ) ) {
891 $this->setFollowPolicy( $policy['follow'] );
892 }
893 }
894
902 public function setIndexPolicy( $policy ) {
903 $policy = trim( $policy );
904 if ( in_array( $policy, [ 'index', 'noindex' ] ) ) {
905 $this->mIndexPolicy = $policy;
906 }
907 }
908
916 public function setFollowPolicy( $policy ) {
917 $policy = trim( $policy );
918 if ( in_array( $policy, [ 'follow', 'nofollow' ] ) ) {
919 $this->mFollowPolicy = $policy;
920 }
921 }
922
929 public function setPageTitleActionText( $text ) {
930 $this->mPageTitleActionText = $text;
931 }
932
938 public function getPageTitleActionText() {
940 }
941
948 public function setHTMLTitle( $name ) {
949 if ( $name instanceof Message ) {
950 $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
951 } else {
952 $this->mHTMLtitle = $name;
953 }
954 }
955
961 public function getHTMLTitle() {
962 return $this->mHTMLtitle;
963 }
964
970 public function setRedirectedFrom( $t ) {
971 $this->mRedirectedFrom = $t;
972 }
973
984 public function setPageTitle( $name ) {
985 if ( $name instanceof Message ) {
986 $name = $name->setContext( $this->getContext() )->text();
987 }
988
989 # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
990 # but leave "<i>foobar</i>" alone
991 $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) );
992 $this->mPagetitle = $nameWithTags;
993
994 # change "<i>foo&amp;bar</i>" to "foo&bar"
995 $this->setHTMLTitle(
996 $this->msg( 'pagetitle' )->rawParams( Sanitizer::stripAllTags( $nameWithTags ) )
997 ->inContentLanguage()
998 );
999 }
1000
1006 public function getPageTitle() {
1007 return $this->mPagetitle;
1008 }
1009
1015 public function setTitle( Title $t ) {
1016 $this->getContext()->setTitle( $t );
1017 }
1018
1024 public function setSubtitle( $str ) {
1025 $this->clearSubtitle();
1026 $this->addSubtitle( $str );
1027 }
1028
1034 public function addSubtitle( $str ) {
1035 if ( $str instanceof Message ) {
1036 $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
1037 } else {
1038 $this->mSubtitle[] = $str;
1039 }
1040 }
1041
1050 public static function buildBacklinkSubtitle( Title $title, $query = [] ) {
1051 if ( $title->isRedirect() ) {
1052 $query['redirect'] = 'no';
1053 }
1054 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1055 return wfMessage( 'backlinksubtitle' )
1056 ->rawParams( $linkRenderer->makeLink( $title, null, [], $query ) );
1057 }
1058
1065 public function addBacklinkSubtitle( Title $title, $query = [] ) {
1066 $this->addSubtitle( self::buildBacklinkSubtitle( $title, $query ) );
1067 }
1068
1072 public function clearSubtitle() {
1073 $this->mSubtitle = [];
1074 }
1075
1081 public function getSubtitle() {
1082 return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
1083 }
1084
1089 public function setPrintable() {
1090 $this->mPrintable = true;
1091 }
1092
1098 public function isPrintable() {
1099 return $this->mPrintable;
1100 }
1101
1105 public function disable() {
1106 $this->mDoNothing = true;
1107 }
1108
1114 public function isDisabled() {
1115 return $this->mDoNothing;
1116 }
1117
1123 public function showNewSectionLink() {
1125 }
1126
1132 public function forceHideNewSectionLink() {
1134 }
1135
1144 public function setSyndicated( $show = true ) {
1145 if ( $show ) {
1146 $this->setFeedAppendQuery( false );
1147 } else {
1148 $this->mFeedLinks = [];
1149 }
1150 }
1151
1161 public function setFeedAppendQuery( $val ) {
1162 $this->mFeedLinks = [];
1163
1164 foreach ( $this->getConfig()->get( 'AdvertisedFeedTypes' ) as $type ) {
1165 $query = "feed=$type";
1166 if ( is_string( $val ) ) {
1167 $query .= '&' . $val;
1168 }
1169 $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
1170 }
1171 }
1172
1179 public function addFeedLink( $format, $href ) {
1180 if ( in_array( $format, $this->getConfig()->get( 'AdvertisedFeedTypes' ) ) ) {
1181 $this->mFeedLinks[$format] = $href;
1182 }
1183 }
1184
1189 public function isSyndicated() {
1190 return count( $this->mFeedLinks ) > 0;
1191 }
1192
1197 public function getSyndicationLinks() {
1198 return $this->mFeedLinks;
1199 }
1200
1206 public function getFeedAppendQuery() {
1208 }
1209
1217 public function setArticleFlag( $v ) {
1218 $this->mIsarticle = $v;
1219 if ( $v ) {
1220 $this->mIsArticleRelated = $v;
1221 }
1222 }
1223
1230 public function isArticle() {
1231 return $this->mIsarticle;
1232 }
1233
1240 public function setArticleRelated( $v ) {
1241 $this->mIsArticleRelated = $v;
1242 if ( !$v ) {
1243 $this->mIsarticle = false;
1244 }
1245 }
1246
1252 public function isArticleRelated() {
1254 }
1255
1262 public function addLanguageLinks( array $newLinkArray ) {
1263 $this->mLanguageLinks += $newLinkArray;
1264 }
1265
1272 public function setLanguageLinks( array $newLinkArray ) {
1273 $this->mLanguageLinks = $newLinkArray;
1274 }
1275
1281 public function getLanguageLinks() {
1282 return $this->mLanguageLinks;
1283 }
1284
1290 public function addCategoryLinks( array $categories ) {
1292
1293 if ( !is_array( $categories ) || count( $categories ) == 0 ) {
1294 return;
1295 }
1296
1297 $res = $this->addCategoryLinksToLBAndGetResult( $categories );
1298
1299 # Set all the values to 'normal'.
1300 $categories = array_fill_keys( array_keys( $categories ), 'normal' );
1301
1302 # Mark hidden categories
1303 foreach ( $res as $row ) {
1304 if ( isset( $row->pp_value ) ) {
1305 $categories[$row->page_title] = 'hidden';
1306 }
1307 }
1308
1309 // Avoid PHP 7.1 warning of passing $this by reference
1310 $outputPage = $this;
1311 # Add the remaining categories to the skin
1312 if ( Hooks::run(
1313 'OutputPageMakeCategoryLinks',
1314 [ &$outputPage, $categories, &$this->mCategoryLinks ] )
1315 ) {
1316 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1317 foreach ( $categories as $category => $type ) {
1318 // array keys will cast numeric category names to ints, so cast back to string
1319 $category = (string)$category;
1320 $origcategory = $category;
1321 $title = Title::makeTitleSafe( NS_CATEGORY, $category );
1322 if ( !$title ) {
1323 continue;
1324 }
1325 $wgContLang->findVariantLink( $category, $title, true );
1326 if ( $category != $origcategory && array_key_exists( $category, $categories ) ) {
1327 continue;
1328 }
1329 $text = $wgContLang->convertHtml( $title->getText() );
1330 $this->mCategories[$type][] = $title->getText();
1331 $this->mCategoryLinks[$type][] = $linkRenderer->makeLink( $title, new HtmlArmor( $text ) );
1332 }
1333 }
1334 }
1335
1340 protected function addCategoryLinksToLBAndGetResult( array $categories ) {
1341 # Add the links to a LinkBatch
1342 $arr = [ NS_CATEGORY => $categories ];
1343 $lb = new LinkBatch;
1344 $lb->setArray( $arr );
1345
1346 # Fetch existence plus the hiddencat property
1347 $dbr = wfGetDB( DB_REPLICA );
1348 $fields = array_merge(
1349 LinkCache::getSelectFields(),
1350 [ 'page_namespace', 'page_title', 'pp_value' ]
1351 );
1352
1353 $res = $dbr->select( [ 'page', 'page_props' ],
1354 $fields,
1355 $lb->constructSet( 'page', $dbr ),
1356 __METHOD__,
1357 [],
1358 [ 'page_props' => [ 'LEFT JOIN', [
1359 'pp_propname' => 'hiddencat',
1360 'pp_page = page_id'
1361 ] ] ]
1362 );
1363
1364 # Add the results to the link cache
1365 $lb->addResultToCache( LinkCache::singleton(), $res );
1366
1367 return $res;
1368 }
1369
1375 public function setCategoryLinks( array $categories ) {
1376 $this->mCategoryLinks = [];
1377 $this->addCategoryLinks( $categories );
1378 }
1379
1388 public function getCategoryLinks() {
1389 return $this->mCategoryLinks;
1390 }
1391
1401 public function getCategories( $type = 'all' ) {
1402 if ( $type === 'all' ) {
1403 $allCategories = [];
1404 foreach ( $this->mCategories as $categories ) {
1405 $allCategories = array_merge( $allCategories, $categories );
1406 }
1407 return $allCategories;
1408 }
1409 if ( !isset( $this->mCategories[$type] ) ) {
1410 throw new InvalidArgumentException( 'Invalid category type given: ' . $type );
1411 }
1412 return $this->mCategories[$type];
1413 }
1414
1424 public function setIndicators( array $indicators ) {
1425 $this->mIndicators = $indicators + $this->mIndicators;
1426 // Keep ordered by key
1427 ksort( $this->mIndicators );
1428 }
1429
1438 public function getIndicators() {
1439 return $this->mIndicators;
1440 }
1441
1450 public function addHelpLink( $to, $overrideBaseUrl = false ) {
1451 $this->addModuleStyles( 'mediawiki.helplink' );
1452 $text = $this->msg( 'helppage-top-gethelp' )->escaped();
1453
1454 if ( $overrideBaseUrl ) {
1455 $helpUrl = $to;
1456 } else {
1457 $toUrlencoded = wfUrlencode( str_replace( ' ', '_', $to ) );
1458 $helpUrl = "//www.mediawiki.org/wiki/Special:MyLanguage/$toUrlencoded";
1459 }
1460
1461 $link = Html::rawElement(
1462 'a',
1463 [
1464 'href' => $helpUrl,
1465 'target' => '_blank',
1466 'class' => 'mw-helplink',
1467 ],
1468 $text
1469 );
1470
1471 $this->setIndicators( [ 'mw-helplink' => $link ] );
1472 }
1473
1482 public function disallowUserJs() {
1483 $this->reduceAllowedModules(
1486 );
1487
1488 // Site-wide styles are controlled by a config setting, see T73621
1489 // for background on why. User styles are never allowed.
1490 if ( $this->getConfig()->get( 'AllowSiteCSSOnRestrictedPages' ) ) {
1492 } else {
1494 }
1495 $this->reduceAllowedModules(
1497 $styleOrigin
1498 );
1499 }
1500
1507 public function getAllowedModules( $type ) {
1509 return min( array_values( $this->mAllowedModules ) );
1510 } else {
1511 return isset( $this->mAllowedModules[$type] )
1512 ? $this->mAllowedModules[$type]
1514 }
1515 }
1516
1526 public function reduceAllowedModules( $type, $level ) {
1527 $this->mAllowedModules[$type] = min( $this->getAllowedModules( $type ), $level );
1528 }
1529
1535 public function prependHTML( $text ) {
1536 $this->mBodytext = $text . $this->mBodytext;
1537 }
1538
1544 public function addHTML( $text ) {
1545 $this->mBodytext .= $text;
1546 }
1547
1557 public function addElement( $element, array $attribs = [], $contents = '' ) {
1558 $this->addHTML( Html::element( $element, $attribs, $contents ) );
1559 }
1560
1564 public function clearHTML() {
1565 $this->mBodytext = '';
1566 }
1567
1573 public function getHTML() {
1574 return $this->mBodytext;
1575 }
1576
1584 public function parserOptions( $options = null ) {
1585 if ( $options !== null && !empty( $options->isBogus ) ) {
1586 // Someone is trying to set a bogus pre-$wgUser PO. Check if it has
1587 // been changed somehow, and keep it if so.
1588 $anonPO = ParserOptions::newFromAnon();
1589 $anonPO->setEditSection( false );
1590 $anonPO->setAllowUnsafeRawHtml( false );
1591 if ( !$options->matches( $anonPO ) ) {
1592 wfLogWarning( __METHOD__ . ': Setting a changed bogus ParserOptions: ' . wfGetAllCallers( 5 ) );
1593 $options->isBogus = false;
1594 }
1595 }
1596
1597 if ( !$this->mParserOptions ) {
1598 if ( !$this->getContext()->getUser()->isSafeToLoad() ) {
1599 // $wgUser isn't unstubbable yet, so don't try to get a
1600 // ParserOptions for it. And don't cache this ParserOptions
1601 // either.
1602 $po = ParserOptions::newFromAnon();
1603 $po->setEditSection( false );
1604 $po->setAllowUnsafeRawHtml( false );
1605 $po->isBogus = true;
1606 if ( $options !== null ) {
1607 $this->mParserOptions = empty( $options->isBogus ) ? $options : null;
1608 }
1609 return $po;
1610 }
1611
1612 $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
1613 $this->mParserOptions->setEditSection( false );
1614 $this->mParserOptions->setAllowUnsafeRawHtml( false );
1615 }
1616
1617 if ( $options !== null && !empty( $options->isBogus ) ) {
1618 // They're trying to restore the bogus pre-$wgUser PO. Do the right
1619 // thing.
1620 return wfSetVar( $this->mParserOptions, null, true );
1621 } else {
1622 return wfSetVar( $this->mParserOptions, $options );
1623 }
1624 }
1625
1633 public function setRevisionId( $revid ) {
1634 $val = is_null( $revid ) ? null : intval( $revid );
1635 return wfSetVar( $this->mRevisionId, $val );
1636 }
1637
1643 public function getRevisionId() {
1644 return $this->mRevisionId;
1645 }
1646
1654 public function setRevisionTimestamp( $timestamp ) {
1655 return wfSetVar( $this->mRevisionTimestamp, $timestamp );
1656 }
1657
1664 public function getRevisionTimestamp() {
1666 }
1667
1674 public function setFileVersion( $file ) {
1675 $val = null;
1676 if ( $file instanceof File && $file->exists() ) {
1677 $val = [ 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ];
1678 }
1679 return wfSetVar( $this->mFileVersion, $val, true );
1680 }
1681
1687 public function getFileVersion() {
1688 return $this->mFileVersion;
1689 }
1690
1697 public function getTemplateIds() {
1698 return $this->mTemplateIds;
1699 }
1700
1707 public function getFileSearchOptions() {
1708 return $this->mImageTimeKeys;
1709 }
1710
1720 public function addWikiText( $text, $linestart = true, $interface = true ) {
1721 $title = $this->getTitle(); // Work around E_STRICT
1722 if ( !$title ) {
1723 throw new MWException( 'Title is null' );
1724 }
1725 $this->addWikiTextTitle( $text, $title, $linestart, /*tidy*/false, $interface );
1726 }
1727
1735 public function addWikiTextWithTitle( $text, &$title, $linestart = true ) {
1736 $this->addWikiTextTitle( $text, $title, $linestart );
1737 }
1738
1746 function addWikiTextTitleTidy( $text, &$title, $linestart = true ) {
1747 $this->addWikiTextTitle( $text, $title, $linestart, true );
1748 }
1749
1756 public function addWikiTextTidy( $text, $linestart = true ) {
1757 $title = $this->getTitle();
1758 $this->addWikiTextTitleTidy( $text, $title, $linestart );
1759 }
1760
1771 public function addWikiTextTitle( $text, Title $title, $linestart,
1772 $tidy = false, $interface = false
1773 ) {
1775
1776 $popts = $this->parserOptions();
1777 $oldTidy = $popts->setTidy( $tidy );
1778 $popts->setInterfaceMessage( (bool)$interface );
1779
1780 $parserOutput = $wgParser->getFreshParser()->parse(
1781 $text, $title, $popts,
1782 $linestart, true, $this->mRevisionId
1783 );
1784
1785 $popts->setTidy( $oldTidy );
1786
1787 $this->addParserOutput( $parserOutput );
1788 }
1789
1798 public function addParserOutputMetadata( $parserOutput ) {
1799 $this->mLanguageLinks += $parserOutput->getLanguageLinks();
1800 $this->addCategoryLinks( $parserOutput->getCategories() );
1801 $this->setIndicators( $parserOutput->getIndicators() );
1802 $this->mNewSectionLink = $parserOutput->getNewSection();
1803 $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
1804
1805 if ( !$parserOutput->isCacheable() ) {
1806 $this->enableClientCache( false );
1807 }
1808 $this->mNoGallery = $parserOutput->getNoGallery();
1809 $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
1810 $this->addModules( $parserOutput->getModules() );
1811 $this->addModuleScripts( $parserOutput->getModuleScripts() );
1812 $this->addModuleStyles( $parserOutput->getModuleStyles() );
1813 $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1814 $this->mPreventClickjacking = $this->mPreventClickjacking
1815 || $parserOutput->preventClickjacking();
1816
1817 // Template versioning...
1818 foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
1819 if ( isset( $this->mTemplateIds[$ns] ) ) {
1820 $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
1821 } else {
1822 $this->mTemplateIds[$ns] = $dbks;
1823 }
1824 }
1825 // File versioning...
1826 foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) {
1827 $this->mImageTimeKeys[$dbk] = $data;
1828 }
1829
1830 // Hooks registered in the object
1831 $parserOutputHooks = $this->getConfig()->get( 'ParserOutputHooks' );
1832 foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
1833 list( $hookName, $data ) = $hookInfo;
1834 if ( isset( $parserOutputHooks[$hookName] ) ) {
1835 call_user_func( $parserOutputHooks[$hookName], $this, $parserOutput, $data );
1836 }
1837 }
1838
1839 // Enable OOUI if requested via ParserOutput
1840 if ( $parserOutput->getEnableOOUI() ) {
1841 $this->enableOOUI();
1842 }
1843
1844 // Include parser limit report
1845 if ( !$this->limitReportJSData ) {
1846 $this->limitReportJSData = $parserOutput->getLimitReportJSData();
1847 }
1848
1849 // Link flags are ignored for now, but may in the future be
1850 // used to mark individual language links.
1851 $linkFlags = [];
1852 // Avoid PHP 7.1 warning of passing $this by reference
1853 $outputPage = $this;
1854 Hooks::run( 'LanguageLinks', [ $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ] );
1855 Hooks::run( 'OutputPageParserOutput', [ &$outputPage, $parserOutput ] );
1856
1857 // This check must be after 'OutputPageParserOutput' runs in addParserOutputMetadata
1858 // so that extensions may modify ParserOutput to toggle TOC.
1859 // This cannot be moved to addParserOutputText because that is not
1860 // called by EditPage for Preview.
1861 if ( $parserOutput->getTOCEnabled() && $parserOutput->getTOCHTML() ) {
1862 $this->mEnableTOC = true;
1863 }
1864 }
1865
1873 public function addParserOutputContent( $parserOutput ) {
1874 $this->addParserOutputText( $parserOutput );
1875
1876 $this->addModules( $parserOutput->getModules() );
1877 $this->addModuleScripts( $parserOutput->getModuleScripts() );
1878 $this->addModuleStyles( $parserOutput->getModuleStyles() );
1879
1880 $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1881 }
1882
1889 public function addParserOutputText( $parserOutput ) {
1890 $text = $parserOutput->getText();
1891 // Avoid PHP 7.1 warning of passing $this by reference
1892 $outputPage = $this;
1893 Hooks::run( 'OutputPageBeforeHTML', [ &$outputPage, &$text ] );
1894 $this->addHTML( $text );
1895 }
1896
1902 function addParserOutput( $parserOutput ) {
1903 $this->addParserOutputMetadata( $parserOutput );
1904
1905 // Touch section edit links only if not previously disabled
1906 if ( $parserOutput->getEditSectionTokens() ) {
1907 $parserOutput->setEditSectionTokens( $this->mEnableSectionEditLinks );
1908 }
1909
1910 $this->addParserOutputText( $parserOutput );
1911 }
1912
1918 public function addTemplate( &$template ) {
1919 $this->addHTML( $template->getHTML() );
1920 }
1921
1934 public function parse( $text, $linestart = true, $interface = false, $language = null ) {
1936
1937 if ( is_null( $this->getTitle() ) ) {
1938 throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
1939 }
1940
1941 $popts = $this->parserOptions();
1942 if ( $interface ) {
1943 $popts->setInterfaceMessage( true );
1944 }
1945 if ( $language !== null ) {
1946 $oldLang = $popts->setTargetLanguage( $language );
1947 }
1948
1949 $parserOutput = $wgParser->getFreshParser()->parse(
1950 $text, $this->getTitle(), $popts,
1951 $linestart, true, $this->mRevisionId
1952 );
1953
1954 if ( $interface ) {
1955 $popts->setInterfaceMessage( false );
1956 }
1957 if ( $language !== null ) {
1958 $popts->setTargetLanguage( $oldLang );
1959 }
1960
1961 return $parserOutput->getText();
1962 }
1963
1974 public function parseInline( $text, $linestart = true, $interface = false ) {
1975 $parsed = $this->parse( $text, $linestart, $interface );
1976 return Parser::stripOuterParagraph( $parsed );
1977 }
1978
1983 public function setSquidMaxage( $maxage ) {
1984 $this->setCdnMaxage( $maxage );
1985 }
1986
1992 public function setCdnMaxage( $maxage ) {
1993 $this->mCdnMaxage = min( $maxage, $this->mCdnMaxageLimit );
1994 }
1995
2002 public function lowerCdnMaxage( $maxage ) {
2003 $this->mCdnMaxageLimit = min( $maxage, $this->mCdnMaxageLimit );
2004 $this->setCdnMaxage( $this->mCdnMaxage );
2005 }
2006
2020 public function adaptCdnTTL( $mtime, $minTTL = 0, $maxTTL = 0 ) {
2021 $minTTL = $minTTL ?: IExpiringStore::TTL_MINUTE;
2022 $maxTTL = $maxTTL ?: $this->getConfig()->get( 'SquidMaxage' );
2023
2024 if ( $mtime === null || $mtime === false ) {
2025 return $minTTL; // entity does not exist
2026 }
2027
2028 $age = time() - wfTimestamp( TS_UNIX, $mtime );
2029 $adaptiveTTL = max( 0.9 * $age, $minTTL );
2030 $adaptiveTTL = min( $adaptiveTTL, $maxTTL );
2031
2032 $this->lowerCdnMaxage( (int)$adaptiveTTL );
2033
2034 return $adaptiveTTL;
2035 }
2036
2044 public function enableClientCache( $state ) {
2045 return wfSetVar( $this->mEnableClientCache, $state );
2046 }
2047
2054 static $cookies;
2055 if ( $cookies === null ) {
2056 $config = $this->getConfig();
2057 $cookies = array_merge(
2058 SessionManager::singleton()->getVaryCookies(),
2059 [
2060 'forceHTTPS',
2061 ],
2062 $config->get( 'CacheVaryCookies' )
2063 );
2064 Hooks::run( 'GetCacheVaryCookies', [ $this, &$cookies ] );
2065 }
2066 return $cookies;
2067 }
2068
2076 $request = $this->getRequest();
2077 foreach ( $this->getCacheVaryCookies() as $cookieName ) {
2078 if ( $request->getCookie( $cookieName, '', '' ) !== '' ) {
2079 wfDebug( __METHOD__ . ": found $cookieName\n" );
2080 return true;
2081 }
2082 }
2083 wfDebug( __METHOD__ . ": no cache-varying cookies found\n" );
2084 return false;
2085 }
2086
2095 public function addVaryHeader( $header, array $option = null ) {
2096 if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
2097 $this->mVaryHeader[$header] = [];
2098 }
2099 if ( !is_array( $option ) ) {
2100 $option = [];
2101 }
2102 $this->mVaryHeader[$header] = array_unique( array_merge( $this->mVaryHeader[$header], $option ) );
2103 }
2104
2111 public function getVaryHeader() {
2112 // If we vary on cookies, let's make sure it's always included here too.
2113 if ( $this->getCacheVaryCookies() ) {
2114 $this->addVaryHeader( 'Cookie' );
2115 }
2116
2117 foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2118 $this->addVaryHeader( $header, $options );
2119 }
2120 return 'Vary: ' . implode( ', ', array_keys( $this->mVaryHeader ) );
2121 }
2122
2128 public function addLinkHeader( $header ) {
2129 $this->mLinkHeader[] = $header;
2130 }
2131
2137 public function getLinkHeader() {
2138 if ( !$this->mLinkHeader ) {
2139 return false;
2140 }
2141
2142 return 'Link: ' . implode( ',', $this->mLinkHeader );
2143 }
2144
2150 public function getKeyHeader() {
2151 $cvCookies = $this->getCacheVaryCookies();
2152
2153 $cookiesOption = [];
2154 foreach ( $cvCookies as $cookieName ) {
2155 $cookiesOption[] = 'param=' . $cookieName;
2156 }
2157 $this->addVaryHeader( 'Cookie', $cookiesOption );
2158
2159 foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2160 $this->addVaryHeader( $header, $options );
2161 }
2162
2163 $headers = [];
2164 foreach ( $this->mVaryHeader as $header => $option ) {
2165 $newheader = $header;
2166 if ( is_array( $option ) && count( $option ) > 0 ) {
2167 $newheader .= ';' . implode( ';', $option );
2168 }
2169 $headers[] = $newheader;
2170 }
2171 $key = 'Key: ' . implode( ',', $headers );
2172
2173 return $key;
2174 }
2175
2185 $title = $this->getTitle();
2186 if ( !$title instanceof Title ) {
2187 return;
2188 }
2189
2190 $lang = $title->getPageLanguage();
2191 if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
2192 $variants = $lang->getVariants();
2193 $aloption = [];
2194 foreach ( $variants as $variant ) {
2195 if ( $variant === $lang->getCode() ) {
2196 continue;
2197 } else {
2198 $aloption[] = 'substr=' . $variant;
2199
2200 // IE and some other browsers use BCP 47 standards in
2201 // their Accept-Language header, like "zh-CN" or "zh-Hant".
2202 // We should handle these too.
2203 $variantBCP47 = wfBCP47( $variant );
2204 if ( $variantBCP47 !== $variant ) {
2205 $aloption[] = 'substr=' . $variantBCP47;
2206 }
2207 }
2208 }
2209 $this->addVaryHeader( 'Accept-Language', $aloption );
2210 }
2211 }
2212
2223 public function preventClickjacking( $enable = true ) {
2224 $this->mPreventClickjacking = $enable;
2225 }
2226
2232 public function allowClickjacking() {
2233 $this->mPreventClickjacking = false;
2234 }
2235
2242 public function getPreventClickjacking() {
2244 }
2245
2253 public function getFrameOptions() {
2254 $config = $this->getConfig();
2255 if ( $config->get( 'BreakFrames' ) ) {
2256 return 'DENY';
2257 } elseif ( $this->mPreventClickjacking && $config->get( 'EditPageFrameOptions' ) ) {
2258 return $config->get( 'EditPageFrameOptions' );
2259 }
2260 return false;
2261 }
2262
2266 public function sendCacheControl() {
2267 $response = $this->getRequest()->response();
2268 $config = $this->getConfig();
2269
2270 $this->addVaryHeader( 'Cookie' );
2271 $this->addAcceptLanguage();
2272
2273 # don't serve compressed data to clients who can't handle it
2274 # maintain different caches for logged-in users and non-logged in ones
2275 $response->header( $this->getVaryHeader() );
2276
2277 if ( $config->get( 'UseKeyHeader' ) ) {
2278 $response->header( $this->getKeyHeader() );
2279 }
2280
2281 if ( $this->mEnableClientCache ) {
2282 if (
2283 $config->get( 'UseSquid' ) &&
2284 !$response->hasCookies() &&
2285 !SessionManager::getGlobalSession()->isPersistent() &&
2286 !$this->isPrintable() &&
2287 $this->mCdnMaxage != 0 &&
2288 !$this->haveCacheVaryCookies()
2289 ) {
2290 if ( $config->get( 'UseESI' ) ) {
2291 # We'll purge the proxy cache explicitly, but require end user agents
2292 # to revalidate against the proxy on each visit.
2293 # Surrogate-Control controls our CDN, Cache-Control downstream caches
2294 wfDebug( __METHOD__ .
2295 ": proxy caching with ESI; {$this->mLastModified} **", 'private' );
2296 # start with a shorter timeout for initial testing
2297 # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
2298 $response->header(
2299 "Surrogate-Control: max-age={$config->get( 'SquidMaxage' )}" .
2300 "+{$this->mCdnMaxage}, content=\"ESI/1.0\""
2301 );
2302 $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
2303 } else {
2304 # We'll purge the proxy cache for anons explicitly, but require end user agents
2305 # to revalidate against the proxy on each visit.
2306 # IMPORTANT! The CDN needs to replace the Cache-Control header with
2307 # Cache-Control: s-maxage=0, must-revalidate, max-age=0
2308 wfDebug( __METHOD__ .
2309 ": local proxy caching; {$this->mLastModified} **", 'private' );
2310 # start with a shorter timeout for initial testing
2311 # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
2312 $response->header( "Cache-Control: " .
2313 "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" );
2314 }
2315 } else {
2316 # We do want clients to cache if they can, but they *must* check for updates
2317 # on revisiting the page.
2318 wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **", 'private' );
2319 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2320 $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
2321 }
2322 if ( $this->mLastModified ) {
2323 $response->header( "Last-Modified: {$this->mLastModified}" );
2324 }
2325 } else {
2326 wfDebug( __METHOD__ . ": no caching **", 'private' );
2327
2328 # In general, the absence of a last modified header should be enough to prevent
2329 # the client from using its cache. We send a few other things just to make sure.
2330 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2331 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
2332 $response->header( 'Pragma: no-cache' );
2333 }
2334 }
2335
2346 public function output( $return = false ) {
2348
2349 if ( $this->mDoNothing ) {
2350 return $return ? '' : null;
2351 }
2352
2353 $response = $this->getRequest()->response();
2354 $config = $this->getConfig();
2355
2356 if ( $this->mRedirect != '' ) {
2357 # Standards require redirect URLs to be absolute
2358 $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT );
2359
2360 $redirect = $this->mRedirect;
2362
2363 if ( Hooks::run( "BeforePageRedirect", [ $this, &$redirect, &$code ] ) ) {
2364 if ( $code == '301' || $code == '303' ) {
2365 if ( !$config->get( 'DebugRedirects' ) ) {
2366 $response->statusHeader( $code );
2367 }
2368 $this->mLastModified = wfTimestamp( TS_RFC2822 );
2369 }
2370 if ( $config->get( 'VaryOnXFP' ) ) {
2371 $this->addVaryHeader( 'X-Forwarded-Proto' );
2372 }
2373 $this->sendCacheControl();
2374
2375 $response->header( "Content-Type: text/html; charset=utf-8" );
2376 if ( $config->get( 'DebugRedirects' ) ) {
2377 $url = htmlspecialchars( $redirect );
2378 print "<!DOCTYPE html>\n<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
2379 print "<p>Location: <a href=\"$url\">$url</a></p>\n";
2380 print "</body>\n</html>\n";
2381 } else {
2382 $response->header( 'Location: ' . $redirect );
2383 }
2384 }
2385
2386 return $return ? '' : null;
2387 } elseif ( $this->mStatusCode ) {
2388 $response->statusHeader( $this->mStatusCode );
2389 }
2390
2391 # Buffer output; final headers may depend on later processing
2392 ob_start();
2393
2394 $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
2395 $response->header( 'Content-language: ' . $wgContLang->getHtmlCode() );
2396
2397 // Avoid Internet Explorer "compatibility view" in IE 8-10, so that
2398 // jQuery etc. can work correctly.
2399 $response->header( 'X-UA-Compatible: IE=Edge' );
2400
2401 if ( !$this->mArticleBodyOnly ) {
2402 $sk = $this->getSkin();
2403
2404 if ( $sk->shouldPreloadLogo() ) {
2406 }
2407 }
2408
2409 $linkHeader = $this->getLinkHeader();
2410 if ( $linkHeader ) {
2411 $response->header( $linkHeader );
2412 }
2413
2414 // Prevent framing, if requested
2415 $frameOptions = $this->getFrameOptions();
2416 if ( $frameOptions ) {
2417 $response->header( "X-Frame-Options: $frameOptions" );
2418 }
2419
2420 if ( $this->mArticleBodyOnly ) {
2421 echo $this->mBodytext;
2422 } else {
2423 // Enable safe mode if requested
2424 if ( $this->getRequest()->getBool( 'safemode' ) ) {
2425 $this->disallowUserJs();
2426 }
2427
2428 $sk = $this->getSkin();
2429 foreach ( $sk->getDefaultModules() as $group ) {
2430 $this->addModules( $group );
2431 }
2432
2433 MWDebug::addModules( $this );
2434
2435 // Avoid PHP 7.1 warning of passing $this by reference
2436 $outputPage = $this;
2437 // Hook that allows last minute changes to the output page, e.g.
2438 // adding of CSS or Javascript by extensions.
2439 Hooks::run( 'BeforePageDisplay', [ &$outputPage, &$sk ] );
2440
2441 try {
2442 $sk->outputPage();
2443 } catch ( Exception $e ) {
2444 ob_end_clean(); // bug T129657
2445 throw $e;
2446 }
2447 }
2448
2449 try {
2450 // This hook allows last minute changes to final overall output by modifying output buffer
2451 Hooks::run( 'AfterFinalPageOutput', [ $this ] );
2452 } catch ( Exception $e ) {
2453 ob_end_clean(); // bug T129657
2454 throw $e;
2455 }
2456
2457 $this->sendCacheControl();
2458
2459 if ( $return ) {
2460 return ob_get_clean();
2461 } else {
2462 ob_end_flush();
2463 return null;
2464 }
2465 }
2466
2477 public function prepareErrorPage( $pageTitle, $htmlTitle = false ) {
2478 $this->setPageTitle( $pageTitle );
2479 if ( $htmlTitle !== false ) {
2480 $this->setHTMLTitle( $htmlTitle );
2481 }
2482 $this->setRobotPolicy( 'noindex,nofollow' );
2483 $this->setArticleRelated( false );
2484 $this->enableClientCache( false );
2485 $this->mRedirect = '';
2486 $this->clearSubtitle();
2487 $this->clearHTML();
2488 }
2489
2502 public function showErrorPage( $title, $msg, $params = [] ) {
2503 if ( !$title instanceof Message ) {
2504 $title = $this->msg( $title );
2505 }
2506
2507 $this->prepareErrorPage( $title );
2508
2509 if ( $msg instanceof Message ) {
2510 if ( $params !== [] ) {
2511 trigger_error( 'Argument ignored: $params. The message parameters argument '
2512 . 'is discarded when the $msg argument is a Message object instead of '
2513 . 'a string.', E_USER_NOTICE );
2514 }
2515 $this->addHTML( $msg->parseAsBlock() );
2516 } else {
2517 $this->addWikiMsgArray( $msg, $params );
2518 }
2519
2520 $this->returnToMain();
2521 }
2522
2529 public function showPermissionsErrorPage( array $errors, $action = null ) {
2530 foreach ( $errors as $key => $error ) {
2531 $errors[$key] = (array)$error;
2532 }
2533
2534 // For some action (read, edit, create and upload), display a "login to do this action"
2535 // error if all of the following conditions are met:
2536 // 1. the user is not logged in
2537 // 2. the only error is insufficient permissions (i.e. no block or something else)
2538 // 3. the error can be avoided simply by logging in
2539 if ( in_array( $action, [ 'read', 'edit', 'createpage', 'createtalk', 'upload' ] )
2540 && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
2541 && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
2542 && ( User::groupHasPermission( 'user', $action )
2543 || User::groupHasPermission( 'autoconfirmed', $action ) )
2544 ) {
2545 $displayReturnto = null;
2546
2547 # Due to T34276, if a user does not have read permissions,
2548 # $this->getTitle() will just give Special:Badtitle, which is
2549 # not especially useful as a returnto parameter. Use the title
2550 # from the request instead, if there was one.
2551 $request = $this->getRequest();
2552 $returnto = Title::newFromText( $request->getVal( 'title', '' ) );
2553 if ( $action == 'edit' ) {
2554 $msg = 'whitelistedittext';
2555 $displayReturnto = $returnto;
2556 } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
2557 $msg = 'nocreatetext';
2558 } elseif ( $action == 'upload' ) {
2559 $msg = 'uploadnologintext';
2560 } else { # Read
2561 $msg = 'loginreqpagetext';
2562 $displayReturnto = Title::newMainPage();
2563 }
2564
2565 $query = [];
2566
2567 if ( $returnto ) {
2568 $query['returnto'] = $returnto->getPrefixedText();
2569
2570 if ( !$request->wasPosted() ) {
2571 $returntoquery = $request->getValues();
2572 unset( $returntoquery['title'] );
2573 unset( $returntoquery['returnto'] );
2574 unset( $returntoquery['returntoquery'] );
2575 $query['returntoquery'] = wfArrayToCgi( $returntoquery );
2576 }
2577 }
2578 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
2579 $loginLink = $linkRenderer->makeKnownLink(
2580 SpecialPage::getTitleFor( 'Userlogin' ),
2581 $this->msg( 'loginreqlink' )->text(),
2582 [],
2583 $query
2584 );
2585
2586 $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) );
2587 $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->parse() );
2588
2589 # Don't return to a page the user can't read otherwise
2590 # we'll end up in a pointless loop
2591 if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) {
2592 $this->returnToMain( null, $displayReturnto );
2593 }
2594 } else {
2595 $this->prepareErrorPage( $this->msg( 'permissionserrors' ) );
2596 $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) );
2597 }
2598 }
2599
2606 public function versionRequired( $version ) {
2607 $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) );
2608
2609 $this->addWikiMsg( 'versionrequiredtext', $version );
2610 $this->returnToMain();
2611 }
2612
2620 public function formatPermissionsErrorMessage( array $errors, $action = null ) {
2621 if ( $action == null ) {
2622 $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
2623 } else {
2624 $action_desc = $this->msg( "action-$action" )->plain();
2625 $text = $this->msg(
2626 'permissionserrorstext-withaction',
2627 count( $errors ),
2628 $action_desc
2629 )->plain() . "\n\n";
2630 }
2631
2632 if ( count( $errors ) > 1 ) {
2633 $text .= '<ul class="permissions-errors">' . "\n";
2634
2635 foreach ( $errors as $error ) {
2636 $text .= '<li>';
2637 $text .= call_user_func_array( [ $this, 'msg' ], $error )->plain();
2638 $text .= "</li>\n";
2639 }
2640 $text .= '</ul>';
2641 } else {
2642 $text .= "<div class=\"permissions-errors\">\n" .
2643 call_user_func_array( [ $this, 'msg' ], reset( $errors ) )->plain() .
2644 "\n</div>";
2645 }
2646
2647 return $text;
2648 }
2649
2661 public function readOnlyPage() {
2662 if ( func_num_args() > 0 ) {
2663 throw new MWException( __METHOD__ . ' no longer accepts arguments since 1.25.' );
2664 }
2665
2666 throw new ReadOnlyError;
2667 }
2668
2675 public function rateLimited() {
2676 wfDeprecated( __METHOD__, '1.25' );
2677 throw new ThrottledError;
2678 }
2679
2689 public function showLagWarning( $lag ) {
2690 $config = $this->getConfig();
2691 if ( $lag >= $config->get( 'SlaveLagWarning' ) ) {
2692 $lag = floor( $lag ); // floor to avoid nano seconds to display
2693 $message = $lag < $config->get( 'SlaveLagCritical' )
2694 ? 'lag-warn-normal'
2695 : 'lag-warn-high';
2696 $wrap = Html::rawElement( 'div', [ 'class' => "mw-{$message}" ], "\n$1\n" );
2697 $this->wrapWikiMsg( "$wrap\n", [ $message, $this->getLanguage()->formatNum( $lag ) ] );
2698 }
2699 }
2700
2701 public function showFatalError( $message ) {
2702 $this->prepareErrorPage( $this->msg( 'internalerror' ) );
2703
2704 $this->addHTML( $message );
2705 }
2706
2707 public function showUnexpectedValueError( $name, $val ) {
2708 $this->showFatalError( $this->msg( 'unexpected', $name, $val )->text() );
2709 }
2710
2711 public function showFileCopyError( $old, $new ) {
2712 $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->text() );
2713 }
2714
2715 public function showFileRenameError( $old, $new ) {
2716 $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->text() );
2717 }
2718
2719 public function showFileDeleteError( $name ) {
2720 $this->showFatalError( $this->msg( 'filedeleteerror', $name )->text() );
2721 }
2722
2723 public function showFileNotFoundError( $name ) {
2724 $this->showFatalError( $this->msg( 'filenotfound', $name )->text() );
2725 }
2726
2735 public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) {
2736 $linkRenderer = MediaWikiServices::getInstance()
2737 ->getLinkRendererFactory()->createFromLegacyOptions( $options );
2738 $link = $this->msg( 'returnto' )->rawParams(
2739 $linkRenderer->makeLink( $title, $text, [], $query ) )->escaped();
2740 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
2741 }
2742
2751 public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
2752 if ( $returnto == null ) {
2753 $returnto = $this->getRequest()->getText( 'returnto' );
2754 }
2755
2756 if ( $returntoquery == null ) {
2757 $returntoquery = $this->getRequest()->getText( 'returntoquery' );
2758 }
2759
2760 if ( $returnto === '' ) {
2761 $returnto = Title::newMainPage();
2762 }
2763
2764 if ( is_object( $returnto ) ) {
2765 $titleObj = $returnto;
2766 } else {
2767 $titleObj = Title::newFromText( $returnto );
2768 }
2769 // We don't want people to return to external interwiki. That
2770 // might potentially be used as part of a phishing scheme
2771 if ( !is_object( $titleObj ) || $titleObj->isExternal() ) {
2772 $titleObj = Title::newMainPage();
2773 }
2774
2775 $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) );
2776 }
2777
2778 private function getRlClientContext() {
2779 if ( !$this->rlClientContext ) {
2781 [], // modules; not relevant
2782 $this->getLanguage()->getCode(),
2783 $this->getSkin()->getSkinName(),
2784 $this->getUser()->isLoggedIn() ? $this->getUser()->getName() : null,
2785 null, // version; not relevant
2787 null, // only; not relevant
2788 $this->isPrintable(),
2789 $this->getRequest()->getBool( 'handheld' )
2790 );
2791 $this->rlClientContext = new ResourceLoaderContext(
2792 $this->getResourceLoader(),
2793 new FauxRequest( $query )
2794 );
2795 }
2797 }
2798
2810 public function getRlClient() {
2811 if ( !$this->rlClient ) {
2812 $context = $this->getRlClientContext();
2813 $rl = $this->getResourceLoader();
2814 $this->addModules( [
2815 'user.options',
2816 'user.tokens',
2817 ] );
2818 $this->addModuleStyles( [
2819 'site.styles',
2820 'noscript',
2821 'user.styles',
2822 ] );
2823 $this->getSkin()->setupSkinUserCss( $this );
2824
2825 // Prepare exempt modules for buildExemptModules()
2826 $exemptGroups = [ 'site' => [], 'noscript' => [], 'private' => [], 'user' => [] ];
2827 $exemptStates = [];
2828 $moduleStyles = $this->getModuleStyles( /*filter*/ true );
2829
2830 // Preload getTitleInfo for isKnownEmpty calls below and in ResourceLoaderClientHtml
2831 // Separate user-specific batch for improved cache-hit ratio.
2832 $userBatch = [ 'user.styles', 'user' ];
2833 $siteBatch = array_diff( $moduleStyles, $userBatch );
2834 $dbr = wfGetDB( DB_REPLICA );
2837
2838 // Filter out modules handled by buildExemptModules()
2839 $moduleStyles = array_filter( $moduleStyles,
2840 function ( $name ) use ( $rl, $context, &$exemptGroups, &$exemptStates ) {
2841 $module = $rl->getModule( $name );
2842 if ( $module ) {
2843 if ( $name === 'user.styles' && $this->isUserCssPreview() ) {
2844 $exemptStates[$name] = 'ready';
2845 // Special case in buildExemptModules()
2846 return false;
2847 }
2848 $group = $module->getGroup();
2849 if ( isset( $exemptGroups[$group] ) ) {
2850 $exemptStates[$name] = 'ready';
2851 if ( !$module->isKnownEmpty( $context ) ) {
2852 // E.g. Don't output empty <styles>
2853 $exemptGroups[$group][] = $name;
2854 }
2855 return false;
2856 }
2857 }
2858 return true;
2859 }
2860 );
2861 $this->rlExemptStyleModules = $exemptGroups;
2862
2863 $isUserModuleFiltered = !$this->filterModules( [ 'user' ] );
2864 // If this page filters out 'user', makeResourceLoaderLink will drop it.
2865 // Avoid indefinite "loading" state or untrue "ready" state (T145368).
2866 if ( !$isUserModuleFiltered ) {
2867 // Manually handled by getBottomScripts()
2868 $userModule = $rl->getModule( 'user' );
2869 $userState = $userModule->isKnownEmpty( $context ) && !$this->isUserJsPreview()
2870 ? 'ready'
2871 : 'loading';
2872 $this->rlUserModuleState = $exemptStates['user'] = $userState;
2873 }
2874
2876 $rlClient->setConfig( $this->getJSVars() );
2877 $rlClient->setModules( $this->getModules( /*filter*/ true ) );
2878 $rlClient->setModuleStyles( $moduleStyles );
2879 $rlClient->setModuleScripts( $this->getModuleScripts( /*filter*/ true ) );
2880 $rlClient->setExemptStates( $exemptStates );
2881 $this->rlClient = $rlClient;
2882 }
2883 return $this->rlClient;
2884 }
2885
2891 public function headElement( Skin $sk, $includeStyle = true ) {
2893
2894 $userdir = $this->getLanguage()->getDir();
2895 $sitedir = $wgContLang->getDir();
2896
2897 $pieces = [];
2898 $pieces[] = Html::htmlHeader( Sanitizer::mergeAttributes(
2899 $this->getRlClient()->getDocumentAttributes(),
2901 ) );
2902 $pieces[] = Html::openElement( 'head' );
2903
2904 if ( $this->getHTMLTitle() == '' ) {
2905 $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() );
2906 }
2907
2908 if ( !Html::isXmlMimeType( $this->getConfig()->get( 'MimeType' ) ) ) {
2909 // Add <meta charset="UTF-8">
2910 // This should be before <title> since it defines the charset used by
2911 // text including the text inside <title>.
2912 // The spec recommends defining XHTML5's charset using the XML declaration
2913 // instead of meta.
2914 // Our XML declaration is output by Html::htmlHeader.
2915 // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type
2916 // https://html.spec.whatwg.org/multipage/semantics.html#charset
2917 $pieces[] = Html::element( 'meta', [ 'charset' => 'UTF-8' ] );
2918 }
2919
2920 $pieces[] = Html::element( 'title', null, $this->getHTMLTitle() );
2921 $pieces[] = $this->getRlClient()->getHeadHtml();
2922 $pieces[] = $this->buildExemptModules();
2923 $pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) );
2924 $pieces = array_merge( $pieces, array_values( $this->mHeadItems ) );
2925
2926 $min = ResourceLoader::inDebugMode() ? '' : '.min';
2927 // Use an IE conditional comment to serve the script only to old IE
2928 $pieces[] = '<!--[if lt IE 9]>' .
2929 Html::element( 'script', [
2930 'src' => self::transformResourcePath(
2931 $this->getConfig(),
2932 "/resources/lib/html5shiv/html5shiv{$min}.js"
2933 ),
2934 ] ) .
2935 '<![endif]-->';
2936
2937 $pieces[] = Html::closeElement( 'head' );
2938
2939 $bodyClasses = $this->mAdditionalBodyClasses;
2940 $bodyClasses[] = 'mediawiki';
2941
2942 # Classes for LTR/RTL directionality support
2943 $bodyClasses[] = $userdir;
2944 $bodyClasses[] = "sitedir-$sitedir";
2945
2946 $underline = $this->getUser()->getOption( 'underline' );
2947 if ( $underline < 2 ) {
2948 // The following classes can be used here:
2949 // * mw-underline-always
2950 // * mw-underline-never
2951 $bodyClasses[] = 'mw-underline-' . ( $underline ? 'always' : 'never' );
2952 }
2953
2954 if ( $this->getLanguage()->capitalizeAllNouns() ) {
2955 # A <body> class is probably not the best way to do this . . .
2956 $bodyClasses[] = 'capitalize-all-nouns';
2957 }
2958
2959 // Parser feature migration class
2960 // The idea is that this will eventually be removed, after the wikitext
2961 // which requires it is cleaned up.
2962 $bodyClasses[] = 'mw-hide-empty-elt';
2963
2964 $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
2965 $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
2966 $bodyClasses[] =
2967 'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
2968
2969 $bodyAttrs = [];
2970 // While the implode() is not strictly needed, it's used for backwards compatibility
2971 // (this used to be built as a string and hooks likely still expect that).
2972 $bodyAttrs['class'] = implode( ' ', $bodyClasses );
2973
2974 // Allow skins and extensions to add body attributes they need
2975 $sk->addToBodyAttributes( $this, $bodyAttrs );
2976 Hooks::run( 'OutputPageBodyAttributes', [ $this, $sk, &$bodyAttrs ] );
2977
2978 $pieces[] = Html::openElement( 'body', $bodyAttrs );
2979
2980 return self::combineWrappedStrings( $pieces );
2981 }
2982
2988 public function getResourceLoader() {
2989 if ( is_null( $this->mResourceLoader ) ) {
2990 $this->mResourceLoader = new ResourceLoader(
2991 $this->getConfig(),
2992 LoggerFactory::getInstance( 'resourceloader' )
2993 );
2994 }
2996 }
2997
3006 public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) {
3007 // Apply 'target' and 'origin' filters
3008 $modules = $this->filterModules( (array)$modules, null, $only );
3009
3011 $this->getRlClientContext(),
3012 $modules,
3013 $only,
3014 $extraQuery
3015 );
3016 }
3017
3024 protected static function combineWrappedStrings( array $chunks ) {
3025 // Filter out empty values
3026 $chunks = array_filter( $chunks, 'strlen' );
3027 return WrappedString::join( "\n", $chunks );
3028 }
3029
3030 private function isUserJsPreview() {
3031 return $this->getConfig()->get( 'AllowUserJs' )
3032 && $this->getTitle()
3033 && $this->getTitle()->isJsSubpage()
3034 && $this->userCanPreview();
3035 }
3036
3037 protected function isUserCssPreview() {
3038 return $this->getConfig()->get( 'AllowUserCss' )
3039 && $this->getTitle()
3040 && $this->getTitle()->isCssSubpage()
3041 && $this->userCanPreview();
3042 }
3043
3050 public function getBottomScripts() {
3051 $chunks = [];
3052 $chunks[] = $this->getRlClient()->getBodyHtml();
3053
3054 // Legacy non-ResourceLoader scripts
3055 $chunks[] = $this->mScripts;
3056
3057 // Exempt 'user' module
3058 // - May need excludepages for live preview. (T28283)
3059 // - Must use TYPE_COMBINED so its response is handled by mw.loader.implement() which
3060 // ensures execution is scheduled after the "site" module.
3061 // - Don't load if module state is already resolved as "ready".
3062 if ( $this->rlUserModuleState === 'loading' ) {
3063 if ( $this->isUserJsPreview() ) {
3065 [ 'excludepage' => $this->getTitle()->getPrefixedDBkey() ]
3066 );
3068 Xml::encodeJsCall( 'mw.loader.using', [
3069 [ 'user', 'site' ],
3070 new XmlJsCode(
3071 'function () {'
3072 . Xml::encodeJsCall( '$.globalEval', [
3073 $this->getRequest()->getText( 'wpTextbox1' )
3074 ] )
3075 . '}'
3076 )
3077 ] )
3078 );
3079 // FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded
3080 // asynchronously and may arrive *after* the inline script here. So the previewed code
3081 // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js.
3082 // Similarly, when previewing ./common.js and the user module does arrive first,
3083 // it will arrive without common.js and the inline script runs after.
3084 // Thus running common after the excluded subpage.
3085 } else {
3086 // Load normally
3087 $chunks[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_COMBINED );
3088 }
3089 }
3090
3091 if ( $this->limitReportJSData ) {
3094 [ 'wgPageParseReport' => $this->limitReportJSData ]
3095 )
3096 );
3097 }
3098
3099 return self::combineWrappedStrings( $chunks );
3100 }
3101
3108 public function getJsConfigVars() {
3109 return $this->mJsConfigVars;
3110 }
3111
3118 public function addJsConfigVars( $keys, $value = null ) {
3119 if ( is_array( $keys ) ) {
3120 foreach ( $keys as $key => $value ) {
3121 $this->mJsConfigVars[$key] = $value;
3122 }
3123 return;
3124 }
3125
3126 $this->mJsConfigVars[$keys] = $value;
3127 }
3128
3138 public function getJSVars() {
3140
3141 $curRevisionId = 0;
3142 $articleId = 0;
3143 $canonicalSpecialPageName = false; # T23115
3144
3145 $title = $this->getTitle();
3146 $ns = $title->getNamespace();
3147 $canonicalNamespace = MWNamespace::exists( $ns )
3148 ? MWNamespace::getCanonicalName( $ns )
3149 : $title->getNsText();
3150
3151 $sk = $this->getSkin();
3152 // Get the relevant title so that AJAX features can use the correct page name
3153 // when making API requests from certain special pages (T36972).
3154 $relevantTitle = $sk->getRelevantTitle();
3155 $relevantUser = $sk->getRelevantUser();
3156
3157 if ( $ns == NS_SPECIAL ) {
3158 list( $canonicalSpecialPageName, /*...*/ ) =
3160 } elseif ( $this->canUseWikiPage() ) {
3161 $wikiPage = $this->getWikiPage();
3162 $curRevisionId = $wikiPage->getLatest();
3163 $articleId = $wikiPage->getId();
3164 }
3165
3166 $lang = $title->getPageViewLanguage();
3167
3168 // Pre-process information
3169 $separatorTransTable = $lang->separatorTransformTable();
3170 $separatorTransTable = $separatorTransTable ? $separatorTransTable : [];
3171 $compactSeparatorTransTable = [
3172 implode( "\t", array_keys( $separatorTransTable ) ),
3173 implode( "\t", $separatorTransTable ),
3174 ];
3175 $digitTransTable = $lang->digitTransformTable();
3176 $digitTransTable = $digitTransTable ? $digitTransTable : [];
3177 $compactDigitTransTable = [
3178 implode( "\t", array_keys( $digitTransTable ) ),
3179 implode( "\t", $digitTransTable ),
3180 ];
3181
3182 $user = $this->getUser();
3183
3184 $vars = [
3185 'wgCanonicalNamespace' => $canonicalNamespace,
3186 'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
3187 'wgNamespaceNumber' => $title->getNamespace(),
3188 'wgPageName' => $title->getPrefixedDBkey(),
3189 'wgTitle' => $title->getText(),
3190 'wgCurRevisionId' => $curRevisionId,
3191 'wgRevisionId' => (int)$this->getRevisionId(),
3192 'wgArticleId' => $articleId,
3193 'wgIsArticle' => $this->isArticle(),
3194 'wgIsRedirect' => $title->isRedirect(),
3195 'wgAction' => Action::getActionName( $this->getContext() ),
3196 'wgUserName' => $user->isAnon() ? null : $user->getName(),
3197 'wgUserGroups' => $user->getEffectiveGroups(),
3198 'wgCategories' => $this->getCategories(),
3199 'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
3200 'wgPageContentLanguage' => $lang->getCode(),
3201 'wgPageContentModel' => $title->getContentModel(),
3202 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
3203 'wgDigitTransformTable' => $compactDigitTransTable,
3204 'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
3205 'wgMonthNames' => $lang->getMonthNamesArray(),
3206 'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
3207 'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
3208 'wgRelevantArticleId' => $relevantTitle->getArticleID(),
3209 'wgRequestId' => WebRequest::getRequestId(),
3210 ];
3211
3212 if ( $user->isLoggedIn() ) {
3213 $vars['wgUserId'] = $user->getId();
3214 $vars['wgUserEditCount'] = $user->getEditCount();
3215 $userReg = $user->getRegistration();
3216 $vars['wgUserRegistration'] = $userReg ? wfTimestamp( TS_UNIX, $userReg ) * 1000 : null;
3217 // Get the revision ID of the oldest new message on the user's talk
3218 // page. This can be used for constructing new message alerts on
3219 // the client side.
3220 $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId();
3221 }
3222
3223 if ( $wgContLang->hasVariants() ) {
3224 $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
3225 }
3226 // Same test as SkinTemplate
3227 $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user )
3228 && ( $title->exists() || $title->quickUserCan( 'create', $user ) );
3229
3230 $vars['wgRelevantPageIsProbablyEditable'] = $relevantTitle
3231 && $relevantTitle->quickUserCan( 'edit', $user )
3232 && ( $relevantTitle->exists() || $relevantTitle->quickUserCan( 'create', $user ) );
3233
3234 foreach ( $title->getRestrictionTypes() as $type ) {
3235 $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
3236 }
3237
3238 if ( $title->isMainPage() ) {
3239 $vars['wgIsMainPage'] = true;
3240 }
3241
3242 if ( $this->mRedirectedFrom ) {
3243 $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey();
3244 }
3245
3246 if ( $relevantUser ) {
3247 $vars['wgRelevantUserName'] = $relevantUser->getName();
3248 }
3249
3250 // Allow extensions to add their custom variables to the mw.config map.
3251 // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
3252 // page-dependant but site-wide (without state).
3253 // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
3254 Hooks::run( 'MakeGlobalVariablesScript', [ &$vars, $this ] );
3255
3256 // Merge in variables from addJsConfigVars last
3257 return array_merge( $vars, $this->getJsConfigVars() );
3258 }
3259
3269 public function userCanPreview() {
3270 $request = $this->getRequest();
3271 if (
3272 $request->getVal( 'action' ) !== 'submit' ||
3273 !$request->getCheck( 'wpPreview' ) ||
3274 !$request->wasPosted()
3275 ) {
3276 return false;
3277 }
3278
3279 $user = $this->getUser();
3280
3281 if ( !$user->isLoggedIn() ) {
3282 // Anons have predictable edit tokens
3283 return false;
3284 }
3285 if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
3286 return false;
3287 }
3288
3289 $title = $this->getTitle();
3290 if ( !$title->isJsSubpage() && !$title->isCssSubpage() ) {
3291 return false;
3292 }
3293 if ( !$title->isSubpageOf( $user->getUserPage() ) ) {
3294 // Don't execute another user's CSS or JS on preview (T85855)
3295 return false;
3296 }
3297
3298 $errors = $title->getUserPermissionsErrors( 'edit', $user );
3299 if ( count( $errors ) !== 0 ) {
3300 return false;
3301 }
3302
3303 return true;
3304 }
3305
3309 public function getHeadLinksArray() {
3311
3312 $tags = [];
3313 $config = $this->getConfig();
3314
3315 $canonicalUrl = $this->mCanonicalUrl;
3316
3317 $tags['meta-generator'] = Html::element( 'meta', [
3318 'name' => 'generator',
3319 'content' => "MediaWiki $wgVersion",
3320 ] );
3321
3322 if ( $config->get( 'ReferrerPolicy' ) !== false ) {
3323 $tags['meta-referrer'] = Html::element( 'meta', [
3324 'name' => 'referrer',
3325 'content' => $config->get( 'ReferrerPolicy' )
3326 ] );
3327 }
3328
3329 $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
3330 if ( $p !== 'index,follow' ) {
3331 // http://www.robotstxt.org/wc/meta-user.html
3332 // Only show if it's different from the default robots policy
3333 $tags['meta-robots'] = Html::element( 'meta', [
3334 'name' => 'robots',
3335 'content' => $p,
3336 ] );
3337 }
3338
3339 foreach ( $this->mMetatags as $tag ) {
3340 if ( strncasecmp( $tag[0], 'http:', 5 ) === 0 ) {
3341 $a = 'http-equiv';
3342 $tag[0] = substr( $tag[0], 5 );
3343 } elseif ( strncasecmp( $tag[0], 'og:', 3 ) === 0 ) {
3344 $a = 'property';
3345 } else {
3346 $a = 'name';
3347 }
3348 $tagName = "meta-{$tag[0]}";
3349 if ( isset( $tags[$tagName] ) ) {
3350 $tagName .= $tag[1];
3351 }
3352 $tags[$tagName] = Html::element( 'meta',
3353 [
3354 $a => $tag[0],
3355 'content' => $tag[1]
3356 ]
3357 );
3358 }
3359
3360 foreach ( $this->mLinktags as $tag ) {
3361 $tags[] = Html::element( 'link', $tag );
3362 }
3363
3364 # Universal edit button
3365 if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) {
3366 $user = $this->getUser();
3367 if ( $this->getTitle()->quickUserCan( 'edit', $user )
3368 && ( $this->getTitle()->exists() ||
3369 $this->getTitle()->quickUserCan( 'create', $user ) )
3370 ) {
3371 // Original UniversalEditButton
3372 $msg = $this->msg( 'edit' )->text();
3373 $tags['universal-edit-button'] = Html::element( 'link', [
3374 'rel' => 'alternate',
3375 'type' => 'application/x-wiki',
3376 'title' => $msg,
3377 'href' => $this->getTitle()->getEditURL(),
3378 ] );
3379 // Alternate edit link
3380 $tags['alternative-edit'] = Html::element( 'link', [
3381 'rel' => 'edit',
3382 'title' => $msg,
3383 'href' => $this->getTitle()->getEditURL(),
3384 ] );
3385 }
3386 }
3387
3388 # Generally the order of the favicon and apple-touch-icon links
3389 # should not matter, but Konqueror (3.5.9 at least) incorrectly
3390 # uses whichever one appears later in the HTML source. Make sure
3391 # apple-touch-icon is specified first to avoid this.
3392 if ( $config->get( 'AppleTouchIcon' ) !== false ) {
3393 $tags['apple-touch-icon'] = Html::element( 'link', [
3394 'rel' => 'apple-touch-icon',
3395 'href' => $config->get( 'AppleTouchIcon' )
3396 ] );
3397 }
3398
3399 if ( $config->get( 'Favicon' ) !== false ) {
3400 $tags['favicon'] = Html::element( 'link', [
3401 'rel' => 'shortcut icon',
3402 'href' => $config->get( 'Favicon' )
3403 ] );
3404 }
3405
3406 # OpenSearch description link
3407 $tags['opensearch'] = Html::element( 'link', [
3408 'rel' => 'search',
3409 'type' => 'application/opensearchdescription+xml',
3410 'href' => wfScript( 'opensearch_desc' ),
3411 'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
3412 ] );
3413
3414 if ( $config->get( 'EnableAPI' ) ) {
3415 # Real Simple Discovery link, provides auto-discovery information
3416 # for the MediaWiki API (and potentially additional custom API
3417 # support such as WordPress or Twitter-compatible APIs for a
3418 # blogging extension, etc)
3419 $tags['rsd'] = Html::element( 'link', [
3420 'rel' => 'EditURI',
3421 'type' => 'application/rsd+xml',
3422 // Output a protocol-relative URL here if $wgServer is protocol-relative.
3423 // Whether RSD accepts relative or protocol-relative URLs is completely
3424 // undocumented, though.
3425 'href' => wfExpandUrl( wfAppendQuery(
3426 wfScript( 'api' ),
3427 [ 'action' => 'rsd' ] ),
3429 ),
3430 ] );
3431 }
3432
3433 # Language variants
3434 if ( !$config->get( 'DisableLangConversion' ) ) {
3435 $lang = $this->getTitle()->getPageLanguage();
3436 if ( $lang->hasVariants() ) {
3437 $variants = $lang->getVariants();
3438 foreach ( $variants as $variant ) {
3439 $tags["variant-$variant"] = Html::element( 'link', [
3440 'rel' => 'alternate',
3441 'hreflang' => wfBCP47( $variant ),
3442 'href' => $this->getTitle()->getLocalURL(
3443 [ 'variant' => $variant ] )
3444 ]
3445 );
3446 }
3447 # x-default link per https://support.google.com/webmasters/answer/189077?hl=en
3448 $tags["variant-x-default"] = Html::element( 'link', [
3449 'rel' => 'alternate',
3450 'hreflang' => 'x-default',
3451 'href' => $this->getTitle()->getLocalURL() ] );
3452 }
3453 }
3454
3455 # Copyright
3456 if ( $this->copyrightUrl !== null ) {
3457 $copyright = $this->copyrightUrl;
3458 } else {
3459 $copyright = '';
3460 if ( $config->get( 'RightsPage' ) ) {
3461 $copy = Title::newFromText( $config->get( 'RightsPage' ) );
3462
3463 if ( $copy ) {
3464 $copyright = $copy->getLocalURL();
3465 }
3466 }
3467
3468 if ( !$copyright && $config->get( 'RightsUrl' ) ) {
3469 $copyright = $config->get( 'RightsUrl' );
3470 }
3471 }
3472
3473 if ( $copyright ) {
3474 $tags['copyright'] = Html::element( 'link', [
3475 'rel' => 'license',
3476 'href' => $copyright ]
3477 );
3478 }
3479
3480 # Feeds
3481 if ( $config->get( 'Feed' ) ) {
3482 $feedLinks = [];
3483
3484 foreach ( $this->getSyndicationLinks() as $format => $link ) {
3485 # Use the page name for the title. In principle, this could
3486 # lead to issues with having the same name for different feeds
3487 # corresponding to the same page, but we can't avoid that at
3488 # this low a level.
3489
3490 $feedLinks[] = $this->feedLink(
3491 $format,
3492 $link,
3493 # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
3494 $this->msg(
3495 "page-{$format}-feed", $this->getTitle()->getPrefixedText()
3496 )->text()
3497 );
3498 }
3499
3500 # Recent changes feed should appear on every page (except recentchanges,
3501 # that would be redundant). Put it after the per-page feed to avoid
3502 # changing existing behavior. It's still available, probably via a
3503 # menu in your browser. Some sites might have a different feed they'd
3504 # like to promote instead of the RC feed (maybe like a "Recent New Articles"
3505 # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
3506 # If so, use it instead.
3507 $sitename = $config->get( 'Sitename' );
3508 if ( $config->get( 'OverrideSiteFeed' ) ) {
3509 foreach ( $config->get( 'OverrideSiteFeed' ) as $type => $feedUrl ) {
3510 // Note, this->feedLink escapes the url.
3511 $feedLinks[] = $this->feedLink(
3512 $type,
3513 $feedUrl,
3514 $this->msg( "site-{$type}-feed", $sitename )->text()
3515 );
3516 }
3517 } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
3518 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
3519 foreach ( $config->get( 'AdvertisedFeedTypes' ) as $format ) {
3520 $feedLinks[] = $this->feedLink(
3521 $format,
3522 $rctitle->getLocalURL( [ 'feed' => $format ] ),
3523 # For grep: 'site-rss-feed', 'site-atom-feed'
3524 $this->msg( "site-{$format}-feed", $sitename )->text()
3525 );
3526 }
3527 }
3528
3529 # Allow extensions to change the list pf feeds. This hook is primarily for changing,
3530 # manipulating or removing existing feed tags. If you want to add new feeds, you should
3531 # use OutputPage::addFeedLink() instead.
3532 Hooks::run( 'AfterBuildFeedLinks', [ &$feedLinks ] );
3533
3534 $tags += $feedLinks;
3535 }
3536
3537 # Canonical URL
3538 if ( $config->get( 'EnableCanonicalServerLink' ) ) {
3539 if ( $canonicalUrl !== false ) {
3540 $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL );
3541 } else {
3542 if ( $this->isArticleRelated() ) {
3543 // This affects all requests where "setArticleRelated" is true. This is
3544 // typically all requests that show content (query title, curid, oldid, diff),
3545 // and all wikipage actions (edit, delete, purge, info, history etc.).
3546 // It does not apply to File pages and Special pages.
3547 // 'history' and 'info' actions address page metadata rather than the page
3548 // content itself, so they may not be canonicalized to the view page url.
3549 // TODO: this ought to be better encapsulated in the Action class.
3550 $action = Action::getActionName( $this->getContext() );
3551 if ( in_array( $action, [ 'history', 'info' ] ) ) {
3552 $query = "action={$action}";
3553 } else {
3554 $query = '';
3555 }
3556 $canonicalUrl = $this->getTitle()->getCanonicalURL( $query );
3557 } else {
3558 $reqUrl = $this->getRequest()->getRequestURL();
3559 $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL );
3560 }
3561 }
3562 }
3563 if ( $canonicalUrl !== false ) {
3564 $tags[] = Html::element( 'link', [
3565 'rel' => 'canonical',
3566 'href' => $canonicalUrl
3567 ] );
3568 }
3569
3570 return $tags;
3571 }
3572
3581 private function feedLink( $type, $url, $text ) {
3582 return Html::element( 'link', [
3583 'rel' => 'alternate',
3584 'type' => "application/$type+xml",
3585 'title' => $text,
3586 'href' => $url ]
3587 );
3588 }
3589
3599 public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
3600 $options = [];
3601 if ( $media ) {
3602 $options['media'] = $media;
3603 }
3604 if ( $condition ) {
3605 $options['condition'] = $condition;
3606 }
3607 if ( $dir ) {
3608 $options['dir'] = $dir;
3609 }
3610 $this->styles[$style] = $options;
3611 }
3612
3620 public function addInlineStyle( $style_css, $flip = 'noflip' ) {
3621 if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
3622 # If wanted, and the interface is right-to-left, flip the CSS
3623 $style_css = CSSJanus::transform( $style_css, true, false );
3624 }
3625 $this->mInlineStyles .= Html::inlineStyle( $style_css );
3626 }
3627
3633 protected function buildExemptModules() {
3635
3636 $chunks = [];
3637 // Things that go after the ResourceLoaderDynamicStyles marker
3638 $append = [];
3639
3640 // Exempt 'user' styles module (may need 'excludepages' for live preview)
3641 if ( $this->isUserCssPreview() ) {
3642 $append[] = $this->makeResourceLoaderLink(
3643 'user.styles',
3645 [ 'excludepage' => $this->getTitle()->getPrefixedDBkey() ]
3646 );
3647
3648 // Load the previewed CSS. Janus it if needed.
3649 // User-supplied CSS is assumed to in the wiki's content language.
3650 $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' );
3651 if ( $this->getLanguage()->getDir() !== $wgContLang->getDir() ) {
3652 $previewedCSS = CSSJanus::transform( $previewedCSS, true, false );
3653 }
3654 $append[] = Html::inlineStyle( $previewedCSS );
3655 }
3656
3657 // We want site, private and user styles to override dynamically added styles from
3658 // general modules, but we want dynamically added styles to override statically added
3659 // style modules. So the order has to be:
3660 // - page style modules (formatted by ResourceLoaderClientHtml::getHeadHtml())
3661 // - dynamically loaded styles (added by mw.loader before ResourceLoaderDynamicStyles)
3662 // - ResourceLoaderDynamicStyles marker
3663 // - site/private/user styles
3664
3665 // Add legacy styles added through addStyle()/addInlineStyle() here
3666 $chunks[] = implode( '', $this->buildCssLinksArray() ) . $this->mInlineStyles;
3667
3668 $chunks[] = Html::element(
3669 'meta',
3670 [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ]
3671 );
3672
3673 $separateReq = [ 'site.styles', 'user.styles' ];
3674 foreach ( $this->rlExemptStyleModules as $group => $moduleNames ) {
3675 // Combinable modules
3676 $chunks[] = $this->makeResourceLoaderLink(
3677 array_diff( $moduleNames, $separateReq ),
3679 );
3680
3681 foreach ( array_intersect( $moduleNames, $separateReq ) as $name ) {
3682 // These require their own dedicated request in order to support "@import"
3683 // syntax, which is incompatible with concatenation. (T147667, T37562)
3684 $chunks[] = $this->makeResourceLoaderLink( $name,
3686 );
3687 }
3688 }
3689
3690 return self::combineWrappedStrings( array_merge( $chunks, $append ) );
3691 }
3692
3696 public function buildCssLinksArray() {
3697 $links = [];
3698
3699 // Add any extension CSS
3700 foreach ( $this->mExtStyles as $url ) {
3701 $this->addStyle( $url );
3702 }
3703 $this->mExtStyles = [];
3704
3705 foreach ( $this->styles as $file => $options ) {
3706 $link = $this->styleLink( $file, $options );
3707 if ( $link ) {
3708 $links[$file] = $link;
3709 }
3710 }
3711 return $links;
3712 }
3713
3721 protected function styleLink( $style, array $options ) {
3722 if ( isset( $options['dir'] ) ) {
3723 if ( $this->getLanguage()->getDir() != $options['dir'] ) {
3724 return '';
3725 }
3726 }
3727
3728 if ( isset( $options['media'] ) ) {
3729 $media = self::transformCssMedia( $options['media'] );
3730 if ( is_null( $media ) ) {
3731 return '';
3732 }
3733 } else {
3734 $media = 'all';
3735 }
3736
3737 if ( substr( $style, 0, 1 ) == '/' ||
3738 substr( $style, 0, 5 ) == 'http:' ||
3739 substr( $style, 0, 6 ) == 'https:' ) {
3740 $url = $style;
3741 } else {
3742 $config = $this->getConfig();
3743 $url = $config->get( 'StylePath' ) . '/' . $style . '?' .
3744 $config->get( 'StyleVersion' );
3745 }
3746
3747 $link = Html::linkedStyle( $url, $media );
3748
3749 if ( isset( $options['condition'] ) ) {
3750 $condition = htmlspecialchars( $options['condition'] );
3751 $link = "<!--[if $condition]>$link<![endif]-->";
3752 }
3753 return $link;
3754 }
3755
3777 public static function transformResourcePath( Config $config, $path ) {
3778 global $IP;
3779
3780 $localDir = $IP;
3781 $remotePathPrefix = $config->get( 'ResourceBasePath' );
3782 if ( $remotePathPrefix === '' ) {
3783 // The configured base path is required to be empty string for
3784 // wikis in the domain root
3785 $remotePath = '/';
3786 } else {
3787 $remotePath = $remotePathPrefix;
3788 }
3789 if ( strpos( $path, $remotePath ) !== 0 || substr( $path, 0, 2 ) === '//' ) {
3790 // - Path is outside wgResourceBasePath, ignore.
3791 // - Path is protocol-relative. Fixes T155310. Not supported by RelPath lib.
3792 return $path;
3793 }
3794 // For files in resources, extensions/ or skins/, ResourceBasePath is preferred here.
3795 // For other misc files in $IP, we'll fallback to that as well. There is, however, a fourth
3796 // supported dir/path pair in the configuration (wgUploadDirectory, wgUploadPath)
3797 // which is not expected to be in wgResourceBasePath on CDNs. (T155146)
3798 $uploadPath = $config->get( 'UploadPath' );
3799 if ( strpos( $path, $uploadPath ) === 0 ) {
3800 $localDir = $config->get( 'UploadDirectory' );
3801 $remotePathPrefix = $remotePath = $uploadPath;
3802 }
3803
3804 $path = RelPath\getRelativePath( $path, $remotePath );
3805 return self::transformFilePath( $remotePathPrefix, $localDir, $path );
3806 }
3807
3819 public static function transformFilePath( $remotePathPrefix, $localPath, $file ) {
3820 $hash = md5_file( "$localPath/$file" );
3821 if ( $hash === false ) {
3822 wfLogWarning( __METHOD__ . ": Failed to hash $localPath/$file" );
3823 $hash = '';
3824 }
3825 return "$remotePathPrefix/$file?" . substr( $hash, 0, 5 );
3826 }
3827
3835 public static function transformCssMedia( $media ) {
3837
3838 // https://www.w3.org/TR/css3-mediaqueries/#syntax
3839 $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
3840
3841 // Switch in on-screen display for media testing
3842 $switches = [
3843 'printable' => 'print',
3844 'handheld' => 'handheld',
3845 ];
3846 foreach ( $switches as $switch => $targetMedia ) {
3847 if ( $wgRequest->getBool( $switch ) ) {
3848 if ( $media == $targetMedia ) {
3849 $media = '';
3850 } elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
3851 /* This regex will not attempt to understand a comma-separated media_query_list
3852 *
3853 * Example supported values for $media:
3854 * 'screen', 'only screen', 'screen and (min-width: 982px)' ),
3855 * Example NOT supported value for $media:
3856 * '3d-glasses, screen, print and resolution > 90dpi'
3857 *
3858 * If it's a print request, we never want any kind of screen stylesheets
3859 * If it's a handheld request (currently the only other choice with a switch),
3860 * we don't want simple 'screen' but we might want screen queries that
3861 * have a max-width or something, so we'll pass all others on and let the
3862 * client do the query.
3863 */
3864 if ( $targetMedia == 'print' || $media == 'screen' ) {
3865 return null;
3866 }
3867 }
3868 }
3869 }
3870
3871 return $media;
3872 }
3873
3880 public function addWikiMsg( /*...*/ ) {
3881 $args = func_get_args();
3882 $name = array_shift( $args );
3883 $this->addWikiMsgArray( $name, $args );
3884 }
3885
3894 public function addWikiMsgArray( $name, $args ) {
3895 $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
3896 }
3897
3923 public function wrapWikiMsg( $wrap /*, ...*/ ) {
3924 $msgSpecs = func_get_args();
3925 array_shift( $msgSpecs );
3926 $msgSpecs = array_values( $msgSpecs );
3927 $s = $wrap;
3928 foreach ( $msgSpecs as $n => $spec ) {
3929 if ( is_array( $spec ) ) {
3930 $args = $spec;
3931 $name = array_shift( $args );
3932 if ( isset( $args['options'] ) ) {
3933 unset( $args['options'] );
3935 'Adding "options" to ' . __METHOD__ . ' is no longer supported',
3936 '1.20'
3937 );
3938 }
3939 } else {
3940 $args = [];
3941 $name = $spec;
3942 }
3943 $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
3944 }
3945 $this->addWikiText( $s );
3946 }
3947
3953 public function isTOCEnabled() {
3954 return $this->mEnableTOC;
3955 }
3956
3962 public function enableSectionEditLinks( $flag = true ) {
3963 $this->mEnableSectionEditLinks = $flag;
3964 }
3965
3970 public function sectionEditLinksEnabled() {
3972 }
3973
3981 public static function setupOOUI( $skinName = 'default', $dir = 'ltr' ) {
3982 $themes = ResourceLoaderOOUIModule::getSkinThemeMap();
3983 $theme = isset( $themes[$skinName] ) ? $themes[$skinName] : $themes['default'];
3984 // For example, 'OOUI\WikimediaUITheme'.
3985 $themeClass = "OOUI\\{$theme}Theme";
3986 OOUI\Theme::setSingleton( new $themeClass() );
3987 OOUI\Element::setDefaultDir( $dir );
3988 }
3989
3996 public function enableOOUI() {
3998 strtolower( $this->getSkin()->getSkinName() ),
3999 $this->getLanguage()->getDir()
4000 );
4001 $this->addModuleStyles( [
4002 'oojs-ui-core.styles',
4003 'oojs-ui.styles.indicators',
4004 'oojs-ui.styles.textures',
4005 'mediawiki.widgets.styles',
4006 'oojs-ui.styles.icons-content',
4007 'oojs-ui.styles.icons-alerts',
4008 'oojs-ui.styles.icons-interactions',
4009 ] );
4010 }
4011
4017 protected function addLogoPreloadLinkHeaders() {
4018 $logo = ResourceLoaderSkinModule::getLogo( $this->getConfig() );
4019
4020 $tags = [];
4021 $logosPerDppx = [];
4022 $logos = [];
4023
4024 if ( !is_array( $logo ) ) {
4025 // No media queries required if we only have one variant
4026 $this->addLinkHeader( '<' . $logo . '>;rel=preload;as=image' );
4027 return;
4028 }
4029
4030 foreach ( $logo as $dppx => $src ) {
4031 // Keys are in this format: "1.5x"
4032 $dppx = substr( $dppx, 0, -1 );
4033 $logosPerDppx[$dppx] = $src;
4034 }
4035
4036 // Because PHP can't have floats as array keys
4037 uksort( $logosPerDppx, function ( $a , $b ) {
4038 $a = floatval( $a );
4039 $b = floatval( $b );
4040
4041 if ( $a == $b ) {
4042 return 0;
4043 }
4044 // Sort from smallest to largest (e.g. 1x, 1.5x, 2x)
4045 return ( $a < $b ) ? -1 : 1;
4046 } );
4047
4048 foreach ( $logosPerDppx as $dppx => $src ) {
4049 $logos[] = [ 'dppx' => $dppx, 'src' => $src ];
4050 }
4051
4052 $logosCount = count( $logos );
4053 // Logic must match ResourceLoaderSkinModule:
4054 // - 1x applies to resolution < 1.5dppx
4055 // - 1.5x applies to resolution >= 1.5dppx && < 2dppx
4056 // - 2x applies to resolution >= 2dppx
4057 // Note that min-resolution and max-resolution are both inclusive.
4058 for ( $i = 0; $i < $logosCount; $i++ ) {
4059 if ( $i === 0 ) {
4060 // Smallest dppx
4061 // min-resolution is ">=" (larger than or equal to)
4062 // "not min-resolution" is essentially "<"
4063 $media_query = 'not all and (min-resolution: ' . $logos[ 1 ]['dppx'] . 'dppx)';
4064 } elseif ( $i !== $logosCount - 1 ) {
4065 // In between
4066 // Media query expressions can only apply "not" to the entire expression
4067 // (e.g. can't express ">= 1.5 and not >= 2).
4068 // Workaround: Use <= 1.9999 in place of < 2.
4069 $upper_bound = floatval( $logos[ $i + 1 ]['dppx'] ) - 0.000001;
4070 $media_query = '(min-resolution: ' . $logos[ $i ]['dppx'] .
4071 'dppx) and (max-resolution: ' . $upper_bound . 'dppx)';
4072 } else {
4073 // Largest dppx
4074 $media_query = '(min-resolution: ' . $logos[ $i ]['dppx'] . 'dppx)';
4075 }
4076
4077 $this->addLinkHeader(
4078 '<' . $logos[$i]['src'] . '>;rel=preload;as=image;media=' . $media_query
4079 );
4080 }
4081 }
4082}
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,...
wfBCP47( $code)
Get the normalised IETF language tag See unit test for examples.
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.
$wgParser
Definition Setup.php:832
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:662
$IP
Definition WebStart.php:57
if( $line===false) $args
Definition cdb.php:63
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:823
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
getSkin()
Get the Skin object.
msg( $key)
Get a Message object with context set Parameters are the same as wfMessage()
getUser()
Get the User object.
getRequest()
Get the WebRequest object.
canUseWikiPage()
Check whether a WikiPage object can be get with getWikiPage().
getConfig()
Get the Config object.
getTitle()
Get the Title object.
IContextSource $context
getWikiPage()
Get the WikiPage object.
getLanguage()
Get the Language object.
getContext()
Get the base IContextSource object.
setContext(IContextSource $context)
Set the IContextSource object.
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:877
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.
setSquidMaxage( $maxage)
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.
bool $mEnableSectionEditLinks
Whether parser output should contain section edit 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
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.
readOnlyPage()
Display a page stating that the Wiki is in read-only mode.
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.
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.
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.
rateLimited()
Turn off regular page output and return an error response for when rate limiting has triggered.
__construct(IContextSource $context=null)
Constructor for OutputPage.
static transformResourcePath(Config $config, $path)
Transform path to web-accessible static resource.
string $mRedirect
addParserOutputText( $parserOutput)
Add the HTML associated with a ParserOutput object, without any metadata.
array $mModuleScripts
addExtensionStyle( $url)
Register and add a stylesheet from an extension directory.
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.
addParserOutputContent( $parserOutput)
Add the HTML and enhancements for it (like ResourceLoader modules) associated with a ParserOutput obj...
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.
array $mExtStyles
Additional stylesheets.
warnModuleTargetFilter( $moduleName)
string $mBodytext
Contains all of the "<body>" content.
int $mContainsNewMagic
addScript( $script)
Add raw HTML to the list of scripts (including <script> tag, etc.) Internal use only.
clearHTML()
Clear the body HTML.
array $mSubtitle
Contains the page subtitle.
addVaryHeader( $header, array $option=null)
Add an HTTP header that will influence on the cache.
addStyle( $style, $media='', $condition='', $dir='')
Add a local or specified stylesheet, with the given media options.
getLinkHeader()
Return a Link: header.
setPrintable()
Set the page as printable, i.e.
getResourceLoader()
Get a ResourceLoader object associated with this OutputPage.
feedLink( $type, $url, $text)
Generate a "<link rel/>" for a feed.
bool $mNoGallery
Comes from the parser.
getModuleStyles( $filter=false, $position=null)
Get the list of 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.
addParserOutput( $parserOutput)
Add everything from a ParserOutput object.
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.
setETag( $tag)
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?
getExtStyle()
Get all styles added by extensions.
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.
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
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...
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)
Construct an inline script tag with given JS code.
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:438
getSkinName()
Definition Skin.php:139
addToBodyAttributes( $out, &$bodyAttrs)
This will be called by OutputPage::headElement when it is creating the "<body>" tag,...
Definition Skin.php:454
getPageClasses( $title)
TODO: document.
Definition Skin.php:410
static resolveAlias( $alias)
Given a special page name with a possible subpage, return an array where the first element is the spe...
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Show an error when the user hits a rate limit.
Represents a title within MediaWiki.
Definition Title.php:39
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
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:224
const PROTO_CURRENT
Definition Defines.php:223
const NS_SPECIAL
Definition Defines.php:54
const PROTO_RELATIVE
Definition Defines.php:222
const NS_CATEGORY
Definition Defines.php:79
the array() calling protocol came about after MediaWiki 1.4rc1.
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition hooks.txt:2198
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:2775
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:829
either a plain
Definition hooks.txt:2026
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:1971
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:863
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:962
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:2989
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:1984
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:2787
this hook is for auditing only $response
Definition hooks.txt:781
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:1610
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:2026
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:1265
returning false will NOT prevent logging $e
Definition hooks.txt:2146
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 objects which can provide a MediaWiki context on request.
const DB_REPLICA
Definition defines.php:25
$params
if(!isset( $args[0])) $lang
$header