Merge "ParserOptions: Include wrapping class in options hash"
[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 * All member variables are supposed to be private in theory, although in
29 * practice this is not the case.
30 *
31 * @ingroup Parser
32 */
33 class ParserOptions {
34
35 /**
36 * Interlanguage links are removed and returned in an array
37 */
38 private $mInterwikiMagic;
39
40 /**
41 * Allow external images inline?
42 */
43 private $mAllowExternalImages;
44
45 /**
46 * If not, any exception?
47 */
48 private $mAllowExternalImagesFrom;
49
50 /**
51 * If not or it doesn't match, should we check an on-wiki whitelist?
52 */
53 private $mEnableImageWhitelist;
54
55 /**
56 * Date format index
57 */
58 private $mDateFormat = null;
59
60 /**
61 * Create "edit section" links?
62 */
63 private $mEditSection = true;
64
65 /**
66 * Allow inclusion of special pages?
67 */
68 private $mAllowSpecialInclusion;
69
70 /**
71 * Use tidy to cleanup output HTML?
72 */
73 private $mTidy = false;
74
75 /**
76 * Which lang to call for PLURAL and GRAMMAR
77 */
78 private $mInterfaceMessage = false;
79
80 /**
81 * Overrides $mInterfaceMessage with arbitrary language
82 */
83 private $mTargetLanguage = null;
84
85 /**
86 * Maximum size of template expansions, in bytes
87 */
88 private $mMaxIncludeSize;
89
90 /**
91 * Maximum number of nodes touched by PPFrame::expand()
92 */
93 private $mMaxPPNodeCount;
94
95 /**
96 * Maximum number of nodes generated by Preprocessor::preprocessToObj()
97 */
98 private $mMaxGeneratedPPNodeCount;
99
100 /**
101 * Maximum recursion depth in PPFrame::expand()
102 */
103 private $mMaxPPExpandDepth;
104
105 /**
106 * Maximum recursion depth for templates within templates
107 */
108 private $mMaxTemplateDepth;
109
110 /**
111 * Maximum number of calls per parse to expensive parser functions
112 */
113 private $mExpensiveParserFunctionLimit;
114
115 /**
116 * Remove HTML comments. ONLY APPLIES TO PREPROCESS OPERATIONS
117 */
118 private $mRemoveComments = true;
119
120 /**
121 * @var callable Callback for current revision fetching; first argument to call_user_func().
122 */
123 private $mCurrentRevisionCallback =
124 [ 'Parser', 'statelessFetchRevision' ];
125
126 /**
127 * @var callable Callback for template fetching; first argument to call_user_func().
128 */
129 private $mTemplateCallback =
130 [ 'Parser', 'statelessFetchTemplate' ];
131
132 /**
133 * @var callable|null Callback to generate a guess for {{REVISIONID}}
134 */
135 private $mSpeculativeRevIdCallback;
136
137 /**
138 * Enable limit report in an HTML comment on output
139 */
140 private $mEnableLimitReport = false;
141
142 /**
143 * Timestamp used for {{CURRENTDAY}} etc.
144 */
145 private $mTimestamp;
146
147 /**
148 * Target attribute for external links
149 */
150 private $mExternalLinkTarget;
151
152 /**
153 * Clean up signature texts?
154 * @see Parser::cleanSig
155 */
156 private $mCleanSignatures;
157
158 /**
159 * Transform wiki markup when saving the page?
160 */
161 private $mPreSaveTransform = true;
162
163 /**
164 * Whether content conversion should be disabled
165 */
166 private $mDisableContentConversion;
167
168 /**
169 * Whether title conversion should be disabled
170 */
171 private $mDisableTitleConversion;
172
173 /**
174 * Automatically number headings?
175 */
176 private $mNumberHeadings;
177
178 /**
179 * Thumb size preferred by the user.
180 */
181 private $mThumbSize;
182
183 /**
184 * Maximum article size of an article to be marked as "stub"
185 */
186 private $mStubThreshold;
187
188 /**
189 * Language object of the User language.
190 */
191 private $mUserLang;
192
193 /**
194 * @var User
195 * Stored user object
196 */
197 private $mUser;
198
199 /**
200 * Parsing the page for a "preview" operation?
201 */
202 private $mIsPreview = false;
203
204 /**
205 * Parsing the page for a "preview" operation on a single section?
206 */
207 private $mIsSectionPreview = false;
208
209 /**
210 * Parsing the printable version of the page?
211 */
212 private $mIsPrintable = false;
213
214 /**
215 * Extra key that should be present in the caching key.
216 */
217 private $mExtraKey = '';
218
219 /**
220 * Are magic ISBN links enabled?
221 */
222 private $mMagicISBNLinks = true;
223
224 /**
225 * Are magic PMID links enabled?
226 */
227 private $mMagicPMIDLinks = true;
228
229 /**
230 * Are magic RFC links enabled?
231 */
232 private $mMagicRFCLinks = true;
233
234 /**
235 * Function to be called when an option is accessed.
236 */
237 private $onAccessCallback = null;
238
239 /**
240 * If the page being parsed is a redirect, this should hold the redirect
241 * target.
242 * @var Title|null
243 */
244 private $redirectTarget = null;
245
246 /**
247 * If the wiki is configured to allow raw html ($wgRawHtml = true)
248 * is it allowed in the specific case of parsing this page.
249 *
250 * This is meant to disable unsafe parser tags in cases where
251 * a malicious user may control the input to the parser.
252 *
253 * @note This is expected to be true for normal pages even if the
254 * wiki has $wgRawHtml disabled in general. The setting only
255 * signifies that raw html would be unsafe in the current context
256 * provided that raw html is allowed at all.
257 * @var boolean
258 */
259 private $allowUnsafeRawHtml = true;
260
261 /**
262 * CSS class to use to wrap output from Parser::parse().
263 * @var string|false
264 */
265 private $wrapOutputClass = 'mw-parser-output';
266
267 public function getInterwikiMagic() {
268 return $this->mInterwikiMagic;
269 }
270
271 public function getAllowExternalImages() {
272 return $this->mAllowExternalImages;
273 }
274
275 public function getAllowExternalImagesFrom() {
276 return $this->mAllowExternalImagesFrom;
277 }
278
279 public function getEnableImageWhitelist() {
280 return $this->mEnableImageWhitelist;
281 }
282
283 public function getEditSection() {
284 return $this->mEditSection;
285 }
286
287 public function getNumberHeadings() {
288 $this->optionUsed( 'numberheadings' );
289
290 return $this->mNumberHeadings;
291 }
292
293 public function getAllowSpecialInclusion() {
294 return $this->mAllowSpecialInclusion;
295 }
296
297 public function getTidy() {
298 return $this->mTidy;
299 }
300
301 public function getInterfaceMessage() {
302 return $this->mInterfaceMessage;
303 }
304
305 public function getTargetLanguage() {
306 return $this->mTargetLanguage;
307 }
308
309 public function getMaxIncludeSize() {
310 return $this->mMaxIncludeSize;
311 }
312
313 public function getMaxPPNodeCount() {
314 return $this->mMaxPPNodeCount;
315 }
316
317 public function getMaxGeneratedPPNodeCount() {
318 return $this->mMaxGeneratedPPNodeCount;
319 }
320
321 public function getMaxPPExpandDepth() {
322 return $this->mMaxPPExpandDepth;
323 }
324
325 public function getMaxTemplateDepth() {
326 return $this->mMaxTemplateDepth;
327 }
328
329 /* @since 1.20 */
330 public function getExpensiveParserFunctionLimit() {
331 return $this->mExpensiveParserFunctionLimit;
332 }
333
334 public function getRemoveComments() {
335 return $this->mRemoveComments;
336 }
337
338 /* @since 1.24 */
339 public function getCurrentRevisionCallback() {
340 return $this->mCurrentRevisionCallback;
341 }
342
343 public function getTemplateCallback() {
344 return $this->mTemplateCallback;
345 }
346
347 /** @since 1.28 */
348 public function getSpeculativeRevIdCallback() {
349 return $this->mSpeculativeRevIdCallback;
350 }
351
352 public function getEnableLimitReport() {
353 return $this->mEnableLimitReport;
354 }
355
356 public function getCleanSignatures() {
357 return $this->mCleanSignatures;
358 }
359
360 public function getExternalLinkTarget() {
361 return $this->mExternalLinkTarget;
362 }
363
364 public function getDisableContentConversion() {
365 return $this->mDisableContentConversion;
366 }
367
368 public function getDisableTitleConversion() {
369 return $this->mDisableTitleConversion;
370 }
371
372 public function getThumbSize() {
373 $this->optionUsed( 'thumbsize' );
374
375 return $this->mThumbSize;
376 }
377
378 public function getStubThreshold() {
379 $this->optionUsed( 'stubthreshold' );
380
381 return $this->mStubThreshold;
382 }
383
384 public function getIsPreview() {
385 return $this->mIsPreview;
386 }
387
388 public function getIsSectionPreview() {
389 return $this->mIsSectionPreview;
390 }
391
392 public function getIsPrintable() {
393 $this->optionUsed( 'printable' );
394
395 return $this->mIsPrintable;
396 }
397
398 public function getUser() {
399 return $this->mUser;
400 }
401
402 public function getPreSaveTransform() {
403 return $this->mPreSaveTransform;
404 }
405
406 public function getDateFormat() {
407 $this->optionUsed( 'dateformat' );
408 if ( !isset( $this->mDateFormat ) ) {
409 $this->mDateFormat = $this->mUser->getDatePreference();
410 }
411 return $this->mDateFormat;
412 }
413
414 public function getTimestamp() {
415 if ( !isset( $this->mTimestamp ) ) {
416 $this->mTimestamp = wfTimestampNow();
417 }
418 return $this->mTimestamp;
419 }
420
421 /**
422 * Get the user language used by the parser for this page and split the parser cache.
423 *
424 * @warning: Calling this causes the parser cache to be fragmented by user language!
425 * To avoid cache fragmentation, output should not depend on the user language.
426 * Use Parser::getFunctionLang() or Parser::getTargetLanguage() instead!
427 *
428 * @note This function will trigger a cache fragmentation by recording the
429 * 'userlang' option, see optionUsed(). This is done to avoid cache pollution
430 * when the page is rendered based on the language of the user.
431 *
432 * @note When saving, this will return the default language instead of the user's.
433 * {{int: }} uses this which used to produce inconsistent link tables (T16404).
434 *
435 * @return Language
436 * @since 1.19
437 */
438 public function getUserLangObj() {
439 $this->optionUsed( 'userlang' );
440 return $this->mUserLang;
441 }
442
443 /**
444 * Same as getUserLangObj() but returns a string instead.
445 *
446 * @warning: Calling this causes the parser cache to be fragmented by user language!
447 * To avoid cache fragmentation, output should not depend on the user language.
448 * Use Parser::getFunctionLang() or Parser::getTargetLanguage() instead!
449 *
450 * @see getUserLangObj()
451 *
452 * @return string Language code
453 * @since 1.17
454 */
455 public function getUserLang() {
456 return $this->getUserLangObj()->getCode();
457 }
458
459 /**
460 * @since 1.28
461 * @return bool
462 */
463 public function getMagicISBNLinks() {
464 return $this->mMagicISBNLinks;
465 }
466
467 /**
468 * @since 1.28
469 * @return bool
470 */
471 public function getMagicPMIDLinks() {
472 return $this->mMagicPMIDLinks;
473 }
474 /**
475 * @since 1.28
476 * @return bool
477 */
478 public function getMagicRFCLinks() {
479 return $this->mMagicRFCLinks;
480 }
481
482 /**
483 * @since 1.29
484 * @return bool
485 */
486 public function getAllowUnsafeRawHtml() {
487 return $this->allowUnsafeRawHtml;
488 }
489
490 /**
491 * Class to use to wrap output from Parser::parse()
492 * @since 1.30
493 * @return string|bool
494 */
495 public function getWrapOutputClass() {
496 $this->optionUsed( 'wrapclass' );
497 return $this->wrapOutputClass;
498 }
499
500 public function setInterwikiMagic( $x ) {
501 return wfSetVar( $this->mInterwikiMagic, $x );
502 }
503
504 public function setAllowExternalImages( $x ) {
505 return wfSetVar( $this->mAllowExternalImages, $x );
506 }
507
508 public function setAllowExternalImagesFrom( $x ) {
509 return wfSetVar( $this->mAllowExternalImagesFrom, $x );
510 }
511
512 public function setEnableImageWhitelist( $x ) {
513 return wfSetVar( $this->mEnableImageWhitelist, $x );
514 }
515
516 public function setDateFormat( $x ) {
517 return wfSetVar( $this->mDateFormat, $x );
518 }
519
520 public function setEditSection( $x ) {
521 return wfSetVar( $this->mEditSection, $x );
522 }
523
524 public function setNumberHeadings( $x ) {
525 return wfSetVar( $this->mNumberHeadings, $x );
526 }
527
528 public function setAllowSpecialInclusion( $x ) {
529 return wfSetVar( $this->mAllowSpecialInclusion, $x );
530 }
531
532 public function setTidy( $x ) {
533 return wfSetVar( $this->mTidy, $x );
534 }
535
536 public function setInterfaceMessage( $x ) {
537 return wfSetVar( $this->mInterfaceMessage, $x );
538 }
539
540 public function setTargetLanguage( $x ) {
541 return wfSetVar( $this->mTargetLanguage, $x, true );
542 }
543
544 public function setMaxIncludeSize( $x ) {
545 return wfSetVar( $this->mMaxIncludeSize, $x );
546 }
547
548 public function setMaxPPNodeCount( $x ) {
549 return wfSetVar( $this->mMaxPPNodeCount, $x );
550 }
551
552 public function setMaxGeneratedPPNodeCount( $x ) {
553 return wfSetVar( $this->mMaxGeneratedPPNodeCount, $x );
554 }
555
556 public function setMaxTemplateDepth( $x ) {
557 return wfSetVar( $this->mMaxTemplateDepth, $x );
558 }
559
560 /* @since 1.20 */
561 public function setExpensiveParserFunctionLimit( $x ) {
562 return wfSetVar( $this->mExpensiveParserFunctionLimit, $x );
563 }
564
565 public function setRemoveComments( $x ) {
566 return wfSetVar( $this->mRemoveComments, $x );
567 }
568
569 /* @since 1.24 */
570 public function setCurrentRevisionCallback( $x ) {
571 return wfSetVar( $this->mCurrentRevisionCallback, $x );
572 }
573
574 /** @since 1.28 */
575 public function setSpeculativeRevIdCallback( $x ) {
576 return wfSetVar( $this->mSpeculativeRevIdCallback, $x );
577 }
578
579 public function setTemplateCallback( $x ) {
580 return wfSetVar( $this->mTemplateCallback, $x );
581 }
582
583 public function enableLimitReport( $x = true ) {
584 return wfSetVar( $this->mEnableLimitReport, $x );
585 }
586
587 public function setTimestamp( $x ) {
588 return wfSetVar( $this->mTimestamp, $x );
589 }
590
591 public function setCleanSignatures( $x ) {
592 return wfSetVar( $this->mCleanSignatures, $x );
593 }
594
595 public function setExternalLinkTarget( $x ) {
596 return wfSetVar( $this->mExternalLinkTarget, $x );
597 }
598
599 public function disableContentConversion( $x = true ) {
600 return wfSetVar( $this->mDisableContentConversion, $x );
601 }
602
603 public function disableTitleConversion( $x = true ) {
604 return wfSetVar( $this->mDisableTitleConversion, $x );
605 }
606
607 public function setUserLang( $x ) {
608 if ( is_string( $x ) ) {
609 $x = Language::factory( $x );
610 }
611
612 return wfSetVar( $this->mUserLang, $x );
613 }
614
615 public function setThumbSize( $x ) {
616 return wfSetVar( $this->mThumbSize, $x );
617 }
618
619 public function setStubThreshold( $x ) {
620 return wfSetVar( $this->mStubThreshold, $x );
621 }
622
623 public function setPreSaveTransform( $x ) {
624 return wfSetVar( $this->mPreSaveTransform, $x );
625 }
626
627 public function setIsPreview( $x ) {
628 return wfSetVar( $this->mIsPreview, $x );
629 }
630
631 public function setIsSectionPreview( $x ) {
632 return wfSetVar( $this->mIsSectionPreview, $x );
633 }
634
635 public function setIsPrintable( $x ) {
636 return wfSetVar( $this->mIsPrintable, $x );
637 }
638
639 /**
640 * @param bool|null Value to set or null to get current value
641 * @return bool Current value for allowUnsafeRawHtml
642 * @since 1.29
643 */
644 public function setAllowUnsafeRawHtml( $x ) {
645 return wfSetVar( $this->allowUnsafeRawHtml, $x );
646 }
647
648 /**
649 * CSS class to use to wrap output from Parser::parse()
650 * @since 1.30
651 * @param string|bool $className Set false to disable wrapping.
652 * @return string|bool Current value
653 */
654 public function setWrapOutputClass( $className ) {
655 if ( $className === true ) { // DWIM, they probably want the default class name
656 $className = 'mw-parser-output';
657 }
658 return wfSetVar( $this->wrapOutputClass, $className );
659 }
660
661 /**
662 * Set the redirect target.
663 *
664 * Note that setting or changing this does not *make* the page a redirect
665 * or change its target, it merely records the information for reference
666 * during the parse.
667 *
668 * @since 1.24
669 * @param Title|null $title
670 */
671 function setRedirectTarget( $title ) {
672 $this->redirectTarget = $title;
673 }
674
675 /**
676 * Get the previously-set redirect target.
677 *
678 * @since 1.24
679 * @return Title|null
680 */
681 function getRedirectTarget() {
682 return $this->redirectTarget;
683 }
684
685 /**
686 * Extra key that should be present in the parser cache key.
687 * @param string $key
688 */
689 public function addExtraKey( $key ) {
690 $this->mExtraKey .= '!' . $key;
691 }
692
693 /**
694 * Constructor
695 * @param User $user
696 * @param Language $lang
697 */
698 public function __construct( $user = null, $lang = null ) {
699 if ( $user === null ) {
700 global $wgUser;
701 if ( $wgUser === null ) {
702 $user = new User;
703 } else {
704 $user = $wgUser;
705 }
706 }
707 if ( $lang === null ) {
708 global $wgLang;
709 if ( !StubObject::isRealObject( $wgLang ) ) {
710 $wgLang->_unstub();
711 }
712 $lang = $wgLang;
713 }
714 $this->initialiseFromUser( $user, $lang );
715 }
716
717 /**
718 * Get a ParserOptions object for an anonymous user
719 * @since 1.27
720 * @return ParserOptions
721 */
722 public static function newFromAnon() {
723 global $wgContLang;
724 return new ParserOptions( new User, $wgContLang );
725 }
726
727 /**
728 * Get a ParserOptions object from a given user.
729 * Language will be taken from $wgLang.
730 *
731 * @param User $user
732 * @return ParserOptions
733 */
734 public static function newFromUser( $user ) {
735 return new ParserOptions( $user );
736 }
737
738 /**
739 * Get a ParserOptions object from a given user and language
740 *
741 * @param User $user
742 * @param Language $lang
743 * @return ParserOptions
744 */
745 public static function newFromUserAndLang( User $user, Language $lang ) {
746 return new ParserOptions( $user, $lang );
747 }
748
749 /**
750 * Get a ParserOptions object from a IContextSource object
751 *
752 * @param IContextSource $context
753 * @return ParserOptions
754 */
755 public static function newFromContext( IContextSource $context ) {
756 return new ParserOptions( $context->getUser(), $context->getLanguage() );
757 }
758
759 /**
760 * Get user options
761 *
762 * @param User $user
763 * @param Language $lang
764 */
765 private function initialiseFromUser( $user, $lang ) {
766 global $wgInterwikiMagic, $wgAllowExternalImages,
767 $wgAllowExternalImagesFrom, $wgEnableImageWhitelist, $wgAllowSpecialInclusion,
768 $wgMaxArticleSize, $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth,
769 $wgCleanSignatures, $wgExternalLinkTarget, $wgExpensiveParserFunctionLimit,
770 $wgMaxGeneratedPPNodeCount, $wgDisableLangConversion, $wgDisableTitleConversion,
771 $wgEnableMagicLinks;
772
773 // *UPDATE* ParserOptions::matches() if any of this changes as needed
774 $this->mInterwikiMagic = $wgInterwikiMagic;
775 $this->mAllowExternalImages = $wgAllowExternalImages;
776 $this->mAllowExternalImagesFrom = $wgAllowExternalImagesFrom;
777 $this->mEnableImageWhitelist = $wgEnableImageWhitelist;
778 $this->mAllowSpecialInclusion = $wgAllowSpecialInclusion;
779 $this->mMaxIncludeSize = $wgMaxArticleSize * 1024;
780 $this->mMaxPPNodeCount = $wgMaxPPNodeCount;
781 $this->mMaxGeneratedPPNodeCount = $wgMaxGeneratedPPNodeCount;
782 $this->mMaxPPExpandDepth = $wgMaxPPExpandDepth;
783 $this->mMaxTemplateDepth = $wgMaxTemplateDepth;
784 $this->mExpensiveParserFunctionLimit = $wgExpensiveParserFunctionLimit;
785 $this->mCleanSignatures = $wgCleanSignatures;
786 $this->mExternalLinkTarget = $wgExternalLinkTarget;
787 $this->mDisableContentConversion = $wgDisableLangConversion;
788 $this->mDisableTitleConversion = $wgDisableLangConversion || $wgDisableTitleConversion;
789 $this->mMagicISBNLinks = $wgEnableMagicLinks['ISBN'];
790 $this->mMagicPMIDLinks = $wgEnableMagicLinks['PMID'];
791 $this->mMagicRFCLinks = $wgEnableMagicLinks['RFC'];
792
793 $this->mUser = $user;
794 $this->mNumberHeadings = $user->getOption( 'numberheadings' );
795 $this->mThumbSize = $user->getOption( 'thumbsize' );
796 $this->mStubThreshold = $user->getStubThreshold();
797 $this->mUserLang = $lang;
798 }
799
800 /**
801 * Check if these options match that of another options set
802 *
803 * This ignores report limit settings that only affect HTML comments
804 *
805 * @param ParserOptions $other
806 * @return bool
807 * @since 1.25
808 */
809 public function matches( ParserOptions $other ) {
810 $fields = array_keys( get_class_vars( __CLASS__ ) );
811 $fields = array_diff( $fields, [
812 'mEnableLimitReport', // only effects HTML comments
813 'onAccessCallback', // only used for ParserOutput option tracking
814 ] );
815 foreach ( $fields as $field ) {
816 if ( !is_object( $this->$field ) && $this->$field !== $other->$field ) {
817 return false;
818 }
819 }
820 // Check the object and lazy-loaded options
821 return (
822 $this->mUserLang->equals( $other->mUserLang ) &&
823 $this->getDateFormat() === $other->getDateFormat()
824 );
825 }
826
827 /**
828 * Registers a callback for tracking which ParserOptions which are used.
829 * This is a private API with the parser.
830 * @param callable $callback
831 */
832 public function registerWatcher( $callback ) {
833 $this->onAccessCallback = $callback;
834 }
835
836 /**
837 * Called when an option is accessed.
838 * Calls the watcher that was set using registerWatcher().
839 * Typically, the watcher callback is ParserOutput::registerOption().
840 * The information registered that way will be used by ParserCache::save().
841 *
842 * @param string $optionName Name of the option
843 */
844 public function optionUsed( $optionName ) {
845 if ( $this->onAccessCallback ) {
846 call_user_func( $this->onAccessCallback, $optionName );
847 }
848 }
849
850 /**
851 * Returns the full array of options that would have been used by
852 * in 1.16.
853 * Used to get the old parser cache entries when available.
854 * @return array
855 */
856 public static function legacyOptions() {
857 return [
858 'stubthreshold',
859 'numberheadings',
860 'userlang',
861 'thumbsize',
862 'editsection',
863 'printable'
864 ];
865 }
866
867 /**
868 * Generate a hash string with the values set on these ParserOptions
869 * for the keys given in the array.
870 * This will be used as part of the hash key for the parser cache,
871 * so users sharing the options with vary for the same page share
872 * the same cached data safely.
873 *
874 * Extensions which require it should install 'PageRenderingHash' hook,
875 * which will give them a chance to modify this key based on their own
876 * settings.
877 *
878 * @since 1.17
879 * @param array $forOptions
880 * @param Title $title Used to get the content language of the page (since r97636)
881 * @return string Page rendering hash
882 */
883 public function optionsHash( $forOptions, $title = null ) {
884 global $wgRenderHashAppend;
885
886 // FIXME: Once the cache key is reorganized this argument
887 // can be dropped. It was used when the math extension was
888 // part of core.
889 $confstr = '*';
890
891 // Space assigned for the stubthreshold but unused
892 // since it disables the parser cache, its value will always
893 // be 0 when this function is called by parsercache.
894 if ( in_array( 'stubthreshold', $forOptions ) ) {
895 $confstr .= '!' . $this->mStubThreshold;
896 } else {
897 $confstr .= '!*';
898 }
899
900 if ( in_array( 'dateformat', $forOptions ) ) {
901 $confstr .= '!' . $this->getDateFormat();
902 }
903
904 if ( in_array( 'numberheadings', $forOptions ) ) {
905 $confstr .= '!' . ( $this->mNumberHeadings ? '1' : '' );
906 } else {
907 $confstr .= '!*';
908 }
909
910 if ( in_array( 'userlang', $forOptions ) ) {
911 $confstr .= '!' . $this->mUserLang->getCode();
912 } else {
913 $confstr .= '!*';
914 }
915
916 if ( in_array( 'thumbsize', $forOptions ) ) {
917 $confstr .= '!' . $this->mThumbSize;
918 } else {
919 $confstr .= '!*';
920 }
921
922 // add in language specific options, if any
923 // @todo FIXME: This is just a way of retrieving the url/user preferred variant
924 if ( !is_null( $title ) ) {
925 $confstr .= $title->getPageLanguage()->getExtraHashOptions();
926 } else {
927 global $wgContLang;
928 $confstr .= $wgContLang->getExtraHashOptions();
929 }
930
931 $confstr .= $wgRenderHashAppend;
932
933 // @note: as of Feb 2015, core never sets the editsection flag, since it uses
934 // <mw:editsection> tags to inject editsections on the fly. However, extensions
935 // may be using it by calling ParserOption::optionUsed resp. ParserOutput::registerOption
936 // directly. At least Wikibase does at this point in time.
937 if ( !in_array( 'editsection', $forOptions ) ) {
938 $confstr .= '!*';
939 } elseif ( !$this->mEditSection ) {
940 $confstr .= '!edit=0';
941 }
942
943 if ( $this->mIsPrintable && in_array( 'printable', $forOptions ) ) {
944 $confstr .= '!printable=1';
945 }
946
947 if ( $this->wrapOutputClass !== 'mw-parser-output' && in_array( 'wrapclass', $forOptions ) ) {
948 $confstr .= '!wrapclass=' . $this->wrapOutputClass;
949 }
950
951 if ( $this->mExtraKey != '' ) {
952 $confstr .= $this->mExtraKey;
953 }
954
955 // Give a chance for extensions to modify the hash, if they have
956 // extra options or other effects on the parser cache.
957 Hooks::run( 'PageRenderingHash', [ &$confstr, $this->getUser(), &$forOptions ] );
958
959 // Make it a valid memcached key fragment
960 $confstr = str_replace( ' ', '_', $confstr );
961
962 return $confstr;
963 }
964
965 /**
966 * Sets a hook to force that a page exists, and sets a current revision callback to return
967 * a revision with custom content when the current revision of the page is requested.
968 *
969 * @since 1.25
970 * @param Title $title
971 * @param Content $content
972 * @param User $user The user that the fake revision is attributed to
973 * @return ScopedCallback to unset the hook
974 */
975 public function setupFakeRevision( $title, $content, $user ) {
976 $oldCallback = $this->setCurrentRevisionCallback(
977 function (
978 $titleToCheck, $parser = false ) use ( $title, $content, $user, &$oldCallback
979 ) {
980 if ( $titleToCheck->equals( $title ) ) {
981 return new Revision( [
982 'page' => $title->getArticleID(),
983 'user_text' => $user->getName(),
984 'user' => $user->getId(),
985 'parent_id' => $title->getLatestRevID(),
986 'title' => $title,
987 'content' => $content
988 ] );
989 } else {
990 return call_user_func( $oldCallback, $titleToCheck, $parser );
991 }
992 }
993 );
994
995 global $wgHooks;
996 $wgHooks['TitleExists'][] =
997 function ( $titleToCheck, &$exists ) use ( $title ) {
998 if ( $titleToCheck->equals( $title ) ) {
999 $exists = true;
1000 }
1001 };
1002 end( $wgHooks['TitleExists'] );
1003 $key = key( $wgHooks['TitleExists'] );
1004 LinkCache::singleton()->clearBadLink( $title->getPrefixedDBkey() );
1005 return new ScopedCallback( function () use ( $title, $key ) {
1006 global $wgHooks;
1007 unset( $wgHooks['TitleExists'][$key] );
1008 LinkCache::singleton()->clearLink( $title );
1009 } );
1010 }
1011 }