MediaWiki REL1_28
OutputPage.php
Go to the documentation of this file.
1<?php
25use WrappedString\WrappedString;
26use WrappedString\WrappedStringList;
27
45 protected $mMetatags = [];
46
48 protected $mLinktags = [];
49
51 protected $mCanonicalUrl = false;
52
57 protected $mExtStyles = [];
58
62 public $mPagetitle = '';
63
68 public $mBodytext = '';
69
71 private $mHTMLtitle = '';
72
77 private $mIsarticle = false;
78
80 private $mIsArticleRelated = true;
81
86 private $mPrintable = false;
87
92 private $mSubtitle = [];
93
95 public $mRedirect = '';
96
98 protected $mStatusCode;
99
104 protected $mLastModified = '';
105
107 protected $mCategoryLinks = [];
108
110 protected $mCategories = [];
111
113 protected $mIndicators = [];
114
116 private $mLanguageLinks = [];
117
124 private $mScripts = '';
125
127 protected $mInlineStyles = '';
128
133 public $mPageLinkTitle = '';
134
136 protected $mHeadItems = [];
137
139 protected $mModules = [];
140
142 protected $mModuleScripts = [];
143
145 protected $mModuleStyles = [];
146
149
151 private $rlClient;
152
155
158
161
163 protected $mJsConfigVars = [];
164
166 protected $mTemplateIds = [];
167
169 protected $mImageTimeKeys = [];
170
172 public $mRedirectCode = '';
173
174 protected $mFeedLinksAppendQuery = null;
175
181 protected $mAllowedModules = [
183 ];
184
186 protected $mDoNothing = false;
187
188 // Parser related.
189
191 protected $mContainsNewMagic = 0;
192
197 protected $mParserOptions = null;
198
204 private $mFeedLinks = [];
205
206 // Gwicke work on squid caching? Roughly from 2003.
207 protected $mEnableClientCache = true;
208
210 private $mArticleBodyOnly = false;
211
213 protected $mNewSectionLink = false;
214
216 protected $mHideNewSectionLink = false;
217
223 public $mNoGallery = false;
224
227
229 protected $mCdnMaxage = 0;
231 protected $mCdnMaxageLimit = INF;
232
238 protected $mPreventClickjacking = true;
239
241 private $mRevisionId = null;
242
244 private $mRevisionTimestamp = null;
245
247 protected $mFileVersion = null;
248
257 protected $styles = [];
258
259 private $mIndexPolicy = 'index';
260 private $mFollowPolicy = 'follow';
261 private $mVaryHeader = [
262 'Accept-Encoding' => [ 'match=gzip' ],
263 ];
264
271 private $mRedirectedFrom = null;
272
276 private $mProperties = [];
277
281 private $mTarget = null;
282
286 private $mEnableTOC = true;
287
292
297
305 if ( $context === null ) {
306 # Extensions should use `new RequestContext` instead of `new OutputPage` now.
307 wfDeprecated( __METHOD__, '1.18' );
308 } else {
309 $this->setContext( $context );
310 }
311 }
312
319 public function redirect( $url, $responsecode = '302' ) {
320 # Strip newlines as a paranoia check for header injection in PHP<5.1.2
321 $this->mRedirect = str_replace( "\n", '', $url );
322 $this->mRedirectCode = $responsecode;
323 }
324
330 public function getRedirect() {
331 return $this->mRedirect;
332 }
333
342 public function setCopyrightUrl( $url ) {
343 $this->copyrightUrl = $url;
344 }
345
351 public function setStatusCode( $statusCode ) {
352 $this->mStatusCode = $statusCode;
353 }
354
362 function addMeta( $name, $val ) {
363 array_push( $this->mMetatags, [ $name, $val ] );
364 }
365
372 public function getMetaTags() {
373 return $this->mMetatags;
374 }
375
383 function addLink( array $linkarr ) {
384 array_push( $this->mLinktags, $linkarr );
385 }
386
393 public function getLinkTags() {
394 return $this->mLinktags;
395 }
396
404 function addMetadataLink( array $linkarr ) {
405 $linkarr['rel'] = $this->getMetadataAttribute();
406 $this->addLink( $linkarr );
407 }
408
414 function setCanonicalUrl( $url ) {
415 $this->mCanonicalUrl = $url;
416 }
417
425 public function getCanonicalUrl() {
427 }
428
434 public function getMetadataAttribute() {
435 # note: buggy CC software only reads first "meta" link
436 static $haveMeta = false;
437 if ( $haveMeta ) {
438 return 'alternate meta';
439 } else {
440 $haveMeta = true;
441 return 'meta';
442 }
443 }
444
452 function addScript( $script ) {
453 $this->mScripts .= $script;
454 }
455
465 public function addExtensionStyle( $url ) {
466 wfDeprecated( __METHOD__, '1.27' );
467 array_push( $this->mExtStyles, $url );
468 }
469
476 function getExtStyle() {
477 wfDeprecated( __METHOD__, '1.27' );
478 return $this->mExtStyles;
479 }
480
489 public function addScriptFile( $file, $version = null ) {
490 // See if $file parameter is an absolute URL or begins with a slash
491 if ( substr( $file, 0, 1 ) == '/' || preg_match( '#^[a-z]*://#i', $file ) ) {
492 $path = $file;
493 } else {
494 $path = $this->getConfig()->get( 'StylePath' ) . "/common/{$file}";
495 }
496 if ( is_null( $version ) ) {
497 $version = $this->getConfig()->get( 'StyleVersion' );
498 }
499 $this->addScript( Html::linkedScript( wfAppendQuery( $path, $version ) ) );
500 }
501
508 public function addInlineScript( $script ) {
509 $this->mScripts .= Html::inlineScript( $script );
510 }
511
520 protected function filterModules( array $modules, $position = null,
522 ) {
524 $filteredModules = [];
525 foreach ( $modules as $val ) {
526 $module = $resourceLoader->getModule( $val );
527 if ( $module instanceof ResourceLoaderModule
528 && $module->getOrigin() <= $this->getAllowedModules( $type )
529 && ( is_null( $position ) || $module->getPosition() == $position )
530 && ( !$this->mTarget || in_array( $this->mTarget, $module->getTargets() ) )
531 ) {
532 $filteredModules[] = $val;
533 }
534 }
535 return $filteredModules;
536 }
537
546 public function getModules( $filter = false, $position = null, $param = 'mModules',
548 ) {
549 $modules = array_values( array_unique( $this->$param ) );
550 return $filter
551 ? $this->filterModules( $modules, $position, $type )
552 : $modules;
553 }
554
562 public function addModules( $modules ) {
563 $this->mModules = array_merge( $this->mModules, (array)$modules );
564 }
565
573 public function getModuleScripts( $filter = false, $position = null ) {
574 return $this->getModules( $filter, $position, 'mModuleScripts',
576 );
577 }
578
586 public function addModuleScripts( $modules ) {
587 $this->mModuleScripts = array_merge( $this->mModuleScripts, (array)$modules );
588 }
589
597 public function getModuleStyles( $filter = false, $position = null ) {
598 return $this->getModules( $filter, $position, 'mModuleStyles',
600 );
601 }
602
612 public function addModuleStyles( $modules ) {
613 $this->mModuleStyles = array_merge( $this->mModuleStyles, (array)$modules );
614 }
615
619 public function getTarget() {
620 return $this->mTarget;
621 }
622
628 public function setTarget( $target ) {
629 $this->mTarget = $target;
630 }
631
637 function getHeadItemsArray() {
638 return $this->mHeadItems;
639 }
640
653 public function addHeadItem( $name, $value ) {
654 $this->mHeadItems[$name] = $value;
655 }
656
663 public function addHeadItems( $values ) {
664 $this->mHeadItems = array_merge( $this->mHeadItems, (array)$values );
665 }
666
673 public function hasHeadItem( $name ) {
674 return isset( $this->mHeadItems[$name] );
675 }
676
681 public function setETag( $tag ) {
682 }
683
691 public function setArticleBodyOnly( $only ) {
692 $this->mArticleBodyOnly = $only;
693 }
694
700 public function getArticleBodyOnly() {
702 }
703
711 public function setProperty( $name, $value ) {
712 $this->mProperties[$name] = $value;
713 }
714
722 public function getProperty( $name ) {
723 if ( isset( $this->mProperties[$name] ) ) {
724 return $this->mProperties[$name];
725 } else {
726 return null;
727 }
728 }
729
741 public function checkLastModified( $timestamp ) {
742 if ( !$timestamp || $timestamp == '19700101000000' ) {
743 wfDebug( __METHOD__ . ": CACHE DISABLED, NO TIMESTAMP\n" );
744 return false;
745 }
746 $config = $this->getConfig();
747 if ( !$config->get( 'CachePages' ) ) {
748 wfDebug( __METHOD__ . ": CACHE DISABLED\n" );
749 return false;
750 }
751
753 $modifiedTimes = [
754 'page' => $timestamp,
755 'user' => $this->getUser()->getTouched(),
756 'epoch' => $config->get( 'CacheEpoch' )
757 ];
758 if ( $config->get( 'UseSquid' ) ) {
759 // bug 44570: the core page itself may not change, but resources might
760 $modifiedTimes['sepoch'] = wfTimestamp( TS_MW, time() - $config->get( 'SquidMaxage' ) );
761 }
762 Hooks::run( 'OutputPageCheckLastModified', [ &$modifiedTimes, $this ] );
763
764 $maxModified = max( $modifiedTimes );
765 $this->mLastModified = wfTimestamp( TS_RFC2822, $maxModified );
766
767 $clientHeader = $this->getRequest()->getHeader( 'If-Modified-Since' );
768 if ( $clientHeader === false ) {
769 wfDebug( __METHOD__ . ": client did not send If-Modified-Since header", 'private' );
770 return false;
771 }
772
773 # IE sends sizes after the date like this:
774 # Wed, 20 Aug 2003 06:51:19 GMT; length=5202
775 # this breaks strtotime().
776 $clientHeader = preg_replace( '/;.*$/', '', $clientHeader );
777
778 MediaWiki\suppressWarnings(); // E_STRICT system time bitching
779 $clientHeaderTime = strtotime( $clientHeader );
780 MediaWiki\restoreWarnings();
781 if ( !$clientHeaderTime ) {
782 wfDebug( __METHOD__
783 . ": unable to parse the client's If-Modified-Since header: $clientHeader\n" );
784 return false;
785 }
786 $clientHeaderTime = wfTimestamp( TS_MW, $clientHeaderTime );
787
788 # Make debug info
789 $info = '';
790 foreach ( $modifiedTimes as $name => $value ) {
791 if ( $info !== '' ) {
792 $info .= ', ';
793 }
794 $info .= "$name=" . wfTimestamp( TS_ISO_8601, $value );
795 }
796
797 wfDebug( __METHOD__ . ": client sent If-Modified-Since: " .
798 wfTimestamp( TS_ISO_8601, $clientHeaderTime ), 'private' );
799 wfDebug( __METHOD__ . ": effective Last-Modified: " .
800 wfTimestamp( TS_ISO_8601, $maxModified ), 'private' );
801 if ( $clientHeaderTime < $maxModified ) {
802 wfDebug( __METHOD__ . ": STALE, $info", 'private' );
803 return false;
804 }
805
806 # Not modified
807 # Give a 304 Not Modified response code and disable body output
808 wfDebug( __METHOD__ . ": NOT MODIFIED, $info", 'private' );
809 ini_set( 'zlib.output_compression', 0 );
810 $this->getRequest()->response()->statusHeader( 304 );
811 $this->sendCacheControl();
812 $this->disable();
813
814 // Don't output a compressed blob when using ob_gzhandler;
815 // it's technically against HTTP spec and seems to confuse
816 // Firefox when the response gets split over two packets.
818
819 return true;
820 }
821
828 public function setLastModified( $timestamp ) {
829 $this->mLastModified = wfTimestamp( TS_RFC2822, $timestamp );
830 }
831
840 public function setRobotPolicy( $policy ) {
841 $policy = Article::formatRobotPolicy( $policy );
842
843 if ( isset( $policy['index'] ) ) {
844 $this->setIndexPolicy( $policy['index'] );
845 }
846 if ( isset( $policy['follow'] ) ) {
847 $this->setFollowPolicy( $policy['follow'] );
848 }
849 }
850
858 public function setIndexPolicy( $policy ) {
859 $policy = trim( $policy );
860 if ( in_array( $policy, [ 'index', 'noindex' ] ) ) {
861 $this->mIndexPolicy = $policy;
862 }
863 }
864
872 public function setFollowPolicy( $policy ) {
873 $policy = trim( $policy );
874 if ( in_array( $policy, [ 'follow', 'nofollow' ] ) ) {
875 $this->mFollowPolicy = $policy;
876 }
877 }
878
885 public function setPageTitleActionText( $text ) {
886 $this->mPageTitleActionText = $text;
887 }
888
894 public function getPageTitleActionText() {
896 }
897
904 public function setHTMLTitle( $name ) {
905 if ( $name instanceof Message ) {
906 $this->mHTMLtitle = $name->setContext( $this->getContext() )->text();
907 } else {
908 $this->mHTMLtitle = $name;
909 }
910 }
911
917 public function getHTMLTitle() {
918 return $this->mHTMLtitle;
919 }
920
926 public function setRedirectedFrom( $t ) {
927 $this->mRedirectedFrom = $t;
928 }
929
940 public function setPageTitle( $name ) {
941 if ( $name instanceof Message ) {
942 $name = $name->setContext( $this->getContext() )->text();
943 }
944
945 # change "<script>foo&bar</script>" to "&lt;script&gt;foo&amp;bar&lt;/script&gt;"
946 # but leave "<i>foobar</i>" alone
947 $nameWithTags = Sanitizer::normalizeCharReferences( Sanitizer::removeHTMLtags( $name ) );
948 $this->mPagetitle = $nameWithTags;
949
950 # change "<i>foo&amp;bar</i>" to "foo&bar"
951 $this->setHTMLTitle(
952 $this->msg( 'pagetitle' )->rawParams( Sanitizer::stripAllTags( $nameWithTags ) )
953 ->inContentLanguage()
954 );
955 }
956
962 public function getPageTitle() {
963 return $this->mPagetitle;
964 }
965
971 public function setTitle( Title $t ) {
972 $this->getContext()->setTitle( $t );
973 }
974
980 public function setSubtitle( $str ) {
981 $this->clearSubtitle();
982 $this->addSubtitle( $str );
983 }
984
990 public function addSubtitle( $str ) {
991 if ( $str instanceof Message ) {
992 $this->mSubtitle[] = $str->setContext( $this->getContext() )->parse();
993 } else {
994 $this->mSubtitle[] = $str;
995 }
996 }
997
1006 public static function buildBacklinkSubtitle( Title $title, $query = [] ) {
1007 if ( $title->isRedirect() ) {
1008 $query['redirect'] = 'no';
1009 }
1010 return wfMessage( 'backlinksubtitle' )
1011 ->rawParams( Linker::link( $title, null, [], $query ) );
1012 }
1013
1020 public function addBacklinkSubtitle( Title $title, $query = [] ) {
1021 $this->addSubtitle( self::buildBacklinkSubtitle( $title, $query ) );
1022 }
1023
1027 public function clearSubtitle() {
1028 $this->mSubtitle = [];
1029 }
1030
1036 public function getSubtitle() {
1037 return implode( "<br />\n\t\t\t\t", $this->mSubtitle );
1038 }
1039
1044 public function setPrintable() {
1045 $this->mPrintable = true;
1046 }
1047
1053 public function isPrintable() {
1054 return $this->mPrintable;
1055 }
1056
1060 public function disable() {
1061 $this->mDoNothing = true;
1062 }
1063
1069 public function isDisabled() {
1070 return $this->mDoNothing;
1071 }
1072
1078 public function showNewSectionLink() {
1080 }
1081
1087 public function forceHideNewSectionLink() {
1089 }
1090
1099 public function setSyndicated( $show = true ) {
1100 if ( $show ) {
1101 $this->setFeedAppendQuery( false );
1102 } else {
1103 $this->mFeedLinks = [];
1104 }
1105 }
1106
1116 public function setFeedAppendQuery( $val ) {
1117 $this->mFeedLinks = [];
1118
1119 foreach ( $this->getConfig()->get( 'AdvertisedFeedTypes' ) as $type ) {
1120 $query = "feed=$type";
1121 if ( is_string( $val ) ) {
1122 $query .= '&' . $val;
1123 }
1124 $this->mFeedLinks[$type] = $this->getTitle()->getLocalURL( $query );
1125 }
1126 }
1127
1134 public function addFeedLink( $format, $href ) {
1135 if ( in_array( $format, $this->getConfig()->get( 'AdvertisedFeedTypes' ) ) ) {
1136 $this->mFeedLinks[$format] = $href;
1137 }
1138 }
1139
1144 public function isSyndicated() {
1145 return count( $this->mFeedLinks ) > 0;
1146 }
1147
1152 public function getSyndicationLinks() {
1153 return $this->mFeedLinks;
1154 }
1155
1161 public function getFeedAppendQuery() {
1163 }
1164
1172 public function setArticleFlag( $v ) {
1173 $this->mIsarticle = $v;
1174 if ( $v ) {
1175 $this->mIsArticleRelated = $v;
1176 }
1177 }
1178
1185 public function isArticle() {
1186 return $this->mIsarticle;
1187 }
1188
1195 public function setArticleRelated( $v ) {
1196 $this->mIsArticleRelated = $v;
1197 if ( !$v ) {
1198 $this->mIsarticle = false;
1199 }
1200 }
1201
1207 public function isArticleRelated() {
1209 }
1210
1217 public function addLanguageLinks( array $newLinkArray ) {
1218 $this->mLanguageLinks += $newLinkArray;
1219 }
1220
1227 public function setLanguageLinks( array $newLinkArray ) {
1228 $this->mLanguageLinks = $newLinkArray;
1229 }
1230
1236 public function getLanguageLinks() {
1237 return $this->mLanguageLinks;
1238 }
1239
1245 public function addCategoryLinks( array $categories ) {
1247
1248 if ( !is_array( $categories ) || count( $categories ) == 0 ) {
1249 return;
1250 }
1251
1252 # Add the links to a LinkBatch
1253 $arr = [ NS_CATEGORY => $categories ];
1254 $lb = new LinkBatch;
1255 $lb->setArray( $arr );
1256
1257 # Fetch existence plus the hiddencat property
1258 $dbr = wfGetDB( DB_REPLICA );
1259 $fields = array_merge(
1260 LinkCache::getSelectFields(),
1261 [ 'page_namespace', 'page_title', 'pp_value' ]
1262 );
1263
1264 $res = $dbr->select( [ 'page', 'page_props' ],
1265 $fields,
1266 $lb->constructSet( 'page', $dbr ),
1267 __METHOD__,
1268 [],
1269 [ 'page_props' => [ 'LEFT JOIN', [
1270 'pp_propname' => 'hiddencat',
1271 'pp_page = page_id'
1272 ] ] ]
1273 );
1274
1275 # Add the results to the link cache
1276 $lb->addResultToCache( LinkCache::singleton(), $res );
1277
1278 # Set all the values to 'normal'.
1279 $categories = array_fill_keys( array_keys( $categories ), 'normal' );
1280
1281 # Mark hidden categories
1282 foreach ( $res as $row ) {
1283 if ( isset( $row->pp_value ) ) {
1284 $categories[$row->page_title] = 'hidden';
1285 }
1286 }
1287
1288 # Add the remaining categories to the skin
1289 if ( Hooks::run(
1290 'OutputPageMakeCategoryLinks',
1291 [ &$this, $categories, &$this->mCategoryLinks ] )
1292 ) {
1293 foreach ( $categories as $category => $type ) {
1294 // array keys will cast numeric category names to ints, so cast back to string
1295 $category = (string)$category;
1296 $origcategory = $category;
1297 $title = Title::makeTitleSafe( NS_CATEGORY, $category );
1298 if ( !$title ) {
1299 continue;
1300 }
1301 $wgContLang->findVariantLink( $category, $title, true );
1302 if ( $category != $origcategory && array_key_exists( $category, $categories ) ) {
1303 continue;
1304 }
1305 $text = $wgContLang->convertHtml( $title->getText() );
1306 $this->mCategories[] = $title->getText();
1307 $this->mCategoryLinks[$type][] = Linker::link( $title, $text );
1308 }
1309 }
1310 }
1311
1317 public function setCategoryLinks( array $categories ) {
1318 $this->mCategoryLinks = [];
1319 $this->addCategoryLinks( $categories );
1320 }
1321
1330 public function getCategoryLinks() {
1331 return $this->mCategoryLinks;
1332 }
1333
1339 public function getCategories() {
1340 return $this->mCategories;
1341 }
1342
1352 public function setIndicators( array $indicators ) {
1353 $this->mIndicators = $indicators + $this->mIndicators;
1354 // Keep ordered by key
1355 ksort( $this->mIndicators );
1356 }
1357
1366 public function getIndicators() {
1367 return $this->mIndicators;
1368 }
1369
1378 public function addHelpLink( $to, $overrideBaseUrl = false ) {
1379 $this->addModuleStyles( 'mediawiki.helplink' );
1380 $text = $this->msg( 'helppage-top-gethelp' )->escaped();
1381
1382 if ( $overrideBaseUrl ) {
1383 $helpUrl = $to;
1384 } else {
1385 $toUrlencoded = wfUrlencode( str_replace( ' ', '_', $to ) );
1386 $helpUrl = "//www.mediawiki.org/wiki/Special:MyLanguage/$toUrlencoded";
1387 }
1388
1389 $link = Html::rawElement(
1390 'a',
1391 [
1392 'href' => $helpUrl,
1393 'target' => '_blank',
1394 'class' => 'mw-helplink',
1395 ],
1396 $text
1397 );
1398
1399 $this->setIndicators( [ 'mw-helplink' => $link ] );
1400 }
1401
1410 public function disallowUserJs() {
1411 $this->reduceAllowedModules(
1414 );
1415
1416 // Site-wide styles are controlled by a config setting, see bug 71621
1417 // for background on why. User styles are never allowed.
1418 if ( $this->getConfig()->get( 'AllowSiteCSSOnRestrictedPages' ) ) {
1420 } else {
1422 }
1423 $this->reduceAllowedModules(
1425 $styleOrigin
1426 );
1427 }
1428
1435 public function getAllowedModules( $type ) {
1437 return min( array_values( $this->mAllowedModules ) );
1438 } else {
1439 return isset( $this->mAllowedModules[$type] )
1440 ? $this->mAllowedModules[$type]
1442 }
1443 }
1444
1454 public function reduceAllowedModules( $type, $level ) {
1455 $this->mAllowedModules[$type] = min( $this->getAllowedModules( $type ), $level );
1456 }
1457
1463 public function prependHTML( $text ) {
1464 $this->mBodytext = $text . $this->mBodytext;
1465 }
1466
1472 public function addHTML( $text ) {
1473 $this->mBodytext .= $text;
1474 }
1475
1485 public function addElement( $element, array $attribs = [], $contents = '' ) {
1486 $this->addHTML( Html::element( $element, $attribs, $contents ) );
1487 }
1488
1492 public function clearHTML() {
1493 $this->mBodytext = '';
1494 }
1495
1501 public function getHTML() {
1502 return $this->mBodytext;
1503 }
1504
1512 public function parserOptions( $options = null ) {
1513 if ( $options !== null && !empty( $options->isBogus ) ) {
1514 // Someone is trying to set a bogus pre-$wgUser PO. Check if it has
1515 // been changed somehow, and keep it if so.
1516 $anonPO = ParserOptions::newFromAnon();
1517 $anonPO->setEditSection( false );
1518 $anonPO->setAllowUnsafeRawHtml( false );
1519 if ( !$options->matches( $anonPO ) ) {
1520 wfLogWarning( __METHOD__ . ': Setting a changed bogus ParserOptions: ' . wfGetAllCallers( 5 ) );
1521 $options->isBogus = false;
1522 }
1523 }
1524
1525 if ( !$this->mParserOptions ) {
1526 if ( !$this->getContext()->getUser()->isSafeToLoad() ) {
1527 // $wgUser isn't unstubbable yet, so don't try to get a
1528 // ParserOptions for it. And don't cache this ParserOptions
1529 // either.
1531 $po->setEditSection( false );
1532 $po->setAllowUnsafeRawHtml( false );
1533 $po->isBogus = true;
1534 if ( $options !== null ) {
1535 $this->mParserOptions = empty( $options->isBogus ) ? $options : null;
1536 }
1537 return $po;
1538 }
1539
1540 $this->mParserOptions = ParserOptions::newFromContext( $this->getContext() );
1541 $this->mParserOptions->setEditSection( false );
1542 $this->mParserOptions->setAllowUnsafeRawHtml( false );
1543 }
1544
1545 if ( $options !== null && !empty( $options->isBogus ) ) {
1546 // They're trying to restore the bogus pre-$wgUser PO. Do the right
1547 // thing.
1548 return wfSetVar( $this->mParserOptions, null, true );
1549 } else {
1550 return wfSetVar( $this->mParserOptions, $options );
1551 }
1552 }
1553
1561 public function setRevisionId( $revid ) {
1562 $val = is_null( $revid ) ? null : intval( $revid );
1563 return wfSetVar( $this->mRevisionId, $val );
1564 }
1565
1571 public function getRevisionId() {
1572 return $this->mRevisionId;
1573 }
1574
1583 return wfSetVar( $this->mRevisionTimestamp, $timestamp );
1584 }
1585
1592 public function getRevisionTimestamp() {
1594 }
1595
1602 public function setFileVersion( $file ) {
1603 $val = null;
1604 if ( $file instanceof File && $file->exists() ) {
1605 $val = [ 'time' => $file->getTimestamp(), 'sha1' => $file->getSha1() ];
1606 }
1607 return wfSetVar( $this->mFileVersion, $val, true );
1608 }
1609
1615 public function getFileVersion() {
1616 return $this->mFileVersion;
1617 }
1618
1625 public function getTemplateIds() {
1626 return $this->mTemplateIds;
1627 }
1628
1635 public function getFileSearchOptions() {
1636 return $this->mImageTimeKeys;
1637 }
1638
1648 public function addWikiText( $text, $linestart = true, $interface = true ) {
1649 $title = $this->getTitle(); // Work around E_STRICT
1650 if ( !$title ) {
1651 throw new MWException( 'Title is null' );
1652 }
1653 $this->addWikiTextTitle( $text, $title, $linestart, /*tidy*/false, $interface );
1654 }
1655
1663 public function addWikiTextWithTitle( $text, &$title, $linestart = true ) {
1664 $this->addWikiTextTitle( $text, $title, $linestart );
1665 }
1666
1674 function addWikiTextTitleTidy( $text, &$title, $linestart = true ) {
1675 $this->addWikiTextTitle( $text, $title, $linestart, true );
1676 }
1677
1684 public function addWikiTextTidy( $text, $linestart = true ) {
1685 $title = $this->getTitle();
1686 $this->addWikiTextTitleTidy( $text, $title, $linestart );
1687 }
1688
1699 public function addWikiTextTitle( $text, Title $title, $linestart,
1700 $tidy = false, $interface = false
1701 ) {
1703
1704 $popts = $this->parserOptions();
1705 $oldTidy = $popts->setTidy( $tidy );
1706 $popts->setInterfaceMessage( (bool)$interface );
1707
1708 $parserOutput = $wgParser->getFreshParser()->parse(
1709 $text, $title, $popts,
1710 $linestart, true, $this->mRevisionId
1711 );
1712
1713 $popts->setTidy( $oldTidy );
1714
1716
1717 }
1718
1726 wfDeprecated( __METHOD__, '1.24' );
1728 }
1729
1739 $this->mLanguageLinks += $parserOutput->getLanguageLinks();
1740 $this->addCategoryLinks( $parserOutput->getCategories() );
1741 $this->setIndicators( $parserOutput->getIndicators() );
1742 $this->mNewSectionLink = $parserOutput->getNewSection();
1743 $this->mHideNewSectionLink = $parserOutput->getHideNewSection();
1744
1745 if ( !$parserOutput->isCacheable() ) {
1746 $this->enableClientCache( false );
1747 }
1748 $this->mNoGallery = $parserOutput->getNoGallery();
1749 $this->mHeadItems = array_merge( $this->mHeadItems, $parserOutput->getHeadItems() );
1750 $this->addModules( $parserOutput->getModules() );
1751 $this->addModuleScripts( $parserOutput->getModuleScripts() );
1752 $this->addModuleStyles( $parserOutput->getModuleStyles() );
1753 $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1754 $this->mPreventClickjacking = $this->mPreventClickjacking
1755 || $parserOutput->preventClickjacking();
1756
1757 // Template versioning...
1758 foreach ( (array)$parserOutput->getTemplateIds() as $ns => $dbks ) {
1759 if ( isset( $this->mTemplateIds[$ns] ) ) {
1760 $this->mTemplateIds[$ns] = $dbks + $this->mTemplateIds[$ns];
1761 } else {
1762 $this->mTemplateIds[$ns] = $dbks;
1763 }
1764 }
1765 // File versioning...
1766 foreach ( (array)$parserOutput->getFileSearchOptions() as $dbk => $data ) {
1767 $this->mImageTimeKeys[$dbk] = $data;
1768 }
1769
1770 // Hooks registered in the object
1771 $parserOutputHooks = $this->getConfig()->get( 'ParserOutputHooks' );
1772 foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
1773 list( $hookName, $data ) = $hookInfo;
1774 if ( isset( $parserOutputHooks[$hookName] ) ) {
1775 call_user_func( $parserOutputHooks[$hookName], $this, $parserOutput, $data );
1776 }
1777 }
1778
1779 // enable OOUI if requested via ParserOutput
1780 if ( $parserOutput->getEnableOOUI() ) {
1781 $this->enableOOUI();
1782 }
1783
1784 // Link flags are ignored for now, but may in the future be
1785 // used to mark individual language links.
1786 $linkFlags = [];
1787 Hooks::run( 'LanguageLinks', [ $this->getTitle(), &$this->mLanguageLinks, &$linkFlags ] );
1788 Hooks::run( 'OutputPageParserOutput', [ &$this, $parserOutput ] );
1789 }
1790
1800
1801 $this->addModules( $parserOutput->getModules() );
1802 $this->addModuleScripts( $parserOutput->getModuleScripts() );
1803 $this->addModuleStyles( $parserOutput->getModuleStyles() );
1804
1805 $this->addJsConfigVars( $parserOutput->getJsConfigVars() );
1806 }
1807
1815 $text = $parserOutput->getText();
1816 Hooks::run( 'OutputPageBeforeHTML', [ &$this, &$text ] );
1817 $this->addHTML( $text );
1818 }
1819
1827 $parserOutput->setTOCEnabled( $this->mEnableTOC );
1828
1829 // Touch section edit links only if not previously disabled
1830 if ( $parserOutput->getEditSectionTokens() ) {
1831 $parserOutput->setEditSectionTokens( $this->mEnableSectionEditLinks );
1832 }
1833
1835 }
1836
1842 public function addTemplate( &$template ) {
1843 $this->addHTML( $template->getHTML() );
1844 }
1845
1858 public function parse( $text, $linestart = true, $interface = false, $language = null ) {
1860
1861 if ( is_null( $this->getTitle() ) ) {
1862 throw new MWException( 'Empty $mTitle in ' . __METHOD__ );
1863 }
1864
1865 $popts = $this->parserOptions();
1866 if ( $interface ) {
1867 $popts->setInterfaceMessage( true );
1868 }
1869 if ( $language !== null ) {
1870 $oldLang = $popts->setTargetLanguage( $language );
1871 }
1872
1873 $parserOutput = $wgParser->getFreshParser()->parse(
1874 $text, $this->getTitle(), $popts,
1875 $linestart, true, $this->mRevisionId
1876 );
1877
1878 if ( $interface ) {
1879 $popts->setInterfaceMessage( false );
1880 }
1881 if ( $language !== null ) {
1882 $popts->setTargetLanguage( $oldLang );
1883 }
1884
1885 return $parserOutput->getText();
1886 }
1887
1898 public function parseInline( $text, $linestart = true, $interface = false ) {
1899 $parsed = $this->parse( $text, $linestart, $interface );
1900 return Parser::stripOuterParagraph( $parsed );
1901 }
1902
1907 public function setSquidMaxage( $maxage ) {
1908 $this->setCdnMaxage( $maxage );
1909 }
1910
1916 public function setCdnMaxage( $maxage ) {
1917 $this->mCdnMaxage = min( $maxage, $this->mCdnMaxageLimit );
1918 }
1919
1926 public function lowerCdnMaxage( $maxage ) {
1927 $this->mCdnMaxageLimit = min( $maxage, $this->mCdnMaxageLimit );
1928 $this->setCdnMaxage( $this->mCdnMaxage );
1929 }
1930
1944 public function adaptCdnTTL( $mtime, $minTTL = 0, $maxTTL = 0 ) {
1945 $minTTL = $minTTL ?: IExpiringStore::TTL_MINUTE;
1946 $maxTTL = $maxTTL ?: $this->getConfig()->get( 'SquidMaxage' );
1947
1948 if ( $mtime === null || $mtime === false ) {
1949 return $minTTL; // entity does not exist
1950 }
1951
1952 $age = time() - wfTimestamp( TS_UNIX, $mtime );
1953 $adaptiveTTL = max( .9 * $age, $minTTL );
1954 $adaptiveTTL = min( $adaptiveTTL, $maxTTL );
1955
1956 $this->lowerCdnMaxage( (int)$adaptiveTTL );
1957
1958 return $adaptiveTTL;
1959 }
1960
1968 public function enableClientCache( $state ) {
1969 return wfSetVar( $this->mEnableClientCache, $state );
1970 }
1971
1978 static $cookies;
1979 if ( $cookies === null ) {
1980 $config = $this->getConfig();
1981 $cookies = array_merge(
1982 SessionManager::singleton()->getVaryCookies(),
1983 [
1984 'forceHTTPS',
1985 ],
1986 $config->get( 'CacheVaryCookies' )
1987 );
1988 Hooks::run( 'GetCacheVaryCookies', [ $this, &$cookies ] );
1989 }
1990 return $cookies;
1991 }
1992
2000 $request = $this->getRequest();
2001 foreach ( $this->getCacheVaryCookies() as $cookieName ) {
2002 if ( $request->getCookie( $cookieName, '', '' ) !== '' ) {
2003 wfDebug( __METHOD__ . ": found $cookieName\n" );
2004 return true;
2005 }
2006 }
2007 wfDebug( __METHOD__ . ": no cache-varying cookies found\n" );
2008 return false;
2009 }
2010
2019 public function addVaryHeader( $header, array $option = null ) {
2020 if ( !array_key_exists( $header, $this->mVaryHeader ) ) {
2021 $this->mVaryHeader[$header] = [];
2022 }
2023 if ( !is_array( $option ) ) {
2024 $option = [];
2025 }
2026 $this->mVaryHeader[$header] = array_unique( array_merge( $this->mVaryHeader[$header], $option ) );
2027 }
2028
2035 public function getVaryHeader() {
2036 // If we vary on cookies, let's make sure it's always included here too.
2037 if ( $this->getCacheVaryCookies() ) {
2038 $this->addVaryHeader( 'Cookie' );
2039 }
2040
2041 foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2042 $this->addVaryHeader( $header, $options );
2043 }
2044 return 'Vary: ' . implode( ', ', array_keys( $this->mVaryHeader ) );
2045 }
2046
2052 public function getKeyHeader() {
2053 $cvCookies = $this->getCacheVaryCookies();
2054
2055 $cookiesOption = [];
2056 foreach ( $cvCookies as $cookieName ) {
2057 $cookiesOption[] = 'param=' . $cookieName;
2058 }
2059 $this->addVaryHeader( 'Cookie', $cookiesOption );
2060
2061 foreach ( SessionManager::singleton()->getVaryHeaders() as $header => $options ) {
2062 $this->addVaryHeader( $header, $options );
2063 }
2064
2065 $headers = [];
2066 foreach ( $this->mVaryHeader as $header => $option ) {
2067 $newheader = $header;
2068 if ( is_array( $option ) && count( $option ) > 0 ) {
2069 $newheader .= ';' . implode( ';', $option );
2070 }
2071 $headers[] = $newheader;
2072 }
2073 $key = 'Key: ' . implode( ',', $headers );
2074
2075 return $key;
2076 }
2077
2087 $title = $this->getTitle();
2088 if ( !$title instanceof Title ) {
2089 return;
2090 }
2091
2092 $lang = $title->getPageLanguage();
2093 if ( !$this->getRequest()->getCheck( 'variant' ) && $lang->hasVariants() ) {
2094 $variants = $lang->getVariants();
2095 $aloption = [];
2096 foreach ( $variants as $variant ) {
2097 if ( $variant === $lang->getCode() ) {
2098 continue;
2099 } else {
2100 $aloption[] = 'substr=' . $variant;
2101
2102 // IE and some other browsers use BCP 47 standards in
2103 // their Accept-Language header, like "zh-CN" or "zh-Hant".
2104 // We should handle these too.
2105 $variantBCP47 = wfBCP47( $variant );
2106 if ( $variantBCP47 !== $variant ) {
2107 $aloption[] = 'substr=' . $variantBCP47;
2108 }
2109 }
2110 }
2111 $this->addVaryHeader( 'Accept-Language', $aloption );
2112 }
2113 }
2114
2125 public function preventClickjacking( $enable = true ) {
2126 $this->mPreventClickjacking = $enable;
2127 }
2128
2134 public function allowClickjacking() {
2135 $this->mPreventClickjacking = false;
2136 }
2137
2144 public function getPreventClickjacking() {
2146 }
2147
2155 public function getFrameOptions() {
2156 $config = $this->getConfig();
2157 if ( $config->get( 'BreakFrames' ) ) {
2158 return 'DENY';
2159 } elseif ( $this->mPreventClickjacking && $config->get( 'EditPageFrameOptions' ) ) {
2160 return $config->get( 'EditPageFrameOptions' );
2161 }
2162 return false;
2163 }
2164
2168 public function sendCacheControl() {
2169 $response = $this->getRequest()->response();
2170 $config = $this->getConfig();
2171
2172 $this->addVaryHeader( 'Cookie' );
2173 $this->addAcceptLanguage();
2174
2175 # don't serve compressed data to clients who can't handle it
2176 # maintain different caches for logged-in users and non-logged in ones
2177 $response->header( $this->getVaryHeader() );
2178
2179 if ( $config->get( 'UseKeyHeader' ) ) {
2180 $response->header( $this->getKeyHeader() );
2181 }
2182
2183 if ( $this->mEnableClientCache ) {
2184 if (
2185 $config->get( 'UseSquid' ) &&
2186 !$response->hasCookies() &&
2187 !SessionManager::getGlobalSession()->isPersistent() &&
2188 !$this->isPrintable() &&
2189 $this->mCdnMaxage != 0 &&
2190 !$this->haveCacheVaryCookies()
2191 ) {
2192 if ( $config->get( 'UseESI' ) ) {
2193 # We'll purge the proxy cache explicitly, but require end user agents
2194 # to revalidate against the proxy on each visit.
2195 # Surrogate-Control controls our CDN, Cache-Control downstream caches
2196 wfDebug( __METHOD__ . ": proxy caching with ESI; {$this->mLastModified} **", 'private' );
2197 # start with a shorter timeout for initial testing
2198 # header( 'Surrogate-Control: max-age=2678400+2678400, content="ESI/1.0"');
2199 $response->header( 'Surrogate-Control: max-age=' . $config->get( 'SquidMaxage' )
2200 . '+' . $this->mCdnMaxage . ', content="ESI/1.0"' );
2201 $response->header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
2202 } else {
2203 # We'll purge the proxy cache for anons explicitly, but require end user agents
2204 # to revalidate against the proxy on each visit.
2205 # IMPORTANT! The CDN needs to replace the Cache-Control header with
2206 # Cache-Control: s-maxage=0, must-revalidate, max-age=0
2207 wfDebug( __METHOD__ . ": local proxy caching; {$this->mLastModified} **", 'private' );
2208 # start with a shorter timeout for initial testing
2209 # header( "Cache-Control: s-maxage=2678400, must-revalidate, max-age=0" );
2210 $response->header( 'Cache-Control: s-maxage=' . $this->mCdnMaxage
2211 . ', must-revalidate, max-age=0' );
2212 }
2213 } else {
2214 # We do want clients to cache if they can, but they *must* check for updates
2215 # on revisiting the page.
2216 wfDebug( __METHOD__ . ": private caching; {$this->mLastModified} **", 'private' );
2217 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2218 $response->header( "Cache-Control: private, must-revalidate, max-age=0" );
2219 }
2220 if ( $this->mLastModified ) {
2221 $response->header( "Last-Modified: {$this->mLastModified}" );
2222 }
2223 } else {
2224 wfDebug( __METHOD__ . ": no caching **", 'private' );
2225
2226 # In general, the absence of a last modified header should be enough to prevent
2227 # the client from using its cache. We send a few other things just to make sure.
2228 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
2229 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
2230 $response->header( 'Pragma: no-cache' );
2231 }
2232 }
2233
2244 public function output( $return = false ) {
2246
2247 if ( $this->mDoNothing ) {
2248 return $return ? '' : null;
2249 }
2250
2251 $response = $this->getRequest()->response();
2252 $config = $this->getConfig();
2253
2254 if ( $this->mRedirect != '' ) {
2255 # Standards require redirect URLs to be absolute
2256 $this->mRedirect = wfExpandUrl( $this->mRedirect, PROTO_CURRENT );
2257
2258 $redirect = $this->mRedirect;
2260
2261 if ( Hooks::run( "BeforePageRedirect", [ $this, &$redirect, &$code ] ) ) {
2262 if ( $code == '301' || $code == '303' ) {
2263 if ( !$config->get( 'DebugRedirects' ) ) {
2264 $response->statusHeader( $code );
2265 }
2266 $this->mLastModified = wfTimestamp( TS_RFC2822 );
2267 }
2268 if ( $config->get( 'VaryOnXFP' ) ) {
2269 $this->addVaryHeader( 'X-Forwarded-Proto' );
2270 }
2271 $this->sendCacheControl();
2272
2273 $response->header( "Content-Type: text/html; charset=utf-8" );
2274 if ( $config->get( 'DebugRedirects' ) ) {
2275 $url = htmlspecialchars( $redirect );
2276 print "<html>\n<head>\n<title>Redirect</title>\n</head>\n<body>\n";
2277 print "<p>Location: <a href=\"$url\">$url</a></p>\n";
2278 print "</body>\n</html>\n";
2279 } else {
2280 $response->header( 'Location: ' . $redirect );
2281 }
2282 }
2283
2284 return $return ? '' : null;
2285 } elseif ( $this->mStatusCode ) {
2286 $response->statusHeader( $this->mStatusCode );
2287 }
2288
2289 # Buffer output; final headers may depend on later processing
2290 ob_start();
2291
2292 $response->header( 'Content-type: ' . $config->get( 'MimeType' ) . '; charset=UTF-8' );
2293 $response->header( 'Content-language: ' . $wgContLang->getHtmlCode() );
2294
2295 // Avoid Internet Explorer "compatibility view" in IE 8-10, so that
2296 // jQuery etc. can work correctly.
2297 $response->header( 'X-UA-Compatible: IE=Edge' );
2298
2299 // Prevent framing, if requested
2300 $frameOptions = $this->getFrameOptions();
2301 if ( $frameOptions ) {
2302 $response->header( "X-Frame-Options: $frameOptions" );
2303 }
2304
2305 if ( $this->mArticleBodyOnly ) {
2306 echo $this->mBodytext;
2307 } else {
2308 $sk = $this->getSkin();
2309 // add skin specific modules
2310 $modules = $sk->getDefaultModules();
2311
2312 // Enforce various default modules for all pages and all skins
2313 $coreModules = [
2314 // Keep this list as small as possible
2315 'site',
2316 'mediawiki.page.startup',
2317 'mediawiki.user',
2318 ];
2319
2320 // Support for high-density display images if enabled
2321 if ( $config->get( 'ResponsiveImages' ) ) {
2322 $coreModules[] = 'mediawiki.hidpi';
2323 }
2324
2325 $this->addModules( $coreModules );
2326 foreach ( $modules as $group ) {
2327 $this->addModules( $group );
2328 }
2329 MWDebug::addModules( $this );
2330
2331 // Hook that allows last minute changes to the output page, e.g.
2332 // adding of CSS or Javascript by extensions.
2333 Hooks::run( 'BeforePageDisplay', [ &$this, &$sk ] );
2334
2335 try {
2336 $sk->outputPage();
2337 } catch ( Exception $e ) {
2338 ob_end_clean(); // bug T129657
2339 throw $e;
2340 }
2341 }
2342
2343 try {
2344 // This hook allows last minute changes to final overall output by modifying output buffer
2345 Hooks::run( 'AfterFinalPageOutput', [ $this ] );
2346 } catch ( Exception $e ) {
2347 ob_end_clean(); // bug T129657
2348 throw $e;
2349 }
2350
2351 $this->sendCacheControl();
2352
2353 if ( $return ) {
2354 return ob_get_clean();
2355 } else {
2356 ob_end_flush();
2357 return null;
2358 }
2359 }
2360
2371 public function prepareErrorPage( $pageTitle, $htmlTitle = false ) {
2372 $this->setPageTitle( $pageTitle );
2373 if ( $htmlTitle !== false ) {
2374 $this->setHTMLTitle( $htmlTitle );
2375 }
2376 $this->setRobotPolicy( 'noindex,nofollow' );
2377 $this->setArticleRelated( false );
2378 $this->enableClientCache( false );
2379 $this->mRedirect = '';
2380 $this->clearSubtitle();
2381 $this->clearHTML();
2382 }
2383
2396 public function showErrorPage( $title, $msg, $params = [] ) {
2397 if ( !$title instanceof Message ) {
2398 $title = $this->msg( $title );
2399 }
2400
2401 $this->prepareErrorPage( $title );
2402
2403 if ( $msg instanceof Message ) {
2404 if ( $params !== [] ) {
2405 trigger_error( 'Argument ignored: $params. The message parameters argument '
2406 . 'is discarded when the $msg argument is a Message object instead of '
2407 . 'a string.', E_USER_NOTICE );
2408 }
2409 $this->addHTML( $msg->parseAsBlock() );
2410 } else {
2411 $this->addWikiMsgArray( $msg, $params );
2412 }
2413
2414 $this->returnToMain();
2415 }
2416
2423 public function showPermissionsErrorPage( array $errors, $action = null ) {
2424 foreach ( $errors as $key => $error ) {
2425 $errors[$key] = (array)$error;
2426 }
2427
2428 // For some action (read, edit, create and upload), display a "login to do this action"
2429 // error if all of the following conditions are met:
2430 // 1. the user is not logged in
2431 // 2. the only error is insufficient permissions (i.e. no block or something else)
2432 // 3. the error can be avoided simply by logging in
2433 if ( in_array( $action, [ 'read', 'edit', 'createpage', 'createtalk', 'upload' ] )
2434 && $this->getUser()->isAnon() && count( $errors ) == 1 && isset( $errors[0][0] )
2435 && ( $errors[0][0] == 'badaccess-groups' || $errors[0][0] == 'badaccess-group0' )
2436 && ( User::groupHasPermission( 'user', $action )
2437 || User::groupHasPermission( 'autoconfirmed', $action ) )
2438 ) {
2439 $displayReturnto = null;
2440
2441 # Due to bug 32276, if a user does not have read permissions,
2442 # $this->getTitle() will just give Special:Badtitle, which is
2443 # not especially useful as a returnto parameter. Use the title
2444 # from the request instead, if there was one.
2445 $request = $this->getRequest();
2446 $returnto = Title::newFromText( $request->getVal( 'title', '' ) );
2447 if ( $action == 'edit' ) {
2448 $msg = 'whitelistedittext';
2449 $displayReturnto = $returnto;
2450 } elseif ( $action == 'createpage' || $action == 'createtalk' ) {
2451 $msg = 'nocreatetext';
2452 } elseif ( $action == 'upload' ) {
2453 $msg = 'uploadnologintext';
2454 } else { # Read
2455 $msg = 'loginreqpagetext';
2456 $displayReturnto = Title::newMainPage();
2457 }
2458
2459 $query = [];
2460
2461 if ( $returnto ) {
2462 $query['returnto'] = $returnto->getPrefixedText();
2463
2464 if ( !$request->wasPosted() ) {
2465 $returntoquery = $request->getValues();
2466 unset( $returntoquery['title'] );
2467 unset( $returntoquery['returnto'] );
2468 unset( $returntoquery['returntoquery'] );
2469 $query['returntoquery'] = wfArrayToCgi( $returntoquery );
2470 }
2471 }
2472 $loginLink = Linker::linkKnown(
2473 SpecialPage::getTitleFor( 'Userlogin' ),
2474 $this->msg( 'loginreqlink' )->escaped(),
2475 [],
2476 $query
2477 );
2478
2479 $this->prepareErrorPage( $this->msg( 'loginreqtitle' ) );
2480 $this->addHTML( $this->msg( $msg )->rawParams( $loginLink )->parse() );
2481
2482 # Don't return to a page the user can't read otherwise
2483 # we'll end up in a pointless loop
2484 if ( $displayReturnto && $displayReturnto->userCan( 'read', $this->getUser() ) ) {
2485 $this->returnToMain( null, $displayReturnto );
2486 }
2487 } else {
2488 $this->prepareErrorPage( $this->msg( 'permissionserrors' ) );
2489 $this->addWikiText( $this->formatPermissionsErrorMessage( $errors, $action ) );
2490 }
2491 }
2492
2499 public function versionRequired( $version ) {
2500 $this->prepareErrorPage( $this->msg( 'versionrequired', $version ) );
2501
2502 $this->addWikiMsg( 'versionrequiredtext', $version );
2503 $this->returnToMain();
2504 }
2505
2513 public function formatPermissionsErrorMessage( array $errors, $action = null ) {
2514 if ( $action == null ) {
2515 $text = $this->msg( 'permissionserrorstext', count( $errors ) )->plain() . "\n\n";
2516 } else {
2517 $action_desc = $this->msg( "action-$action" )->plain();
2518 $text = $this->msg(
2519 'permissionserrorstext-withaction',
2520 count( $errors ),
2521 $action_desc
2522 )->plain() . "\n\n";
2523 }
2524
2525 if ( count( $errors ) > 1 ) {
2526 $text .= '<ul class="permissions-errors">' . "\n";
2527
2528 foreach ( $errors as $error ) {
2529 $text .= '<li>';
2530 $text .= call_user_func_array( [ $this, 'msg' ], $error )->plain();
2531 $text .= "</li>\n";
2532 }
2533 $text .= '</ul>';
2534 } else {
2535 $text .= "<div class=\"permissions-errors\">\n" .
2536 call_user_func_array( [ $this, 'msg' ], reset( $errors ) )->plain() .
2537 "\n</div>";
2538 }
2539
2540 return $text;
2541 }
2542
2554 public function readOnlyPage() {
2555 if ( func_num_args() > 0 ) {
2556 throw new MWException( __METHOD__ . ' no longer accepts arguments since 1.25.' );
2557 }
2558
2559 throw new ReadOnlyError;
2560 }
2561
2568 public function rateLimited() {
2569 wfDeprecated( __METHOD__, '1.25' );
2570 throw new ThrottledError;
2571 }
2572
2582 public function showLagWarning( $lag ) {
2583 $config = $this->getConfig();
2584 if ( $lag >= $config->get( 'SlaveLagWarning' ) ) {
2585 $lag = floor( $lag ); // floor to avoid nano seconds to display
2586 $message = $lag < $config->get( 'SlaveLagCritical' )
2587 ? 'lag-warn-normal'
2588 : 'lag-warn-high';
2589 $wrap = Html::rawElement( 'div', [ 'class' => "mw-{$message}" ], "\n$1\n" );
2590 $this->wrapWikiMsg( "$wrap\n", [ $message, $this->getLanguage()->formatNum( $lag ) ] );
2591 }
2592 }
2593
2594 public function showFatalError( $message ) {
2595 $this->prepareErrorPage( $this->msg( 'internalerror' ) );
2596
2597 $this->addHTML( $message );
2598 }
2599
2600 public function showUnexpectedValueError( $name, $val ) {
2601 $this->showFatalError( $this->msg( 'unexpected', $name, $val )->text() );
2602 }
2603
2604 public function showFileCopyError( $old, $new ) {
2605 $this->showFatalError( $this->msg( 'filecopyerror', $old, $new )->text() );
2606 }
2607
2608 public function showFileRenameError( $old, $new ) {
2609 $this->showFatalError( $this->msg( 'filerenameerror', $old, $new )->text() );
2610 }
2611
2612 public function showFileDeleteError( $name ) {
2613 $this->showFatalError( $this->msg( 'filedeleteerror', $name )->text() );
2614 }
2615
2616 public function showFileNotFoundError( $name ) {
2617 $this->showFatalError( $this->msg( 'filenotfound', $name )->text() );
2618 }
2619
2628 public function addReturnTo( $title, array $query = [], $text = null, $options = [] ) {
2629 $link = $this->msg( 'returnto' )->rawParams(
2630 Linker::link( $title, $text, [], $query, $options ) )->escaped();
2631 $this->addHTML( "<p id=\"mw-returnto\">{$link}</p>\n" );
2632 }
2633
2642 public function returnToMain( $unused = null, $returnto = null, $returntoquery = null ) {
2643 if ( $returnto == null ) {
2644 $returnto = $this->getRequest()->getText( 'returnto' );
2645 }
2646
2647 if ( $returntoquery == null ) {
2648 $returntoquery = $this->getRequest()->getText( 'returntoquery' );
2649 }
2650
2651 if ( $returnto === '' ) {
2652 $returnto = Title::newMainPage();
2653 }
2654
2655 if ( is_object( $returnto ) ) {
2656 $titleObj = $returnto;
2657 } else {
2658 $titleObj = Title::newFromText( $returnto );
2659 }
2660 // We don't want people to return to external interwiki. That
2661 // might potentially be used as part of a phishing scheme
2662 if ( !is_object( $titleObj ) || $titleObj->isExternal() ) {
2663 $titleObj = Title::newMainPage();
2664 }
2665
2666 $this->addReturnTo( $titleObj, wfCgiToArray( $returntoquery ) );
2667 }
2668
2669 private function getRlClientContext() {
2670 if ( !$this->rlClientContext ) {
2672 [], // modules; not relevant
2673 $this->getLanguage()->getCode(),
2674 $this->getSkin()->getSkinName(),
2675 $this->getUser()->isLoggedIn() ? $this->getUser()->getName() : null,
2676 null, // version; not relevant
2678 null, // only; not relevant
2679 $this->isPrintable(),
2680 $this->getRequest()->getBool( 'handheld' )
2681 );
2682 $this->rlClientContext = new ResourceLoaderContext(
2683 $this->getResourceLoader(),
2684 new FauxRequest( $query )
2685 );
2686 }
2688 }
2689
2701 public function getRlClient() {
2702 if ( !$this->rlClient ) {
2703 $context = $this->getRlClientContext();
2704 $rl = $this->getResourceLoader();
2705 $this->addModules( [
2706 'user.options',
2707 'user.tokens',
2708 ] );
2709 $this->addModuleStyles( [
2710 'site.styles',
2711 'noscript',
2712 'user.styles',
2713 'user.cssprefs',
2714 ] );
2715 $this->getSkin()->setupSkinUserCss( $this );
2716
2717 // Prepare exempt modules for buildExemptModules()
2718 $exemptGroups = [ 'site' => [], 'noscript' => [], 'private' => [], 'user' => [] ];
2719 $exemptStates = [];
2720 $moduleStyles = $this->getModuleStyles( /*filter*/ true );
2721
2722 // Preload getTitleInfo for isKnownEmpty calls below and in ResourceLoaderClientHtml
2723 // Separate user-specific batch for improved cache-hit ratio.
2724 $userBatch = [ 'user.styles', 'user' ];
2725 $siteBatch = array_diff( $moduleStyles, $userBatch );
2726 $dbr = wfGetDB( DB_REPLICA );
2729
2730 // Filter out modules handled by buildExemptModules()
2731 $moduleStyles = array_filter( $moduleStyles,
2732 function ( $name ) use ( $rl, $context, &$exemptGroups, &$exemptStates ) {
2733 $module = $rl->getModule( $name );
2734 if ( $module ) {
2735 if ( $name === 'user.styles' && $this->isUserCssPreview() ) {
2736 $exemptStates[$name] = 'ready';
2737 // Special case in buildExemptModules()
2738 return false;
2739 }
2740 $group = $module->getGroup();
2741 if ( isset( $exemptGroups[$group] ) ) {
2742 $exemptStates[$name] = 'ready';
2743 if ( !$module->isKnownEmpty( $context ) ) {
2744 // E.g. Don't output empty <styles>
2745 $exemptGroups[$group][] = $name;
2746 }
2747 return false;
2748 }
2749 }
2750 return true;
2751 }
2752 );
2753 $this->rlExemptStyleModules = $exemptGroups;
2754
2755 $isUserModuleFiltered = !$this->filterModules( [ 'user' ] );
2756 // If this page filters out 'user', makeResourceLoaderLink will drop it.
2757 // Avoid indefinite "loading" state or untrue "ready" state (T145368).
2758 if ( !$isUserModuleFiltered ) {
2759 // Manually handled by getBottomScripts()
2760 $userModule = $rl->getModule( 'user' );
2761 $userState = $userModule->isKnownEmpty( $context ) && !$this->isUserJsPreview()
2762 ? 'ready'
2763 : 'loading';
2764 $this->rlUserModuleState = $exemptStates['user'] = $userState;
2765 }
2766
2768 $rlClient->setConfig( $this->getJSVars() );
2769 $rlClient->setModules( $this->getModules( /*filter*/ true ) );
2770 $rlClient->setModuleStyles( $moduleStyles );
2771 $rlClient->setModuleScripts( $this->getModuleScripts( /*filter*/ true ) );
2772 $rlClient->setExemptStates( $exemptStates );
2773 $this->rlClient = $rlClient;
2774 }
2775 return $this->rlClient;
2776 }
2777
2783 public function headElement( Skin $sk, $includeStyle = true ) {
2785
2786 $userdir = $this->getLanguage()->getDir();
2787 $sitedir = $wgContLang->getDir();
2788
2789 $pieces = [];
2790 $pieces[] = Html::htmlHeader( Sanitizer::mergeAttributes(
2791 $this->getRlClient()->getDocumentAttributes(),
2793 ) );
2794 $pieces[] = Html::openElement( 'head' );
2795
2796 if ( $this->getHTMLTitle() == '' ) {
2797 $this->setHTMLTitle( $this->msg( 'pagetitle', $this->getPageTitle() )->inContentLanguage() );
2798 }
2799
2800 if ( !Html::isXmlMimeType( $this->getConfig()->get( 'MimeType' ) ) ) {
2801 // Add <meta charset="UTF-8">
2802 // This should be before <title> since it defines the charset used by
2803 // text including the text inside <title>.
2804 // The spec recommends defining XHTML5's charset using the XML declaration
2805 // instead of meta.
2806 // Our XML declaration is output by Html::htmlHeader.
2807 // http://www.whatwg.org/html/semantics.html#attr-meta-http-equiv-content-type
2808 // http://www.whatwg.org/html/semantics.html#charset
2809 $pieces[] = Html::element( 'meta', [ 'charset' => 'UTF-8' ] );
2810 }
2811
2812 $pieces[] = Html::element( 'title', null, $this->getHTMLTitle() );
2813 $pieces[] = $this->getRlClient()->getHeadHtml();
2814 $pieces[] = $this->buildExemptModules();
2815 $pieces = array_merge( $pieces, array_values( $this->getHeadLinksArray() ) );
2816 $pieces = array_merge( $pieces, array_values( $this->mHeadItems ) );
2817 $pieces[] = Html::closeElement( 'head' );
2818
2819 $bodyClasses = [];
2820 $bodyClasses[] = 'mediawiki';
2821
2822 # Classes for LTR/RTL directionality support
2823 $bodyClasses[] = $userdir;
2824 $bodyClasses[] = "sitedir-$sitedir";
2825
2826 if ( $this->getLanguage()->capitalizeAllNouns() ) {
2827 # A <body> class is probably not the best way to do this . . .
2828 $bodyClasses[] = 'capitalize-all-nouns';
2829 }
2830
2831 // Parser feature migration class
2832 // The idea is that this will eventually be removed, after the wikitext
2833 // which requires it is cleaned up.
2834 $bodyClasses[] = 'mw-hide-empty-elt';
2835
2836 $bodyClasses[] = $sk->getPageClasses( $this->getTitle() );
2837 $bodyClasses[] = 'skin-' . Sanitizer::escapeClass( $sk->getSkinName() );
2838 $bodyClasses[] =
2839 'action-' . Sanitizer::escapeClass( Action::getActionName( $this->getContext() ) );
2840
2841 $bodyAttrs = [];
2842 // While the implode() is not strictly needed, it's used for backwards compatibility
2843 // (this used to be built as a string and hooks likely still expect that).
2844 $bodyAttrs['class'] = implode( ' ', $bodyClasses );
2845
2846 // Allow skins and extensions to add body attributes they need
2847 $sk->addToBodyAttributes( $this, $bodyAttrs );
2848 Hooks::run( 'OutputPageBodyAttributes', [ $this, $sk, &$bodyAttrs ] );
2849
2850 $pieces[] = Html::openElement( 'body', $bodyAttrs );
2851
2852 return self::combineWrappedStrings( $pieces );
2853 }
2854
2860 public function getResourceLoader() {
2861 if ( is_null( $this->mResourceLoader ) ) {
2862 $this->mResourceLoader = new ResourceLoader(
2863 $this->getConfig(),
2864 LoggerFactory::getInstance( 'resourceloader' )
2865 );
2866 }
2868 }
2869
2878 public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) {
2879 // Apply 'target' and 'origin' filters
2880 $modules = $this->filterModules( (array)$modules, null, $only );
2881
2883 $this->getRlClientContext(),
2884 $modules,
2885 $only,
2886 $extraQuery
2887 );
2888 }
2889
2896 protected static function combineWrappedStrings( array $chunks ) {
2897 // Filter out empty values
2898 $chunks = array_filter( $chunks, 'strlen' );
2899 return WrappedString::join( "\n", $chunks );
2900 }
2901
2902 private function isUserJsPreview() {
2903 return $this->getConfig()->get( 'AllowUserJs' )
2904 && $this->getTitle()
2905 && $this->getTitle()->isJsSubpage()
2906 && $this->userCanPreview();
2907 }
2908
2909 private function isUserCssPreview() {
2910 return $this->getConfig()->get( 'AllowUserCss' )
2911 && $this->getTitle()
2912 && $this->getTitle()->isCssSubpage()
2913 && $this->userCanPreview();
2914 }
2915
2922 public function getBottomScripts() {
2923 $chunks = [];
2924 $chunks[] = $this->getRlClient()->getBodyHtml();
2925
2926 // Legacy non-ResourceLoader scripts
2927 $chunks[] = $this->mScripts;
2928
2929 // Exempt 'user' module
2930 // - May need excludepages for live preview. (T28283)
2931 // - Must use TYPE_COMBINED so its response is handled by mw.loader.implement() which
2932 // ensures execution is scheduled after the "site" module.
2933 // - Don't load if module state is already resolved as "ready".
2934 if ( $this->rlUserModuleState === 'loading' ) {
2935 if ( $this->isUserJsPreview() ) {
2937 [ 'excludepage' => $this->getTitle()->getPrefixedDBkey() ]
2938 );
2940 Xml::encodeJsCall( 'mw.loader.using', [
2941 [ 'user', 'site' ],
2942 new XmlJsCode(
2943 'function () {'
2944 . Xml::encodeJsCall( '$.globalEval', [
2945 $this->getRequest()->getText( 'wpTextbox1' )
2946 ] )
2947 . '}'
2948 )
2949 ] )
2950 );
2951 // FIXME: If the user is previewing, say, ./vector.js, his ./common.js will be loaded
2952 // asynchronously and may arrive *after* the inline script here. So the previewed code
2953 // may execute before ./common.js runs. Normally, ./common.js runs before ./vector.js.
2954 // Similarly, when previewing ./common.js and the user module does arrive first,
2955 // it will arrive without common.js and the inline script runs after.
2956 // Thus running common after the excluded subpage.
2957 } else {
2958 // Load normally
2959 $chunks[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_COMBINED );
2960 }
2961 }
2962
2963 return self::combineWrappedStrings( $chunks );
2964 }
2965
2972 public function getJsConfigVars() {
2973 return $this->mJsConfigVars;
2974 }
2975
2982 public function addJsConfigVars( $keys, $value = null ) {
2983 if ( is_array( $keys ) ) {
2984 foreach ( $keys as $key => $value ) {
2985 $this->mJsConfigVars[$key] = $value;
2986 }
2987 return;
2988 }
2989
2990 $this->mJsConfigVars[$keys] = $value;
2991 }
2992
3002 public function getJSVars() {
3004
3005 $curRevisionId = 0;
3006 $articleId = 0;
3007 $canonicalSpecialPageName = false; # bug 21115
3008
3009 $title = $this->getTitle();
3010 $ns = $title->getNamespace();
3011 $canonicalNamespace = MWNamespace::exists( $ns )
3012 ? MWNamespace::getCanonicalName( $ns )
3013 : $title->getNsText();
3014
3015 $sk = $this->getSkin();
3016 // Get the relevant title so that AJAX features can use the correct page name
3017 // when making API requests from certain special pages (bug 34972).
3018 $relevantTitle = $sk->getRelevantTitle();
3019 $relevantUser = $sk->getRelevantUser();
3020
3021 if ( $ns == NS_SPECIAL ) {
3022 list( $canonicalSpecialPageName, /*...*/ ) =
3024 } elseif ( $this->canUseWikiPage() ) {
3025 $wikiPage = $this->getWikiPage();
3026 $curRevisionId = $wikiPage->getLatest();
3027 $articleId = $wikiPage->getId();
3028 }
3029
3030 $lang = $title->getPageViewLanguage();
3031
3032 // Pre-process information
3033 $separatorTransTable = $lang->separatorTransformTable();
3034 $separatorTransTable = $separatorTransTable ? $separatorTransTable : [];
3035 $compactSeparatorTransTable = [
3036 implode( "\t", array_keys( $separatorTransTable ) ),
3037 implode( "\t", $separatorTransTable ),
3038 ];
3039 $digitTransTable = $lang->digitTransformTable();
3040 $digitTransTable = $digitTransTable ? $digitTransTable : [];
3041 $compactDigitTransTable = [
3042 implode( "\t", array_keys( $digitTransTable ) ),
3043 implode( "\t", $digitTransTable ),
3044 ];
3045
3046 $user = $this->getUser();
3047
3048 $vars = [
3049 'wgCanonicalNamespace' => $canonicalNamespace,
3050 'wgCanonicalSpecialPageName' => $canonicalSpecialPageName,
3051 'wgNamespaceNumber' => $title->getNamespace(),
3052 'wgPageName' => $title->getPrefixedDBkey(),
3053 'wgTitle' => $title->getText(),
3054 'wgCurRevisionId' => $curRevisionId,
3055 'wgRevisionId' => (int)$this->getRevisionId(),
3056 'wgArticleId' => $articleId,
3057 'wgIsArticle' => $this->isArticle(),
3058 'wgIsRedirect' => $title->isRedirect(),
3059 'wgAction' => Action::getActionName( $this->getContext() ),
3060 'wgUserName' => $user->isAnon() ? null : $user->getName(),
3061 'wgUserGroups' => $user->getEffectiveGroups(),
3062 'wgCategories' => $this->getCategories(),
3063 'wgBreakFrames' => $this->getFrameOptions() == 'DENY',
3064 'wgPageContentLanguage' => $lang->getCode(),
3065 'wgPageContentModel' => $title->getContentModel(),
3066 'wgSeparatorTransformTable' => $compactSeparatorTransTable,
3067 'wgDigitTransformTable' => $compactDigitTransTable,
3068 'wgDefaultDateFormat' => $lang->getDefaultDateFormat(),
3069 'wgMonthNames' => $lang->getMonthNamesArray(),
3070 'wgMonthNamesShort' => $lang->getMonthAbbreviationsArray(),
3071 'wgRelevantPageName' => $relevantTitle->getPrefixedDBkey(),
3072 'wgRelevantArticleId' => $relevantTitle->getArticleID(),
3073 'wgRequestId' => WebRequest::getRequestId(),
3074 ];
3075
3076 if ( $user->isLoggedIn() ) {
3077 $vars['wgUserId'] = $user->getId();
3078 $vars['wgUserEditCount'] = $user->getEditCount();
3079 $userReg = $user->getRegistration();
3080 $vars['wgUserRegistration'] = $userReg ? wfTimestamp( TS_UNIX, $userReg ) * 1000 : null;
3081 // Get the revision ID of the oldest new message on the user's talk
3082 // page. This can be used for constructing new message alerts on
3083 // the client side.
3084 $vars['wgUserNewMsgRevisionId'] = $user->getNewMessageRevisionId();
3085 }
3086
3087 if ( $wgContLang->hasVariants() ) {
3088 $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
3089 }
3090 // Same test as SkinTemplate
3091 $vars['wgIsProbablyEditable'] = $title->quickUserCan( 'edit', $user )
3092 && ( $title->exists() || $title->quickUserCan( 'create', $user ) );
3093
3094 foreach ( $title->getRestrictionTypes() as $type ) {
3095 $vars['wgRestriction' . ucfirst( $type )] = $title->getRestrictions( $type );
3096 }
3097
3098 if ( $title->isMainPage() ) {
3099 $vars['wgIsMainPage'] = true;
3100 }
3101
3102 if ( $this->mRedirectedFrom ) {
3103 $vars['wgRedirectedFrom'] = $this->mRedirectedFrom->getPrefixedDBkey();
3104 }
3105
3106 if ( $relevantUser ) {
3107 $vars['wgRelevantUserName'] = $relevantUser->getName();
3108 }
3109
3110 // Allow extensions to add their custom variables to the mw.config map.
3111 // Use the 'ResourceLoaderGetConfigVars' hook if the variable is not
3112 // page-dependant but site-wide (without state).
3113 // Alternatively, you may want to use OutputPage->addJsConfigVars() instead.
3114 Hooks::run( 'MakeGlobalVariablesScript', [ &$vars, $this ] );
3115
3116 // Merge in variables from addJsConfigVars last
3117 return array_merge( $vars, $this->getJsConfigVars() );
3118 }
3119
3129 public function userCanPreview() {
3130 $request = $this->getRequest();
3131 if (
3132 $request->getVal( 'action' ) !== 'submit' ||
3133 !$request->getCheck( 'wpPreview' ) ||
3134 !$request->wasPosted()
3135 ) {
3136 return false;
3137 }
3138
3139 $user = $this->getUser();
3140
3141 if ( !$user->isLoggedIn() ) {
3142 // Anons have predictable edit tokens
3143 return false;
3144 }
3145 if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
3146 return false;
3147 }
3148
3149 $title = $this->getTitle();
3150 if ( !$title->isJsSubpage() && !$title->isCssSubpage() ) {
3151 return false;
3152 }
3153 if ( !$title->isSubpageOf( $user->getUserPage() ) ) {
3154 // Don't execute another user's CSS or JS on preview (T85855)
3155 return false;
3156 }
3157
3158 $errors = $title->getUserPermissionsErrors( 'edit', $user );
3159 if ( count( $errors ) !== 0 ) {
3160 return false;
3161 }
3162
3163 return true;
3164 }
3165
3169 public function getHeadLinksArray() {
3171
3172 $tags = [];
3173 $config = $this->getConfig();
3174
3175 $canonicalUrl = $this->mCanonicalUrl;
3176
3177 $tags['meta-generator'] = Html::element( 'meta', [
3178 'name' => 'generator',
3179 'content' => "MediaWiki $wgVersion",
3180 ] );
3181
3182 if ( $config->get( 'ReferrerPolicy' ) !== false ) {
3183 $tags['meta-referrer'] = Html::element( 'meta', [
3184 'name' => 'referrer',
3185 'content' => $config->get( 'ReferrerPolicy' )
3186 ] );
3187 }
3188
3189 $p = "{$this->mIndexPolicy},{$this->mFollowPolicy}";
3190 if ( $p !== 'index,follow' ) {
3191 // http://www.robotstxt.org/wc/meta-user.html
3192 // Only show if it's different from the default robots policy
3193 $tags['meta-robots'] = Html::element( 'meta', [
3194 'name' => 'robots',
3195 'content' => $p,
3196 ] );
3197 }
3198
3199 foreach ( $this->mMetatags as $tag ) {
3200 if ( 0 == strcasecmp( 'http:', substr( $tag[0], 0, 5 ) ) ) {
3201 $a = 'http-equiv';
3202 $tag[0] = substr( $tag[0], 5 );
3203 } else {
3204 $a = 'name';
3205 }
3206 $tagName = "meta-{$tag[0]}";
3207 if ( isset( $tags[$tagName] ) ) {
3208 $tagName .= $tag[1];
3209 }
3210 $tags[$tagName] = Html::element( 'meta',
3211 [
3212 $a => $tag[0],
3213 'content' => $tag[1]
3214 ]
3215 );
3216 }
3217
3218 foreach ( $this->mLinktags as $tag ) {
3219 $tags[] = Html::element( 'link', $tag );
3220 }
3221
3222 # Universal edit button
3223 if ( $config->get( 'UniversalEditButton' ) && $this->isArticleRelated() ) {
3224 $user = $this->getUser();
3225 if ( $this->getTitle()->quickUserCan( 'edit', $user )
3226 && ( $this->getTitle()->exists() ||
3227 $this->getTitle()->quickUserCan( 'create', $user ) )
3228 ) {
3229 // Original UniversalEditButton
3230 $msg = $this->msg( 'edit' )->text();
3231 $tags['universal-edit-button'] = Html::element( 'link', [
3232 'rel' => 'alternate',
3233 'type' => 'application/x-wiki',
3234 'title' => $msg,
3235 'href' => $this->getTitle()->getEditURL(),
3236 ] );
3237 // Alternate edit link
3238 $tags['alternative-edit'] = Html::element( 'link', [
3239 'rel' => 'edit',
3240 'title' => $msg,
3241 'href' => $this->getTitle()->getEditURL(),
3242 ] );
3243 }
3244 }
3245
3246 # Generally the order of the favicon and apple-touch-icon links
3247 # should not matter, but Konqueror (3.5.9 at least) incorrectly
3248 # uses whichever one appears later in the HTML source. Make sure
3249 # apple-touch-icon is specified first to avoid this.
3250 if ( $config->get( 'AppleTouchIcon' ) !== false ) {
3251 $tags['apple-touch-icon'] = Html::element( 'link', [
3252 'rel' => 'apple-touch-icon',
3253 'href' => $config->get( 'AppleTouchIcon' )
3254 ] );
3255 }
3256
3257 if ( $config->get( 'Favicon' ) !== false ) {
3258 $tags['favicon'] = Html::element( 'link', [
3259 'rel' => 'shortcut icon',
3260 'href' => $config->get( 'Favicon' )
3261 ] );
3262 }
3263
3264 # OpenSearch description link
3265 $tags['opensearch'] = Html::element( 'link', [
3266 'rel' => 'search',
3267 'type' => 'application/opensearchdescription+xml',
3268 'href' => wfScript( 'opensearch_desc' ),
3269 'title' => $this->msg( 'opensearch-desc' )->inContentLanguage()->text(),
3270 ] );
3271
3272 if ( $config->get( 'EnableAPI' ) ) {
3273 # Real Simple Discovery link, provides auto-discovery information
3274 # for the MediaWiki API (and potentially additional custom API
3275 # support such as WordPress or Twitter-compatible APIs for a
3276 # blogging extension, etc)
3277 $tags['rsd'] = Html::element( 'link', [
3278 'rel' => 'EditURI',
3279 'type' => 'application/rsd+xml',
3280 // Output a protocol-relative URL here if $wgServer is protocol-relative.
3281 // Whether RSD accepts relative or protocol-relative URLs is completely
3282 // undocumented, though.
3283 'href' => wfExpandUrl( wfAppendQuery(
3284 wfScript( 'api' ),
3285 [ 'action' => 'rsd' ] ),
3287 ),
3288 ] );
3289 }
3290
3291 # Language variants
3292 if ( !$config->get( 'DisableLangConversion' ) ) {
3293 $lang = $this->getTitle()->getPageLanguage();
3294 if ( $lang->hasVariants() ) {
3295 $variants = $lang->getVariants();
3296 foreach ( $variants as $variant ) {
3297 $tags["variant-$variant"] = Html::element( 'link', [
3298 'rel' => 'alternate',
3299 'hreflang' => wfBCP47( $variant ),
3300 'href' => $this->getTitle()->getLocalURL(
3301 [ 'variant' => $variant ] )
3302 ]
3303 );
3304 }
3305 # x-default link per https://support.google.com/webmasters/answer/189077?hl=en
3306 $tags["variant-x-default"] = Html::element( 'link', [
3307 'rel' => 'alternate',
3308 'hreflang' => 'x-default',
3309 'href' => $this->getTitle()->getLocalURL() ] );
3310 }
3311 }
3312
3313 # Copyright
3314 if ( $this->copyrightUrl !== null ) {
3315 $copyright = $this->copyrightUrl;
3316 } else {
3317 $copyright = '';
3318 if ( $config->get( 'RightsPage' ) ) {
3319 $copy = Title::newFromText( $config->get( 'RightsPage' ) );
3320
3321 if ( $copy ) {
3322 $copyright = $copy->getLocalURL();
3323 }
3324 }
3325
3326 if ( !$copyright && $config->get( 'RightsUrl' ) ) {
3327 $copyright = $config->get( 'RightsUrl' );
3328 }
3329 }
3330
3331 if ( $copyright ) {
3332 $tags['copyright'] = Html::element( 'link', [
3333 'rel' => 'copyright',
3334 'href' => $copyright ]
3335 );
3336 }
3337
3338 # Feeds
3339 if ( $config->get( 'Feed' ) ) {
3340 $feedLinks = [];
3341
3342 foreach ( $this->getSyndicationLinks() as $format => $link ) {
3343 # Use the page name for the title. In principle, this could
3344 # lead to issues with having the same name for different feeds
3345 # corresponding to the same page, but we can't avoid that at
3346 # this low a level.
3347
3348 $feedLinks[] = $this->feedLink(
3349 $format,
3350 $link,
3351 # Used messages: 'page-rss-feed' and 'page-atom-feed' (for an easier grep)
3352 $this->msg(
3353 "page-{$format}-feed", $this->getTitle()->getPrefixedText()
3354 )->text()
3355 );
3356 }
3357
3358 # Recent changes feed should appear on every page (except recentchanges,
3359 # that would be redundant). Put it after the per-page feed to avoid
3360 # changing existing behavior. It's still available, probably via a
3361 # menu in your browser. Some sites might have a different feed they'd
3362 # like to promote instead of the RC feed (maybe like a "Recent New Articles"
3363 # or "Breaking news" one). For this, we see if $wgOverrideSiteFeed is defined.
3364 # If so, use it instead.
3365 $sitename = $config->get( 'Sitename' );
3366 if ( $config->get( 'OverrideSiteFeed' ) ) {
3367 foreach ( $config->get( 'OverrideSiteFeed' ) as $type => $feedUrl ) {
3368 // Note, this->feedLink escapes the url.
3369 $feedLinks[] = $this->feedLink(
3370 $type,
3371 $feedUrl,
3372 $this->msg( "site-{$type}-feed", $sitename )->text()
3373 );
3374 }
3375 } elseif ( !$this->getTitle()->isSpecial( 'Recentchanges' ) ) {
3376 $rctitle = SpecialPage::getTitleFor( 'Recentchanges' );
3377 foreach ( $config->get( 'AdvertisedFeedTypes' ) as $format ) {
3378 $feedLinks[] = $this->feedLink(
3379 $format,
3380 $rctitle->getLocalURL( [ 'feed' => $format ] ),
3381 # For grep: 'site-rss-feed', 'site-atom-feed'
3382 $this->msg( "site-{$format}-feed", $sitename )->text()
3383 );
3384 }
3385 }
3386
3387 # Allow extensions to change the list pf feeds. This hook is primarily for changing,
3388 # manipulating or removing existing feed tags. If you want to add new feeds, you should
3389 # use OutputPage::addFeedLink() instead.
3390 Hooks::run( 'AfterBuildFeedLinks', [ &$feedLinks ] );
3391
3392 $tags += $feedLinks;
3393 }
3394
3395 # Canonical URL
3396 if ( $config->get( 'EnableCanonicalServerLink' ) ) {
3397 if ( $canonicalUrl !== false ) {
3398 $canonicalUrl = wfExpandUrl( $canonicalUrl, PROTO_CANONICAL );
3399 } else {
3400 if ( $this->isArticleRelated() ) {
3401 // This affects all requests where "setArticleRelated" is true. This is
3402 // typically all requests that show content (query title, curid, oldid, diff),
3403 // and all wikipage actions (edit, delete, purge, info, history etc.).
3404 // It does not apply to File pages and Special pages.
3405 // 'history' and 'info' actions address page metadata rather than the page
3406 // content itself, so they may not be canonicalized to the view page url.
3407 // TODO: this ought to be better encapsulated in the Action class.
3408 $action = Action::getActionName( $this->getContext() );
3409 if ( in_array( $action, [ 'history', 'info' ] ) ) {
3410 $query = "action={$action}";
3411 } else {
3412 $query = '';
3413 }
3414 $canonicalUrl = $this->getTitle()->getCanonicalURL( $query );
3415 } else {
3416 $reqUrl = $this->getRequest()->getRequestURL();
3417 $canonicalUrl = wfExpandUrl( $reqUrl, PROTO_CANONICAL );
3418 }
3419 }
3420 }
3421 if ( $canonicalUrl !== false ) {
3422 $tags[] = Html::element( 'link', [
3423 'rel' => 'canonical',
3424 'href' => $canonicalUrl
3425 ] );
3426 }
3427
3428 return $tags;
3429 }
3430
3436 public function getHeadLinks() {
3437 wfDeprecated( __METHOD__, '1.24' );
3438 return implode( "\n", $this->getHeadLinksArray() );
3439 }
3440
3449 private function feedLink( $type, $url, $text ) {
3450 return Html::element( 'link', [
3451 'rel' => 'alternate',
3452 'type' => "application/$type+xml",
3453 'title' => $text,
3454 'href' => $url ]
3455 );
3456 }
3457
3467 public function addStyle( $style, $media = '', $condition = '', $dir = '' ) {
3468 $options = [];
3469 if ( $media ) {
3470 $options['media'] = $media;
3471 }
3472 if ( $condition ) {
3473 $options['condition'] = $condition;
3474 }
3475 if ( $dir ) {
3476 $options['dir'] = $dir;
3477 }
3478 $this->styles[$style] = $options;
3479 }
3480
3488 public function addInlineStyle( $style_css, $flip = 'noflip' ) {
3489 if ( $flip === 'flip' && $this->getLanguage()->isRTL() ) {
3490 # If wanted, and the interface is right-to-left, flip the CSS
3491 $style_css = CSSJanus::transform( $style_css, true, false );
3492 }
3493 $this->mInlineStyles .= Html::inlineStyle( $style_css );
3494 }
3495
3501 protected function buildExemptModules() {
3503
3505 $chunks = [];
3506 // Things that go after the ResourceLoaderDynamicStyles marker
3507 $append = [];
3508
3509 // Exempt 'user' styles module (may need 'excludepages' for live preview)
3510 if ( $this->isUserCssPreview() ) {
3511 $append[] = $this->makeResourceLoaderLink(
3512 'user.styles',
3514 [ 'excludepage' => $this->getTitle()->getPrefixedDBkey() ]
3515 );
3516
3517 // Load the previewed CSS. Janus it if needed.
3518 // User-supplied CSS is assumed to in the wiki's content language.
3519 $previewedCSS = $this->getRequest()->getText( 'wpTextbox1' );
3520 if ( $this->getLanguage()->getDir() !== $wgContLang->getDir() ) {
3521 $previewedCSS = CSSJanus::transform( $previewedCSS, true, false );
3522 }
3523 $append[] = Html::inlineStyle( $previewedCSS );
3524 }
3525
3526 // We want site, private and user styles to override dynamically added styles from
3527 // general modules, but we want dynamically added styles to override statically added
3528 // style modules. So the order has to be:
3529 // - page style modules (formatted by ResourceLoaderClientHtml::getHeadHtml())
3530 // - dynamically loaded styles (added by mw.loader before ResourceLoaderDynamicStyles)
3531 // - ResourceLoaderDynamicStyles marker
3532 // - site/private/user styles
3533
3534 // Add legacy styles added through addStyle()/addInlineStyle() here
3535 $chunks[] = implode( '', $this->buildCssLinksArray() ) . $this->mInlineStyles;
3536
3537 $chunks[] = Html::element(
3538 'meta',
3539 [ 'name' => 'ResourceLoaderDynamicStyles', 'content' => '' ]
3540 );
3541
3542 foreach ( $this->rlExemptStyleModules as $group => $moduleNames ) {
3543 $chunks[] = $this->makeResourceLoaderLink( $moduleNames,
3545 );
3546 }
3547
3548 return self::combineWrappedStrings( array_merge( $chunks, $append ) );
3549 }
3550
3554 public function buildCssLinksArray() {
3555 $links = [];
3556
3557 // Add any extension CSS
3558 foreach ( $this->mExtStyles as $url ) {
3559 $this->addStyle( $url );
3560 }
3561 $this->mExtStyles = [];
3562
3563 foreach ( $this->styles as $file => $options ) {
3564 $link = $this->styleLink( $file, $options );
3565 if ( $link ) {
3566 $links[$file] = $link;
3567 }
3568 }
3569 return $links;
3570 }
3571
3579 protected function styleLink( $style, array $options ) {
3580 if ( isset( $options['dir'] ) ) {
3581 if ( $this->getLanguage()->getDir() != $options['dir'] ) {
3582 return '';
3583 }
3584 }
3585
3586 if ( isset( $options['media'] ) ) {
3587 $media = self::transformCssMedia( $options['media'] );
3588 if ( is_null( $media ) ) {
3589 return '';
3590 }
3591 } else {
3592 $media = 'all';
3593 }
3594
3595 if ( substr( $style, 0, 1 ) == '/' ||
3596 substr( $style, 0, 5 ) == 'http:' ||
3597 substr( $style, 0, 6 ) == 'https:' ) {
3598 $url = $style;
3599 } else {
3600 $config = $this->getConfig();
3601 $url = $config->get( 'StylePath' ) . '/' . $style . '?' .
3602 $config->get( 'StyleVersion' );
3603 }
3604
3605 $link = Html::linkedStyle( $url, $media );
3606
3607 if ( isset( $options['condition'] ) ) {
3608 $condition = htmlspecialchars( $options['condition'] );
3609 $link = "<!--[if $condition]>$link<![endif]-->";
3610 }
3611 return $link;
3612 }
3613
3635 public static function transformResourcePath( Config $config, $path ) {
3636 global $IP;
3637 $remotePathPrefix = $config->get( 'ResourceBasePath' );
3638 if ( $remotePathPrefix === '' ) {
3639 // The configured base path is required to be empty string for
3640 // wikis in the domain root
3641 $remotePath = '/';
3642 } else {
3643 $remotePath = $remotePathPrefix;
3644 }
3645 if ( strpos( $path, $remotePath ) !== 0 ) {
3646 // Path is outside wgResourceBasePath, ignore.
3647 return $path;
3648 }
3649 $path = RelPath\getRelativePath( $path, $remotePath );
3650 return self::transformFilePath( $remotePathPrefix, $IP, $path );
3651 }
3652
3664 public static function transformFilePath( $remotePathPrefix, $localPath, $file ) {
3665 $hash = md5_file( "$localPath/$file" );
3666 if ( $hash === false ) {
3667 wfLogWarning( __METHOD__ . ": Failed to hash $localPath/$file" );
3668 $hash = '';
3669 }
3670 return "$remotePathPrefix/$file?" . substr( $hash, 0, 5 );
3671 }
3672
3680 public static function transformCssMedia( $media ) {
3682
3683 // http://www.w3.org/TR/css3-mediaqueries/#syntax
3684 $screenMediaQueryRegex = '/^(?:only\s+)?screen\b/i';
3685
3686 // Switch in on-screen display for media testing
3687 $switches = [
3688 'printable' => 'print',
3689 'handheld' => 'handheld',
3690 ];
3691 foreach ( $switches as $switch => $targetMedia ) {
3692 if ( $wgRequest->getBool( $switch ) ) {
3693 if ( $media == $targetMedia ) {
3694 $media = '';
3695 } elseif ( preg_match( $screenMediaQueryRegex, $media ) === 1 ) {
3696 /* This regex will not attempt to understand a comma-separated media_query_list
3697 *
3698 * Example supported values for $media:
3699 * 'screen', 'only screen', 'screen and (min-width: 982px)' ),
3700 * Example NOT supported value for $media:
3701 * '3d-glasses, screen, print and resolution > 90dpi'
3702 *
3703 * If it's a print request, we never want any kind of screen stylesheets
3704 * If it's a handheld request (currently the only other choice with a switch),
3705 * we don't want simple 'screen' but we might want screen queries that
3706 * have a max-width or something, so we'll pass all others on and let the
3707 * client do the query.
3708 */
3709 if ( $targetMedia == 'print' || $media == 'screen' ) {
3710 return null;
3711 }
3712 }
3713 }
3714 }
3715
3716 return $media;
3717 }
3718
3725 public function addWikiMsg( /*...*/ ) {
3726 $args = func_get_args();
3727 $name = array_shift( $args );
3728 $this->addWikiMsgArray( $name, $args );
3729 }
3730
3739 public function addWikiMsgArray( $name, $args ) {
3740 $this->addHTML( $this->msg( $name, $args )->parseAsBlock() );
3741 }
3742
3768 public function wrapWikiMsg( $wrap /*, ...*/ ) {
3769 $msgSpecs = func_get_args();
3770 array_shift( $msgSpecs );
3771 $msgSpecs = array_values( $msgSpecs );
3772 $s = $wrap;
3773 foreach ( $msgSpecs as $n => $spec ) {
3774 if ( is_array( $spec ) ) {
3775 $args = $spec;
3776 $name = array_shift( $args );
3777 if ( isset( $args['options'] ) ) {
3778 unset( $args['options'] );
3780 'Adding "options" to ' . __METHOD__ . ' is no longer supported',
3781 '1.20'
3782 );
3783 }
3784 } else {
3785 $args = [];
3786 $name = $spec;
3787 }
3788 $s = str_replace( '$' . ( $n + 1 ), $this->msg( $name, $args )->plain(), $s );
3789 }
3790 $this->addWikiText( $s );
3791 }
3792
3798 public function enableTOC( $flag = true ) {
3799 $this->mEnableTOC = $flag;
3800 }
3801
3806 public function isTOCEnabled() {
3807 return $this->mEnableTOC;
3808 }
3809
3815 public function enableSectionEditLinks( $flag = true ) {
3816 $this->mEnableSectionEditLinks = $flag;
3817 }
3818
3823 public function sectionEditLinksEnabled() {
3825 }
3826
3834 public static function setupOOUI( $skinName = '', $dir = 'ltr' ) {
3835 $themes = ExtensionRegistry::getInstance()->getAttribute( 'SkinOOUIThemes' );
3836 // Make keys (skin names) lowercase for case-insensitive matching.
3837 $themes = array_change_key_case( $themes, CASE_LOWER );
3838 $theme = isset( $themes[$skinName] ) ? $themes[$skinName] : 'MediaWiki';
3839 // For example, 'OOUI\MediaWikiTheme'.
3840 $themeClass = "OOUI\\{$theme}Theme";
3841 OOUI\Theme::setSingleton( new $themeClass() );
3842 OOUI\Element::setDefaultDir( $dir );
3843 }
3844
3851 public function enableOOUI() {
3853 strtolower( $this->getSkin()->getSkinName() ),
3854 $this->getLanguage()->getDir()
3855 );
3856 $this->addModuleStyles( [
3857 'oojs-ui-core.styles',
3858 'oojs-ui.styles.icons',
3859 'oojs-ui.styles.indicators',
3860 'oojs-ui.styles.textures',
3861 'mediawiki.widgets.styles',
3862 ] );
3863 }
3864}
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:821
if(! $wgDBerrorLogTZ) $wgRequest
Definition Setup.php:664
$IP
Definition WebStart.php:58
if( $line===false) $args
Definition cdb.php:64
static getActionName(IContextSource $context)
Get the action that will be executed, not necessarily the one passed passed through the "action" requ...
Definition Action.php:122
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition Article.php:911
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:50
exists()
Returns true if file exists in the repository.
Definition File.php:876
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition LinkBatch.php:32
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:96
static link( $target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition Linker.php:203
static linkKnown( $target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to 'known'.
Definition Linker.php:255
MediaWiki exception.
PSR-3 logger instance factory.
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.
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.
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?
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.
getCategories()
Get the list of category names this page belongs to.
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...
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.
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.
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,...
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.
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 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:396
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:412
getPageClasses( $title)
TODO: document.
Definition Skin.php:368
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:36
A wrapper class which causes Xml::encodeJsVar() and Xml::encodeJsCall() to interpret a given string a...
Definition Xml.php:884
static encodeJsCall( $name, $args, $pretty=false)
Create a call to a JavaScript function.
Definition Xml.php:682
$res
Definition database.txt:21
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the local content language as $wgContLang
Definition design.txt:57
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition design.txt:18
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
while(( $__line=Maintenance::readconsole()) !==false) print
Definition eval.php:64
const PROTO_CANONICAL
Definition Defines.php:227
const PROTO_CURRENT
Definition Defines.php:226
const NS_SPECIAL
Definition Defines.php:45
const PROTO_RELATIVE
Definition Defines.php:225
const NS_CATEGORY
Definition Defines.php:70
the array() calling protocol came about after MediaWiki 1.4rc1.
static configuration should be added through ResourceLoaderGetConfigVars instead & $vars
Definition hooks.txt:2162
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 hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context $parserOutput
Definition hooks.txt:1090
namespace are movable Hooks may change this value to override the return value of MWNamespace::isMovable(). 'NewDifferenceEngine' do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition hooks.txt:2568
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:853
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:986
either a plain
Definition hooks.txt:1990
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition hooks.txt:1096
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 $request
Definition hooks.txt:2685
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books $tag
Definition hooks.txt:1033
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition hooks.txt:2900
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:1958
this hook is for auditing only $response
Definition hooks.txt:805
error also a ContextSource you ll probably need to make sure the header is varied on such as when responding to a resource loader request or generating HTML output & $resourceLoader
Definition hooks.txt:2692
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
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:1595
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:1233
returning false will NOT prevent logging $e
Definition hooks.txt:2110
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:887
if(count( $args)==0) $dir
if( $limit) $timestamp
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
Interface for configuration instances.
Definition Config.php:28
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
Interface for objects which can provide a MediaWiki context on request.
const DB_REPLICA
Definition defines.php:22
$params
if(!isset( $args[0])) $lang
const TS_ISO_8601
ISO 8601 format with no timezone: 1986-02-09T20:00:00Z.
Definition defines.php:28
const TS_UNIX
Unix time - the number of seconds since 1970-01-01 00:00:00 UTC.
Definition defines.php:6
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
Definition defines.php:11
const TS_RFC2822
RFC 2822 format, for E-mail and HTTP headers.
Definition defines.php:21
$header