Completed
Branch master (715cbe)
by
unknown
51:55
created

includes/page/Article.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * User interface for page actions.
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
 */
22
23
/**
24
 * Class for viewing MediaWiki article and history.
25
 *
26
 * This maintains WikiPage functions for backwards compatibility.
27
 *
28
 * @todo Move and rewrite code to an Action class
29
 *
30
 * See design.txt for an overview.
31
 * Note: edit user interface and cache support functions have been
32
 * moved to separate EditPage and HTMLFileCache classes.
33
 */
34
class Article implements Page {
35
	/** @var IContextSource The context this Article is executed in */
36
	protected $mContext;
37
38
	/** @var WikiPage The WikiPage object of this instance */
39
	protected $mPage;
40
41
	/** @var ParserOptions ParserOptions object for $wgUser articles */
42
	public $mParserOptions;
43
44
	/**
45
	 * @var string Text of the revision we are working on
46
	 * @todo BC cruft
47
	 */
48
	public $mContent;
49
50
	/**
51
	 * @var Content Content of the revision we are working on
52
	 * @since 1.21
53
	 */
54
	public $mContentObject;
55
56
	/** @var bool Is the content ($mContent) already loaded? */
57
	public $mContentLoaded = false;
58
59
	/** @var int|null The oldid of the article that is to be shown, 0 for the current revision */
60
	public $mOldId;
61
62
	/** @var Title Title from which we were redirected here */
63
	public $mRedirectedFrom = null;
64
65
	/** @var string|bool URL to redirect to or false if none */
66
	public $mRedirectUrl = false;
67
68
	/** @var int Revision ID of revision we are working on */
69
	public $mRevIdFetched = 0;
70
71
	/** @var Revision Revision we are working on */
72
	public $mRevision = null;
73
74
	/** @var ParserOutput */
75
	public $mParserOutput;
76
77
	/**
78
	 * Constructor and clear the article
79
	 * @param Title $title Reference to a Title object.
80
	 * @param int $oldId Revision ID, null to fetch from request, zero for current
81
	 */
82
	public function __construct( Title $title, $oldId = null ) {
83
		$this->mOldId = $oldId;
84
		$this->mPage = $this->newPage( $title );
85
	}
86
87
	/**
88
	 * @param Title $title
89
	 * @return WikiPage
90
	 */
91
	protected function newPage( Title $title ) {
92
		return new WikiPage( $title );
93
	}
94
95
	/**
96
	 * Constructor from a page id
97
	 * @param int $id Article ID to load
98
	 * @return Article|null
99
	 */
100
	public static function newFromID( $id ) {
101
		$t = Title::newFromID( $id );
102
		return $t == null ? null : new static( $t );
103
	}
104
105
	/**
106
	 * Create an Article object of the appropriate class for the given page.
107
	 *
108
	 * @param Title $title
109
	 * @param IContextSource $context
110
	 * @return Article
111
	 */
112
	public static function newFromTitle( $title, IContextSource $context ) {
113
		if ( NS_MEDIA == $title->getNamespace() ) {
114
			// FIXME: where should this go?
115
			$title = Title::makeTitle( NS_FILE, $title->getDBkey() );
116
		}
117
118
		$page = null;
119
		Hooks::run( 'ArticleFromTitle', [ &$title, &$page, $context ] );
120
		if ( !$page ) {
121
			switch ( $title->getNamespace() ) {
122
				case NS_FILE:
123
					$page = new ImagePage( $title );
124
					break;
125
				case NS_CATEGORY:
126
					$page = new CategoryPage( $title );
127
					break;
128
				default:
129
					$page = new Article( $title );
130
			}
131
		}
132
		$page->setContext( $context );
133
134
		return $page;
135
	}
136
137
	/**
138
	 * Create an Article object of the appropriate class for the given page.
139
	 *
140
	 * @param WikiPage $page
141
	 * @param IContextSource $context
142
	 * @return Article
143
	 */
144
	public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
145
		$article = self::newFromTitle( $page->getTitle(), $context );
146
		$article->mPage = $page; // override to keep process cached vars
147
		return $article;
148
	}
149
150
	/**
151
	 * Get the page this view was redirected from
152
	 * @return Title|null
153
	 * @since 1.28
154
	 */
155
	public function getRedirectedFrom() {
156
		return $this->mRedirectedFrom;
157
	}
158
159
	/**
160
	 * Tell the page view functions that this view was redirected
161
	 * from another page on the wiki.
162
	 * @param Title $from
163
	 */
164
	public function setRedirectedFrom( Title $from ) {
165
		$this->mRedirectedFrom = $from;
166
	}
167
168
	/**
169
	 * Get the title object of the article
170
	 *
171
	 * @return Title Title object of this page
172
	 */
173
	public function getTitle() {
174
		return $this->mPage->getTitle();
175
	}
176
177
	/**
178
	 * Get the WikiPage object of this instance
179
	 *
180
	 * @since 1.19
181
	 * @return WikiPage
182
	 */
183
	public function getPage() {
184
		return $this->mPage;
185
	}
186
187
	/**
188
	 * Clear the object
189
	 */
190
	public function clear() {
191
		$this->mContentLoaded = false;
192
193
		$this->mRedirectedFrom = null; # Title object if set
194
		$this->mRevIdFetched = 0;
195
		$this->mRedirectUrl = false;
196
197
		$this->mPage->clear();
198
	}
199
200
	/**
201
	 * Note that getContent does not follow redirects anymore.
202
	 * If you need to fetch redirectable content easily, try
203
	 * the shortcut in WikiPage::getRedirectTarget()
204
	 *
205
	 * This function has side effects! Do not use this function if you
206
	 * only want the real revision text if any.
207
	 *
208
	 * @deprecated since 1.21; use WikiPage::getContent() instead
209
	 *
210
	 * @return string Return the text of this revision
211
	 */
212
	public function getContent() {
213
		wfDeprecated( __METHOD__, '1.21' );
214
		$content = $this->getContentObject();
215
		return ContentHandler::getContentText( $content );
216
	}
217
218
	/**
219
	 * Returns a Content object representing the pages effective display content,
220
	 * not necessarily the revision's content!
221
	 *
222
	 * Note that getContent does not follow redirects anymore.
223
	 * If you need to fetch redirectable content easily, try
224
	 * the shortcut in WikiPage::getRedirectTarget()
225
	 *
226
	 * This function has side effects! Do not use this function if you
227
	 * only want the real revision text if any.
228
	 *
229
	 * @return Content Return the content of this revision
230
	 *
231
	 * @since 1.21
232
	 */
233
	protected function getContentObject() {
234
235
		if ( $this->mPage->getId() === 0 ) {
236
			# If this is a MediaWiki:x message, then load the messages
237
			# and return the message value for x.
238
			if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) {
239
				$text = $this->getTitle()->getDefaultMessageText();
240
				if ( $text === false ) {
241
					$text = '';
242
				}
243
244
				$content = ContentHandler::makeContent( $text, $this->getTitle() );
245
			} else {
246
				$message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
247
				$content = new MessageContent( $message, null, 'parsemag' );
248
			}
249
		} else {
250
			$this->fetchContentObject();
251
			$content = $this->mContentObject;
252
		}
253
254
		return $content;
255
	}
256
257
	/**
258
	 * @return int The oldid of the article that is to be shown, 0 for the current revision
259
	 */
260
	public function getOldID() {
261
		if ( is_null( $this->mOldId ) ) {
262
			$this->mOldId = $this->getOldIDFromRequest();
263
		}
264
265
		return $this->mOldId;
266
	}
267
268
	/**
269
	 * Sets $this->mRedirectUrl to a correct URL if the query parameters are incorrect
270
	 *
271
	 * @return int The old id for the request
272
	 */
273
	public function getOldIDFromRequest() {
274
		$this->mRedirectUrl = false;
275
276
		$request = $this->getContext()->getRequest();
277
		$oldid = $request->getIntOrNull( 'oldid' );
278
279
		if ( $oldid === null ) {
280
			return 0;
281
		}
282
283
		if ( $oldid !== 0 ) {
284
			# Load the given revision and check whether the page is another one.
285
			# In that case, update this instance to reflect the change.
286
			if ( $oldid === $this->mPage->getLatest() ) {
287
				$this->mRevision = $this->mPage->getRevision();
288
			} else {
289
				$this->mRevision = Revision::newFromId( $oldid );
290
				if ( $this->mRevision !== null ) {
291
					// Revision title doesn't match the page title given?
292
					if ( $this->mPage->getId() != $this->mRevision->getPage() ) {
293
						$function = [ get_class( $this->mPage ), 'newFromID' ];
294
						$this->mPage = call_user_func( $function, $this->mRevision->getPage() );
295
					}
296
				}
297
			}
298
		}
299
300
		if ( $request->getVal( 'direction' ) == 'next' ) {
301
			$nextid = $this->getTitle()->getNextRevisionID( $oldid );
302
			if ( $nextid ) {
303
				$oldid = $nextid;
304
				$this->mRevision = null;
305
			} else {
306
				$this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
307
			}
308
		} elseif ( $request->getVal( 'direction' ) == 'prev' ) {
309
			$previd = $this->getTitle()->getPreviousRevisionID( $oldid );
310
			if ( $previd ) {
311
				$oldid = $previd;
312
				$this->mRevision = null;
313
			}
314
		}
315
316
		return $oldid;
317
	}
318
319
	/**
320
	 * Get text of an article from database
321
	 * Does *NOT* follow redirects.
322
	 *
323
	 * @protected
324
	 * @note This is really internal functionality that should really NOT be
325
	 * used by other functions. For accessing article content, use the WikiPage
326
	 * class, especially WikiBase::getContent(). However, a lot of legacy code
327
	 * uses this method to retrieve page text from the database, so the function
328
	 * has to remain public for now.
329
	 *
330
	 * @return string|bool String containing article contents, or false if null
331
	 * @deprecated since 1.21, use WikiPage::getContent() instead
332
	 */
333
	function fetchContent() {
334
		// BC cruft!
335
336
		wfDeprecated( __METHOD__, '1.21' );
337
338
		if ( $this->mContentLoaded && $this->mContent ) {
339
			return $this->mContent;
340
		}
341
342
		$content = $this->fetchContentObject();
343
344
		if ( !$content ) {
345
			return false;
346
		}
347
348
		// @todo Get rid of mContent everywhere!
349
		$this->mContent = ContentHandler::getContentText( $content );
350
		ContentHandler::runLegacyHooks(
351
			'ArticleAfterFetchContent',
352
			[ &$this, &$this->mContent ],
353
			'1.21'
354
		);
355
356
		return $this->mContent;
357
	}
358
359
	/**
360
	 * Get text content object
361
	 * Does *NOT* follow redirects.
362
	 * @todo When is this null?
363
	 *
364
	 * @note Code that wants to retrieve page content from the database should
365
	 * use WikiPage::getContent().
366
	 *
367
	 * @return Content|null|bool
368
	 *
369
	 * @since 1.21
370
	 */
371
	protected function fetchContentObject() {
372
		if ( $this->mContentLoaded ) {
373
			return $this->mContentObject;
374
		}
375
376
		$this->mContentLoaded = true;
377
		$this->mContent = null;
378
379
		$oldid = $this->getOldID();
380
381
		# Pre-fill content with error message so that if something
382
		# fails we'll have something telling us what we intended.
383
		// XXX: this isn't page content but a UI message. horrible.
384
		$this->mContentObject = new MessageContent( 'missing-revision', [ $oldid ] );
385
386
		if ( $oldid ) {
387
			# $this->mRevision might already be fetched by getOldIDFromRequest()
388
			if ( !$this->mRevision ) {
389
				$this->mRevision = Revision::newFromId( $oldid );
390
				if ( !$this->mRevision ) {
391
					wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" );
392
					return false;
393
				}
394
			}
395
		} else {
396
			$oldid = $this->mPage->getLatest();
397
			if ( !$oldid ) {
398
				wfDebug( __METHOD__ . " failed to find page data for title " .
399
					$this->getTitle()->getPrefixedText() . "\n" );
400
				return false;
401
			}
402
403
			# Update error message with correct oldid
404
			$this->mContentObject = new MessageContent( 'missing-revision', [ $oldid ] );
405
406
			$this->mRevision = $this->mPage->getRevision();
407
408
			if ( !$this->mRevision ) {
409
				wfDebug( __METHOD__ . " failed to retrieve current page, rev_id $oldid\n" );
410
				return false;
411
			}
412
		}
413
414
		// @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
415
		// We should instead work with the Revision object when we need it...
416
		// Loads if user is allowed
417
		$content = $this->mRevision->getContent(
418
			Revision::FOR_THIS_USER,
419
			$this->getContext()->getUser()
420
		);
421
422
		if ( !$content ) {
423
			wfDebug( __METHOD__ . " failed to retrieve content of revision " .
424
				$this->mRevision->getId() . "\n" );
425
			return false;
426
		}
427
428
		$this->mContentObject = $content;
429
		$this->mRevIdFetched = $this->mRevision->getId();
430
431
		ContentHandler::runLegacyHooks(
432
			'ArticleAfterFetchContentObject',
433
			[ &$this, &$this->mContentObject ],
434
			'1.21'
435
		);
436
437
		return $this->mContentObject;
438
	}
