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