Completed
Branch master (8d5465)
by
unknown
31:25
created

Article::getLastPurgeTimestamp()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
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
		ContentHandler::deprecated( __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 ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $nextid of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
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 ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $previd of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
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
		ContentHandler::deprecated( __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( 'ArticleAfterFetchContent', [ &$this, &$this->mContent ] );
351
352
		return $this->mContent;
353
	}
354
355
	/**
356
	 * Get text content object
357
	 * Does *NOT* follow redirects.
358
	 * @todo When is this null?
359
	 *
360
	 * @note Code that wants to retrieve page content from the database should
361
	 * use WikiPage::getContent().
362
	 *
363
	 * @return Content|null|bool
364
	 *
365
	 * @since 1.21
366
	 */
367
	protected function fetchContentObject() {
368
		if ( $this->mContentLoaded ) {
369
			return $this->mContentObject;
370
		}
371
372
		$this->mContentLoaded = true;
373
		$this->mContent = null;
374
375
		$oldid = $this->getOldID();
376
377
		# Pre-fill content with error message so that if something
378
		# fails we'll have something telling us what we intended.
379
		// XXX: this isn't page content but a UI message. horrible.
380
		$this->mContentObject = new MessageContent( 'missing-revision', [ $oldid ] );
381
382
		if ( $oldid ) {
383
			# $this->mRevision might already be fetched by getOldIDFromRequest()
384
			if ( !$this->mRevision ) {
385
				$this->mRevision = Revision::newFromId( $oldid );
386
				if ( !$this->mRevision ) {
387
					wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" );
388
					return false;
389
				}
390
			}
391
		} else {
392
			$oldid = $this->mPage->getLatest();
393
			if ( !$oldid ) {
394
				wfDebug( __METHOD__ . " failed to find page data for title " .
395
					$this->getTitle()->getPrefixedText() . "\n" );
396
				return false;
397
			}
398
399
			# Update error message with correct oldid
400
			$this->mContentObject = new MessageContent( 'missing-revision', [ $oldid ] );
401
402
			$this->mRevision = $this->mPage->getRevision();
403
404
			if ( !$this->mRevision ) {
405
				wfDebug( __METHOD__ . " failed to retrieve current page, rev_id $oldid\n" );
406
				return false;
407
			}
408
		}
409
410
		// @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
411
		// We should instead work with the Revision object when we need it...
412
		// Loads if user is allowed
413
		$content = $this->mRevision->getContent(
414
			Revision::FOR_THIS_USER,
415
			$this->getContext()->getUser()
416
		);
417
418
		if ( !$content ) {
419
			wfDebug( __METHOD__ . " failed to retrieve content of revision " .
420
				$this->mRevision->getId() . "\n" );
421
			return false;
422
		}
423
424
		$this->mContentObject = $content;
425
		$this->mRevIdFetched = $this->mRevision->getId();
426
427
		Hooks::run( 'ArticleAfterFetchContentObject', [ &$this, &$this->mContentObject ] );
428
429
		return $this->mContentObject;
430
	}
431
432
	/**
433
	 * Returns true if the currently-referenced revision is the current edit
434
	 * to this page (and it exists).
435
	 * @return bool
436
	 */
437
	public function isCurrent() {
438
		# If no oldid, this is the current version.
439
		if ( $this->getOldID() == 0 ) {
440
			return true;
441
		}
442
443
		return $this->mPage->exists() && $this->mRevision && $this->mRevision->isCurrent();
444
	}
445
446
	/**
447
	 * Get the fetched Revision object depending on request parameters or null
448
	 * on failure.
449
	 *
450
	 * @since 1.19
451
	 * @return Revision|null
452
	 */
453
	public function getRevisionFetched() {
454
		$this->fetchContentObject();
455
456
		return $this->mRevision;
457
	}
458
459
	/**
460
	 * Use this to fetch the rev ID used on page views
461
	 *
462
	 * @return int Revision ID of last article revision
463
	 */
464
	public function getRevIdFetched() {
465
		if ( $this->mRevIdFetched ) {
466
			return $this->mRevIdFetched;
467
		} else {
468
			return $this->mPage->getLatest();
469
		}
470
	}
471
472
	/**
473
	 * This is the default action of the index.php entry point: just view the
474
	 * page of the given title.
475
	 */
476
	public function view() {
477
		global $wgUseFileCache, $wgDebugToolbar;
478
479
		# Get variables from query string
480
		# As side effect this will load the revision and update the title
481
		# in a revision ID is passed in the request, so this should remain
482
		# the first call of this method even if $oldid is used way below.
483
		$oldid = $this->getOldID();
484
485
		$user = $this->getContext()->getUser();
486
		# Another whitelist check in case getOldID() is altering the title
487
		$permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $user );
488
		if ( count( $permErrors ) ) {
489
			wfDebug( __METHOD__ . ": denied on secondary read check\n" );
490
			throw new PermissionsError( 'read', $permErrors );
491
		}
492
493
		$outputPage = $this->getContext()->getOutput();
494
		# getOldID() may as well want us to redirect somewhere else
495
		if ( $this->mRedirectUrl ) {
496
			$outputPage->redirect( $this->mRedirectUrl );
0 ignored issues
show
Bug introduced by
It seems like $this->mRedirectUrl can also be of type boolean; however, OutputPage::redirect() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
497
			wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
498
499
			return;
500
		}
501
502
		# If we got diff in the query, we want to see a diff page instead of the article.
503
		if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) {
504
			wfDebug( __METHOD__ . ": showing diff page\n" );
505
			$this->showDiffPage();
506
507
			return;
508
		}
509
510
		# Set page title (may be overridden by DISPLAYTITLE)
511
		$outputPage->setPageTitle( $this->getTitle()->getPrefixedText() );
512
513
		$outputPage->setArticleFlag( true );
514
		# Allow frames by default
515
		$outputPage->allowClickjacking();
516
517
		$parserCache = ParserCache::singleton();
518
519
		$parserOptions = $this->getParserOptions();
520
		# Render printable version, use printable version cache
521
		if ( $outputPage->isPrintable() ) {
522
			$parserOptions->setIsPrintable( true );
523
			$parserOptions->setEditSection( false );
524
		} elseif ( !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user ) ) {
525
			$parserOptions->setEditSection( false );
526
		}
527
528
		# Try client and file cache
529
		if ( !$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched() ) {
530
			# Try to stream the output from file cache
531
			if ( $wgUseFileCache && $this->tryFileCache() ) {
532
				wfDebug( __METHOD__ . ": done file cache\n" );
533
				# tell wgOut that output is taken care of
534
				$outputPage->disable();
535
				$this->mPage->doViewUpdates( $user, $oldid );
536
537
				return;
538
			}
539
		}
540
541
		# Should the parser cache be used?
542
		$useParserCache = $this->mPage->shouldCheckParserCache( $parserOptions, $oldid );
543
		wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
544
		if ( $user->getStubThreshold() ) {
545
			$this->getContext()->getStats()->increment( 'pcache_miss_stub' );
546
		}
547
548
		$this->showRedirectedFromHeader();
549
		$this->showNamespaceHeader();
550
551
		# Iterate through the possible ways of constructing the output text.
552
		# Keep going until $outputDone is set, or we run out of things to do.
553
		$pass = 0;
554
		$outputDone = false;
555
		$this->mParserOutput = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type object<ParserOutput> of property $mParserOutput.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
556
557
		while ( !$outputDone && ++$pass ) {
558
			switch ( $pass ) {
559
				case 1:
560
					Hooks::run( 'ArticleViewHeader', [ &$this, &$outputDone, &$useParserCache ] );
561
					break;
562
				case 2:
563
					# Early abort if the page doesn't exist
564
					if ( !$this->mPage->exists() ) {
565
						wfDebug( __METHOD__ . ": showing missing article\n" );
566
						$this->showMissingArticle();
567
						$this->mPage->doViewUpdates( $user );
568
						return;
569
					}
570
571
					# Try the parser cache
572
					if ( $useParserCache ) {
573
						$this->mParserOutput = $parserCache->get( $this->mPage, $parserOptions );
0 ignored issues
show
Documentation Bug introduced by
It seems like $parserCache->get($this->mPage, $parserOptions) can also be of type false. However, the property $mParserOutput is declared as type object<ParserOutput>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
574
575
						if ( $this->mParserOutput !== false ) {
576
							if ( $oldid ) {
577
								wfDebug( __METHOD__ . ": showing parser cache contents for current rev permalink\n" );
578
								$this->setOldSubtitle( $oldid );
579
							} else {
580
								wfDebug( __METHOD__ . ": showing parser cache contents\n" );
581
							}
582
							$outputPage->addParserOutput( $this->mParserOutput );
583
							# Ensure that UI elements requiring revision ID have
584
							# the correct version information.
585
							$outputPage->setRevisionId( $this->mPage->getLatest() );
586
							# Preload timestamp to avoid a DB hit
587
							$cachedTimestamp = $this->mParserOutput->getTimestamp();
588
							if ( $cachedTimestamp !== null ) {
589
								$outputPage->setRevisionTimestamp( $cachedTimestamp );
590
								$this->mPage->setTimestamp( $cachedTimestamp );
591
							}
592
							$outputDone = true;
593
						}
594
					}
595
					break;
596
				case 3:
597
					# This will set $this->mRevision if needed
598
					$this->fetchContentObject();
599
600
					# Are we looking at an old revision
601
					if ( $oldid && $this->mRevision ) {
602
						$this->setOldSubtitle( $oldid );
603
604
						if ( !$this->showDeletedRevisionHeader() ) {
605
							wfDebug( __METHOD__ . ": cannot view deleted revision\n" );
606
							return;
607
						}
608
					}
609
610
					# Ensure that UI elements requiring revision ID have
611
					# the correct version information.
612
					$outputPage->setRevisionId( $this->getRevIdFetched() );
613
					# Preload timestamp to avoid a DB hit
614
					$outputPage->setRevisionTimestamp( $this->mPage->getTimestamp() );
0 ignored issues
show
Security Bug introduced by
It seems like $this->mPage->getTimestamp() targeting WikiPage::getTimestamp() can also be of type false; however, OutputPage::setRevisionTimestamp() does only seem to accept string|null, did you maybe forget to handle an error condition?
Loading history...
615
616
					# Pages containing custom CSS or JavaScript get special treatment
617
					if ( $this->getTitle()->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage() ) {
618
						wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
619
						$this->showCssOrJsPage();
620
						$outputDone = true;
621
					} elseif ( !Hooks::run( 'ArticleContentViewCustom',
622
							[ $this->fetchContentObject(), $this->getTitle(), $outputPage ] ) ) {
623
624
						# Allow extensions do their own custom view for certain pages
625
						$outputDone = true;
626
					} elseif ( !ContentHandler::runLegacyHooks( 'ArticleViewCustom',
627
							[ $this->fetchContentObject(), $this->getTitle(), $outputPage ] ) ) {
628
629
						# Allow extensions do their own custom view for certain pages
630
						$outputDone = true;
631
					}
632
					break;
633
				case 4:
634
					# Run the parse, protected by a pool counter
635
					wfDebug( __METHOD__ . ": doing uncached parse\n" );
636
637
					$content = $this->getContentObject();
638
					$poolArticleView = new PoolWorkArticleView( $this->getPage(), $parserOptions,
639
						$this->getRevIdFetched(), $useParserCache, $content );
640
641
					if ( !$poolArticleView->execute() ) {
642
						$error = $poolArticleView->getError();
643
						if ( $error ) {
644
							$outputPage->clearHTML(); // for release() errors
645
							$outputPage->enableClientCache( false );
646
							$outputPage->setRobotPolicy( 'noindex,nofollow' );
647
648
							$errortext = $error->getWikiText( false, 'view-pool-error' );
649
							$outputPage->addWikiText( '<div class="errorbox">' . $errortext . '</div>' );
650
						}
651
						# Connection or timeout error
652
						return;
653
					}
654
655
					$this->mParserOutput = $poolArticleView->getParserOutput();
656
					$outputPage->addParserOutput( $this->mParserOutput );
657
					if ( $content->getRedirectTarget() ) {
658
						$outputPage->addSubtitle( "<span id=\"redirectsub\">" .
659
							$this->getContext()->msg( 'redirectpagesub' )->parse() . "</span>" );
660
					}
661
662
					# Don't cache a dirty ParserOutput object
663
					if ( $poolArticleView->getIsDirty() ) {
664
						$outputPage->setCdnMaxage( 0 );
665
						$outputPage->addHTML( "<!-- parser cache is expired, " .
666
							"sending anyway due to pool overload-->\n" );
667
					}
668
669
					$outputDone = true;
670
					break;
671
				# Should be unreachable, but just in case...
672
				default:
673
					break 2;
674
			}
675
		}