439
440
	/**
441
	 * Returns true if the currently-referenced revision is the current edit
442
	 * to this page (and it exists).
443
	 * @return bool
444
	 */
445
	public function isCurrent() {
446
		# If no oldid, this is the current version.
447
		if ( $this->getOldID() == 0 ) {
448
			return true;
449
		}
450
451
		return $this->mPage->exists() && $this->mRevision && $this->mRevision->isCurrent();
452
	}
453
454
	/**
455
	 * Get the fetched Revision object depending on request parameters or null
456
	 * on failure.
457
	 *
458
	 * @since 1.19
459
	 * @return Revision|null
460
	 */
461
	public function getRevisionFetched() {
462
		$this->fetchContentObject();
463
464
		return $this->mRevision;
465
	}
466
467
	/**
468
	 * Use this to fetch the rev ID used on page views
469
	 *
470
	 * @return int Revision ID of last article revision
471
	 */
472
	public function getRevIdFetched() {
473
		if ( $this->mRevIdFetched ) {
474
			return $this->mRevIdFetched;
475
		} else {
476
			return $this->mPage->getLatest();
477
		}
478
	}
479
480
	/**
481
	 * This is the default action of the index.php entry point: just view the
482
	 * page of the given title.
483
	 */
484
	public function view() {
485
		global $wgUseFileCache, $wgDebugToolbar;
486
487
		# Get variables from query string
488
		# As side effect this will load the revision and update the title
489
		# in a revision ID is passed in the request, so this should remain
490
		# the first call of this method even if $oldid is used way below.
491
		$oldid = $this->getOldID();
492
493
		$user = $this->getContext()->getUser();
494
		# Another whitelist check in case getOldID() is altering the title
495
		$permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $user );
496
		if ( count( $permErrors ) ) {
497
			wfDebug( __METHOD__ . ": denied on secondary read check\n" );
498
			throw new PermissionsError( 'read', $permErrors );
499
		}
500
501
		$outputPage = $this->getContext()->getOutput();
502
		# getOldID() may as well want us to redirect somewhere else
503
		if ( $this->mRedirectUrl ) {
504
			$outputPage->redirect( $this->mRedirectUrl );
505
			wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
506
507
			return;
508
		}
509
510
		# If we got diff in the query, we want to see a diff page instead of the article.
511
		if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) {
512
			wfDebug( __METHOD__ . ": showing diff page\n" );
513
			$this->showDiffPage();
514
515
			return;
516
		}
517
518
		# Set page title (may be overridden by DISPLAYTITLE)
519
		$outputPage->setPageTitle( $this->getTitle()->getPrefixedText() );
520
521
		$outputPage->setArticleFlag( true );
522
		# Allow frames by default
523
		$outputPage->allowClickjacking();
524
525
		$parserCache = ParserCache::singleton();
526
527
		$parserOptions = $this->getParserOptions();
528
		# Render printable version, use printable version cache
529
		if ( $outputPage->isPrintable() ) {
530
			$parserOptions->setIsPrintable( true );
531
			$parserOptions->setEditSection( false );
532
		} elseif ( !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user ) ) {
533
			$parserOptions->setEditSection( false );
534
		}
535
536
		# Try client and file cache
537
		if ( !$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched() ) {
538
			# Try to stream the output from file cache
539
			if ( $wgUseFileCache && $this->tryFileCache() ) {
540
				wfDebug( __METHOD__ . ": done file cache\n" );
541
				# tell wgOut that output is taken care of
542
				$outputPage->disable();
543
				$this->mPage->doViewUpdates( $user, $oldid );
544
545
				return;
546
			}
547
		}
548
549
		# Should the parser cache be used?
550
		$useParserCache = $this->mPage->shouldCheckParserCache( $parserOptions, $oldid );
551
		wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
552
		if ( $user->getStubThreshold() ) {
553
			$this->getContext()->getStats()->increment( 'pcache_miss_stub' );
554
		}
555
556
		$this->showRedirectedFromHeader();
557
		$this->showNamespaceHeader();
558
559
		# Iterate through the possible ways of constructing the output text.
560
		# Keep going until $outputDone is set, or we run out of things to do.
561
		$pass = 0;
562
		$outputDone = false;
563
		$this->mParserOutput = false;
564
565
		while ( !$outputDone && ++$pass ) {
566
			switch ( $pass ) {
567
				case 1:
568
					Hooks::run( 'ArticleViewHeader', [ &$this, &$outputDone, &$useParserCache ] );
569
					break;
570
				case 2:
571
					# Early abort if the page doesn't exist
572
					if ( !$this->mPage->exists() ) {
573
						wfDebug( __METHOD__ . ": showing missing article\n" );
574
						$this->showMissingArticle();
575
						$this->mPage->doViewUpdates( $user );
576
						return;
577
					}
578
579
					# Try the parser cache
580
					if ( $useParserCache ) {
581
						$this->mParserOutput = $parserCache->get( $this->mPage, $parserOptions );
582
583
						if ( $this->mParserOutput !== false ) {
584
							if ( $oldid ) {
585
								wfDebug( __METHOD__ . ": showing parser cache contents for current rev permalink\n" );
586
								$this->setOldSubtitle( $oldid );
587
							} else {
588
								wfDebug( __METHOD__ . ": showing parser cache contents\n" );
589
							}
590
							$outputPage->addParserOutput( $this->mParserOutput );
591
							# Ensure that UI elements requiring revision ID have
592
							# the correct version information.
593
							$outputPage->setRevisionId( $this->mPage->getLatest() );
594
							# Preload timestamp to avoid a DB hit
595
							$cachedTimestamp = $this->mParserOutput->getTimestamp();
596
							if ( $cachedTimestamp !== null ) {
597
								$outputPage->setRevisionTimestamp( $cachedTimestamp );
598
								$this->mPage->setTimestamp( $cachedTimestamp );
599
							}
600
							$outputDone = true;
601
						}
602
					}
603
					break;
604
				case 3:
605
					# This will set $this->mRevision if needed
606
					$this->fetchContentObject();
607
608
					# Are we looking at an old revision
609
					if ( $oldid && $this->mRevision ) {
610
						$this->setOldSubtitle( $oldid );
611
612
						if ( !$this->showDeletedRevisionHeader() ) {
613
							wfDebug( __METHOD__ . ": cannot view deleted revision\n" );
614
							return;
615
						}
616
					}
617
618
					# Ensure that UI elements requiring revision ID have
619
					# the correct version information.
620
					$outputPage->setRevisionId( $this->getRevIdFetched() );
621
					# Preload timestamp to avoid a DB hit
622
					$outputPage->setRevisionTimestamp( $this->mPage->getTimestamp() );
623
624
					# Pages containing custom CSS or JavaScript get special treatment
625
					if ( $this->getTitle()->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage() ) {
626
						wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
627
						$this->showCssOrJsPage();
628
						$outputDone = true;
629
					} elseif ( !Hooks::run( 'ArticleContentViewCustom',
630
							[ $this->fetchContentObject(), $this->getTitle(), $outputPage ] ) ) {
631
632
						# Allow extensions do their own custom view for certain pages
633
						$outputDone = true;
634
					} elseif ( !ContentHandler::runLegacyHooks(
635
						'ArticleViewCustom',
636
						[ $this->fetchContentObject(), $this->getTitle(), $outputPage ],
637
						'1.21'
638
					) ) {
639
						# Allow extensions do their own custom view for certain pages
640
						$outputDone = true;
641
					}
642
					break;
643
				case 4:
644
					# Run the parse, protected by a pool counter
645
					wfDebug( __METHOD__ . ": doing uncached parse\n" );
646
647
					$content = $this->getContentObject();
648
					$poolArticleView = new PoolWorkArticleView( $this->getPage(), $parserOptions,
649
						$this->getRevIdFetched(), $useParserCache, $content );
650
651
					if ( !$poolArticleView->execute() ) {
652
						$error = $poolArticleView->getError();
653
						if ( $error ) {
654
							$outputPage->clearHTML(); // for release() errors
655
							$outputPage->enableClientCache( false );
656
							$outputPage->setRobotPolicy( 'noindex,nofollow' );
657
658
							$errortext = $error->getWikiText( false, 'view-pool-error' );
659
							$outputPage->addWikiText( '<div class="errorbox">' . $errortext . '</div>' );
660
						}
661
						# Connection or timeout error
662
						return;
663
					}
664
665
					$this->mParserOutput = $poolArticleView->getParserOutput();
666
					$outputPage->addParserOutput( $this->mParserOutput );
667
					if ( $content->getRedirectTarget() ) {
668
						$outputPage->addSubtitle( "<span id=\"redirectsub\">" .
669
							$this->getContext()->msg( 'redirectpagesub' )->parse() . "</span>" );
670
					}
671
672
					# Don't cache a dirty ParserOutput object
673
					if ( $poolArticleView->getIsDirty() ) {
674
						$outputPage->setCdnMaxage( 0 );
675
						$outputPage->addHTML( "<!-- parser cache is expired, " .
676
							"sending anyway due to pool overload-->\n" );
677
					}
678
679
					$outputDone = true;
680
					break;
681
				# Should be unreachable, but just in case...
682
				default:
683
					break 2;
684
			}
685
		}
686
687
		# Get the ParserOutput actually *displayed* here.
688
		# Note that $this->mParserOutput is the *current*/oldid version output.
689
		$pOutput = ( $outputDone instanceof ParserOutput )
690
			? $outputDone // object fetched by hook
691
			: $this->mParserOutput;
692
693
		# Adjust title for main page & pages with displaytitle
694
		if ( $pOutput ) {
695
			$this->adjustDisplayTitle( $pOutput );
696
		}
697
698
		# For the main page, overwrite the <title> element with the con-
699
		# tents of 'pagetitle-view-mainpage' instead of the default (if
700
		# that's not empty).
701
		# This message always exists because it is in the i18n files
702
		if ( $this->getTitle()->isMainPage() ) {
703
			$msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
704
			if ( !$msg->isDisabled() ) {
705
				$outputPage->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
706
			}
707
		}
708
709
		# Use adaptive TTLs for CDN so delayed/failed purges are noticed less often.
710
		# This could use getTouched(), but that could be scary for major template edits.
711
		$outputPage->adaptCdnTTL( $this->mPage->getTimestamp(), IExpiringStore::TTL_DAY );
712
713
		# Check for any __NOINDEX__ tags on the page using $pOutput
714
		$policy = $this->getRobotPolicy( 'view', $pOutput );
0 ignored issues
show
It seems like $pOutput defined by $outputDone instanceof \... : $this->mParserOutput on line 689 can also be of type false; however, Article::getRobotPolicy() does only seem to accept object<ParserOutput>|null, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
715
		$outputPage->setIndexPolicy( $policy['index'] );
716
		$outputPage->setFollowPolicy( $policy['follow'] );
717
718
		$this->showViewFooter();
719
		$this->mPage->doViewUpdates( $user, $oldid );
720
721
		$outputPage->addModules( 'mediawiki.action.view.postEdit' );
722
	}
723
724
	/**
725
	 * Adjust title for pages with displaytitle, -{T|}- or language conversion
726
	 * @param ParserOutput $pOutput
727
	 */
728
	public function adjustDisplayTitle( ParserOutput $pOutput ) {
729
		# Adjust the title if it was set by displaytitle, -{T|}- or language conversion
730
		$titleText = $pOutput->getTitleText();
731
		if ( strval( $titleText ) !== '' ) {
732
			$this->getContext()->getOutput()->setPageTitle( $titleText );
733
		}
734
	}
735
736
	/**
737
	 * Show a diff page according to current request variables. For use within
738
	 * Article::view() only, other callers should use the DifferenceEngine class.
739
	 *
740
	 */
741
	protected function showDiffPage() {
742
		$request = $this->getContext()->getRequest();
743
		$user = $this->getContext()->getUser();
744
		$diff = $request->getVal( 'diff' );
745
		$rcid = $request->getVal( 'rcid' );
746
		$diffOnly = $request->getBool( 'diffonly', $user->getOption( 'diffonly' ) );
747
		$purge = $request->getVal( 'action' ) == 'purge';
748
		$unhide = $request->getInt( 'unhide' ) == 1;
749
		$oldid = $this->getOldID();
750
751
		$rev = $this->getRevisionFetched();
752
753
		if ( !$rev ) {
754
			$this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) );
