Merge "Drop index oi_name_archive_name on table oldimage"
[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 public function getInterwikiMagic() {
247 return $this->mInterwikiMagic;
248 }
249
250 public function getAllowExternalImages() {
251 return $this->mAllowExternalImages;
252 }
253
254 public function getAllowExternalImagesFrom() {
255 return $this->mAllowExternalImagesFrom;
256 }
257
258 public function getEnableImageWhitelist() {
259 return $this->mEnableImageWhitelist;
260 }
261
262 public function getEditSection() {
263 return $this->mEditSection;
264 }
265
266 public function getNumberHeadings() {
267 $this->optionUsed( 'numberheadings' );
268
269 return $this->mNumberHeadings;
270 }
271
272 public function getAllowSpecialInclusion() {
273 return $this->mAllowSpecialInclusion;
274 }
275
276 public function getTidy() {
277 return $this->mTidy;
278 }
279
280 public function getInterfaceMessage() {
281 return $this->mInterfaceMessage;
282 }
283
284 public function getTargetLanguage() {
285 return $this->mTargetLanguage;
286 }
287
288 public function getMaxIncludeSize() {
289 return $this->mMaxIncludeSize;
290 }
291
292 public function getMaxPPNodeCount() {
293 return $this->mMaxPPNodeCount;
294 }
295
296 public function getMaxGeneratedPPNodeCount() {
297 return $this->mMaxGeneratedPPNodeCount;
298 }
299
300 public function getMaxPPExpandDepth() {
301 return $this->mMaxPPExpandDepth;
302 }
303
304 public function getMaxTemplateDepth() {
305 return $this->mMaxTemplateDepth;
306 }
307
308 /* @since 1.20 */
309 public function getExpensiveParserFunctionLimit() {
310 return $this->mExpensiveParserFunctionLimit;
311 }
312
313 public function getRemoveComments() {
314 return $this->mRemoveComments;
315 }
316
317 /* @since 1.24 */
318 public function getCurrentRevisionCallback() {
319 return $this->mCurrentRevisionCallback;
320 }
321
322 public function getTemplateCallback() {
323 return $this->mTemplateCallback;
324 }
325
326 /** @since 1.28 */
327 public function getSpeculativeRevIdCallback() {
328 return $this->mSpeculativeRevIdCallback;
329 }
330
331 public function getEnableLimitReport() {
332 return $this->mEnableLimitReport;
333 }
334
335 public function getCleanSignatures() {
336 return $this->mCleanSignatures;
337 }
338
339 public function getExternalLinkTarget() {
340 return $this->mExternalLinkTarget;
341 }
342
343 public function getDisableContentConversion() {
344 return $this->mDisableContentConversion;
345 }
346
347 public function getDisableTitleConversion() {
348 return $this->mDisableTitleConversion;
349 }
350
351 public function getThumbSize() {
352 $this->optionUsed( 'thumbsize' );
353
354 return $this->mThumbSize;
355 }
356
357 public function getStubThreshold() {
358 $this->optionUsed( 'stubthreshold' );
359
360 return $this->mStubThreshold;
361 }
362
363 public function getIsPreview() {
364 return $this->mIsPreview;
365 }
366
367 public function getIsSectionPreview() {
368 return $this->mIsSectionPreview;
369 }
370
371 public function getIsPrintable() {
372 $this->optionUsed( 'printable' );
373
374 return $this->mIsPrintable;
375 }
376
377 public function getUser() {
378 return $this->mUser;
379 }
380
381 public function getPreSaveTransform() {
382 return $this->mPreSaveTransform;
383 }
384
385 public function getDateFormat() {
386 $this->optionUsed( 'dateformat' );
387 if ( !isset( $this->mDateFormat ) ) {
388 $this->mDateFormat = $this->mUser->getDatePreference();
389 }
390 return $this->mDateFormat;
391 }
392
393 public function getTimestamp() {
394 if ( !isset( $this->mTimestamp ) ) {
395 $this->mTimestamp = wfTimestampNow();
396 }
397 return $this->mTimestamp;
398 }
399
400 /**
401 * Get the user language used by the parser for this page and split the parser cache.
402 *
403 * @warning: Calling this causes the parser cache to be fragmented by user language!
404 * To avoid cache fragmentation, output should not depend on the user language.
405 * Use Parser::getFunctionLang() or Parser::getTargetLanguage() instead!
406 *
407 * @note This function will trigger a cache fragmentation by recording the
408 * 'userlang' option, see optionUsed(). This is done to avoid cache pollution
409 * when the page is rendered based on the language of the user.
410 *
411 * @note When saving, this will return the default language instead of the user's.
412 * {{int: }} uses this which used to produce inconsistent link tables (T16404).
413 *
414 * @return Language
415 * @since 1.19
416 */
417 public function getUserLangObj() {
418 $this->optionUsed( 'userlang' );
419 return $this->mUserLang;
420 }
421
422 /**
423 * Same as getUserLangObj() but returns a string instead.
424 *
425 * @warning: Calling this causes the parser cache to be fragmented by user language!
426 * To avoid cache fragmentation, output should not depend on the user language.
427 * Use Parser::getFunctionLang() or Parser::getTargetLanguage() instead!
428 *
429 * @see getUserLangObj()
430 *
431 * @return string Language code
432 * @since 1.17
433 */
434 public function getUserLang() {
435 return $this->getUserLangObj()->getCode();
436 }
437
438 /**
439 * @since 1.28
440 * @return bool
441 */
442 public function getMagicISBNLinks() {
443 return $this->mMagicISBNLinks;
444 }
445
446 /**
447 * @since 1.28
448 * @return bool
449 */
450 public function getMagicPMIDLinks() {
451 return $this->mMagicPMIDLinks;
452 }
453 /**
454 * @since 1.28
455 * @return bool
456 */
457 public function getMagicRFCLinks() {
458 return $this->mMagicRFCLinks;
459 }
460 public function setInterwikiMagic( $x ) {
461 return wfSetVar( $this->mInterwikiMagic, $x );
462 }
463
464 public function setAllowExternalImages( $x ) {
465 return wfSetVar( $this->mAllowExternalImages, $x );
466 }
467
468 public function setAllowExternalImagesFrom( $x ) {
469 return wfSetVar( $this->mAllowExternalImagesFrom, $x );
470 }
471
472 public function setEnableImageWhitelist( $x ) {
473 return wfSetVar( $this->mEnableImageWhitelist, $x );
474 }
475
476 public function setDateFormat( $x ) {
477 return wfSetVar( $this->mDateFormat, $x );
478 }
479
480 public function setEditSection( $x ) {
481 return wfSetVar( $this->mEditSection, $x );
482 }
483
484 public function setNumberHeadings( $x ) {
485 return wfSetVar( $this->mNumberHeadings, $x );
486 }
487
488 public function setAllowSpecialInclusion( $x ) {
489 return wfSetVar( $this->mAllowSpecialInclusion, $x );
490 }
491
492 public function setTidy( $x ) {
493 return wfSetVar( $this->mTidy, $x );
494 }
495
496 public function setInterfaceMessage( $x ) {
497 return wfSetVar( $this->mInterfaceMessage, $x );
498 }
499
500 public function setTargetLanguage( $x ) {
501 return wfSetVar( $this->mTargetLanguage, $x, true );
502 }
503
504 public function setMaxIncludeSize( $x ) {
505 return wfSetVar( $this->mMaxIncludeSize, $x );
506 }
507
508 public function setMaxPPNodeCount( $x ) {
509 return wfSetVar( $this->mMaxPPNodeCount, $x );
510 }
511
512 public function setMaxGeneratedPPNodeCount( $x ) {
513 return wfSetVar( $this->mMaxGeneratedPPNodeCount, $x );
514 }
515
516 public function setMaxTemplateDepth( $x ) {
517 return wfSetVar( $this->mMaxTemplateDepth, $x );
518 }
519
520 /* @since 1.20 */
521 public function setExpensiveParserFunctionLimit( $x ) {
522 return wfSetVar( $this->mExpensiveParserFunctionLimit, $x );
523 }
524
525 public function setRemoveComments( $x ) {
526 return wfSetVar( $this->mRemoveComments, $x );
527 }
528
529 /* @since 1.24 */
530 public function setCurrentRevisionCallback( $x ) {
531 return wfSetVar( $this->mCurrentRevisionCallback, $x );
532 }
533
534 /** @since 1.28 */
535 public function setSpeculativeRevIdCallback( $x ) {
536 return wfSetVar( $this->mSpeculativeRevIdCallback, $x );
537 }
538
539 public function setTemplateCallback( $x ) {
540 return wfSetVar( $this->mTemplateCallback, $x );
541 }
542
543 public function enableLimitReport( $x = true ) {
544 return wfSetVar( $this->mEnableLimitReport, $x );
545 }
546
547 public function setTimestamp( $x ) {
548 return wfSetVar( $this->mTimestamp, $x );
549 }
550
551 public function setCleanSignatures( $x ) {
552 return wfSetVar( $this->mCleanSignatures, $x );
553 }
554
555 public function setExternalLinkTarget( $x ) {
556 return wfSetVar( $this->mExternalLinkTarget, $x );
557 }
558
559 public function disableContentConversion( $x = true ) {
560 return wfSetVar( $this->mDisableContentConversion, $x );
561 }
562
563 public function disableTitleConversion( $x = true ) {
564 return wfSetVar( $this->mDisableTitleConversion, $x );
565 }
566
567 public function setUserLang( $x ) {
568 if ( is_string( $x ) ) {
569 $x = Language::factory( $x );
570 }
571
572 return wfSetVar( $this->mUserLang, $x );
573 }
574
575 public function setThumbSize( $x ) {
576 return wfSetVar( $this->mThumbSize, $x );
577 }
578
579 public function setStubThreshold( $x ) {
580 return wfSetVar( $this->mStubThreshold, $x );
581 }
582
583 public function setPreSaveTransform( $x ) {
584 return wfSetVar( $this->mPreSaveTransform, $x );
585 }
586
587 public function setIsPreview( $x ) {
588 return wfSetVar( $this->mIsPreview, $x );
589 }
590
591 public function setIsSectionPreview( $x ) {
592 return wfSetVar( $this->mIsSectionPreview, $x );
593 }
594
595 public function setIsPrintable( $x ) {
596 return wfSetVar( $this->mIsPrintable, $x );
597 }
598
599 /**
600 * Set the redirect target.
601 *
602 * Note that setting or changing this does not *make* the page a redirect
603 * or change its target, it merely records the information for reference
604 * during the parse.
605 *
606 * @since 1.24
607 * @param Title|null $title
608 */
609 function setRedirectTarget( $title ) {
610 $this->redirectTarget = $title;
611 }
612
613 /**
614 * Get the previously-set redirect target.
615 *
616 * @since 1.24
617 * @return Title|null
618 */
619 function getRedirectTarget() {
620 return $this->redirectTarget;
621 }
622
623 /**
624 * Extra key that should be present in the parser cache key.
625 * @param string $key
626 */
627 public function addExtraKey( $key ) {
628 $this->mExtraKey .= '!' . $key;
629 }
630
631 /**
632 * Constructor
633 * @param User $user
634 * @param Language $lang
635 */
636 public function __construct( $user = null, $lang = null ) {
637 if ( $user === null ) {
638 global $wgUser;
639 if ( $wgUser === null ) {
640 $user = new User;
641 } else {
642 $user = $wgUser;
643 }
644 }
645 if ( $lang === null ) {
646 global $wgLang;
647 if ( !StubObject::isRealObject( $wgLang ) ) {
648 $wgLang->_unstub();
649 }
650 $lang = $wgLang;
651 }
652 $this->initialiseFromUser( $user, $lang );
653 }
654
655 /**
656 * Get a ParserOptions object for an anonymous user
657 * @since 1.27
658 * @return ParserOptions
659 */
660 public static function newFromAnon() {
661 global $wgContLang;
662 return new ParserOptions( new User, $wgContLang );
663 }
664
665 /**
666 * Get a ParserOptions object from a given user.
667 * Language will be taken from $wgLang.
668 *
669 * @param User $user
670 * @return ParserOptions
671 */
672 public static function newFromUser( $user ) {
673 return new ParserOptions( $user );
674 }
675
676 /**
677 * Get a ParserOptions object from a given user and language
678 *
679 * @param User $user
680 * @param Language $lang
681 * @return ParserOptions
682 */
683 public static function newFromUserAndLang( User $user, Language $lang ) {
684 return new ParserOptions( $user, $lang );
685 }
686
687 /**
688 * Get a ParserOptions object from a IContextSource object
689 *
690 * @param IContextSource $context
691 * @return ParserOptions
692 */
693 public static function newFromContext( IContextSource $context ) {
694 return new ParserOptions( $context->getUser(), $context->getLanguage() );
695 }
696
697 /**
698 * Get user options
699 *
700 * @param User $user
701 * @param Language $lang
702 */
703 private function initialiseFromUser( $user, $lang ) {
704 global $wgInterwikiMagic, $wgAllowExternalImages,
705 $wgAllowExternalImagesFrom, $wgEnableImageWhitelist, $wgAllowSpecialInclusion,
706 $wgMaxArticleSize, $wgMaxPPNodeCount, $wgMaxTemplateDepth, $wgMaxPPExpandDepth,
707 $wgCleanSignatures, $wgExternalLinkTarget, $wgExpensiveParserFunctionLimit,
708 $wgMaxGeneratedPPNodeCount, $wgDisableLangConversion, $wgDisableTitleConversion,
709 $wgEnableMagicLinks;
710
711 // *UPDATE* ParserOptions::matches() if any of this changes as needed
712 $this->mInterwikiMagic = $wgInterwikiMagic;
713 $this->mAllowExternalImages = $wgAllowExternalImages;
714 $this->mAllowExternalImagesFrom = $wgAllowExternalImagesFrom;
715 $this->mEnableImageWhitelist = $wgEnableImageWhitelist;
716 $this->mAllowSpecialInclusion = $wgAllowSpecialInclusion;
717 $this->mMaxIncludeSize = $wgMaxArticleSize * 1024;
718 $this->mMaxPPNodeCount = $wgMaxPPNodeCount;
719 $this->mMaxGeneratedPPNodeCount = $wgMaxGeneratedPPNodeCount;
720 $this->mMaxPPExpandDepth = $wgMaxPPExpandDepth;
721 $this->mMaxTemplateDepth = $wgMaxTemplateDepth;
722 $this->mExpensiveParserFunctionLimit = $wgExpensiveParserFunctionLimit;
723 $this->mCleanSignatures = $wgCleanSignatures;
724 $this->mExternalLinkTarget = $wgExternalLinkTarget;
725 $this->mDisableContentConversion = $wgDisableLangConversion;
726 $this->mDisableTitleConversion = $wgDisableLangConversion || $wgDisableTitleConversion;
727 $this->mMagicISBNLinks = $wgEnableMagicLinks['ISBN'];
728 $this->mMagicPMIDLinks = $wgEnableMagicLinks['PMID'];
729 $this->mMagicRFCLinks = $wgEnableMagicLinks['RFC'];
730
731 $this->mUser = $user;
732 $this->mNumberHeadings = $user->getOption( 'numberheadings' );
733 $this->mThumbSize = $user->getOption( 'thumbsize' );
734 $this->mStubThreshold = $user->getStubThreshold();
735 $this->mUserLang = $lang;
736 }
737
738 /**
739 * Check if these options match that of another options set
740 *
741 * This ignores report limit settings that only affect HTML comments
742 *
743 * @param ParserOptions $other
744 * @return bool
745 * @since 1.25
746 */
747 public function matches( ParserOptions $other ) {
748 $fields = array_keys( get_class_vars( __CLASS__ ) );
749 $fields = array_diff( $fields, [
750 'mEnableLimitReport', // only effects HTML comments
751 'onAccessCallback', // only used for ParserOutput option tracking
752 ] );
753 foreach ( $fields as $field ) {
754 if ( !is_object( $this->$field ) && $this->$field !== $other->$field ) {
755 return false;
756 }
757 }
758 // Check the object and lazy-loaded options
759 return (
760 $this->mUserLang->equals( $other->mUserLang ) &&
761 $this->getDateFormat() === $other->getDateFormat()
762 );
763 }
764
765 /**
766 * Registers a callback for tracking which ParserOptions which are used.
767 * This is a private API with the parser.
768 * @param callable $callback
769 */
770 public function registerWatcher( $callback ) {
771 $this->onAccessCallback = $callback;
772 }
773
774 /**
775 * Called when an option is accessed.
776 * Calls the watcher that was set using registerWatcher().
777 * Typically, the watcher callback is ParserOutput::registerOption().
778 * The information registered that way will be used by ParserCache::save().
779 *
780 * @param string $optionName Name of the option
781 */
782 public function optionUsed( $optionName ) {
783 if ( $this->onAccessCallback ) {
784 call_user_func( $this->onAccessCallback, $optionName );
785 }
786 }
787
788 /**
789 * Returns the full array of options that would have been used by
790 * in 1.16.
791 * Used to get the old parser cache entries when available.
792 * @return array
793 */
794 public static function legacyOptions() {
795 return [
796 'stubthreshold',
797 'numberheadings',
798 'userlang',
799 'thumbsize',
800 'editsection',
801 'printable'
802 ];
803 }
804
805 /**
806 * Generate a hash string with the values set on these ParserOptions
807 * for the keys given in the array.
808 * This will be used as part of the hash key for the parser cache,
809 * so users sharing the options with vary for the same page share
810 * the same cached data safely.
811 *
812 * Extensions which require it should install 'PageRenderingHash' hook,
813 * which will give them a chance to modify this key based on their own
814 * settings.
815 *
816 * @since 1.17
817 * @param array $forOptions
818 * @param Title $title Used to get the content language of the page (since r97636)
819 * @return string Page rendering hash
820 */
821 public function optionsHash( $forOptions, $title = null ) {
822 global $wgRenderHashAppend;
823
824 // FIXME: Once the cache key is reorganized this argument
825 // can be dropped. It was used when the math extension was
826 // part of core.
827 $confstr = '*';
828
829 // Space assigned for the stubthreshold but unused
830 // since it disables the parser cache, its value will always
831 // be 0 when this function is called by parsercache.
832 if ( in_array( 'stubthreshold', $forOptions ) ) {
833 $confstr .= '!' . $this->mStubThreshold;
834 } else {
835 $confstr .= '!*';
836 }
837
838 if ( in_array( 'dateformat', $forOptions ) ) {
839 $confstr .= '!' . $this->getDateFormat();
840 }
841
842 if ( in_array( 'numberheadings', $forOptions ) ) {
843 $confstr .= '!' . ( $this->mNumberHeadings ? '1' : '' );
844 } else {
845 $confstr .= '!*';
846 }
847
848 if ( in_array( 'userlang', $forOptions ) ) {
849 $confstr .= '!' . $this->mUserLang->getCode();
850 } else {
851 $confstr .= '!*';
852 }
853
854 if ( in_array( 'thumbsize', $forOptions ) ) {
855 $confstr .= '!' . $this->mThumbSize;
856 } else {
857 $confstr .= '!*';
858 }
859
860 // add in language specific options, if any
861 // @todo FIXME: This is just a way of retrieving the url/user preferred variant
862 if ( !is_null( $title ) ) {
863 $confstr .= $title->getPageLanguage()->getExtraHashOptions();
864 } else {
865 global $wgContLang;
866 $confstr .= $wgContLang->getExtraHashOptions();
867 }
868
869 $confstr .= $wgRenderHashAppend;
870
871 // @note: as of Feb 2015, core never sets the editsection flag, since it uses
872 // <mw:editsection> tags to inject editsections on the fly. However, extensions
873 // may be using it by calling ParserOption::optionUsed resp. ParserOutput::registerOption
874 // directly. At least Wikibase does at this point in time.
875 if ( !in_array( 'editsection', $forOptions ) ) {
876 $confstr .= '!*';
877 } elseif ( !$this->mEditSection ) {
878 $confstr .= '!edit=0';
879 }
880
881 if ( $this->mIsPrintable && in_array( 'printable', $forOptions ) ) {
882 $confstr .= '!printable=1';
883 }
884
885 if ( $this->mExtraKey != '' ) {
886 $confstr .= $this->mExtraKey;
887 }
888
889 // Give a chance for extensions to modify the hash, if they have
890 // extra options or other effects on the parser cache.
891 Hooks::run( 'PageRenderingHash', [ &$confstr, $this->getUser(), &$forOptions ] );
892
893 // Make it a valid memcached key fragment
894 $confstr = str_replace( ' ', '_', $confstr );
895
896 return $confstr;
897 }
898
899 /**
900 * Sets a hook to force that a page exists, and sets a current revision callback to return
901 * a revision with custom content when the current revision of the page is requested.
902 *
903 * @since 1.25
904 * @param Title $title
905 * @param Content $content
906 * @param User $user The user that the fake revision is attributed to
907 * @return ScopedCallback to unset the hook
908 */
909 public function setupFakeRevision( $title, $content, $user ) {
910 $oldCallback = $this->setCurrentRevisionCallback(
911 function (
912 $titleToCheck, $parser = false ) use ( $title, $content, $user, &$oldCallback
913 ) {
914 if ( $titleToCheck->equals( $title ) ) {
915 return new Revision( [
916 'page' => $title->getArticleID(),
917 'user_text' => $user->getName(),
918 'user' => $user->getId(),
919 'parent_id' => $title->getLatestRevID(),
920 'title' => $title,
921 'content' => $content
922 ] );
923 } else {
924 return call_user_func( $oldCallback, $titleToCheck, $parser );
925 }
926 }
927 );
928
929 global $wgHooks;
930 $wgHooks['TitleExists'][] =
931 function ( $titleToCheck, &$exists ) use ( $title ) {
932 if ( $titleToCheck->equals( $title ) ) {
933 $exists = true;
934 }
935 };
936 end( $wgHooks['TitleExists'] );
937 $key = key( $wgHooks['TitleExists'] );
938 LinkCache::singleton()->clearBadLink( $title->getPrefixedDBkey() );
939 return new ScopedCallback( function () use ( $title, $key ) {
940 global $wgHooks;
941 unset( $wgHooks['TitleExists'][$key] );
942 LinkCache::singleton()->clearLink( $title );
943 } );
944 }
945 }