676
677
		# Get the ParserOutput actually *displayed* here.
678
		# Note that $this->mParserOutput is the *current*/oldid version output.
679
		$pOutput = ( $outputDone instanceof ParserOutput )
680
			? $outputDone // object fetched by hook
681
			: $this->mParserOutput;
682
683
		# Adjust title for main page & pages with displaytitle
684
		if ( $pOutput ) {
685
			$this->adjustDisplayTitle( $pOutput );
686
		}
687
688
		# For the main page, overwrite the <title> element with the con-
689
		# tents of 'pagetitle-view-mainpage' instead of the default (if
690
		# that's not empty).
691
		# This message always exists because it is in the i18n files
692
		if ( $this->getTitle()->isMainPage() ) {
693
			$msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
694
			if ( !$msg->isDisabled() ) {
695
				$outputPage->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
696
			}
697
		}
698
699
		# Check for any __NOINDEX__ tags on the page using $pOutput
700
		$policy = $this->getRobotPolicy( 'view', $pOutput );
0 ignored issues
show
Security Bug introduced by
It seems like $pOutput defined by $outputDone instanceof \... : $this->mParserOutput on line 679 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...
701
		$outputPage->setIndexPolicy( $policy['index'] );
702
		$outputPage->setFollowPolicy( $policy['follow'] );
703
704
		$this->showViewFooter();
705
		$this->mPage->doViewUpdates( $user, $oldid );
706
707
		$outputPage->addModules( 'mediawiki.action.view.postEdit' );
708
709
	}
710
711
	/**
712
	 * Adjust title for pages with displaytitle, -{T|}- or language conversion
713
	 * @param ParserOutput $pOutput
714
	 */
715
	public function adjustDisplayTitle( ParserOutput $pOutput ) {
716
		# Adjust the title if it was set by displaytitle, -{T|}- or language conversion
717
		$titleText = $pOutput->getTitleText();
718
		if ( strval( $titleText ) !== '' ) {
719
			$this->getContext()->getOutput()->setPageTitle( $titleText );
720
		}
721
	}
722
723
	/**
724
	 * Show a diff page according to current request variables. For use within
725
	 * Article::view() only, other callers should use the DifferenceEngine class.
726
	 *
727
	 */
728
	protected function showDiffPage() {
729
		$request = $this->getContext()->getRequest();
730
		$user = $this->getContext()->getUser();
731
		$diff = $request->getVal( 'diff' );
732
		$rcid = $request->getVal( 'rcid' );
733
		$diffOnly = $request->getBool( 'diffonly', $user->getOption( 'diffonly' ) );
734
		$purge = $request->getVal( 'action' ) == 'purge';
735
		$unhide = $request->getInt( 'unhide' ) == 1;
736
		$oldid = $this->getOldID();
737
738
		$rev = $this->getRevisionFetched();
739
740
		if ( !$rev ) {
741
			$this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) );
742
			$msg = $this->getContext()->msg( 'difference-missing-revision' )
743
				->params( $oldid )
744
				->numParams( 1 )
745
				->parseAsBlock();
746
			$this->getContext()->getOutput()->addHTML( $msg );
747
			return;
748
		}
749
750
		$contentHandler = $rev->getContentHandler();
751
		$de = $contentHandler->createDifferenceEngine(
752
			$this->getContext(),
753
			$oldid,
754
			$diff,
755
			$rcid,
756
			$purge,
757
			$unhide
758
		);
759
760
		// DifferenceEngine directly fetched the revision:
761
		$this->mRevIdFetched = $de->mNewid;
762
		$de->showDiffPage( $diffOnly );
763
764
		// Run view updates for the newer revision being diffed (and shown
765
		// below the diff if not $diffOnly).
766
		list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
0 ignored issues
show
Unused Code introduced by
The assignment to $old is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
767
		// New can be false, convert it to 0 - this conveniently means the latest revision
768
		$this->mPage->doViewUpdates( $user, (int)$new );
769
	}
770
771
	/**
772
	 * Show a page view for a page formatted as CSS or JavaScript. To be called by
773
	 * Article::view() only.
774
	 *
775
	 * This exists mostly to serve the deprecated ShowRawCssJs hook (used to customize these views).
776
	 * It has been replaced by the ContentGetParserOutput hook, which lets you do the same but with
777
	 * more flexibility.
778
	 *
779
	 * @param bool $showCacheHint Whether to show a message telling the user
780
	 *   to clear the browser cache (default: true).
781
	 */
782
	protected function showCssOrJsPage( $showCacheHint = true ) {
783
		$outputPage = $this->getContext()->getOutput();
784
785
		if ( $showCacheHint ) {
786
			$dir = $this->getContext()->getLanguage()->getDir();
787
			$lang = $this->getContext()->getLanguage()->getHtmlCode();
788
789
			$outputPage->wrapWikiMsg(
790
				"<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
791
				'clearyourcache'
792
			);
793
		}
794
795
		$this->fetchContentObject();
796
797
		if ( $this->mContentObject ) {
798
			// Give hooks a chance to customise the output
799
			if ( ContentHandler::runLegacyHooks(
800
				'ShowRawCssJs',
801
				[ $this->mContentObject, $this->getTitle(), $outputPage ] )
802
			) {
803
				// If no legacy hooks ran, display the content of the parser output, including RL modules,
804
				// but excluding metadata like categories and language links
805
				$po = $this->mContentObject->getParserOutput( $this->getTitle() );
806
				$outputPage->addParserOutputContent( $po );
807
			}
808
		}
809
	}
810
811
	/**
812
	 * Get the robot policy to be used for the current view
813
	 * @param string $action The action= GET parameter
814
	 * @param ParserOutput|null $pOutput
815
	 * @return array The policy that should be set
816
	 * @todo actions other than 'view'
817
	 */
818
	public function getRobotPolicy( $action, $pOutput = null ) {
819
		global $wgArticleRobotPolicies, $wgNamespaceRobotPolicies, $wgDefaultRobotPolicy;
820
821
		$ns = $this->getTitle()->getNamespace();
822
823
		# Don't index user and user talk pages for blocked users (bug 11443)
824
		if ( ( $ns == NS_USER || $ns == NS_USER_TALK ) && !$this->getTitle()->isSubpage() ) {
825
			$specificTarget = null;
826
			$vagueTarget = null;
827
			$titleText = $this->getTitle()->getText();
828
			if ( IP::isValid( $titleText ) ) {
829
				$vagueTarget = $titleText;
830
			} else {
831
				$specificTarget = $titleText;
832
			}
833
			if ( Block::newFromTarget( $specificTarget, $vagueTarget ) instanceof Block ) {
834
				return [
835
					'index' => 'noindex',
836
					'follow' => 'nofollow'
837
				];
838
			}
839
		}
840
841
		if ( $this->mPage->getId() === 0 || $this->getOldID() ) {
842
			# Non-articles (special pages etc), and old revisions
843
			return [
844
				'index' => 'noindex',
845
				'follow' => 'nofollow'
846
			];
847
		} elseif ( $this->getContext()->getOutput()->isPrintable() ) {
848
			# Discourage indexing of printable versions, but encourage following
849
			return [
850
				'index' => 'noindex',
851
				'follow' => 'follow'
852
			];
853
		} elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) {
854
			# For ?curid=x urls, disallow indexing
855
			return [
856
				'index' => 'noindex',
857
				'follow' => 'follow'
858
			];
859
		}
860
861
		# Otherwise, construct the policy based on the various config variables.
862
		$policy = self::formatRobotPolicy( $wgDefaultRobotPolicy );
863
864
		if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
865
			# Honour customised robot policies for this namespace
866
			$policy = array_merge(
867
				$policy,
868
				self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
869
			);
870
		}
871
		if ( $this->getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
872
			# __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
873
			# a final sanity check that we have really got the parser output.
874
			$policy = array_merge(
875
				$policy,
876
				[ 'index' => $pOutput->getIndexPolicy() ]
877
			);
878
		}
879
880
		if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) {
881
			# (bug 14900) site config can override user-defined __INDEX__ or __NOINDEX__
882
			$policy = array_merge(
883
				$policy,
884
				self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] )
885
			);
886
		}
887
888
		return $policy;
889
	}
890
891
	/**
892
	 * Converts a String robot policy into an associative array, to allow
893
	 * merging of several policies using array_merge().
894
	 * @param array|string $policy Returns empty array on null/false/'', transparent
895
	 *   to already-converted arrays, converts string.
896
	 * @return array 'index' => \<indexpolicy\>, 'follow' => \<followpolicy\>
897
	 */
898
	public static function formatRobotPolicy( $policy ) {
899
		if ( is_array( $policy ) ) {
900
			return $policy;
901
		} elseif ( !$policy ) {
902
			return [];
903
		}
904
905
		$policy = explode( ',', $policy );
906
		$policy = array_map( 'trim', $policy );
907
908
		$arr = [];
909
		foreach ( $policy as $var ) {
910
			if ( in_array( $var, [ 'index', 'noindex' ] ) ) {
911
				$arr['index'] = $var;
912
			} elseif ( in_array( $var, [ 'follow', 'nofollow' ] ) ) {
913
				$arr['follow'] = $var;
914
			}
915
		}
916
917
		return $arr;
918
	}
919
920
	/**
921
	 * If this request is a redirect view, send "redirected from" subtitle to
922
	 * the output. Returns true if the header was needed, false if this is not
923
	 * a redirect view. Handles both local and remote redirects.
924
	 *
925
	 * @return bool
926
	 */