755
			$msg = $this->getContext()->msg( 'difference-missing-revision' )
756
				->params( $oldid )
757
				->numParams( 1 )
758
				->parseAsBlock();
759
			$this->getContext()->getOutput()->addHTML( $msg );
760
			return;
761
		}
762
763
		$contentHandler = $rev->getContentHandler();
764
		$de = $contentHandler->createDifferenceEngine(
765
			$this->getContext(),
766
			$oldid,
767
			$diff,
768
			$rcid,
769
			$purge,
770
			$unhide
771
		);
772
773
		// DifferenceEngine directly fetched the revision:
774
		$this->mRevIdFetched = $de->mNewid;
775
		$de->showDiffPage( $diffOnly );
776
777
		// Run view updates for the newer revision being diffed (and shown
778
		// below the diff if not $diffOnly).
779
		list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
780
		// New can be false, convert it to 0 - this conveniently means the latest revision
781
		$this->mPage->doViewUpdates( $user, (int)$new );
782
	}
783
784
	/**
785
	 * Show a page view for a page formatted as CSS or JavaScript. To be called by
786
	 * Article::view() only.
787
	 *
788
	 * This exists mostly to serve the deprecated ShowRawCssJs hook (used to customize these views).
789
	 * It has been replaced by the ContentGetParserOutput hook, which lets you do the same but with
790
	 * more flexibility.
791
	 *
792
	 * @param bool $showCacheHint Whether to show a message telling the user
793
	 *   to clear the browser cache (default: true).
794
	 */
795
	protected function showCssOrJsPage( $showCacheHint = true ) {
796
		$outputPage = $this->getContext()->getOutput();
797
798
		if ( $showCacheHint ) {
799
			$dir = $this->getContext()->getLanguage()->getDir();
800
			$lang = $this->getContext()->getLanguage()->getHtmlCode();
801
802
			$outputPage->wrapWikiMsg(
803
				"<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
804
				'clearyourcache'
805
			);
806
		}
807
808
		$this->fetchContentObject();
809
810
		if ( $this->mContentObject ) {
811
			// Give hooks a chance to customise the output
812
			if ( ContentHandler::runLegacyHooks(
813
				'ShowRawCssJs',
814
				[ $this->mContentObject, $this->getTitle(), $outputPage ],
815
				'1.24'
816
			) ) {
817
				// If no legacy hooks ran, display the content of the parser output, including RL modules,
818
				// but excluding metadata like categories and language links
819
				$po = $this->mContentObject->getParserOutput( $this->getTitle() );
820
				$outputPage->addParserOutputContent( $po );
821
			}
822
		}
823
	}
824
825
	/**
826
	 * Get the robot policy to be used for the current view
827
	 * @param string $action The action= GET parameter
828
	 * @param ParserOutput|null $pOutput
829
	 * @return array The policy that should be set
830
	 * @todo actions other than 'view'
831
	 */
832
	public function getRobotPolicy( $action, $pOutput = null ) {
833
		global $wgArticleRobotPolicies, $wgNamespaceRobotPolicies, $wgDefaultRobotPolicy;
834
835
		$ns = $this->getTitle()->getNamespace();
836
837
		# Don't index user and user talk pages for blocked users (bug 11443)
838
		if ( ( $ns == NS_USER || $ns == NS_USER_TALK ) && !$this->getTitle()->isSubpage() ) {
839
			$specificTarget = null;
840
			$vagueTarget = null;
841
			$titleText = $this->getTitle()->getText();
842
			if ( IP::isValid( $titleText ) ) {
843
				$vagueTarget = $titleText;
844
			} else {
845
				$specificTarget = $titleText;
846
			}
847
			if ( Block::newFromTarget( $specificTarget, $vagueTarget ) instanceof Block ) {
848
				return [
849
					'index' => 'noindex',
850
					'follow' => 'nofollow'
851
				];
852
			}
853
		}
854
855
		if ( $this->mPage->getId() === 0 || $this->getOldID() ) {
856
			# Non-articles (special pages etc), and old revisions
857
			return [
858
				'index' => 'noindex',
859
				'follow' => 'nofollow'
860
			];
861
		} elseif ( $this->getContext()->getOutput()->isPrintable() ) {
862
			# Discourage indexing of printable versions, but encourage following
863
			return [
864
				'index' => 'noindex',
865
				'follow' => 'follow'
866
			];
867
		} elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) {
868
			# For ?curid=x urls, disallow indexing
869
			return [
870
				'index' => 'noindex',
871
				'follow' => 'follow'
872
			];
873
		}
874
875
		# Otherwise, construct the policy based on the various config variables.
876
		$policy = self::formatRobotPolicy( $wgDefaultRobotPolicy );
877
878
		if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
879
			# Honour customised robot policies for this namespace
880
			$policy = array_merge(
881
				$policy,
882
				self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
883
			);
884
		}
885
		if ( $this->getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
886
			# __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
887
			# a final sanity check that we have really got the parser output.
888
			$policy = array_merge(
889
				$policy,
890
				[ 'index' => $pOutput->getIndexPolicy() ]
891
			);
892
		}
893
894
		if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) {
895
			# (bug 14900) site config can override user-defined __INDEX__ or __NOINDEX__
896
			$policy = array_merge(
897
				$policy,
898
				self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] )
899
			);
900
		}
901
902
		return $policy;
903
	}
904
905
	/**
906
	 * Converts a String robot policy into an associative array, to allow
907
	 * merging of several policies using array_merge().
908
	 * @param array|string $policy Returns empty array on null/false/'', transparent
909
	 *   to already-converted arrays, converts string.
910
	 * @return array 'index' => \<indexpolicy\>, 'follow' => \<followpolicy\>
911
	 */
912
	public static function formatRobotPolicy( $policy ) {
913
		if ( is_array( $policy ) ) {
914
			return $policy;
915
		} elseif ( !$policy ) {
916
			return [];
917
		}
918
919
		$policy = explode( ',', $policy );
920
		$policy = array_map( 'trim', $policy );
921
922
		$arr = [];
923
		foreach ( $policy as $var ) {
924
			if ( in_array( $var, [ 'index', 'noindex' ] ) ) {
925
				$arr['index'] = $var;
926
			} elseif ( in_array( $var, [ 'follow', 'nofollow' ] ) ) {
927
				$arr['follow'] = $var;
928
			}
929
		}
930
931
		return $arr;
932
	}
933
934
	/**
935
	 * If this request is a redirect view, send "redirected from" subtitle to
936
	 * the output. Returns true if the header was needed, false if this is not
937
	 * a redirect view. Handles both local and remote redirects.
938
	 *
939
	 * @return bool
940
	 */
941
	public function showRedirectedFromHeader() {
942
		global $wgRedirectSources;
943
944
		$context = $this->getContext();
945
		$outputPage = $context->getOutput();
946
		$request = $context->getRequest();
947
		$rdfrom = $request->getVal( 'rdfrom' );
948
949
		// Construct a URL for the current page view, but with the target title
950
		$query = $request->getValues();
951
		unset( $query['rdfrom'] );
952
		unset( $query['title'] );
953
		if ( $this->getTitle()->isRedirect() ) {
954
			// Prevent double redirects
955
			$query['redirect'] = 'no';
956
		}
957
		$redirectTargetUrl = $this->getTitle()->getLinkURL( $query );
958
959
		if ( isset( $this->mRedirectedFrom ) ) {
960
			// This is an internally redirected page view.
961
			// We'll need a backlink to the source page for navigation.
962
			if ( Hooks::run( 'ArticleViewRedirect', [ &$this ] ) ) {
963
				$redir = Linker::linkKnown(
964
					$this->mRedirectedFrom,
965
					null,
966
					[],
967
					[ 'redirect' => 'no' ]
968
				);
969
970
				$outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
971
					$context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
972
				. "</span>" );
973
974
				// Add the script to update the displayed URL and
975
				// set the fragment if one was specified in the redirect
976
				$outputPage->addJsConfigVars( [
977
					'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
978
				] );
979
				$outputPage->addModules( 'mediawiki.action.view.redirect' );
980
981
				// Add a <link rel="canonical"> tag
982
				$outputPage->setCanonicalUrl( $this->getTitle()->getCanonicalURL() );
983
984
				// Tell the output object that the user arrived at this article through a redirect
985
				$outputPage->setRedirectedFrom( $this->mRedirectedFrom );
986
987
				return true;
988
			}
989
		} elseif ( $rdfrom ) {
990
			// This is an externally redirected view, from some other wiki.
991
			// If it was reported from a trusted site, supply a backlink.
992
			if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
993
				$redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
994
				$outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
995
					$context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
996
				. "</span>" );
997
998
				// Add the script to update the displayed URL
999
				$outputPage->addJsConfigVars( [
1000
					'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1001
				] );
1002
				$outputPage->addModules( 'mediawiki.action.view.redirect' );
1003
1004
				return true;
1005
			}
1006
		}
1007
1008
		return false;
1009
	}
1010
1011
	/**
1012
	 * Show a header specific to the namespace currently being viewed, like
1013
	 * [[MediaWiki:Talkpagetext]]. For Article::view().
1014
	 */
1015
	public function showNamespaceHeader() {
1016
		if ( $this->getTitle()->isTalkPage() ) {
1017
			if ( !wfMessage( 'talkpageheader' )->isDisabled() ) {
1018
				$this->getContext()->getOutput()->wrapWikiMsg(
1019
					"<div class=\"mw-talkpageheader\">\n$1\n</div>",
1020
					[ 'talkpageheader' ]
1021
				);
1022
			}
1023
		}
1024
	}
1025
1026
	/**
1027
	 * Show the footer section of an ordinary page view
1028
	 */
1029
	public function showViewFooter() {
1030
		# check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1031
		if ( $this->getTitle()->getNamespace() == NS_USER_TALK
1032
			&& IP::isValid( $this->getTitle()->getText() )
1033
		) {
1034
			$this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
1035
		}
1036
1037
		// Show a footer allowing the user to patrol the shown revision or page if possible
1038
		$patrolFooterShown = $this->showPatrolFooter();
1039
1040
		Hooks::run( 'ArticleViewFooter', [ $this, $patrolFooterShown ] );
1041
	}
1042
1043
	/**
1044
	 * If patrol is possible, output a patrol UI box. This is called from the
1045
	 * footer section of ordinary page views. If patrol is not possible or not
1046
	 * desired, does nothing.
1047
	 * Side effect: When the patrol link is build, this method will call
1048
	 * OutputPage::preventClickjacking() and load mediawiki.page.patrol.ajax.
1049
	 *
1050
	 * @return bool
1051
	 */
