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