927
	public function showRedirectedFromHeader() {
928
		global $wgRedirectSources;
929
930
		$context = $this->getContext();
931
		$outputPage = $context->getOutput();
932
		$request = $context->getRequest();
933
		$rdfrom = $request->getVal( 'rdfrom' );
934
935
		// Construct a URL for the current page view, but with the target title
936
		$query = $request->getValues();
937
		unset( $query['rdfrom'] );
938
		unset( $query['title'] );
939
		if ( $this->getTitle()->isRedirect() ) {
940
			// Prevent double redirects
941
			$query['redirect'] = 'no';
942
		}
943
		$redirectTargetUrl = $this->getTitle()->getLinkURL( $query );
944
945
		if ( isset( $this->mRedirectedFrom ) ) {
946
			// This is an internally redirected page view.
947
			// We'll need a backlink to the source page for navigation.
948
			if ( Hooks::run( 'ArticleViewRedirect', [ &$this ] ) ) {
949
				$redir = Linker::linkKnown(
950
					$this->mRedirectedFrom,
951
					null,
952
					[],
953
					[ 'redirect' => 'no' ]
954
				);
955
956
				$outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
957
					$context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
958
				. "</span>" );
959
960
				// Add the script to update the displayed URL and
961
				// set the fragment if one was specified in the redirect
962
				$outputPage->addJsConfigVars( [
963
					'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
964
				] );
965
				$outputPage->addModules( 'mediawiki.action.view.redirect' );
966
967
				// Add a <link rel="canonical"> tag
968
				$outputPage->setCanonicalUrl( $this->getTitle()->getCanonicalURL() );
0 ignored issues
show
Security Bug introduced by
It seems like $this->getTitle()->getCanonicalURL() targeting Title::getCanonicalURL() can also be of type false; however, OutputPage::setCanonicalUrl() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
969
970
				// Tell the output object that the user arrived at this article through a redirect
971
				$outputPage->setRedirectedFrom( $this->mRedirectedFrom );
972
973
				return true;
974
			}
975
		} elseif ( $rdfrom ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rdfrom of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
976
			// This is an externally redirected view, from some other wiki.
977
			// If it was reported from a trusted site, supply a backlink.
978
			if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
979
				$redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
980
				$outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
981
					$context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
982
				. "</span>" );
983
984
				// Add the script to update the displayed URL
985
				$outputPage->addJsConfigVars( [
986
					'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
987
				] );
988
				$outputPage->addModules( 'mediawiki.action.view.redirect' );
989
990
				return true;
991
			}
992
		}
993
994
		return false;
995
	}
996
997
	/**
998
	 * Show a header specific to the namespace currently being viewed, like
999
	 * [[MediaWiki:Talkpagetext]]. For Article::view().
1000
	 */
1001
	public function showNamespaceHeader() {
1002
		if ( $this->getTitle()->isTalkPage() ) {
1003
			if ( !wfMessage( 'talkpageheader' )->isDisabled() ) {
1004
				$this->getContext()->getOutput()->wrapWikiMsg(
1005
					"<div class=\"mw-talkpageheader\">\n$1\n</div>",
1006
					[ 'talkpageheader' ]
1007
				);
1008
			}
1009
		}
1010
	}
1011
1012
	/**
1013
	 * Show the footer section of an ordinary page view
1014
	 */
1015
	public function showViewFooter() {
1016
		# check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1017
		if ( $this->getTitle()->getNamespace() == NS_USER_TALK
1018
			&& IP::isValid( $this->getTitle()->getText() )
1019
		) {
1020
			$this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
1021
		}
1022
1023
		// Show a footer allowing the user to patrol the shown revision or page if possible
1024
		$patrolFooterShown = $this->showPatrolFooter();
1025
1026
		Hooks::run( 'ArticleViewFooter', [ $this, $patrolFooterShown ] );
1027
	}
1028
1029
	/**
1030
	 * If patrol is possible, output a patrol UI box. This is called from the
1031
	 * footer section of ordinary page views. If patrol is not possible or not
1032
	 * desired, does nothing.
1033
	 * Side effect: When the patrol link is build, this method will call
1034
	 * OutputPage::preventClickjacking() and load mediawiki.page.patrol.ajax.
1035
	 *
1036
	 * @return bool
1037
	 */
1038
	public function showPatrolFooter() {
1039
		global $wgUseNPPatrol, $wgUseRCPatrol, $wgUseFilePatrol, $wgEnableAPI, $wgEnableWriteAPI;
1040
1041
		$outputPage = $this->getContext()->getOutput();
1042
		$user = $this->getContext()->getUser();
1043
		$title = $this->getTitle();
1044
		$rc = false;
1045
1046
		if ( !$title->quickUserCan( 'patrol', $user )
1047
			|| !( $wgUseRCPatrol || $wgUseNPPatrol
1048
				|| ( $wgUseFilePatrol && $title->inNamespace( NS_FILE ) ) )
1049
		) {
1050
			// Patrolling is disabled or the user isn't allowed to
1051
			return false;
1052
		}
1053
1054
		if ( $this->mRevision
1055
			&& !RecentChange::isInRCLifespan( $this->mRevision->getTimestamp(), 21600 )
1056
		) {
1057
			// The current revision is already older than what could be in the RC table
1058
			// 6h tolerance because the RC might not be cleaned out regularly
1059
			return false;
1060
		}
1061
1062
		// Check for cached results
1063
		$key = wfMemcKey( 'unpatrollable-page', $title->getArticleID() );
1064
		$cache = ObjectCache::getMainWANInstance();
1065
		if ( $cache->get( $key ) ) {
1066
			return false;
1067
		}
1068
1069
		$dbr = wfGetDB( DB_REPLICA );
1070
		$oldestRevisionTimestamp = $dbr->selectField(
1071
			'revision',
1072
			'MIN( rev_timestamp )',
1073
			[ 'rev_page' => $title->getArticleID() ],
1074
			__METHOD__
1075
		);
1076
1077
		// New page patrol: Get the timestamp of the oldest revison which
1078
		// the revision table holds for the given page. Then we look
1079
		// whether it's within the RC lifespan and if it is, we try
1080
		// to get the recentchanges row belonging to that entry
1081
		// (with rc_new = 1).
1082
		$recentPageCreation = false;
1083
		if ( $oldestRevisionTimestamp
1084
			&& RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
1085
		) {
1086
			// 6h tolerance because the RC might not be cleaned out regularly
1087
			$recentPageCreation = true;
1088
			$rc = RecentChange::newFromConds(
1089
				[
1090
					'rc_new' => 1,
1091
					'rc_timestamp' => $oldestRevisionTimestamp,
1092
					'rc_namespace' => $title->getNamespace(),
1093
					'rc_cur_id' => $title->getArticleID()
1094
				],
1095
				__METHOD__
1096
			);
1097
			if ( $rc ) {
1098
				// Use generic patrol message for new pages
1099
				$markPatrolledMsg = wfMessage( 'markaspatrolledtext' );
1100
			}
1101
		}
1102
1103
		// File patrol: Get the timestamp of the latest upload for this page,
1104
		// check whether it is within the RC lifespan and if it is, we try
1105
		// to get the recentchanges row belonging to that entry
1106
		// (with rc_type = RC_LOG, rc_log_type = upload).
1107
		$recentFileUpload = false;
1108
		if ( ( !$rc || $rc->getAttribute( 'rc_patrolled' ) ) && $wgUseFilePatrol
1109
			&& $title->getNamespace() === NS_FILE ) {
1110
			// Retrieve timestamp of most recent upload
1111
			$newestUploadTimestamp = $dbr->selectField(
1112
				'image',
1113
				'MAX( img_timestamp )',
1114
				[ 'img_name' => $title->getDBkey() ],
1115
				__METHOD__
1116
			);
1117
			if ( $newestUploadTimestamp
1118
				&& RecentChange::isInRCLifespan( $newestUploadTimestamp, 21600 )
1119
			) {
1120
				// 6h tolerance because the RC might not be cleaned out regularly
1121
				$recentFileUpload = true;
1122
				$rc = RecentChange::newFromConds(
1123
					[
1124
						'rc_type' => RC_LOG,
1125
						'rc_log_type' => 'upload',
1126
						'rc_timestamp' => $newestUploadTimestamp,
1127
						'rc_namespace' => NS_FILE,
1128
						'rc_cur_id' => $title->getArticleID()
1129
					],
1130
					__METHOD__,
1131
					[ 'USE INDEX' => 'rc_timestamp' ]
1132
				);
1133
				if ( $rc ) {
1134
					// Use patrol message specific to files
1135
					$markPatrolledMsg = wfMessage( 'markaspatrolledtext-file' );
1136
				}
1137
			}
1138
		}
1139
1140
		if ( !$recentPageCreation && !$recentFileUpload ) {
1141
			// Page creation and latest upload (for files) is too old to be in RC
1142
1143
			// We definitely can't patrol so cache the information
1144
			// When a new file version is uploaded, the cache is cleared
1145
			$cache->set( $key, '1' );
1146
1147
			return false;
1148
		}
1149
1150
		if ( !$rc ) {
1151
			// Don't cache: This can be hit if the page gets accessed very fast after
1152
			// its creation / latest upload or in case we have high replica DB lag. In case
1153
			// the revision is too old, we will already return above.
1154
			return false;
1155
		}
1156
1157
		if ( $rc->getAttribute( 'rc_patrolled' ) ) {
1158
			// Patrolled RC entry around
1159
1160
			// Cache the information we gathered above in case we can't patrol
1161
			// Don't cache in case we can patrol as this could change
1162
			$cache->set( $key, '1' );
1163
1164
			return false;
1165
		}
1166
1167
		if ( $rc->getPerformer()->equals( $user ) ) {
1168
			// Don't show a patrol link for own creations/uploads. If the user could
1169
			// patrol them, they already would be patrolled
1170
			return false;
1171
		}
1172
1173
		$rcid = $rc->getAttribute( 'rc_id' );
1174
1175
		$token = $user->getEditToken( $rcid );
1176
1177
		$outputPage->preventClickjacking();
1178
		if ( $wgEnableAPI && $wgEnableWriteAPI && $user->isAllowed( 'writeapi' ) ) {
1179
			$outputPage->addModules( 'mediawiki.page.patrol.ajax' );
1180
		}
1181
1182
		$link = Linker::linkKnown(
1183
			$title,
1184
			$markPatrolledMsg->escaped(),
0 ignored issues
show
Bug introduced by
The variable $markPatrolledMsg does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1185
			[],
1186
			[
1187
				'action' => 'markpatrolled',
1188
				'rcid' => $rcid,
1189
				'token' => $token,
1190
			]
1191
		);
1192
1193
		$outputPage->addHTML(
1194
			"<div class='patrollink' data-mw='interface'>" .
1195
				wfMessage( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
1196
			'</div>'
1197
		);
1198
1199
		return true;
1200
	}
1201
1202
	/**
1203
	 * Purge the cache used to check if it is worth showing the patrol footer
1204
	 * For example, it is done during re-uploads when file patrol is used.
1205
	 * @param int $articleID ID of the article to purge
1206
	 * @since 1.27
1207
	 */
1208
	public static function purgePatrolFooterCache( $articleID ) {
1209
		$cache = ObjectCache::getMainWANInstance();
1210
		$cache->delete( wfMemcKey( 'unpatrollable-page', $articleID ) );
1211
	}
1212
1213
	/**
1214
	 * Show the error text for a missing article. For articles in the MediaWiki
1215
	 * namespace, show the default message text. To be called from Article::view().
1216
	 */
1217
	public function showMissingArticle() {
1218
		global $wgSend404Code;
1219
1220
		$outputPage = $this->getContext()->getOutput();
1221
		// Whether the page is a root user page of an existing user (but not a subpage)
1222
		$validUserPage = false;
1223
1224
		$title = $this->getTitle();
1225
1226
		# Show info in user (talk) namespace. Does the user exist? Is he blocked?
1227
		if ( $title->getNamespace() == NS_USER
1228
			|| $title->getNamespace() == NS_USER_TALK
1229
		) {
1230
			$rootPart = explode( '/', $title->getText() )[0];
1231
			$user = User::newFromName( $rootPart, false /* allow IP users*/ );
1232
			$ip = User::isIP( $rootPart );
1233
			$block = Block::newFromTarget( $user, $user );
0 ignored issues
show
Security Bug introduced by
It seems like $user defined by \User::newFromName($rootPart, false) on line 1231 can also be of type false; however, Block::newFromTarget() does only seem to accept string|object<User>|integer, 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...
Security Bug introduced by
It seems like $user defined by \User::newFromName($rootPart, false) on line 1231 can also be of type false; however, Block::newFromTarget() does only seem to accept string|object<User>|integer|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...
1234
1235
			if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
1236
				$outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
1237
					[ 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ] );
1238 View Code Duplication
			} elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) {
1239
				# Show log extract if the user is currently blocked
1240
				LogEventsList::showLogExtract(
1241
					$outputPage,
1242
					'block',
1243
					MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(),
1244
					'',
1245
					[
1246
						'lim' => 1,
1247
						'showIfEmpty' => false,
1248
						'msgKey' => [
1249
							'blocked-notice-logextract',
1250
							$user->getName() # Support GENDER in notice
1251
						]
1252
					]
1253
				);
1254
				$validUserPage = !$title->isSubpage();
1255
			} else {
1256
				$validUserPage = !$title->isSubpage();
1257
			}
