3 * Options for the PHP parser
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
24 use MediaWiki\MediaWikiServices
;
25 use Wikimedia\ScopedCallback
;
28 * @brief Set options of the Parser
30 * How to add an option in core:
31 * 1. Add it to one of the arrays in ParserOptions::setDefaults()
32 * 2. If necessary, add an entry to ParserOptions::$inCacheKey
33 * 3. Add a getter and setter in the section for that.
35 * How to add an option in an extension:
36 * 1. Use the 'ParserOptionsRegister' hook to register it.
37 * 2. Where necessary, use $popt->getOption() and $popt->setOption()
45 * Flag indicating that newCanonical() accepts an IContextSource or the string 'canonical', for
46 * back-compat checks from extensions.
49 const HAS_NEWCANONICAL_FROM_CONTEXT
= 1;
52 * Default values for all options that are relevant for caching.
53 * @see self::getDefaults()
56 private static $defaults = null;
62 private static $lazyOptions = [
63 'dateformat' => [ __CLASS__
, 'initDateFormat' ],
64 'speculativeRevId' => [ __CLASS__
, 'initSpeculativeRevId' ],
65 'speculativePageId' => [ __CLASS__
, 'initSpeculativePageId' ],
69 * Specify options that are included in the cache key
72 private static $inCacheKey = [
74 'numberheadings' => true,
76 'stubthreshold' => true,
82 * Current values for all options that are relevant for caching.
88 * Timestamp used for {{CURRENTDAY}} etc.
90 * @note Caching based on parse time is handled externally
97 * @todo Track this for caching somehow without fragmenting the cache insanely
102 * Function to be called when an option is accessed.
104 * @note Used for collecting used options, does not affect caching
106 private $onAccessCallback = null;
109 * If the page being parsed is a redirect, this should hold the redirect
112 * @todo Track this for caching somehow
114 private $redirectTarget = null;
117 * Appended to the options hash
119 private $mExtraKey = '';
122 * Fetch an option and track that is was accessed
124 * @param string $name Option name
127 public function getOption( $name ) {
128 if ( !array_key_exists( $name, $this->options
) ) {
129 throw new InvalidArgumentException( "Unknown parser option $name" );
132 $this->lazyLoadOption( $name );
133 if ( !empty( self
::$inCacheKey[$name] ) ) {
134 $this->optionUsed( $name );
136 return $this->options
[$name];
140 * @param string $name Lazy load option without tracking usage
142 private function lazyLoadOption( $name ) {
143 if ( isset( self
::$lazyOptions[$name] ) && $this->options
[$name] === null ) {
144 $this->options
[$name] = call_user_func( self
::$lazyOptions[$name], $this, $name );
149 * Set an option, generically
151 * @param string $name Option name
152 * @param mixed $value New value. Passing null will set null, unlike many
153 * of the existing accessors which ignore null for historical reasons.
154 * @return mixed Old value
156 public function setOption( $name, $value ) {
157 if ( !array_key_exists( $name, $this->options
) ) {
158 throw new InvalidArgumentException( "Unknown parser option $name" );
160 $old = $this->options
[$name];
161 $this->options
[$name] = $value;
166 * Legacy implementation
167 * @since 1.30 For implementing legacy setters only. Don't use this in new code.
168 * @deprecated since 1.30
169 * @param string $name Option name
170 * @param mixed $value New value. Passing null does not set the value.
171 * @return mixed Old value
173 protected function setOptionLegacy( $name, $value ) {
174 if ( !array_key_exists( $name, $this->options
) ) {
175 throw new InvalidArgumentException( "Unknown parser option $name" );
177 return wfSetVar( $this->options
[$name], $value );
181 * Whether to extract interlanguage links
183 * When true, interlanguage links will be returned by
184 * ParserOutput::getLanguageLinks() instead of generating link HTML.
188 public function getInterwikiMagic() {
189 return $this->getOption( 'interwikiMagic' );
193 * Specify whether to extract interlanguage links
194 * @param bool|null $x New value (null is no change)
195 * @return bool Old value
197 public function setInterwikiMagic( $x ) {
198 return $this->setOptionLegacy( 'interwikiMagic', $x );
202 * Allow all external images inline?
205 public function getAllowExternalImages() {
206 return $this->getOption( 'allowExternalImages' );
210 * Allow all external images inline?
211 * @param bool|null $x New value (null is no change)
212 * @return bool Old value
214 public function setAllowExternalImages( $x ) {
215 return $this->setOptionLegacy( 'allowExternalImages', $x );
219 * External images to allow
221 * When self::getAllowExternalImages() is false
223 * @return string|string[] URLs to allow
225 public function getAllowExternalImagesFrom() {
226 return $this->getOption( 'allowExternalImagesFrom' );
230 * External images to allow
232 * When self::getAllowExternalImages() is false
234 * @param string|string[]|null $x New value (null is no change)
235 * @return string|string[] Old value
237 public function setAllowExternalImagesFrom( $x ) {
238 return $this->setOptionLegacy( 'allowExternalImagesFrom', $x );
242 * Use the on-wiki external image whitelist?
245 public function getEnableImageWhitelist() {
246 return $this->getOption( 'enableImageWhitelist' );
250 * Use the on-wiki external image whitelist?
251 * @param bool|null $x New value (null is no change)
252 * @return bool Old value
254 public function setEnableImageWhitelist( $x ) {
255 return $this->setOptionLegacy( 'enableImageWhitelist', $x );
259 * Automatically number headings?
262 public function getNumberHeadings() {
263 return $this->getOption( 'numberheadings' );
267 * Automatically number headings?
268 * @param bool|null $x New value (null is no change)
269 * @return bool Old value
271 public function setNumberHeadings( $x ) {
272 return $this->setOptionLegacy( 'numberheadings', $x );
276 * Allow inclusion of special pages?
279 public function getAllowSpecialInclusion() {
280 return $this->getOption( 'allowSpecialInclusion' );
284 * Allow inclusion of special pages?
285 * @param bool|null $x New value (null is no change)
286 * @return bool Old value
288 public function setAllowSpecialInclusion( $x ) {
289 return $this->setOptionLegacy( 'allowSpecialInclusion', $x );
293 * Use tidy to cleanup output HTML?
296 public function getTidy() {
297 return $this->getOption( 'tidy' );
301 * Use tidy to cleanup output HTML?
302 * @param bool|null $x New value (null is no change)
303 * @return bool Old value
305 public function setTidy( $x ) {
306 return $this->setOptionLegacy( 'tidy', $x );
310 * Parsing an interface message?
313 public function getInterfaceMessage() {
314 return $this->getOption( 'interfaceMessage' );
318 * Parsing an interface message?
319 * @param bool|null $x New value (null is no change)
320 * @return bool Old value
322 public function setInterfaceMessage( $x ) {
323 return $this->setOptionLegacy( 'interfaceMessage', $x );
327 * Target language for the parse
328 * @return Language|null
330 public function getTargetLanguage() {
331 return $this->getOption( 'targetLanguage' );
335 * Target language for the parse
336 * @param Language|null $x New value
337 * @return Language|null Old value
339 public function setTargetLanguage( $x ) {
340 return $this->setOption( 'targetLanguage', $x );
344 * Maximum size of template expansions, in bytes
347 public function getMaxIncludeSize() {
348 return $this->getOption( 'maxIncludeSize' );
352 * Maximum size of template expansions, in bytes
353 * @param int|null $x New value (null is no change)
354 * @return int Old value
356 public function setMaxIncludeSize( $x ) {
357 return $this->setOptionLegacy( 'maxIncludeSize', $x );
361 * Maximum number of nodes touched by PPFrame::expand()
364 public function getMaxPPNodeCount() {
365 return $this->getOption( 'maxPPNodeCount' );
369 * Maximum number of nodes touched by PPFrame::expand()
370 * @param int|null $x New value (null is no change)
371 * @return int Old value
373 public function setMaxPPNodeCount( $x ) {
374 return $this->setOptionLegacy( 'maxPPNodeCount', $x );
378 * Maximum number of nodes generated by Preprocessor::preprocessToObj()
381 public function getMaxGeneratedPPNodeCount() {
382 return $this->getOption( 'maxGeneratedPPNodeCount' );
386 * Maximum number of nodes generated by Preprocessor::preprocessToObj()
387 * @param int|null $x New value (null is no change)
390 public function setMaxGeneratedPPNodeCount( $x ) {
391 return $this->setOptionLegacy( 'maxGeneratedPPNodeCount', $x );
395 * Maximum recursion depth in PPFrame::expand()
398 public function getMaxPPExpandDepth() {
399 return $this->getOption( 'maxPPExpandDepth' );
403 * Maximum recursion depth for templates within templates
406 public function getMaxTemplateDepth() {
407 return $this->getOption( 'maxTemplateDepth' );
411 * Maximum recursion depth for templates within templates
412 * @param int|null $x New value (null is no change)
413 * @return int Old value
415 public function setMaxTemplateDepth( $x ) {
416 return $this->setOptionLegacy( 'maxTemplateDepth', $x );
420 * Maximum number of calls per parse to expensive parser functions
424 public function getExpensiveParserFunctionLimit() {
425 return $this->getOption( 'expensiveParserFunctionLimit' );
429 * Maximum number of calls per parse to expensive parser functions
431 * @param int|null $x New value (null is no change)
432 * @return int Old value
434 public function setExpensiveParserFunctionLimit( $x ) {
435 return $this->setOptionLegacy( 'expensiveParserFunctionLimit', $x );
439 * Remove HTML comments
440 * @warning Only applies to preprocess operations
443 public function getRemoveComments() {
444 return $this->getOption( 'removeComments' );
448 * Remove HTML comments
449 * @warning Only applies to preprocess operations
450 * @param bool|null $x New value (null is no change)
451 * @return bool Old value
453 public function setRemoveComments( $x ) {
454 return $this->setOptionLegacy( 'removeComments', $x );
458 * Enable limit report in an HTML comment on output
461 public function getEnableLimitReport() {
462 return $this->getOption( 'enableLimitReport' );
466 * Enable limit report in an HTML comment on output
467 * @param bool|null $x New value (null is no change)
468 * @return bool Old value
470 public function enableLimitReport( $x = true ) {
471 return $this->setOptionLegacy( 'enableLimitReport', $x );
475 * Clean up signature texts?
476 * @see Parser::cleanSig
479 public function getCleanSignatures() {
480 return $this->getOption( 'cleanSignatures' );
484 * Clean up signature texts?
485 * @see Parser::cleanSig
486 * @param bool|null $x New value (null is no change)
487 * @return bool Old value
489 public function setCleanSignatures( $x ) {
490 return $this->setOptionLegacy( 'cleanSignatures', $x );
494 * Target attribute for external links
497 public function getExternalLinkTarget() {
498 return $this->getOption( 'externalLinkTarget' );
502 * Target attribute for external links
503 * @param string|null $x New value (null is no change)
504 * @return string Old value
506 public function setExternalLinkTarget( $x ) {
507 return $this->setOptionLegacy( 'externalLinkTarget', $x );
511 * Whether content conversion should be disabled
514 public function getDisableContentConversion() {
515 return $this->getOption( 'disableContentConversion' );
519 * Whether content conversion should be disabled
520 * @param bool|null $x New value (null is no change)
521 * @return bool Old value
523 public function disableContentConversion( $x = true ) {
524 return $this->setOptionLegacy( 'disableContentConversion', $x );
528 * Whether title conversion should be disabled
531 public function getDisableTitleConversion() {
532 return $this->getOption( 'disableTitleConversion' );
536 * Whether title conversion should be disabled
537 * @param bool|null $x New value (null is no change)
538 * @return bool Old value
540 public function disableTitleConversion( $x = true ) {
541 return $this->setOptionLegacy( 'disableTitleConversion', $x );
545 * Thumb size preferred by the user.
548 public function getThumbSize() {
549 return $this->getOption( 'thumbsize' );
553 * Thumb size preferred by the user.
554 * @param int|null $x New value (null is no change)
555 * @return int Old value
557 public function setThumbSize( $x ) {
558 return $this->setOptionLegacy( 'thumbsize', $x );
562 * Thumb size preferred by the user.
565 public function getStubThreshold() {
566 return $this->getOption( 'stubthreshold' );
570 * Thumb size preferred by the user.
571 * @param int|null $x New value (null is no change)
572 * @return int Old value
574 public function setStubThreshold( $x ) {
575 return $this->setOptionLegacy( 'stubthreshold', $x );
579 * Parsing the page for a "preview" operation?
582 public function getIsPreview() {
583 return $this->getOption( 'isPreview' );
587 * Parsing the page for a "preview" operation?
588 * @param bool|null $x New value (null is no change)
589 * @return bool Old value
591 public function setIsPreview( $x ) {
592 return $this->setOptionLegacy( 'isPreview', $x );
596 * Parsing the page for a "preview" operation on a single section?
599 public function getIsSectionPreview() {
600 return $this->getOption( 'isSectionPreview' );
604 * Parsing the page for a "preview" operation on a single section?
605 * @param bool|null $x New value (null is no change)
606 * @return bool Old value
608 public function setIsSectionPreview( $x ) {
609 return $this->setOptionLegacy( 'isSectionPreview', $x );
613 * Parsing the printable version of the page?
616 public function getIsPrintable() {
617 return $this->getOption( 'printable' );
621 * Parsing the printable version of the page?
622 * @param bool|null $x New value (null is no change)
623 * @return bool Old value
625 public function setIsPrintable( $x ) {
626 return $this->setOptionLegacy( 'printable', $x );
630 * Transform wiki markup when saving the page?
633 public function getPreSaveTransform() {
634 return $this->getOption( 'preSaveTransform' );
638 * Transform wiki markup when saving the page?
639 * @param bool|null $x New value (null is no change)
640 * @return bool Old value
642 public function setPreSaveTransform( $x ) {
643 return $this->setOptionLegacy( 'preSaveTransform', $x );
650 public function getDateFormat() {
651 return $this->getOption( 'dateformat' );
655 * Lazy initializer for dateFormat
656 * @param ParserOptions $popt
659 private static function initDateFormat( ParserOptions
$popt ) {
660 return $popt->mUser
->getDatePreference();
665 * @param string|null $x New value (null is no change)
666 * @return string Old value
668 public function setDateFormat( $x ) {
669 return $this->setOptionLegacy( 'dateformat', $x );
673 * Get the user language used by the parser for this page and split the parser cache.
675 * @warning Calling this causes the parser cache to be fragmented by user language!
676 * To avoid cache fragmentation, output should not depend on the user language.
677 * Use Parser::getFunctionLang() or Parser::getTargetLanguage() instead!
679 * @note This function will trigger a cache fragmentation by recording the
680 * 'userlang' option, see optionUsed(). This is done to avoid cache pollution
681 * when the page is rendered based on the language of the user.
683 * @note When saving, this will return the default language instead of the user's.
684 * {{int: }} uses this which used to produce inconsistent link tables (T16404).
689 public function getUserLangObj() {
690 return $this->getOption( 'userlang' );
694 * Same as getUserLangObj() but returns a string instead.
696 * @warning Calling this causes the parser cache to be fragmented by user language!
697 * To avoid cache fragmentation, output should not depend on the user language.
698 * Use Parser::getFunctionLang() or Parser::getTargetLanguage() instead!
700 * @see getUserLangObj()
702 * @return string Language code
705 public function getUserLang() {
706 return $this->getUserLangObj()->getCode();
710 * Set the user language used by the parser for this page and split the parser cache.
711 * @param string|Language $x New value
712 * @return Language Old value
714 public function setUserLang( $x ) {
715 if ( is_string( $x ) ) {
716 $x = Language
::factory( $x );
719 return $this->setOptionLegacy( 'userlang', $x );
723 * Are magic ISBN links enabled?
727 public function getMagicISBNLinks() {
728 return $this->getOption( 'magicISBNLinks' );
732 * Are magic PMID links enabled?
736 public function getMagicPMIDLinks() {
737 return $this->getOption( 'magicPMIDLinks' );
741 * Are magic RFC links enabled?
745 public function getMagicRFCLinks() {
746 return $this->getOption( 'magicRFCLinks' );
750 * If the wiki is configured to allow raw html ($wgRawHtml = true)
751 * is it allowed in the specific case of parsing this page.
753 * This is meant to disable unsafe parser tags in cases where
754 * a malicious user may control the input to the parser.
756 * @note This is expected to be true for normal pages even if the
757 * wiki has $wgRawHtml disabled in general. The setting only
758 * signifies that raw html would be unsafe in the current context
759 * provided that raw html is allowed at all.
763 public function getAllowUnsafeRawHtml() {
764 return $this->getOption( 'allowUnsafeRawHtml' );
768 * If the wiki is configured to allow raw html ($wgRawHtml = true)
769 * is it allowed in the specific case of parsing this page.
770 * @see self::getAllowUnsafeRawHtml()
772 * @param bool|null $x Value to set or null to get current value
773 * @return bool Current value for allowUnsafeRawHtml
775 public function setAllowUnsafeRawHtml( $x ) {
776 return $this->setOptionLegacy( 'allowUnsafeRawHtml', $x );
780 * Class to use to wrap output from Parser::parse()
782 * @return string|bool
784 public function getWrapOutputClass() {
785 return $this->getOption( 'wrapclass' );
789 * CSS class to use to wrap output from Parser::parse()
791 * @param string $className Class name to use for wrapping.
792 * Passing false to indicate "no wrapping" was deprecated in MediaWiki 1.31.
793 * @return string|bool Current value
795 public function setWrapOutputClass( $className ) {
796 if ( $className === true ) { // DWIM, they probably want the default class name
797 $className = 'mw-parser-output';
799 if ( $className === false ) {
800 wfDeprecated( __METHOD__
. '( false )', '1.31' );
802 return $this->setOption( 'wrapclass', $className );
806 * Callback for current revision fetching; first argument to call_user_func().
810 public function getCurrentRevisionCallback() {
811 return $this->getOption( 'currentRevisionCallback' );
815 * Callback for current revision fetching; first argument to call_user_func().
817 * @param callable|null $x New value (null is no change)
818 * @return callable Old value
820 public function setCurrentRevisionCallback( $x ) {
821 return $this->setOptionLegacy( 'currentRevisionCallback', $x );
825 * Callback for template fetching; first argument to call_user_func().
828 public function getTemplateCallback() {
829 return $this->getOption( 'templateCallback' );
833 * Callback for template fetching; first argument to call_user_func().
834 * @param callable|null $x New value (null is no change)
835 * @return callable Old value
837 public function setTemplateCallback( $x ) {
838 return $this->setOptionLegacy( 'templateCallback', $x );
842 * A guess for {{REVISIONID}}, calculated using the callback provided via
843 * setSpeculativeRevIdCallback(). For consistency, the value will be calculated upon the
844 * first call of this method, and re-used for subsequent calls.
846 * If no callback was defined via setSpeculativeRevIdCallback(), this method will return false.
851 public function getSpeculativeRevId() {
852 return $this->getOption( 'speculativeRevId' );
856 * A guess for {{PAGEID}}, calculated using the callback provided via
857 * setSpeculativeRevPageCallback(). For consistency, the value will be calculated upon the
858 * first call of this method, and re-used for subsequent calls.
860 * If no callback was defined via setSpeculativePageIdCallback(), this method will return false.
865 public function getSpeculativePageId() {
866 return $this->getOption( 'speculativePageId' );
870 * Callback registered with ParserOptions::$lazyOptions, triggered by getSpeculativeRevId().
872 * @param ParserOptions $popt
875 private static function initSpeculativeRevId( ParserOptions
$popt ) {
876 $cb = $popt->getOption( 'speculativeRevIdCallback' );
877 $id = $cb ?
$cb() : null;
879 // returning null would result in this being re-called every access
884 * Callback registered with ParserOptions::$lazyOptions, triggered by getSpeculativePageId().
886 * @param ParserOptions $popt
889 private static function initSpeculativePageId( ParserOptions
$popt ) {
890 $cb = $popt->getOption( 'speculativePageIdCallback' );
891 $id = $cb ?
$cb() : null;
893 // returning null would result in this being re-called every access
898 * Callback to generate a guess for {{REVISIONID}}
899 * @param callable|null $x New value
900 * @return callable|null Old value
903 public function setSpeculativeRevIdCallback( $x ) {
904 $this->setOption( 'speculativeRevId', null ); // reset
905 return $this->setOption( 'speculativeRevIdCallback', $x );
909 * Callback to generate a guess for {{PAGEID}}
910 * @param callable|null $x New value
911 * @return callable|null Old value
914 public function setSpeculativePageIdCallback( $x ) {
915 $this->setOption( 'speculativePageId', null ); // reset
916 return $this->setOption( 'speculativePageIdCallback', $x );
920 * Timestamp used for {{CURRENTDAY}} etc.
921 * @return string TS_MW timestamp
923 public function getTimestamp() {
924 if ( !isset( $this->mTimestamp
) ) {
925 $this->mTimestamp
= wfTimestampNow();
927 return $this->mTimestamp
;
931 * Timestamp used for {{CURRENTDAY}} etc.
932 * @param string|null $x New value (null is no change)
933 * @return string Old value
935 public function setTimestamp( $x ) {
936 return wfSetVar( $this->mTimestamp
, $x );
940 * Set the redirect target.
942 * Note that setting or changing this does not *make* the page a redirect
943 * or change its target, it merely records the information for reference
947 * @param Title|null $title
949 function setRedirectTarget( $title ) {
950 $this->redirectTarget
= $title;
954 * Get the previously-set redirect target.
959 function getRedirectTarget() {
960 return $this->redirectTarget
;
964 * Extra key that should be present in the parser cache key.
965 * @warning Consider registering your additional options with the
966 * ParserOptionsRegister hook instead of using this method.
969 public function addExtraKey( $key ) {
970 $this->mExtraKey
.= '!' . $key;
977 public function getUser() {
982 * @warning For interaction with the parser cache, use
983 * WikiPage::makeParserOptions() or ParserOptions::newCanonical() instead.
984 * @param User|null $user
985 * @param Language|null $lang
987 public function __construct( $user = null, $lang = null ) {
988 if ( $user === null ) {
990 if ( $wgUser === null ) {
996 if ( $lang === null ) {
998 if ( !StubObject
::isRealObject( $wgLang ) ) {
1003 $this->initialiseFromUser( $user, $lang );
1007 * Get a ParserOptions object for an anonymous user
1008 * @warning For interaction with the parser cache, use
1009 * WikiPage::makeParserOptions() or ParserOptions::newCanonical() instead.
1011 * @return ParserOptions
1013 public static function newFromAnon() {
1014 return new ParserOptions( new User
,
1015 MediaWikiServices
::getInstance()->getContentLanguage() );
1019 * Get a ParserOptions object from a given user.
1020 * Language will be taken from $wgLang.
1022 * @warning For interaction with the parser cache, use
1023 * WikiPage::makeParserOptions() or ParserOptions::newCanonical() instead.
1025 * @return ParserOptions
1027 public static function newFromUser( $user ) {
1028 return new ParserOptions( $user );
1032 * Get a ParserOptions object from a given user and language
1034 * @warning For interaction with the parser cache, use
1035 * WikiPage::makeParserOptions() or ParserOptions::newCanonical() instead.
1037 * @param Language $lang
1038 * @return ParserOptions
1040 public static function newFromUserAndLang( User
$user, Language
$lang ) {
1041 return new ParserOptions( $user, $lang );
1045 * Get a ParserOptions object from a IContextSource object
1047 * @warning For interaction with the parser cache, use
1048 * WikiPage::makeParserOptions() or ParserOptions::newCanonical() instead.
1049 * @param IContextSource $context
1050 * @return ParserOptions
1052 public static function newFromContext( IContextSource
$context ) {
1053 return new ParserOptions( $context->getUser(), $context->getLanguage() );
1057 * Creates a "canonical" ParserOptions object
1059 * For historical reasons, certain options have default values that are
1060 * different from the canonical values used for caching.
1063 * @since 1.32 Added string and IContextSource as options for the first parameter
1064 * @param IContextSource|string|User|null $context
1065 * - If an IContextSource, the options are initialized based on the source's User and Language.
1066 * - If the string 'canonical', the options are initialized with an anonymous user and
1067 * the content language.
1068 * - If a User or null, the options are initialized for that User (or $wgUser if null).
1069 * 'userlang' is taken from the $userLang parameter, defaulting to $wgLang if that is null.
1070 * @param Language|StubObject|null $userLang (see above)
1071 * @return ParserOptions
1073 public static function newCanonical( $context = null, $userLang = null ) {
1074 if ( $context instanceof IContextSource
) {
1075 $ret = self
::newFromContext( $context );
1076 } elseif ( $context === 'canonical' ) {
1077 $ret = self
::newFromAnon();
1078 } elseif ( $context instanceof User ||
$context === null ) {
1079 $ret = new self( $context, $userLang );
1081 throw new InvalidArgumentException(
1082 '$context must be an IContextSource, the string "canonical", a User, or null'
1086 foreach ( self
::getCanonicalOverrides() as $k => $v ) {
1087 $ret->setOption( $k, $v );
1093 * Get default option values
1094 * @warning If you change the default for an existing option (unless it's
1095 * being overridden by self::getCanonicalOverrides()), all existing parser
1096 * cache entries will be invalid. To avoid bugs, you'll need to handle
1097 * that somehow (e.g. with the RejectParserCacheValue hook) because
1098 * MediaWiki won't do it for you.
1101 private static function getDefaults() {
1102 global $wgInterwikiMagic, $wgAllowExternalImages,
1103 $wgAllowExternalImagesFrom, $wgEnableImageWhitelist, $wgAllowSpecialInclusion,
1104 $wgMaxArticleSize, $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth,
1105 $wgCleanSignatures, $wgExternalLinkTarget, $wgExpensiveParserFunctionLimit,
1106 $wgMaxGeneratedPPNodeCount, $wgDisableLangConversion, $wgDisableTitleConversion,
1107 $wgEnableMagicLinks;
1109 if ( self
::$defaults === null ) {
1110 // *UPDATE* ParserOptions::matches() if any of this changes as needed
1112 'dateformat' => null,
1114 'interfaceMessage' => false,
1115 'targetLanguage' => null,
1116 'removeComments' => true,
1117 'enableLimitReport' => false,
1118 'preSaveTransform' => true,
1119 'isPreview' => false,
1120 'isSectionPreview' => false,
1121 'printable' => false,
1122 'allowUnsafeRawHtml' => true,
1123 'wrapclass' => 'mw-parser-output',
1124 'currentRevisionCallback' => [ Parser
::class, 'statelessFetchRevision' ],
1125 'templateCallback' => [ Parser
::class, 'statelessFetchTemplate' ],
1126 'speculativeRevIdCallback' => null,
1127 'speculativeRevId' => null,
1128 'speculativePageIdCallback' => null,
1129 'speculativePageId' => null,
1132 Hooks
::run( 'ParserOptionsRegister', [
1135 &self
::$lazyOptions,
1138 ksort( self
::$inCacheKey );
1141 // Unit tests depend on being able to modify the globals at will
1142 return self
::$defaults +
[
1143 'interwikiMagic' => $wgInterwikiMagic,
1144 'allowExternalImages' => $wgAllowExternalImages,
1145 'allowExternalImagesFrom' => $wgAllowExternalImagesFrom,
1146 'enableImageWhitelist' => $wgEnableImageWhitelist,
1147 'allowSpecialInclusion' => $wgAllowSpecialInclusion,
1148 'maxIncludeSize' => $wgMaxArticleSize * 1024,
1149 'maxPPNodeCount' => $wgMaxPPNodeCount,
1150 'maxGeneratedPPNodeCount' => $wgMaxGeneratedPPNodeCount,
1151 'maxPPExpandDepth' => $wgMaxPPExpandDepth,
1152 'maxTemplateDepth' => $wgMaxTemplateDepth,
1153 'expensiveParserFunctionLimit' => $wgExpensiveParserFunctionLimit,
1154 'externalLinkTarget' => $wgExternalLinkTarget,
1155 'cleanSignatures' => $wgCleanSignatures,
1156 'disableContentConversion' => $wgDisableLangConversion,
1157 'disableTitleConversion' => $wgDisableLangConversion ||
$wgDisableTitleConversion,
1158 'magicISBNLinks' => $wgEnableMagicLinks['ISBN'],
1159 'magicPMIDLinks' => $wgEnableMagicLinks['PMID'],
1160 'magicRFCLinks' => $wgEnableMagicLinks['RFC'],
1161 'numberheadings' => User
::getDefaultOption( 'numberheadings' ),
1162 'thumbsize' => User
::getDefaultOption( 'thumbsize' ),
1163 'stubthreshold' => 0,
1164 'userlang' => MediaWikiServices
::getInstance()->getContentLanguage(),
1169 * Get "canonical" non-default option values
1170 * @see self::newCanonical
1171 * @warning If you change the override for an existing option, all existing
1172 * parser cache entries will be invalid. To avoid bugs, you'll need to
1173 * handle that somehow (e.g. with the RejectParserCacheValue hook) because
1174 * MediaWiki won't do it for you.
1177 private static function getCanonicalOverrides() {
1178 global $wgEnableParserLimitReporting;
1181 'enableLimitReport' => $wgEnableParserLimitReporting,
1189 * @param Language $lang
1191 private function initialiseFromUser( $user, $lang ) {
1192 $this->options
= self
::getDefaults();
1194 $this->mUser
= $user;
1195 $this->options
['numberheadings'] = $user->getOption( 'numberheadings' );
1196 $this->options
['thumbsize'] = $user->getOption( 'thumbsize' );
1197 $this->options
['stubthreshold'] = $user->getStubThreshold();
1198 $this->options
['userlang'] = $lang;
1202 * Check if these options match that of another options set
1204 * This ignores report limit settings that only affect HTML comments
1206 * @param ParserOptions $other
1210 public function matches( ParserOptions
$other ) {
1211 // Compare most options
1212 $options = array_keys( $this->options
);
1213 $options = array_diff( $options, [
1214 'enableLimitReport', // only affects HTML comments
1216 foreach ( $options as $option ) {
1217 // Resolve any lazy options
1218 $this->lazyLoadOption( $option );
1219 $other->lazyLoadOption( $option );
1221 $o1 = $this->optionToString( $this->options
[$option] );
1222 $o2 = $this->optionToString( $other->options
[$option] );
1223 if ( $o1 !== $o2 ) {
1228 // Compare most other fields
1229 $fields = array_keys( get_class_vars( __CLASS__
) );
1230 $fields = array_diff( $fields, [
1231 'defaults', // static
1232 'lazyOptions', // static
1233 'inCacheKey', // static
1234 'options', // Already checked above
1235 'onAccessCallback', // only used for ParserOutput option tracking
1237 foreach ( $fields as $field ) {
1238 if ( !is_object( $this->$field ) && $this->$field !== $other->$field ) {
1247 * @param ParserOptions $other
1248 * @return bool Whether the cache key relevant options match those of $other
1251 public function matchesForCacheKey( ParserOptions
$other ) {
1252 foreach ( self
::allCacheVaryingOptions() as $option ) {
1253 // Populate any lazy options
1254 $this->lazyLoadOption( $option );
1255 $other->lazyLoadOption( $option );
1257 $o1 = $this->optionToString( $this->options
[$option] );
1258 $o2 = $this->optionToString( $other->options
[$option] );
1259 if ( $o1 !== $o2 ) {
1268 * Registers a callback for tracking which ParserOptions which are used.
1269 * This is a private API with the parser.
1270 * @param callable $callback
1272 public function registerWatcher( $callback ) {
1273 $this->onAccessCallback
= $callback;
1277 * Called when an option is accessed.
1278 * Calls the watcher that was set using registerWatcher().
1279 * Typically, the watcher callback is ParserOutput::registerOption().
1280 * The information registered that way will be used by ParserCache::save().
1282 * @param string $optionName Name of the option
1284 public function optionUsed( $optionName ) {
1285 if ( $this->onAccessCallback
) {
1286 call_user_func( $this->onAccessCallback
, $optionName );
1291 * Return all option keys that vary the options hash
1295 public static function allCacheVaryingOptions() {
1296 // Trigger a call to the 'ParserOptionsRegister' hook if it hasn't
1297 // already been called.
1298 if ( self
::$defaults === null ) {
1299 self
::getDefaults();
1301 return array_keys( array_filter( self
::$inCacheKey ) );
1305 * Convert an option to a string value
1306 * @param mixed $value
1309 private function optionToString( $value ) {
1310 if ( $value === true ) {
1312 } elseif ( $value === false ) {
1314 } elseif ( $value === null ) {
1316 } elseif ( $value instanceof Language
) {
1317 return $value->getCode();
1318 } elseif ( is_array( $value ) ) {
1319 return '[' . implode( ',', array_map( [ $this, 'optionToString' ], $value ) ) . ']';
1321 return (string)$value;
1326 * Generate a hash string with the values set on these ParserOptions
1327 * for the keys given in the array.
1328 * This will be used as part of the hash key for the parser cache,
1329 * so users sharing the options with vary for the same page share
1330 * the same cached data safely.
1333 * @param string[] $forOptions
1334 * @param Title|null $title Used to get the content language of the page (since r97636)
1335 * @return string Page rendering hash
1337 public function optionsHash( $forOptions, $title = null ) {
1338 global $wgRenderHashAppend;
1340 $inCacheKey = self
::allCacheVaryingOptions();
1342 // Resolve any lazy options
1343 $lazyOpts = array_intersect( $forOptions, $inCacheKey, array_keys( self
::$lazyOptions ) );
1344 foreach ( $lazyOpts as $k ) {
1345 $this->lazyLoadOption( $k );
1348 $options = $this->options
;
1349 $defaults = self
::getCanonicalOverrides() + self
::getDefaults();
1351 // We only include used options with non-canonical values in the key
1352 // so adding a new option doesn't invalidate the entire parser cache.
1353 // The drawback to this is that changing the default value of an option
1354 // requires manual invalidation of existing cache entries, as mentioned
1355 // in the docs on the relevant methods and hooks.
1357 foreach ( array_intersect( $inCacheKey, $forOptions ) as $option ) {
1358 $v = $this->optionToString( $options[$option] );
1359 $d = $this->optionToString( $defaults[$option] );
1361 $values[] = "$option=$v";
1365 $confstr = $values ?
implode( '!', $values ) : 'canonical';
1367 // add in language specific options, if any
1368 // @todo FIXME: This is just a way of retrieving the url/user preferred variant
1369 if ( !is_null( $title ) ) {
1370 $confstr .= $title->getPageLanguage()->getExtraHashOptions();
1373 MediaWikiServices
::getInstance()->getContentLanguage()->getExtraHashOptions();
1376 $confstr .= $wgRenderHashAppend;
1378 if ( $this->mExtraKey
!= '' ) {
1379 $confstr .= $this->mExtraKey
;
1382 // Give a chance for extensions to modify the hash, if they have
1383 // extra options or other effects on the parser cache.
1384 Hooks
::run( 'PageRenderingHash', [ &$confstr, $this->getUser(), &$forOptions ] );
1386 // Make it a valid memcached key fragment
1387 $confstr = str_replace( ' ', '_', $confstr );
1393 * Test whether these options are safe to cache
1397 public function isSafeToCache() {
1398 $defaults = self
::getCanonicalOverrides() + self
::getDefaults();
1399 foreach ( $this->options
as $option => $value ) {
1400 if ( empty( self
::$inCacheKey[$option] ) ) {
1401 $v = $this->optionToString( $value );
1402 $d = $this->optionToString( $defaults[$option] );
1412 * Sets a hook to force that a page exists, and sets a current revision callback to return
1413 * a revision with custom content when the current revision of the page is requested.
1416 * @param Title $title
1417 * @param Content $content
1418 * @param User $user The user that the fake revision is attributed to
1419 * @return ScopedCallback to unset the hook
1421 public function setupFakeRevision( $title, $content, $user ) {
1422 $oldCallback = $this->setCurrentRevisionCallback(
1424 $titleToCheck, $parser = false ) use ( $title, $content, $user, &$oldCallback
1426 if ( $titleToCheck->equals( $title ) ) {
1427 return new Revision( [
1428 'page' => $title->getArticleID(),
1429 'user_text' => $user->getName(),
1430 'user' => $user->getId(),
1431 'parent_id' => $title->getLatestRevID(),
1433 'content' => $content
1436 return call_user_func( $oldCallback, $titleToCheck, $parser );
1442 $wgHooks['TitleExists'][] =
1443 function ( $titleToCheck, &$exists ) use ( $title ) {
1444 if ( $titleToCheck->equals( $title ) ) {
1448 end( $wgHooks['TitleExists'] );
1449 $key = key( $wgHooks['TitleExists'] );
1450 $linkCache = MediaWikiServices
::getInstance()->getLinkCache();
1451 $linkCache->clearBadLink( $title->getPrefixedDBkey() );
1452 return new ScopedCallback( function () use ( $title, $key, $linkCache ) {
1454 unset( $wgHooks['TitleExists'][$key] );
1455 $linkCache->clearLink( $title );
1461 * For really cool vim folding this needs to be at the end:
1462 * vim: foldmarker=@{,@} foldmethod=marker