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' ],
68 * Specify options that are included in the cache key
71 private static $inCacheKey = [
73 'numberheadings' => true,
75 'stubthreshold' => true,
81 * Current values for all options that are relevant for caching.
87 * Timestamp used for {{CURRENTDAY}} etc.
89 * @note Caching based on parse time is handled externally
96 * @todo Track this for caching somehow without fragmenting the cache insanely
101 * Function to be called when an option is accessed.
103 * @note Used for collecting used options, does not affect caching
105 private $onAccessCallback = null;
108 * If the page being parsed is a redirect, this should hold the redirect
111 * @todo Track this for caching somehow
113 private $redirectTarget = null;
116 * Appended to the options hash
118 private $mExtraKey = '';
121 * @name Option accessors
126 * Fetch an option, generically
128 * @param string $name Option name
131 public function getOption( $name ) {
132 if ( !array_key_exists( $name, $this->options
) ) {
133 throw new InvalidArgumentException( "Unknown parser option $name" );
136 if ( isset( self
::$lazyOptions[$name] ) && $this->options
[$name] === null ) {
137 $this->options
[$name] = call_user_func( self
::$lazyOptions[$name], $this, $name );
139 if ( !empty( self
::$inCacheKey[$name] ) ) {
140 $this->optionUsed( $name );
142 return $this->options
[$name];
146 * Set an option, generically
148 * @param string $name Option name
149 * @param mixed $value New value. Passing null will set null, unlike many
150 * of the existing accessors which ignore null for historical reasons.
151 * @return mixed Old value
153 public function setOption( $name, $value ) {
154 if ( !array_key_exists( $name, $this->options
) ) {
155 throw new InvalidArgumentException( "Unknown parser option $name" );
157 $old = $this->options
[$name];
158 $this->options
[$name] = $value;
163 * Legacy implementation
164 * @since 1.30 For implementing legacy setters only. Don't use this in new code.
165 * @deprecated since 1.30
166 * @param string $name Option name
167 * @param mixed $value New value. Passing null does not set the value.
168 * @return mixed Old value
170 protected function setOptionLegacy( $name, $value ) {
171 if ( !array_key_exists( $name, $this->options
) ) {
172 throw new InvalidArgumentException( "Unknown parser option $name" );
174 return wfSetVar( $this->options
[$name], $value );
178 * Whether to extract interlanguage links
180 * When true, interlanguage links will be returned by
181 * ParserOutput::getLanguageLinks() instead of generating link HTML.
185 public function getInterwikiMagic() {
186 return $this->getOption( 'interwikiMagic' );
190 * Specify whether to extract interlanguage links
191 * @param bool|null $x New value (null is no change)
192 * @return bool Old value
194 public function setInterwikiMagic( $x ) {
195 return $this->setOptionLegacy( 'interwikiMagic', $x );
199 * Allow all external images inline?
202 public function getAllowExternalImages() {
203 return $this->getOption( 'allowExternalImages' );
207 * Allow all external images inline?
208 * @param bool|null $x New value (null is no change)
209 * @return bool Old value
211 public function setAllowExternalImages( $x ) {
212 return $this->setOptionLegacy( 'allowExternalImages', $x );
216 * External images to allow
218 * When self::getAllowExternalImages() is false
220 * @return string|string[] URLs to allow
222 public function getAllowExternalImagesFrom() {
223 return $this->getOption( 'allowExternalImagesFrom' );
227 * External images to allow
229 * When self::getAllowExternalImages() is false
231 * @param string|string[]|null $x New value (null is no change)
232 * @return string|string[] Old value
234 public function setAllowExternalImagesFrom( $x ) {
235 return $this->setOptionLegacy( 'allowExternalImagesFrom', $x );
239 * Use the on-wiki external image whitelist?
242 public function getEnableImageWhitelist() {
243 return $this->getOption( 'enableImageWhitelist' );
247 * Use the on-wiki external image whitelist?
248 * @param bool|null $x New value (null is no change)
249 * @return bool Old value
251 public function setEnableImageWhitelist( $x ) {
252 return $this->setOptionLegacy( 'enableImageWhitelist', $x );
256 * Automatically number headings?
259 public function getNumberHeadings() {
260 return $this->getOption( 'numberheadings' );
264 * Automatically number headings?
265 * @param bool|null $x New value (null is no change)
266 * @return bool Old value
268 public function setNumberHeadings( $x ) {
269 return $this->setOptionLegacy( 'numberheadings', $x );
273 * Allow inclusion of special pages?
276 public function getAllowSpecialInclusion() {
277 return $this->getOption( 'allowSpecialInclusion' );
281 * Allow inclusion of special pages?
282 * @param bool|null $x New value (null is no change)
283 * @return bool Old value
285 public function setAllowSpecialInclusion( $x ) {
286 return $this->setOptionLegacy( 'allowSpecialInclusion', $x );
290 * Use tidy to cleanup output HTML?
293 public function getTidy() {
294 return $this->getOption( 'tidy' );
298 * Use tidy to cleanup output HTML?
299 * @param bool|null $x New value (null is no change)
300 * @return bool Old value
302 public function setTidy( $x ) {
303 return $this->setOptionLegacy( 'tidy', $x );
307 * Parsing an interface message?
310 public function getInterfaceMessage() {
311 return $this->getOption( 'interfaceMessage' );
315 * Parsing an interface message?
316 * @param bool|null $x New value (null is no change)
317 * @return bool Old value
319 public function setInterfaceMessage( $x ) {
320 return $this->setOptionLegacy( 'interfaceMessage', $x );
324 * Target language for the parse
325 * @return Language|null
327 public function getTargetLanguage() {
328 return $this->getOption( 'targetLanguage' );
332 * Target language for the parse
333 * @param Language|null $x New value
334 * @return Language|null Old value
336 public function setTargetLanguage( $x ) {
337 return $this->setOption( 'targetLanguage', $x );
341 * Maximum size of template expansions, in bytes
344 public function getMaxIncludeSize() {
345 return $this->getOption( 'maxIncludeSize' );
349 * Maximum size of template expansions, in bytes
350 * @param int|null $x New value (null is no change)
351 * @return int Old value
353 public function setMaxIncludeSize( $x ) {
354 return $this->setOptionLegacy( 'maxIncludeSize', $x );
358 * Maximum number of nodes touched by PPFrame::expand()
361 public function getMaxPPNodeCount() {
362 return $this->getOption( 'maxPPNodeCount' );
366 * Maximum number of nodes touched by PPFrame::expand()
367 * @param int|null $x New value (null is no change)
368 * @return int Old value
370 public function setMaxPPNodeCount( $x ) {
371 return $this->setOptionLegacy( 'maxPPNodeCount', $x );
375 * Maximum number of nodes generated by Preprocessor::preprocessToObj()
378 public function getMaxGeneratedPPNodeCount() {
379 return $this->getOption( 'maxGeneratedPPNodeCount' );
383 * Maximum number of nodes generated by Preprocessor::preprocessToObj()
384 * @param int|null $x New value (null is no change)
387 public function setMaxGeneratedPPNodeCount( $x ) {
388 return $this->setOptionLegacy( 'maxGeneratedPPNodeCount', $x );
392 * Maximum recursion depth in PPFrame::expand()
395 public function getMaxPPExpandDepth() {
396 return $this->getOption( 'maxPPExpandDepth' );
400 * Maximum recursion depth for templates within templates
403 public function getMaxTemplateDepth() {
404 return $this->getOption( 'maxTemplateDepth' );
408 * Maximum recursion depth for templates within templates
409 * @param int|null $x New value (null is no change)
410 * @return int Old value
412 public function setMaxTemplateDepth( $x ) {
413 return $this->setOptionLegacy( 'maxTemplateDepth', $x );
417 * Maximum number of calls per parse to expensive parser functions
421 public function getExpensiveParserFunctionLimit() {
422 return $this->getOption( 'expensiveParserFunctionLimit' );
426 * Maximum number of calls per parse to expensive parser functions
428 * @param int|null $x New value (null is no change)
429 * @return int Old value
431 public function setExpensiveParserFunctionLimit( $x ) {
432 return $this->setOptionLegacy( 'expensiveParserFunctionLimit', $x );
436 * Remove HTML comments
437 * @warning Only applies to preprocess operations
440 public function getRemoveComments() {
441 return $this->getOption( 'removeComments' );
445 * Remove HTML comments
446 * @warning Only applies to preprocess operations
447 * @param bool|null $x New value (null is no change)
448 * @return bool Old value
450 public function setRemoveComments( $x ) {
451 return $this->setOptionLegacy( 'removeComments', $x );
455 * Enable limit report in an HTML comment on output
458 public function getEnableLimitReport() {
459 return $this->getOption( 'enableLimitReport' );
463 * Enable limit report in an HTML comment on output
464 * @param bool|null $x New value (null is no change)
465 * @return bool Old value
467 public function enableLimitReport( $x = true ) {
468 return $this->setOptionLegacy( 'enableLimitReport', $x );
472 * Clean up signature texts?
473 * @see Parser::cleanSig
476 public function getCleanSignatures() {
477 return $this->getOption( 'cleanSignatures' );
481 * Clean up signature texts?
482 * @see Parser::cleanSig
483 * @param bool|null $x New value (null is no change)
484 * @return bool Old value
486 public function setCleanSignatures( $x ) {
487 return $this->setOptionLegacy( 'cleanSignatures', $x );
491 * Target attribute for external links
494 public function getExternalLinkTarget() {
495 return $this->getOption( 'externalLinkTarget' );
499 * Target attribute for external links
500 * @param string|null $x New value (null is no change)
501 * @return string Old value
503 public function setExternalLinkTarget( $x ) {
504 return $this->setOptionLegacy( 'externalLinkTarget', $x );
508 * Whether content conversion should be disabled
511 public function getDisableContentConversion() {
512 return $this->getOption( 'disableContentConversion' );
516 * Whether content conversion should be disabled
517 * @param bool|null $x New value (null is no change)
518 * @return bool Old value
520 public function disableContentConversion( $x = true ) {
521 return $this->setOptionLegacy( 'disableContentConversion', $x );
525 * Whether title conversion should be disabled
528 public function getDisableTitleConversion() {
529 return $this->getOption( 'disableTitleConversion' );
533 * Whether title conversion should be disabled
534 * @param bool|null $x New value (null is no change)
535 * @return bool Old value
537 public function disableTitleConversion( $x = true ) {
538 return $this->setOptionLegacy( 'disableTitleConversion', $x );
542 * Thumb size preferred by the user.
545 public function getThumbSize() {
546 return $this->getOption( 'thumbsize' );
550 * Thumb size preferred by the user.
551 * @param int|null $x New value (null is no change)
552 * @return int Old value
554 public function setThumbSize( $x ) {
555 return $this->setOptionLegacy( 'thumbsize', $x );
559 * Thumb size preferred by the user.
562 public function getStubThreshold() {
563 return $this->getOption( 'stubthreshold' );
567 * Thumb size preferred by the user.
568 * @param int|null $x New value (null is no change)
569 * @return int Old value
571 public function setStubThreshold( $x ) {
572 return $this->setOptionLegacy( 'stubthreshold', $x );
576 * Parsing the page for a "preview" operation?
579 public function getIsPreview() {
580 return $this->getOption( 'isPreview' );
584 * Parsing the page for a "preview" operation?
585 * @param bool|null $x New value (null is no change)
586 * @return bool Old value
588 public function setIsPreview( $x ) {
589 return $this->setOptionLegacy( 'isPreview', $x );
593 * Parsing the page for a "preview" operation on a single section?
596 public function getIsSectionPreview() {
597 return $this->getOption( 'isSectionPreview' );
601 * Parsing the page for a "preview" operation on a single section?
602 * @param bool|null $x New value (null is no change)
603 * @return bool Old value
605 public function setIsSectionPreview( $x ) {
606 return $this->setOptionLegacy( 'isSectionPreview', $x );
610 * Parsing the printable version of the page?
613 public function getIsPrintable() {
614 return $this->getOption( 'printable' );
618 * Parsing the printable version of the page?
619 * @param bool|null $x New value (null is no change)
620 * @return bool Old value
622 public function setIsPrintable( $x ) {
623 return $this->setOptionLegacy( 'printable', $x );
627 * Transform wiki markup when saving the page?
630 public function getPreSaveTransform() {
631 return $this->getOption( 'preSaveTransform' );
635 * Transform wiki markup when saving the page?
636 * @param bool|null $x New value (null is no change)
637 * @return bool Old value
639 public function setPreSaveTransform( $x ) {
640 return $this->setOptionLegacy( 'preSaveTransform', $x );
647 public function getDateFormat() {
648 return $this->getOption( 'dateformat' );
652 * Lazy initializer for dateFormat
653 * @param ParserOptions $popt
656 private static function initDateFormat( ParserOptions
$popt ) {
657 return $popt->mUser
->getDatePreference();
662 * @param string|null $x New value (null is no change)
663 * @return string Old value
665 public function setDateFormat( $x ) {
666 return $this->setOptionLegacy( 'dateformat', $x );
670 * Get the user language used by the parser for this page and split the parser cache.
672 * @warning Calling this causes the parser cache to be fragmented by user language!
673 * To avoid cache fragmentation, output should not depend on the user language.
674 * Use Parser::getFunctionLang() or Parser::getTargetLanguage() instead!
676 * @note This function will trigger a cache fragmentation by recording the
677 * 'userlang' option, see optionUsed(). This is done to avoid cache pollution
678 * when the page is rendered based on the language of the user.
680 * @note When saving, this will return the default language instead of the user's.
681 * {{int: }} uses this which used to produce inconsistent link tables (T16404).
686 public function getUserLangObj() {
687 return $this->getOption( 'userlang' );
691 * Same as getUserLangObj() but returns a string instead.
693 * @warning Calling this causes the parser cache to be fragmented by user language!
694 * To avoid cache fragmentation, output should not depend on the user language.
695 * Use Parser::getFunctionLang() or Parser::getTargetLanguage() instead!
697 * @see getUserLangObj()
699 * @return string Language code
702 public function getUserLang() {
703 return $this->getUserLangObj()->getCode();
707 * Set the user language used by the parser for this page and split the parser cache.
708 * @param string|Language $x New value
709 * @return Language Old value
711 public function setUserLang( $x ) {
712 if ( is_string( $x ) ) {
713 $x = Language
::factory( $x );
716 return $this->setOptionLegacy( 'userlang', $x );
720 * Are magic ISBN links enabled?
724 public function getMagicISBNLinks() {
725 return $this->getOption( 'magicISBNLinks' );
729 * Are magic PMID links enabled?
733 public function getMagicPMIDLinks() {
734 return $this->getOption( 'magicPMIDLinks' );
738 * Are magic RFC links enabled?
742 public function getMagicRFCLinks() {
743 return $this->getOption( 'magicRFCLinks' );
747 * If the wiki is configured to allow raw html ($wgRawHtml = true)
748 * is it allowed in the specific case of parsing this page.
750 * This is meant to disable unsafe parser tags in cases where
751 * a malicious user may control the input to the parser.
753 * @note This is expected to be true for normal pages even if the
754 * wiki has $wgRawHtml disabled in general. The setting only
755 * signifies that raw html would be unsafe in the current context
756 * provided that raw html is allowed at all.
760 public function getAllowUnsafeRawHtml() {
761 return $this->getOption( 'allowUnsafeRawHtml' );
765 * If the wiki is configured to allow raw html ($wgRawHtml = true)
766 * is it allowed in the specific case of parsing this page.
767 * @see self::getAllowUnsafeRawHtml()
769 * @param bool|null $x Value to set or null to get current value
770 * @return bool Current value for allowUnsafeRawHtml
772 public function setAllowUnsafeRawHtml( $x ) {
773 return $this->setOptionLegacy( 'allowUnsafeRawHtml', $x );
777 * Class to use to wrap output from Parser::parse()
779 * @return string|bool
781 public function getWrapOutputClass() {
782 return $this->getOption( 'wrapclass' );
786 * CSS class to use to wrap output from Parser::parse()
788 * @param string $className Class name to use for wrapping.
789 * Passing false to indicate "no wrapping" was deprecated in MediaWiki 1.31.
790 * @return string|bool Current value
792 public function setWrapOutputClass( $className ) {
793 if ( $className === true ) { // DWIM, they probably want the default class name
794 $className = 'mw-parser-output';
796 if ( $className === false ) {
797 wfDeprecated( __METHOD__
. '( false )', '1.31' );
799 return $this->setOption( 'wrapclass', $className );
803 * Callback for current revision fetching; first argument to call_user_func().
807 public function getCurrentRevisionCallback() {
808 return $this->getOption( 'currentRevisionCallback' );
812 * Callback for current revision fetching; first argument to call_user_func().
814 * @param callable|null $x New value (null is no change)
815 * @return callable Old value
817 public function setCurrentRevisionCallback( $x ) {
818 return $this->setOptionLegacy( 'currentRevisionCallback', $x );
822 * Callback for template fetching; first argument to call_user_func().
825 public function getTemplateCallback() {
826 return $this->getOption( 'templateCallback' );
830 * Callback for template fetching; first argument to call_user_func().
831 * @param callable|null $x New value (null is no change)
832 * @return callable Old value
834 public function setTemplateCallback( $x ) {
835 return $this->setOptionLegacy( 'templateCallback', $x );
839 * A guess for {{REVISIONID}}, calculated using the callback provided via
840 * setSpeculativeRevIdCallback(). For consistency, the value will be calculated upon the
841 * first call of this method, and re-used for subsequent calls.
843 * If no callback was defined via setSpeculativeRevIdCallback(), this method will return false.
848 public function getSpeculativeRevId() {
849 return $this->getOption( 'speculativeRevId' );
853 * Callback registered with ParserOptions::$lazyOptions, triggered by getSpeculativeRevId().
855 * @param ParserOptions $popt
858 private static function initSpeculativeRevId( ParserOptions
$popt ) {
859 $cb = $popt->getOption( 'speculativeRevIdCallback' );
860 $id = $cb ?
$cb() : null;
862 // returning null would result in this being re-called every access
867 * Callback to generate a guess for {{REVISIONID}}
869 * @deprecated since 1.32, use getSpeculativeRevId() instead!
870 * @return callable|null
872 public function getSpeculativeRevIdCallback() {
873 return $this->getOption( 'speculativeRevIdCallback' );
877 * Callback to generate a guess for {{REVISIONID}}
879 * @param callable|null $x New value (null is no change)
880 * @return callable|null Old value
882 public function setSpeculativeRevIdCallback( $x ) {
883 $this->setOption( 'speculativeRevId', null ); // reset
884 return $this->setOptionLegacy( 'speculativeRevIdCallback', $x );
890 * Timestamp used for {{CURRENTDAY}} etc.
893 public function getTimestamp() {
894 if ( !isset( $this->mTimestamp
) ) {
895 $this->mTimestamp
= wfTimestampNow();
897 return $this->mTimestamp
;
901 * Timestamp used for {{CURRENTDAY}} etc.
902 * @param string|null $x New value (null is no change)
903 * @return string Old value
905 public function setTimestamp( $x ) {
906 return wfSetVar( $this->mTimestamp
, $x );
910 * Create "edit section" links?
911 * @deprecated since 1.31, use ParserOutput::getText() options instead.
914 public function getEditSection() {
915 wfDeprecated( __METHOD__
, '1.31' );
920 * Create "edit section" links?
921 * @deprecated since 1.31, use ParserOutput::getText() options instead.
922 * @param bool|null $x New value (null is no change)
923 * @return bool Old value
925 public function setEditSection( $x ) {
926 wfDeprecated( __METHOD__
, '1.31' );
931 * Set the redirect target.
933 * Note that setting or changing this does not *make* the page a redirect
934 * or change its target, it merely records the information for reference
938 * @param Title|null $title
940 function setRedirectTarget( $title ) {
941 $this->redirectTarget
= $title;
945 * Get the previously-set redirect target.
950 function getRedirectTarget() {
951 return $this->redirectTarget
;
955 * Extra key that should be present in the parser cache key.
956 * @warning Consider registering your additional options with the
957 * ParserOptionsRegister hook instead of using this method.
960 public function addExtraKey( $key ) {
961 $this->mExtraKey
.= '!' . $key;
968 public function getUser() {
973 * @warning For interaction with the parser cache, use
974 * WikiPage::makeParserOptions() or ParserOptions::newCanonical() instead.
975 * @param User|null $user
976 * @param Language|null $lang
978 public function __construct( $user = null, $lang = null ) {
979 if ( $user === null ) {
981 if ( $wgUser === null ) {
987 if ( $lang === null ) {
989 if ( !StubObject
::isRealObject( $wgLang ) ) {
994 $this->initialiseFromUser( $user, $lang );
998 * Get a ParserOptions object for an anonymous user
999 * @warning For interaction with the parser cache, use
1000 * WikiPage::makeParserOptions() or ParserOptions::newCanonical() instead.
1002 * @return ParserOptions
1004 public static function newFromAnon() {
1005 return new ParserOptions( new User
,
1006 MediaWikiServices
::getInstance()->getContentLanguage() );
1010 * Get a ParserOptions object from a given user.
1011 * Language will be taken from $wgLang.
1013 * @warning For interaction with the parser cache, use
1014 * WikiPage::makeParserOptions() or ParserOptions::newCanonical() instead.
1016 * @return ParserOptions
1018 public static function newFromUser( $user ) {
1019 return new ParserOptions( $user );
1023 * Get a ParserOptions object from a given user and language
1025 * @warning For interaction with the parser cache, use
1026 * WikiPage::makeParserOptions() or ParserOptions::newCanonical() instead.
1028 * @param Language $lang
1029 * @return ParserOptions
1031 public static function newFromUserAndLang( User
$user, Language
$lang ) {
1032 return new ParserOptions( $user, $lang );
1036 * Get a ParserOptions object from a IContextSource object
1038 * @warning For interaction with the parser cache, use
1039 * WikiPage::makeParserOptions() or ParserOptions::newCanonical() instead.
1040 * @param IContextSource $context
1041 * @return ParserOptions
1043 public static function newFromContext( IContextSource
$context ) {
1044 return new ParserOptions( $context->getUser(), $context->getLanguage() );
1048 * Creates a "canonical" ParserOptions object
1050 * For historical reasons, certain options have default values that are
1051 * different from the canonical values used for caching.
1054 * @since 1.32 Added string and IContextSource as options for the first parameter
1055 * @param IContextSource|string|User|null $context
1056 * - If an IContextSource, the options are initialized based on the source's User and Language.
1057 * - If the string 'canonical', the options are initialized with an anonymous user and
1058 * the content language.
1059 * - If a User or null, the options are initialized for that User (or $wgUser if null).
1060 * 'userlang' is taken from the $userLang parameter, defaulting to $wgLang if that is null.
1061 * @param Language|StubObject|null $userLang (see above)
1062 * @return ParserOptions
1064 public static function newCanonical( $context = null, $userLang = null ) {
1065 if ( $context instanceof IContextSource
) {
1066 $ret = self
::newFromContext( $context );
1067 } elseif ( $context === 'canonical' ) {
1068 $ret = self
::newFromAnon();
1069 } elseif ( $context instanceof User ||
$context === null ) {
1070 $ret = new self( $context, $userLang );
1072 throw new InvalidArgumentException(
1073 '$context must be an IContextSource, the string "canonical", a User, or null'
1077 foreach ( self
::getCanonicalOverrides() as $k => $v ) {
1078 $ret->setOption( $k, $v );
1084 * Get default option values
1085 * @warning If you change the default for an existing option (unless it's
1086 * being overridden by self::getCanonicalOverrides()), all existing parser
1087 * cache entries will be invalid. To avoid bugs, you'll need to handle
1088 * that somehow (e.g. with the RejectParserCacheValue hook) because
1089 * MediaWiki won't do it for you.
1092 private static function getDefaults() {
1093 global $wgInterwikiMagic, $wgAllowExternalImages,
1094 $wgAllowExternalImagesFrom, $wgEnableImageWhitelist, $wgAllowSpecialInclusion,
1095 $wgMaxArticleSize, $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth,
1096 $wgCleanSignatures, $wgExternalLinkTarget, $wgExpensiveParserFunctionLimit,
1097 $wgMaxGeneratedPPNodeCount, $wgDisableLangConversion, $wgDisableTitleConversion,
1098 $wgEnableMagicLinks;
1100 if ( self
::$defaults === null ) {
1101 // *UPDATE* ParserOptions::matches() if any of this changes as needed
1103 'dateformat' => null,
1105 'interfaceMessage' => false,
1106 'targetLanguage' => null,
1107 'removeComments' => true,
1108 'enableLimitReport' => false,
1109 'preSaveTransform' => true,
1110 'isPreview' => false,
1111 'isSectionPreview' => false,
1112 'printable' => false,
1113 'allowUnsafeRawHtml' => true,
1114 'wrapclass' => 'mw-parser-output',
1115 'currentRevisionCallback' => [ Parser
::class, 'statelessFetchRevision' ],
1116 'templateCallback' => [ Parser
::class, 'statelessFetchTemplate' ],
1117 'speculativeRevIdCallback' => null,
1118 'speculativeRevId' => null,
1121 Hooks
::run( 'ParserOptionsRegister', [
1124 &self
::$lazyOptions,
1127 ksort( self
::$inCacheKey );
1130 // Unit tests depend on being able to modify the globals at will
1131 return self
::$defaults +
[
1132 'interwikiMagic' => $wgInterwikiMagic,
1133 'allowExternalImages' => $wgAllowExternalImages,
1134 'allowExternalImagesFrom' => $wgAllowExternalImagesFrom,
1135 'enableImageWhitelist' => $wgEnableImageWhitelist,
1136 'allowSpecialInclusion' => $wgAllowSpecialInclusion,
1137 'maxIncludeSize' => $wgMaxArticleSize * 1024,
1138 'maxPPNodeCount' => $wgMaxPPNodeCount,
1139 'maxGeneratedPPNodeCount' => $wgMaxGeneratedPPNodeCount,
1140 'maxPPExpandDepth' => $wgMaxPPExpandDepth,
1141 'maxTemplateDepth' => $wgMaxTemplateDepth,
1142 'expensiveParserFunctionLimit' => $wgExpensiveParserFunctionLimit,
1143 'externalLinkTarget' => $wgExternalLinkTarget,
1144 'cleanSignatures' => $wgCleanSignatures,
1145 'disableContentConversion' => $wgDisableLangConversion,
1146 'disableTitleConversion' => $wgDisableLangConversion ||
$wgDisableTitleConversion,
1147 'magicISBNLinks' => $wgEnableMagicLinks['ISBN'],
1148 'magicPMIDLinks' => $wgEnableMagicLinks['PMID'],
1149 'magicRFCLinks' => $wgEnableMagicLinks['RFC'],
1150 'numberheadings' => User
::getDefaultOption( 'numberheadings' ),
1151 'thumbsize' => User
::getDefaultOption( 'thumbsize' ),
1152 'stubthreshold' => 0,
1153 'userlang' => MediaWikiServices
::getInstance()->getContentLanguage(),
1158 * Get "canonical" non-default option values
1159 * @see self::newCanonical
1160 * @warning If you change the override for an existing option, all existing
1161 * parser cache entries will be invalid. To avoid bugs, you'll need to
1162 * handle that somehow (e.g. with the RejectParserCacheValue hook) because
1163 * MediaWiki won't do it for you.
1166 private static function getCanonicalOverrides() {
1167 global $wgEnableParserLimitReporting;
1170 'enableLimitReport' => $wgEnableParserLimitReporting,
1178 * @param Language $lang
1180 private function initialiseFromUser( $user, $lang ) {
1181 $this->options
= self
::getDefaults();
1183 $this->mUser
= $user;
1184 $this->options
['numberheadings'] = $user->getOption( 'numberheadings' );
1185 $this->options
['thumbsize'] = $user->getOption( 'thumbsize' );
1186 $this->options
['stubthreshold'] = $user->getStubThreshold();
1187 $this->options
['userlang'] = $lang;
1191 * Check if these options match that of another options set
1193 * This ignores report limit settings that only affect HTML comments
1195 * @param ParserOptions $other
1199 public function matches( ParserOptions
$other ) {
1200 // Populate lazy options
1201 foreach ( self
::$lazyOptions as $name => $callback ) {
1202 if ( $this->options
[$name] === null ) {
1203 $this->options
[$name] = call_user_func( $callback, $this, $name );
1205 if ( $other->options
[$name] === null ) {
1206 $other->options
[$name] = call_user_func( $callback, $other, $name );
1210 // Compare most options
1211 $options = array_keys( $this->options
);
1212 $options = array_diff( $options, [
1213 'enableLimitReport', // only affects HTML comments
1215 foreach ( $options as $option ) {
1216 $o1 = $this->optionToString( $this->options
[$option] );
1217 $o2 = $this->optionToString( $other->options
[$option] );
1218 if ( $o1 !== $o2 ) {
1223 // Compare most other fields
1224 $fields = array_keys( get_class_vars( __CLASS__
) );
1225 $fields = array_diff( $fields, [
1226 'defaults', // static
1227 'lazyOptions', // static
1228 'inCacheKey', // static
1229 'options', // Already checked above
1230 'onAccessCallback', // only used for ParserOutput option tracking
1232 foreach ( $fields as $field ) {
1233 if ( !is_object( $this->$field ) && $this->$field !== $other->$field ) {
1242 * Registers a callback for tracking which ParserOptions which are used.
1243 * This is a private API with the parser.
1244 * @param callable $callback
1246 public function registerWatcher( $callback ) {
1247 $this->onAccessCallback
= $callback;
1251 * Called when an option is accessed.
1252 * Calls the watcher that was set using registerWatcher().
1253 * Typically, the watcher callback is ParserOutput::registerOption().
1254 * The information registered that way will be used by ParserCache::save().
1256 * @param string $optionName Name of the option
1258 public function optionUsed( $optionName ) {
1259 if ( $this->onAccessCallback
) {
1260 call_user_func( $this->onAccessCallback
, $optionName );
1265 * Return all option keys that vary the options hash
1269 public static function allCacheVaryingOptions() {
1270 // Trigger a call to the 'ParserOptionsRegister' hook if it hasn't
1271 // already been called.
1272 if ( self
::$defaults === null ) {
1273 self
::getDefaults();
1275 return array_keys( array_filter( self
::$inCacheKey ) );
1279 * Convert an option to a string value
1280 * @param mixed $value
1283 private function optionToString( $value ) {
1284 if ( $value === true ) {
1286 } elseif ( $value === false ) {
1288 } elseif ( $value === null ) {
1290 } elseif ( $value instanceof Language
) {
1291 return $value->getCode();
1292 } elseif ( is_array( $value ) ) {
1293 return '[' . implode( ',', array_map( [ $this, 'optionToString' ], $value ) ) . ']';
1295 return (string)$value;
1300 * Generate a hash string with the values set on these ParserOptions
1301 * for the keys given in the array.
1302 * This will be used as part of the hash key for the parser cache,
1303 * so users sharing the options with vary for the same page share
1304 * the same cached data safely.
1307 * @param string[] $forOptions
1308 * @param Title|null $title Used to get the content language of the page (since r97636)
1309 * @return string Page rendering hash
1311 public function optionsHash( $forOptions, $title = null ) {
1312 global $wgRenderHashAppend;
1314 $inCacheKey = self
::allCacheVaryingOptions();
1316 // Resolve any lazy options
1317 foreach ( array_intersect( $forOptions, $inCacheKey, array_keys( self
::$lazyOptions ) ) as $k ) {
1318 if ( $this->options
[$k] === null ) {
1319 $this->options
[$k] = call_user_func( self
::$lazyOptions[$k], $this, $k );
1323 $options = $this->options
;
1324 $defaults = self
::getCanonicalOverrides() + self
::getDefaults();
1326 // We only include used options with non-canonical values in the key
1327 // so adding a new option doesn't invalidate the entire parser cache.
1328 // The drawback to this is that changing the default value of an option
1329 // requires manual invalidation of existing cache entries, as mentioned
1330 // in the docs on the relevant methods and hooks.
1332 foreach ( array_intersect( $inCacheKey, $forOptions ) as $option ) {
1333 $v = $this->optionToString( $options[$option] );
1334 $d = $this->optionToString( $defaults[$option] );
1336 $values[] = "$option=$v";
1340 $confstr = $values ?
implode( '!', $values ) : 'canonical';
1342 // add in language specific options, if any
1343 // @todo FIXME: This is just a way of retrieving the url/user preferred variant
1344 if ( !is_null( $title ) ) {
1345 $confstr .= $title->getPageLanguage()->getExtraHashOptions();
1348 MediaWikiServices
::getInstance()->getContentLanguage()->getExtraHashOptions();
1351 $confstr .= $wgRenderHashAppend;
1353 if ( $this->mExtraKey
!= '' ) {
1354 $confstr .= $this->mExtraKey
;
1357 // Give a chance for extensions to modify the hash, if they have
1358 // extra options or other effects on the parser cache.
1359 Hooks
::run( 'PageRenderingHash', [ &$confstr, $this->getUser(), &$forOptions ] );
1361 // Make it a valid memcached key fragment
1362 $confstr = str_replace( ' ', '_', $confstr );
1368 * Test whether these options are safe to cache
1372 public function isSafeToCache() {
1373 $defaults = self
::getCanonicalOverrides() + self
::getDefaults();
1374 foreach ( $this->options
as $option => $value ) {
1375 if ( empty( self
::$inCacheKey[$option] ) ) {
1376 $v = $this->optionToString( $value );
1377 $d = $this->optionToString( $defaults[$option] );
1387 * Sets a hook to force that a page exists, and sets a current revision callback to return
1388 * a revision with custom content when the current revision of the page is requested.
1391 * @param Title $title
1392 * @param Content $content
1393 * @param User $user The user that the fake revision is attributed to
1394 * @return ScopedCallback to unset the hook
1396 public function setupFakeRevision( $title, $content, $user ) {
1397 $oldCallback = $this->setCurrentRevisionCallback(
1399 $titleToCheck, $parser = false ) use ( $title, $content, $user, &$oldCallback
1401 if ( $titleToCheck->equals( $title ) ) {
1402 return new Revision( [
1403 'page' => $title->getArticleID(),
1404 'user_text' => $user->getName(),
1405 'user' => $user->getId(),
1406 'parent_id' => $title->getLatestRevID(),
1408 'content' => $content
1411 return call_user_func( $oldCallback, $titleToCheck, $parser );
1417 $wgHooks['TitleExists'][] =
1418 function ( $titleToCheck, &$exists ) use ( $title ) {
1419 if ( $titleToCheck->equals( $title ) ) {
1423 end( $wgHooks['TitleExists'] );
1424 $key = key( $wgHooks['TitleExists'] );
1425 $linkCache = MediaWikiServices
::getInstance()->getLinkCache();
1426 $linkCache->clearBadLink( $title->getPrefixedDBkey() );
1427 return new ScopedCallback( function () use ( $title, $key, $linkCache ) {
1429 unset( $wgHooks['TitleExists'][$key] );
1430 $linkCache->clearLink( $title );
1436 * For really cool vim folding this needs to be at the end:
1437 * vim: foldmarker=@{,@} foldmethod=marker