1052
	public function showPatrolFooter() {
1053
		global $wgUseNPPatrol, $wgUseRCPatrol, $wgUseFilePatrol, $wgEnableAPI, $wgEnableWriteAPI;
1054
1055
		$outputPage = $this->getContext()->getOutput();
1056
		$user = $this->getContext()->getUser();
1057
		$title = $this->getTitle();
1058
		$rc = false;
1059
1060
		if ( !$title->quickUserCan( 'patrol', $user )
1061
			|| !( $wgUseRCPatrol || $wgUseNPPatrol
1062
				|| ( $wgUseFilePatrol && $title->inNamespace( NS_FILE ) ) )
1063
		) {
1064
			// Patrolling is disabled or the user isn't allowed to
1065
			return false;
1066
		}
1067
1068
		if ( $this->mRevision
1069
			&& !RecentChange::isInRCLifespan( $this->mRevision->getTimestamp(), 21600 )
1070
		) {
1071
			// The current revision is already older than what could be in the RC table
1072
			// 6h tolerance because the RC might not be cleaned out regularly
1073
			return false;
1074
		}
1075
1076
		// Check for cached results
1077
		$key = wfMemcKey( 'unpatrollable-page', $title->getArticleID() );
1078
		$cache = ObjectCache::getMainWANInstance();
1079
		if ( $cache->get( $key ) ) {
1080
			return false;
1081
		}
1082
1083
		$dbr = wfGetDB( DB_REPLICA );
1084
		$oldestRevisionTimestamp = $dbr->selectField(
1085
			'revision',
1086
			'MIN( rev_timestamp )',
1087
			[ 'rev_page' => $title->getArticleID() ],
1088
			__METHOD__
1089
		);
1090
1091
		// New page patrol: Get the timestamp of the oldest revison which
1092
		// the revision table holds for the given page. Then we look
1093
		// whether it's within the RC lifespan and if it is, we try
1094
		// to get the recentchanges row belonging to that entry
1095
		// (with rc_new = 1).
1096
		$recentPageCreation = false;
1097
		if ( $oldestRevisionTimestamp
1098
			&& RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
1099
		) {
1100
			// 6h tolerance because the RC might not be cleaned out regularly
1101
			$recentPageCreation = true;
1102
			$rc = RecentChange::newFromConds(
1103
				[
1104
					'rc_new' => 1,
1105
					'rc_timestamp' => $oldestRevisionTimestamp,
1106
					'rc_namespace' => $title->getNamespace(),
1107
					'rc_cur_id' => $title->getArticleID()
1108
				],
1109
				__METHOD__
1110
			);
1111
			if ( $rc ) {
1112
				// Use generic patrol message for new pages
1113
				$markPatrolledMsg = wfMessage( 'markaspatrolledtext' );
1114
			}
1115
		}
1116
1117
		// File patrol: Get the timestamp of the latest upload for this page,
1118
		// check whether it is within the RC lifespan and if it is, we try
1119
		// to get the recentchanges row belonging to that entry
1120
		// (with rc_type = RC_LOG, rc_log_type = upload).
1121
		$recentFileUpload = false;
1122
		if ( ( !$rc || $rc->getAttribute( 'rc_patrolled' ) ) && $wgUseFilePatrol
1123
			&& $title->getNamespace() === NS_FILE ) {
1124
			// Retrieve timestamp of most recent upload
1125
			$newestUploadTimestamp = $dbr->selectField(
1126
				'image',
1127
				'MAX( img_timestamp )',
1128
				[ 'img_name' => $title->getDBkey() ],
1129
				__METHOD__
1130
			);
1131
			if ( $newestUploadTimestamp
1132
				&& RecentChange::isInRCLifespan( $newestUploadTimestamp, 21600 )
1133
			) {
1134
				// 6h tolerance because the RC might not be cleaned out regularly
1135
				$recentFileUpload = true;
1136
				$rc = RecentChange::newFromConds(
1137
					[
1138
						'rc_type' => RC_LOG,
1139
						'rc_log_type' => 'upload',
1140
						'rc_timestamp' => $newestUploadTimestamp,
1141
						'rc_namespace' => NS_FILE,
1142
						'rc_cur_id' => $title->getArticleID()
1143
					],
1144
					__METHOD__,
1145
					[ 'USE INDEX' => 'rc_timestamp' ]
1146
				);
1147
				if ( $rc ) {
1148
					// Use patrol message specific to files
1149
					$markPatrolledMsg = wfMessage( 'markaspatrolledtext-file' );
1150
				}
1151
			}
1152
		}
1153
1154
		if ( !$recentPageCreation && !$recentFileUpload ) {
1155
			// Page creation and latest upload (for files) is too old to be in RC
1156
1157
			// We definitely can't patrol so cache the information
1158
			// When a new file version is uploaded, the cache is cleared
1159
			$cache->set( $key, '1' );
1160
1161
			return false;
1162
		}
1163
1164
		if ( !$rc ) {
1165
			// Don't cache: This can be hit if the page gets accessed very fast after
1166
			// its creation / latest upload or in case we have high replica DB lag. In case
1167
			// the revision is too old, we will already return above.
1168
			return false;
1169
		}
1170
1171
		if ( $rc->getAttribute( 'rc_patrolled' ) ) {
1172
			// Patrolled RC entry around
1173
1174
			// Cache the information we gathered above in case we can't patrol
1175
			// Don't cache in case we can patrol as this could change
1176
			$cache->set( $key, '1' );
1177
1178
			return false;
1179
		}
1180
1181
		if ( $rc->getPerformer()->equals( $user ) ) {
1182
			// Don't show a patrol link for own creations/uploads. If the user could
1183
			// patrol them, they already would be patrolled
1184
			return false;
1185
		}
1186
1187
		$rcid = $rc->getAttribute( 'rc_id' );
1188
1189
		$token = $user->getEditToken( $rcid );
1190
1191
		$outputPage->preventClickjacking();
1192
		if ( $wgEnableAPI && $wgEnableWriteAPI && $user->isAllowed( 'writeapi' ) ) {
1193
			$outputPage->addModules( 'mediawiki.page.patrol.ajax' );
1194
		}
1195
1196
		$link = Linker::linkKnown(
1197
			$title,
1198
			$markPatrolledMsg->escaped(),
1199
			[],
1200
			[
1201
				'action' => 'markpatrolled',
1202
				'rcid' => $rcid,
1203
				'token' => $token,
1204
			]
1205
		);
1206
1207
		$outputPage->addHTML(
1208
			"<div class='patrollink' data-mw='interface'>" .
1209
				wfMessage( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
1210
			'</div>'
1211
		);
1212
1213
		return true;
1214
	}
1215
1216
	/**
1217
	 * Purge the cache used to check if it is worth showing the patrol footer
1218
	 * For example, it is done during re-uploads when file patrol is used.
1219
	 * @param int $articleID ID of the article to purge
1220
	 * @since 1.27
1221
	 */
1222
	public static function purgePatrolFooterCache( $articleID ) {
1223
		$cache = ObjectCache::getMainWANInstance();
1224
		$cache->delete( wfMemcKey( 'unpatrollable-page', $articleID ) );
1225
	}
1226
1227
	/**
1228
	 * Show the error text for a missing article. For articles in the MediaWiki
1229
	 * namespace, show the default message text. To be called from Article::view().
1230
	 */
1231
	public function showMissingArticle() {
1232
		global $wgSend404Code;
1233
1234
		$outputPage = $this->getContext()->getOutput();
1235
		// Whether the page is a root user page of an existing user (but not a subpage)
1236
		$validUserPage = false;
1237
1238
		$title = $this->getTitle();
1239
1240
		# Show info in user (talk) namespace. Does the user exist? Is he blocked?
1241
		if ( $title->getNamespace() == NS_USER
1242
			|| $title->getNamespace() == NS_USER_TALK
1243
		) {
1244
			$rootPart = explode( '/', $title->getText() )[0];
1245
			$user = User::newFromName( $rootPart, false /* allow IP users*/ );
1246
			$ip = User::isIP( $rootPart );
1247
			$block = Block::newFromTarget( $user, $user );
1248
1249
			if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
1250
				$outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
1251
					[ 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ] );
1252 View Code Duplication
			} elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) {
1253
				# Show log extract if the user is currently blocked
1254
				LogEventsList::showLogExtract(
1255
					$outputPage,
1256
					'block',
1257
					MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(),
1258
					'',
1259
					[
1260
						'lim' => 1,
1261
						'showIfEmpty' => false,
1262
						'msgKey' => [
1263
							'blocked-notice-logextract',
1264
							$user->getName() # Support GENDER in notice
1265
						]
1266
					]
1267
				);
1268
				$validUserPage = !$title->isSubpage();
1269
			} else {
1270
				$validUserPage = !$title->isSubpage();
1271
			}
1272
		}
1273
1274
		Hooks::run( 'ShowMissingArticle', [ $this ] );
1275
1276
		# Show delete and move logs if there were any such events.
1277
		# The logging query can DOS the site when bots/crawlers cause 404 floods,
1278
		# so be careful showing this. 404 pages must be cheap as they are hard to cache.
1279
		$cache = ObjectCache::getMainStashInstance();
1280
		$key = wfMemcKey( 'page-recent-delete', md5( $title->getPrefixedText() ) );
1281
		$loggedIn = $this->getContext()->getUser()->isLoggedIn();
1282
		if ( $loggedIn || $cache->get( $key ) ) {
1283
			$logTypes = [ 'delete', 'move' ];
1284
			$conds = [ "log_action != 'revision'" ];
1285
			// Give extensions a chance to hide their (unrelated) log entries
1286
			Hooks::run( 'Article::MissingArticleConditions', [ &$conds, $logTypes ] );
1287
			LogEventsList::showLogExtract(
1288
				$outputPage,
1289
				$logTypes,
1290
				$title,
1291
				'',
1292
				[
1293
					'lim' => 10,
1294
					'conds' => $conds,
1295
					'showIfEmpty' => false,
1296
					'msgKey' => [ $loggedIn
1297
						? 'moveddeleted-notice'
1298
						: 'moveddeleted-notice-recent'
1299
					]
1300
				]
1301
			);
1302
		}
1303
1304
		if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) {
1305
			// If there's no backing content, send a 404 Not Found
1306
			// for better machine handling of broken links.
1307
			$this->getContext()->getRequest()->response()->statusHeader( 404 );
1308
		}
1309
1310
		// Also apply the robot policy for nonexisting pages (even if a 404 was used for sanity)
1311
		$policy = $this->getRobotPolicy( 'view' );
1312
		$outputPage->setIndexPolicy( $policy['index'] );
1313
		$outputPage->setFollowPolicy( $policy['follow'] );
1314
1315
		$hookResult = Hooks::run( 'BeforeDisplayNoArticleText', [ $this ] );
1316
1317
		if ( !$hookResult ) {
1318
			return;
1319
		}
1320
1321
		# Show error message
1322
		$oldid = $this->getOldID();
1323
		if ( !$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText() ) {
1324
			$outputPage->addParserOutput( $this->getContentObject()->getParserOutput( $title ) );
1325
		} else {
1326
			if ( $oldid ) {
1327
				$text = wfMessage( 'missing-revision', $oldid )->plain();
1328
			} elseif ( $title->quickUserCan( 'create', $this->getContext()->getUser() )
1329
				&& $title->quickUserCan( 'edit', $this->getContext()->getUser() )
1330
			) {
1331
				$message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
1332
				$text = wfMessage( $message )->plain();
1333
			} else {
1334
				$text = wfMessage( 'noarticletext-nopermission' )->plain();
1335
			}
1336
1337
			$dir = $this->getContext()->getLanguage()->getDir();
1338
			$lang = $this->getContext()->getLanguage()->getCode();
1339
			$outputPage->addWikiText( Xml::openElement( 'div', [
1340
				'class' => "noarticletext mw-content-$dir",
1341
				'dir' => $dir,
1342
				'lang' => $lang,
1343
			] ) . "\n$text\n</div>" );
1344
		}
1345
	}
1346
1347
	/**
1348
	 * If the revision requested for view is deleted, check permissions.
1349
	 * Send either an error message or a warning header to the output.
1350
	 *
1351
	 * @return bool True if the view is allowed, false if not.
1352
	 */
1353
	public function showDeletedRevisionHeader() {
1354
		if ( !$this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
1355
			// Not deleted
1356
			return true;
1357
		}
1358
1359
		$outputPage = $this->getContext()->getOutput();
1360
		$user = $this->getContext()->getUser();
1361
		// If the user is not allowed to see it...
1362
		if ( !$this->mRevision->userCan( Revision::DELETED_TEXT, $user ) ) {
1363
			$outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1364
				'rev-deleted-text-permission' );
1365
1366
			return false;
1367
		// If the user needs to confirm that they want to see it...
1368
		} elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) {
1369
			# Give explanation and add a link to view the revision...
1370
			$oldid = intval( $this->getOldID() );
1371
			$link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
1372
			$msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
1373
				'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
1374
			$outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1375
				[ $msg, $link ] );
1376
1377
			return false;
1378
		// We are allowed to see...
1379
		} else {
1380
			$msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
1381
				'rev-suppressed-text-view' : 'rev-deleted-text-view';
1382
			$outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", $msg );
1383
1384
			return true;
1385
		}
1386
	}
1387
1388
	/**
1389
	 * Generate the navigation links when browsing through an article revisions
1390
	 * It shows the information as:
1391
	 *   Revision as of \<date\>; view current revision
1392
	 *   \<- Previous version | Next Version -\>
1393
	 *
1394
	 * @param int $oldid Revision ID of this article revision
1395
	 */
