Completed
Branch master (e9bc15)
by
unknown
32:19
created

Article::doQuickEditContent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 5
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0

1 Method

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