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