]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/parser/ParserOptions.php
MediaWiki 1.30.2-scripts2
[autoinstalls/mediawiki.git] / includes / parser / ParserOptions.php
1 <?php
2 /**
3  * Options for the PHP parser
4  *
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.
9  *
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.
14  *
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
19  *
20  * @file
21  * @ingroup Parser
22  */
23 use Wikimedia\ScopedCallback;
24
25 /**
26  * @brief Set options of the Parser
27  *
28  * How to add an option in core:
29  *  1. Add it to one of the arrays in ParserOptions::setDefaults()
30  *  2. If necessary, add an entry to ParserOptions::$inCacheKey
31  *  3. Add a getter and setter in the section for that.
32  *
33  * How to add an option in an extension:
34  *  1. Use the 'ParserOptionsRegister' hook to register it.
35  *  2. Where necessary, use $popt->getOption() and $popt->setOption()
36  *     to access it.
37  *
38  * @ingroup Parser
39  */
40 class ParserOptions {
41
42         /**
43          * Default values for all options that are relevant for caching.
44          * @see self::getDefaults()
45          * @var array|null
46          */
47         private static $defaults = null;
48
49         /**
50          * Lazy-loaded options
51          * @var callback[]
52          */
53         private static $lazyOptions = [
54                 'dateformat' => [ __CLASS__, 'initDateFormat' ],
55         ];
56
57         /**
58          * Specify options that are included in the cache key
59          * @var array
60          */
61         private static $inCacheKey = [
62                 'dateformat' => true,
63                 'numberheadings' => true,
64                 'thumbsize' => true,
65                 'stubthreshold' => true,
66                 'printable' => true,
67                 'userlang' => true,
68                 'wrapclass' => true,
69         ];
70
71         /**
72          * Current values for all options that are relevant for caching.
73          * @var array
74          */
75         private $options;
76
77         /**
78          * Timestamp used for {{CURRENTDAY}} etc.
79          * @var string|null
80          * @note Caching based on parse time is handled externally
81          */
82         private $mTimestamp;
83
84         /**
85          * The edit section flag is in ParserOptions for historical reasons, but
86          * doesn't actually affect the parser output since Feb 2015.
87          * @var bool
88          */
89         private $mEditSection = true;
90
91         /**
92          * Stored user object
93          * @var User
94          * @todo Track this for caching somehow without fragmenting the cache insanely
95          */
96         private $mUser;
97
98         /**
99          * Function to be called when an option is accessed.
100          * @var callable|null
101          * @note Used for collecting used options, does not affect caching
102          */
103         private $onAccessCallback = null;
104
105         /**
106          * If the page being parsed is a redirect, this should hold the redirect
107          * target.
108          * @var Title|null
109          * @todo Track this for caching somehow
110          */
111         private $redirectTarget = null;
112
113         /**
114          * Appended to the options hash
115          */
116         private $mExtraKey = '';
117
118         /**
119          * @name Option accessors
120          * @{
121          */
122
123         /**
124          * Fetch an option, generically
125          * @since 1.30
126          * @param string $name Option name
127          * @return mixed
128          */
129         public function getOption( $name ) {
130                 if ( !array_key_exists( $name, $this->options ) ) {
131                         throw new InvalidArgumentException( "Unknown parser option $name" );
132                 }
133
134                 if ( isset( self::$lazyOptions[$name] ) && $this->options[$name] === null ) {
135                         $this->options[$name] = call_user_func( self::$lazyOptions[$name], $this, $name );
136                 }
137                 if ( !empty( self::$inCacheKey[$name] ) ) {
138                         $this->optionUsed( $name );
139                 }
140                 return $this->options[$name];
141         }
142
143         /**
144          * Set an option, generically
145          * @since 1.30
146          * @param string $name Option name
147          * @param mixed $value New value. Passing null will set null, unlike many
148          *  of the existing accessors which ignore null for historical reasons.
149          * @return mixed Old value
150          */
151         public function setOption( $name, $value ) {
152                 if ( !array_key_exists( $name, $this->options ) ) {
153                         throw new InvalidArgumentException( "Unknown parser option $name" );
154                 }
155                 $old = $this->options[$name];
156                 $this->options[$name] = $value;
157                 return $old;
158         }
159
160         /**
161          * Legacy implementation
162          * @since 1.30 For implementing legacy setters only. Don't use this in new code.
163          * @deprecated since 1.30
164          * @param string $name Option name
165          * @param mixed $value New value. Passing null does not set the value.
166          * @return mixed Old value
167          */
168         protected function setOptionLegacy( $name, $value ) {
169                 if ( !array_key_exists( $name, $this->options ) ) {
170                         throw new InvalidArgumentException( "Unknown parser option $name" );
171                 }
172                 return wfSetVar( $this->options[$name], $value );
173         }
174
175         /**
176          * Whether to extract interlanguage links
177          *
178          * When true, interlanguage links will be returned by
179          * ParserOutput::getLanguageLinks() instead of generating link HTML.
180          *
181          * @return bool
182          */
183         public function getInterwikiMagic() {
184                 return $this->getOption( 'interwikiMagic' );
185         }
186
187         /**
188          * Specify whether to extract interlanguage links
189          * @param bool|null $x New value (null is no change)
190          * @return bool Old value
191          */
192         public function setInterwikiMagic( $x ) {
193                 return $this->setOptionLegacy( 'interwikiMagic', $x );
194         }
195
196         /**
197          * Allow all external images inline?
198          * @return bool
199          */
200         public function getAllowExternalImages() {
201                 return $this->getOption( 'allowExternalImages' );
202         }
203
204         /**
205          * Allow all external images inline?
206          * @param bool|null $x New value (null is no change)
207          * @return bool Old value
208          */
209         public function setAllowExternalImages( $x ) {
210                 return $this->setOptionLegacy( 'allowExternalImages', $x );
211         }
212
213         /**
214          * External images to allow
215          *
216          * When self::getAllowExternalImages() is false
217          *
218          * @return string|string[] URLs to allow
219          */
220         public function getAllowExternalImagesFrom() {
221                 return $this->getOption( 'allowExternalImagesFrom' );
222         }
223
224         /**
225          * External images to allow
226          *
227          * When self::getAllowExternalImages() is false
228          *
229          * @param string|string[]|null $x New value (null is no change)
230          * @return string|string[] Old value
231          */
232         public function setAllowExternalImagesFrom( $x ) {
233                 return $this->setOptionLegacy( 'allowExternalImagesFrom', $x );
234         }
235
236         /**
237          * Use the on-wiki external image whitelist?
238          * @return bool
239          */
240         public function getEnableImageWhitelist() {
241                 return $this->getOption( 'enableImageWhitelist' );
242         }
243
244         /**
245          * Use the on-wiki external image whitelist?
246          * @param bool|null $x New value (null is no change)
247          * @return bool Old value
248          */
249         public function setEnableImageWhitelist( $x ) {
250                 return $this->setOptionLegacy( 'enableImageWhitelist', $x );
251         }
252
253         /**
254          * Automatically number headings?
255          * @return bool
256          */
257         public function getNumberHeadings() {
258                 return $this->getOption( 'numberheadings' );
259         }
260
261         /**
262          * Automatically number headings?
263          * @param bool|null $x New value (null is no change)
264          * @return bool Old value
265          */
266         public function setNumberHeadings( $x ) {
267                 return $this->setOptionLegacy( 'numberheadings', $x );
268         }
269
270         /**
271          * Allow inclusion of special pages?
272          * @return bool
273          */
274         public function getAllowSpecialInclusion() {
275                 return $this->getOption( 'allowSpecialInclusion' );
276         }
277
278         /**
279          * Allow inclusion of special pages?
280          * @param bool|null $x New value (null is no change)
281          * @return bool Old value
282          */
283         public function setAllowSpecialInclusion( $x ) {
284                 return $this->setOptionLegacy( 'allowSpecialInclusion', $x );
285         }
286
287         /**
288          * Use tidy to cleanup output HTML?
289          * @return bool
290          */
291         public function getTidy() {
292                 return $this->getOption( 'tidy' );
293         }
294
295         /**
296          * Use tidy to cleanup output HTML?
297          * @param bool|null $x New value (null is no change)
298          * @return bool Old value
299          */
300         public function setTidy( $x ) {
301                 return $this->setOptionLegacy( 'tidy', $x );
302         }
303
304         /**
305          * Parsing an interface message?
306          * @return bool
307          */
308         public function getInterfaceMessage() {
309                 return $this->getOption( 'interfaceMessage' );
310         }
311
312         /**
313          * Parsing an interface message?
314          * @param bool|null $x New value (null is no change)
315          * @return bool Old value
316          */
317         public function setInterfaceMessage( $x ) {
318                 return $this->setOptionLegacy( 'interfaceMessage', $x );
319         }
320
321         /**
322          * Target language for the parse
323          * @return Language|null
324          */
325         public function getTargetLanguage() {
326                 return $this->getOption( 'targetLanguage' );
327         }
328
329         /**
330          * Target language for the parse
331          * @param Language|null $x New value
332          * @return Language|null Old value
333          */
334         public function setTargetLanguage( $x ) {
335                 return $this->setOption( 'targetLanguage', $x );
336         }
337
338         /**
339          * Maximum size of template expansions, in bytes
340          * @return int
341          */
342         public function getMaxIncludeSize() {
343                 return $this->getOption( 'maxIncludeSize' );
344         }
345
346         /**
347          * Maximum size of template expansions, in bytes
348          * @param int|null $x New value (null is no change)
349          * @return int Old value
350          */
351         public function setMaxIncludeSize( $x ) {
352                 return $this->setOptionLegacy( 'maxIncludeSize', $x );
353         }
354
355         /**
356          * Maximum number of nodes touched by PPFrame::expand()
357          * @return int
358          */
359         public function getMaxPPNodeCount() {
360                 return $this->getOption( 'maxPPNodeCount' );
361         }
362
363         /**
364          * Maximum number of nodes touched by PPFrame::expand()
365          * @param int|null $x New value (null is no change)
366          * @return int Old value
367          */
368         public function setMaxPPNodeCount( $x ) {
369                 return $this->setOptionLegacy( 'maxPPNodeCount', $x );
370         }
371
372         /**
373          * Maximum number of nodes generated by Preprocessor::preprocessToObj()
374          * @return int
375          */
376         public function getMaxGeneratedPPNodeCount() {
377                 return $this->getOption( 'maxGeneratedPPNodeCount' );
378         }
379
380         /**
381          * Maximum number of nodes generated by Preprocessor::preprocessToObj()
382          * @param int|null $x New value (null is no change)
383          * @return int
384          */
385         public function setMaxGeneratedPPNodeCount( $x ) {
386                 return $this->setOptionLegacy( 'maxGeneratedPPNodeCount', $x );
387         }
388
389         /**
390          * Maximum recursion depth in PPFrame::expand()
391          * @return int
392          */
393         public function getMaxPPExpandDepth() {
394                 return $this->getOption( 'maxPPExpandDepth' );
395         }
396
397         /**
398          * Maximum recursion depth for templates within templates
399          * @return int
400          */
401         public function getMaxTemplateDepth() {
402                 return $this->getOption( 'maxTemplateDepth' );
403         }
404
405         /**
406          * Maximum recursion depth for templates within templates
407          * @param int|null $x New value (null is no change)
408          * @return int Old value
409          */
410         public function setMaxTemplateDepth( $x ) {
411                 return $this->setOptionLegacy( 'maxTemplateDepth', $x );
412         }
413
414         /**
415          * Maximum number of calls per parse to expensive parser functions
416          * @since 1.20
417          * @return int
418          */
419         public function getExpensiveParserFunctionLimit() {
420                 return $this->getOption( 'expensiveParserFunctionLimit' );
421         }
422
423         /**
424          * Maximum number of calls per parse to expensive parser functions
425          * @since 1.20
426          * @param int|null $x New value (null is no change)
427          * @return int Old value
428          */
429         public function setExpensiveParserFunctionLimit( $x ) {
430                 return $this->setOptionLegacy( 'expensiveParserFunctionLimit', $x );
431         }
432
433         /**
434          * Remove HTML comments
435          * @warning Only applies to preprocess operations
436          * @return bool
437          */
438         public function getRemoveComments() {
439                 return $this->getOption( 'removeComments' );
440         }
441
442         /**
443          * Remove HTML comments
444          * @warning Only applies to preprocess operations
445          * @param bool|null $x New value (null is no change)
446          * @return bool Old value
447          */
448         public function setRemoveComments( $x ) {
449                 return $this->setOptionLegacy( 'removeComments', $x );
450         }
451
452         /**
453          * Enable limit report in an HTML comment on output
454          * @return bool
455          */
456         public function getEnableLimitReport() {
457                 return $this->getOption( 'enableLimitReport' );
458         }
459
460         /**
461          * Enable limit report in an HTML comment on output
462          * @param bool|null $x New value (null is no change)
463          * @return bool Old value
464          */
465         public function enableLimitReport( $x = true ) {
466                 return $this->setOptionLegacy( 'enableLimitReport', $x );
467         }
468
469         /**
470          * Clean up signature texts?
471          * @see Parser::cleanSig
472          * @return bool
473          */
474         public function getCleanSignatures() {
475                 return $this->getOption( 'cleanSignatures' );
476         }
477
478         /**
479          * Clean up signature texts?
480          * @see Parser::cleanSig
481          * @param bool|null $x New value (null is no change)
482          * @return bool Old value
483          */
484         public function setCleanSignatures( $x ) {
485                 return $this->setOptionLegacy( 'cleanSignatures', $x );
486         }
487
488         /**
489          * Target attribute for external links
490          * @return string
491          */
492         public function getExternalLinkTarget() {
493                 return $this->getOption( 'externalLinkTarget' );
494         }
495
496         /**
497          * Target attribute for external links
498          * @param string|null $x New value (null is no change)
499          * @return string Old value
500          */
501         public function setExternalLinkTarget( $x ) {
502                 return $this->setOptionLegacy( 'externalLinkTarget', $x );
503         }
504
505         /**
506          * Whether content conversion should be disabled
507          * @return bool
508          */
509         public function getDisableContentConversion() {
510                 return $this->getOption( 'disableContentConversion' );
511         }
512
513         /**
514          * Whether content conversion should be disabled
515          * @param bool|null $x New value (null is no change)
516          * @return bool Old value
517          */
518         public function disableContentConversion( $x = true ) {
519                 return $this->setOptionLegacy( 'disableContentConversion', $x );
520         }
521
522         /**
523          * Whether title conversion should be disabled
524          * @return bool
525          */
526         public function getDisableTitleConversion() {
527                 return $this->getOption( 'disableTitleConversion' );
528         }
529
530         /**
531          * Whether title conversion should be disabled
532          * @param bool|null $x New value (null is no change)
533          * @return bool Old value
534          */
535         public function disableTitleConversion( $x = true ) {
536                 return $this->setOptionLegacy( 'disableTitleConversion', $x );
537         }
538
539         /**
540          * Thumb size preferred by the user.
541          * @return int
542          */
543         public function getThumbSize() {
544                 return $this->getOption( 'thumbsize' );
545         }
546
547         /**
548          * Thumb size preferred by the user.
549          * @param int|null $x New value (null is no change)
550          * @return int Old value
551          */
552         public function setThumbSize( $x ) {
553                 return $this->setOptionLegacy( 'thumbsize', $x );
554         }
555
556         /**
557          * Thumb size preferred by the user.
558          * @return int
559          */
560         public function getStubThreshold() {
561                 return $this->getOption( 'stubthreshold' );
562         }
563
564         /**
565          * Thumb size preferred by the user.
566          * @param int|null $x New value (null is no change)
567          * @return int Old value
568          */
569         public function setStubThreshold( $x ) {
570                 return $this->setOptionLegacy( 'stubthreshold', $x );
571         }
572
573         /**
574          * Parsing the page for a "preview" operation?
575          * @return bool
576          */
577         public function getIsPreview() {
578                 return $this->getOption( 'isPreview' );
579         }
580
581         /**
582          * Parsing the page for a "preview" operation?
583          * @param bool|null $x New value (null is no change)
584          * @return bool Old value
585          */
586         public function setIsPreview( $x ) {
587                 return $this->setOptionLegacy( 'isPreview', $x );
588         }
589
590         /**
591          * Parsing the page for a "preview" operation on a single section?
592          * @return bool
593          */
594         public function getIsSectionPreview() {
595                 return $this->getOption( 'isSectionPreview' );
596         }
597
598         /**
599          * Parsing the page for a "preview" operation on a single section?
600          * @param bool|null $x New value (null is no change)
601          * @return bool Old value
602          */
603         public function setIsSectionPreview( $x ) {
604                 return $this->setOptionLegacy( 'isSectionPreview', $x );
605         }
606
607         /**
608          * Parsing the printable version of the page?
609          * @return bool
610          */
611         public function getIsPrintable() {
612                 return $this->getOption( 'printable' );
613         }
614
615         /**
616          * Parsing the printable version of the page?
617          * @param bool|null $x New value (null is no change)
618          * @return bool Old value
619          */
620         public function setIsPrintable( $x ) {
621                 return $this->setOptionLegacy( 'printable', $x );
622         }
623
624         /**
625          * Transform wiki markup when saving the page?
626          * @return bool
627          */
628         public function getPreSaveTransform() {
629                 return $this->getOption( 'preSaveTransform' );
630         }
631
632         /**
633          * Transform wiki markup when saving the page?
634          * @param bool|null $x New value (null is no change)
635          * @return bool Old value
636          */
637         public function setPreSaveTransform( $x ) {
638                 return $this->setOptionLegacy( 'preSaveTransform', $x );
639         }
640
641         /**
642          * Date format index
643          * @return string
644          */
645         public function getDateFormat() {
646                 return $this->getOption( 'dateformat' );
647         }
648
649         /**
650          * Lazy initializer for dateFormat
651          */
652         private static function initDateFormat( $popt ) {
653                 return $popt->mUser->getDatePreference();
654         }
655
656         /**
657          * Date format index
658          * @param string|null $x New value (null is no change)
659          * @return string Old value
660          */
661         public function setDateFormat( $x ) {
662                 return $this->setOptionLegacy( 'dateformat', $x );
663         }
664
665         /**
666          * Get the user language used by the parser for this page and split the parser cache.
667          *
668          * @warning: Calling this causes the parser cache to be fragmented by user language!
669          * To avoid cache fragmentation, output should not depend on the user language.
670          * Use Parser::getFunctionLang() or Parser::getTargetLanguage() instead!
671          *
672          * @note This function will trigger a cache fragmentation by recording the
673          * 'userlang' option, see optionUsed(). This is done to avoid cache pollution
674          * when the page is rendered based on the language of the user.
675          *
676          * @note When saving, this will return the default language instead of the user's.
677          * {{int: }} uses this which used to produce inconsistent link tables (T16404).
678          *
679          * @return Language
680          * @since 1.19
681          */
682         public function getUserLangObj() {
683                 return $this->getOption( 'userlang' );
684         }
685
686         /**
687          * Same as getUserLangObj() but returns a string instead.
688          *
689          * @warning: Calling this causes the parser cache to be fragmented by user language!
690          * To avoid cache fragmentation, output should not depend on the user language.
691          * Use Parser::getFunctionLang() or Parser::getTargetLanguage() instead!
692          *
693          * @see getUserLangObj()
694          *
695          * @return string Language code
696          * @since 1.17
697          */
698         public function getUserLang() {
699                 return $this->getUserLangObj()->getCode();
700         }
701
702         /**
703          * Set the user language used by the parser for this page and split the parser cache.
704          * @param string|Language $x New value
705          * @return Language Old value
706          */
707         public function setUserLang( $x ) {
708                 if ( is_string( $x ) ) {
709                         $x = Language::factory( $x );
710                 }
711
712                 return $this->setOptionLegacy( 'userlang', $x );
713         }
714
715         /**
716          * Are magic ISBN links enabled?
717          * @since 1.28
718          * @return bool
719          */
720         public function getMagicISBNLinks() {
721                 return $this->getOption( 'magicISBNLinks' );
722         }
723
724         /**
725          * Are magic PMID links enabled?
726          * @since 1.28
727          * @return bool
728          */
729         public function getMagicPMIDLinks() {
730                 return $this->getOption( 'magicPMIDLinks' );
731         }
732         /**
733          * Are magic RFC links enabled?
734          * @since 1.28
735          * @return bool
736          */
737         public function getMagicRFCLinks() {
738                 return $this->getOption( 'magicRFCLinks' );
739         }
740
741         /**
742          * If the wiki is configured to allow raw html ($wgRawHtml = true)
743          * is it allowed in the specific case of parsing this page.
744          *
745          * This is meant to disable unsafe parser tags in cases where
746          * a malicious user may control the input to the parser.
747          *
748          * @note This is expected to be true for normal pages even if the
749          *  wiki has $wgRawHtml disabled in general. The setting only
750          *  signifies that raw html would be unsafe in the current context
751          *  provided that raw html is allowed at all.
752          * @since 1.29
753          * @return bool
754          */
755         public function getAllowUnsafeRawHtml() {
756                 return $this->getOption( 'allowUnsafeRawHtml' );
757         }
758
759         /**
760          * If the wiki is configured to allow raw html ($wgRawHtml = true)
761          * is it allowed in the specific case of parsing this page.
762          * @see self::getAllowUnsafeRawHtml()
763          * @since 1.29
764          * @param bool|null $x Value to set or null to get current value
765          * @return bool Current value for allowUnsafeRawHtml
766          */
767         public function setAllowUnsafeRawHtml( $x ) {
768                 return $this->setOptionLegacy( 'allowUnsafeRawHtml', $x );
769         }
770
771         /**
772          * Class to use to wrap output from Parser::parse()
773          * @since 1.30
774          * @return string|bool
775          */
776         public function getWrapOutputClass() {
777                 return $this->getOption( 'wrapclass' );
778         }
779
780         /**
781          * CSS class to use to wrap output from Parser::parse()
782          * @since 1.30
783          * @param string|bool $className Set false to disable wrapping.
784          * @return string|bool Current value
785          */
786         public function setWrapOutputClass( $className ) {
787                 if ( $className === true ) { // DWIM, they probably want the default class name
788                         $className = 'mw-parser-output';
789                 }
790                 return $this->setOption( 'wrapclass', $className );
791         }
792
793         /**
794          * Callback for current revision fetching; first argument to call_user_func().
795          * @since 1.24
796          * @return callable
797          */
798         public function getCurrentRevisionCallback() {
799                 return $this->getOption( 'currentRevisionCallback' );
800         }
801
802         /**
803          * Callback for current revision fetching; first argument to call_user_func().
804          * @since 1.24
805          * @param callable|null $x New value (null is no change)
806          * @return callable Old value
807          */
808         public function setCurrentRevisionCallback( $x ) {
809                 return $this->setOptionLegacy( 'currentRevisionCallback', $x );
810         }
811
812         /**
813          * Callback for template fetching; first argument to call_user_func().
814          * @return callable
815          */
816         public function getTemplateCallback() {
817                 return $this->getOption( 'templateCallback' );
818         }
819
820         /**
821          * Callback for template fetching; first argument to call_user_func().
822          * @param callable|null $x New value (null is no change)
823          * @return callable Old value
824          */
825         public function setTemplateCallback( $x ) {
826                 return $this->setOptionLegacy( 'templateCallback', $x );
827         }
828
829         /**
830          * Callback to generate a guess for {{REVISIONID}}
831          * @since 1.28
832          * @return callable|null
833          */
834         public function getSpeculativeRevIdCallback() {
835                 return $this->getOption( 'speculativeRevIdCallback' );
836         }
837
838         /**
839          * Callback to generate a guess for {{REVISIONID}}
840          * @since 1.28
841          * @param callable|null $x New value (null is no change)
842          * @return callable|null Old value
843          */
844         public function setSpeculativeRevIdCallback( $x ) {
845                 return $this->setOptionLegacy( 'speculativeRevIdCallback', $x );
846         }
847
848         /**@}*/
849
850         /**
851          * Timestamp used for {{CURRENTDAY}} etc.
852          * @return string
853          */
854         public function getTimestamp() {
855                 if ( !isset( $this->mTimestamp ) ) {
856                         $this->mTimestamp = wfTimestampNow();
857                 }
858                 return $this->mTimestamp;
859         }
860
861         /**
862          * Timestamp used for {{CURRENTDAY}} etc.
863          * @param string|null $x New value (null is no change)
864          * @return string Old value
865          */
866         public function setTimestamp( $x ) {
867                 return wfSetVar( $this->mTimestamp, $x );
868         }
869
870         /**
871          * Create "edit section" links?
872          * @return bool
873          */
874         public function getEditSection() {
875                 return $this->mEditSection;
876         }
877
878         /**
879          * Create "edit section" links?
880          * @param bool|null $x New value (null is no change)
881          * @return bool Old value
882          */
883         public function setEditSection( $x ) {
884                 return wfSetVar( $this->mEditSection, $x );
885         }
886
887         /**
888          * Set the redirect target.
889          *
890          * Note that setting or changing this does not *make* the page a redirect
891          * or change its target, it merely records the information for reference
892          * during the parse.
893          *
894          * @since 1.24
895          * @param Title|null $title
896          */
897         function setRedirectTarget( $title ) {
898                 $this->redirectTarget = $title;
899         }
900
901         /**
902          * Get the previously-set redirect target.
903          *
904          * @since 1.24
905          * @return Title|null
906          */
907         function getRedirectTarget() {
908                 return $this->redirectTarget;
909         }
910
911         /**
912          * Extra key that should be present in the parser cache key.
913          * @warning Consider registering your additional options with the
914          *  ParserOptionsRegister hook instead of using this method.
915          * @param string $key
916          */
917         public function addExtraKey( $key ) {
918                 $this->mExtraKey .= '!' . $key;
919         }
920
921         /**
922          * Current user
923          * @return User
924          */
925         public function getUser() {
926                 return $this->mUser;
927         }
928
929         /**
930          * @warning For interaction with the parser cache, use
931          *  WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
932          *  ParserOptions::newCanonical() instead.
933          * @param User $user
934          * @param Language $lang
935          */
936         public function __construct( $user = null, $lang = null ) {
937                 if ( $user === null ) {
938                         global $wgUser;
939                         if ( $wgUser === null ) {
940                                 $user = new User;
941                         } else {
942                                 $user = $wgUser;
943                         }
944                 }
945                 if ( $lang === null ) {
946                         global $wgLang;
947                         if ( !StubObject::isRealObject( $wgLang ) ) {
948                                 $wgLang->_unstub();
949                         }
950                         $lang = $wgLang;
951                 }
952                 $this->initialiseFromUser( $user, $lang );
953         }
954
955         /**
956          * Get a ParserOptions object for an anonymous user
957          * @warning For interaction with the parser cache, use
958          *  WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
959          *  ParserOptions::newCanonical() instead.
960          * @since 1.27
961          * @return ParserOptions
962          */
963         public static function newFromAnon() {
964                 global $wgContLang;
965                 return new ParserOptions( new User, $wgContLang );
966         }
967
968         /**
969          * Get a ParserOptions object from a given user.
970          * Language will be taken from $wgLang.
971          *
972          * @warning For interaction with the parser cache, use
973          *  WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
974          *  ParserOptions::newCanonical() instead.
975          * @param User $user
976          * @return ParserOptions
977          */
978         public static function newFromUser( $user ) {
979                 return new ParserOptions( $user );
980         }
981
982         /**
983          * Get a ParserOptions object from a given user and language
984          *
985          * @warning For interaction with the parser cache, use
986          *  WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
987          *  ParserOptions::newCanonical() instead.
988          * @param User $user
989          * @param Language $lang
990          * @return ParserOptions
991          */
992         public static function newFromUserAndLang( User $user, Language $lang ) {
993                 return new ParserOptions( $user, $lang );
994         }
995
996         /**
997          * Get a ParserOptions object from a IContextSource object
998          *
999          * @warning For interaction with the parser cache, use
1000          *  WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
1001          *  ParserOptions::newCanonical() instead.
1002          * @param IContextSource $context
1003          * @return ParserOptions
1004          */
1005         public static function newFromContext( IContextSource $context ) {
1006                 return new ParserOptions( $context->getUser(), $context->getLanguage() );
1007         }
1008
1009         /**
1010          * Creates a "canonical" ParserOptions object
1011          *
1012          * For historical reasons, certain options have default values that are
1013          * different from the canonical values used for caching.
1014          *
1015          * @since 1.30
1016          * @param User|null $user
1017          * @param Language|StubObject|null $lang
1018          * @return ParserOptions
1019          */
1020         public static function newCanonical( User $user = null, $lang = null ) {
1021                 $ret = new ParserOptions( $user, $lang );
1022                 foreach ( self::getCanonicalOverrides() as $k => $v ) {
1023                         $ret->setOption( $k, $v );
1024                 }
1025                 return $ret;
1026         }
1027
1028         /**
1029          * Get default option values
1030          * @warning If you change the default for an existing option (unless it's
1031          *  being overridden by self::getCanonicalOverrides()), all existing parser
1032          *  cache entries will be invalid. To avoid bugs, you'll need to handle
1033          *  that somehow (e.g. with the RejectParserCacheValue hook) because
1034          *  MediaWiki won't do it for you.
1035          * @return array
1036          */
1037         private static function getDefaults() {
1038                 global $wgInterwikiMagic, $wgAllowExternalImages,
1039                         $wgAllowExternalImagesFrom, $wgEnableImageWhitelist, $wgAllowSpecialInclusion,
1040                         $wgMaxArticleSize, $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth,
1041                         $wgCleanSignatures, $wgExternalLinkTarget, $wgExpensiveParserFunctionLimit,
1042                         $wgMaxGeneratedPPNodeCount, $wgDisableLangConversion, $wgDisableTitleConversion,
1043                         $wgEnableMagicLinks, $wgContLang;
1044
1045                 if ( self::$defaults === null ) {
1046                         // *UPDATE* ParserOptions::matches() if any of this changes as needed
1047                         self::$defaults = [
1048                                 'dateformat' => null,
1049                                 'tidy' => false,
1050                                 'interfaceMessage' => false,
1051                                 'targetLanguage' => null,
1052                                 'removeComments' => true,
1053                                 'enableLimitReport' => false,
1054                                 'preSaveTransform' => true,
1055                                 'isPreview' => false,
1056                                 'isSectionPreview' => false,
1057                                 'printable' => false,
1058                                 'allowUnsafeRawHtml' => true,
1059                                 'wrapclass' => 'mw-parser-output',
1060                                 'currentRevisionCallback' => [ 'Parser', 'statelessFetchRevision' ],
1061                                 'templateCallback' => [ 'Parser', 'statelessFetchTemplate' ],
1062                                 'speculativeRevIdCallback' => null,
1063                         ];
1064
1065                         // @codingStandardsIgnoreStart Squiz.WhiteSpace.OperatorSpacing.NoSpaceAfterAmp
1066                         Hooks::run( 'ParserOptionsRegister', [
1067                                 &self::$defaults,
1068                                 &self::$inCacheKey,
1069                                 &self::$lazyOptions,
1070                         ] );
1071                         // @codingStandardsIgnoreEnd
1072
1073                         ksort( self::$inCacheKey );
1074                 }
1075
1076                 // Unit tests depend on being able to modify the globals at will
1077                 return self::$defaults + [
1078                         'interwikiMagic' => $wgInterwikiMagic,
1079                         'allowExternalImages' => $wgAllowExternalImages,
1080                         'allowExternalImagesFrom' => $wgAllowExternalImagesFrom,
1081                         'enableImageWhitelist' => $wgEnableImageWhitelist,
1082                         'allowSpecialInclusion' => $wgAllowSpecialInclusion,
1083                         'maxIncludeSize' => $wgMaxArticleSize * 1024,
1084                         'maxPPNodeCount' => $wgMaxPPNodeCount,
1085                         'maxGeneratedPPNodeCount' => $wgMaxGeneratedPPNodeCount,
1086                         'maxPPExpandDepth' => $wgMaxPPExpandDepth,
1087                         'maxTemplateDepth' => $wgMaxTemplateDepth,
1088                         'expensiveParserFunctionLimit' => $wgExpensiveParserFunctionLimit,
1089                         'externalLinkTarget' => $wgExternalLinkTarget,
1090                         'cleanSignatures' => $wgCleanSignatures,
1091                         'disableContentConversion' => $wgDisableLangConversion,
1092                         'disableTitleConversion' => $wgDisableLangConversion || $wgDisableTitleConversion,
1093                         'magicISBNLinks' => $wgEnableMagicLinks['ISBN'],
1094                         'magicPMIDLinks' => $wgEnableMagicLinks['PMID'],
1095                         'magicRFCLinks' => $wgEnableMagicLinks['RFC'],
1096                         'numberheadings' => User::getDefaultOption( 'numberheadings' ),
1097                         'thumbsize' => User::getDefaultOption( 'thumbsize' ),
1098                         'stubthreshold' => 0,
1099                         'userlang' => $wgContLang,
1100                 ];
1101         }
1102
1103         /**
1104          * Get "canonical" non-default option values
1105          * @see self::newCanonical
1106          * @warning If you change the override for an existing option, all existing
1107          *  parser cache entries will be invalid. To avoid bugs, you'll need to
1108          *  handle that somehow (e.g. with the RejectParserCacheValue hook) because
1109          *  MediaWiki won't do it for you.
1110          * @return array
1111          */
1112         private static function getCanonicalOverrides() {
1113                 global $wgEnableParserLimitReporting;
1114
1115                 return [
1116                         'tidy' => true,
1117                         'enableLimitReport' => $wgEnableParserLimitReporting,
1118                 ];
1119         }
1120
1121         /**
1122          * Get user options
1123          *
1124          * @param User $user
1125          * @param Language $lang
1126          */
1127         private function initialiseFromUser( $user, $lang ) {
1128                 $this->options = self::getDefaults();
1129
1130                 $this->mUser = $user;
1131                 $this->options['numberheadings'] = $user->getOption( 'numberheadings' );
1132                 $this->options['thumbsize'] = $user->getOption( 'thumbsize' );
1133                 $this->options['stubthreshold'] = $user->getStubThreshold();
1134                 $this->options['userlang'] = $lang;
1135         }
1136
1137         /**
1138          * Check if these options match that of another options set
1139          *
1140          * This ignores report limit settings that only affect HTML comments
1141          *
1142          * @param ParserOptions $other
1143          * @return bool
1144          * @since 1.25
1145          */
1146         public function matches( ParserOptions $other ) {
1147                 // Populate lazy options
1148                 foreach ( self::$lazyOptions as $name => $callback ) {
1149                         if ( $this->options[$name] === null ) {
1150                                 $this->options[$name] = call_user_func( $callback, $this, $name );
1151                         }
1152                         if ( $other->options[$name] === null ) {
1153                                 $other->options[$name] = call_user_func( $callback, $other, $name );
1154                         }
1155                 }
1156
1157                 // Compare most options
1158                 $options = array_keys( $this->options );
1159                 $options = array_diff( $options, [
1160                         'enableLimitReport', // only affects HTML comments
1161                 ] );
1162                 foreach ( $options as $option ) {
1163                         $o1 = $this->optionToString( $this->options[$option] );
1164                         $o2 = $this->optionToString( $other->options[$option] );
1165                         if ( $o1 !== $o2 ) {
1166                                 return false;
1167                         }
1168                 }
1169
1170                 // Compare most other fields
1171                 $fields = array_keys( get_class_vars( __CLASS__ ) );
1172                 $fields = array_diff( $fields, [
1173                         'defaults', // static
1174                         'lazyOptions', // static
1175                         'inCacheKey', // static
1176                         'options', // Already checked above
1177                         'onAccessCallback', // only used for ParserOutput option tracking
1178                 ] );
1179                 foreach ( $fields as $field ) {
1180                         if ( !is_object( $this->$field ) && $this->$field !== $other->$field ) {
1181                                 return false;
1182                         }
1183                 }
1184
1185                 return true;
1186         }
1187
1188         /**
1189          * Registers a callback for tracking which ParserOptions which are used.
1190          * This is a private API with the parser.
1191          * @param callable $callback
1192          */
1193         public function registerWatcher( $callback ) {
1194                 $this->onAccessCallback = $callback;
1195         }
1196
1197         /**
1198          * Called when an option is accessed.
1199          * Calls the watcher that was set using registerWatcher().
1200          * Typically, the watcher callback is ParserOutput::registerOption().
1201          * The information registered that way will be used by ParserCache::save().
1202          *
1203          * @param string $optionName Name of the option
1204          */
1205         public function optionUsed( $optionName ) {
1206                 if ( $this->onAccessCallback ) {
1207                         call_user_func( $this->onAccessCallback, $optionName );
1208                 }
1209         }
1210
1211         /**
1212          * Returns the full array of options that would have been used by
1213          * in 1.16.
1214          * Used to get the old parser cache entries when available.
1215          * @deprecated since 1.30. You probably want self::allCacheVaryingOptions() instead.
1216          * @return array
1217          */
1218         public static function legacyOptions() {
1219                 wfDeprecated( __METHOD__, '1.30' );
1220                 return [
1221                         'stubthreshold',
1222                         'numberheadings',
1223                         'userlang',
1224                         'thumbsize',
1225                         'editsection',
1226                         'printable'
1227                 ];
1228         }
1229
1230         /**
1231          * Return all option keys that vary the options hash
1232          * @since 1.30
1233          * @return string[]
1234          */
1235         public static function allCacheVaryingOptions() {
1236                 // Trigger a call to the 'ParserOptionsRegister' hook if it hasn't
1237                 // already been called.
1238                 if ( self::$defaults === null ) {
1239                         self::getDefaults();
1240                 }
1241                 return array_keys( array_filter( self::$inCacheKey ) );
1242         }
1243
1244         /**
1245          * Convert an option to a string value
1246          * @param mixed $value
1247          * @return string
1248          */
1249         private function optionToString( $value ) {
1250                 if ( $value === true ) {
1251                         return '1';
1252                 } elseif ( $value === false ) {
1253                         return '0';
1254                 } elseif ( $value === null ) {
1255                         return '';
1256                 } elseif ( $value instanceof Language ) {
1257                         return $value->getCode();
1258                 } elseif ( is_array( $value ) ) {
1259                         return '[' . join( ',', array_map( [ $this, 'optionToString' ], $value ) ) . ']';
1260                 } else {
1261                         return (string)$value;
1262                 }
1263         }
1264
1265         /**
1266          * Generate a hash string with the values set on these ParserOptions
1267          * for the keys given in the array.
1268          * This will be used as part of the hash key for the parser cache,
1269          * so users sharing the options with vary for the same page share
1270          * the same cached data safely.
1271          *
1272          * @since 1.17
1273          * @param array $forOptions
1274          * @param Title $title Used to get the content language of the page (since r97636)
1275          * @return string Page rendering hash
1276          */
1277         public function optionsHash( $forOptions, $title = null ) {
1278                 global $wgRenderHashAppend;
1279
1280                 $options = $this->options;
1281                 $defaults = self::getCanonicalOverrides() + self::getDefaults();
1282                 $inCacheKey = self::$inCacheKey;
1283
1284                 // Historical hack: 'editsection' hasn't been a true parser option since
1285                 // Feb 2015 (instead the parser outputs a constant placeholder and post-parse
1286                 // processing handles the option). But Wikibase forces it in $forOptions
1287                 // and expects the cache key to still vary on it for T85252.
1288                 // @deprecated since 1.30, Wikibase should use addExtraKey() or something instead.
1289                 if ( in_array( 'editsection', $forOptions, true ) ) {
1290                         $options['editsection'] = $this->mEditSection;
1291                         $defaults['editsection'] = true;
1292                         $inCacheKey['editsection'] = true;
1293                         ksort( $inCacheKey );
1294                 }
1295
1296                 // We only include used options with non-canonical values in the key
1297                 // so adding a new option doesn't invalidate the entire parser cache.
1298                 // The drawback to this is that changing the default value of an option
1299                 // requires manual invalidation of existing cache entries, as mentioned
1300                 // in the docs on the relevant methods and hooks.
1301                 $values = [];
1302                 foreach ( $inCacheKey as $option => $include ) {
1303                         if ( $include && in_array( $option, $forOptions, true ) ) {
1304                                 $v = $this->optionToString( $options[$option] );
1305                                 $d = $this->optionToString( $defaults[$option] );
1306                                 if ( $v !== $d ) {
1307                                         $values[] = "$option=$v";
1308                                 }
1309                         }
1310                 }
1311
1312                 $confstr = $values ? join( '!', $values ) : 'canonical';
1313
1314                 // add in language specific options, if any
1315                 // @todo FIXME: This is just a way of retrieving the url/user preferred variant
1316                 if ( !is_null( $title ) ) {
1317                         $confstr .= $title->getPageLanguage()->getExtraHashOptions();
1318                 } else {
1319                         global $wgContLang;
1320                         $confstr .= $wgContLang->getExtraHashOptions();
1321                 }
1322
1323                 $confstr .= $wgRenderHashAppend;
1324
1325                 if ( $this->mExtraKey != '' ) {
1326                         $confstr .= $this->mExtraKey;
1327                 }
1328
1329                 // Give a chance for extensions to modify the hash, if they have
1330                 // extra options or other effects on the parser cache.
1331                 Hooks::run( 'PageRenderingHash', [ &$confstr, $this->getUser(), &$forOptions ] );
1332
1333                 // Make it a valid memcached key fragment
1334                 $confstr = str_replace( ' ', '_', $confstr );
1335
1336                 return $confstr;
1337         }
1338
1339         /**
1340          * Test whether these options are safe to cache
1341          * @since 1.30
1342          * @return bool
1343          */
1344         public function isSafeToCache() {
1345                 $defaults = self::getCanonicalOverrides() + self::getDefaults();
1346                 foreach ( $this->options as $option => $value ) {
1347                         if ( empty( self::$inCacheKey[$option] ) ) {
1348                                 $v = $this->optionToString( $value );
1349                                 $d = $this->optionToString( $defaults[$option] );
1350                                 if ( $v !== $d ) {
1351                                         return false;
1352                                 }
1353                         }
1354                 }
1355                 return true;
1356         }
1357
1358         /**
1359          * Sets a hook to force that a page exists, and sets a current revision callback to return
1360          * a revision with custom content when the current revision of the page is requested.
1361          *
1362          * @since 1.25
1363          * @param Title $title
1364          * @param Content $content
1365          * @param User $user The user that the fake revision is attributed to
1366          * @return ScopedCallback to unset the hook
1367          */
1368         public function setupFakeRevision( $title, $content, $user ) {
1369                 $oldCallback = $this->setCurrentRevisionCallback(
1370                         function (
1371                                 $titleToCheck, $parser = false ) use ( $title, $content, $user, &$oldCallback
1372                         ) {
1373                                 if ( $titleToCheck->equals( $title ) ) {
1374                                         return new Revision( [
1375                                                 'page' => $title->getArticleID(),
1376                                                 'user_text' => $user->getName(),
1377                                                 'user' => $user->getId(),
1378                                                 'parent_id' => $title->getLatestRevID(),
1379                                                 'title' => $title,
1380                                                 'content' => $content
1381                                         ] );
1382                                 } else {
1383                                         return call_user_func( $oldCallback, $titleToCheck, $parser );
1384                                 }
1385                         }
1386                 );
1387
1388                 global $wgHooks;
1389                 $wgHooks['TitleExists'][] =
1390                         function ( $titleToCheck, &$exists ) use ( $title ) {
1391                                 if ( $titleToCheck->equals( $title ) ) {
1392                                         $exists = true;
1393                                 }
1394                         };
1395                 end( $wgHooks['TitleExists'] );
1396                 $key = key( $wgHooks['TitleExists'] );
1397                 LinkCache::singleton()->clearBadLink( $title->getPrefixedDBkey() );
1398                 return new ScopedCallback( function () use ( $title, $key ) {
1399                         global $wgHooks;
1400                         unset( $wgHooks['TitleExists'][$key] );
1401                         LinkCache::singleton()->clearLink( $title );
1402                 } );
1403         }
1404 }
1405
1406 /**
1407  * For really cool vim folding this needs to be at the end:
1408  * vim: foldmarker=@{,@} foldmethod=marker
1409  */