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