1396
	public function setOldSubtitle( $oldid = 0 ) {
1397
		if ( !Hooks::run( 'DisplayOldSubtitle', [ &$this, &$oldid ] ) ) {
1398
			return;
1399
		}
1400
1401
		$context = $this->getContext();
1402
		$unhide = $context->getRequest()->getInt( 'unhide' ) == 1;
1403
1404
		# Cascade unhide param in links for easy deletion browsing
1405
		$extraParams = [];
1406
		if ( $unhide ) {
1407
			$extraParams['unhide'] = 1;
1408
		}
1409
1410
		if ( $this->mRevision && $this->mRevision->getId() === $oldid ) {
1411
			$revision = $this->mRevision;
1412
		} else {
1413
			$revision = Revision::newFromId( $oldid );
1414
		}
1415
1416
		$timestamp = $revision->getTimestamp();
1417
1418
		$current = ( $oldid == $this->mPage->getLatest() );
1419
		$language = $context->getLanguage();
1420
		$user = $context->getUser();
1421
1422
		$td = $language->userTimeAndDate( $timestamp, $user );
1423
		$tddate = $language->userDate( $timestamp, $user );
1424
		$tdtime = $language->userTime( $timestamp, $user );
1425
1426
		# Show user links if allowed to see them. If hidden, then show them only if requested...
1427
		$userlinks = Linker::revUserTools( $revision, !$unhide );
1428
1429
		$infomsg = $current && !$context->msg( 'revision-info-current' )->isDisabled()
1430
			? 'revision-info-current'
1431
			: 'revision-info';
1432
1433
		$outputPage = $context->getOutput();
1434
		$revisionInfo = "<div id=\"mw-{$infomsg}\">" .
1435
			$context->msg( $infomsg, $td )
1436
				->rawParams( $userlinks )
1437
				->params( $revision->getId(), $tddate, $tdtime, $revision->getUserText() )
1438
				->rawParams( Linker::revComment( $revision, true, true ) )
1439
				->parse() .
1440
			"</div>";
1441
1442
		$lnk = $current
1443
			? $context->msg( 'currentrevisionlink' )->escaped()
1444
			: Linker::linkKnown(
1445
				$this->getTitle(),
1446
				$context->msg( 'currentrevisionlink' )->escaped(),
1447
				[],
1448
				$extraParams
1449
			);
1450
		$curdiff = $current
1451
			? $context->msg( 'diff' )->escaped()
1452
			: Linker::linkKnown(
1453
				$this->getTitle(),
1454
				$context->msg( 'diff' )->escaped(),
1455
				[],
1456
				[
1457
					'diff' => 'cur',
1458
					'oldid' => $oldid
1459
				] + $extraParams
1460
			);
1461
		$prev = $this->getTitle()->getPreviousRevisionID( $oldid );
1462
		$prevlink = $prev
1463
			? Linker::linkKnown(
1464
				$this->getTitle(),
1465
				$context->msg( 'previousrevision' )->escaped(),
1466
				[],
1467
				[
1468
					'direction' => 'prev',
1469
					'oldid' => $oldid
1470
				] + $extraParams
1471
			)
1472
			: $context->msg( 'previousrevision' )->escaped();
1473
		$prevdiff = $prev
1474
			? Linker::linkKnown(
1475
				$this->getTitle(),
1476
				$context->msg( 'diff' )->escaped(),
1477
				[],
1478
				[
1479
					'diff' => 'prev',
1480
					'oldid' => $oldid
1481
				] + $extraParams
1482
			)
1483
			: $context->msg( 'diff' )->escaped();
1484
		$nextlink = $current
1485
			? $context->msg( 'nextrevision' )->escaped()
1486
			: Linker::linkKnown(
1487
				$this->getTitle(),
1488
				$context->msg( 'nextrevision' )->escaped(),
1489
				[],
1490
				[
1491
					'direction' => 'next',
1492
					'oldid' => $oldid
1493
				] + $extraParams
1494
			);
1495
		$nextdiff = $current
1496
			? $context->msg( 'diff' )->escaped()
1497
			: Linker::linkKnown(
1498
				$this->getTitle(),
1499
				$context->msg( 'diff' )->escaped(),
1500
				[],
1501
				[
1502
					'diff' => 'next',
1503
					'oldid' => $oldid
1504
				] + $extraParams
1505
			);
1506
1507
		$cdel = Linker::getRevDeleteLink( $user, $revision, $this->getTitle() );
1508
		if ( $cdel !== '' ) {
1509
			$cdel .= ' ';
1510
		}
1511
1512
		// the outer div is need for styling the revision info and nav in MobileFrontend
1513
		$outputPage->addSubtitle( "<div class=\"mw-revision\">" . $revisionInfo .
1514
			"<div id=\"mw-revision-nav\">" . $cdel .
1515
			$context->msg( 'revision-nav' )->rawParams(
1516
				$prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1517
			)->escaped() . "</div></div>" );
1518
	}
1519
1520
	/**
1521
	 * Return the HTML for the top of a redirect page
1522
	 *
1523
	 * Chances are you should just be using the ParserOutput from
1524
	 * WikitextContent::getParserOutput instead of calling this for redirects.
1525
	 *
1526
	 * @param Title|array $target Destination(s) to redirect
1527
	 * @param bool $appendSubtitle [optional]
1528
	 * @param bool $forceKnown Should the image be shown as a bluelink regardless of existence?
1529
	 * @return string Containing HTML with redirect link
1530
	 */
1531
	public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
1532
		$lang = $this->getTitle()->getPageLanguage();
1533
		$out = $this->getContext()->getOutput();
1534
		if ( $appendSubtitle ) {
1535
			$out->addSubtitle( wfMessage( 'redirectpagesub' ) );
1536
		}
1537
		$out->addModuleStyles( 'mediawiki.action.view.redirectPage' );
1538
		return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
1539
	}
1540
1541
	/**
1542
	 * Return the HTML for the top of a redirect page
1543
	 *
1544
	 * Chances are you should just be using the ParserOutput from
1545
	 * WikitextContent::getParserOutput instead of calling this for redirects.
1546
	 *
1547
	 * @since 1.23
1548
	 * @param Language $lang
1549
	 * @param Title|array $target Destination(s) to redirect
1550
	 * @param bool $forceKnown Should the image be shown as a bluelink regardless of existence?
1551
	 * @return string Containing HTML with redirect link
1552
	 */
1553
	public static function getRedirectHeaderHtml( Language $lang, $target, $forceKnown = false ) {
1554
		if ( !is_array( $target ) ) {
1555
			$target = [ $target ];
1556
		}
1557
1558
		$html = '<ul class="redirectText">';
1559
		/** @var Title $title */
1560
		foreach ( $target as $title ) {
1561
			$html .= '<li>' . Linker::link(
1562
				$title,
1563
				htmlspecialchars( $title->getFullText() ),
1564
				[],
1565
				// Make sure wiki page redirects are not followed
1566
				$title->isRedirect() ? [ 'redirect' => 'no' ] : [],
1567
				( $forceKnown ? [ 'known', 'noclasses' ] : [] )
1568
			) . '</li>';
1569
		}
1570
		$html .= '</ul>';
1571
1572
		$redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->escaped();
1573
1574
		return '<div class="redirectMsg">' .
1575
			'<p>' . $redirectToText . '</p>' .
1576
			$html .
1577
			'</div>';
1578
	}
1579
1580
	/**
1581
	 * Adds help link with an icon via page indicators.
1582
	 * Link target can be overridden by a local message containing a wikilink:
1583
	 * the message key is: 'namespace-' + namespace number + '-helppage'.
1584
	 * @param string $to Target MediaWiki.org page title or encoded URL.
1585
	 * @param bool $overrideBaseUrl Whether $url is a full URL, to avoid MW.o.
1586
	 * @since 1.25
1587
	 */
1588 View Code Duplication
	public function addHelpLink( $to, $overrideBaseUrl = false ) {
1589
		$msg = wfMessage(
1590
			'namespace-' . $this->getTitle()->getNamespace() . '-helppage'
1591
		);
1592
1593
		$out = $this->getContext()->getOutput();
1594
		if ( !$msg->isDisabled() ) {
1595
			$helpUrl = Skin::makeUrl( $msg->plain() );
1596
			$out->addHelpLink( $helpUrl, true );
1597
		} else {
1598
			$out->addHelpLink( $to, $overrideBaseUrl );
1599
		}
1600
	}
1601
1602
	/**
1603
	 * Handle action=render
1604
	 */
1605
	public function render() {
1606
		$this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
1607
		$this->getContext()->getOutput()->setArticleBodyOnly( true );
1608
		$this->getContext()->getOutput()->enableSectionEditLinks( false );
1609
		$this->view();
1610
	}
1611
1612
	/**
1613
	 * action=protect handler
1614
	 */
1615
	public function protect() {
1616
		$form = new ProtectionForm( $this );
1617
		$form->execute();
1618
	}
1619
1620
	/**
1621
	 * action=unprotect handler (alias)
1622
	 */
1623
	public function unprotect() {
1624
		$this->protect();
1625
	}
1626
1627
	/**
1628
	 * UI entry point for page deletion
1629
	 */
1630
	public function delete() {
1631
		# This code desperately needs to be totally rewritten
1632
1633
		$title = $this->getTitle();
1634
		$context = $this->getContext();
1635
		$user = $context->getUser();
1636
		$request = $context->getRequest();
1637
1638
		# Check permissions
1639
		$permissionErrors = $title->getUserPermissionsErrors( 'delete', $user );
1640
		if ( count( $permissionErrors ) ) {
1641
			throw new PermissionsError( 'delete', $permissionErrors );
1642
		}
1643
1644
		# Read-only check...
1645
		if ( wfReadOnly() ) {
1646
			throw new ReadOnlyError;
1647
		}
1648
1649
		# Better double-check that it hasn't been deleted yet!
1650
		$this->mPage->loadPageData(
1651
			$request->wasPosted() ? WikiPage::READ_LATEST : WikiPage::READ_NORMAL
1652
		);
1653
		if ( !$this->mPage->exists() ) {
1654
			$deleteLogPage = new LogPage( 'delete' );
1655
			$outputPage = $context->getOutput();
1656
			$outputPage->setPageTitle( $context->msg( 'cannotdelete-title', $title->getPrefixedText() ) );
1657
			$outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
1658
					[ 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) ]
1659
				);
1660
			$outputPage->addHTML(
1661
				Xml::element( 'h2', null, $deleteLogPage->getName()->text() )
1662
			);
1663
			LogEventsList::showLogExtract(
1664
				$outputPage,
1665
				'delete',
1666
				$title
1667
			);
1668
1669
			return;
1670
		}
1671
1672
		$deleteReasonList = $request->getText( 'wpDeleteReasonList', 'other' );
1673
		$deleteReason = $request->getText( 'wpReason' );
1674
1675 View Code Duplication
		if ( $deleteReasonList == 'other' ) {
1676
			$reason = $deleteReason;
1677
		} elseif ( $deleteReason != '' ) {
1678
			// Entry from drop down menu + additional comment
1679
			$colonseparator = wfMessage( 'colon-separator' )->inContentLanguage()->text();
1680
			$reason = $deleteReasonList . $colonseparator . $deleteReason;
1681
		} else {
1682
			$reason = $deleteReasonList;
1683
		}
1684
1685
		if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'wpEditToken' ),
1686
			[ 'delete', $this->getTitle()->getPrefixedText() ] )
1687
		) {
1688
			# Flag to hide all contents of the archived revisions
1689
			$suppress = $request->getVal( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' );
1690
1691
			$this->doDelete( $reason, $suppress );
1692
1693
			WatchAction::doWatchOrUnwatch( $request->getCheck( 'wpWatch' ), $title, $user );
1694
1695
			return;
1696
		}
1697
1698
		// Generate deletion reason
1699
		$hasHistory = false;
1700
		if ( !$reason ) {
1701
			try {
1702
				$reason = $this->generateReason( $hasHistory );
1703
			} catch ( Exception $e ) {
1704
				# if a page is horribly broken, we still want to be able to
1705
				# delete it. So be lenient about errors here.
1706
				wfDebug( "Error while building auto delete summary: $e" );
1707
				$reason = '';
1708
			}
1709
		}
1710
1711
		// If the page has a history, insert a warning
1712
		if ( $hasHistory ) {
1713
			$title = $this->getTitle();
1714
1715
			// The following can use the real revision count as this is only being shown for users
1716
			// that can delete this page.
1717
			// This, as a side-effect, also makes sure that the following query isn't being run for
1718
			// pages with a larger history, unless the user has the 'bigdelete' right
1719
			// (and is about to delete this page).
1720
			$dbr = wfGetDB( DB_REPLICA );
1721
			$revisions = $edits = (int)$dbr->selectField(
1722
				'revision',
1723
				'COUNT(rev_page)',
1724
				[ 'rev_page' => $title->getArticleID() ],
1725
				__METHOD__
1726
			);
1727
1728
			// @todo FIXME: i18n issue/patchwork message
1729
			$context->getOutput()->addHTML(
1730
				'<strong class="mw-delete-warning-revisions">' .
1731
				$context->msg( 'historywarning' )->numParams( $revisions )->parse() .
1732
				$context->msg( 'word-separator' )->escaped() . Linker::linkKnown( $title,
1733
					$context->msg( 'history' )->escaped(),
1734
					[],
1735
					[ 'action' => 'history' ] ) .
1736
				'</strong>'
1737
			);
1738
1739
			if ( $title->isBigDeletion() ) {
1740
				global $wgDeleteRevisionsLimit;
1741
				$context->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
1742
					[
1743
						'delete-warning-toobig',
1744
						$context->getLanguage()->formatNum( $wgDeleteRevisionsLimit )
1745
					]
1746
				);
1747
			}
1748
		}
