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