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
23 use Wikimedia\ScopedCallback
;
26 * @brief Set options of the Parser
28 * All member variables are supposed to be private in theory, although in
29 * practice this is not the case.
36 * Interlanguage links are removed and returned in an array
38 private $mInterwikiMagic;
41 * Allow external images inline?
43 private $mAllowExternalImages;
46 * If not, any exception?
48 private $mAllowExternalImagesFrom;
51 * If not or it doesn't match, should we check an on-wiki whitelist?
53 private $mEnableImageWhitelist;
58 private $mDateFormat = null;
61 * Create "edit section" links?
63 private $mEditSection = true;
66 * Allow inclusion of special pages?
68 private $mAllowSpecialInclusion;
71 * Use tidy to cleanup output HTML?
73 private $mTidy = false;
76 * Which lang to call for PLURAL and GRAMMAR
78 private $mInterfaceMessage = false;
81 * Overrides $mInterfaceMessage with arbitrary language
83 private $mTargetLanguage = null;
86 * Maximum size of template expansions, in bytes
88 private $mMaxIncludeSize;
91 * Maximum number of nodes touched by PPFrame::expand()
93 private $mMaxPPNodeCount;
96 * Maximum number of nodes generated by Preprocessor::preprocessToObj()
98 private $mMaxGeneratedPPNodeCount;
101 * Maximum recursion depth in PPFrame::expand()
103 private $mMaxPPExpandDepth;
106 * Maximum recursion depth for templates within templates
108 private $mMaxTemplateDepth;
111 * Maximum number of calls per parse to expensive parser functions
113 private $mExpensiveParserFunctionLimit;
116 * Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS
118 private $mRemoveComments = true;
121 * @var callable Callback for current revision fetching; first argument to call_user_func().
123 private $mCurrentRevisionCallback =
124 [ 'Parser', 'statelessFetchRevision' ];
127 * @var callable Callback for template fetching; first argument to call_user_func().
129 private $mTemplateCallback =
130 [ 'Parser', 'statelessFetchTemplate' ];
133 * @var callable|null Callback to generate a guess for {{REVISIONID}}
135 private $mSpeculativeRevIdCallback;
138 * Enable limit report in an HTML comment on output
140 private $mEnableLimitReport = false;
143 * Timestamp used for {{CURRENTDAY}} etc.
148 * Target attribute for external links
150 private $mExternalLinkTarget;
153 * Clean up signature texts?
154 * @see Parser::cleanSig
156 private $mCleanSignatures;
159 * Transform wiki markup when saving the page?
161 private $mPreSaveTransform = true;
164 * Whether content conversion should be disabled
166 private $mDisableContentConversion;
169 * Whether title conversion should be disabled
171 private $mDisableTitleConversion;
174 * Automatically number headings?
176 private $mNumberHeadings;
179 * Thumb size preferred by the user.
184 * Maximum article size of an article to be marked as "stub"
186 private $mStubThreshold;
189 * Language object of the User language.
200 * Parsing the page for a "preview" operation?
202 private $mIsPreview = false;
205 * Parsing the page for a "preview" operation on a single section?
207 private $mIsSectionPreview = false;
210 * Parsing the printable version of the page?
212 private $mIsPrintable = false;
215 * Extra key that should be present in the caching key.
217 private $mExtraKey = '';
220 * Are magic ISBN links enabled?
222 private $mMagicISBNLinks = true;
225 * Are magic PMID links enabled?
227 private $mMagicPMIDLinks = true;
230 * Are magic RFC links enabled?
232 private $mMagicRFCLinks = true;
235 * Function to be called when an option is accessed.
237 private $onAccessCallback = null;
240 * If the page being parsed is a redirect, this should hold the redirect
244 private $redirectTarget = null;
247 * If the wiki is configured to allow raw html ($wgRawHtml = true)
248 * is it allowed in the specific case of parsing this page.
250 * This is meant to disable unsafe parser tags in cases where
251 * a malicious user may control the input to the parser.
253 * @note This is expected to be true for normal pages even if the
254 * wiki has $wgRawHtml disabled in general. The setting only
255 * signifies that raw html would be unsafe in the current context
256 * provided that raw html is allowed at all.
259 private $allowUnsafeRawHtml = true;
261 public function getInterwikiMagic() {
262 return $this->mInterwikiMagic
;
265 public function getAllowExternalImages() {
266 return $this->mAllowExternalImages
;
269 public function getAllowExternalImagesFrom() {
270 return $this->mAllowExternalImagesFrom
;
273 public function getEnableImageWhitelist() {
274 return $this->mEnableImageWhitelist
;
277 public function getEditSection() {
278 return $this->mEditSection
;
281 public function getNumberHeadings() {
282 $this->optionUsed( 'numberheadings' );
284 return $this->mNumberHeadings
;
287 public function getAllowSpecialInclusion() {
288 return $this->mAllowSpecialInclusion
;
291 public function getTidy() {
295 public function getInterfaceMessage() {
296 return $this->mInterfaceMessage
;
299 public function getTargetLanguage() {
300 return $this->mTargetLanguage
;
303 public function getMaxIncludeSize() {
304 return $this->mMaxIncludeSize
;
307 public function getMaxPPNodeCount() {
308 return $this->mMaxPPNodeCount
;
311 public function getMaxGeneratedPPNodeCount() {
312 return $this->mMaxGeneratedPPNodeCount
;
315 public function getMaxPPExpandDepth() {
316 return $this->mMaxPPExpandDepth
;
319 public function getMaxTemplateDepth() {
320 return $this->mMaxTemplateDepth
;
324 public function getExpensiveParserFunctionLimit() {
325 return $this->mExpensiveParserFunctionLimit
;
328 public function getRemoveComments() {
329 return $this->mRemoveComments
;
333 public function getCurrentRevisionCallback() {
334 return $this->mCurrentRevisionCallback
;
337 public function getTemplateCallback() {
338 return $this->mTemplateCallback
;
342 public function getSpeculativeRevIdCallback() {
343 return $this->mSpeculativeRevIdCallback
;
346 public function getEnableLimitReport() {
347 return $this->mEnableLimitReport
;
350 public function getCleanSignatures() {
351 return $this->mCleanSignatures
;
354 public function getExternalLinkTarget() {
355 return $this->mExternalLinkTarget
;
358 public function getDisableContentConversion() {
359 return $this->mDisableContentConversion
;
362 public function getDisableTitleConversion() {
363 return $this->mDisableTitleConversion
;
366 public function getThumbSize() {
367 $this->optionUsed( 'thumbsize' );
369 return $this->mThumbSize
;
372 public function getStubThreshold() {
373 $this->optionUsed( 'stubthreshold' );
375 return $this->mStubThreshold
;
378 public function getIsPreview() {
379 return $this->mIsPreview
;
382 public function getIsSectionPreview() {
383 return $this->mIsSectionPreview
;
386 public function getIsPrintable() {
387 $this->optionUsed( 'printable' );
389 return $this->mIsPrintable
;
392 public function getUser() {
396 public function getPreSaveTransform() {
397 return $this->mPreSaveTransform
;
400 public function getDateFormat() {
401 $this->optionUsed( 'dateformat' );
402 if ( !isset( $this->mDateFormat
) ) {
403 $this->mDateFormat
= $this->mUser
->getDatePreference();
405 return $this->mDateFormat
;
408 public function getTimestamp() {
409 if ( !isset( $this->mTimestamp
) ) {
410 $this->mTimestamp
= wfTimestampNow();
412 return $this->mTimestamp
;
416 * Get the user language used by the parser for this page and split the parser cache.
418 * @warning: Calling this causes the parser cache to be fragmented by user language!
419 * To avoid cache fragmentation, output should not depend on the user language.
420 * Use Parser::getFunctionLang() or Parser::getTargetLanguage() instead!
422 * @note This function will trigger a cache fragmentation by recording the
423 * 'userlang' option, see optionUsed(). This is done to avoid cache pollution
424 * when the page is rendered based on the language of the user.
426 * @note When saving, this will return the default language instead of the user's.
427 * {{int: }} uses this which used to produce inconsistent link tables (T16404).
432 public function getUserLangObj() {
433 $this->optionUsed( 'userlang' );
434 return $this->mUserLang
;
438 * Same as getUserLangObj() but returns a string instead.
440 * @warning: Calling this causes the parser cache to be fragmented by user language!
441 * To avoid cache fragmentation, output should not depend on the user language.
442 * Use Parser::getFunctionLang() or Parser::getTargetLanguage() instead!
444 * @see getUserLangObj()
446 * @return string Language code
449 public function getUserLang() {
450 return $this->getUserLangObj()->getCode();
457 public function getMagicISBNLinks() {
458 return $this->mMagicISBNLinks
;
465 public function getMagicPMIDLinks() {
466 return $this->mMagicPMIDLinks
;
472 public function getMagicRFCLinks() {
473 return $this->mMagicRFCLinks
;
480 public function getAllowUnsafeRawHtml() {
481 return $this->allowUnsafeRawHtml
;
484 public function setInterwikiMagic( $x ) {
485 return wfSetVar( $this->mInterwikiMagic
, $x );
488 public function setAllowExternalImages( $x ) {
489 return wfSetVar( $this->mAllowExternalImages
, $x );
492 public function setAllowExternalImagesFrom( $x ) {
493 return wfSetVar( $this->mAllowExternalImagesFrom
, $x );
496 public function setEnableImageWhitelist( $x ) {
497 return wfSetVar( $this->mEnableImageWhitelist
, $x );
500 public function setDateFormat( $x ) {
501 return wfSetVar( $this->mDateFormat
, $x );
504 public function setEditSection( $x ) {
505 return wfSetVar( $this->mEditSection
, $x );
508 public function setNumberHeadings( $x ) {
509 return wfSetVar( $this->mNumberHeadings
, $x );
512 public function setAllowSpecialInclusion( $x ) {
513 return wfSetVar( $this->mAllowSpecialInclusion
, $x );
516 public function setTidy( $x ) {
517 return wfSetVar( $this->mTidy
, $x );
520 public function setInterfaceMessage( $x ) {
521 return wfSetVar( $this->mInterfaceMessage
, $x );
524 public function setTargetLanguage( $x ) {
525 return wfSetVar( $this->mTargetLanguage
, $x, true );
528 public function setMaxIncludeSize( $x ) {
529 return wfSetVar( $this->mMaxIncludeSize
, $x );
532 public function setMaxPPNodeCount( $x ) {
533 return wfSetVar( $this->mMaxPPNodeCount
, $x );
536 public function setMaxGeneratedPPNodeCount( $x ) {
537 return wfSetVar( $this->mMaxGeneratedPPNodeCount
, $x );
540 public function setMaxTemplateDepth( $x ) {
541 return wfSetVar( $this->mMaxTemplateDepth
, $x );
545 public function setExpensiveParserFunctionLimit( $x ) {
546 return wfSetVar( $this->mExpensiveParserFunctionLimit
, $x );
549 public function setRemoveComments( $x ) {
550 return wfSetVar( $this->mRemoveComments
, $x );
554 public function setCurrentRevisionCallback( $x ) {
555 return wfSetVar( $this->mCurrentRevisionCallback
, $x );
559 public function setSpeculativeRevIdCallback( $x ) {
560 return wfSetVar( $this->mSpeculativeRevIdCallback
, $x );
563 public function setTemplateCallback( $x ) {
564 return wfSetVar( $this->mTemplateCallback
, $x );
567 public function enableLimitReport( $x = true ) {
568 return wfSetVar( $this->mEnableLimitReport
, $x );
571 public function setTimestamp( $x ) {
572 return wfSetVar( $this->mTimestamp
, $x );
575 public function setCleanSignatures( $x ) {
576 return wfSetVar( $this->mCleanSignatures
, $x );
579 public function setExternalLinkTarget( $x ) {
580 return wfSetVar( $this->mExternalLinkTarget
, $x );
583 public function disableContentConversion( $x = true ) {
584 return wfSetVar( $this->mDisableContentConversion
, $x );
587 public function disableTitleConversion( $x = true ) {
588 return wfSetVar( $this->mDisableTitleConversion
, $x );
591 public function setUserLang( $x ) {
592 if ( is_string( $x ) ) {
593 $x = Language
::factory( $x );
596 return wfSetVar( $this->mUserLang
, $x );
599 public function setThumbSize( $x ) {
600 return wfSetVar( $this->mThumbSize
, $x );
603 public function setStubThreshold( $x ) {
604 return wfSetVar( $this->mStubThreshold
, $x );
607 public function setPreSaveTransform( $x ) {
608 return wfSetVar( $this->mPreSaveTransform
, $x );
611 public function setIsPreview( $x ) {
612 return wfSetVar( $this->mIsPreview
, $x );
615 public function setIsSectionPreview( $x ) {
616 return wfSetVar( $this->mIsSectionPreview
, $x );
619 public function setIsPrintable( $x ) {
620 return wfSetVar( $this->mIsPrintable
, $x );
624 * @param bool|null Value to set or null to get current value
625 * @return bool Current value for allowUnsafeRawHtml
628 public function setAllowUnsafeRawHtml( $x ) {
629 return wfSetVar( $this->allowUnsafeRawHtml
, $x );
633 * Set the redirect target.
635 * Note that setting or changing this does not *make* the page a redirect
636 * or change its target, it merely records the information for reference
640 * @param Title|null $title
642 function setRedirectTarget( $title ) {
643 $this->redirectTarget
= $title;
647 * Get the previously-set redirect target.
652 function getRedirectTarget() {
653 return $this->redirectTarget
;
657 * Extra key that should be present in the parser cache key.
660 public function addExtraKey( $key ) {
661 $this->mExtraKey
.= '!' . $key;
667 * @param Language $lang
669 public function __construct( $user = null, $lang = null ) {
670 if ( $user === null ) {
672 if ( $wgUser === null ) {
678 if ( $lang === null ) {
680 if ( !StubObject
::isRealObject( $wgLang ) ) {
685 $this->initialiseFromUser( $user, $lang );
689 * Get a ParserOptions object for an anonymous user
691 * @return ParserOptions
693 public static function newFromAnon() {
695 return new ParserOptions( new User
, $wgContLang );
699 * Get a ParserOptions object from a given user.
700 * Language will be taken from $wgLang.
703 * @return ParserOptions
705 public static function newFromUser( $user ) {
706 return new ParserOptions( $user );
710 * Get a ParserOptions object from a given user and language
713 * @param Language $lang
714 * @return ParserOptions
716 public static function newFromUserAndLang( User
$user, Language
$lang ) {
717 return new ParserOptions( $user, $lang );
721 * Get a ParserOptions object from a IContextSource object
723 * @param IContextSource $context
724 * @return ParserOptions
726 public static function newFromContext( IContextSource
$context ) {
727 return new ParserOptions( $context->getUser(), $context->getLanguage() );
734 * @param Language $lang
736 private function initialiseFromUser( $user, $lang ) {
737 global $wgInterwikiMagic, $wgAllowExternalImages,
738 $wgAllowExternalImagesFrom, $wgEnableImageWhitelist, $wgAllowSpecialInclusion,
739 $wgMaxArticleSize, $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth,
740 $wgCleanSignatures, $wgExternalLinkTarget, $wgExpensiveParserFunctionLimit,
741 $wgMaxGeneratedPPNodeCount, $wgDisableLangConversion, $wgDisableTitleConversion,
744 // *UPDATE* ParserOptions::matches() if any of this changes as needed
745 $this->mInterwikiMagic
= $wgInterwikiMagic;
746 $this->mAllowExternalImages
= $wgAllowExternalImages;
747 $this->mAllowExternalImagesFrom
= $wgAllowExternalImagesFrom;
748 $this->mEnableImageWhitelist
= $wgEnableImageWhitelist;
749 $this->mAllowSpecialInclusion
= $wgAllowSpecialInclusion;
750 $this->mMaxIncludeSize
= $wgMaxArticleSize * 1024;
751 $this->mMaxPPNodeCount
= $wgMaxPPNodeCount;
752 $this->mMaxGeneratedPPNodeCount
= $wgMaxGeneratedPPNodeCount;
753 $this->mMaxPPExpandDepth
= $wgMaxPPExpandDepth;
754 $this->mMaxTemplateDepth
= $wgMaxTemplateDepth;
755 $this->mExpensiveParserFunctionLimit
= $wgExpensiveParserFunctionLimit;
756 $this->mCleanSignatures
= $wgCleanSignatures;
757 $this->mExternalLinkTarget
= $wgExternalLinkTarget;
758 $this->mDisableContentConversion
= $wgDisableLangConversion;
759 $this->mDisableTitleConversion
= $wgDisableLangConversion ||
$wgDisableTitleConversion;
760 $this->mMagicISBNLinks
= $wgEnableMagicLinks['ISBN'];
761 $this->mMagicPMIDLinks
= $wgEnableMagicLinks['PMID'];
762 $this->mMagicRFCLinks
= $wgEnableMagicLinks['RFC'];
764 $this->mUser
= $user;
765 $this->mNumberHeadings
= $user->getOption( 'numberheadings' );
766 $this->mThumbSize
= $user->getOption( 'thumbsize' );
767 $this->mStubThreshold
= $user->getStubThreshold();
768 $this->mUserLang
= $lang;
772 * Check if these options match that of another options set
774 * This ignores report limit settings that only affect HTML comments
776 * @param ParserOptions $other
780 public function matches( ParserOptions
$other ) {
781 $fields = array_keys( get_class_vars( __CLASS__
) );
782 $fields = array_diff( $fields, [
783 'mEnableLimitReport', // only effects HTML comments
784 'onAccessCallback', // only used for ParserOutput option tracking
786 foreach ( $fields as $field ) {
787 if ( !is_object( $this->$field ) && $this->$field !== $other->$field ) {
791 // Check the object and lazy-loaded options
793 $this->mUserLang
->equals( $other->mUserLang
) &&
794 $this->getDateFormat() === $other->getDateFormat()
799 * Registers a callback for tracking which ParserOptions which are used.
800 * This is a private API with the parser.
801 * @param callable $callback
803 public function registerWatcher( $callback ) {
804 $this->onAccessCallback
= $callback;
808 * Called when an option is accessed.
809 * Calls the watcher that was set using registerWatcher().
810 * Typically, the watcher callback is ParserOutput::registerOption().
811 * The information registered that way will be used by ParserCache::save().
813 * @param string $optionName Name of the option
815 public function optionUsed( $optionName ) {
816 if ( $this->onAccessCallback
) {
817 call_user_func( $this->onAccessCallback
, $optionName );
822 * Returns the full array of options that would have been used by
824 * Used to get the old parser cache entries when available.
827 public static function legacyOptions() {
839 * Generate a hash string with the values set on these ParserOptions
840 * for the keys given in the array.
841 * This will be used as part of the hash key for the parser cache,
842 * so users sharing the options with vary for the same page share
843 * the same cached data safely.
845 * Extensions which require it should install 'PageRenderingHash' hook,
846 * which will give them a chance to modify this key based on their own
850 * @param array $forOptions
851 * @param Title $title Used to get the content language of the page (since r97636)
852 * @return string Page rendering hash
854 public function optionsHash( $forOptions, $title = null ) {
855 global $wgRenderHashAppend;
857 // FIXME: Once the cache key is reorganized this argument
858 // can be dropped. It was used when the math extension was
862 // Space assigned for the stubthreshold but unused
863 // since it disables the parser cache, its value will always
864 // be 0 when this function is called by parsercache.
865 if ( in_array( 'stubthreshold', $forOptions ) ) {
866 $confstr .= '!' . $this->mStubThreshold
;
871 if ( in_array( 'dateformat', $forOptions ) ) {
872 $confstr .= '!' . $this->getDateFormat();
875 if ( in_array( 'numberheadings', $forOptions ) ) {
876 $confstr .= '!' . ( $this->mNumberHeadings ?
'1' : '' );
881 if ( in_array( 'userlang', $forOptions ) ) {
882 $confstr .= '!' . $this->mUserLang
->getCode();
887 if ( in_array( 'thumbsize', $forOptions ) ) {
888 $confstr .= '!' . $this->mThumbSize
;
893 // add in language specific options, if any
894 // @todo FIXME: This is just a way of retrieving the url/user preferred variant
895 if ( !is_null( $title ) ) {
896 $confstr .= $title->getPageLanguage()->getExtraHashOptions();
899 $confstr .= $wgContLang->getExtraHashOptions();
902 $confstr .= $wgRenderHashAppend;
904 // @note: as of Feb 2015, core never sets the editsection flag, since it uses
905 // <mw:editsection> tags to inject editsections on the fly. However, extensions
906 // may be using it by calling ParserOption::optionUsed resp. ParserOutput::registerOption
907 // directly. At least Wikibase does at this point in time.
908 if ( !in_array( 'editsection', $forOptions ) ) {
910 } elseif ( !$this->mEditSection
) {
911 $confstr .= '!edit=0';
914 if ( $this->mIsPrintable
&& in_array( 'printable', $forOptions ) ) {
915 $confstr .= '!printable=1';
918 if ( $this->mExtraKey
!= '' ) {
919 $confstr .= $this->mExtraKey
;
922 // Give a chance for extensions to modify the hash, if they have
923 // extra options or other effects on the parser cache.
924 Hooks
::run( 'PageRenderingHash', [ &$confstr, $this->getUser(), &$forOptions ] );
926 // Make it a valid memcached key fragment
927 $confstr = str_replace( ' ', '_', $confstr );
933 * Sets a hook to force that a page exists, and sets a current revision callback to return
934 * a revision with custom content when the current revision of the page is requested.
937 * @param Title $title
938 * @param Content $content
939 * @param User $user The user that the fake revision is attributed to
940 * @return ScopedCallback to unset the hook
942 public function setupFakeRevision( $title, $content, $user ) {
943 $oldCallback = $this->setCurrentRevisionCallback(
945 $titleToCheck, $parser = false ) use ( $title, $content, $user, &$oldCallback
947 if ( $titleToCheck->equals( $title ) ) {
948 return new Revision( [
949 'page' => $title->getArticleID(),
950 'user_text' => $user->getName(),
951 'user' => $user->getId(),
952 'parent_id' => $title->getLatestRevID(),
954 'content' => $content
957 return call_user_func( $oldCallback, $titleToCheck, $parser );
963 $wgHooks['TitleExists'][] =
964 function ( $titleToCheck, &$exists ) use ( $title ) {
965 if ( $titleToCheck->equals( $title ) ) {
969 end( $wgHooks['TitleExists'] );
970 $key = key( $wgHooks['TitleExists'] );
971 LinkCache
::singleton()->clearBadLink( $title->getPrefixedDBkey() );
972 return new ScopedCallback( function () use ( $title, $key ) {
974 unset( $wgHooks['TitleExists'][$key] );
975 LinkCache
::singleton()->clearLink( $title );