1749
1750
		$this->confirmDelete( $reason );
1751
	}
1752
1753
	/**
1754
	 * Output deletion confirmation dialog
1755
	 * @todo FIXME: Move to another file?
1756
	 * @param string $reason Prefilled reason
1757
	 */
1758
	public function confirmDelete( $reason ) {
1759
		wfDebug( "Article::confirmDelete\n" );
1760
1761
		$title = $this->getTitle();
1762
		$ctx = $this->getContext();
1763
		$outputPage = $ctx->getOutput();
1764
		$useMediaWikiUIEverywhere = $ctx->getConfig()->get( 'UseMediaWikiUIEverywhere' );
1765
		$outputPage->setPageTitle( wfMessage( 'delete-confirm', $title->getPrefixedText() ) );
1766
		$outputPage->addBacklinkSubtitle( $title );
1767
		$outputPage->setRobotPolicy( 'noindex,nofollow' );
1768
		$backlinkCache = $title->getBacklinkCache();
1769
		if ( $backlinkCache->hasLinks( 'pagelinks' ) || $backlinkCache->hasLinks( 'templatelinks' ) ) {
1770
			$outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1771
				'deleting-backlinks-warning' );
1772
		}
1773
		$outputPage->addWikiMsg( 'confirmdeletetext' );
1774
1775
		Hooks::run( 'ArticleConfirmDelete', [ $this, $outputPage, &$reason ] );
1776
1777
		$user = $this->getContext()->getUser();
1778
1779
		if ( $user->isAllowed( 'suppressrevision' ) ) {
1780
			$suppress = Html::openElement( 'div', [ 'id' => 'wpDeleteSuppressRow' ] ) .
1781
				Xml::checkLabel( wfMessage( 'revdelete-suppress' )->text(),
1782
					'wpSuppress', 'wpSuppress', false, [ 'tabindex' => '4' ] ) .
1783
				Html::closeElement( 'div' );
1784
		} else {
1785
			$suppress = '';
1786
		}
1787
		$checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $title );
1788
1789
		$form = Html::openElement( 'form', [ 'method' => 'post',
1790
			'action' => $title->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ] ) .
1791
			Html::openElement( 'fieldset', [ 'id' => 'mw-delete-table' ] ) .
1792
			Html::element( 'legend', null, wfMessage( 'delete-legend' )->text() ) .
1793
			Html::openElement( 'div', [ 'id' => 'mw-deleteconfirm-table' ] ) .
1794
			Html::openElement( 'div', [ 'id' => 'wpDeleteReasonListRow' ] ) .
1795
			Html::label( wfMessage( 'deletecomment' )->text(), 'wpDeleteReasonList' ) .
1796
			'&nbsp;' .
1797
			Xml::listDropDown(
1798
				'wpDeleteReasonList',
1799
				wfMessage( 'deletereason-dropdown' )->inContentLanguage()->text(),
1800
				wfMessage( 'deletereasonotherlist' )->inContentLanguage()->text(),
1801
				'',
1802
				'wpReasonDropDown',
1803
				1
1804
			) .
1805
			Html::closeElement( 'div' ) .
1806
			Html::openElement( 'div', [ 'id' => 'wpDeleteReasonRow' ] ) .
1807
			Html::label( wfMessage( 'deleteotherreason' )->text(), 'wpReason' ) .
1808
			'&nbsp;' .
1809
			Html::input( 'wpReason', $reason, 'text', [
1810
				'size' => '60',
1811
				'maxlength' => '255',
1812
				'tabindex' => '2',
1813
				'id' => 'wpReason',
1814
				'class' => 'mw-ui-input-inline',
1815
				'autofocus'
1816
			] ) .
1817
			Html::closeElement( 'div' );
1818
1819
		# Disallow watching if user is not logged in
1820 View Code Duplication
		if ( $user->isLoggedIn() ) {
1821
			$form .=
1822
					Xml::checkLabel( wfMessage( 'watchthis' )->text(),
1823
						'wpWatch', 'wpWatch', $checkWatch, [ 'tabindex' => '3' ] );
1824
		}
1825
1826
		$form .=
1827
				Html::openElement( 'div' ) .
1828
				$suppress .
1829
					Xml::submitButton( wfMessage( 'deletepage' )->text(),
1830
						[
1831
							'name' => 'wpConfirmB',
1832
							'id' => 'wpConfirmB',
1833
							'tabindex' => '5',
1834
							'class' => $useMediaWikiUIEverywhere ? 'mw-ui-button mw-ui-destructive' : '',
1835
						]
1836
					) .
1837
				Html::closeElement( 'div' ) .
1838
			Html::closeElement( 'div' ) .
1839
			Xml::closeElement( 'fieldset' ) .
1840
			Html::hidden(
1841
				'wpEditToken',
1842
				$user->getEditToken( [ 'delete', $title->getPrefixedText() ] )
1843
			) .
1844
			Xml::closeElement( 'form' );
1845
1846 View Code Duplication
			if ( $user->isAllowed( 'editinterface' ) ) {
1847
				$link = Linker::linkKnown(
1848
					$ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->getTitle(),
1849
					wfMessage( 'delete-edit-reasonlist' )->escaped(),
1850
					[],
1851
					[ 'action' => 'edit' ]
1852
				);
1853
				$form .= '<p class="mw-delete-editreasons">' . $link . '</p>';
1854
			}
1855
1856
		$outputPage->addHTML( $form );
1857
1858
		$deleteLogPage = new LogPage( 'delete' );
1859
		$outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
1860
		LogEventsList::showLogExtract( $outputPage, 'delete', $title );
1861
	}
1862
1863
	/**
1864
	 * Perform a deletion and output success or failure messages
1865
	 * @param string $reason
1866
	 * @param bool $suppress
1867
	 */
1868
	public function doDelete( $reason, $suppress = false ) {
1869
		$error = '';
1870
		$context = $this->getContext();
1871
		$outputPage = $context->getOutput();
1872
		$user = $context->getUser();
1873
		$status = $this->mPage->doDeleteArticleReal( $reason, $suppress, 0, true, $error, $user );
1874
1875
		if ( $status->isGood() ) {
1876
			$deleted = $this->getTitle()->getPrefixedText();
1877
1878
			$outputPage->setPageTitle( wfMessage( 'actioncomplete' ) );
1879
			$outputPage->setRobotPolicy( 'noindex,nofollow' );
1880
1881
			$loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]';
1882
1883
			$outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
1884
1885
			Hooks::run( 'ArticleDeleteAfterSuccess', [ $this->getTitle(), $outputPage ] );
1886
1887
			$outputPage->returnToMain( false );
1888
		} else {
1889
			$outputPage->setPageTitle(
1890
				wfMessage( 'cannotdelete-title',
1891
					$this->getTitle()->getPrefixedText() )
1892
			);
1893
1894
			if ( $error == '' ) {
1895
				$outputPage->addWikiText(
1896
					"<div class=\"error mw-error-cannotdelete\">\n" . $status->getWikiText() . "\n</div>"
1897
				);
1898
				$deleteLogPage = new LogPage( 'delete' );
1899
				$outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
1900
1901
				LogEventsList::showLogExtract(
1902
					$outputPage,
1903
					'delete',
1904
					$this->getTitle()
1905
				);
1906
			} else {
1907
				$outputPage->addHTML( $error );
1908
			}
1909
		}
1910
	}
1911
1912
	/* Caching functions */
1913
1914
	/**
1915
	 * checkLastModified returns true if it has taken care of all
1916
	 * output to the client that is necessary for this request.
1917
	 * (that is, it has sent a cached version of the page)
1918
	 *
1919
	 * @return bool True if cached version send, false otherwise
1920
	 */
1921
	protected function tryFileCache() {
1922
		static $called = false;
1923
1924
		if ( $called ) {
1925
			wfDebug( "Article::tryFileCache(): called twice!?\n" );
1926
			return false;
1927
		}
1928
1929
		$called = true;
1930
		if ( $this->isFileCacheable() ) {
1931
			$cache = new HTMLFileCache( $this->getTitle(), 'view' );
1932
			if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
1933
				wfDebug( "Article::tryFileCache(): about to load file\n" );
1934
				$cache->loadFromFileCache( $this->getContext() );
1935
				return true;
1936
			} else {
1937
				wfDebug( "Article::tryFileCache(): starting buffer\n" );
1938
				ob_start( [ &$cache, 'saveToFileCache' ] );
1939
			}
1940
		} else {
1941
			wfDebug( "Article::tryFileCache(): not cacheable\n" );
1942
		}
1943
1944
		return false;
1945
	}
1946
1947
	/**
1948
	 * Check if the page can be cached
1949
	 * @param integer $mode One of the HTMLFileCache::MODE_* constants (since 1.28)
1950
	 * @return bool
1951
	 */
1952
	public function isFileCacheable( $mode = HTMLFileCache::MODE_NORMAL ) {
1953
		$cacheable = false;
1954
1955
		if ( HTMLFileCache::useFileCache( $this->getContext(), $mode ) ) {
1956
			$cacheable = $this->mPage->getId()
1957
				&& !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
1958
			// Extension may have reason to disable file caching on some pages.
1959
			if ( $cacheable ) {
1960
				$cacheable = Hooks::run( 'IsFileCacheable', [ &$this ] );
1961
			}
1962
		}
1963
1964
		return $cacheable;
1965
	}
1966
1967
	/**#@-*/
1968
1969
	/**
1970
	 * Lightweight method to get the parser output for a page, checking the parser cache
1971
	 * and so on. Doesn't consider most of the stuff that WikiPage::view is forced to
1972
	 * consider, so it's not appropriate to use there.
1973
	 *
1974
	 * @since 1.16 (r52326) for LiquidThreads
1975
	 *
1976
	 * @param int|null $oldid Revision ID or null
1977
	 * @param User $user The relevant user
1978
	 * @return ParserOutput|bool ParserOutput or false if the given revision ID is not found
1979
	 */
1980
	public function getParserOutput( $oldid = null, User $user = null ) {
1981
		// XXX: bypasses mParserOptions and thus setParserOptions()
1982
1983
		if ( $user === null ) {
1984
			$parserOptions = $this->getParserOptions();
1985
		} else {
1986
			$parserOptions = $this->mPage->makeParserOptions( $user );
1987
		}
1988
1989
		return $this->mPage->getParserOutput( $parserOptions, $oldid );
1990
	}
1991
1992
	/**
1993
	 * Override the ParserOptions used to render the primary article wikitext.
1994
	 *
1995
	 * @param ParserOptions $options
1996
	 * @throws MWException If the parser options where already initialized.
1997
	 */
1998
	public function setParserOptions( ParserOptions $options ) {
1999
		if ( $this->mParserOptions ) {
2000
			throw new MWException( "can't change parser options after they have already been set" );
2001
		}
2002
2003
		// clone, so if $options is modified later, it doesn't confuse the parser cache.
2004
		$this->mParserOptions = clone $options;
2005
	}
2006
2007
	/**
2008
	 * Get parser options suitable for rendering the primary article wikitext
2009
	 * @return ParserOptions
2010
	 */
2011
	public function getParserOptions() {
2012
		if ( !$this->mParserOptions ) {
2013
			$this->mParserOptions = $this->mPage->makeParserOptions( $this->getContext() );
2014
		}
2015
		// Clone to allow modifications of the return value without affecting cache
2016
		return clone $this->mParserOptions;
2017
	}
2018
2019
	/**
2020
	 * Sets the context this Article is executed in
2021
	 *
2022
	 * @param IContextSource $context
2023
	 * @since 1.18
2024
	 */
2025
	public function setContext( $context ) {
2026
		$this->mContext = $context;
2027
	}
2028
2029
	/**
2030
	 * Gets the context this Article is executed in
2031
	 *
2032
	 * @return IContextSource
2033
	 * @since 1.18
2034
	 */
2035 View Code Duplication
	public function getContext() {
2036
		if ( $this->mContext instanceof IContextSource ) {
2037
			return $this->mContext;
2038
		} else {
2039
			wfDebug( __METHOD__ . " called and \$mContext is null. " .
2040
				"Return RequestContext::getMain(); for sanity\n" );
2041
			return RequestContext::getMain();
2042
		}
2043
	}
