/**
* Options for the PHP parser
*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
* @file
* @ingroup Parser
*/
-
+use Wikimedia\ScopedCallback;
+
/**
- * Set options of the Parser
- * @todo document
+ * @brief Set options of the Parser
+ *
+ * How to add an option in core:
+ * 1. Add it to one of the arrays in ParserOptions::setDefaults()
+ * 2. If necessary, add an entry to ParserOptions::$inCacheKey
+ * 3. Add a getter and setter in the section for that.
+ *
+ * How to add an option in an extension:
+ * 1. Use the 'ParserOptionsRegister' hook to register it.
+ * 2. Where necessary, use $popt->getOption() and $popt->setOption()
+ * to access it.
+ *
* @ingroup Parser
*/
class ParserOptions {
- # All variables are supposed to be private in theory, although in practise this is not the case.
- var $mUseDynamicDates; # Use DateFormatter to format dates
- var $mInterwikiMagic; # Interlanguage links are removed and returned in an array
- var $mAllowExternalImages; # Allow external images inline
- var $mAllowExternalImagesFrom; # If not, any exception?
- var $mEnableImageWhitelist; # If not or it doesn't match, should we check an on-wiki whitelist?
- var $mSkin; # Reference to the preferred skin
- var $mDateFormat; # Date format index
- var $mEditSection; # Create "edit section" links
- var $mNumberHeadings; # Automatically number headings
- var $mAllowSpecialInclusion; # Allow inclusion of special pages
- var $mTidy; # Ask for tidy cleanup
- var $mInterfaceMessage; # Which lang to call for PLURAL and GRAMMAR
- var $mTargetLanguage; # Overrides above setting with arbitrary language
- var $mMaxIncludeSize; # Maximum size of template expansions, in bytes
- var $mMaxPPNodeCount; # Maximum number of nodes touched by PPFrame::expand()
- var $mMaxPPExpandDepth; # Maximum recursion depth in PPFrame::expand()
- var $mMaxTemplateDepth; # Maximum recursion depth for templates within templates
- var $mRemoveComments; # Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS
- var $mTemplateCallback; # Callback for template fetching
- var $mEnableLimitReport; # Enable limit report in an HTML comment on output
- var $mTimestamp; # Timestamp used for {{CURRENTDAY}} etc.
- var $mExternalLinkTarget; # Target attribute for external links
- var $mMath; # User math preference (as integer)
- var $mUserLang; # Language code of the User language.
- var $mThumbSize; # Thumb size preferred by the user.
- var $mCleanSignatures; #
-
- var $mUser; # Stored user object, just used to initialise the skin
- var $mIsPreview; # Parsing the page for a "preview" operation
- var $mIsSectionPreview; # Parsing the page for a "preview" operation on a single section
- var $mIsPrintable; # Parsing the printable version of the page
-
- var $mExtraKey = ''; # Extra key that should be present in the caching key.
-
- protected $onAccessCallback = null;
-
- function getUseDynamicDates() { return $this->mUseDynamicDates; }
- function getInterwikiMagic() { return $this->mInterwikiMagic; }
- function getAllowExternalImages() { return $this->mAllowExternalImages; }
- function getAllowExternalImagesFrom() { return $this->mAllowExternalImagesFrom; }
- function getEnableImageWhitelist() { return $this->mEnableImageWhitelist; }
- function getEditSection() { $this->optionUsed('editsection');
- return $this->mEditSection; }
- function getNumberHeadings() { $this->optionUsed('numberheadings');
- return $this->mNumberHeadings; }
- function getAllowSpecialInclusion() { return $this->mAllowSpecialInclusion; }
- function getTidy() { return $this->mTidy; }
- function getInterfaceMessage() { return $this->mInterfaceMessage; }
- function getTargetLanguage() { return $this->mTargetLanguage; }
- function getMaxIncludeSize() { return $this->mMaxIncludeSize; }
- function getMaxPPNodeCount() { return $this->mMaxPPNodeCount; }
- function getMaxPPExpandDepth() { return $this->mMaxPPExpandDepth; }
- function getMaxTemplateDepth() { return $this->mMaxTemplateDepth; }
- function getRemoveComments() { return $this->mRemoveComments; }
- function getTemplateCallback() { return $this->mTemplateCallback; }
- function getEnableLimitReport() { return $this->mEnableLimitReport; }
- function getCleanSignatures() { return $this->mCleanSignatures; }
- function getExternalLinkTarget() { return $this->mExternalLinkTarget; }
- function getMath() { $this->optionUsed('math');
- return $this->mMath; }
- function getThumbSize() { $this->optionUsed('thumbsize');
- return $this->mThumbSize; }
-
- function getIsPreview() { return $this->mIsPreview; }
- function getIsSectionPreview() { return $this->mIsSectionPreview; }
- function getIsPrintable() { $this->optionUsed('printable');
- return $this->mIsPrintable; }
-
- function getSkin( $title = null ) {
- if ( !isset( $this->mSkin ) ) {
- $this->mSkin = $this->mUser->getSkin( $title );
+
+ /**
+ * Default values for all options that are relevant for caching.
+ * @see self::getDefaults()
+ * @var array|null
+ */
+ private static $defaults = null;
+
+ /**
+ * Lazy-loaded options
+ * @var callback[]
+ */
+ private static $lazyOptions = [
+ 'dateformat' => [ __CLASS__, 'initDateFormat' ],
+ ];
+
+ /**
+ * Specify options that are included in the cache key
+ * @var array
+ */
+ private static $inCacheKey = [
+ 'dateformat' => true,
+ 'numberheadings' => true,
+ 'thumbsize' => true,
+ 'stubthreshold' => true,
+ 'printable' => true,
+ 'userlang' => true,
+ 'wrapclass' => true,
+ ];
+
+ /**
+ * Current values for all options that are relevant for caching.
+ * @var array
+ */
+ private $options;
+
+ /**
+ * Timestamp used for {{CURRENTDAY}} etc.
+ * @var string|null
+ * @note Caching based on parse time is handled externally
+ */
+ private $mTimestamp;
+
+ /**
+ * The edit section flag is in ParserOptions for historical reasons, but
+ * doesn't actually affect the parser output since Feb 2015.
+ * @var bool
+ */
+ private $mEditSection = true;
+
+ /**
+ * Stored user object
+ * @var User
+ * @todo Track this for caching somehow without fragmenting the cache insanely
+ */
+ private $mUser;
+
+ /**
+ * Function to be called when an option is accessed.
+ * @var callable|null
+ * @note Used for collecting used options, does not affect caching
+ */
+ private $onAccessCallback = null;
+
+ /**
+ * If the page being parsed is a redirect, this should hold the redirect
+ * target.
+ * @var Title|null
+ * @todo Track this for caching somehow
+ */
+ private $redirectTarget = null;
+
+ /**
+ * Appended to the options hash
+ */
+ private $mExtraKey = '';
+
+ /**
+ * @name Option accessors
+ * @{
+ */
+
+ /**
+ * Fetch an option, generically
+ * @since 1.30
+ * @param string $name Option name
+ * @return mixed
+ */
+ public function getOption( $name ) {
+ if ( !array_key_exists( $name, $this->options ) ) {
+ throw new InvalidArgumentException( "Unknown parser option $name" );
+ }
+
+ if ( isset( self::$lazyOptions[$name] ) && $this->options[$name] === null ) {
+ $this->options[$name] = call_user_func( self::$lazyOptions[$name], $this, $name );
+ }
+ if ( !empty( self::$inCacheKey[$name] ) ) {
+ $this->optionUsed( $name );
+ }
+ return $this->options[$name];
+ }
+
+ /**
+ * Set an option, generically
+ * @since 1.30
+ * @param string $name Option name
+ * @param mixed $value New value. Passing null will set null, unlike many
+ * of the existing accessors which ignore null for historical reasons.
+ * @return mixed Old value
+ */
+ public function setOption( $name, $value ) {
+ if ( !array_key_exists( $name, $this->options ) ) {
+ throw new InvalidArgumentException( "Unknown parser option $name" );
+ }
+ $old = $this->options[$name];
+ $this->options[$name] = $value;
+ return $old;
+ }
+
+ /**
+ * Legacy implementation
+ * @since 1.30 For implementing legacy setters only. Don't use this in new code.
+ * @deprecated since 1.30
+ * @param string $name Option name
+ * @param mixed $value New value. Passing null does not set the value.
+ * @return mixed Old value
+ */
+ protected function setOptionLegacy( $name, $value ) {
+ if ( !array_key_exists( $name, $this->options ) ) {
+ throw new InvalidArgumentException( "Unknown parser option $name" );
+ }
+ return wfSetVar( $this->options[$name], $value );
+ }
+
+ /**
+ * Whether to extract interlanguage links
+ *
+ * When true, interlanguage links will be returned by
+ * ParserOutput::getLanguageLinks() instead of generating link HTML.
+ *
+ * @return bool
+ */
+ public function getInterwikiMagic() {
+ return $this->getOption( 'interwikiMagic' );
+ }
+
+ /**
+ * Specify whether to extract interlanguage links
+ * @param bool|null $x New value (null is no change)
+ * @return bool Old value
+ */
+ public function setInterwikiMagic( $x ) {
+ return $this->setOptionLegacy( 'interwikiMagic', $x );
+ }
+
+ /**
+ * Allow all external images inline?
+ * @return bool
+ */
+ public function getAllowExternalImages() {
+ return $this->getOption( 'allowExternalImages' );
+ }
+
+ /**
+ * Allow all external images inline?
+ * @param bool|null $x New value (null is no change)
+ * @return bool Old value
+ */
+ public function setAllowExternalImages( $x ) {
+ return $this->setOptionLegacy( 'allowExternalImages', $x );
+ }
+
+ /**
+ * External images to allow
+ *
+ * When self::getAllowExternalImages() is false
+ *
+ * @return string|string[] URLs to allow
+ */
+ public function getAllowExternalImagesFrom() {
+ return $this->getOption( 'allowExternalImagesFrom' );
+ }
+
+ /**
+ * External images to allow
+ *
+ * When self::getAllowExternalImages() is false
+ *
+ * @param string|string[]|null $x New value (null is no change)
+ * @return string|string[] Old value
+ */
+ public function setAllowExternalImagesFrom( $x ) {
+ return $this->setOptionLegacy( 'allowExternalImagesFrom', $x );
+ }
+
+ /**
+ * Use the on-wiki external image whitelist?
+ * @return bool
+ */
+ public function getEnableImageWhitelist() {
+ return $this->getOption( 'enableImageWhitelist' );
+ }
+
+ /**
+ * Use the on-wiki external image whitelist?
+ * @param bool|null $x New value (null is no change)
+ * @return bool Old value
+ */
+ public function setEnableImageWhitelist( $x ) {
+ return $this->setOptionLegacy( 'enableImageWhitelist', $x );
+ }
+
+ /**
+ * Automatically number headings?
+ * @return bool
+ */
+ public function getNumberHeadings() {
+ return $this->getOption( 'numberheadings' );
+ }
+
+ /**
+ * Automatically number headings?
+ * @param bool|null $x New value (null is no change)
+ * @return bool Old value
+ */
+ public function setNumberHeadings( $x ) {
+ return $this->setOptionLegacy( 'numberheadings', $x );
+ }
+
+ /**
+ * Allow inclusion of special pages?
+ * @return bool
+ */
+ public function getAllowSpecialInclusion() {
+ return $this->getOption( 'allowSpecialInclusion' );
+ }
+
+ /**
+ * Allow inclusion of special pages?
+ * @param bool|null $x New value (null is no change)
+ * @return bool Old value
+ */
+ public function setAllowSpecialInclusion( $x ) {
+ return $this->setOptionLegacy( 'allowSpecialInclusion', $x );
+ }
+
+ /**
+ * Use tidy to cleanup output HTML?
+ * @return bool
+ */
+ public function getTidy() {
+ return $this->getOption( 'tidy' );
+ }
+
+ /**
+ * Use tidy to cleanup output HTML?
+ * @param bool|null $x New value (null is no change)
+ * @return bool Old value
+ */
+ public function setTidy( $x ) {
+ return $this->setOptionLegacy( 'tidy', $x );
+ }
+
+ /**
+ * Parsing an interface message?
+ * @return bool
+ */
+ public function getInterfaceMessage() {
+ return $this->getOption( 'interfaceMessage' );
+ }
+
+ /**
+ * Parsing an interface message?
+ * @param bool|null $x New value (null is no change)
+ * @return bool Old value
+ */
+ public function setInterfaceMessage( $x ) {
+ return $this->setOptionLegacy( 'interfaceMessage', $x );
+ }
+
+ /**
+ * Target language for the parse
+ * @return Language|null
+ */
+ public function getTargetLanguage() {
+ return $this->getOption( 'targetLanguage' );
+ }
+
+ /**
+ * Target language for the parse
+ * @param Language|null $x New value
+ * @return Language|null Old value
+ */
+ public function setTargetLanguage( $x ) {
+ return $this->setOption( 'targetLanguage', $x );
+ }
+
+ /**
+ * Maximum size of template expansions, in bytes
+ * @return int
+ */
+ public function getMaxIncludeSize() {
+ return $this->getOption( 'maxIncludeSize' );
+ }
+
+ /**
+ * Maximum size of template expansions, in bytes
+ * @param int|null $x New value (null is no change)
+ * @return int Old value
+ */
+ public function setMaxIncludeSize( $x ) {
+ return $this->setOptionLegacy( 'maxIncludeSize', $x );
+ }
+
+ /**
+ * Maximum number of nodes touched by PPFrame::expand()
+ * @return int
+ */
+ public function getMaxPPNodeCount() {
+ return $this->getOption( 'maxPPNodeCount' );
+ }
+
+ /**
+ * Maximum number of nodes touched by PPFrame::expand()
+ * @param int|null $x New value (null is no change)
+ * @return int Old value
+ */
+ public function setMaxPPNodeCount( $x ) {
+ return $this->setOptionLegacy( 'maxPPNodeCount', $x );
+ }
+
+ /**
+ * Maximum number of nodes generated by Preprocessor::preprocessToObj()
+ * @return int
+ */
+ public function getMaxGeneratedPPNodeCount() {
+ return $this->getOption( 'maxGeneratedPPNodeCount' );
+ }
+
+ /**
+ * Maximum number of nodes generated by Preprocessor::preprocessToObj()
+ * @param int|null $x New value (null is no change)
+ * @return int
+ */
+ public function setMaxGeneratedPPNodeCount( $x ) {
+ return $this->setOptionLegacy( 'maxGeneratedPPNodeCount', $x );
+ }
+
+ /**
+ * Maximum recursion depth in PPFrame::expand()
+ * @return int
+ */
+ public function getMaxPPExpandDepth() {
+ return $this->getOption( 'maxPPExpandDepth' );
+ }
+
+ /**
+ * Maximum recursion depth for templates within templates
+ * @return int
+ */
+ public function getMaxTemplateDepth() {
+ return $this->getOption( 'maxTemplateDepth' );
+ }
+
+ /**
+ * Maximum recursion depth for templates within templates
+ * @param int|null $x New value (null is no change)
+ * @return int Old value
+ */
+ public function setMaxTemplateDepth( $x ) {
+ return $this->setOptionLegacy( 'maxTemplateDepth', $x );
+ }
+
+ /**
+ * Maximum number of calls per parse to expensive parser functions
+ * @since 1.20
+ * @return int
+ */
+ public function getExpensiveParserFunctionLimit() {
+ return $this->getOption( 'expensiveParserFunctionLimit' );
+ }
+
+ /**
+ * Maximum number of calls per parse to expensive parser functions
+ * @since 1.20
+ * @param int|null $x New value (null is no change)
+ * @return int Old value
+ */
+ public function setExpensiveParserFunctionLimit( $x ) {
+ return $this->setOptionLegacy( 'expensiveParserFunctionLimit', $x );
+ }
+
+ /**
+ * Remove HTML comments
+ * @warning Only applies to preprocess operations
+ * @return bool
+ */
+ public function getRemoveComments() {
+ return $this->getOption( 'removeComments' );
+ }
+
+ /**
+ * Remove HTML comments
+ * @warning Only applies to preprocess operations
+ * @param bool|null $x New value (null is no change)
+ * @return bool Old value
+ */
+ public function setRemoveComments( $x ) {
+ return $this->setOptionLegacy( 'removeComments', $x );
+ }
+
+ /**
+ * Enable limit report in an HTML comment on output
+ * @return bool
+ */
+ public function getEnableLimitReport() {
+ return $this->getOption( 'enableLimitReport' );
+ }
+
+ /**
+ * Enable limit report in an HTML comment on output
+ * @param bool|null $x New value (null is no change)
+ * @return bool Old value
+ */
+ public function enableLimitReport( $x = true ) {
+ return $this->setOptionLegacy( 'enableLimitReport', $x );
+ }
+
+ /**
+ * Clean up signature texts?
+ * @see Parser::cleanSig
+ * @return bool
+ */
+ public function getCleanSignatures() {
+ return $this->getOption( 'cleanSignatures' );
+ }
+
+ /**
+ * Clean up signature texts?
+ * @see Parser::cleanSig
+ * @param bool|null $x New value (null is no change)
+ * @return bool Old value
+ */
+ public function setCleanSignatures( $x ) {
+ return $this->setOptionLegacy( 'cleanSignatures', $x );
+ }
+
+ /**
+ * Target attribute for external links
+ * @return string
+ */
+ public function getExternalLinkTarget() {
+ return $this->getOption( 'externalLinkTarget' );
+ }
+
+ /**
+ * Target attribute for external links
+ * @param string|null $x New value (null is no change)
+ * @return string Old value
+ */
+ public function setExternalLinkTarget( $x ) {
+ return $this->setOptionLegacy( 'externalLinkTarget', $x );
+ }
+
+ /**
+ * Whether content conversion should be disabled
+ * @return bool
+ */
+ public function getDisableContentConversion() {
+ return $this->getOption( 'disableContentConversion' );
+ }
+
+ /**
+ * Whether content conversion should be disabled
+ * @param bool|null $x New value (null is no change)
+ * @return bool Old value
+ */
+ public function disableContentConversion( $x = true ) {
+ return $this->setOptionLegacy( 'disableContentConversion', $x );
+ }
+
+ /**
+ * Whether title conversion should be disabled
+ * @return bool
+ */
+ public function getDisableTitleConversion() {
+ return $this->getOption( 'disableTitleConversion' );
+ }
+
+ /**
+ * Whether title conversion should be disabled
+ * @param bool|null $x New value (null is no change)
+ * @return bool Old value
+ */
+ public function disableTitleConversion( $x = true ) {
+ return $this->setOptionLegacy( 'disableTitleConversion', $x );
+ }
+
+ /**
+ * Thumb size preferred by the user.
+ * @return int
+ */
+ public function getThumbSize() {
+ return $this->getOption( 'thumbsize' );
+ }
+
+ /**
+ * Thumb size preferred by the user.
+ * @param int|null $x New value (null is no change)
+ * @return int Old value
+ */
+ public function setThumbSize( $x ) {
+ return $this->setOptionLegacy( 'thumbsize', $x );
+ }
+
+ /**
+ * Thumb size preferred by the user.
+ * @return int
+ */
+ public function getStubThreshold() {
+ return $this->getOption( 'stubthreshold' );
+ }
+
+ /**
+ * Thumb size preferred by the user.
+ * @param int|null $x New value (null is no change)
+ * @return int Old value
+ */
+ public function setStubThreshold( $x ) {
+ return $this->setOptionLegacy( 'stubthreshold', $x );
+ }
+
+ /**
+ * Parsing the page for a "preview" operation?
+ * @return bool
+ */
+ public function getIsPreview() {
+ return $this->getOption( 'isPreview' );
+ }
+
+ /**
+ * Parsing the page for a "preview" operation?
+ * @param bool|null $x New value (null is no change)
+ * @return bool Old value
+ */
+ public function setIsPreview( $x ) {
+ return $this->setOptionLegacy( 'isPreview', $x );
+ }
+
+ /**
+ * Parsing the page for a "preview" operation on a single section?
+ * @return bool
+ */
+ public function getIsSectionPreview() {
+ return $this->getOption( 'isSectionPreview' );
+ }
+
+ /**
+ * Parsing the page for a "preview" operation on a single section?
+ * @param bool|null $x New value (null is no change)
+ * @return bool Old value
+ */
+ public function setIsSectionPreview( $x ) {
+ return $this->setOptionLegacy( 'isSectionPreview', $x );
+ }
+
+ /**
+ * Parsing the printable version of the page?
+ * @return bool
+ */
+ public function getIsPrintable() {
+ return $this->getOption( 'printable' );
+ }
+
+ /**
+ * Parsing the printable version of the page?
+ * @param bool|null $x New value (null is no change)
+ * @return bool Old value
+ */
+ public function setIsPrintable( $x ) {
+ return $this->setOptionLegacy( 'printable', $x );
+ }
+
+ /**
+ * Transform wiki markup when saving the page?
+ * @return bool
+ */
+ public function getPreSaveTransform() {
+ return $this->getOption( 'preSaveTransform' );
+ }
+
+ /**
+ * Transform wiki markup when saving the page?
+ * @param bool|null $x New value (null is no change)
+ * @return bool Old value
+ */
+ public function setPreSaveTransform( $x ) {
+ return $this->setOptionLegacy( 'preSaveTransform', $x );
+ }
+
+ /**
+ * Date format index
+ * @return string
+ */
+ public function getDateFormat() {
+ return $this->getOption( 'dateformat' );
+ }
+
+ /**
+ * Lazy initializer for dateFormat
+ */
+ private static function initDateFormat( $popt ) {
+ return $popt->mUser->getDatePreference();
+ }
+
+ /**
+ * Date format index
+ * @param string|null $x New value (null is no change)
+ * @return string Old value
+ */
+ public function setDateFormat( $x ) {
+ return $this->setOptionLegacy( 'dateformat', $x );
+ }
+
+ /**
+ * Get the user language used by the parser for this page and split the parser cache.
+ *
+ * @warning: Calling this causes the parser cache to be fragmented by user language!
+ * To avoid cache fragmentation, output should not depend on the user language.
+ * Use Parser::getFunctionLang() or Parser::getTargetLanguage() instead!
+ *
+ * @note This function will trigger a cache fragmentation by recording the
+ * 'userlang' option, see optionUsed(). This is done to avoid cache pollution
+ * when the page is rendered based on the language of the user.
+ *
+ * @note When saving, this will return the default language instead of the user's.
+ * {{int: }} uses this which used to produce inconsistent link tables (T16404).
+ *
+ * @return Language
+ * @since 1.19
+ */
+ public function getUserLangObj() {
+ return $this->getOption( 'userlang' );
+ }
+
+ /**
+ * Same as getUserLangObj() but returns a string instead.
+ *
+ * @warning: Calling this causes the parser cache to be fragmented by user language!
+ * To avoid cache fragmentation, output should not depend on the user language.
+ * Use Parser::getFunctionLang() or Parser::getTargetLanguage() instead!
+ *
+ * @see getUserLangObj()
+ *
+ * @return string Language code
+ * @since 1.17
+ */
+ public function getUserLang() {
+ return $this->getUserLangObj()->getCode();
+ }
+
+ /**
+ * Set the user language used by the parser for this page and split the parser cache.
+ * @param string|Language $x New value
+ * @return Language Old value
+ */
+ public function setUserLang( $x ) {
+ if ( is_string( $x ) ) {
+ $x = Language::factory( $x );
}
- return $this->mSkin;
+
+ return $this->setOptionLegacy( 'userlang', $x );
+ }
+
+ /**
+ * Are magic ISBN links enabled?
+ * @since 1.28
+ * @return bool
+ */
+ public function getMagicISBNLinks() {
+ return $this->getOption( 'magicISBNLinks' );
}
- function getDateFormat() {
- $this->optionUsed('dateformat');
- if ( !isset( $this->mDateFormat ) ) {
- $this->mDateFormat = $this->mUser->getDatePreference();
+ /**
+ * Are magic PMID links enabled?
+ * @since 1.28
+ * @return bool
+ */
+ public function getMagicPMIDLinks() {
+ return $this->getOption( 'magicPMIDLinks' );
+ }
+ /**
+ * Are magic RFC links enabled?
+ * @since 1.28
+ * @return bool
+ */
+ public function getMagicRFCLinks() {
+ return $this->getOption( 'magicRFCLinks' );
+ }
+
+ /**
+ * If the wiki is configured to allow raw html ($wgRawHtml = true)
+ * is it allowed in the specific case of parsing this page.
+ *
+ * This is meant to disable unsafe parser tags in cases where
+ * a malicious user may control the input to the parser.
+ *
+ * @note This is expected to be true for normal pages even if the
+ * wiki has $wgRawHtml disabled in general. The setting only
+ * signifies that raw html would be unsafe in the current context
+ * provided that raw html is allowed at all.
+ * @since 1.29
+ * @return bool
+ */
+ public function getAllowUnsafeRawHtml() {
+ return $this->getOption( 'allowUnsafeRawHtml' );
+ }
+
+ /**
+ * If the wiki is configured to allow raw html ($wgRawHtml = true)
+ * is it allowed in the specific case of parsing this page.
+ * @see self::getAllowUnsafeRawHtml()
+ * @since 1.29
+ * @param bool|null $x Value to set or null to get current value
+ * @return bool Current value for allowUnsafeRawHtml
+ */
+ public function setAllowUnsafeRawHtml( $x ) {
+ return $this->setOptionLegacy( 'allowUnsafeRawHtml', $x );
+ }
+
+ /**
+ * Class to use to wrap output from Parser::parse()
+ * @since 1.30
+ * @return string|bool
+ */
+ public function getWrapOutputClass() {
+ return $this->getOption( 'wrapclass' );
+ }
+
+ /**
+ * CSS class to use to wrap output from Parser::parse()
+ * @since 1.30
+ * @param string|bool $className Set false to disable wrapping.
+ * @return string|bool Current value
+ */
+ public function setWrapOutputClass( $className ) {
+ if ( $className === true ) { // DWIM, they probably want the default class name
+ $className = 'mw-parser-output';
}
- return $this->mDateFormat;
+ return $this->setOption( 'wrapclass', $className );
+ }
+
+ /**
+ * Callback for current revision fetching; first argument to call_user_func().
+ * @since 1.24
+ * @return callable
+ */
+ public function getCurrentRevisionCallback() {
+ return $this->getOption( 'currentRevisionCallback' );
+ }
+
+ /**
+ * Callback for current revision fetching; first argument to call_user_func().
+ * @since 1.24
+ * @param callable|null $x New value (null is no change)
+ * @return callable Old value
+ */
+ public function setCurrentRevisionCallback( $x ) {
+ return $this->setOptionLegacy( 'currentRevisionCallback', $x );
}
- function getTimestamp() {
+ /**
+ * Callback for template fetching; first argument to call_user_func().
+ * @return callable
+ */
+ public function getTemplateCallback() {
+ return $this->getOption( 'templateCallback' );
+ }
+
+ /**
+ * Callback for template fetching; first argument to call_user_func().
+ * @param callable|null $x New value (null is no change)
+ * @return callable Old value
+ */
+ public function setTemplateCallback( $x ) {
+ return $this->setOptionLegacy( 'templateCallback', $x );
+ }
+
+ /**
+ * Callback to generate a guess for {{REVISIONID}}
+ * @since 1.28
+ * @return callable|null
+ */
+ public function getSpeculativeRevIdCallback() {
+ return $this->getOption( 'speculativeRevIdCallback' );
+ }
+
+ /**
+ * Callback to generate a guess for {{REVISIONID}}
+ * @since 1.28
+ * @param callable|null $x New value (null is no change)
+ * @return callable|null Old value
+ */
+ public function setSpeculativeRevIdCallback( $x ) {
+ return $this->setOptionLegacy( 'speculativeRevIdCallback', $x );
+ }
+
+ /**@}*/
+
+ /**
+ * Timestamp used for {{CURRENTDAY}} etc.
+ * @return string
+ */
+ public function getTimestamp() {
if ( !isset( $this->mTimestamp ) ) {
$this->mTimestamp = wfTimestampNow();
}
}
/**
- * You shouldn't use this. Really. $parser->getFunctionLang() is all you need.
- * Using this fragments the cache and is discouraged. Yes, {{int: }} uses this,
- * producing inconsistent tables (Bug 14404).
- */
- function getUserLang() {
- $this->optionUsed('userlang');
- return $this->mUserLang;
- }
-
- function setUseDynamicDates( $x ) { return wfSetVar( $this->mUseDynamicDates, $x ); }
- function setInterwikiMagic( $x ) { return wfSetVar( $this->mInterwikiMagic, $x ); }
- function setAllowExternalImages( $x ) { return wfSetVar( $this->mAllowExternalImages, $x ); }
- function setAllowExternalImagesFrom( $x ) { return wfSetVar( $this->mAllowExternalImagesFrom, $x ); }
- function setEnableImageWhitelist( $x ) { return wfSetVar( $this->mEnableImageWhitelist, $x ); }
- function setDateFormat( $x ) { return wfSetVar( $this->mDateFormat, $x ); }
- function setEditSection( $x ) { return wfSetVar( $this->mEditSection, $x ); }
- function setNumberHeadings( $x ) { return wfSetVar( $this->mNumberHeadings, $x ); }
- function setAllowSpecialInclusion( $x ) { return wfSetVar( $this->mAllowSpecialInclusion, $x ); }
- function setTidy( $x ) { return wfSetVar( $this->mTidy, $x); }
- function setSkin( $x ) { $this->mSkin = $x; }
- function setInterfaceMessage( $x ) { return wfSetVar( $this->mInterfaceMessage, $x); }
- function setTargetLanguage( $x ) { return wfSetVar( $this->mTargetLanguage, $x, true ); }
- function setMaxIncludeSize( $x ) { return wfSetVar( $this->mMaxIncludeSize, $x ); }
- function setMaxPPNodeCount( $x ) { return wfSetVar( $this->mMaxPPNodeCount, $x ); }
- function setMaxTemplateDepth( $x ) { return wfSetVar( $this->mMaxTemplateDepth, $x ); }
- function setRemoveComments( $x ) { return wfSetVar( $this->mRemoveComments, $x ); }
- function setTemplateCallback( $x ) { return wfSetVar( $this->mTemplateCallback, $x ); }
- function enableLimitReport( $x = true ) { return wfSetVar( $this->mEnableLimitReport, $x ); }
- function setTimestamp( $x ) { return wfSetVar( $this->mTimestamp, $x ); }
- function setCleanSignatures( $x ) { return wfSetVar( $this->mCleanSignatures, $x ); }
- function setExternalLinkTarget( $x ) { return wfSetVar( $this->mExternalLinkTarget, $x ); }
- function setMath( $x ) { return wfSetVar( $this->mMath, $x ); }
- function setUserLang( $x ) { return wfSetVar( $this->mUserLang, $x ); }
- function setThumbSize( $x ) { return wfSetVar( $this->mThumbSize, $x ); }
-
- function setIsPreview( $x ) { return wfSetVar( $this->mIsPreview, $x ); }
- function setIsSectionPreview( $x ) { return wfSetVar( $this->mIsSectionPreview, $x ); }
- function setIsPrintable( $x ) { return wfSetVar( $this->mIsPrintable, $x ); }
+ * Timestamp used for {{CURRENTDAY}} etc.
+ * @param string|null $x New value (null is no change)
+ * @return string Old value
+ */
+ public function setTimestamp( $x ) {
+ return wfSetVar( $this->mTimestamp, $x );
+ }
/**
- * Extra key that should be present in the parser cache key.
+ * Create "edit section" links?
+ * @return bool
*/
- function addExtraKey( $key ) {
- $this->mExtraKey .= '!' . $key;
+ public function getEditSection() {
+ return $this->mEditSection;
}
- function __construct( $user = null ) {
- $this->initialiseFromUser( $user );
+ /**
+ * Create "edit section" links?
+ * @param bool|null $x New value (null is no change)
+ * @return bool Old value
+ */
+ public function setEditSection( $x ) {
+ return wfSetVar( $this->mEditSection, $x );
}
/**
- * Get parser options
+ * Set the redirect target.
+ *
+ * Note that setting or changing this does not *make* the page a redirect
+ * or change its target, it merely records the information for reference
+ * during the parse.
*
- * @param $user User object
- * @return ParserOptions object
+ * @since 1.24
+ * @param Title|null $title
*/
- static function newFromUser( $user ) {
- return new ParserOptions( $user );
+ function setRedirectTarget( $title ) {
+ $this->redirectTarget = $title;
}
- /** Get user options */
- function initialiseFromUser( $userInput ) {
- global $wgUseDynamicDates, $wgInterwikiMagic, $wgAllowExternalImages;
- global $wgAllowExternalImagesFrom, $wgEnableImageWhitelist, $wgAllowSpecialInclusion, $wgMaxArticleSize;
- global $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth, $wgCleanSignatures;
- global $wgExternalLinkTarget, $wgLang;
+ /**
+ * Get the previously-set redirect target.
+ *
+ * @since 1.24
+ * @return Title|null
+ */
+ function getRedirectTarget() {
+ return $this->redirectTarget;
+ }
- wfProfileIn( __METHOD__ );
+ /**
+ * Extra key that should be present in the parser cache key.
+ * @warning Consider registering your additional options with the
+ * ParserOptionsRegister hook instead of using this method.
+ * @param string $key
+ */
+ public function addExtraKey( $key ) {
+ $this->mExtraKey .= '!' . $key;
+ }
+
+ /**
+ * Current user
+ * @return User
+ */
+ public function getUser() {
+ return $this->mUser;
+ }
- if ( !$userInput ) {
+ /**
+ * @warning For interaction with the parser cache, use
+ * WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
+ * ParserOptions::newCanonical() instead.
+ * @param User $user
+ * @param Language $lang
+ */
+ public function __construct( $user = null, $lang = null ) {
+ if ( $user === null ) {
global $wgUser;
- if ( isset( $wgUser ) ) {
- $user = $wgUser;
- } else {
+ if ( $wgUser === null ) {
$user = new User;
+ } else {
+ $user = $wgUser;
}
- } else {
- $user =& $userInput;
}
+ if ( $lang === null ) {
+ global $wgLang;
+ if ( !StubObject::isRealObject( $wgLang ) ) {
+ $wgLang->_unstub();
+ }
+ $lang = $wgLang;
+ }
+ $this->initialiseFromUser( $user, $lang );
+ }
+
+ /**
+ * Get a ParserOptions object for an anonymous user
+ * @warning For interaction with the parser cache, use
+ * WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
+ * ParserOptions::newCanonical() instead.
+ * @since 1.27
+ * @return ParserOptions
+ */
+ public static function newFromAnon() {
+ global $wgContLang;
+ return new ParserOptions( new User, $wgContLang );
+ }
+
+ /**
+ * Get a ParserOptions object from a given user.
+ * Language will be taken from $wgLang.
+ *
+ * @warning For interaction with the parser cache, use
+ * WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
+ * ParserOptions::newCanonical() instead.
+ * @param User $user
+ * @return ParserOptions
+ */
+ public static function newFromUser( $user ) {
+ return new ParserOptions( $user );
+ }
+
+ /**
+ * Get a ParserOptions object from a given user and language
+ *
+ * @warning For interaction with the parser cache, use
+ * WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
+ * ParserOptions::newCanonical() instead.
+ * @param User $user
+ * @param Language $lang
+ * @return ParserOptions
+ */
+ public static function newFromUserAndLang( User $user, Language $lang ) {
+ return new ParserOptions( $user, $lang );
+ }
+
+ /**
+ * Get a ParserOptions object from a IContextSource object
+ *
+ * @warning For interaction with the parser cache, use
+ * WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
+ * ParserOptions::newCanonical() instead.
+ * @param IContextSource $context
+ * @return ParserOptions
+ */
+ public static function newFromContext( IContextSource $context ) {
+ return new ParserOptions( $context->getUser(), $context->getLanguage() );
+ }
+
+ /**
+ * Creates a "canonical" ParserOptions object
+ *
+ * For historical reasons, certain options have default values that are
+ * different from the canonical values used for caching.
+ *
+ * @since 1.30
+ * @param User|null $user
+ * @param Language|StubObject|null $lang
+ * @return ParserOptions
+ */
+ public static function newCanonical( User $user = null, $lang = null ) {
+ $ret = new ParserOptions( $user, $lang );
+ foreach ( self::getCanonicalOverrides() as $k => $v ) {
+ $ret->setOption( $k, $v );
+ }
+ return $ret;
+ }
+
+ /**
+ * Get default option values
+ * @warning If you change the default for an existing option (unless it's
+ * being overridden by self::getCanonicalOverrides()), all existing parser
+ * cache entries will be invalid. To avoid bugs, you'll need to handle
+ * that somehow (e.g. with the RejectParserCacheValue hook) because
+ * MediaWiki won't do it for you.
+ * @return array
+ */
+ private static function getDefaults() {
+ global $wgInterwikiMagic, $wgAllowExternalImages,
+ $wgAllowExternalImagesFrom, $wgEnableImageWhitelist, $wgAllowSpecialInclusion,
+ $wgMaxArticleSize, $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth,
+ $wgCleanSignatures, $wgExternalLinkTarget, $wgExpensiveParserFunctionLimit,
+ $wgMaxGeneratedPPNodeCount, $wgDisableLangConversion, $wgDisableTitleConversion,
+ $wgEnableMagicLinks, $wgContLang;
+
+ if ( self::$defaults === null ) {
+ // *UPDATE* ParserOptions::matches() if any of this changes as needed
+ self::$defaults = [
+ 'dateformat' => null,
+ 'tidy' => false,
+ 'interfaceMessage' => false,
+ 'targetLanguage' => null,
+ 'removeComments' => true,
+ 'enableLimitReport' => false,
+ 'preSaveTransform' => true,
+ 'isPreview' => false,
+ 'isSectionPreview' => false,
+ 'printable' => false,
+ 'allowUnsafeRawHtml' => true,
+ 'wrapclass' => 'mw-parser-output',
+ 'currentRevisionCallback' => [ 'Parser', 'statelessFetchRevision' ],
+ 'templateCallback' => [ 'Parser', 'statelessFetchTemplate' ],
+ 'speculativeRevIdCallback' => null,
+ ];
+
+ // @codingStandardsIgnoreStart Squiz.WhiteSpace.OperatorSpacing.NoSpaceAfterAmp
+ Hooks::run( 'ParserOptionsRegister', [
+ &self::$defaults,
+ &self::$inCacheKey,
+ &self::$lazyOptions,
+ ] );
+ // @codingStandardsIgnoreEnd
+
+ ksort( self::$inCacheKey );
+ }
+
+ // Unit tests depend on being able to modify the globals at will
+ return self::$defaults + [
+ 'interwikiMagic' => $wgInterwikiMagic,
+ 'allowExternalImages' => $wgAllowExternalImages,
+ 'allowExternalImagesFrom' => $wgAllowExternalImagesFrom,
+ 'enableImageWhitelist' => $wgEnableImageWhitelist,
+ 'allowSpecialInclusion' => $wgAllowSpecialInclusion,
+ 'maxIncludeSize' => $wgMaxArticleSize * 1024,
+ 'maxPPNodeCount' => $wgMaxPPNodeCount,
+ 'maxGeneratedPPNodeCount' => $wgMaxGeneratedPPNodeCount,
+ 'maxPPExpandDepth' => $wgMaxPPExpandDepth,
+ 'maxTemplateDepth' => $wgMaxTemplateDepth,
+ 'expensiveParserFunctionLimit' => $wgExpensiveParserFunctionLimit,
+ 'externalLinkTarget' => $wgExternalLinkTarget,
+ 'cleanSignatures' => $wgCleanSignatures,
+ 'disableContentConversion' => $wgDisableLangConversion,
+ 'disableTitleConversion' => $wgDisableLangConversion || $wgDisableTitleConversion,
+ 'magicISBNLinks' => $wgEnableMagicLinks['ISBN'],
+ 'magicPMIDLinks' => $wgEnableMagicLinks['PMID'],
+ 'magicRFCLinks' => $wgEnableMagicLinks['RFC'],
+ 'numberheadings' => User::getDefaultOption( 'numberheadings' ),
+ 'thumbsize' => User::getDefaultOption( 'thumbsize' ),
+ 'stubthreshold' => 0,
+ 'userlang' => $wgContLang,
+ ];
+ }
+
+ /**
+ * Get "canonical" non-default option values
+ * @see self::newCanonical
+ * @warning If you change the override for an existing option, all existing
+ * parser cache entries will be invalid. To avoid bugs, you'll need to
+ * handle that somehow (e.g. with the RejectParserCacheValue hook) because
+ * MediaWiki won't do it for you.
+ * @return array
+ */
+ private static function getCanonicalOverrides() {
+ global $wgEnableParserLimitReporting;
+
+ return [
+ 'tidy' => true,
+ 'enableLimitReport' => $wgEnableParserLimitReporting,
+ ];
+ }
+
+ /**
+ * Get user options
+ *
+ * @param User $user
+ * @param Language $lang
+ */
+ private function initialiseFromUser( $user, $lang ) {
+ $this->options = self::getDefaults();
$this->mUser = $user;
+ $this->options['numberheadings'] = $user->getOption( 'numberheadings' );
+ $this->options['thumbsize'] = $user->getOption( 'thumbsize' );
+ $this->options['stubthreshold'] = $user->getStubThreshold();
+ $this->options['userlang'] = $lang;
+ }
+
+ /**
+ * Check if these options match that of another options set
+ *
+ * This ignores report limit settings that only affect HTML comments
+ *
+ * @param ParserOptions $other
+ * @return bool
+ * @since 1.25
+ */
+ public function matches( ParserOptions $other ) {
+ // Populate lazy options
+ foreach ( self::$lazyOptions as $name => $callback ) {
+ if ( $this->options[$name] === null ) {
+ $this->options[$name] = call_user_func( $callback, $this, $name );
+ }
+ if ( $other->options[$name] === null ) {
+ $other->options[$name] = call_user_func( $callback, $other, $name );
+ }
+ }
+
+ // Compare most options
+ $options = array_keys( $this->options );
+ $options = array_diff( $options, [
+ 'enableLimitReport', // only affects HTML comments
+ ] );
+ foreach ( $options as $option ) {
+ $o1 = $this->optionToString( $this->options[$option] );
+ $o2 = $this->optionToString( $other->options[$option] );
+ if ( $o1 !== $o2 ) {
+ return false;
+ }
+ }
- $this->mUseDynamicDates = $wgUseDynamicDates;
- $this->mInterwikiMagic = $wgInterwikiMagic;
- $this->mAllowExternalImages = $wgAllowExternalImages;
- $this->mAllowExternalImagesFrom = $wgAllowExternalImagesFrom;
- $this->mEnableImageWhitelist = $wgEnableImageWhitelist;
- $this->mSkin = null; # Deferred
- $this->mDateFormat = null; # Deferred
- $this->mEditSection = true;
- $this->mNumberHeadings = $user->getOption( 'numberheadings' );
- $this->mAllowSpecialInclusion = $wgAllowSpecialInclusion;
- $this->mTidy = false;
- $this->mInterfaceMessage = false;
- $this->mTargetLanguage = null; // default depends on InterfaceMessage setting
- $this->mMaxIncludeSize = $wgMaxArticleSize * 1024;
- $this->mMaxPPNodeCount = $wgMaxPPNodeCount;
- $this->mMaxPPExpandDepth = $wgMaxPPExpandDepth;
- $this->mMaxTemplateDepth = $wgMaxTemplateDepth;
- $this->mRemoveComments = true;
- $this->mTemplateCallback = array( 'Parser', 'statelessFetchTemplate' );
- $this->mEnableLimitReport = false;
- $this->mCleanSignatures = $wgCleanSignatures;
- $this->mExternalLinkTarget = $wgExternalLinkTarget;
- $this->mMath = $user->getOption( 'math' );
- $this->mUserLang = $wgLang->getCode();
- $this->mThumbSize = $user->getOption( 'thumbsize' );
-
- $this->mIsPreview = false;
- $this->mIsSectionPreview = false;
- $this->mIsPrintable = false;
-
- wfProfileOut( __METHOD__ );
+ // Compare most other fields
+ $fields = array_keys( get_class_vars( __CLASS__ ) );
+ $fields = array_diff( $fields, [
+ 'defaults', // static
+ 'lazyOptions', // static
+ 'inCacheKey', // static
+ 'options', // Already checked above
+ 'onAccessCallback', // only used for ParserOutput option tracking
+ ] );
+ foreach ( $fields as $field ) {
+ if ( !is_object( $this->$field ) && $this->$field !== $other->$field ) {
+ return false;
+ }
+ }
+
+ return true;
}
/**
* Registers a callback for tracking which ParserOptions which are used.
* This is a private API with the parser.
+ * @param callable $callback
*/
- function registerWatcher( $callback ) {
+ public function registerWatcher( $callback ) {
$this->onAccessCallback = $callback;
}
-
+
/**
* Called when an option is accessed.
+ * Calls the watcher that was set using registerWatcher().
+ * Typically, the watcher callback is ParserOutput::registerOption().
+ * The information registered that way will be used by ParserCache::save().
+ *
+ * @param string $optionName Name of the option
*/
- protected function optionUsed( $optionName ) {
+ public function optionUsed( $optionName ) {
if ( $this->onAccessCallback ) {
call_user_func( $this->onAccessCallback, $optionName );
}
}
-
+
/**
- * Returns the full array of options that would have been used by
+ * Returns the full array of options that would have been used by
* in 1.16.
* Used to get the old parser cache entries when available.
+ * @deprecated since 1.30. You probably want self::allCacheVaryingOptions() instead.
+ * @return array
*/
public static function legacyOptions() {
- global $wgUseDynamicDates;
- $legacyOpts = array( 'math', 'stubthreshold', 'numberheadings', 'userlang', 'thumbsize', 'editsection', 'printable' );
- if ( $wgUseDynamicDates ) {
- $legacyOpts[] = 'dateformat';
+ wfDeprecated( __METHOD__, '1.30' );
+ return [
+ 'stubthreshold',
+ 'numberheadings',
+ 'userlang',
+ 'thumbsize',
+ 'editsection',
+ 'printable'
+ ];
+ }
+
+ /**
+ * Return all option keys that vary the options hash
+ * @since 1.30
+ * @return string[]
+ */
+ public static function allCacheVaryingOptions() {
+ // Trigger a call to the 'ParserOptionsRegister' hook if it hasn't
+ // already been called.
+ if ( self::$defaults === null ) {
+ self::getDefaults();
}
- return $legacyOpts;
+ return array_keys( array_filter( self::$inCacheKey ) );
}
-
+
+ /**
+ * Convert an option to a string value
+ * @param mixed $value
+ * @return string
+ */
+ private function optionToString( $value ) {
+ if ( $value === true ) {
+ return '1';
+ } elseif ( $value === false ) {
+ return '0';
+ } elseif ( $value === null ) {
+ return '';
+ } elseif ( $value instanceof Language ) {
+ return $value->getCode();
+ } elseif ( is_array( $value ) ) {
+ return '[' . join( ',', array_map( [ $this, 'optionToString' ], $value ) ) . ']';
+ } else {
+ return (string)$value;
+ }
+ }
+
/**
* Generate a hash string with the values set on these ParserOptions
* for the keys given in the array.
* This will be used as part of the hash key for the parser cache,
- * so users sharign the options with vary for the same page share
+ * so users sharing the options with vary for the same page share
* the same cached data safely.
- *
- * Replaces User::getPageRenderingHash()
- *
- * Extensions which require it should install 'PageRenderingHash' hook,
- * which will give them a chance to modify this key based on their own
- * settings.
*
* @since 1.17
- * @return \string Page rendering hash
- */
- public function optionsHash( $forOptions ) {
- global $wgContLang, $wgRenderHashAppend;
-
- $confstr = '';
-
- if ( in_array( 'math', $forOptions ) )
- $confstr .= $this->mMath;
- else
- $confstr .= '*';
-
-
- // Space assigned for the stubthreshold but unused
- // since it disables the parser cache, its value will always
- // be 0 when this function is called by parsercache.
- // The conditional is here to avoid a confusing 0
- if ( true || in_array( 'stubthreshold', $forOptions ) )
- $confstr .= '!0' ;
- else
- $confstr .= '!*' ;
-
- if ( in_array( 'dateformat', $forOptions ) )
- $confstr .= '!' . $this->getDateFormat();
-
- if ( in_array( 'numberheadings', $forOptions ) )
- $confstr .= '!' . ( $this->mNumberHeadings ? '1' : '' );
- else
- $confstr .= '!*';
-
- if ( in_array( 'userlang', $forOptions ) )
- $confstr .= '!' . $this->mUserLang;
- else
- $confstr .= '!*';
-
- if ( in_array( 'thumbsize', $forOptions ) )
- $confstr .= '!' . $this->mThumbSize;
- else
- $confstr .= '!*';
+ * @param array $forOptions
+ * @param Title $title Used to get the content language of the page (since r97636)
+ * @return string Page rendering hash
+ */
+ public function optionsHash( $forOptions, $title = null ) {
+ global $wgRenderHashAppend;
+
+ $options = $this->options;
+ $defaults = self::getCanonicalOverrides() + self::getDefaults();
+ $inCacheKey = self::$inCacheKey;
+
+ // Historical hack: 'editsection' hasn't been a true parser option since
+ // Feb 2015 (instead the parser outputs a constant placeholder and post-parse
+ // processing handles the option). But Wikibase forces it in $forOptions
+ // and expects the cache key to still vary on it for T85252.
+ // @deprecated since 1.30, Wikibase should use addExtraKey() or something instead.
+ if ( in_array( 'editsection', $forOptions, true ) ) {
+ $options['editsection'] = $this->mEditSection;
+ $defaults['editsection'] = true;
+ $inCacheKey['editsection'] = true;
+ ksort( $inCacheKey );
+ }
+
+ // We only include used options with non-canonical values in the key
+ // so adding a new option doesn't invalidate the entire parser cache.
+ // The drawback to this is that changing the default value of an option
+ // requires manual invalidation of existing cache entries, as mentioned
+ // in the docs on the relevant methods and hooks.
+ $values = [];
+ foreach ( $inCacheKey as $option => $include ) {
+ if ( $include && in_array( $option, $forOptions, true ) ) {
+ $v = $this->optionToString( $options[$option] );
+ $d = $this->optionToString( $defaults[$option] );
+ if ( $v !== $d ) {
+ $values[] = "$option=$v";
+ }
+ }
+ }
+
+ $confstr = $values ? join( '!', $values ) : 'canonical';
// add in language specific options, if any
- // FIXME: This is just a way of retrieving the url/user preferred variant
- $confstr .= $wgContLang->getExtraHashOptions();
+ // @todo FIXME: This is just a way of retrieving the url/user preferred variant
+ if ( !is_null( $title ) ) {
+ $confstr .= $title->getPageLanguage()->getExtraHashOptions();
+ } else {
+ global $wgContLang;
+ $confstr .= $wgContLang->getExtraHashOptions();
+ }
- // Since the skin could be overloading link(), it should be
- // included here but in practice, none of our skins do that.
- // $confstr .= "!" . $this->mSkin->getSkinName();
-
$confstr .= $wgRenderHashAppend;
- if ( !in_array( 'editsection', $forOptions ) ) {
- $confstr .= '!*';
- } elseif ( !$this->mEditSection ) {
- $confstr .= '!edit=0';
- }
-
- if ( $this->mIsPrintable && in_array( 'printable', $forOptions ) )
- $confstr .= '!printable=1';
-
- if ( $this->mExtraKey != '' )
+ if ( $this->mExtraKey != '' ) {
$confstr .= $this->mExtraKey;
-
+ }
+
// Give a chance for extensions to modify the hash, if they have
// extra options or other effects on the parser cache.
- wfRunHooks( 'PageRenderingHash', array( &$confstr ) );
+ Hooks::run( 'PageRenderingHash', [ &$confstr, $this->getUser(), &$forOptions ] );
// Make it a valid memcached key fragment
$confstr = str_replace( ' ', '_', $confstr );
-
+
return $confstr;
}
+
+ /**
+ * Test whether these options are safe to cache
+ * @since 1.30
+ * @return bool
+ */
+ public function isSafeToCache() {
+ $defaults = self::getCanonicalOverrides() + self::getDefaults();
+ foreach ( $this->options as $option => $value ) {
+ if ( empty( self::$inCacheKey[$option] ) ) {
+ $v = $this->optionToString( $value );
+ $d = $this->optionToString( $defaults[$option] );
+ if ( $v !== $d ) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Sets a hook to force that a page exists, and sets a current revision callback to return
+ * a revision with custom content when the current revision of the page is requested.
+ *
+ * @since 1.25
+ * @param Title $title
+ * @param Content $content
+ * @param User $user The user that the fake revision is attributed to
+ * @return ScopedCallback to unset the hook
+ */
+ public function setupFakeRevision( $title, $content, $user ) {
+ $oldCallback = $this->setCurrentRevisionCallback(
+ function (
+ $titleToCheck, $parser = false ) use ( $title, $content, $user, &$oldCallback
+ ) {
+ if ( $titleToCheck->equals( $title ) ) {
+ return new Revision( [
+ 'page' => $title->getArticleID(),
+ 'user_text' => $user->getName(),
+ 'user' => $user->getId(),
+ 'parent_id' => $title->getLatestRevID(),
+ 'title' => $title,
+ 'content' => $content
+ ] );
+ } else {
+ return call_user_func( $oldCallback, $titleToCheck, $parser );
+ }
+ }
+ );
+
+ global $wgHooks;
+ $wgHooks['TitleExists'][] =
+ function ( $titleToCheck, &$exists ) use ( $title ) {
+ if ( $titleToCheck->equals( $title ) ) {
+ $exists = true;
+ }
+ };
+ end( $wgHooks['TitleExists'] );
+ $key = key( $wgHooks['TitleExists'] );
+ LinkCache::singleton()->clearBadLink( $title->getPrefixedDBkey() );
+ return new ScopedCallback( function () use ( $title, $key ) {
+ global $wgHooks;
+ unset( $wgHooks['TitleExists'][$key] );
+ LinkCache::singleton()->clearLink( $title );
+ } );
+ }
}
+
+/**
+ * For really cool vim folding this needs to be at the end:
+ * vim: foldmarker=@{,@} foldmethod=marker
+ */