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