2044
2045
	/**
2046
	 * Use PHP's magic __get handler to handle accessing of
2047
	 * raw WikiPage fields for backwards compatibility.
2048
	 *
2049
	 * @param string $fname Field name
2050
	 * @return mixed
2051
	 */
2052
	public function __get( $fname ) {
2053
		if ( property_exists( $this->mPage, $fname ) ) {
2054
			# wfWarn( "Access to raw $fname field " . __CLASS__ );
2055
			return $this->mPage->$fname;
2056
		}
2057
		trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
2058
	}
2059
2060
	/**
2061
	 * Use PHP's magic __set handler to handle setting of
2062
	 * raw WikiPage fields for backwards compatibility.
2063
	 *
2064
	 * @param string $fname Field name
2065
	 * @param mixed $fvalue New value
2066
	 */
2067
	public function __set( $fname, $fvalue ) {
2068
		if ( property_exists( $this->mPage, $fname ) ) {
2069
			# wfWarn( "Access to raw $fname field of " . __CLASS__ );
2070
			$this->mPage->$fname = $fvalue;
2071
		// Note: extensions may want to toss on new fields
2072
		} elseif ( !in_array( $fname, [ 'mContext', 'mPage' ] ) ) {
2073
			$this->mPage->$fname = $fvalue;
2074
		} else {
2075
			trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
2076
		}
2077
	}
2078
2079
	/**
2080
	 * Call to WikiPage function for backwards compatibility.
2081
	 * @see WikiPage::checkFlags
2082
	 */
2083
	public function checkFlags( $flags ) {
2084
		return $this->mPage->checkFlags( $flags );
2085
	}
2086
2087
	/**
2088
	 * Call to WikiPage function for backwards compatibility.
2089
	 * @see WikiPage::checkTouched
2090
	 */
2091
	public function checkTouched() {
2092
		return $this->mPage->checkTouched();
2093
	}
2094
2095
	/**
2096
	 * Call to WikiPage function for backwards compatibility.
2097
	 * @see WikiPage::clearPreparedEdit
2098
	 */
2099
	public function clearPreparedEdit() {
2100
		$this->mPage->clearPreparedEdit();
2101
	}
2102
2103
	/**
2104
	 * Call to WikiPage function for backwards compatibility.
2105
	 * @see WikiPage::doDeleteArticleReal
2106
	 */
2107
	public function doDeleteArticleReal(
2108
		$reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', User $user = null,
2109
		$tags = []
2110
	) {
2111
		return $this->mPage->doDeleteArticleReal(
2112
			$reason, $suppress, $u1, $u2, $error, $user, $tags
2113
		);
2114
	}
2115
2116
	/**
2117
	 * Call to WikiPage function for backwards compatibility.
2118
	 * @see WikiPage::doDeleteUpdates
2119
	 */
2120
	public function doDeleteUpdates( $id, Content $content = null ) {
2121
		return $this->mPage->doDeleteUpdates( $id, $content );
2122
	}
2123
2124
	/**
2125
	 * Call to WikiPage function for backwards compatibility.
2126
	 * @see WikiPage::doEdit
2127
	 *
2128
	 * @deprecated since 1.21: use doEditContent() instead.
2129
	 */
2130
	public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
2131
		wfDeprecated( __METHOD__, '1.21' );
2132
		return $this->mPage->doEdit( $text, $summary, $flags, $baseRevId, $user );
2133
	}
2134
2135
	/**
2136
	 * Call to WikiPage function for backwards compatibility.
2137
	 * @see WikiPage::doEditContent
2138
	 */
2139
	public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
2140
		User $user = null, $serialFormat = null
2141
	) {
2142
		return $this->mPage->doEditContent( $content, $summary, $flags, $baseRevId,
2143
			$user, $serialFormat
2144
		);
2145
	}
2146
2147
	/**
2148
	 * Call to WikiPage function for backwards compatibility.
2149
	 * @see WikiPage::doEditUpdates
2150
	 */
2151
	public function doEditUpdates( Revision $revision, User $user, array $options = [] ) {
2152
		return $this->mPage->doEditUpdates( $revision, $user, $options );
2153
	}
2154
2155
	/**
2156
	 * Call to WikiPage function for backwards compatibility.
2157
	 * @see WikiPage::doPurge
2158
	 */
2159
	public function doPurge( $flags = WikiPage::PURGE_ALL ) {
2160
		return $this->mPage->doPurge( $flags );
2161
	}
2162
2163
	/**
2164
	 * Call to WikiPage function for backwards compatibility.
2165
	 * @see WikiPage::getLastPurgeTimestamp
2166
	 */
2167
	public function getLastPurgeTimestamp() {
2168
		return $this->mPage->getLastPurgeTimestamp();
2169
	}
2170
2171
	/**
2172
	 * Call to WikiPage function for backwards compatibility.
2173
	 * @see WikiPage::doViewUpdates
2174
	 */
2175
	public function doViewUpdates( User $user, $oldid = 0 ) {
2176
		$this->mPage->doViewUpdates( $user, $oldid );
2177
	}
2178
2179
	/**
2180
	 * Call to WikiPage function for backwards compatibility.
2181
	 * @see WikiPage::exists
2182
	 */
2183
	public function exists() {
2184
		return $this->mPage->exists();
2185
	}
2186
2187
	/**
2188
	 * Call to WikiPage function for backwards compatibility.
2189
	 * @see WikiPage::followRedirect
2190
	 */
2191
	public function followRedirect() {
2192
		return $this->mPage->followRedirect();
2193
	}
2194
2195
	/**
2196
	 * Call to WikiPage function for backwards compatibility.
2197
	 * @see ContentHandler::getActionOverrides
2198
	 */
2199
	public function getActionOverrides() {
2200
		return $this->mPage->getActionOverrides();
2201
	}
2202
2203
	/**
2204
	 * Call to WikiPage function for backwards compatibility.
2205
	 * @see WikiPage::getAutoDeleteReason
2206
	 */
2207
	public function getAutoDeleteReason( &$hasHistory ) {
2208
		return $this->mPage->getAutoDeleteReason( $hasHistory );
2209
	}
2210
2211
	/**
2212
	 * Call to WikiPage function for backwards compatibility.
2213
	 * @see WikiPage::getCategories
2214
	 */
2215
	public function getCategories() {
2216
		return $this->mPage->getCategories();
2217
	}
2218
2219
	/**
2220
	 * Call to WikiPage function for backwards compatibility.
2221
	 * @see WikiPage::getComment
2222
	 */
2223
	public function getComment( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2224
		return $this->mPage->getComment( $audience, $user );
2225
	}
2226
2227
	/**
2228
	 * Call to WikiPage function for backwards compatibility.
2229
	 * @see WikiPage::getContentHandler
2230
	 */
2231
	public function getContentHandler() {
2232
		return $this->mPage->getContentHandler();
2233
	}
2234
2235
	/**
2236
	 * Call to WikiPage function for backwards compatibility.
2237
	 * @see WikiPage::getContentModel
2238
	 */
2239
	public function getContentModel() {
2240
		return $this->mPage->getContentModel();
2241
	}
2242
2243
	/**
2244
	 * Call to WikiPage function for backwards compatibility.
2245
	 * @see WikiPage::getContributors
2246
	 */
2247
	public function getContributors() {
2248
		return $this->mPage->getContributors();
2249
	}
2250
2251
	/**
2252
	 * Call to WikiPage function for backwards compatibility.
2253
	 * @see WikiPage::getCreator
2254
	 */
2255
	public function getCreator( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2256
		return $this->mPage->getCreator( $audience, $user );
2257
	}
2258
2259
	/**
2260
	 * Call to WikiPage function for backwards compatibility.
2261
	 * @see WikiPage::getDeletionUpdates
2262
	 */
2263
	public function getDeletionUpdates( Content $content = null ) {
2264
		return $this->mPage->getDeletionUpdates( $content );
2265
	}
2266
2267
	/**
2268
	 * Call to WikiPage function for backwards compatibility.
2269
	 * @see WikiPage::getHiddenCategories
2270
	 */
2271
	public function getHiddenCategories() {
2272
		return $this->mPage->getHiddenCategories();
2273
	}
2274
2275
	/**
2276
	 * Call to WikiPage function for backwards compatibility.
2277
	 * @see WikiPage::getId
2278
	 */
2279
	public function getId() {
2280
		return $this->mPage->getId();
2281
	}
2282
2283
	/**
2284
	 * Call to WikiPage function for backwards compatibility.
2285
	 * @see WikiPage::getLatest
2286
	 */
2287
	public function getLatest() {
2288
		return $this->mPage->getLatest();
2289
	}
2290
2291
	/**
2292
	 * Call to WikiPage function for backwards compatibility.
2293
	 * @see WikiPage::getLinksTimestamp
2294
	 */
2295
	public function getLinksTimestamp() {
2296
		return $this->mPage->getLinksTimestamp();
2297
	}
2298
2299
	/**
2300
	 * Call to WikiPage function for backwards compatibility.
2301
	 * @see WikiPage::getMinorEdit
2302
	 */
2303
	public function getMinorEdit() {
2304
		return $this->mPage->getMinorEdit();
2305
	}
2306
2307
	/**
2308
	 * Call to WikiPage function for backwards compatibility.
2309
	 * @see WikiPage::getOldestRevision
2310
	 */
2311
	public function getOldestRevision() {
2312
		return $this->mPage->getOldestRevision();
2313
	}
2314
2315
	/**
2316
	 * Call to WikiPage function for backwards compatibility.
2317
	 * @see WikiPage::getRedirectTarget
2318
	 */
2319
	public function getRedirectTarget() {
2320
		return $this->mPage->getRedirectTarget();
2321
	}
2322
2323
	/**
2324
	 * Call to WikiPage function for backwards compatibility.
2325
	 * @see WikiPage::getRedirectURL
2326
	 */
2327
	public function getRedirectURL( $rt ) {
2328
		return $this->mPage->getRedirectURL( $rt );
2329
	}
2330
2331
	/**
2332
	 * Call to WikiPage function for backwards compatibility.
2333
	 * @see WikiPage::getRevision
2334
	 */
2335
	public function getRevision() {
2336
		return $this->mPage->getRevision();
2337
	}
2338
2339
	/**
2340
	 * Call to WikiPage function for backwards compatibility.
2341
	 * @see WikiPage::getText
2342
	 * @deprecated since 1.21 use WikiPage::getContent() instead
2343
	 */
2344
	public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2345
		wfDeprecated( __METHOD__, '1.21' );
2346
		return $this->mPage->getText( $audience, $user );
2347
	}
2348
2349
	/**
2350
	 * Call to WikiPage function for backwards compatibility.
2351
	 * @see WikiPage::getTimestamp
2352
	 */
2353
	public function getTimestamp() {
2354
		return $this->mPage->getTimestamp();
2355
	}
2356
2357
	/**
2358
	 * Call to WikiPage function for backwards compatibility.
2359
	 * @see WikiPage::getTouched
2360
	 */
2361
	public function getTouched() {
2362
		return $this->mPage->getTouched();
2363
	}
2364
2365
	/**
2366
	 * Call to WikiPage function for backwards compatibility.
2367
	 * @see WikiPage::getUndoContent
2368
	 */
2369
	public function getUndoContent( Revision $undo, Revision $undoafter = null ) {
2370
		return $this->mPage->getUndoContent( $undo, $undoafter );
2371
	}
2372
2373
	/**
2374
	 * Call to WikiPage function for backwards compatibility.
2375
	 * @see WikiPage::getUser
2376
	 */
2377
	public function getUser( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2378
		return $this->mPage->getUser( $audience, $user );
2379
	}
2380
2381
	/**
2382
	 * Call to WikiPage function for backwards compatibility.
2383
	 * @see WikiPage::getUserText
2384
	 */
2385
	public function getUserText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2386
		return $this->mPage->getUserText( $audience, $user );
2387
	}
2388
2389
	/**
2390
	 * Call to WikiPage function for backwards compatibility.
2391
	 * @see WikiPage::hasViewableContent
2392
	 */
2393
	public function hasViewableContent() {
2394
		return $this->mPage->hasViewableContent();
2395
	}
2396
2397
	/**
2398
	 * Call to WikiPage function for backwards compatibility.
2399
	 * @see WikiPage::insertOn
2400
	 */
2401
	public function insertOn( $dbw, $pageId = null ) {
2402
		return $this->mPage->insertOn( $dbw, $pageId );
2403
	}
2404
2405
	/**
2406
	 * Call to WikiPage function for backwards compatibility.
2407
	 * @see WikiPage::insertProtectNullRevision
2408
	 */