1258
		}
1259
1260
		Hooks::run( 'ShowMissingArticle', [ $this ] );
1261
1262
		# Show delete and move logs if there were any such events.
1263
		# The logging query can DOS the site when bots/crawlers cause 404 floods,
1264
		# so be careful showing this. 404 pages must be cheap as they are hard to cache.
1265
		$cache = ObjectCache::getMainStashInstance();
1266
		$key = wfMemcKey( 'page-recent-delete', md5( $title->getPrefixedText() ) );
1267
		$loggedIn = $this->getContext()->getUser()->isLoggedIn();
1268
		if ( $loggedIn || $cache->get( $key ) ) {
1269
			$logTypes = [ 'delete', 'move' ];
1270
			$conds = [ "log_action != 'revision'" ];
1271
			// Give extensions a chance to hide their (unrelated) log entries
1272
			Hooks::run( 'Article::MissingArticleConditions', [ &$conds, $logTypes ] );
1273
			LogEventsList::showLogExtract(
1274
				$outputPage,
1275
				$logTypes,
1276
				$title,
1277
				'',
1278
				[
1279
					'lim' => 10,
1280
					'conds' => $conds,
1281
					'showIfEmpty' => false,
1282
					'msgKey' => [ $loggedIn
1283
						? 'moveddeleted-notice'
1284
						: 'moveddeleted-notice-recent'
1285
					]
1286
				]
1287
			);
1288
		}
1289
1290
		if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) {
1291
			// If there's no backing content, send a 404 Not Found
1292
			// for better machine handling of broken links.
1293
			$this->getContext()->getRequest()->response()->statusHeader( 404 );
1294
		}
1295
1296
		// Also apply the robot policy for nonexisting pages (even if a 404 was used for sanity)
1297
		$policy = $this->getRobotPolicy( 'view' );
1298
		$outputPage->setIndexPolicy( $policy['index'] );
1299
		$outputPage->setFollowPolicy( $policy['follow'] );
1300
1301
		$hookResult = Hooks::run( 'BeforeDisplayNoArticleText', [ $this ] );
1302
1303
		if ( !$hookResult ) {
1304
			return;
1305
		}
1306
1307
		# Show error message
1308
		$oldid = $this->getOldID();
1309
		if ( !$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText() ) {
1310
			$outputPage->addParserOutput( $this->getContentObject()->getParserOutput( $title ) );
1311
		} else {
1312
			if ( $oldid ) {
1313
				$text = wfMessage( 'missing-revision', $oldid )->plain();
1314
			} elseif ( $title->quickUserCan( 'create', $this->getContext()->getUser() )
1315
				&& $title->quickUserCan( 'edit', $this->getContext()->getUser() )
1316
			) {
1317
				$message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
1318
				$text = wfMessage( $message )->plain();
1319
			} else {
1320
				$text = wfMessage( 'noarticletext-nopermission' )->plain();
1321
			}
1322
1323
			$dir = $this->getContext()->getLanguage()->getDir();
1324
			$lang = $this->getContext()->getLanguage()->getCode();
1325
			$outputPage->addWikiText( Xml::openElement( 'div', [
1326
				'class' => "noarticletext mw-content-$dir",
1327
				'dir' => $dir,
1328
				'lang' => $lang,
1329
			] ) . "\n$text\n</div>" );
1330
		}
1331
	}
1332
1333
	/**
1334
	 * If the revision requested for view is deleted, check permissions.
1335
	 * Send either an error message or a warning header to the output.
1336
	 *
1337
	 * @return bool True if the view is allowed, false if not.
1338
	 */
1339
	public function showDeletedRevisionHeader() {
1340
		if ( !$this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
1341
			// Not deleted
1342
			return true;
1343
		}
1344
1345
		$outputPage = $this->getContext()->getOutput();
1346
		$user = $this->getContext()->getUser();
1347
		// If the user is not allowed to see it...
1348
		if ( !$this->mRevision->userCan( Revision::DELETED_TEXT, $user ) ) {
1349
			$outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1350
				'rev-deleted-text-permission' );
1351
1352
			return false;
1353
		// If the user needs to confirm that they want to see it...
1354
		} elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) {
1355
			# Give explanation and add a link to view the revision...
1356
			$oldid = intval( $this->getOldID() );
1357
			$link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
1358
			$msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
1359
				'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
1360
			$outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1361
				[ $msg, $link ] );
1362
1363
			return false;
1364
		// We are allowed to see...
1365
		} else {
1366
			$msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
1367
				'rev-suppressed-text-view' : 'rev-deleted-text-view';
1368
			$outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", $msg );
1369
1370
			return true;
1371
		}
1372
	}
1373
1374
	/**
1375
	 * Generate the navigation links when browsing through an article revisions
1376
	 * It shows the information as:
1377
	 *   Revision as of \<date\>; view current revision
1378
	 *   \<- Previous version | Next Version -\>
1379
	 *
1380
	 * @param int $oldid Revision ID of this article revision
1381
	 */
1382
	public function setOldSubtitle( $oldid = 0 ) {
1383
		if ( !Hooks::run( 'DisplayOldSubtitle', [ &$this, &$oldid ] ) ) {
1384
			return;
1385
		}
1386
1387
		$context = $this->getContext();
1388
		$unhide = $context->getRequest()->getInt( 'unhide' ) == 1;
1389
1390
		# Cascade unhide param in links for easy deletion browsing
1391
		$extraParams = [];
1392
		if ( $unhide ) {
1393
			$extraParams['unhide'] = 1;
1394
		}
1395
1396
		if ( $this->mRevision && $this->mRevision->getId() === $oldid ) {
1397
			$revision = $this->mRevision;
1398
		} else {
1399
			$revision = Revision::newFromId( $oldid );
1400
		}
1401
1402
		$timestamp = $revision->getTimestamp();
1403
1404
		$current = ( $oldid == $this->mPage->getLatest() );
1405
		$language = $context->getLanguage();
1406
		$user = $context->getUser();
1407
1408
		$td = $language->userTimeAndDate( $timestamp, $user );
1409
		$tddate = $language->userDate( $timestamp, $user );
1410
		$tdtime = $language->userTime( $timestamp, $user );
1411
1412
		# Show user links if allowed to see them. If hidden, then show them only if requested...
1413
		$userlinks = Linker::revUserTools( $revision, !$unhide );
0 ignored issues
show
Bug introduced by
It seems like $revision defined by \Revision::newFromId($oldid) on line 1399 can be null; however, Linker::revUserTools() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1414
1415
		$infomsg = $current && !$context->msg( 'revision-info-current' )->isDisabled()
1416
			? 'revision-info-current'
1417
			: 'revision-info';
1418
1419
		$outputPage = $context->getOutput();
1420
		$revisionInfo = "<div id=\"mw-{$infomsg}\">" .
1421
			$context->msg( $infomsg, $td )
1422
				->rawParams( $userlinks )
1423
				->params( $revision->getId(), $tddate, $tdtime, $revision->getUserText() )
1424
				->rawParams( Linker::revComment( $revision, true, true ) )
0 ignored issues
show
Bug introduced by
It seems like $revision defined by \Revision::newFromId($oldid) on line 1399 can be null; however, Linker::revComment() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1425
				->parse() .
1426
			"</div>";
1427
1428
		$lnk = $current
1429
			? $context->msg( 'currentrevisionlink' )->escaped()
1430
			: Linker::linkKnown(
1431
				$this->getTitle(),
1432
				$context->msg( 'currentrevisionlink' )->escaped(),
1433
				[],
1434
				$extraParams
1435
			);
1436
		$curdiff = $current
1437
			? $context->msg( 'diff' )->escaped()
1438
			: Linker::linkKnown(
1439
				$this->getTitle(),
1440
				$context->msg( 'diff' )->escaped(),
1441
				[],
1442
				[
1443
					'diff' => 'cur',
1444
					'oldid' => $oldid
1445
				] + $extraParams
1446
			);
1447
		$prev = $this->getTitle()->getPreviousRevisionID( $oldid );
1448
		$prevlink = $prev
1449
			? Linker::linkKnown(
1450
				$this->getTitle(),
1451
				$context->msg( 'previousrevision' )->escaped(),
1452
				[],
1453
				[
1454
					'direction' => 'prev',
1455
					'oldid' => $oldid
1456
				] + $extraParams
1457
			)
1458
			: $context->msg( 'previousrevision' )->escaped();
1459
		$prevdiff = $prev
1460
			? Linker::linkKnown(
1461
				$this->getTitle(),
1462
				$context->msg( 'diff' )->escaped(),
1463
				[],
1464
				[
1465
					'diff' => 'prev',
1466
					'oldid' => $oldid
1467
				] + $extraParams
1468
			)
1469
			: $context->msg( 'diff' )->escaped();
1470
		$nextlink = $current
1471
			? $context->msg( 'nextrevision' )->escaped()
1472
			: Linker::linkKnown(
1473
				$this->getTitle(),
1474
				$context->msg( 'nextrevision' )->escaped(),
1475
				[],
1476
				[
1477
					'direction' => 'next',
1478
					'oldid' => $oldid
1479
				] + $extraParams
1480
			);
1481
		$nextdiff = $current
1482
			? $context->msg( 'diff' )->escaped()
1483
			: Linker::linkKnown(
1484
				$this->getTitle(),
1485
				$context->msg( 'diff' )->escaped(),
1486
				[],
1487
				[
1488
					'diff' => 'next',
1489
					'oldid' => $oldid
1490
				] + $extraParams
1491
			);
1492
1493
		$cdel = Linker::getRevDeleteLink( $user, $revision, $this->getTitle() );
0 ignored issues
show
Bug introduced by
It seems like $revision defined by \Revision::newFromId($oldid) on line 1399 can be null; however, Linker::getRevDeleteLink() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1494
		if ( $cdel !== '' ) {
1495
			$cdel .= ' ';
1496
		}
1497
1498
		// the outer div is need for styling the revision info and nav in MobileFrontend
1499
		$outputPage->addSubtitle( "<div class=\"mw-revision\">" . $revisionInfo .
1500
			"<div id=\"mw-revision-nav\">" . $cdel .
1501
			$context->msg( 'revision-nav' )->rawParams(
1502
				$prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1503
			)->escaped() . "</div></div>" );
1504
	}
1505
1506
	/**
1507
	 * Return the HTML for the top of a redirect page
1508
	 *
1509
	 * Chances are you should just be using the ParserOutput from
1510
	 * WikitextContent::getParserOutput instead of calling this for redirects.
1511
	 *
1512
	 * @param Title|array $target Destination(s) to redirect
1513
	 * @param bool $appendSubtitle [optional]
1514
	 * @param bool $forceKnown Should the image be shown as a bluelink regardless of existence?
1515
	 * @return string Containing HTML with redirect link
1516
	 */
