MediaWiki REL1_29
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
143 protected $mModules = [];
144
146 protected $mModuleScripts = [];
147
149 protected $mModuleStyles = [];
150
153
155 private $rlClient;
156
159
162
165
167 protected $mJsConfigVars = [];
168
170 protected $mTemplateIds = [];
171
173 protected $mImageTimeKeys = [];
174
176 public $mRedirectCode = '';
177
178 protected $mFeedLinksAppendQuery = null;
179
185 protected $mAllowedModules = [
187 ];
188
190 protected $mDoNothing = false;
191
192 // Parser related.
193
195 protected $mContainsNewMagic = 0;
196
201 protected $mParserOptions = null;
202
208 private $mFeedLinks = [];
209
210 // Gwicke work on squid caching? Roughly from 2003.
211 protected $mEnableClientCache = true;
212
214 private $mArticleBodyOnly = false;
215
217 protected $mNewSectionLink = false;
218
220 protected $mHideNewSectionLink = false;
221
227 public $mNoGallery = false;
228
231
233 protected $mCdnMaxage = 0;
235 protected $mCdnMaxageLimit = INF;
236
242 protected $mPreventClickjacking = true;
243
245 private $mRevisionId = null;
246
248 private $mRevisionTimestamp = null;
249
251 protected $mFileVersion = null;
252
261 protected $styles = [];
262
263 private $mIndexPolicy = 'index';
264 private $mFollowPolicy = 'follow';
265 private $mVaryHeader = [
266 'Accept-Encoding' => [ 'match=gzip' ],
267 ];
268
275 private $mRedirectedFrom = null;
276
280 private $mProperties = [];
281
285 private $mTarget = null;
286
290 private $mEnableTOC = true;
291
296
301
303 private $limitReportJSData = [];
304
308 private $mLinkHeader = [];
309
317 if ( $context === null ) {
318 # Extensions should use `new RequestContext` instead of `new OutputPage` now.
319 wfDeprecated( __METHOD__, '1.18' );
320 } else {
321 $this->setContext( $context );
322 }
323 }
324
331 public function redirect( $url, $responsecode = '302' ) {
332 # Strip newlines as a paranoia check for header injection in PHP<5.1.2
333 $this->mRedirect = str_replace( "\n", '', $url );
334 $this->mRedirectCode = $responsecode;
335 }
336
342 public function getRedirect() {
343 return $this->mRedirect;
344 }
345
354 public function setCopyrightUrl( $url ) {
355 $this->copyrightUrl = $url;
356 }
357
363 public function setStatusCode( $statusCode ) {
364 $this->mStatusCode = $statusCode;
365 }
366
374 function addMeta( $name, $val ) {
375 array_push( $this->mMetatags, [ $name, $val ] );
376 }
377
384 public function getMetaTags() {
385 return $this->mMetatags;
386 }
387
395 function addLink( array $linkarr ) {
396 array_push( $this->mLinktags, $linkarr );
397 }
398
405 public function getLinkTags() {
406 return $this->mLinktags;
407 }
408
416 function addMetadataLink( array $linkarr ) {
417 $linkarr['rel'] = $this->getMetadataAttribute();
418 $this->addLink( $linkarr );
419 }
420
426 function setCanonicalUrl( $url ) {
427 $this->mCanonicalUrl = $url;
428 }
429
437 public function getCanonicalUrl() {
439 }
440
446 public function getMetadataAttribute() {
447 # note: buggy CC software only reads first "meta" link
448 static $haveMeta = false;
449 if ( $haveMeta ) {
450 return 'alternate meta';
451 } else {
452 $haveMeta = true;
453 return 'meta';
454 }
455 }
456
464 function addScript( $script ) {
465 $this->mScripts .= $script;
466 }
467
477 public function addExtensionStyle( $url ) {
478 wfDeprecated( __METHOD__, '1.27' );
479 array_push( $this->mExtStyles, $url );
480 }
481
488 function getExtStyle() {
489 wfDeprecated( __METHOD__, '1.27' );
490 return $this->mExtStyles;
491 }
492
501 public function addScriptFile( $file, $version = null ) {
502 // See if $file parameter is an absolute URL or begins with a slash
503 if ( substr( $file, 0, 1 ) == '/' || preg_match( '#^[a-z]*://#i', $file ) ) {
504 $path = $file;
505 } else {
506 $path = $this->getConfig()->get( 'StylePath' ) . "/common/{$file}";
507 }
508 if ( is_null( $version ) ) {
509 $version = $this->getConfig()->get( 'StyleVersion' );
510 }
511 $this->addScript( Html::linkedScript( wfAppendQuery( $path, $version ) ) );
512 }
513
520 public function addInlineScript( $script ) {
521 $this->mScripts .= Html::inlineScript( $script );
522 }
523
532 protected function filterModules( array $modules, $position = null,
534 ) {
536 $filteredModules = [];
537 foreach ( $modules as $val ) {
538 $module = $resourceLoader->getModule( $val );
539 if ( $module instanceof ResourceLoaderModule
540 && $module->getOrigin() <= $this->getAllowedModules( $type )
541 && ( is_null( $position ) || $module->getPosition() == $position )
542 ) {
543 if ( $this->mTarget && !in_array( $this->mTarget, $module->getTargets() ) ) {
544 $this->warnModuleTargetFilter( $module->getName() );
545 continue;
546 }
547 $filteredModules[] = $val;
548 }
549 }
550 return $filteredModules;
551 }
552
553 private function warnModuleTargetFilter( $moduleName ) {
554 static $warnings = [];
555 if ( isset( $warnings[$this->mTarget][$moduleName] ) ) {
556 return;
557 }
558 $warnings[$this->mTarget][$moduleName] = true;
559 $this->getResourceLoader()->getLogger()->debug(
560 'Module "{module}" not loadable on target "{target}".',
561 [
562 'module' => $moduleName,
563 'target' => $this->mTarget,
564 ]
565 );
566 }
567
576 public function getModules( $filter = false, $position = null, $param = 'mModules',
578 ) {
579 $modules = array_values( array_unique( $this->$param ) );
580 return $filter
581 ? $this->filterModules( $modules, $position, $type )
582 : $modules;
583 }
584
592 public function addModules( $modules ) {
593 $this->mModules = array_merge( $this->mModules, (array)$modules );
594 }
595
603 public function getModuleScripts( $filter = false, $position = null ) {
604 return $this->getModules( $filter, $position, 'mModuleScripts',
606 );
607 }
608
616 public function addModuleScripts( $modules ) {
617 $this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules );
618 }
619
627 public function getModuleStyles( $filter = false, $position = null ) {
628 return $this->getModules( $filter, $position, 'mModuleStyles',
630 );
631 }
632
642 public function addModuleStyles( $modules ) {
643 $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
644 }
645
649 public function getTarget() {
650 return $this->mTarget;
651 }
652
658 public function setTarget( $target ) {
659 $this->mTarget = $target;
660 }
661
667 function getHeadItemsArray() {
668 return $this->mHeadItems;
669 }
670
683 public function addHeadItem( $name, $value ) {
684 $this->mHeadItems[$name] = $value;
685 }
686
693 public function addHeadItems( $values ) {
694 $this->mHeadItems = array_merge( $this->mHeadItems, (array)$values );
695 }
696
703 public function hasHeadItem( $name ) {
704 return isset( $this->mHeadItems[$name] );
705 }
706
711 public function setETag( $tag ) {
712 }
713
721 public function setArticleBodyOnly( $only ) {
722 $this->mArticleBodyOnly = $only;
723 }
724
730 public function getArticleBodyOnly() {
732 }
733
741 public function setProperty( $name, $value ) {
742 $this->mProperties[$name] = $value;
743 }
744
752 public function getProperty( $name ) {
753 if ( isset( $this->mProperties[$name] ) ) {
754 return $this->mProperties[$name];
755 } else {
756 return null;
757 }
758 }
759
771 public function checkLastModified( $timestamp ) {
772 if ( !$timestamp || $timestamp == '19700101000000' ) {
773 wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
774 return false;
775 }
776 $config = $this->getConfig();
777 if ( !$config->get( 'CachePages' ) ) {
778 wfDebug( __METHOD__ . ": CACHE DISABLED\n" );
779 return false;
780 }
781
782 $timestamp = wfTimestamp( TS_MW, $timestamp );
783 $modifiedTimes = [
784 'page' => $timestamp,
785 'user' => $this->getUser()->getTouched(),
786 'epoch' => $config->get( 'CacheEpoch' )
787 ];
788 if ( $config->get( 'UseSquid' ) ) {
789 // T46570: the core page itself may not change, but resources might
790 $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, time() - $config->get( 'SquidMaxage' ) );
791 }
792 Hooks::run( 'OutputPageCheckLastModified', [ &$modifiedTimes, $this ] );
793
794 $maxModified = max( $modifiedTimes );
795 $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
796
797 $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' );
798 if ( $clientHeader === false ) {
799 wfDebug( __METHOD__ . ": client did not send If-Modified-Since header", 'private' );
800 return false;
801 }
802
803 # IE sends sizes after the date like this:
804 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
805 # this breaks strtotime().
806 $clientHeader = preg_replace( '/;.*$/', '', $clientHeader );
807
808 MediaWiki\suppressWarnings(); // E_STRICT system time bitching
809 $clientHeaderTime = strtotime( $clientHeader );
810 MediaWiki\restoreWarnings();
811 if ( !$clientHeaderTime ) {
812 wfDebug( __METHOD__
813 . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
814 return false;
815 }
816 $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
817
818 # Make debug info
819 $info = '';
820 foreach ( $modifiedTimes as $name => $value ) {
821 if ( $info !== '' ) {
822 $info .= ', ';
823 }
824 $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
825 }
826
827 wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
828 wfTimestamp( TS_ISO_8601, $clientHeaderTime ), 'private' );
829 wfDebug( __METHOD__ . ": effective Last-Modified: " .
830 wfTimestamp( TS_ISO_8601, $maxModified ), 'private' );
831 if ( $clientHeaderTime < $maxModified ) {
832 wfDebug( __METHOD__ . ": STALE, $info", 'private' );
833 return false;
834 }
835
836 # Not modified
837 # Give a 304 Not Modified response code and disable body output
838 wfDebug( __METHOD__ . ": NOT MODIFIED, $info", 'private' );
839 ini_set( 'zlib.output_compression', 0 );
840 $this->getRequest()->response()->statusHeader( 304 );
841 $this->sendCacheControl();
842 $this->disable();
843
844 // Don't output a compressed blob when using ob_gzhandler;
845 // it's technically against HTTP spec and seems to confuse
846 // Firefox when the response gets split over two packets.
848
849 return true;
850 }
851
858 public function setLastModified( $timestamp ) {
859 $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp );
860 }
861
870 public function setRobotPolicy( $policy ) {
871 $policy = Article::formatRobotPolicy( $policy );
872
873 if ( isset( $policy['index'] ) ) {
874 $this->setIndexPolicy( $policy['index'] );
875 }
876 if ( isset( $policy['follow'] ) ) {
877 $this->setFollowPolicy( $policy['follow'] );
878 }
879 }
880
888 public function setIndexPolicy( $policy ) {
889 $policy = trim( $policy );
890 if ( in_array( $policy, [ 'index', 'noindex' ] ) ) {
891 $this->mIndexPolicy = $policy;
892 }
893 }
894
902 public function setFollowPolicy( $policy ) {
903 $policy = trim( $policy );
904 if ( in_array( $policy, [ 'follow', 'nofollow' ] ) ) {
905 $this->mFollowPolicy = $policy;
906 }
907 }
908
915 public function setPageTitleActionText( $text ) {
916 $this->mPageTitleActionText = $text;
917 }
918
924 public function getPageTitleActionText() {
926 }
927
934 public function setHTMLTitle( $name ) {
935 if ( $name instanceof Message ) {
936 $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
937 } else {
938 $this->mHTMLtitle = $name;
939 }
940 }
941
947 public function getHTMLTitle() {
948 return $this->mHTMLtitle;
949 }
950
956 public function setRedirectedFrom( $t ) {
957 $this->mRedirectedFrom = $t;
958 }
959
970 public function setPageTitle( $name ) {
971 if ( $name instanceof Message ) {
972 $name = $name->setContext( $this->getContext() )->text();
973 }
974
975 # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
976 # but leave "<i>foobar</i>" alone
977 $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) );
978 $this->mPagetitle = $nameWithTags;
979
980 # change "<i>foo&amp;bar</i>" to "foo&bar"
981 $this->setHTMLTitle(
982 $this->msg( 'pagetitle' )->rawParams( Sanitizer::stripAllTags( $nameWithTags ) )
983 ->inContentLanguage()
984 );
985 }
986
992 public function getPageTitle() {
993 return $this->mPagetitle;
994 }
995
1001 public function setTitle( Title $t ) {
1002 $this->getContext()->setTitle( $t );
1003 }
1004
1010 public function setSubtitle( $str ) {
1011 $this->clearSubtitle();
1012 $this->addSubtitle( $str );
1013 }
1014
1020 public function addSubtitle( $str ) {
1021 if ( $str instanceof Message ) {
1022 $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
1023 } else {
1024 $this->mSubtitle[] = $str;
1025 }
1026 }
1027
1036 public static function buildBacklinkSubtitle( Title $title, $query = [] ) {
1037 if ( $title->isRedirect() ) {
1038 $query['redirect'] = 'no';
1039 }
1040 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1041 return wfMessage( 'backlinksubtitle' )
1042 ->rawParams( $linkRenderer->makeLink( $title, null, [], $query ) );
1043 }
1044
1051 public function addBacklinkSubtitle( Title $title, $query = [] ) {
1052 $this->addSubtitle( self::buildBacklinkSubtitle( $title, $query ) );
1053 }
1054
1058 public function clearSubtitle() {
1059 $this->mSubtitle = [];
1060 }
1061
1067 public function getSubtitle() {
1068 return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
1069 }
1070
1075 public function setPrintable() {
1076 $this->mPrintable = true;
1077 }
1078
1084 public function isPrintable() {
1085 return $this->mPrintable;
1086 }
1087
1091 public function disable() {
1092 $this->mDoNothing = true;
1093 }
1094
1100 public function isDisabled() {
1101 return $this->mDoNothing;
1102 }
1103
1109 public function showNewSectionLink() {
1111 }
1112
1118 public function forceHideNewSectionLink() {
1120 }
1121
1130 public function setSyndicated( $show = true ) {
1131 if ( $show ) {
1132 $this->setFeedAppendQuery( false );
1133 } else {
1134 $this->mFeedLinks = [];
1135 }
1136 }
1137
1147 public function setFeedAppendQuery( $val ) {
1148 $this->mFeedLinks = [];
1149
1150 foreach ( $this->getConfig()->get( 'AdvertisedFeedTypes' ) as $type ) {
1151 $query = "feed=$type";
1152 if ( is_string( $val ) ) {
1153 $query .= '&' . $val;
1154 }
1155 $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
1156 }
1157 }
1158
1165 public function addFeedLink( $format, $href ) {
1166 if ( in_array( $format, $this->getConfig()->get( 'AdvertisedFeedTypes' ) ) ) {
1167 $this->mFeedLinks[$format] = $href;
1168 }
1169 }
1170
1175 public function isSyndicated() {
1176 return count( $this->mFeedLinks ) > 0;
1177 }
1178
1183 public function getSyndicationLinks() {
1184 return $this->mFeedLinks;
1185 }
1186
1192 public function getFeedAppendQuery() {
1194 }
1195
1203 public function setArticleFlag( $v ) {
1204 $this->mIsarticle = $v;
1205 if ( $v ) {
1206 $this->mIsArticleRelated = $v;
1207 }
1208 }
1209
1216 public function isArticle() {
1217 return $this->mIsarticle;
1218 }
1219
1226 public function setArticleRelated( $v ) {
1227 $this->mIsArticleRelated = $v;
1228 if ( !$v ) {
1229 $this->mIsarticle = false;
1230 }
1231 }
1232
1238 public function isArticleRelated() {
1240 }
1241
1248 public function addLanguageLinks( array $newLinkArray ) {
1249 $this->mLanguageLinks += $newLinkArray;
1250 }
1251
1258 public function setLanguageLinks( array $newLinkArray ) {
1259 $this->mLanguageLinks = $newLinkArray;
1260 }
1261
1267 public function getLanguageLinks() {
1268 return $this->mLanguageLinks;
1269 }
1270
1276 public function addCategoryLinks( array $categories ) {
1278
1279 if ( !is_array( $categories ) || count( $categories ) == 0 ) {
1280 return;
1281 }
1282
1283 $res = $this->addCategoryLinksToLBAndGetResult( $categories );
1284
1285 # Set all the values to 'normal'.
1286 $categories = array_fill_keys( array_keys( $categories ), 'normal' );
1287
1288 # Mark hidden categories
1289 foreach ( $res as $row ) {
1290 if ( isset( $row->pp_value ) ) {
1291 $categories[$row->page_title] = 'hidden';
1292 }
1293 }
1294
1295 // Avoid PHP 7.1 warning of passing $this by reference
1296 $outputPage = $this;
1297 # Add the remaining categories to the skin
1298 if ( Hooks::run(
1299 'OutputPageMakeCategoryLinks',
1300 [ &$outputPage, $categories, &$this->mCategoryLinks ] )
1301 ) {
1302 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1303 foreach ( $categories as $category => $type ) {
1304 // array keys will cast numeric category names to ints, so cast back to string
1305 $category = (string)$category;
1306 $origcategory = $category;
1307 $title = Title::makeTitleSafe( NS_CATEGORY, $category );
1308 if ( !$title ) {
1309 continue;
1310 }
1311 $wgContLang->findVariantLink( $category, $title, true );
1312 if ( $category != $origcategory && array_key_exists( $category, $categories ) ) {
1313 continue;
1314 }
1315 $text = $wgContLang->convertHtml( $title->getText() );
1316 $this->mCategories[$type][] = $title->getText();
1317 $this->mCategoryLinks[$type][] = $linkRenderer->makeLink( $title, new HtmlArmor( $text ) );
1318 }
1319 }
1320 }
1321
1326 protected function addCategoryLinksToLBAndGetResult( array $categories ) {
1327 # Add the links to a LinkBatch
1328 $arr = [ NS_CATEGORY => $categories ];
1329 $lb = new LinkBatch;
1330 $lb->setArray( $arr );
1331
1332 # Fetch existence plus the hiddencat property
1333 $dbr = wfGetDB( DB_REPLICA );
1334 $fields = array_merge(
1335 LinkCache::getSelectFields(),
1336 [ 'page_namespace', 'page_title', 'pp_value' ]
1337 );
1338
1339 $res = $dbr->select( [ 'page', 'page_props' ],
1340 $fields,
1341 $lb->constructSet( 'page', $dbr ),
1342 __METHOD__,
1343 [],
1344 [ 'page_props' => [ 'LEFT JOIN', [
1345 'pp_propname' => 'hiddencat',
1346 'pp_page = page_id'
1347 ] ] ]
1348 );
1349
1350 # Add the results to the link cache
1351 $lb->addResultToCache( LinkCache::singleton(), $res );
1352
1353 return $res;
1354 }
1355
1361 public function setCategoryLinks( array $categories ) {
1362 $this->mCategoryLinks = [];
1363 $this->addCategoryLinks( $categories );
1364 }
1365
1374 public function getCategoryLinks() {
1375 return $this->mCategoryLinks;
1376 }
1377
1387 public function getCategories( $type = 'all' ) {
1388 if ( $type === 'all' ) {
1389 $allCategories = [];
1390 foreach ( $this->mCategories as $categories ) {
1391 $allCategories = array_merge( $allCategories, $categories );
1392 }
1393 return $allCategories;
1394 }
1395 if ( !isset( $this->mCategories[$type] ) ) {
1396 throw new InvalidArgumentException( 'Invalid category type given: ' . $type );
1397 }
1398 return $this->mCategories[$type];
1399 }
1400
1410 public function setIndicators( array $indicators ) {
1411 $this->mIndicators = $indicators + $this->mIndicators;
1412 // Keep ordered by key
1413 ksort( $this->mIndicators );
1414 }
1415
1424 public function getIndicators() {
1425 return $this->mIndicators;
1426 }
1427
1436 public function addHelpLink( $to, $overrideBaseUrl = false ) {
1437 $this->addModuleStyles( 'mediawiki.helplink' );
1438 $text = $this->msg( 'helppage-top-gethelp' )->escaped();
1439
1440 if ( $overrideBaseUrl ) {
1441 $helpUrl = $to;
1442 } else {
1443 $toUrlencoded = wfUrlencode( str_replace( ' ', '_', $to ) );
1444 $helpUrl = "//www.mediawiki.org/wiki/Special:MyLanguage/$toUrlencoded";
1445 }
1446
1447 $link = Html::rawElement(
1448 'a',
1449 [
1450 'href' => $helpUrl,
1451 'target' => '_blank',
1452 'class' => 'mw-helplink',
1453 ],
1454 $text
1455 );
1456
1457 $this->setIndicators( [ 'mw-helplink' => $link ] );
1458 }
1459
1468 public function disallowUserJs() {
1469 $this->reduceAllowedModules(
1472 );
1473
1474 // Site-wide styles are controlled by a config setting, see T73621
1475 // for background on why. User styles are never allowed.
1476 if ( $this->getConfig()->get( 'AllowSiteCSSOnRestrictedPages' ) ) {
1478 } else {
1480 }
1481 $this->reduceAllowedModules(
1483 $styleOrigin
1484 );
1485 }
1486
1493 public function getAllowedModules( $type ) {
1495 return min( array_values( $this->mAllowedModules ) );
1496 } else {
1497 return isset( $this->mAllowedModules[$type] )
1498 ? $this->mAllowedModules[$type]
1500 }
1501 }
1502
1512 public function reduceAllowedModules( $type, $level ) {
1513 $this->mAllowedModules[$type] = min( $this->getAllowedModules( $type ), $level );
1514 }
1515
1521 public function prependHTML( $text ) {
1522 $this->mBodytext = $text . $this->mBodytext;
1523 }
1524
1530 public function addHTML( $text ) {
1531 $this->mBodytext .= $text;
1532 }
1533
1543 public function addElement( $element, array $attribs = [], $contents = '' ) {
1544 $this->addHTML( Html::element( $element, $attribs, $contents ) );
1545 }
1546
1550 public function clearHTML() {
1551 $this->mBodytext = '';
1552 }
1553
1559 public function getHTML() {
1560 return $this->mBodytext;
1561 }
1562
1570 public function parserOptions( $options = null ) {
1571 if ( $options !== null && !empty( $options->isBogus ) ) {
1572 // Someone is trying to set a bogus pre-$wgUser PO. Check if it has
1573 // been changed somehow, and keep it if so.
1574 $anonPO = ParserOptions::newFromAnon();
1575 $anonPO->setEditSection( false );
1576 $anonPO->setAllowUnsafeRawHtml( false );
1577 if ( !$options->matches( $anonPO ) ) {
1578 wfLogWarning( __METHOD__ . ': Setting a changed bogus ParserOptions: ' . wfGetAllCallers( 5 ) );
1579 $options->isBogus = false;
1580 }
1581 }
1582
1583 if ( !$this->mParserOptions ) {
1584 if ( !$this->getContext()->getUser()->isSafeToLoad() ) {
1585 // $wgUser isn't unstubbable yet, so don't try to get a
1586 // ParserOptions for it. And don't cache this ParserOptions
1587 // either.
1589 $po->setEditSection( false );
1590 $po->setAllowUnsafeRawHtml( false );
1591 $po->isBogus = true;
1592 if ( $options !== null ) {
1593 $this->mParserOptions = empty( $options->isBogus ) ? $options : null;
1594 }
1595 return $po;
1596 }
1597
1598 $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
1599 $this->mParserOptions->setEditSection( false );
1600 $this->mParserOptions->setAllowUnsafeRawHtml( false );
1601 }
1602
1603 if ( $options !== null && !empty( $options->isBogus ) ) {
1604 // They're trying to restore the bogus pre-$wgUser PO. Do the right
1605 // thing.
1606 return wfSetVar( $this->mParserOptions, null, true );
1607 } else {
1608 return wfSetVar( $this->mParserOptions, $options );
1609 }
1610 }
1611
1619 public function setRevisionId( $revid ) {
1620 $val = is_null( $revid ) ? null : intval( $revid );
1621 return wfSetVar( $this->mRevisionId, $val );
1622 }
1623
1629 public function getRevisionId() {
1630 return $this->mRevisionId;
1631 }
1632
1640 public function setRevisionTimestamp( $timestamp ) {
1641 return wfSetVar( $this->mRevisionTimestamp, $timestamp );
1642 }
1643
1650 public function getRevisionTimestamp() {
1652 }
1653
1660 public function setFileVersion( $file ) {
1661 $val = null;
1662 if ( $file instanceof File && $file->exists() ) {
1663 $val = [ 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ];
1664 }
1665 return wfSetVar( $this->mFileVersion, $val, true );
1666 }
1667
1673 public function getFileVersion() {
1674 return $this->mFileVersion;
1675 }
1676
1683 public function getTemplateIds() {
1684 return $this->mTemplateIds;
1685 }
1686
1693 public function getFileSearchOptions() {
1694 return $this->mImageTimeKeys;
1695 }
1696
1706 public function addWikiText( $text, $linestart = true, $interface = true ) {
1707 $title = $this->getTitle(); // Work around E_STRICT
1708 if ( !$title ) {
1709 throw new MWException( 'Title is null' );
1710 }
1711 $this->addWikiTextTitle( $text, $title, $linestart, /*tidy*/false, $interface );
1712 }
1713
1721 public function addWikiTextWithTitle( $text, &$title, $linestart = true ) {
1722 $this->addWikiTextTitle( $text, $title, $linestart );
1723 }
1724
1732 function addWikiTextTitleTidy( $text, &$title, $linestart = true ) {
1733 $this->addWikiTextTitle( $text, $title, $linestart, true );
1734 }
1735
1742 public function addWikiTextTidy( $text, $linestart = true ) {
1743 $title = $this->getTitle();
1744 $this->addWikiTextTitleTidy( $text, $title, $linestart );
1745 }
1746
1757 public function addWikiTextTitle( $text, Title $title, $linestart,
1758 $tidy = false, $interface = false
1759 ) {
1761
1762 $popts = $this->parserOptions();
1763 $oldTidy = $popts->setTidy( $tidy );
1764 $popts->setInterfaceMessage( (bool)$interface );
1765
1766 $parserOutput = $wgParser->getFreshParser()->parse(
1767 $text, $title, $popts,
1768 $linestart, true, $this->mRevisionId
1769 );
1770
1771 $popts->setTidy( $oldTidy );
1772
1774 }
1775
1783 wfDeprecated( __METHOD__, '1.24' );
1785 }
1786
1796 $this->mLanguageLinks += $parserOutput->getLanguageLinks();
1797 $this->addCategoryLinks( $parserOutput->getCategories() );
1798 $this->setIndicators( $parserOutput->getIndicators() );
1799 $this->mNewSectionLink = $parserOutput->getNewSection();
1800 $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
1801
1802 if ( !$parserOutput->isCacheable() ) {
1803 $this->enableClientCache( false );
1804 }
1805 $this->mNoGallery = $parserOutput->getNoGallery();
1806 $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
1807 $this->addModules( $parserOutput->getModules() );
1808 $this->addModuleScripts( $parserOutput->getModuleScripts() );
1809 $this->addModuleStyles( $parserOutput->getModuleStyles() );
1810 $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1811 $this->mPreventClickjacking = $this->mPreventClickjacking
1812 || $parserOutput->preventClickjacking();
1813
1814 // Template versioning...
1815 foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
1816 if ( isset( $this->mTemplateIds[$ns] ) ) {
1817 $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
1818 } else {
1819 $this->mTemplateIds[$ns] = $dbks;
1820 }
1821 }
1822 // File versioning...
1823 foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) {
1824 $this->mImageTimeKeys[$dbk] = $data;
1825 }
1826
1827 // Hooks registered in the object
1828 $parserOutputHooks = $this->getConfig()->get( 'ParserOutputHooks' );
1829 foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
1830 list( $hookName, $data ) = $hookInfo;
1831 if ( isset( $parserOutputHooks[$hookName] ) ) {
1832 call_user_func( $parserOutputHooks[$hookName], $this, $parserOutput, $data );
1833 }
1834 }
1835
1836 // Enable OOUI if requested via ParserOutput
1837 if ( $parserOutput->getEnableOOUI() ) {
1838 $this->enableOOUI();
1839 }
1840
1841 // Include parser limit report
1842 if ( !$this->limitReportJSData ) {
1843 $this->limitReportJSData = $parserOutput->getLimitReportJSData();
1844 }
1845
1846 // Link flags are ignored for now, but may in the future be
1847 // used to mark individual language links.
1848 $linkFlags = [];
1849 // Avoid PHP 7.1 warning of passing $this by reference
1850 $outputPage = $this;
1851 Hooks::run( 'LanguageLinks', [ $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ] );
1852 Hooks::run( 'OutputPageParserOutput', [ &$outputPage, $parserOutput ] );
1853 }
1854
1864
1865 $this->addModules( $parserOutput->getModules() );
1866 $this->addModuleScripts( $parserOutput->getModuleScripts() );
1867 $this->addModuleStyles( $parserOutput->getModuleStyles() );
1868
1869 $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1870 }
1871
1879 $text = $parserOutput->getText();
1880 // Avoid PHP 7.1 warning of passing $this by reference
1881 $outputPage = $this;
1882 Hooks::run( 'OutputPageBeforeHTML', [ &$outputPage, &$text ] );
1883 $this->addHTML( $text );
1884 }
1885
1893 $parserOutput->setTOCEnabled( $this->mEnableTOC );
1894
1895 // Touch section edit links only if not previously disabled
1896 if ( $parserOutput->getEditSectionTokens() ) {
1897 $parserOutput->setEditSectionTokens( $this->mEnableSectionEditLinks );
1898 }
1899
1901 }
1902
1908 public function addTemplate( &$template ) {
1909 $this->addHTML( $template->getHTML() );
1910 }
1911
1924 public function parse( $text, $linestart = true, $interface = false, $language = null ) {
1926
1927 if ( is_null( $this->getTitle() ) ) {
1928 throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
1929 }
1930
1931 $popts = $this->parserOptions();
1932 if ( $interface ) {
1933 $popts->setInterfaceMessage( true );
1934 }
1935 if ( $language !== null ) {
1936 $oldLang = $popts->setTargetLanguage( $language );
1937 }
1938
1939 $parserOutput = $wgParser->getFreshParser()->parse(
1940 $text, $this->getTitle(), $popts,
1941 $linestart, true, $this->mRevisionId
1942 );
1943
1944 if ( $interface ) {
1945 $popts->setInterfaceMessage( false );
1946 }
1947 if ( $language !== null ) {
1948 $popts->setTargetLanguage( $oldLang );
1949 }
1950
1951 return $parserOutput->getText();
1952 }
1953
1964 public function parseInline( $text, $linestart = true, $interface = false ) {
1965 $parsed = $this->parse( $text, $linestart, $interface );
1966 return Parser::stripOuterParagraph( $parsed );
1967 }
1968
1973 public function setSquidMaxage( $maxage ) {
1974 $this->setCdnMaxage( $maxage );
1975 }
1976
1982 public function setCdnMaxage( $maxage ) {
1983 $this->mCdnMaxage = min( $maxage, $this->mCdnMaxageLimit );
1984 }
1985
1992 public function lowerCdnMaxage( $maxage ) {
1993 $this->mCdnMaxageLimit = min( $maxage, $this->mCdnMaxageLimit );
1994 $this->setCdnMaxage( $this->mCdnMaxage );
1995 }
1996
2010 public function adaptCdnTTL( $mtime, $minTTL = 0, $maxTTL = 0 ) {
2011 $minTTL = $minTTL ?: IExpiringStore::TTL_MINUTE;
2012 $maxTTL = $maxTTL ?: $this->getConfig()->get( 'SquidMaxage' );
2013
2014 if ( $mtime === null || $mtime === false ) {
2015 return $minTTL; // entity does not exist
2016 }
2017
2018 $age = time() - wfTimestamp( TS_UNIX, $mtime );
2019 $adaptiveTTL = max( .9 * $age, $minTTL );
2020 $adaptiveTTL = min( $adaptiveTTL, $maxTTL );
2021
2022 $this->lowerCdnMaxage( (int)$adaptiveTTL );
2023
2024 return $adaptiveTTL;
2025 }
2026
2034 public function enableClientCache( $state ) {
2035 return wfSetVar( $this->mEnableClientCache, $state );
2036 }
2037
2044 static $cookies;
2045 if ( $cookies === null ) {
2046 $config = $this->getConfig();
2047 $cookies = array_merge(
2048 SessionManager::singleton()->getVaryCookies(),
2049 [
2050 'forceHTTPS',
2051 ],
2052 $config->get( 'CacheVaryCookies' )
2053 );
2054 Hooks::run( 'GetCacheVaryCookies', [ $this, &$cookies ] );
2055 }
2056 return $cookies;
2057 }
2058
2066 $request = $this->getRequest();
2067 foreach ( $this->getCacheVaryCookies() as $cookieName ) {
2068 if ( $request->getCookie( $cookieName, '', '' ) !== '' ) {
2069 wfDebug( __METHOD__ . ": found $cookieName\n" );
2070 return true;
2071 }
2072 }
2073 wfDebug( __METHOD__ . ": no cache-varying cookies found\n" );
2074 return false;
2075 }
2076
2085 public function addVaryHeader( $header, array $option = null ) {
2086 if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
2087 $this->mVaryHeader[$header] = [];
2088 }
2089 if ( !is_array( $option ) ) {
2090 $option = [];
2091 }
2092 $this->mVaryHeader[$header] = array_unique( array_merge( $this->mVaryHeader[$header], $option ) );
2093 }
2094
2101 public function getVaryHeader() {
2102 // If we vary on cookies, let's make sure it's always included here too.
2103 if ( $this->getCacheVaryCookies() ) {
2104 $this->addVaryHeader( 'Cookie' );
2105 }
2106
2107 foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2108 $this->addVaryHeader( $header, $options );
2109 }
2110 return 'Vary: ' . implode( ', ', array_keys( $this->mVaryHeader ) );
2111 }
2112
2118 public function addLinkHeader( $header ) {
2119 $this->mLinkHeader[] = $header;
2120 }
2121
2127 public function getLinkHeader() {
2128 if ( !$this->mLinkHeader ) {
2129 return false;
2130 }
2131
2132 return 'Link: ' . implode( ',', $this->mLinkHeader );
2133 }
2134
2140 public function getKeyHeader() {
2141 $cvCookies = $this->getCacheVaryCookies();
2142
2143 $cookiesOption = [];
2144 foreach ( $cvCookies as $cookieName ) {
2145 $cookiesOption[] = 'param=' . $cookieName;
2146 }
2147 $this->addVaryHeader( 'Cookie', $cookiesOption );
2148
2149 foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2150 $this->addVaryHeader( $header, $options );
2151 }
2152
2153 $headers = [];
2154 foreach ( $this->mVaryHeader as $header => $option ) {
2155 $newheader = $header;
2156 if ( is_array( $option ) && count( $option ) > 0 ) {
2157 $newheader .= ';' . implode( ';', $option );
2158 }
2159 $headers[] = $newheader;
2160 }
2161 $key = 'Key: ' . implode( ',', $headers );
2162
2163 return $key;
2164 }
2165
2175 $title = $this->getTitle();
2176 if ( !$title instanceof Title ) {
2177 return;
2178 }
2179
2180 $lang = $title->getPageLanguage();
2181 if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
2182 $variants = $lang->getVariants();
2183 $aloption = [];
2184 foreach ( $variants as $variant ) {
2185 if ( $variant === $lang->getCode() ) {
2186 continue;
2187 } else {
2188 $aloption[] = 'substr=' . $variant;
2189
2190 // IE and some other browsers use BCP 47 standards in
2191 // their Accept-Language header, like "zh-CN" or "zh-Hant".
2192 // We should handle these too.
2193 $variantBCP47 = wfBCP47( $variant );
2194 if ( $variantBCP47 !== $variant ) {
2195 $aloption[] = 'substr=' . $variantBCP47;
2196 }
2197 }
2198 }
2199 $this->addVaryHeader( 'Accept-Language', $aloption );
2200 }
2201 }
2202
2213 public function preventClickjacking( $enable = true ) {
2214 $this->mPreventClickjacking = $enable;
2215 }
2216
2222 public function allowClickjacking() {
2223 $this->mPreventClickjacking = false;
2224 }
2225
2232 public function getPreventClickjacking() {
2234 }
2235
2243 public function getFrameOptions() {
2244 $config = $this->getConfig();
2245 if ( $config->get( 'BreakFrames' ) ) {
2246 return 'DENY';
2247 } elseif ( $this->mPreventClickjacking && $config->get( 'EditPageFrameOptions' ) ) {
2248 return $config->get( 'EditPageFrameOptions' );
2249 }
2250 return false;
2251 }
2252
2256 public function sendCacheControl() {
2257 $response = $this->getRequest()->response();
2258 $config = $this->getConfig();
2259
2260 $this->addVaryHeader( 'Cookie' );
2261 $this->addAcceptLanguage();
2262
2263 # don't serve compressed data to clients who can't handle it
2264 # maintain different caches for logged-in users and non-logged in ones
2265 $response->header( $this->getVaryHeader() );
2266
2267 if ( $config->get( 'UseKeyHeader' ) ) {
2268 $response->header( $this->getKeyHeader() );
2269 }
2270
2271 if ( $this->mEnableClientCache ) {
2272 if (
2273 $config->get( 'UseSquid' ) &&
2274 !$response->hasCookies() &&
2275 !SessionManager::getGlobalSession()->isPersistent() &&
2276 !$this->isPrintable() &&
2277 $this->mCdnMaxage != 0 &&
2278 !$this->haveCacheVaryCookies()
2279 ) {
2280 if ( $config->get( 'UseESI' ) ) {
2281 # We'll purge the proxy cache explicitly, but require end user agents
2282 # to revalidate against the proxy on each visit.
2283 # Surrogate-Control controls our CDN, Cache-Control downstream caches
2284 wfDebug( __METHOD__ .
2285 ": proxy caching with ESI; {$this->mLastModified} **", 'private' );
2286 # start with a shorter timeout for initial testing
2287 # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
2288 $response->header(
2289 "Surrogate-Control: max-age={$config->get( 'SquidMaxage' )}" .
2290 "+{$this->mCdnMaxage}, content=\"ESI/1.0\""
2291 );
2292 $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
2293 } else {
2294 # We'll purge the proxy cache for anons explicitly, but require end user agents
2295 # to revalidate against the proxy on each visit.
2296 # IMPORTANT! The CDN needs to replace the Cache-Control header with
2297 # Cache-Control: s-maxage=0, must-revalidate, max-age=0
2298 wfDebug( __METHOD__ .
2299 ": local proxy caching; {$this->mLastModified} **", 'private' );
2300 # start with a shorter timeout for initial testing
2301 # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
2302 $response->header( "Cache-Control: " .
2303 "s-maxage={$this->mCdnMaxage}, must-revalidate, max-age=0" );
2304 }
2305 } else {
2306 # We do want clients to cache if they can, but they *must* check for updates
2307 # on revisiting the page.
2308 wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **", 'private' );
2309 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2310 $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
2311 }
2312 if ( $this->mLastModified ) {
2313 $response->header( "Last-Modified: {$this->mLastModified}" );
2314 }
2315 } else {
2316 wfDebug( __METHOD__ . ": no caching **", 'private' );
2317
2318 # In general, the absence of a last modified header should be enough to prevent
2319 # the client from using its cache. We send a few other things just to make sure.
2320 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2321 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
2322 $response->header( 'Pragma: no-cache' );
2323 }
2324 }
2325
2336 public function output( $return = false ) {
2338
2339 if ( $this->mDoNothing ) {
2340 return $return ? '' : null;
2341 }
2342
2343 $response = $this->getRequest()->response();
2344 $config = $this->getConfig();
2345
2346 if ( $this->mRedirect != '' ) {
2347 # Standards require redirect URLs to be absolute
2348 $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT );
2349
2350 $redirect = $this->mRedirect;
2352
2353 if ( Hooks::run( "BeforePageRedirect", [ $this, &$redirect, &$code ] ) ) {
2354 if ( $code == '301' || $code == '303' ) {
2355 if ( !$config->get( 'DebugRedirects' ) ) {
2356 $response->statusHeader( $code );
2357 }
2358 $this->mLastModified = wfTimestamp( TS_RFC2822 );
2359 }
2360 if ( $config->get( 'VaryOnXFP' ) ) {
2361 $this->addVaryHeader( 'X-Forwarded-Proto' );
2362 }
2363 $this->sendCacheControl();
2364
2365 $response->header( "Content-Type: text/html; charset=utf-8" );
2366 if ( $config->get( 'DebugRedirects' ) ) {
2367 $url = htmlspecialchars( $redirect );
2368 print "<!DOCTYPE html>\n<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
2369 print "<p>Location: <a href=\"$url\">$url</a></p>\n";
2370 print "</body>\n</html>\n";
2371 } else {
2372 $response->header( 'Location: ' . $redirect );
2373 }
2374 }
2375
2376 return $return ? '' : null;
2377 } elseif ( $this->mStatusCode ) {
2378 $response->statusHeader( $this->mStatusCode );
2379 }
2380
2381 # Buffer output; final headers may depend on later processing
2382 ob_start();
2383
2384 $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
2385 $response->header( 'Content-language: ' . $wgContLang->getHtmlCode() );
2386
2387 // Avoid Internet Explorer "compatibility view" in IE 8-10, so that
2388 // jQuery etc. can work correctly.
2389 $response->header( 'X-UA-Compatible: IE=Edge' );
2390
2392 $linkHeader = $this->getLinkHeader();
2393 if ( $linkHeader ) {
2394 $response->header( $linkHeader );
2395 }
2396
2397 // Prevent framing, if requested
2398 $frameOptions = $this->getFrameOptions();
2399 if ( $frameOptions ) {
2400 $response->header( "X-Frame-Options: $frameOptions" );
2401 }
2402
2403 if ( $this->mArticleBodyOnly ) {
2404 echo $this->mBodytext;
2405 } else {
2406 // Enable safe mode if requested
2407 if ( $this->getRequest()->getBool( 'safemode' ) ) {
2408 $this->disallowUserJs();
2409 }
2410
2411 $sk = $this->getSkin();
2412 // add skin specific modules
2413 $modules = $sk->getDefaultModules();
2414
2415 // Enforce various default modules for all pages and all skins
2416 $coreModules = [
2417 // Keep this list as small as possible
2418 'site',
2419 'mediawiki.page.startup',
2420 'mediawiki.user',
2421 ];
2422
2423 // Support for high-density display images if enabled
2424 if ( $config->get( 'ResponsiveImages' ) ) {
2425 $coreModules[] = 'mediawiki.hidpi';
2426 }
2427
2428 $this->addModules( $coreModules );
2429 foreach ( $modules as $group ) {
2430 $this->addModules( $group );
2431 }
2432 MWDebug::addModules( $this );
2433
2434 // Avoid PHP 7.1 warning of passing $this by reference
2435 $outputPage = $this;
2436 // Hook that allows last minute changes to the output page, e.g.
2437 // adding of CSS or Javascript by extensions.
2438 Hooks::run( 'BeforePageDisplay', [ &$outputPage, &$sk ] );
2439
2440 try {
2441 $sk->outputPage();
2442 } catch ( Exception $e ) {
2443 ob_end_clean(); // bug T129657
2444 throw $e;
2445 }
2446 }
2447
2448 try {
2449 // This hook allows last minute changes to final overall output by modifying output buffer
2450 Hooks::run( 'AfterFinalPageOutput', [ $this ] );
2451 } catch ( Exception $e ) {
2452 ob_end_clean(); // bug T129657
2453 throw $e;
2454 }
2455
2456 $this->sendCacheControl();
2457
2458 if ( $return ) {
2459 return ob_get_clean();
2460 } else {
2461 ob_end_flush();
2462 return null;
2463 }
2464 }
2465
2476 public function prepareErrorPage( $pageTitle, $htmlTitle = false ) {
2477 $this->setPageTitle( $pageTitle );
2478 if ( $htmlTitle !== false ) {
2479 $this->setHTMLTitle( $htmlTitle );
2480 }
2481 $this->setRobotPolicy( 'noindex,nofollow' );
2482 $this->setArticleRelated( false );
2483 $this->enableClientCache( false );
2484 $this->mRedirect = '';
2485 $this->clearSubtitle();
2486 $this->clearHTML();
2487 }
2488
2501 public function showErrorPage( $title, $msg, $params = [] ) {
2502 if ( !$title instanceof Message ) {
2503 $title = $this->msg( $title );
2504 }
2505
2506 $this->prepareErrorPage( $title );
2507
2508 if ( $msg instanceof Message ) {
2509 if ( $params !== [] ) {
2510 trigger_error( 'Argument ignored: $params. The message parameters argument '
2511 . 'is discarded when the $msg argument is a Message object instead of '
2512 . 'a string.', E_USER_NOTICE );
2513 }
2514 $this->addHTML( $msg->parseAsBlock() );
2515 } else {
2516 $this->addWikiMsgArray( $msg, $params );
2517 }
2518
2519 $this->returnToMain();
2520 }
2521
2528 public function showPermissionsErrorPage( array $errors, $action = null ) {
2529 foreach ( $errors as $key => $error ) {
2530 $errors[$key] = (array)$error;
2531 }
2532
2533 // For some action (read, edit, create and upload), display a "login to do this action"
2534 // error if all of the following conditions are met:
2535 // 1. the user is not logged in
2536 // 2. the only error is insufficient permissions (i.e. no block or something else)
2537 // 3. the error can be avoided simply by logging in
2538 if ( in_array( $action, [ 'read', 'edit', 'createpage', 'createtalk', 'upload' ] )
2539 && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
2540 && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
2541 && ( User::groupHasPermission( 'user', $action )
2542 || User::groupHasPermission( 'autoconfirmed', $action ) )
2543 ) {
2544 $displayReturnto = null;
2545
2546 # Due to T34276, if a user does not have read permissions,
2547 # $this->getTitle() will just give Special:Badtitle, which is
2548 # not especially useful as a returnto parameter. Use the title
2549 # from the request instead, if there was one.
2550 $request = $this->getRequest();
2551 $returnto = Title::newFromText( $request->getVal( 'title', '' ) );
2552 if ( $action == 'edit' ) {
2553 $msg = 'whitelistedittext';
2554 $displayReturnto = $returnto;
2555 } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
2556 $msg = 'nocreatetext';
2557 } elseif ( $action == 'upload' ) {
2558 $msg = 'uploadnologintext';
2559 } else { # Read
2560 $msg = 'loginreqpagetext';
2561 $displayReturnto = Title::newMainPage();
2562 }
2563
2564 $query = [];
2565
2566 if ( $returnto ) {
2567 $query['returnto'] = $returnto->getPrefixedText();
2568
2569 if ( !$request->wasPosted() ) {
2570 $returntoquery = $request->getValues();
2571 unset( $returntoquery['title'] );
2572 unset( $returntoquery['returnto'] );
2573 unset( $returntoquery['returntoquery'] );
2574 $query['returntoquery'] = wfArrayToCgi( $returntoquery );
2575 }
2576 }
2577 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
2578 $loginLink = $linkRenderer->makeKnownLink(
2579 SpecialPage::getTitleFor( 'Userlogin' ),
2580 $this->msg( 'loginreqlink' )->text(),
2581 [],
2582 $query
2583 );
2584
2585 $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) );
2586 $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->parse() );
2587
2588 # Don't return to a page the user can't read otherwise
2589 # we'll end up in a pointless loop
2590 if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) {
2591 $this->returnToMain( null, $displayReturnto );
2592 }
2593 } else {
2594 $this->prepareErrorPage( $this->msg( 'permissionserrors' ) );
2595 $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) );
2596 }
2597 }
2598
2605 public function versionRequired( $version ) {
2606 $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) );
2607
2608 $this->addWikiMsg( 'versionrequiredtext', $version );
2609 $this->returnToMain();
2610 }
2611
2619 public function formatPermissionsErrorMessage( array $errors, $action = null ) {
2620 if ( $action == null ) {
2621 $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
2622 } else {
2623 $action_desc = $this->msg( "action-$action" )->plain();
2624 $text = $this->msg(
2625 'permissionserrorstext-withaction',
2626 count( $errors ),
2627 $action_desc
2628 )->plain() . "\n\n";
2629 }
2630
2631 if ( count( $errors ) > 1 ) {
2632 $text .= '<ul class="permissions-errors">' . "\n";
2633
2634 foreach ( $errors as $error ) {
2635 $text .= '<li>';
2636 $text .= call_user_func_array( [ $this, 'msg' ], $error )->plain();
2637 $text .= "</li>\n";
2638 }
2639 $text .= '</ul>';
2640 } else {
2641 $text .= "<div class=\"permissions-errors\">\n" .
2642 call_user_func_array( [ $this, 'msg' ], reset( $errors ) )->plain() .
2643 "\n</div>";
2644 }
2645
2646 return $text;
2647 }
2648
2660 public function readOnlyPage() {
2661 if ( func_num_args() > 0 ) {
2662 throw new MWException( __METHOD__ . ' no longer accepts arguments since 1.25.' );
2663 }
2664
2665 throw new ReadOnlyError;
2666 }
2667
2674 public function rateLimited() {
2675 wfDeprecated( __METHOD__, '1.25' );
2676 throw new ThrottledError;
2677 }
2678
2688 public function showLagWarning( $lag ) {
2689 $config = $this->getConfig();
2690 if ( $lag >= $config->get( 'SlaveLagWarning' ) ) {
2691 $lag = floor( $lag ); // floor to avoid nano seconds to display
2692 $message = $lag < $config->get( 'SlaveLagCritical' )
2693 ? 'lag-warn-normal'
2694 : 'lag-warn-high';
2695 $wrap = Html::rawElement( 'div', [ 'class' => "mw-{$message}" ], "\n$1\n" );
2696 $this->wrapWikiMsg( "$wrap\n", [ $message, $this->getLanguage()->formatNum( $lag ) ] );
2697 }
2698 }
2699
2700 public function showFatalError( $message ) {
2701 $this->prepareErrorPage( $this->msg( 'internalerror' ) );
2702
2703 $this->addHTML( $message );
2704 }
2705
2706 public function showUnexpectedValueError( $name, $val ) {
2707 $this->showFatalError( $this->msg( 'unexpected', $name, $val )->text() );
2708 }
2709
2710 public function showFileCopyError( $old, $new ) {
2711 $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->text() );
2712 }
2713
2714 public function showFileRenameError( $old, $new ) {
2715 $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->text() );
2716 }
2717
2718 public function showFileDeleteError( $name ) {
2719 $this->showFatalError( $this->msg( 'filedeleteerror', $name )->text() );
2720 }
2721
2722 public function showFileNotFoundError( $name ) {
2723 $this->showFatalError( $this->msg( 'filenotfound', $name )->text() );
2724 }
2725
2734 public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) {
2735 $linkRenderer = MediaWikiServices::getInstance()
2736 ->getLinkRendererFactory()->createFromLegacyOptions( $options );
2737 $link = $this->msg( 'returnto' )->rawParams(
2738 $linkRenderer->makeLink( $title, $text, [], $query ) )->escaped();
2739 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
2740 }
2741
2750 public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
2751 if ( $returnto == null ) {
2752 $returnto = $this->getRequest()->getText( 'returnto' );
2753 }
2754
2755 if ( $returntoquery == null ) {
2756 $returntoquery = $this->getRequest()->getText( 'returntoquery' );
2757 }
2758
2759 if ( $returnto === '' ) {
2760 $returnto = Title::newMainPage();
2761 }
2762
2763 if ( is_object( $returnto ) ) {
2764 $titleObj = $returnto;
2765 } else {
2766 $titleObj = Title::newFromText( $returnto );
2767 }
2768 // We don't want people to return to external interwiki. That
2769 // might potentially be used as part of a phishing scheme
2770 if ( !is_object( $titleObj ) || $titleObj->isExternal() ) {
2771 $titleObj = Title::newMainPage();
2772 }
2773
2774 $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) );
2775 }
2776
2777 private function getRlClientContext() {
2778 if ( !$this->rlClientContext ) {
2780 [], // modules; not relevant
2781 $this->getLanguage()->getCode(),
2782 $this->getSkin()->getSkinName(),
2783 $this->getUser()->isLoggedIn() ? $this->getUser()->getName() : null,
2784 null, // version; not relevant
2786 null, // only; not relevant
2787 $this->isPrintable(),
2788 $this->getRequest()->getBool( 'handheld' )
2789 );
2790 $this->rlClientContext = new ResourceLoaderContext(
2791 $this->getResourceLoader(),
2792 new FauxRequest( $query )
2793 );
2794 }
2796 }
2797
2809 public function getRlClient() {
2810 if ( !$this->rlClient ) {
2811 $context = $this->getRlClientContext();
2812 $rl = $this->getResourceLoader();
2813 $this->addModules( [
2814 'user.options',
2815 'user.tokens',
2816 ] );
2817 $this->addModuleStyles( [
2818 'site.styles',
2819 'noscript',
2820 'user.styles',
2821 ] );
2822 $this->getSkin()->setupSkinUserCss( $this );
2823
2824 // Prepare exempt modules for buildExemptModules()
2825 $exemptGroups = [ 'site' => [], 'noscript' => [], 'private' => [], 'user' => [] ];
2826 $exemptStates = [];
2827 $moduleStyles = $this->getModuleStyles( /*filter*/ true );
2828
2829 // Preload getTitleInfo for isKnownEmpty calls below and in ResourceLoaderClientHtml
2830 // Separate user-specific batch for improved cache-hit ratio.
2831 $userBatch = [ 'user.styles', 'user' ];
2832 $siteBatch = array_diff( $moduleStyles, $userBatch );
2833 $dbr = wfGetDB( DB_REPLICA );
2836
2837 // Filter out modules handled by buildExemptModules()
2838 $moduleStyles = array_filter( $moduleStyles,
2839 function ( $name ) use ( $rl, $context, &$exemptGroups, &$exemptStates ) {
2840 $module = $rl->getModule( $name );
2841 if ( $module ) {
2842 if ( $name === 'user.styles' && $this->isUserCssPreview() ) {
2843 $exemptStates[$name] = 'ready';
2844 // Special case in buildExemptModules()
2845 return false;
2846 }
2847 $group = $module->getGroup();
2848 if ( isset( $exemptGroups[$group] ) ) {
2849 $exemptStates[$name] = 'ready';
2850 if ( !$module->isKnownEmpty( $context ) ) {
2851 // E.g. Don't output empty <styles>
2852 $exemptGroups[$group][] = $name;
2853 }
2854 return false;
2855 }
2856 }
2857 return true;
2858 }
2859 );
2860 $this->rlExemptStyleModules = $exemptGroups;
2861
2862 $isUserModuleFiltered = !$this->filterModules( [ 'user' ] );
2863 // If this page filters out 'user', makeResourceLoaderLink will drop it.
2864 // Avoid indefinite "loading" state or untrue "ready" state (T145368).
2865 if ( !$isUserModuleFiltered ) {
2866 // Manually handled by getBottomScripts()
2867 $userModule = $rl->getModule( 'user' );
2868 $userState = $userModule->isKnownEmpty( $context ) && !$this->isUserJsPreview()
2869 ? 'ready'
2870 : 'loading';
2871 $this->rlUserModuleState = $exemptStates['user'] = $userState;
2872 }
2873
2875 $rlClient->setConfig( $this->getJSVars() );
2876 $rlClient->setModules( $this->getModules( /*filter*/ true ) );
2877 $rlClient->setModuleStyles( $moduleStyles );
2878 $rlClient->setModuleScripts( $this->getModuleScripts( /*filter*/ true ) );
2879 $rlClient->setExemptStates( $exemptStates );
2880 $this->rlClient = $rlClient;
2881 }
2882 return $this->rlClient;
2883 }
2884
2890 public function headElement( Skin $sk, $includeStyle = true ) {
2892
2893 $userdir = $this->getLanguage()->getDir();
2894 $sitedir = $wgContLang->getDir();
2895
2896 $pieces = [];
2897 $pieces[] = Html::htmlHeader( Sanitizer::mergeAttributes(
2898 $this->getRlClient()->getDocumentAttributes(),
2900 ) );
2901 $pieces[] = Html::openElement( 'head' );
2902
2903 if ( $this->getHTMLTitle() == '' ) {
2904 $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() );
2905 }
2906
2907 if ( !Html::isXmlMimeType( $this->getConfig()->get( 'MimeType' ) ) ) {
2908 // Add <meta charset="UTF-8">
2909 // This should be before <title> since it defines the charset used by
2910 // text including the text inside <title>.
2911 // The spec recommends defining XHTML5's charset using the XML declaration
2912 // instead of meta.
2913 // Our XML declaration is output by Html::htmlHeader.
2914 // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-content-type
2915 // https://html.spec.whatwg.org/multipage/semantics.html#charset
2916 $pieces[] = Html::element( 'meta', [ 'charset' => 'UTF-8' ] );
2917 }
2918
2919 $pieces[] = Html::element( 'title', null, $this->getHTMLTitle() );
2920 $pieces[] = $this->getRlClient()->getHeadHtml();
2921 $pieces[] = $this->buildExemptModules();
2922 $pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) );
2923 $pieces = array_merge( $pieces, array_values( $this->mHeadItems ) );
2924 $pieces[] = Html::closeElement( 'head' );
2925
2926 $bodyClasses = [];
2927 $bodyClasses[] = 'mediawiki';
2928
2929 # Classes for LTR/RTL directionality support
2930 $bodyClasses[] = $userdir;
2931 $bodyClasses[] = "sitedir-$sitedir";
2932
2933 $underline = $this->getUser()->getOption( 'underline' );
2934 if ( $underline < 2 ) {
2935 // The following classes can be used here:
2936 // * mw-underline-always
2937 // * mw-underline-never
2938 $bodyClasses[] = 'mw-underline-' . ( $underline ? 'always' : 'never' );
2939 }
2940
2941 if ( $this->getLanguage()->capitalizeAllNouns() ) {
2942 # A <body> class is probably not the best way to do this . . .
2943 $bodyClasses[] = 'capitalize-all-nouns';
2944 }
2945
2946 // Parser feature migration class
2947 // The idea is that this will eventually be removed, after the wikitext
2948 // which requires it is cleaned up.
2949 $bodyClasses[] = 'mw-hide-empty-elt';
2950
2951 $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
2952 $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
2953 $bodyClasses[] =
2954 'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
2955
2956 $bodyAttrs = [];
2957 // While the implode() is not strictly needed, it's used for backwards compatibility
2958 // (this used to be built as a string and hooks likely still expect that).
2959 $bodyAttrs['class'] = implode( ' ', $bodyClasses );
2960
2961 // Allow skins and extensions to add body attributes they need
2962 $sk->addToBodyAttributes( $this, $bodyAttrs );
2963 Hooks::run( 'OutputPageBodyAttributes', [ $this, $sk, &$bodyAttrs ] );
2964
2965 $pieces[] = Html::openElement( 'body', $bodyAttrs );
2966
2967 return self::combineWrappedStrings( $pieces );
2968 }
2969
2975 public function getResourceLoader() {
2976 if ( is_null( $this->mResourceLoader ) ) {
2977 $this->mResourceLoader = new ResourceLoader(
2978 $this->getConfig(),
2979 LoggerFactory::getInstance( 'resourceloader' )
2980 );
2981 }
2983 }
2984
2993 public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) {
2994 // Apply 'target' and 'origin' filters
2995 $modules = $this->filterModules( (array)$modules, null, $only );
2996
2998 $this->getRlClientContext(),
2999 $modules,
3000 $only,
3001 $extraQuery
3002 );
3003 }
3004
3011 protected static function combineWrappedStrings( array $chunks ) {
3012 // Filter out empty values
3013 $chunks = array_filter( $chunks, 'strlen' );
3014 return WrappedString::join( "\n", $chunks );
3015 }
3016
3017 private function isUserJsPreview() {
3018 return $this->getConfig()->get( 'AllowUserJs' )
3019 && $this->getTitle()
3020 && $this->getTitle()->isJsSubpage()
3021 && $this->userCanPreview();
3022 }
3023
3024 private function isUserCssPreview() {
3025 return $this->getConfig()->get( 'AllowUserCss' )
3026 && $this->getTitle()
3027 && $this->getTitle()->isCssSubpage()
3028 && $this->userCanPreview();
3029 }
3030
3037 public function getBottomScripts() {
3038 $chunks = [];
3039 $chunks[] = $this->getRlClient()->getBodyHtml();
3040
3041 // Legacy non-ResourceLoader scripts
3042 $chunks[] = $this->mScripts;
3043
3044 // Exempt 'user' module
3045 // - May need excludepages for live preview. (T28283)
3046 // - Must use TYPE_COMBINED so its response is handled by mw.loader.implement() which
3047 // ensures execution is scheduled after the "site" module.
3048 // - Don't load if module state is already resolved as "ready".
3049 if ( $this->rlUserModuleState === 'loading' ) {
3050 if ( $this->isUserJsPreview() ) {
3052 [ 'excludepage' => $this->getTitle()->getPrefixedDBkey() ]
3053 );
3055 Xml::encodeJsCall( 'mw.loader.using', [
3056 [ 'user', 'site' ],
3057 new XmlJsCode(
3058 'function () {'
3059 . Xml::encodeJsCall( '$.globalEval', [
3060 $this->getRequest()->getText( 'wpTextbox1' )
3061 ] )
3062 . '}'
3063 )
3064 ] )
3065 );
3066 // FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded
3067 // asynchronously and may arrive *after* the inline script here. So the previewed code
3068 // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js.
3069 // Similarly, when previewing ./common.js and the user module does arrive first,
3070 // it will arrive without common.js and the inline script runs after.
3071 // Thus running common after the excluded subpage.
3072 } else {
3073 // Load normally
3074 $chunks[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_COMBINED );
3075 }
3076 }
3077
3078 if ( $this->limitReportJSData ) {
3081 [ 'wgPageParseReport' => $this->limitReportJSData ]
3082 )
3083 );
3084 }
3085
3086 return self::combineWrappedStrings( $chunks );
3087 }
3088
3095 public function getJsConfigVars() {
3096 return $this->mJsConfigVars;
3097 }
3098
3105 public function addJsConfigVars( $keys, $value = null ) {
3106 if ( is_array( $keys ) ) {
3107 foreach ( $keys as $key => $value ) {
3108 $this->mJsConfigVars[$key] = $value;
3109 }
3110 return;
3111 }
3112
3113 $this->mJsConfigVars[$keys] = $value;
3114 }
3115
3125 public function getJSVars() {
3127
3128 $curRevisionId = 0;
3129 $articleId = 0;
3130 $canonicalSpecialPageName = false; # T23115
3131
3132 $title = $this->getTitle();
3133 $ns = $title->getNamespace();
3134 $canonicalNamespace = MWNamespace::exists( $ns )
3135 ? MWNamespace::getCanonicalName( $ns )
3136 : $title->getNsText();
3137
3138 $sk = $this->getSkin();
3139 // Get the relevant title so that AJAX features can use the correct page name
3140 // when making API requests from certain special pages (T36972).
3141 $relevantTitle = $sk->getRelevantTitle();
3142 $relevantUser = $sk->getRelevantUser();
3143
3144 if ( $ns == NS_SPECIAL ) {
3145 list( $canonicalSpecialPageName, /*...*/ ) =
3147 } elseif ( $this->canUseWikiPage() ) {
3148 $wikiPage = $this->getWikiPage();
3149 $curRevisionId = $wikiPage->getLatest();
3150 $articleId = $wikiPage->getId();
3151 }
3152
3153 $lang = $title->getPageViewLanguage();
3154
3155 // Pre-process information
3156 $separatorTransTable = $lang->separatorTransformTable();
3157 $separatorTransTable = $separatorTransTable ? $separatorTransTable : [];
3158 $compactSeparatorTransTable = [
3159 implode( "\t", array_keys( $separatorTransTable ) ),
3160 implode( "\t", $separatorTransTable ),
3161 ];
3162 $digitTransTable = $lang->digitTransformTable();
3163 $digitTransTable = $digitTransTable ? $digitTransTable : [];
3164 $compactDigitTransTable = [
3165 implode( "\t", array_keys( $digitTransTable ) ),
3166 implode( "\t", $digitTransTable ),
3167 ];
3168
3169 $user = $this->getUser();
3170
3171 $vars = [
3172 'wgCanonicalNamespace' => $canonicalNamespace,
3173 'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
3174 'wgNamespaceNumber' => $title->getNamespace(),
3175 'wgPageName' => $title->getPrefixedDBkey(),
3176 'wgTitle' => $title->getText(),
3177 'wgCurRevisionId' => $curRevisionId,
3178 'wgRevisionId' => (int)$this->getRevisionId(),
3179 'wgArticleId' => $articleId,
3180 'wgIsArticle' => $this->isArticle(),
3181 'wgIsRedirect' => $title->isRedirect(),
3182 'wgAction' => Action::getActionName( $this->getContext() ),
3183 'wgUserName' => $user->isAnon() ? null : $user->getName(),
3184 'wgUserGroups' => $user->getEffectiveGroups(),
3185 'wgCategories' => $this->getCategories(),
3186 'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
3187 'wgPageContentLanguage' => $lang->getCode(),
3188 'wgPageContentModel' => $title->getContentModel(),
3189 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
3190 'wgDigitTransformTable' => $compactDigitTransTable,
3191 'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
3192 'wgMonthNames' => $lang->getMonthNamesArray(),
3193 'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
3194 'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
3195 'wgRelevantArticleId' => $relevantTitle->getArticleID(),
3196 'wgRequestId' => WebRequest::getRequestId(),
3197 ];
3198
3199 if ( $user->isLoggedIn() ) {
3200 $vars['wgUserId'] = $user->getId();
3201 $vars['wgUserEditCount'] = $user->getEditCount();
3202 $userReg = $user->getRegistration();
3203 $vars['wgUserRegistration'] = $userReg ? wfTimestamp( TS_UNIX, $userReg ) * 1000 : null;
3204 // Get the revision ID of the oldest new message on the user's talk
3205 // page. This can be used for constructing new message alerts on
3206 // the client side.
3207 $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId();
3208 }
3209
3210 if ( $wgContLang->hasVariants() ) {
3211 $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
3212 }
3213 // Same test as SkinTemplate
3214 $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user )
3215 && ( $title->exists() || $title->quickUserCan( 'create', $user ) );
3216
3217 foreach ( $title->getRestrictionTypes() as $type ) {
3218 $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
3219 }
3220
3221 if ( $title->isMainPage() ) {
3222 $vars['wgIsMainPage'] = true;
3223 }
3224
3225 if ( $this->mRedirectedFrom ) {
3226 $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey();
3227 }
3228
3229 if ( $relevantUser ) {
3230 $vars['wgRelevantUserName'] = $relevantUser->getName();
3231 }
3232
3233 // Allow extensions to add their custom variables to the mw.config map.
3234 // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
3235 // page-dependant but site-wide (without state).
3236 // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
3237 Hooks::run( 'MakeGlobalVariablesScript', [ &$vars, $this ] );
3238
3239 // Merge in variables from addJsConfigVars last
3240 return array_merge( $vars, $this->getJsConfigVars() );
3241 }
3242
3252 public function userCanPreview() {
3253 $request = $this->getRequest();
3254 if (
3255 $request->getVal( 'action' ) !== 'submit' ||
3256 !$request->getCheck( 'wpPreview' ) ||
3257 !$request->wasPosted()
3258 ) {
3259 return false;
3260 }
3261
3262 $user = $this->getUser();
3263
3264 if ( !$user->isLoggedIn() ) {
3265 // Anons have predictable edit tokens
3266 return false;
3267 }
3268 if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
3269 return false;
3270 }
3271
3272 $title = $this->getTitle();
3273 if ( !$title->isJsSubpage() && !$title->isCssSubpage() ) {
3274 return false;
3275 }
3276 if ( !$title->isSubpageOf( $user->getUserPage() ) ) {
3277 // Don't execute another user's CSS or JS on preview (T85855)
3278 return false;
3279 }
3280
3281 $errors = $title->getUserPermissionsErrors( 'edit', $user );
3282 if ( count( $errors ) !== 0 ) {
3283 return false;
3284 }
3285
3286 return true;
3287 }
3288
3292 public function getHeadLinksArray() {
3294
3295 $tags = [];
3296 $config = $this->getConfig();
3297
3298 $canonicalUrl = $this->mCanonicalUrl;
3299
3300 $tags['meta-generator'] = Html::element( 'meta', [
3301 'name' => 'generator',
3302 'content' => "MediaWiki $wgVersion",
3303 ] );
3304
3305 if ( $config->get( 'ReferrerPolicy' ) !== false ) {
3306 $tags['meta-referrer'] = Html::element( 'meta', [
3307 'name' => 'referrer',
3308 'content' => $config->get( 'ReferrerPolicy' )
3309 ] );
3310 }
3311
3312 $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
3313 if ( $p !== 'index,follow' ) {
3314 // http://www.robotstxt.org/wc/meta-user.html
3315 // Only show if it's different from the default robots policy
3316 $tags['meta-robots'] = Html::element( 'meta', [
3317 'name' => 'robots',
3318 'content' => $p,
3319 ] );
3320 }
3321
3322 foreach ( $this->mMetatags as $tag ) {
3323 if ( strncasecmp( $tag[0], 'http:', 5 ) === 0 ) {
3324 $a = 'http-equiv';
3325 $tag[0] = substr( $tag[0], 5 );
3326 } elseif ( strncasecmp( $tag[0], 'og:', 3 ) === 0 ) {
3327 $a = 'property';
3328 } else {
3329 $a = 'name';
3330 }
3331 $tagName = "meta-{$tag[0]}";
3332 if ( isset( $tags[$tagName] ) ) {
3333 $tagName .= $tag[1];
3334 }
3335 $tags[$tagName] = Html::element( 'meta',
3336 [
3337 $a => $tag[0],
3338 'content' => $tag[1]
3339 ]
3340 );
3341 }
3342
3343 foreach ( $this->mLinktags as $tag ) {
3344 $tags[] = Html::element( 'link', $tag );
3345 }
3346
3347 # Universal edit button
3348 if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) {
3349 $user = $this->getUser();
3350 if ( $this->getTitle()->quickUserCan( 'edit', $user )
3351 && ( $this->getTitle()->exists() ||
3352 $this->getTitle()->quickUserCan( 'create', $user ) )
3353 ) {
3354 // Original UniversalEditButton
3355 $msg = $this->msg( 'edit' )->text();
3356 $tags['universal-edit-button'] = Html::element( 'link', [
3357 'rel' => 'alternate',
3358 'type' => 'application/x-wiki',
3359 'title' => $msg,
3360 'href' => $this->getTitle()->getEditURL(),
3361 ] );
3362 // Alternate edit link
3363 $tags['alternative-edit'] = Html::element( 'link', [
3364 'rel' => 'edit',
3365 'title' => $msg,
3366 'href' => $this->getTitle()->getEditURL(),
3367 ] );
3368 }
3369 }
3370
3371 # Generally the order of the favicon and apple-touch-icon links
3372 # should not matter, but Konqueror (3.5.9 at least) incorrectly
3373 # uses whichever one appears later in the HTML source. Make sure
3374 # apple-touch-icon is specified first to avoid this.
3375 if ( $config->get( 'AppleTouchIcon' ) !== false ) {
3376 $tags['apple-touch-icon'] = Html::element( 'link', [
3377 'rel' => 'apple-touch-icon',
3378 'href' => $config->get( 'AppleTouchIcon' )
3379 ] );
3380 }
3381
3382 if ( $config->get( 'Favicon' ) !== false ) {
3383 $tags['favicon'] = Html::element( 'link', [
3384 'rel' => 'shortcut icon',
3385 'href' => $config->get( 'Favicon' )
3386 ] );
3387 }
3388
3389 # OpenSearch description link
3390 $tags['opensearch'] = Html::element( 'link', [
3391 'rel' => 'search',
3392 'type' => 'application/opensearchdescription+xml',
3393 'href' => wfScript( 'opensearch_desc' ),
3394 'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
3395 ] );
3396
3397 if ( $config->get( 'EnableAPI' ) ) {
3398 # Real Simple Discovery link, provides auto-discovery information
3399 # for the MediaWiki API (and potentially additional custom API
3400 # support such as WordPress or Twitter-compatible APIs for a
3401 # blogging extension, etc)
3402 $tags['rsd'] = Html::element( 'link', [
3403 'rel' => 'EditURI',
3404 'type' => 'application/rsd+xml',
3405 // Output a protocol-relative URL here if $wgServer is protocol-relative.
3406 // Whether RSD accepts relative or protocol-relative URLs is completely
3407 // undocumented, though.
3408 'href' => wfExpandUrl( wfAppendQuery(
3409 wfScript( 'api' ),
3410 [ 'action' => 'rsd' ] ),
3412 ),
3413 ] );
3414 }
3415
3416 # Language variants
3417 if ( !$config->get( 'DisableLangConversion' ) ) {
3418 $lang = $this->getTitle()->getPageLanguage();
3419 if ( $lang->hasVariants() ) {
3420 $variants = $lang->getVariants();
3421 foreach ( $variants as $variant ) {
3422 $tags["variant-$variant"] = Html::element( 'link', [
3423 'rel' => 'alternate',
3424 'hreflang' => wfBCP47( $variant ),
3425 'href' => $this->getTitle()->getLocalURL(
3426 [ 'variant' => $variant ] )
3427 ]
3428 );
3429 }
3430 # x-default link per https://support.google.com/webmasters/answer/189077?hl=en
3431 $tags["variant-x-default"] = Html::element( 'link', [
3432 'rel' => 'alternate',
3433 'hreflang' => 'x-default',
3434 'href' => $this->getTitle()->getLocalURL() ] );
3435 }
3436 }
3437
3438 # Copyright
3439 if ( $this->copyrightUrl !== null ) {
3440 $copyright = $this->copyrightUrl;
3441 } else {
3442 $copyright = '';
3443 if ( $config->get( 'RightsPage' ) ) {
3444 $copy = Title::newFromText( $config->get( 'RightsPage' ) );
3445
3446 if ( $copy ) {
3447 $copyright = $copy->getLocalURL();
3448 }
3449 }
3450
3451 if ( !$copyright && $config->get( 'RightsUrl' ) ) {
3452 $copyright = $config->get( 'RightsUrl' );
3453 }
3454 }
3455
3456 if ( $copyright ) {
3457 $tags['copyright'] = Html::element( 'link', [
3458 'rel' => 'copyright',
3459 'href' => $copyright ]
3460 );
3461 }
3462
3463 # Feeds
3464 if ( $config->get( 'Feed' ) ) {
3465 $feedLinks = [];
3466
3467 foreach ( $this->getSyndicationLinks() as $format => $link ) {
3468 # Use the page name for the title. In principle, this could
3469 # lead to issues with having the same name for different feeds
3470 # corresponding to the same page, but we can't avoid that at
3471 # this low a level.
3472
3473 $feedLinks[] = $this->feedLink(
3474 $format,
3475 $link,
3476 # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
3477 $this->msg(
3478 "page-{$format}-feed", $this->getTitle()->getPrefixedText()
3479 )->text()
3480 );
3481 }
3482
3483 # Recent changes feed should appear on every page (except recentchanges,
3484 # that would be redundant). Put it after the per-page feed to avoid
3485 # changing existing behavior. It's still available, probably via a
3486 # menu in your browser. Some sites might have a different feed they'd
3487 # like to promote instead of the RC feed (maybe like a "Recent New Articles"
3488 # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
3489 # If so, use it instead.
3490 $sitename = $config->get( 'Sitename' );
3491 if ( $config->get( 'OverrideSiteFeed' ) ) {
3492 foreach ( $config->get( 'OverrideSiteFeed' ) as $type => $feedUrl ) {
3493 // Note, this->feedLink escapes the url.
3494 $feedLinks[] = $this->feedLink(
3495 $type,
3496 $feedUrl,
3497 $this->msg( "site-{$type}-feed", $sitename )->text()
3498 );
3499 }
3500 } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
3501 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
3502 foreach ( $config->get( 'AdvertisedFeedTypes' ) as $format ) {
3503 $feedLinks[] = $this->feedLink(
3504 $format,
3505 $rctitle->getLocalURL( [ 'feed' => $format ] ),
3506 # For grep: 'site-rss-feed', 'site-atom-feed'
3507 $this->msg( "site-{$format}-feed", $sitename )->text()
3508 );
3509 }
3510 }
3511
3512 # Allow extensions to change the list pf feeds. This hook is primarily for changing,
3513 # manipulating or removing existing feed tags. If you want to add new feeds, you should
3514 # use OutputPage::addFeedLink() instead.
3515 Hooks::run( 'AfterBuildFeedLinks', [ &$feedLinks ] );
3516
3517 $tags += $feedLinks;
3518 }
3519
3520 # Canonical URL
3521 if ( $config->get( 'EnableCanonicalServerLink' ) ) {
3522 if ( $canonicalUrl !== false ) {
3523 $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL );
3524 } else {
3525 if ( $this->isArticleRelated() ) {
3526 // This affects all requests where "setArticleRelated" is true. This is
3527 // typically all requests that show content (query title, curid, oldid, diff),
3528 // and all wikipage actions (edit, delete, purge, info, history etc.).
3529 // It does not apply to File pages and Special pages.
3530 // 'history' and 'info' actions address page metadata rather than the page
3531 // content itself, so they may not be canonicalized to the view page url.
3532 // TODO: this ought to be better encapsulated in the Action class.
3533 $action = Action::getActionName( $this->getContext() );
3534 if ( in_array( $action, [ 'history', 'info' ] ) ) {
3535 $query = "action={$action}";
3536 } else {
3537 $query = '';
3538 }
3539 $canonicalUrl = $this->getTitle()->getCanonicalURL( $query );
3540 } else {
3541 $reqUrl = $this->getRequest()->getRequestURL();
3542 $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL );
3543 }
3544 }
3545 }
3546 if ( $canonicalUrl !== false ) {
3547 $tags[] = Html::element( 'link', [
3548 'rel' => 'canonical',
3549 'href' => $canonicalUrl
3550 ] );
3551 }
3552
3553 return $tags;
3554 }
3555
3561 public function getHeadLinks() {
3562 wfDeprecated( __METHOD__, '1.24' );
3563 return implode( "\n", $this->getHeadLinksArray() );
3564 }
3565
3574 private function feedLink( $type, $url, $text ) {
3575 return Html::element( 'link', [
3576 'rel' => 'alternate',
3577 'type' => "application/$type+xml",
3578 'title' => $text,
3579 'href' => $url ]
3580 );
3581 }
3582
3592 public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
3593 $options = [];
3594 if ( $media ) {
3595 $options['media'] = $media;
3596 }
3597 if ( $condition ) {
3598 $options['condition'] = $condition;
3599 }
3600 if ( $dir ) {
3601 $options['dir'] = $dir;
3602 }
3603 $this->styles[$style] = $options;
3604 }
3605
3613 public function addInlineStyle( $style_css, $flip = 'noflip' ) {
3614 if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
3615 # If wanted, and the interface is right-to-left, flip the CSS
3616 $style_css = CSSJanus::transform( $style_css, true, false );
3617 }
3618 $this->mInlineStyles .= Html::inlineStyle( $style_css );
3619 }
3620
3626 protected function buildExemptModules() {
3628
3629 $chunks = [];
3630 // Things that go after the ResourceLoaderDynamicStyles marker
3631 $append = [];
3632
3633 // Exempt 'user' styles module (may need 'excludepages' for live preview)
3634 if ( $this->isUserCssPreview() ) {
3635 $append[] = $this->makeResourceLoaderLink(
3636 'user.styles',
3638 [ 'excludepage' => $this->getTitle()->getPrefixedDBkey() ]
3639 );
3640
3641 // Load the previewed CSS. Janus it if needed.
3642 // User-supplied CSS is assumed to in the wiki's content language.
3643 $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' );
3644 if ( $this->getLanguage()->getDir() !== $wgContLang->getDir() ) {
3645 $previewedCSS = CSSJanus::transform( $previewedCSS, true, false );
3646 }
3647 $append[] = Html::inlineStyle( $previewedCSS );
3648 }
3649
3650 // We want site, private and user styles to override dynamically added styles from
3651 // general modules, but we want dynamically added styles to override statically added
3652 // style modules. So the order has to be:
3653 // - page style modules (formatted by ResourceLoaderClientHtml::getHeadHtml())
3654 // - dynamically loaded styles (added by mw.loader before ResourceLoaderDynamicStyles)
3655 // - ResourceLoaderDynamicStyles marker
3656 // - site/private/user styles
3657
3658 // Add legacy styles added through addStyle()/addInlineStyle() here
3659 $chunks[] = implode( '', $this->buildCssLinksArray() ) . $this->mInlineStyles;
3660
3661 $chunks[] = Html::element(
3662 'meta',
3663 [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ]
3664 );
3665
3666 foreach ( $this->rlExemptStyleModules as $group => $moduleNames ) {
3667 $chunks[] = $this->makeResourceLoaderLink( $moduleNames,
3669 );
3670 }
3671
3672 return self::combineWrappedStrings( array_merge( $chunks, $append ) );
3673 }
3674
3678 public function buildCssLinksArray() {
3679 $links = [];
3680
3681 // Add any extension CSS
3682 foreach ( $this->mExtStyles as $url ) {
3683 $this->addStyle( $url );
3684 }
3685 $this->mExtStyles = [];
3686
3687 foreach ( $this->styles as $file => $options ) {
3688 $link = $this->styleLink( $file, $options );
3689 if ( $link ) {
3690 $links[$file] = $link;
3691 }
3692 }
3693 return $links;
3694 }
3695
3703 protected function styleLink( $style, array $options ) {
3704 if ( isset( $options['dir'] ) ) {
3705 if ( $this->getLanguage()->getDir() != $options['dir'] ) {
3706 return '';
3707 }
3708 }
3709
3710 if ( isset( $options['media'] ) ) {
3711 $media = self::transformCssMedia( $options['media'] );
3712 if ( is_null( $media ) ) {
3713 return '';
3714 }
3715 } else {
3716 $media = 'all';
3717 }
3718
3719 if ( substr( $style, 0, 1 ) == '/' ||
3720 substr( $style, 0, 5 ) == 'http:' ||
3721 substr( $style, 0, 6 ) == 'https:' ) {
3722 $url = $style;
3723 } else {
3724 $config = $this->getConfig();
3725 $url = $config->get( 'StylePath' ) . '/' . $style . '?' .
3726 $config->get( 'StyleVersion' );
3727 }
3728
3729 $link = Html::linkedStyle( $url, $media );
3730
3731 if ( isset( $options['condition'] ) ) {
3732 $condition = htmlspecialchars( $options['condition'] );
3733 $link = "<!--[if $condition]>$link<![endif]-->";
3734 }
3735 return $link;
3736 }
3737
3759 public static function transformResourcePath( Config $config, $path ) {
3760 global $IP;
3761
3762 $localDir = $IP;
3763 $remotePathPrefix = $config->get( 'ResourceBasePath' );
3764 if ( $remotePathPrefix === '' ) {
3765 // The configured base path is required to be empty string for
3766 // wikis in the domain root
3767 $remotePath = '/';
3768 } else {
3769 $remotePath = $remotePathPrefix;
3770 }
3771 if ( strpos( $path, $remotePath ) !== 0 || substr( $path, 0, 2 ) === '//' ) {
3772 // - Path is outside wgResourceBasePath, ignore.
3773 // - Path is protocol-relative. Fixes T155310. Not supported by RelPath lib.
3774 return $path;
3775 }
3776 // For files in resources, extensions/ or skins/, ResourceBasePath is preferred here.
3777 // For other misc files in $IP, we'll fallback to that as well. There is, however, a fourth
3778 // supported dir/path pair in the configuration (wgUploadDirectory, wgUploadPath)
3779 // which is not expected to be in wgResourceBasePath on CDNs. (T155146)
3780 $uploadPath = $config->get( 'UploadPath' );
3781 if ( strpos( $path, $uploadPath ) === 0 ) {
3782 $localDir = $config->get( 'UploadDirectory' );
3783 $remotePathPrefix = $remotePath = $uploadPath;
3784 }
3785
3786 $path = RelPath\getRelativePath( $path, $remotePath );
3787 return self::transformFilePath( $remotePathPrefix, $localDir, $path );
3788 }
3789
3801 public static function transformFilePath( $remotePathPrefix, $localPath, $file ) {
3802 $hash = md5_file( "$localPath/$file" );
3803 if ( $hash === false ) {
3804 wfLogWarning( __METHOD__ . ": Failed to hash $localPath/$file" );
3805 $hash = '';
3806 }
3807 return "$remotePathPrefix/$file?" . substr( $hash, 0, 5 );
3808 }
3809
3817 public static function transformCssMedia( $media ) {
3819
3820 // https://www.w3.org/TR/css3-mediaqueries/#syntax
3821 $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
3822
3823 // Switch in on-screen display for media testing
3824 $switches = [
3825 'printable' => 'print',
3826 'handheld' => 'handheld',
3827 ];
3828 foreach ( $switches as $switch => $targetMedia ) {
3829 if ( $wgRequest->getBool( $switch ) ) {
3830 if ( $media == $targetMedia ) {
3831 $media = '';
3832 } elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
3833 /* This regex will not attempt to understand a comma-separated media_query_list
3834 *
3835 * Example supported values for $media:
3836 * 'screen', 'only screen', 'screen and (min-width: 982px)' ),
3837 * Example NOT supported value for $media:
3838 * '3d-glasses, screen, print and resolution > 90dpi'
3839 *
3840 * If it's a print request, we never want any kind of screen stylesheets
3841 * If it's a handheld request (currently the only other choice with a switch),
3842 * we don't want simple 'screen' but we might want screen queries that
3843 * have a max-width or something, so we'll pass all others on and let the
3844 * client do the query.
3845 */
3846 if ( $targetMedia == 'print' || $media == 'screen' ) {
3847 return null;
3848 }
3849 }
3850 }
3851 }
3852
3853 return $media;
3854 }
3855
3862 public function addWikiMsg( /*...*/ ) {
3863 $args = func_get_args();
3864 $name = array_shift( $args );
3865 $this->addWikiMsgArray( $name, $args );
3866 }
3867
3876 public function addWikiMsgArray( $name, $args ) {
3877 $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
3878 }
3879
3905 public function wrapWikiMsg( $wrap /*, ...*/ ) {
3906 $msgSpecs = func_get_args();
3907 array_shift( $msgSpecs );
3908 $msgSpecs = array_values( $msgSpecs );
3909 $s = $wrap;
3910 foreach ( $msgSpecs as $n => $spec ) {
3911 if ( is_array( $spec ) ) {
3912 $args = $spec;
3913 $name = array_shift( $args );
3914 if ( isset( $args['options'] ) ) {
3915 unset( $args['options'] );
3917 'Adding "options" to ' . __METHOD__ . ' is no longer supported',
3918 '1.20'
3919 );
3920 }
3921 } else {
3922 $args = [];
3923 $name = $spec;
3924 }
3925 $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
3926 }
3927 $this->addWikiText( $s );
3928 }
3929
3935 public function enableTOC( $flag = true ) {
3936 $this->mEnableTOC = $flag;
3937 }
3938
3943 public function isTOCEnabled() {
3944 return $this->mEnableTOC;
3945 }
3946
3952 public function enableSectionEditLinks( $flag = true ) {
3953 $this->mEnableSectionEditLinks = $flag;
3954 }
3955
3960 public function sectionEditLinksEnabled() {
3962 }
3963
3971 public static function setupOOUI( $skinName = '', $dir = 'ltr' ) {
3972 $themes = ExtensionRegistry::getInstance()->getAttribute( 'SkinOOUIThemes' );
3973 // Make keys (skin names) lowercase for case-insensitive matching.
3974 $themes = array_change_key_case( $themes, CASE_LOWER );
3975 $theme = isset( $themes[$skinName] ) ? $themes[$skinName] : 'MediaWiki';
3976 // For example, 'OOUI\MediaWikiTheme'.
3977 $themeClass = "OOUI\\{$theme}Theme";
3978 OOUI\Theme::setSingleton( new $themeClass() );
3979 OOUI\Element::setDefaultDir( $dir );
3980 }
3981
3988 public function enableOOUI() {
3990 strtolower( $this->getSkin()->getSkinName() ),
3991 $this->getLanguage()->getDir()
3992 );
3993 $this->addModuleStyles( [
3994 'oojs-ui-core.styles',
3995 'oojs-ui.styles.icons',
3996 'oojs-ui.styles.indicators',
3997 'oojs-ui.styles.textures',
3998 'mediawiki.widgets.styles',
3999 ] );
4000 }
4001
4007 protected function addLogoPreloadLinkHeaders() {
4008 $logo = ResourceLoaderSkinModule::getLogo( $this->getConfig() );
4009
4010 $tags = [];
4011 $logosPerDppx = [];
4012 $logos = [];
4013
4014 if ( !is_array( $logo ) ) {
4015 // No media queries required if we only have one variant
4016 $this->addLinkHeader( '<' . $logo . '>;rel=preload;as=image' );
4017 return;
4018 }
4019
4020 foreach ( $logo as $dppx => $src ) {
4021 // Keys are in this format: "1.5x"
4022 $dppx = substr( $dppx, 0, -1 );
4023 $logosPerDppx[$dppx] = $src;
4024 }
4025
4026 // Because PHP can't have floats as array keys
4027 uksort( $logosPerDppx, function ( $a , $b ) {
4028 $a = floatval( $a );
4029 $b = floatval( $b );
4030
4031 if ( $a == $b ) {
4032 return 0;
4033 }
4034 // Sort from smallest to largest (e.g. 1x, 1.5x, 2x)
4035 return ( $a < $b ) ? -1 : 1;
4036 } );
4037
4038 foreach ( $logosPerDppx as $dppx => $src ) {
4039 $logos[] = [ 'dppx' => $dppx, 'src' => $src ];
4040 }
4041
4042 $logosCount = count( $logos );
4043 // Logic must match ResourceLoaderSkinModule:
4044 // - 1x applies to resolution < 1.5dppx
4045 // - 1.5x applies to resolution >= 1.5dppx && < 2dppx
4046 // - 2x applies to resolution >= 2dppx
4047 // Note that min-resolution and max-resolution are both inclusive.
4048 for ( $i = 0; $i < $logosCount; $i++ ) {
4049 if ( $i === 0 ) {
4050 // Smallest dppx
4051 // min-resolution is ">=" (larger than or equal to)
4052 // "not min-resolution" is essentially "<"
4053 $media_query = 'not all and (min-resolution: ' . $logos[ 1 ]['dppx'] . 'dppx)';
4054 } elseif ( $i !== $logosCount - 1 ) {
4055 // In between
4056 // Media query expressions can only apply "not" to the entire expression
4057 // (e.g. can't express ">= 1.5 and not >= 2).
4058 // Workaround: Use <= 1.9999 in place of < 2.
4059 $upper_bound = floatval( $logos[ $i + 1 ]['dppx'] ) - 0.000001;
4060 $media_query = '(min-resolution: ' . $logos[ $i ]['dppx'] .
4061 'dppx) and (max-resolution: ' . $upper_bound . 'dppx)';
4062 } else {
4063 // Largest dppx
4064 $media_query = '(min-resolution: ' . $logos[ $i ]['dppx'] . 'dppx)';
4065 }
4066
4067 $this->addLinkHeader(
4068 '<' . $logos[$i]['src'] . '>;rel=preload;as=image;media=' . $media_query
4069 );
4070 }
4071 }
4072}
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:796
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:639
$IP
Definition WebStart.php:58
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:805
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
getSkin()
Get the Skin object.
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.
msg()
Get a Message object with context set Parameters are the same as wfMessage()
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:98
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.
addParserOutputNoText( $parserOutput)
Add a ParserOutput object, but without Html.
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 ...
versionRequired( $version)
Display an error page indicating that a given version of MediaWiki is required to use it.
static setupOOUI( $skinName='', $dir='ltr')
Helper function to setup the PHP implementation of OOUI to use in this request.
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>".
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.
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.
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=copyright.
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 should contain 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.
enableTOC( $flag=true)
Enables/disables TOC, doesn't override NOTOC
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.
static newFromContext(IContextSource $context)
Get a ParserOptions object from a IContextSource object.
static newFromAnon()
Get a ParserOptions object for an anonymous user.
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:34
getHtmlElementAttributes()
Return values for <html> element.
Definition Skin.php:418
getSkinName()
Definition Skin.php:137
addToBodyAttributes( $out, &$bodyAttrs)
This will be called by OutputPage::headElement when it is creating the "<body>" tag,...
Definition Skin.php:434
getPageClasses( $title)
TODO: document.
Definition Skin.php:390
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 Xml.php:847
$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:63
const PROTO_CANONICAL
Definition Defines.php:221
const PROTO_CURRENT
Definition Defines.php:220
const NS_SPECIAL
Definition Defines.php:51
const PROTO_RELATIVE
Definition Defines.php:219
const NS_CATEGORY
Definition Defines.php:76
the array() calling protocol came about after MediaWiki 1.4rc1.
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition hooks.txt:2179
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context $parserOutput
Definition hooks.txt:1096
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:249
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:183
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping $template
Definition hooks.txt:831
either a plain
Definition hooks.txt:2007
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition hooks.txt:1102
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition hooks.txt:865
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books $tag
Definition hooks.txt:1033
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:964
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt;div ...>$1&lt;/div>"). - flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException':Called before an exception(or PHP error) is logged. This is meant for integration with external error aggregation services
error also a ContextSource 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:2735
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition hooks.txt:2604
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition hooks.txt:2723
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition hooks.txt:2937
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
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:1975
this hook is for auditing only $response
Definition hooks.txt:783
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition hooks.txt:1601
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:2007
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:1252
returning false will NOT prevent logging $e
Definition hooks.txt:2127
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