Merge "rdbms: Bump TransactionProfiler log entries to WARNING"
[lhc/web/wiklou.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 * Passing false is deprecated since MediaWiki 1.31
785 * @return string|bool Current value
786 */
787 public function setWrapOutputClass( $className ) {
788 if ( $className === true ) { // DWIM, they probably want the default class name
789 $className = 'mw-parser-output';
790 }
791 return $this->setOption( 'wrapclass', $className );
792 }
793
794 /**
795 * Callback for current revision fetching; first argument to call_user_func().
796 * @since 1.24
797 * @return callable
798 */
799 public function getCurrentRevisionCallback() {
800 return $this->getOption( 'currentRevisionCallback' );
801 }
802
803 /**
804 * Callback for current revision fetching; first argument to call_user_func().
805 * @since 1.24
806 * @param callable|null $x New value (null is no change)
807 * @return callable Old value
808 */
809 public function setCurrentRevisionCallback( $x ) {
810 return $this->setOptionLegacy( 'currentRevisionCallback', $x );
811 }
812
813 /**
814 * Callback for template fetching; first argument to call_user_func().
815 * @return callable
816 */
817 public function getTemplateCallback() {
818 return $this->getOption( 'templateCallback' );
819 }
820
821 /**
822 * Callback for template fetching; first argument to call_user_func().
823 * @param callable|null $x New value (null is no change)
824 * @return callable Old value
825 */
826 public function setTemplateCallback( $x ) {
827 return $this->setOptionLegacy( 'templateCallback', $x );
828 }
829
830 /**
831 * Callback to generate a guess for {{REVISIONID}}
832 * @since 1.28
833 * @return callable|null
834 */
835 public function getSpeculativeRevIdCallback() {
836 return $this->getOption( 'speculativeRevIdCallback' );
837 }
838
839 /**
840 * Callback to generate a guess for {{REVISIONID}}
841 * @since 1.28
842 * @param callable|null $x New value (null is no change)
843 * @return callable|null Old value
844 */
845 public function setSpeculativeRevIdCallback( $x ) {
846 return $this->setOptionLegacy( 'speculativeRevIdCallback', $x );
847 }
848
849 /**@}*/
850
851 /**
852 * Timestamp used for {{CURRENTDAY}} etc.
853 * @return string
854 */
855 public function getTimestamp() {
856 if ( !isset( $this->mTimestamp ) ) {
857 $this->mTimestamp = wfTimestampNow();
858 }
859 return $this->mTimestamp;
860 }
861
862 /**
863 * Timestamp used for {{CURRENTDAY}} etc.
864 * @param string|null $x New value (null is no change)
865 * @return string Old value
866 */
867 public function setTimestamp( $x ) {
868 return wfSetVar( $this->mTimestamp, $x );
869 }
870
871 /**
872 * Create "edit section" links?
873 * @deprecated since 1.31, use ParserOutput::getText() options instead.
874 * @return bool
875 */
876 public function getEditSection() {
877 return $this->mEditSection;
878 }
879
880 /**
881 * Create "edit section" links?
882 * @deprecated since 1.31, use ParserOutput::getText() options instead.
883 * @param bool|null $x New value (null is no change)
884 * @return bool Old value
885 */
886 public function setEditSection( $x ) {
887 return wfSetVar( $this->mEditSection, $x );
888 }
889
890 /**
891 * Set the redirect target.
892 *
893 * Note that setting or changing this does not *make* the page a redirect
894 * or change its target, it merely records the information for reference
895 * during the parse.
896 *
897 * @since 1.24
898 * @param Title|null $title
899 */
900 function setRedirectTarget( $title ) {
901 $this->redirectTarget = $title;
902 }
903
904 /**
905 * Get the previously-set redirect target.
906 *
907 * @since 1.24
908 * @return Title|null
909 */
910 function getRedirectTarget() {
911 return $this->redirectTarget;
912 }
913
914 /**
915 * Extra key that should be present in the parser cache key.
916 * @warning Consider registering your additional options with the
917 * ParserOptionsRegister hook instead of using this method.
918 * @param string $key
919 */
920 public function addExtraKey( $key ) {
921 $this->mExtraKey .= '!' . $key;
922 }
923
924 /**
925 * Current user
926 * @return User
927 */
928 public function getUser() {
929 return $this->mUser;
930 }
931
932 /**
933 * @warning For interaction with the parser cache, use
934 * WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
935 * ParserOptions::newCanonical() instead.
936 * @param User $user
937 * @param Language $lang
938 */
939 public function __construct( $user = null, $lang = null ) {
940 if ( $user === null ) {
941 global $wgUser;
942 if ( $wgUser === null ) {
943 $user = new User;
944 } else {
945 $user = $wgUser;
946 }
947 }
948 if ( $lang === null ) {
949 global $wgLang;
950 if ( !StubObject::isRealObject( $wgLang ) ) {
951 $wgLang->_unstub();
952 }
953 $lang = $wgLang;
954 }
955 $this->initialiseFromUser( $user, $lang );
956 }
957
958 /**
959 * Get a ParserOptions object for an anonymous user
960 * @warning For interaction with the parser cache, use
961 * WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
962 * ParserOptions::newCanonical() instead.
963 * @since 1.27
964 * @return ParserOptions
965 */
966 public static function newFromAnon() {
967 global $wgContLang;
968 return new ParserOptions( new User, $wgContLang );
969 }
970
971 /**
972 * Get a ParserOptions object from a given user.
973 * Language will be taken from $wgLang.
974 *
975 * @warning For interaction with the parser cache, use
976 * WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
977 * ParserOptions::newCanonical() instead.
978 * @param User $user
979 * @return ParserOptions
980 */
981 public static function newFromUser( $user ) {
982 return new ParserOptions( $user );
983 }
984
985 /**
986 * Get a ParserOptions object from a given user and language
987 *
988 * @warning For interaction with the parser cache, use
989 * WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
990 * ParserOptions::newCanonical() instead.
991 * @param User $user
992 * @param Language $lang
993 * @return ParserOptions
994 */
995 public static function newFromUserAndLang( User $user, Language $lang ) {
996 return new ParserOptions( $user, $lang );
997 }
998
999 /**
1000 * Get a ParserOptions object from a IContextSource object
1001 *
1002 * @warning For interaction with the parser cache, use
1003 * WikiPage::makeParserOptions(), ContentHandler::makeParserOptions(), or
1004 * ParserOptions::newCanonical() instead.
1005 * @param IContextSource $context
1006 * @return ParserOptions
1007 */
1008 public static function newFromContext( IContextSource $context ) {
1009 return new ParserOptions( $context->getUser(), $context->getLanguage() );
1010 }
1011
1012 /**
1013 * Creates a "canonical" ParserOptions object
1014 *
1015 * For historical reasons, certain options have default values that are
1016 * different from the canonical values used for caching.
1017 *
1018 * @since 1.30
1019 * @param User|null $user
1020 * @param Language|StubObject|null $lang
1021 * @return ParserOptions
1022 */
1023 public static function newCanonical( User $user = null, $lang = null ) {
1024 $ret = new ParserOptions( $user, $lang );
1025 foreach ( self::getCanonicalOverrides() as $k => $v ) {
1026 $ret->setOption( $k, $v );
1027 }
1028 return $ret;
1029 }
1030
1031 /**
1032 * Get default option values
1033 * @warning If you change the default for an existing option (unless it's
1034 * being overridden by self::getCanonicalOverrides()), all existing parser
1035 * cache entries will be invalid. To avoid bugs, you'll need to handle
1036 * that somehow (e.g. with the RejectParserCacheValue hook) because
1037 * MediaWiki won't do it for you.
1038 * @return array
1039 */
1040 private static function getDefaults() {
1041 global $wgInterwikiMagic, $wgAllowExternalImages,
1042 $wgAllowExternalImagesFrom, $wgEnableImageWhitelist, $wgAllowSpecialInclusion,
1043 $wgMaxArticleSize, $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth,
1044 $wgCleanSignatures, $wgExternalLinkTarget, $wgExpensiveParserFunctionLimit,
1045 $wgMaxGeneratedPPNodeCount, $wgDisableLangConversion, $wgDisableTitleConversion,
1046 $wgEnableMagicLinks, $wgContLang;
1047
1048 if ( self::$defaults === null ) {
1049 // *UPDATE* ParserOptions::matches() if any of this changes as needed
1050 self::$defaults = [
1051 'dateformat' => null,
1052 'tidy' => false,
1053 'interfaceMessage' => false,
1054 'targetLanguage' => null,
1055 'removeComments' => true,
1056 'enableLimitReport' => false,
1057 'preSaveTransform' => true,
1058 'isPreview' => false,
1059 'isSectionPreview' => false,
1060 'printable' => false,
1061 'allowUnsafeRawHtml' => true,
1062 'wrapclass' => 'mw-parser-output',
1063 'currentRevisionCallback' => [ Parser::class, 'statelessFetchRevision' ],
1064 'templateCallback' => [ Parser::class, 'statelessFetchTemplate' ],
1065 'speculativeRevIdCallback' => null,
1066 ];
1067
1068 Hooks::run( 'ParserOptionsRegister', [
1069 &self::$defaults,
1070 &self::$inCacheKey,
1071 &self::$lazyOptions,
1072 ] );
1073
1074 ksort( self::$inCacheKey );
1075 }
1076
1077 // Unit tests depend on being able to modify the globals at will
1078 return self::$defaults + [
1079 'interwikiMagic' => $wgInterwikiMagic,
1080 'allowExternalImages' => $wgAllowExternalImages,
1081 'allowExternalImagesFrom' => $wgAllowExternalImagesFrom,
1082 'enableImageWhitelist' => $wgEnableImageWhitelist,
1083 'allowSpecialInclusion' => $wgAllowSpecialInclusion,
1084 'maxIncludeSize' => $wgMaxArticleSize * 1024,
1085 'maxPPNodeCount' => $wgMaxPPNodeCount,
1086 'maxGeneratedPPNodeCount' => $wgMaxGeneratedPPNodeCount,
1087 'maxPPExpandDepth' => $wgMaxPPExpandDepth,
1088 'maxTemplateDepth' => $wgMaxTemplateDepth,
1089 'expensiveParserFunctionLimit' => $wgExpensiveParserFunctionLimit,
1090 'externalLinkTarget' => $wgExternalLinkTarget,
1091 'cleanSignatures' => $wgCleanSignatures,
1092 'disableContentConversion' => $wgDisableLangConversion,
1093 'disableTitleConversion' => $wgDisableLangConversion || $wgDisableTitleConversion,
1094 'magicISBNLinks' => $wgEnableMagicLinks['ISBN'],
1095 'magicPMIDLinks' => $wgEnableMagicLinks['PMID'],
1096 'magicRFCLinks' => $wgEnableMagicLinks['RFC'],
1097 'numberheadings' => User::getDefaultOption( 'numberheadings' ),
1098 'thumbsize' => User::getDefaultOption( 'thumbsize' ),
1099 'stubthreshold' => 0,
1100 'userlang' => $wgContLang,
1101 ];
1102 }
1103
1104 /**
1105 * Get "canonical" non-default option values
1106 * @see self::newCanonical
1107 * @warning If you change the override for an existing option, all existing
1108 * parser cache entries will be invalid. To avoid bugs, you'll need to
1109 * handle that somehow (e.g. with the RejectParserCacheValue hook) because
1110 * MediaWiki won't do it for you.
1111 * @return array
1112 */
1113 private static function getCanonicalOverrides() {
1114 global $wgEnableParserLimitReporting;
1115
1116 return [
1117 'tidy' => true,
1118 'enableLimitReport' => $wgEnableParserLimitReporting,
1119 ];
1120 }
1121
1122 /**
1123 * Get user options
1124 *
1125 * @param User $user
1126 * @param Language $lang
1127 */
1128 private function initialiseFromUser( $user, $lang ) {
1129 $this->options = self::getDefaults();
1130
1131 $this->mUser = $user;
1132 $this->options['numberheadings'] = $user->getOption( 'numberheadings' );
1133 $this->options['thumbsize'] = $user->getOption( 'thumbsize' );
1134 $this->options['stubthreshold'] = $user->getStubThreshold();
1135 $this->options['userlang'] = $lang;
1136 }
1137
1138 /**
1139 * Check if these options match that of another options set
1140 *
1141 * This ignores report limit settings that only affect HTML comments
1142 *
1143 * @param ParserOptions $other
1144 * @return bool
1145 * @since 1.25
1146 */
1147 public function matches( ParserOptions $other ) {
1148 // Populate lazy options
1149 foreach ( self::$lazyOptions as $name => $callback ) {
1150 if ( $this->options[$name] === null ) {
1151 $this->options[$name] = call_user_func( $callback, $this, $name );
1152 }
1153 if ( $other->options[$name] === null ) {
1154 $other->options[$name] = call_user_func( $callback, $other, $name );
1155 }
1156 }
1157
1158 // Compare most options
1159 $options = array_keys( $this->options );
1160 $options = array_diff( $options, [
1161 'enableLimitReport', // only affects HTML comments
1162 ] );
1163 foreach ( $options as $option ) {
1164 $o1 = $this->optionToString( $this->options[$option] );
1165 $o2 = $this->optionToString( $other->options[$option] );
1166 if ( $o1 !== $o2 ) {
1167 return false;
1168 }
1169 }
1170
1171 // Compare most other fields
1172 $fields = array_keys( get_class_vars( __CLASS__ ) );
1173 $fields = array_diff( $fields, [
1174 'defaults', // static
1175 'lazyOptions', // static
1176 'inCacheKey', // static
1177 'options', // Already checked above
1178 'onAccessCallback', // only used for ParserOutput option tracking
1179 ] );
1180 foreach ( $fields as $field ) {
1181 if ( !is_object( $this->$field ) && $this->$field !== $other->$field ) {
1182 return false;
1183 }
1184 }
1185
1186 return true;
1187 }
1188
1189 /**
1190 * Registers a callback for tracking which ParserOptions which are used.
1191 * This is a private API with the parser.
1192 * @param callable $callback
1193 */
1194 public function registerWatcher( $callback ) {
1195 $this->onAccessCallback = $callback;
1196 }
1197
1198 /**
1199 * Called when an option is accessed.
1200 * Calls the watcher that was set using registerWatcher().
1201 * Typically, the watcher callback is ParserOutput::registerOption().
1202 * The information registered that way will be used by ParserCache::save().
1203 *
1204 * @param string $optionName Name of the option
1205 */
1206 public function optionUsed( $optionName ) {
1207 if ( $this->onAccessCallback ) {
1208 call_user_func( $this->onAccessCallback, $optionName );
1209 }
1210 }
1211
1212 /**
1213 * Returns the full array of options that would have been used by
1214 * in 1.16.
1215 * Used to get the old parser cache entries when available.
1216 * @deprecated since 1.30. You probably want self::allCacheVaryingOptions() instead.
1217 * @return array
1218 */
1219 public static function legacyOptions() {
1220 wfDeprecated( __METHOD__, '1.30' );
1221 return [
1222 'stubthreshold',
1223 'numberheadings',
1224 'userlang',
1225 'thumbsize',
1226 'editsection',
1227 'printable'
1228 ];
1229 }
1230
1231 /**
1232 * Return all option keys that vary the options hash
1233 * @since 1.30
1234 * @return string[]
1235 */
1236 public static function allCacheVaryingOptions() {
1237 // Trigger a call to the 'ParserOptionsRegister' hook if it hasn't
1238 // already been called.
1239 if ( self::$defaults === null ) {
1240 self::getDefaults();
1241 }
1242 return array_keys( array_filter( self::$inCacheKey ) );
1243 }
1244
1245 /**
1246 * Convert an option to a string value
1247 * @param mixed $value
1248 * @return string
1249 */
1250 private function optionToString( $value ) {
1251 if ( $value === true ) {
1252 return '1';
1253 } elseif ( $value === false ) {
1254 return '0';
1255 } elseif ( $value === null ) {
1256 return '';
1257 } elseif ( $value instanceof Language ) {
1258 return $value->getCode();
1259 } elseif ( is_array( $value ) ) {
1260 return '[' . join( ',', array_map( [ $this, 'optionToString' ], $value ) ) . ']';
1261 } else {
1262 return (string)$value;
1263 }
1264 }
1265
1266 /**
1267 * Generate a hash string with the values set on these ParserOptions
1268 * for the keys given in the array.
1269 * This will be used as part of the hash key for the parser cache,
1270 * so users sharing the options with vary for the same page share
1271 * the same cached data safely.
1272 *
1273 * @since 1.17
1274 * @param array $forOptions
1275 * @param Title $title Used to get the content language of the page (since r97636)
1276 * @return string Page rendering hash
1277 */
1278 public function optionsHash( $forOptions, $title = null ) {
1279 global $wgRenderHashAppend;
1280
1281 $options = $this->options;
1282 $defaults = self::getCanonicalOverrides() + self::getDefaults();
1283 $inCacheKey = self::$inCacheKey;
1284
1285 // Historical hack: 'editsection' hasn't been a true parser option since
1286 // Feb 2015 (instead the parser outputs a constant placeholder and post-parse
1287 // processing handles the option). But Wikibase forces it in $forOptions
1288 // and expects the cache key to still vary on it for T85252.
1289 // @deprecated since 1.30, Wikibase should use addExtraKey() or something instead.
1290 if ( in_array( 'editsection', $forOptions, true ) ) {
1291 $options['editsection'] = $this->mEditSection;
1292 $defaults['editsection'] = true;
1293 $inCacheKey['editsection'] = true;
1294 ksort( $inCacheKey );
1295 }
1296
1297 // We only include used options with non-canonical values in the key
1298 // so adding a new option doesn't invalidate the entire parser cache.
1299 // The drawback to this is that changing the default value of an option
1300 // requires manual invalidation of existing cache entries, as mentioned
1301 // in the docs on the relevant methods and hooks.
1302 $values = [];
1303 foreach ( $inCacheKey as $option => $include ) {
1304 if ( $include && in_array( $option, $forOptions, true ) ) {
1305 $v = $this->optionToString( $options[$option] );
1306 $d = $this->optionToString( $defaults[$option] );
1307 if ( $v !== $d ) {
1308 $values[] = "$option=$v";
1309 }
1310 }
1311 }
1312
1313 $confstr = $values ? join( '!', $values ) : 'canonical';
1314
1315 // add in language specific options, if any
1316 // @todo FIXME: This is just a way of retrieving the url/user preferred variant
1317 if ( !is_null( $title ) ) {
1318 $confstr .= $title->getPageLanguage()->getExtraHashOptions();
1319 } else {
1320 global $wgContLang;
1321 $confstr .= $wgContLang->getExtraHashOptions();
1322 }
1323
1324 $confstr .= $wgRenderHashAppend;
1325
1326 if ( $this->mExtraKey != '' ) {
1327 $confstr .= $this->mExtraKey;
1328 }
1329
1330 // Give a chance for extensions to modify the hash, if they have
1331 // extra options or other effects on the parser cache.
1332 Hooks::run( 'PageRenderingHash', [ &$confstr, $this->getUser(), &$forOptions ] );
1333
1334 // Make it a valid memcached key fragment
1335 $confstr = str_replace( ' ', '_', $confstr );
1336
1337 return $confstr;
1338 }
1339
1340 /**
1341 * Test whether these options are safe to cache
1342 * @since 1.30
1343 * @return bool
1344 */
1345 public function isSafeToCache() {
1346 $defaults = self::getCanonicalOverrides() + self::getDefaults();
1347 foreach ( $this->options as $option => $value ) {
1348 if ( empty( self::$inCacheKey[$option] ) ) {
1349 $v = $this->optionToString( $value );
1350 $d = $this->optionToString( $defaults[$option] );
1351 if ( $v !== $d ) {
1352 return false;
1353 }
1354 }
1355 }
1356 return true;
1357 }
1358
1359 /**
1360 * Sets a hook to force that a page exists, and sets a current revision callback to return
1361 * a revision with custom content when the current revision of the page is requested.
1362 *
1363 * @since 1.25
1364 * @param Title $title
1365 * @param Content $content
1366 * @param User $user The user that the fake revision is attributed to
1367 * @return ScopedCallback to unset the hook
1368 */
1369 public function setupFakeRevision( $title, $content, $user ) {
1370 $oldCallback = $this->setCurrentRevisionCallback(
1371 function (
1372 $titleToCheck, $parser = false ) use ( $title, $content, $user, &$oldCallback
1373 ) {
1374 if ( $titleToCheck->equals( $title ) ) {
1375 return new Revision( [
1376 'page' => $title->getArticleID(),
1377 'user_text' => $user->getName(),
1378 'user' => $user->getId(),
1379 'parent_id' => $title->getLatestRevID(),
1380 'title' => $title,
1381 'content' => $content
1382 ] );
1383 } else {
1384 return call_user_func( $oldCallback, $titleToCheck, $parser );
1385 }
1386 }
1387 );
1388
1389 global $wgHooks;
1390 $wgHooks['TitleExists'][] =
1391 function ( $titleToCheck, &$exists ) use ( $title ) {
1392 if ( $titleToCheck->equals( $title ) ) {
1393 $exists = true;
1394 }
1395 };
1396 end( $wgHooks['TitleExists'] );
1397 $key = key( $wgHooks['TitleExists'] );
1398 LinkCache::singleton()->clearBadLink( $title->getPrefixedDBkey() );
1399 return new ScopedCallback( function () use ( $title, $key ) {
1400 global $wgHooks;
1401 unset( $wgHooks['TitleExists'][$key] );
1402 LinkCache::singleton()->clearLink( $title );
1403 } );
1404 }
1405 }
1406
1407 /**
1408 * For really cool vim folding this needs to be at the end:
1409 * vim: foldmarker=@{,@} foldmethod=marker
1410 */