1517
	public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
1518
		$lang = $this->getTitle()->getPageLanguage();
1519
		$out = $this->getContext()->getOutput();
1520
		if ( $appendSubtitle ) {
1521
			$out->addSubtitle( wfMessage( 'redirectpagesub' ) );
1522
		}
1523
		$out->addModuleStyles( 'mediawiki.action.view.redirectPage' );
1524
		return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
1525
	}
1526
1527
	/**
1528
	 * Return the HTML for the top of a redirect page
1529
	 *
1530
	 * Chances are you should just be using the ParserOutput from
1531
	 * WikitextContent::getParserOutput instead of calling this for redirects.
1532
	 *
1533
	 * @since 1.23
1534
	 * @param Language $lang
1535
	 * @param Title|array $target Destination(s) to redirect
1536
	 * @param bool $forceKnown Should the image be shown as a bluelink regardless of existence?
1537
	 * @return string Containing HTML with redirect link
1538
	 */
1539
	public static function getRedirectHeaderHtml( Language $lang, $target, $forceKnown = false ) {
1540
		if ( !is_array( $target ) ) {
1541
			$target = [ $target ];
1542
		}
1543
1544
		$html = '<ul class="redirectText">';
1545
		/** @var Title $title */
1546
		foreach ( $target as $title ) {
1547
			$html .= '<li>' . Linker::link(
1548
				$title,
1549
				htmlspecialchars( $title->getFullText() ),
1550
				[],
1551
				// Make sure wiki page redirects are not followed
1552
				$title->isRedirect() ? [ 'redirect' => 'no' ] : [],
1553
				( $forceKnown ? [ 'known', 'noclasses' ] : [] )
1554
			) . '</li>';
1555
		}
1556
		$html .= '</ul>';
1557
1558
		$redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->escaped();
1559
1560
		return '<div class="redirectMsg">' .
1561
			'<p>' . $redirectToText . '</p>' .
1562
			$html .
1563
			'</div>';
1564
	}
1565
1566
	/**
1567
	 * Adds help link with an icon via page indicators.
1568
	 * Link target can be overridden by a local message containing a wikilink:
1569
	 * the message key is: 'namespace-' + namespace number + '-helppage'.
1570
	 * @param string $to Target MediaWiki.org page title or encoded URL.
1571
	 * @param bool $overrideBaseUrl Whether $url is a full URL, to avoid MW.o.
1572
	 * @since 1.25
1573
	 */
1574 View Code Duplication
	public function addHelpLink( $to, $overrideBaseUrl = false ) {
1575
		$msg = wfMessage(
1576
			'namespace-' . $this->getTitle()->getNamespace() . '-helppage'
1577
		);
1578
1579
		$out = $this->getContext()->getOutput();
1580
		if ( !$msg->isDisabled() ) {
1581
			$helpUrl = Skin::makeUrl( $msg->plain() );
1582
			$out->addHelpLink( $helpUrl, true );
1583
		} else {
1584
			$out->addHelpLink( $to, $overrideBaseUrl );
1585
		}
1586
	}
1587
1588
	/**
1589
	 * Handle action=render
1590
	 */
1591
	public function render() {
1592
		$this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
1593
		$this->getContext()->getOutput()->setArticleBodyOnly( true );
1594
		$this->getContext()->getOutput()->enableSectionEditLinks( false );
1595
		$this->view();
1596
	}
1597
1598
	/**
1599
	 * action=protect handler
1600
	 */
1601
	public function protect() {
1602
		$form = new ProtectionForm( $this );
1603
		$form->execute();
1604
	}
1605
1606
	/**
1607
	 * action=unprotect handler (alias)
1608
	 */
1609
	public function unprotect() {
1610
		$this->protect();
1611
	}
1612
1613
	/**
1614
	 * UI entry point for page deletion
1615
	 */
1616
	public function delete() {
1617
		# This code desperately needs to be totally rewritten
1618
1619
		$title = $this->getTitle();
1620
		$context = $this->getContext();
1621
		$user = $context->getUser();
1622
		$request = $context->getRequest();
1623
1624
		# Check permissions
1625
		$permissionErrors = $title->getUserPermissionsErrors( 'delete', $user );
1626
		if ( count( $permissionErrors ) ) {
1627
			throw new PermissionsError( 'delete', $permissionErrors );
1628
		}
1629
1630
		# Read-only check...
1631
		if ( wfReadOnly() ) {
1632
			throw new ReadOnlyError;
1633
		}
1634
1635
		# Better double-check that it hasn't been deleted yet!
1636
		$this->mPage->loadPageData(
1637
			$request->wasPosted() ? WikiPage::READ_LATEST : WikiPage::READ_NORMAL
1638
		);
1639
		if ( !$this->mPage->exists() ) {
1640
			$deleteLogPage = new LogPage( 'delete' );
1641
			$outputPage = $context->getOutput();
1642
			$outputPage->setPageTitle( $context->msg( 'cannotdelete-title', $title->getPrefixedText() ) );
1643
			$outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
1644
					[ 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) ]
1645
				);
1646
			$outputPage->addHTML(
1647
				Xml::element( 'h2', null, $deleteLogPage->getName()->text() )
1648
			);
1649
			LogEventsList::showLogExtract(
1650
				$outputPage,
1651
				'delete',
1652
				$title
1653
			);
1654
1655
			return;
1656
		}
1657
1658
		$deleteReasonList = $request->getText( 'wpDeleteReasonList', 'other' );
1659
		$deleteReason = $request->getText( 'wpReason' );
1660
1661 View Code Duplication
		if ( $deleteReasonList == 'other' ) {
1662
			$reason = $deleteReason;
1663
		} elseif ( $deleteReason != '' ) {
1664
			// Entry from drop down menu + additional comment
1665
			$colonseparator = wfMessage( 'colon-separator' )->inContentLanguage()->text();
1666
			$reason = $deleteReasonList . $colonseparator . $deleteReason;
1667
		} else {
1668
			$reason = $deleteReasonList;
1669
		}
1670
1671
		if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'wpEditToken' ),
1672
			[ 'delete', $this->getTitle()->getPrefixedText() ] )
1673
		) {
1674
			# Flag to hide all contents of the archived revisions
1675
			$suppress = $request->getVal( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' );
0 ignored issues
show
Bug Best Practice introduced by
The expression $request->getVal('wpSuppress') of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1676
1677
			$this->doDelete( $reason, $suppress );
1678
1679
			WatchAction::doWatchOrUnwatch( $request->getCheck( 'wpWatch' ), $title, $user );
1680
1681
			return;
1682
		}
1683
1684
		// Generate deletion reason
1685
		$hasHistory = false;
1686
		if ( !$reason ) {
1687
			try {
1688
				$reason = $this->generateReason( $hasHistory );
1689
			} catch ( Exception $e ) {
1690
				# if a page is horribly broken, we still want to be able to
1691
				# delete it. So be lenient about errors here.
1692
				wfDebug( "Error while building auto delete summary: $e" );
1693
				$reason = '';
1694
			}
1695
		}
1696
1697
		// If the page has a history, insert a warning
1698
		if ( $hasHistory ) {
1699
			$title = $this->getTitle();
1700
1701
			// The following can use the real revision count as this is only being shown for users
1702
			// that can delete this page.
1703
			// This, as a side-effect, also makes sure that the following query isn't being run for
1704
			// pages with a larger history, unless the user has the 'bigdelete' right
1705
			// (and is about to delete this page).
1706
			$dbr = wfGetDB( DB_REPLICA );
1707
			$revisions = $edits = (int)$dbr->selectField(
0 ignored issues
show
Unused Code introduced by
$edits is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1708
				'revision',
1709
				'COUNT(rev_page)',
1710
				[ 'rev_page' => $title->getArticleID() ],
1711
				__METHOD__
1712
			);
1713
1714
			// @todo FIXME: i18n issue/patchwork message
1715
			$context->getOutput()->addHTML(
1716
				'<strong class="mw-delete-warning-revisions">' .
1717
				$context->msg( 'historywarning' )->numParams( $revisions )->parse() .
1718
				$context->msg( 'word-separator' )->escaped() . Linker::linkKnown( $title,
1719
					$context->msg( 'history' )->escaped(),
1720
					[],
1721
					[ 'action' => 'history' ] ) .
1722
				'</strong>'
1723
			);
1724
1725
			if ( $title->isBigDeletion() ) {
1726
				global $wgDeleteRevisionsLimit;
1727
				$context->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
1728
					[
1729
						'delete-warning-toobig',
1730
						$context->getLanguage()->formatNum( $wgDeleteRevisionsLimit )
1731
					]
1732
				);
1733
			}
1734
		}
1735
1736
		$this->confirmDelete( $reason );
0 ignored issues
show
Security Bug introduced by
It seems like $reason defined by $this->generateReason($hasHistory) on line 1688 can also be of type false; however, Article::confirmDelete() does only seem to accept string, 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...
1737
	}
1738
1739
	/**
1740
	 * Output deletion confirmation dialog
1741
	 * @todo FIXME: Move to another file?
1742
	 * @param string $reason Prefilled reason
1743
	 */
1744
	public function confirmDelete( $reason ) {
1745
		wfDebug( "Article::confirmDelete\n" );
1746
1747
		$title = $this->getTitle();
1748
		$ctx = $this->getContext();
1749
		$outputPage = $ctx->getOutput();
1750
		$useMediaWikiUIEverywhere = $ctx->getConfig()->get( 'UseMediaWikiUIEverywhere' );
1751
		$outputPage->setPageTitle( wfMessage( 'delete-confirm', $title->getPrefixedText() ) );
1752
		$outputPage->addBacklinkSubtitle( $title );
1753
		$outputPage->setRobotPolicy( 'noindex,nofollow' );
1754
		$backlinkCache = $title->getBacklinkCache();
1755
		if ( $backlinkCache->hasLinks( 'pagelinks' ) || $backlinkCache->hasLinks( 'templatelinks' ) ) {
1756
			$outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1757
				'deleting-backlinks-warning' );
1758
		}
1759
		$outputPage->addWikiMsg( 'confirmdeletetext' );
1760
1761
		Hooks::run( 'ArticleConfirmDelete', [ $this, $outputPage, &$reason ] );
1762
1763
		$user = $this->getContext()->getUser();
1764
1765
		if ( $user->isAllowed( 'suppressrevision' ) ) {
1766
			$suppress = Html::openElement( 'div', [ 'id' => 'wpDeleteSuppressRow' ] ) .
1767
				Xml::checkLabel( wfMessage( 'revdelete-suppress' )->text(),
1768
					'wpSuppress', 'wpSuppress', false, [ 'tabindex' => '4' ] ) .
1769
				Html::closeElement( 'div' );
1770
		} else {
1771
			$suppress = '';
1772
		}
1773
		$checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $title );
1774
1775
		$form = Html::openElement( 'form', [ 'method' => 'post',
1776
			'action' => $title->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ] ) .
1777
			Html::openElement( 'fieldset', [ 'id' => 'mw-delete-table' ] ) .
1778
			Html::element( 'legend', null, wfMessage( 'delete-legend' )->text() ) .
1779
			Html::openElement( 'div', [ 'id' => 'mw-deleteconfirm-table' ] ) .
1780
			Html::openElement( 'div', [ 'id' => 'wpDeleteReasonListRow' ] ) .
1781
			Html::label( wfMessage( 'deletecomment' )->text(), 'wpDeleteReasonList' ) .
1782
			'&nbsp;' .