2409
	public function insertProtectNullRevision( $revCommentMsg, array $limit,
2410
		array $expiry, $cascade, $reason, $user = null
2411
	) {
2412
		return $this->mPage->insertProtectNullRevision( $revCommentMsg, $limit,
2413
			$expiry, $cascade, $reason, $user
2414
		);
2415
	}
2416
2417
	/**
2418
	 * Call to WikiPage function for backwards compatibility.
2419
	 * @see WikiPage::insertRedirect
2420
	 */
2421
	public function insertRedirect() {
2422
		return $this->mPage->insertRedirect();
2423
	}
2424
2425
	/**
2426
	 * Call to WikiPage function for backwards compatibility.
2427
	 * @see WikiPage::insertRedirectEntry
2428
	 */
2429
	public function insertRedirectEntry( Title $rt, $oldLatest = null ) {
2430
		return $this->mPage->insertRedirectEntry( $rt, $oldLatest );
2431
	}
2432
2433
	/**
2434
	 * Call to WikiPage function for backwards compatibility.
2435
	 * @see WikiPage::isCountable
2436
	 */
2437
	public function isCountable( $editInfo = false ) {
2438
		return $this->mPage->isCountable( $editInfo );
2439
	}
2440
2441
	/**
2442
	 * Call to WikiPage function for backwards compatibility.
2443
	 * @see WikiPage::isRedirect
2444
	 */
2445
	public function isRedirect() {
2446
		return $this->mPage->isRedirect();
2447
	}
2448
2449
	/**
2450
	 * Call to WikiPage function for backwards compatibility.
2451
	 * @see WikiPage::loadFromRow
2452
	 */
2453
	public function loadFromRow( $data, $from ) {
2454
		return $this->mPage->loadFromRow( $data, $from );
2455
	}
2456
2457
	/**
2458
	 * Call to WikiPage function for backwards compatibility.
2459
	 * @see WikiPage::loadPageData
2460
	 */
2461
	public function loadPageData( $from = 'fromdb' ) {
2462
		$this->mPage->loadPageData( $from );
2463
	}
2464
2465
	/**
2466
	 * Call to WikiPage function for backwards compatibility.
2467
	 * @see WikiPage::lockAndGetLatest
2468
	 */
2469
	public function lockAndGetLatest() {
2470
		return $this->mPage->lockAndGetLatest();
2471
	}
2472
2473
	/**
2474
	 * Call to WikiPage function for backwards compatibility.
2475
	 * @see WikiPage::makeParserOptions
2476
	 */
2477
	public function makeParserOptions( $context ) {
2478
		return $this->mPage->makeParserOptions( $context );
2479
	}
2480
2481
	/**
2482
	 * Call to WikiPage function for backwards compatibility.
2483
	 * @see WikiPage::pageDataFromId
2484
	 */
2485
	public function pageDataFromId( $dbr, $id, $options = [] ) {
2486
		return $this->mPage->pageDataFromId( $dbr, $id, $options );
2487
	}
2488
2489
	/**
2490
	 * Call to WikiPage function for backwards compatibility.
2491
	 * @see WikiPage::pageDataFromTitle
2492
	 */
2493
	public function pageDataFromTitle( $dbr, $title, $options = [] ) {
2494
		return $this->mPage->pageDataFromTitle( $dbr, $title, $options );
2495
	}
2496
2497
	/**
2498
	 * Call to WikiPage function for backwards compatibility.
2499
	 * @see WikiPage::prepareContentForEdit
2500
	 */
2501
	public function prepareContentForEdit(
2502
		Content $content, $revision = null, User $user = null,
2503
		$serialFormat = null, $useCache = true
2504
	) {
2505
		return $this->mPage->prepareContentForEdit(
2506
			$content, $revision, $user,
2507
			$serialFormat, $useCache
2508
		);
2509
	}
2510
2511
	/**
2512
	 * Call to WikiPage function for backwards compatibility.
2513
	 * @deprecated since 1.21, use prepareContentForEdit
2514
	 * @see WikiPage::prepareTextForEdit
2515
	 */
2516
	public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
2517
		return $this->mPage->prepareTextForEdit( $text, $revid, $user );
2518
	}
2519
2520
	/**
2521
	 * Call to WikiPage function for backwards compatibility.
2522
	 * @see WikiPage::protectDescription
2523
	 */
2524
	public function protectDescription( array $limit, array $expiry ) {
2525
		return $this->mPage->protectDescription( $limit, $expiry );
2526
	}
2527
2528
	/**
2529
	 * Call to WikiPage function for backwards compatibility.
2530
	 * @see WikiPage::protectDescriptionLog
2531
	 */
2532
	public function protectDescriptionLog( array $limit, array $expiry ) {
2533
		return $this->mPage->protectDescriptionLog( $limit, $expiry );
2534
	}
2535
2536
	/**
2537
	 * Call to WikiPage function for backwards compatibility.
2538
	 * @see WikiPage::replaceSectionAtRev
2539
	 */
2540
	public function replaceSectionAtRev( $sectionId, Content $sectionContent,
2541
		$sectionTitle = '', $baseRevId = null
2542
	) {
2543
		return $this->mPage->replaceSectionAtRev( $sectionId, $sectionContent,
2544
			$sectionTitle, $baseRevId
2545
		);
2546
	}
2547
2548
	/**
2549
	 * Call to WikiPage function for backwards compatibility.
2550
	 * @see WikiPage::replaceSectionContent
2551
	 */
2552
	public function replaceSectionContent(
2553
		$sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null
2554
	) {
2555
		return $this->mPage->replaceSectionContent(
2556
			$sectionId, $sectionContent, $sectionTitle, $edittime
2557
		);
2558
	}
2559
2560
	/**
2561
	 * Call to WikiPage function for backwards compatibility.
2562
	 * @see WikiPage::setTimestamp
2563
	 */
2564
	public function setTimestamp( $ts ) {
2565
		return $this->mPage->setTimestamp( $ts );
2566
	}
2567
2568
	/**
2569
	 * Call to WikiPage function for backwards compatibility.
2570
	 * @see WikiPage::shouldCheckParserCache
2571
	 */
2572
	public function shouldCheckParserCache( ParserOptions $parserOptions, $oldId ) {
2573
		return $this->mPage->shouldCheckParserCache( $parserOptions, $oldId );
2574
	}
2575
2576
	/**
2577
	 * Call to WikiPage function for backwards compatibility.
2578
	 * @see WikiPage::supportsSections
2579
	 */
2580
	public function supportsSections() {
2581
		return $this->mPage->supportsSections();
2582
	}
2583
2584
	/**
2585
	 * Call to WikiPage function for backwards compatibility.
2586
	 * @see WikiPage::triggerOpportunisticLinksUpdate
2587
	 */
2588
	public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) {
2589
		return $this->mPage->triggerOpportunisticLinksUpdate( $parserOutput );
2590
	}
2591
2592
	/**
2593
	 * Call to WikiPage function for backwards compatibility.
2594
	 * @see WikiPage::updateCategoryCounts
2595
	 */
2596
	public function updateCategoryCounts( array $added, array $deleted, $id = 0 ) {
2597
		return $this->mPage->updateCategoryCounts( $added, $deleted, $id );
2598
	}
2599
2600
	/**
2601
	 * Call to WikiPage function for backwards compatibility.
2602
	 * @see WikiPage::updateIfNewerOn
2603
	 */
2604
	public function updateIfNewerOn( $dbw, $revision ) {
2605
		return $this->mPage->updateIfNewerOn( $dbw, $revision );
2606
	}
2607
2608
	/**
2609
	 * Call to WikiPage function for backwards compatibility.
2610
	 * @see WikiPage::updateRedirectOn
2611
	 */
2612
	public function updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null ) {
2613
		return $this->mPage->updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null );
2614
	}
2615
2616
	/**
2617
	 * Call to WikiPage function for backwards compatibility.
2618
	 * @see WikiPage::updateRevisionOn
2619
	 */
2620
	public function updateRevisionOn( $dbw, $revision, $lastRevision = null,
2621
		$lastRevIsRedirect = null
2622
	) {
2623
		return $this->mPage->updateRevisionOn( $dbw, $revision, $lastRevision,
2624
			$lastRevIsRedirect
2625
		);
2626
	}
2627
2628
	/**
2629
	 * @param array $limit
2630
	 * @param array $expiry
2631
	 * @param bool $cascade
2632
	 * @param string $reason
2633
	 * @param User $user
2634
	 * @return Status
2635
	 */
2636
	public function doUpdateRestrictions( array $limit, array $expiry, &$cascade,
2637
		$reason, User $user
2638
	) {
2639
		return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user );
2640
	}
2641
2642
	/**
2643
	 * @param array $limit
2644
	 * @param string $reason
2645
	 * @param int $cascade
2646
	 * @param array $expiry
2647
	 * @return bool
2648
	 */
2649
	public function updateRestrictions( $limit = [], $reason = '',
2650
		&$cascade = 0, $expiry = []
2651
	) {
2652
		return $this->mPage->doUpdateRestrictions(
2653
			$limit,
2654
			$expiry,
2655
			$cascade,
2656
			$reason,
2657
			$this->getContext()->getUser()
2658
		);
2659
	}
2660
2661
	/**
2662
	 * @param string $reason
2663
	 * @param bool $suppress
2664
	 * @param int $u1 Unused
2665
	 * @param bool $u2 Unused
2666
	 * @param string $error
2667
	 * @return bool
2668
	 */
2669
	public function doDeleteArticle(
2670
		$reason, $suppress = false, $u1 = null, $u2 = null, &$error = ''
2671
	) {
2672
		return $this->mPage->doDeleteArticle( $reason, $suppress, $u1, $u2, $error );
2673
	}
2674
2675
	/**
2676
	 * @param string $fromP
2677
	 * @param string $summary
2678
	 * @param string $token
2679
	 * @param bool $bot
2680
	 * @param array $resultDetails
2681
	 * @param User|null $user
2682
	 * @return array
2683
	 */
2684
	public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user = null ) {
2685
		$user = is_null( $user ) ? $this->getContext()->getUser() : $user;
2686
		return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user );
2687
	}
2688
2689
	/**
2690
	 * @param string $fromP
2691
	 * @param string $summary
2692
	 * @param bool $bot
2693
	 * @param array $resultDetails
2694
	 * @param User|null $guser
2695
	 * @return array
2696
	 */
2697
	public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) {
2698
		$guser = is_null( $guser ) ? $this->getContext()->getUser() : $guser;
2699
		return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser );
2700
	}
2701
2702
	/**
2703
	 * @param bool $hasHistory
2704
	 * @return mixed
2705
	 */
2706
	public function generateReason( &$hasHistory ) {
2707
		$title = $this->mPage->getTitle();
2708
		$handler = ContentHandler::getForTitle( $title );
2709
		return $handler->getAutoDeleteReason( $title, $hasHistory );
2710
	}
2711
2712
	/**
2713
	 * @return array
2714
	 *
2715
	 * @deprecated since 1.24, use WikiPage::selectFields() instead
2716
	 */
2717
	public static function selectFields() {
2718
		wfDeprecated( __METHOD__, '1.24' );
2719
		return WikiPage::selectFields();
2720
	}
2721
2722
	/**
2723
	 * @param Title $title
2724
	 *
2725
	 * @deprecated since 1.24, use WikiPage::onArticleCreate() instead
2726
	 */
2727
	public static function onArticleCreate( $title ) {
2728
		wfDeprecated( __METHOD__, '1.24' );
2729
		WikiPage::onArticleCreate( $title );
2730
	}
2731
2732
	/**
2733
	 * @param Title $title
2734
	 *
2735
	 * @deprecated since 1.24, use WikiPage::onArticleDelete() instead
2736
	 */
2737
	public static function onArticleDelete( $title ) {
2738
		wfDeprecated( __METHOD__, '1.24' );
2739
		WikiPage::onArticleDelete( $title );
2740
	}
2741
2742
	/**
2743
	 * @param Title $title
2744
	 *
2745
	 * @deprecated since 1.24, use WikiPage::onArticleEdit() instead
2746
	 */
2747
	public static function onArticleEdit( $title ) {
2748
		wfDeprecated( __METHOD__, '1.24' );
2749
		WikiPage::onArticleEdit( $title );
2750
	}
2751
2752
	/**
2753
	 * @param string $oldtext
2754
	 * @param string $newtext
2755
	 * @param int $flags
2756
	 * @return string
2757
	 * @deprecated since 1.21, use ContentHandler::getAutosummary() instead
2758
	 */
2759
	public static function getAutosummary( $oldtext, $newtext, $flags ) {
2760
		return WikiPage::getAutosummary( $oldtext, $newtext, $flags );
2761
	}
2762
	// ******
2763
}
2764