1783
			Xml::listDropDown(
1784
				'wpDeleteReasonList',
1785
				wfMessage( 'deletereason-dropdown' )->inContentLanguage()->text(),
1786
				wfMessage( 'deletereasonotherlist' )->inContentLanguage()->text(),
1787
				'',
1788
				'wpReasonDropDown',
1789
				1
1790
			) .
1791
			Html::closeElement( 'div' ) .
1792
			Html::openElement( 'div', [ 'id' => 'wpDeleteReasonRow' ] ) .
1793
			Html::label( wfMessage( 'deleteotherreason' )->text(), 'wpReason' ) .
1794
			'&nbsp;' .
1795
			Html::input( 'wpReason', $reason, 'text', [
1796
				'size' => '60',
1797
				'maxlength' => '255',
1798
				'tabindex' => '2',
1799
				'id' => 'wpReason',
1800
				'class' => 'mw-ui-input-inline',
1801
				'autofocus'
1802
			] ) .
1803
			Html::closeElement( 'div' );
1804
1805
		# Disallow watching if user is not logged in
1806 View Code Duplication
		if ( $user->isLoggedIn() ) {
1807
			$form .=
1808
					Xml::checkLabel( wfMessage( 'watchthis' )->text(),
1809
						'wpWatch', 'wpWatch', $checkWatch, [ 'tabindex' => '3' ] );
1810
		}
1811
1812
		$form .=
1813
				Html::openElement( 'div' ) .
1814
				$suppress .
1815
					Xml::submitButton( wfMessage( 'deletepage' )->text(),
1816
						[
1817
							'name' => 'wpConfirmB',
1818
							'id' => 'wpConfirmB',
1819
							'tabindex' => '5',
1820
							'class' => $useMediaWikiUIEverywhere ? 'mw-ui-button mw-ui-destructive' : '',
1821
						]
1822
					) .
1823
				Html::closeElement( 'div' ) .
1824
			Html::closeElement( 'div' ) .
1825
			Xml::closeElement( 'fieldset' ) .
1826
			Html::hidden(
1827
				'wpEditToken',
1828
				$user->getEditToken( [ 'delete', $title->getPrefixedText() ] )
1829
			) .
1830
			Xml::closeElement( 'form' );
1831
1832 View Code Duplication
			if ( $user->isAllowed( 'editinterface' ) ) {
1833
				$link = Linker::linkKnown(
1834
					$ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->getTitle(),
1835
					wfMessage( 'delete-edit-reasonlist' )->escaped(),
1836
					[],
1837
					[ 'action' => 'edit' ]
1838
				);
1839
				$form .= '<p class="mw-delete-editreasons">' . $link . '</p>';
1840
			}
1841
1842
		$outputPage->addHTML( $form );
1843
1844
		$deleteLogPage = new LogPage( 'delete' );
1845
		$outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
1846
		LogEventsList::showLogExtract( $outputPage, 'delete', $title );
1847
	}
1848
1849
	/**
1850
	 * Perform a deletion and output success or failure messages
1851
	 * @param string $reason
1852
	 * @param bool $suppress
1853
	 */
1854
	public function doDelete( $reason, $suppress = false ) {
1855
		$error = '';
1856
		$context = $this->getContext();
1857
		$outputPage = $context->getOutput();
1858
		$user = $context->getUser();
1859
		$status = $this->mPage->doDeleteArticleReal( $reason, $suppress, 0, true, $error, $user );
1860
1861
		if ( $status->isGood() ) {
1862
			$deleted = $this->getTitle()->getPrefixedText();
1863
1864
			$outputPage->setPageTitle( wfMessage( 'actioncomplete' ) );
1865
			$outputPage->setRobotPolicy( 'noindex,nofollow' );
1866
1867
			$loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]';
1868
1869
			$outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
1870
1871
			Hooks::run( 'ArticleDeleteAfterSuccess', [ $this->getTitle(), $outputPage ] );
1872
1873
			$outputPage->returnToMain( false );
1874
		} else {
1875
			$outputPage->setPageTitle(
1876
				wfMessage( 'cannotdelete-title',
1877
					$this->getTitle()->getPrefixedText() )
1878
			);
1879
1880
			if ( $error == '' ) {
1881
				$outputPage->addWikiText(
1882
					"<div class=\"error mw-error-cannotdelete\">\n" . $status->getWikiText() . "\n</div>"
1883
				);
1884
				$deleteLogPage = new LogPage( 'delete' );
1885
				$outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
1886
1887
				LogEventsList::showLogExtract(
1888
					$outputPage,
1889
					'delete',
1890
					$this->getTitle()
1891
				);
1892
			} else {
1893
				$outputPage->addHTML( $error );
1894
			}
1895
		}
1896
	}
1897
1898
	/* Caching functions */
1899
1900
	/**
1901
	 * checkLastModified returns true if it has taken care of all
1902
	 * output to the client that is necessary for this request.
1903
	 * (that is, it has sent a cached version of the page)
1904
	 *
1905
	 * @return bool True if cached version send, false otherwise
1906
	 */
1907
	protected function tryFileCache() {
1908
		static $called = false;
1909
1910
		if ( $called ) {
1911
			wfDebug( "Article::tryFileCache(): called twice!?\n" );
1912
			return false;
1913
		}
1914
1915
		$called = true;
1916
		if ( $this->isFileCacheable() ) {
1917
			$cache = new HTMLFileCache( $this->getTitle(), 'view' );
1918
			if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
1919
				wfDebug( "Article::tryFileCache(): about to load file\n" );
1920
				$cache->loadFromFileCache( $this->getContext() );
1921
				return true;
1922
			} else {
1923
				wfDebug( "Article::tryFileCache(): starting buffer\n" );
1924
				ob_start( [ &$cache, 'saveToFileCache' ] );
1925
			}
1926
		} else {
1927
			wfDebug( "Article::tryFileCache(): not cacheable\n" );
1928
		}
1929
1930
		return false;
1931
	}
1932
1933
	/**
1934
	 * Check if the page can be cached
1935
	 * @return bool
1936
	 */
1937
	public function isFileCacheable() {
1938
		$cacheable = false;
1939
1940
		if ( HTMLFileCache::useFileCache( $this->getContext() ) ) {
1941
			$cacheable = $this->mPage->getId()
1942
				&& !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
1943
			// Extension may have reason to disable file caching on some pages.
1944
			if ( $cacheable ) {
1945
				$cacheable = Hooks::run( 'IsFileCacheable', [ &$this ] );
1946
			}
1947
		}
1948
1949
		return $cacheable;
1950
	}
1951
1952
	/**#@-*/
1953
1954
	/**
1955
	 * Lightweight method to get the parser output for a page, checking the parser cache
1956
	 * and so on. Doesn't consider most of the stuff that WikiPage::view is forced to
1957
	 * consider, so it's not appropriate to use there.
1958
	 *
1959
	 * @since 1.16 (r52326) for LiquidThreads
1960
	 *
1961
	 * @param int|null $oldid Revision ID or null
1962
	 * @param User $user The relevant user
1963
	 * @return ParserOutput|bool ParserOutput or false if the given revision ID is not found
1964
	 */
1965
	public function getParserOutput( $oldid = null, User $user = null ) {
1966
		// XXX: bypasses mParserOptions and thus setParserOptions()
1967
1968
		if ( $user === null ) {
1969
			$parserOptions = $this->getParserOptions();
1970
		} else {
1971
			$parserOptions = $this->mPage->makeParserOptions( $user );
1972
		}
1973
1974
		return $this->mPage->getParserOutput( $parserOptions, $oldid );
1975
	}
1976
1977
	/**
1978
	 * Override the ParserOptions used to render the primary article wikitext.
1979
	 *
1980
	 * @param ParserOptions $options
1981
	 * @throws MWException If the parser options where already initialized.
1982
	 */
1983
	public function setParserOptions( ParserOptions $options ) {
1984
		if ( $this->mParserOptions ) {
1985
			throw new MWException( "can't change parser options after they have already been set" );
1986
		}
1987
1988
		// clone, so if $options is modified later, it doesn't confuse the parser cache.
1989
		$this->mParserOptions = clone $options;
1990
	}
1991
1992
	/**
1993
	 * Get parser options suitable for rendering the primary article wikitext
1994
	 * @return ParserOptions
1995
	 */
1996
	public function getParserOptions() {
1997
		if ( !$this->mParserOptions ) {
1998
			$this->mParserOptions = $this->mPage->makeParserOptions( $this->getContext() );
1999
		}
2000
		// Clone to allow modifications of the return value without affecting cache
2001
		return clone $this->mParserOptions;
2002
	}
2003
2004
	/**
2005
	 * Sets the context this Article is executed in
2006
	 *
2007
	 * @param IContextSource $context
2008
	 * @since 1.18
2009
	 */
2010
	public function setContext( $context ) {
2011
		$this->mContext = $context;
2012
	}
2013
2014
	/**
2015
	 * Gets the context this Article is executed in
2016
	 *
2017
	 * @return IContextSource
2018
	 * @since 1.18
2019
	 */
2020 View Code Duplication
	public function getContext() {
2021
		if ( $this->mContext instanceof IContextSource ) {
2022
			return $this->mContext;
2023
		} else {
2024
			wfDebug( __METHOD__ . " called and \$mContext is null. " .
2025
				"Return RequestContext::getMain(); for sanity\n" );
2026
			return RequestContext::getMain();
2027
		}
2028
	}
2029
2030
	/**
2031
	 * Use PHP's magic __get handler to handle accessing of
2032
	 * raw WikiPage fields for backwards compatibility.
2033
	 *
2034
	 * @param string $fname Field name
2035
	 * @return mixed
2036
	 */
2037
	public function __get( $fname ) {
2038
		if ( property_exists( $this->mPage, $fname ) ) {
2039
			# wfWarn( "Access to raw $fname field " . __CLASS__ );
2040
			return $this->mPage->$fname;
2041
		}
2042
		trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
2043
	}
2044
2045
	/**
2046
	 * Use PHP's magic __set handler to handle setting of
2047
	 * raw WikiPage fields for backwards compatibility.
2048
	 *
2049
	 * @param string $fname Field name
2050
	 * @param mixed $fvalue New value
2051
	 */
2052
	public function __set( $fname, $fvalue ) {
2053
		if ( property_exists( $this->mPage, $fname ) ) {
2054
			# wfWarn( "Access to raw $fname field of " . __CLASS__ );
2055
			$this->mPage->$fname = $fvalue;
2056
		// Note: extensions may want to toss on new fields
2057
		} elseif ( !in_array( $fname, [ 'mContext', 'mPage' ] ) ) {
2058
			$this->mPage->$fname = $fvalue;
2059
		} else {
2060
			trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
2061
		}
2062
	}
2063
2064
	/**
2065
	 * Call to WikiPage function for backwards compatibility.
2066
	 * @see WikiPage::checkFlags
2067
	 */
2068
	public function checkFlags( $flags ) {
2069
		return $this->mPage->checkFlags( $flags );
2070
	}
2071
2072
	/**
2073
	 * Call to WikiPage function for backwards compatibility.
2074
	 * @see WikiPage::checkTouched
2075
	 */
2076
	public function checkTouched() {
2077
		return $this->mPage->checkTouched();
2078
	}
2079
2080
	/**
2081
	 * Call to WikiPage function for backwards compatibility.
2082
	 * @see WikiPage::clearPreparedEdit
2083
	 */
2084
	public function clearPreparedEdit() {
2085
		$this->mPage->clearPreparedEdit();
2086
	}
2087
2088
	/**
2089
	 * Call to WikiPage function for backwards compatibility.
2090
	 * @see WikiPage::doDeleteArticleReal
2091
	 */
2092
	public function doDeleteArticleReal(
2093
		$reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', User $user = null
2094
	) {
2095
		return $this->mPage->doDeleteArticleReal(
2096
			$reason, $suppress, $u1, $u2, $error, $user
2097
		);
2098
	}
2099
2100
	/**
2101
	 * Call to WikiPage function for backwards compatibility.
2102
	 * @see WikiPage::doDeleteUpdates
2103
	 */
2104
	public function doDeleteUpdates( $id, Content $content = null ) {
2105
		return $this->mPage->doDeleteUpdates( $id, $content );
2106
	}
2107
2108
	/**
2109
	 * Call to WikiPage function for backwards compatibility.
2110
	 * @see WikiPage::doEdit
2111
	 */
2112
	public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
2113
		ContentHandler::deprecated( __METHOD__, '1.21' );
2114
		return $this->mPage->doEdit( $text, $summary, $flags, $baseRevId, $user );
2115
	}
2116
2117
	/**
2118
	 * Call to WikiPage function for backwards compatibility.
2119
	 * @see WikiPage::doEditContent
2120
	 */
2121
	public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
2122
		User $user = null, $serialFormat = null
2123
	) {
2124
		return $this->mPage->doEditContent( $content, $summary, $flags, $baseRevId,
2125
			$user, $serialFormat
2126
		);
2127
	}
2128
2129
	/**
2130
	 * Call to WikiPage function for backwards compatibility.
2131
	 * @see WikiPage::doEditUpdates
2132
	 */
2133
	public function doEditUpdates( Revision $revision, User $user, array $options = [] ) {
2134
		return $this->mPage->doEditUpdates( $revision, $user, $options );
2135
	}
2136
2137
	/**
2138
	 * Call to WikiPage function for backwards compatibility.
2139
	 * @see WikiPage::doPurge
2140
	 */
2141
	public function doPurge( $flags = WikiPage::PURGE_ALL ) {
2142
		return $this->mPage->doPurge( $flags );
2143
	}
2144
2145
	/**
2146
	 * Call to WikiPage function for backwards compatibility.
2147
	 * @see WikiPage::getLastPurgeTimestamp
2148
	 */
2149
	public function getLastPurgeTimestamp() {
2150
		return $this->mPage->getLastPurgeTimestamp();
2151
	}
2152
2153
	/**
2154
	 * Call to WikiPage function for backwards compatibility.
2155
	 * @see WikiPage::doQuickEditContent
2156
	 */
2157
	public function doQuickEditContent(
2158
		Content $content, User $user, $comment = '', $minor = false, $serialFormat = null
2159
	) {
2160
		return $this->mPage->doQuickEditContent(
2161
			$content, $user, $comment, $minor, $serialFormat
2162
		);
2163
	}
2164
2165
	/**
2166
	 * Call to WikiPage function for backwards compatibility.
2167
	 * @see WikiPage::doViewUpdates
2168
	 */
2169
	public function doViewUpdates( User $user, $oldid = 0 ) {
2170
		$this->mPage->doViewUpdates( $user, $oldid );
2171
	}
2172
2173
	/**
2174
	 * Call to WikiPage function for backwards compatibility.
2175
	 * @see WikiPage::exists
2176
	 */
2177
	public function exists() {
2178
		return $this->mPage->exists();
2179
	}
2180
2181
	/**
2182
	 * Call to WikiPage function for backwards compatibility.
2183
	 * @see WikiPage::followRedirect
2184
	 */
2185
	public function followRedirect() {
2186
		return $this->mPage->followRedirect();
2187
	}
2188
2189
	/**
2190
	 * Call to WikiPage function for backwards compatibility.
2191
	 * @see ContentHandler::getActionOverrides
2192
	 */
2193
	public function getActionOverrides() {
2194
		return $this->mPage->getActionOverrides();
2195
	}
2196
2197
	/**
2198
	 * Call to WikiPage function for backwards compatibility.
2199
	 * @see WikiPage::getAutoDeleteReason
2200
	 */
2201
	public function getAutoDeleteReason( &$hasHistory ) {
2202
		return $this->mPage->getAutoDeleteReason( $hasHistory );
2203
	}
2204
2205
	/**
2206
	 * Call to WikiPage function for backwards compatibility.
2207
	 * @see WikiPage::getCategories
2208
	 */
2209
	public function getCategories() {
2210
		return $this->mPage->getCategories();
2211
	}
2212
2213
	/**
2214
	 * Call to WikiPage function for backwards compatibility.
2215
	 * @see WikiPage::getComment
2216
	 */
2217
	public function getComment( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2218
		return $this->mPage->getComment( $audience, $user );
2219
	}
2220
2221
	/**
2222
	 * Call to WikiPage function for backwards compatibility.
2223
	 * @see WikiPage::getContentHandler
2224
	 */
2225
	public function getContentHandler() {
2226
		return $this->mPage->getContentHandler();
2227
	}
2228
2229
	/**
2230
	 * Call to WikiPage function for backwards compatibility.
2231
	 * @see WikiPage::getContentModel
2232
	 */
2233
	public function getContentModel() {
2234
		return $this->mPage->getContentModel();
2235
	}
2236
2237
	/**
2238
	 * Call to WikiPage function for backwards compatibility.
2239
	 * @see WikiPage::getContributors
2240
	 */
2241
	public function getContributors() {
2242
		return $this->mPage->getContributors();
2243
	}
2244
2245
	/**
2246
	 * Call to WikiPage function for backwards compatibility.
2247
	 * @see WikiPage::getCreator
2248
	 */
2249
	public function getCreator( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2250
		return $this->mPage->getCreator( $audience, $user );
2251
	}
2252
2253
	/**
2254
	 * Call to WikiPage function for backwards compatibility.
2255
	 * @see WikiPage::getDeletionUpdates
2256
	 */
2257
	public function getDeletionUpdates( Content $content = null ) {
2258
		return $this->mPage->getDeletionUpdates( $content );
2259
	}
2260
2261
	/**
2262
	 * Call to WikiPage function for backwards compatibility.
2263
	 * @see WikiPage::getHiddenCategories
2264
	 */
2265
	public function getHiddenCategories() {
2266
		return $this->mPage->getHiddenCategories();
2267
	}
2268
2269
	/**
2270
	 * Call to WikiPage function for backwards compatibility.
2271
	 * @see WikiPage::getId
2272
	 */
2273
	public function getId() {
2274
		return $this->mPage->getId();
2275
	}
2276
2277
	/**
2278
	 * Call to WikiPage function for backwards compatibility.
2279
	 * @see WikiPage::getLatest
2280
	 */
2281
	public function getLatest() {
2282
		return $this->mPage->getLatest();
2283
	}
2284
2285
	/**
2286
	 * Call to WikiPage function for backwards compatibility.
2287
	 * @see WikiPage::getLinksTimestamp
2288
	 */
2289
	public function getLinksTimestamp() {
2290
		return $this->mPage->getLinksTimestamp();
2291
	}
2292
2293
	/**
2294
	 * Call to WikiPage function for backwards compatibility.
2295
	 * @see WikiPage::getMinorEdit
2296
	 */
2297
	public function getMinorEdit() {
2298
		return $this->mPage->getMinorEdit();
2299
	}
2300
2301
	/**
2302
	 * Call to WikiPage function for backwards compatibility.
2303
	 * @see WikiPage::getOldestRevision
2304
	 */
2305
	public function getOldestRevision() {
2306
		return $this->mPage->getOldestRevision();
2307
	}
2308
2309
	/**
2310
	 * Call to WikiPage function for backwards compatibility.
2311
	 * @see WikiPage::getRedirectTarget
2312
	 */
2313
	public function getRedirectTarget() {
2314
		return $this->mPage->getRedirectTarget();
2315
	}
2316
2317
	/**
2318
	 * Call to WikiPage function for backwards compatibility.
2319
	 * @see WikiPage::getRedirectURL
2320
	 */
2321
	public function getRedirectURL( $rt ) {
2322
		return $this->mPage->getRedirectURL( $rt );
2323
	}
2324
2325
	/**
2326
	 * Call to WikiPage function for backwards compatibility.
2327
	 * @see WikiPage::getRevision
2328
	 */
2329
	public function getRevision() {
2330
		return $this->mPage->getRevision();
2331
	}
2332
2333
	/**
2334
	 * Call to WikiPage function for backwards compatibility.
2335
	 * @see WikiPage::getText
2336
	 */
2337
	public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2338
		ContentHandler::deprecated( __METHOD__, '1.21' );
2339
		return $this->mPage->getText( $audience, $user );
2340
	}
2341
2342
	/**
2343
	 * Call to WikiPage function for backwards compatibility.
2344
	 * @see WikiPage::getTimestamp
2345
	 */
2346
	public function getTimestamp() {
2347
		return $this->mPage->getTimestamp();
2348
	}
2349
2350
	/**
2351
	 * Call to WikiPage function for backwards compatibility.
2352
	 * @see WikiPage::getTouched
2353
	 */
2354
	public function getTouched() {
2355
		return $this->mPage->getTouched();
2356
	}
2357
2358
	/**
2359
	 * Call to WikiPage function for backwards compatibility.
2360
	 * @see WikiPage::getUndoContent
2361
	 */
2362
	public function getUndoContent( Revision $undo, Revision $undoafter = null ) {
2363
		return $this->mPage->getUndoContent( $undo, $undoafter );
2364
	}
2365
2366
	/**
2367
	 * Call to WikiPage function for backwards compatibility.
2368
	 * @see WikiPage::getUser
2369
	 */
2370
	public function getUser( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2371
		return $this->mPage->getUser( $audience, $user );
2372
	}
2373
2374
	/**
2375
	 * Call to WikiPage function for backwards compatibility.
2376
	 * @see WikiPage::getUserText
2377
	 */
2378
	public function getUserText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2379
		return $this->mPage->getUserText( $audience, $user );
2380
	}
2381
2382
	/**
2383
	 * Call to WikiPage function for backwards compatibility.
2384
	 * @see WikiPage::hasViewableContent
2385
	 */
2386
	public function hasViewableContent() {
2387
		return $this->mPage->hasViewableContent();
2388
	}
2389
2390
	/**
2391
	 * Call to WikiPage function for backwards compatibility.
2392
	 * @see WikiPage::insertOn
2393
	 */
2394
	public function insertOn( $dbw, $pageId = null ) {
2395
		return $this->mPage->insertOn( $dbw, $pageId );
2396
	}
2397
2398
	/**
2399
	 * Call to WikiPage function for backwards compatibility.
2400
	 * @see WikiPage::insertProtectNullRevision
2401
	 */
2402
	public function insertProtectNullRevision( $revCommentMsg, array $limit,
2403
		array $expiry, $cascade, $reason, $user = null
2404
	) {
2405
		return $this->mPage->insertProtectNullRevision( $revCommentMsg, $limit,
2406
			$expiry, $cascade, $reason, $user
2407
		);
2408
	}
2409
2410
	/**
2411
	 * Call to WikiPage function for backwards compatibility.
2412
	 * @see WikiPage::insertRedirect
2413
	 */
2414
	public function insertRedirect() {
2415
		return $this->mPage->insertRedirect();
2416
	}
2417
2418
	/**
2419
	 * Call to WikiPage function for backwards compatibility.
2420
	 * @see WikiPage::insertRedirectEntry
2421
	 */
2422
	public function insertRedirectEntry( Title $rt, $oldLatest = null ) {
2423
		return $this->mPage->insertRedirectEntry( $rt, $oldLatest );
2424
	}
2425
2426
	/**
2427
	 * Call to WikiPage function for backwards compatibility.
2428
	 * @see WikiPage::isCountable
2429
	 */
2430
	public function isCountable( $editInfo = false ) {
2431
		return $this->mPage->isCountable( $editInfo );
2432
	}
2433
2434
	/**
2435
	 * Call to WikiPage function for backwards compatibility.
2436
	 * @see WikiPage::isRedirect
2437
	 */
2438
	public function isRedirect() {
2439
		return $this->mPage->isRedirect();
2440
	}
2441
2442
	/**
2443
	 * Call to WikiPage function for backwards compatibility.
2444
	 * @see WikiPage::loadFromRow
2445
	 */
2446
	public function loadFromRow( $data, $from ) {
2447
		return $this->mPage->loadFromRow( $data, $from );
2448
	}
2449
2450
	/**
2451
	 * Call to WikiPage function for backwards compatibility.
2452
	 * @see WikiPage::loadPageData
2453
	 */
2454
	public function loadPageData( $from = 'fromdb' ) {
2455
		$this->mPage->loadPageData( $from );
2456
	}
2457
2458
	/**
2459
	 * Call to WikiPage function for backwards compatibility.
2460
	 * @see WikiPage::lockAndGetLatest
2461
	 */
2462
	public function lockAndGetLatest() {
2463
		return $this->mPage->lockAndGetLatest();
2464
	}
2465
2466
	/**
2467
	 * Call to WikiPage function for backwards compatibility.
2468
	 * @see WikiPage::makeParserOptions
2469
	 */
2470
	public function makeParserOptions( $context ) {
2471
		return $this->mPage->makeParserOptions( $context );
2472
	}
2473
2474
	/**
2475
	 * Call to WikiPage function for backwards compatibility.
2476
	 * @see WikiPage::pageDataFromId
2477
	 */
2478
	public function pageDataFromId( $dbr, $id, $options = [] ) {
2479
		return $this->mPage->pageDataFromId( $dbr, $id, $options );
2480
	}
2481
2482
	/**
2483
	 * Call to WikiPage function for backwards compatibility.
2484
	 * @see WikiPage::pageDataFromTitle
2485
	 */
2486
	public function pageDataFromTitle( $dbr, $title, $options = [] ) {
2487
		return $this->mPage->pageDataFromTitle( $dbr, $title, $options );
2488
	}
2489
2490
	/**
2491
	 * Call to WikiPage function for backwards compatibility.
2492
	 * @see WikiPage::prepareContentForEdit
2493
	 */
2494
	public function prepareContentForEdit(
2495
		Content $content, $revision = null, User $user = null,
2496
		$serialFormat = null, $useCache = true
2497
	) {
2498
		return $this->mPage->prepareContentForEdit(
2499
			$content, $revision, $user,
2500
			$serialFormat, $useCache
2501
		);
2502
	}
2503
2504
	/**
2505
	 * Call to WikiPage function for backwards compatibility.
2506
	 * @see WikiPage::prepareTextForEdit
2507
	 */
2508
	public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
2509
		return $this->mPage->prepareTextForEdit( $text, $revid, $user );
2510
	}
2511
2512
	/**
2513
	 * Call to WikiPage function for backwards compatibility.
2514
	 * @see WikiPage::protectDescription
2515
	 */
2516
	public function protectDescription( array $limit, array $expiry ) {
2517
		return $this->mPage->protectDescription( $limit, $expiry );
2518
	}
2519
2520
	/**
2521
	 * Call to WikiPage function for backwards compatibility.
2522
	 * @see WikiPage::protectDescriptionLog
2523
	 */
2524
	public function protectDescriptionLog( array $limit, array $expiry ) {
2525
		return $this->mPage->protectDescriptionLog( $limit, $expiry );
2526
	}
2527
2528
	/**
2529
	 * Call to WikiPage function for backwards compatibility.
2530
	 * @see WikiPage::replaceSectionAtRev
2531
	 */
2532
	public function replaceSectionAtRev( $sectionId, Content $sectionContent,
2533
		$sectionTitle = '', $baseRevId = null
2534
	) {
2535
		return $this->mPage->replaceSectionAtRev( $sectionId, $sectionContent,
2536
			$sectionTitle, $baseRevId
2537
		);
2538
	}
2539
2540
	/**
2541
	 * Call to WikiPage function for backwards compatibility.
2542
	 * @see WikiPage::replaceSectionContent
2543
	 */
2544
	public function replaceSectionContent(
2545
		$sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null
2546
	) {
2547
		return $this->mPage->replaceSectionContent(
2548
			$sectionId, $sectionContent, $sectionTitle, $edittime
2549
		);
2550
	}
2551
2552
	/**
2553
	 * Call to WikiPage function for backwards compatibility.
2554
	 * @see WikiPage::setTimestamp
2555
	 */
2556
	public function setTimestamp( $ts ) {
2557
		return $this->mPage->setTimestamp( $ts );
2558
	}
2559
2560
	/**
2561
	 * Call to WikiPage function for backwards compatibility.
2562
	 * @see WikiPage::shouldCheckParserCache
2563
	 */
2564
	public function shouldCheckParserCache( ParserOptions $parserOptions, $oldId ) {
2565
		return $this->mPage->shouldCheckParserCache( $parserOptions, $oldId );
2566
	}
2567
2568
	/**
2569
	 * Call to WikiPage function for backwards compatibility.
2570
	 * @see WikiPage::supportsSections
2571
	 */
2572
	public function supportsSections() {
2573
		return $this->mPage->supportsSections();
2574
	}
2575
2576
	/**
2577
	 * Call to WikiPage function for backwards compatibility.
2578
	 * @see WikiPage::triggerOpportunisticLinksUpdate
2579
	 */
2580
	public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) {
2581
		return $this->mPage->triggerOpportunisticLinksUpdate( $parserOutput );
2582
	}
2583
2584
	/**
2585
	 * Call to WikiPage function for backwards compatibility.
2586
	 * @see WikiPage::updateCategoryCounts
2587
	 */
2588
	public function updateCategoryCounts( array $added, array $deleted, $id = 0 ) {
2589
		return $this->mPage->updateCategoryCounts( $added, $deleted, $id );
2590
	}
2591
2592
	/**
2593
	 * Call to WikiPage function for backwards compatibility.
2594
	 * @see WikiPage::updateIfNewerOn
2595
	 */
2596
	public function updateIfNewerOn( $dbw, $revision ) {
2597
		return $this->mPage->updateIfNewerOn( $dbw, $revision );
2598
	}
2599
2600
	/**
2601
	 * Call to WikiPage function for backwards compatibility.
2602
	 * @see WikiPage::updateRedirectOn
2603
	 */
2604
	public function updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null ) {
2605
		return $this->mPage->updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null );
2606
	}
2607
2608
	/**
2609
	 * Call to WikiPage function for backwards compatibility.
2610
	 * @see WikiPage::updateRevisionOn
2611
	 */
2612
	public function updateRevisionOn( $dbw, $revision, $lastRevision = null,
2613
		$lastRevIsRedirect = null
2614
	) {
2615
		return $this->mPage->updateRevisionOn( $dbw, $revision, $lastRevision,
2616
			$lastRevIsRedirect
2617
		);
2618
	}
2619
2620
	/**
2621
	 * @param array $limit
2622
	 * @param array $expiry
2623
	 * @param bool $cascade
2624
	 * @param string $reason
2625
	 * @param User $user
2626
	 * @return Status
2627
	 */
2628
	public function doUpdateRestrictions( array $limit, array $expiry, &$cascade,
2629
		$reason, User $user
2630
	) {
2631
		return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user );
2632
	}
2633
2634
	/**
2635
	 * @param array $limit
2636
	 * @param string $reason
2637
	 * @param int $cascade
2638
	 * @param array $expiry
2639
	 * @return bool
2640
	 */
2641
	public function updateRestrictions( $limit = [], $reason = '',
2642
		&$cascade = 0, $expiry = []
2643
	) {
2644
		return $this->mPage->doUpdateRestrictions(
2645
			$limit,
2646
			$expiry,
2647
			$cascade,
2648
			$reason,
2649
			$this->getContext()->getUser()
2650
		);
2651
	}
2652
2653
	/**
2654
	 * @param string $reason
2655
	 * @param bool $suppress
2656
	 * @param int $u1 Unused
2657
	 * @param bool $u2 Unused
2658
	 * @param string $error
2659
	 * @return bool
2660
	 */
2661
	public function doDeleteArticle(
2662
		$reason, $suppress = false, $u1 = null, $u2 = null, &$error = ''
2663
	) {
2664
		return $this->mPage->doDeleteArticle( $reason, $suppress, $u1, $u2, $error );
2665
	}
2666
2667
	/**
2668
	 * @param string $fromP
2669
	 * @param string $summary
2670
	 * @param string $token
2671
	 * @param bool $bot
2672
	 * @param array $resultDetails
2673
	 * @param User|null $user
2674
	 * @return array
2675
	 */
2676
	public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user = null ) {
2677
		$user = is_null( $user ) ? $this->getContext()->getUser() : $user;
2678
		return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user );
2679
	}
2680
2681
	/**
2682
	 * @param string $fromP
2683
	 * @param string $summary
2684
	 * @param bool $bot
2685
	 * @param array $resultDetails
2686
	 * @param User|null $guser
2687
	 * @return array
2688
	 */
2689
	public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) {
2690
		$guser = is_null( $guser ) ? $this->getContext()->getUser() : $guser;
2691
		return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser );
2692
	}
2693
2694
	/**
2695
	 * @param bool $hasHistory
2696
	 * @return mixed
2697
	 */
2698
	public function generateReason( &$hasHistory ) {
2699
		$title = $this->mPage->getTitle();
2700
		$handler = ContentHandler::getForTitle( $title );
2701
		return $handler->getAutoDeleteReason( $title, $hasHistory );
2702
	}
2703
2704
	/**
2705
	 * @return array
2706
	 *
2707
	 * @deprecated since 1.24, use WikiPage::selectFields() instead
2708
	 */
2709
	public static function selectFields() {
2710
		wfDeprecated( __METHOD__, '1.24' );
2711
		return WikiPage::selectFields();
2712
	}
2713
2714
	/**
2715
	 * @param Title $title
2716
	 *
2717
	 * @deprecated since 1.24, use WikiPage::onArticleCreate() instead
2718
	 */
2719
	public static function onArticleCreate( $title ) {
2720
		wfDeprecated( __METHOD__, '1.24' );
2721
		WikiPage::onArticleCreate( $title );
2722
	}
2723
2724
	/**
2725
	 * @param Title $title
2726
	 *
2727
	 * @deprecated since 1.24, use WikiPage::onArticleDelete() instead
2728
	 */
2729
	public static function onArticleDelete( $title ) {
2730
		wfDeprecated( __METHOD__, '1.24' );
2731
		WikiPage::onArticleDelete( $title );
2732
	}
2733
2734
	/**
2735
	 * @param Title $title
2736
	 *
2737
	 * @deprecated since 1.24, use WikiPage::onArticleEdit() instead
2738
	 */
2739
	public static function onArticleEdit( $title ) {
2740
		wfDeprecated( __METHOD__, '1.24' );
2741
		WikiPage::onArticleEdit( $title );
2742
	}
2743
2744
	/**
2745
	 * @param string $oldtext
2746
	 * @param string $newtext
2747
	 * @param int $flags
2748
	 * @return string
2749
	 * @deprecated since 1.21, use ContentHandler::getAutosummary() instead
2750
	 */
2751
	public static function getAutosummary( $oldtext, $newtext, $flags ) {
2752
		return WikiPage::getAutosummary( $oldtext, $newtext, $flags );
2753
	}
2754
	// ******
2755
}
2756