Completed
Branch master (bddb40)
by
unknown
28:50
created

Article::insertProtectNullRevision()   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 6
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * User interface for page actions.
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 * http://www.gnu.org/copyleft/gpl.html
19
 *
20
 * @file
21
 */
22
23
/**
24
 * Class for viewing MediaWiki article and history.
25
 *
26
 * This maintains WikiPage functions for backwards compatibility.
27
 *
28
 * @todo Move and rewrite code to an Action class
29
 *
30
 * See design.txt for an overview.
31
 * Note: edit user interface and cache support functions have been
32
 * moved to separate EditPage and HTMLFileCache classes.
33
 */
34
class Article implements Page {
35
	/** @var IContextSource The context this Article is executed in */
36
	protected $mContext;
37
38
	/** @var WikiPage The WikiPage object of this instance */
39
	protected $mPage;
40
41
	/** @var ParserOptions ParserOptions object for $wgUser articles */
42
	public $mParserOptions;
43
44
	/**
45
	 * @var string Text of the revision we are working on
46
	 * @todo BC cruft
47
	 */
48
	public $mContent;
49
50
	/**
51
	 * @var Content Content of the revision we are working on
52
	 * @since 1.21
53
	 */
54
	public $mContentObject;
55
56
	/** @var bool Is the content ($mContent) already loaded? */
57
	public $mContentLoaded = false;
58
59
	/** @var int|null The oldid of the article that is to be shown, 0 for the current revision */
60
	public $mOldId;
61
62
	/** @var Title Title from which we were redirected here */
63
	public $mRedirectedFrom = null;
64
65
	/** @var string|bool URL to redirect to or false if none */
66
	public $mRedirectUrl = false;
67
68
	/** @var int Revision ID of revision we are working on */
69
	public $mRevIdFetched = 0;
70
71
	/** @var Revision Revision we are working on */
72
	public $mRevision = null;
73
74
	/** @var ParserOutput */
75
	public $mParserOutput;
76
77
	/**
78
	 * Constructor and clear the article
79
	 * @param Title $title Reference to a Title object.
80
	 * @param int $oldId Revision ID, null to fetch from request, zero for current
81
	 */
82
	public function __construct( Title $title, $oldId = null ) {
83
		$this->mOldId = $oldId;
84
		$this->mPage = $this->newPage( $title );
85
	}
86
87
	/**
88
	 * @param Title $title
89
	 * @return WikiPage
90
	 */
91
	protected function newPage( Title $title ) {
92
		return new WikiPage( $title );
93
	}
94
95
	/**
96
	 * Constructor from a page id
97
	 * @param int $id Article ID to load
98
	 * @return Article|null
99
	 */
100
	public static function newFromID( $id ) {
101
		$t = Title::newFromID( $id );
102
		return $t == null ? null : new static( $t );
103
	}
104
105
	/**
106
	 * Create an Article object of the appropriate class for the given page.
107
	 *
108
	 * @param Title $title
109
	 * @param IContextSource $context
110
	 * @return Article
111
	 */
112
	public static function newFromTitle( $title, IContextSource $context ) {
113
		if ( NS_MEDIA == $title->getNamespace() ) {
114
			// FIXME: where should this go?
115
			$title = Title::makeTitle( NS_FILE, $title->getDBkey() );
116
		}
117
118
		$page = null;
119
		Hooks::run( 'ArticleFromTitle', [ &$title, &$page, $context ] );
120
		if ( !$page ) {
121
			switch ( $title->getNamespace() ) {
122
				case NS_FILE:
123
					$page = new ImagePage( $title );
124
					break;
125
				case NS_CATEGORY:
126
					$page = new CategoryPage( $title );
127
					break;
128
				default:
129
					$page = new Article( $title );
130
			}
131
		}
132
		$page->setContext( $context );
133
134
		return $page;
135
	}
136
137
	/**
138
	 * Create an Article object of the appropriate class for the given page.
139
	 *
140
	 * @param WikiPage $page
141
	 * @param IContextSource $context
142
	 * @return Article
143
	 */
144
	public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
145
		$article = self::newFromTitle( $page->getTitle(), $context );
146
		$article->mPage = $page; // override to keep process cached vars
147
		return $article;
148
	}
149
150
	/**
151
	 * Get the page this view was redirected from
152
	 * @return Title|null
153
	 * @since 1.28
154
	 */
155
	public function getRedirectedFrom() {
156
		return $this->mRedirectedFrom;
157
	}
158
159
	/**
160
	 * Tell the page view functions that this view was redirected
161
	 * from another page on the wiki.
162
	 * @param Title $from
163
	 */
164
	public function setRedirectedFrom( Title $from ) {
165
		$this->mRedirectedFrom = $from;
166
	}
167
168
	/**
169
	 * Get the title object of the article
170
	 *
171
	 * @return Title Title object of this page
172
	 */
173
	public function getTitle() {
174
		return $this->mPage->getTitle();
175
	}
176
177
	/**
178
	 * Get the WikiPage object of this instance
179
	 *
180
	 * @since 1.19
181
	 * @return WikiPage
182
	 */
183
	public function getPage() {
184
		return $this->mPage;
185
	}
186
187
	/**
188
	 * Clear the object
189
	 */
190
	public function clear() {
191
		$this->mContentLoaded = false;
192
193
		$this->mRedirectedFrom = null; # Title object if set
194
		$this->mRevIdFetched = 0;
195
		$this->mRedirectUrl = false;
196
197
		$this->mPage->clear();
198
	}
199
200
	/**
201
	 * Note that getContent does not follow redirects anymore.
202
	 * If you need to fetch redirectable content easily, try
203
	 * the shortcut in WikiPage::getRedirectTarget()
204
	 *
205
	 * This function has side effects! Do not use this function if you
206
	 * only want the real revision text if any.
207
	 *
208
	 * @deprecated since 1.21; use WikiPage::getContent() instead
209
	 *
210
	 * @return string Return the text of this revision
211
	 */
212
	public function getContent() {
213
		ContentHandler::deprecated( __METHOD__, '1.21' );
214
		$content = $this->getContentObject();
215
		return ContentHandler::getContentText( $content );
216
	}
217
218
	/**
219
	 * Returns a Content object representing the pages effective display content,
220
	 * not necessarily the revision's content!
221
	 *
222
	 * Note that getContent does not follow redirects anymore.
223
	 * If you need to fetch redirectable content easily, try
224
	 * the shortcut in WikiPage::getRedirectTarget()
225
	 *
226
	 * This function has side effects! Do not use this function if you
227
	 * only want the real revision text if any.
228
	 *
229
	 * @return Content Return the content of this revision
230
	 *
231
	 * @since 1.21
232
	 */
233
	protected function getContentObject() {
234
235
		if ( $this->mPage->getId() === 0 ) {
236
			# If this is a MediaWiki:x message, then load the messages
237
			# and return the message value for x.
238
			if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) {
239
				$text = $this->getTitle()->getDefaultMessageText();
240
				if ( $text === false ) {
241
					$text = '';
242
				}
243
244
				$content = ContentHandler::makeContent( $text, $this->getTitle() );
245
			} else {
246
				$message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
247
				$content = new MessageContent( $message, null, 'parsemag' );
248
			}
249
		} else {
250
			$this->fetchContentObject();
251
			$content = $this->mContentObject;
252
		}
253
254
		return $content;
255
	}
256
257
	/**
258
	 * @return int The oldid of the article that is to be shown, 0 for the current revision
259
	 */
260
	public function getOldID() {
261
		if ( is_null( $this->mOldId ) ) {
262
			$this->mOldId = $this->getOldIDFromRequest();
263
		}
264
265
		return $this->mOldId;
266
	}
267
268
	/**
269
	 * Sets $this->mRedirectUrl to a correct URL if the query parameters are incorrect
270
	 *
271
	 * @return int The old id for the request
272
	 */
273
	public function getOldIDFromRequest() {
274
		$this->mRedirectUrl = false;
275
276
		$request = $this->getContext()->getRequest();
277
		$oldid = $request->getIntOrNull( 'oldid' );
278
279
		if ( $oldid === null ) {
280
			return 0;
281
		}
282
283
		if ( $oldid !== 0 ) {
284
			# Load the given revision and check whether the page is another one.
285
			# In that case, update this instance to reflect the change.
286
			if ( $oldid === $this->mPage->getLatest() ) {
287
				$this->mRevision = $this->mPage->getRevision();
288
			} else {
289
				$this->mRevision = Revision::newFromId( $oldid );
290
				if ( $this->mRevision !== null ) {
291
					// Revision title doesn't match the page title given?
292
					if ( $this->mPage->getId() != $this->mRevision->getPage() ) {
293
						$function = [ get_class( $this->mPage ), 'newFromID' ];
294
						$this->mPage = call_user_func( $function, $this->mRevision->getPage() );
295
					}
296
				}
297
			}
298
		}
299
300
		if ( $request->getVal( 'direction' ) == 'next' ) {
301
			$nextid = $this->getTitle()->getNextRevisionID( $oldid );
302
			if ( $nextid ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $nextid of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
303
				$oldid = $nextid;
304
				$this->mRevision = null;
305
			} else {
306
				$this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
307
			}
308
		} elseif ( $request->getVal( 'direction' ) == 'prev' ) {
309
			$previd = $this->getTitle()->getPreviousRevisionID( $oldid );
310
			if ( $previd ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $previd of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
311
				$oldid = $previd;
312
				$this->mRevision = null;
313
			}
314
		}
315
316
		return $oldid;
317
	}
318
319
	/**
320
	 * Get text of an article from database
321
	 * Does *NOT* follow redirects.
322
	 *
323
	 * @protected
324
	 * @note This is really internal functionality that should really NOT be
325
	 * used by other functions. For accessing article content, use the WikiPage
326
	 * class, especially WikiBase::getContent(). However, a lot of legacy code
327
	 * uses this method to retrieve page text from the database, so the function
328
	 * has to remain public for now.
329
	 *
330
	 * @return string|bool String containing article contents, or false if null
331
	 * @deprecated since 1.21, use WikiPage::getContent() instead
332
	 */
333
	function fetchContent() {
334
		// BC cruft!
335
336
		wfDeprecated( __METHOD__, '1.21' );
337
338
		if ( $this->mContentLoaded && $this->mContent ) {
339
			return $this->mContent;
340
		}
341
342
		$content = $this->fetchContentObject();
343
344
		if ( !$content ) {
345
			return false;
346
		}
347
348
		// @todo Get rid of mContent everywhere!
349
		$this->mContent = ContentHandler::getContentText( $content );
350
		ContentHandler::runLegacyHooks(
351
			'ArticleAfterFetchContent',
352
			[ &$this, &$this->mContent ],
353
			'1.21'
354
		);
355
356
		return $this->mContent;
357
	}
358
359
	/**
360
	 * Get text content object
361
	 * Does *NOT* follow redirects.
362
	 * @todo When is this null?
363
	 *
364
	 * @note Code that wants to retrieve page content from the database should
365
	 * use WikiPage::getContent().
366
	 *
367
	 * @return Content|null|bool
368
	 *
369
	 * @since 1.21
370
	 */
371
	protected function fetchContentObject() {
372
		if ( $this->mContentLoaded ) {
373
			return $this->mContentObject;
374
		}
375
376
		$this->mContentLoaded = true;
377
		$this->mContent = null;
378
379
		$oldid = $this->getOldID();
380
381
		# Pre-fill content with error message so that if something
382
		# fails we'll have something telling us what we intended.
383
		// XXX: this isn't page content but a UI message. horrible.
384
		$this->mContentObject = new MessageContent( 'missing-revision', [ $oldid ] );
385
386
		if ( $oldid ) {
387
			# $this->mRevision might already be fetched by getOldIDFromRequest()
388
			if ( !$this->mRevision ) {
389
				$this->mRevision = Revision::newFromId( $oldid );
390
				if ( !$this->mRevision ) {
391
					wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" );
392
					return false;
393
				}
394
			}
395
		} else {
396
			$oldid = $this->mPage->getLatest();
397
			if ( !$oldid ) {
398
				wfDebug( __METHOD__ . " failed to find page data for title " .
399
					$this->getTitle()->getPrefixedText() . "\n" );
400
				return false;
401
			}
402
403
			# Update error message with correct oldid
404
			$this->mContentObject = new MessageContent( 'missing-revision', [ $oldid ] );
405
406
			$this->mRevision = $this->mPage->getRevision();
407
408
			if ( !$this->mRevision ) {
409
				wfDebug( __METHOD__ . " failed to retrieve current page, rev_id $oldid\n" );
410
				return false;
411
			}
412
		}
413
414
		// @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
415
		// We should instead work with the Revision object when we need it...
416
		// Loads if user is allowed
417
		$content = $this->mRevision->getContent(
418
			Revision::FOR_THIS_USER,
419
			$this->getContext()->getUser()
420
		);
421
422
		if ( !$content ) {
423
			wfDebug( __METHOD__ . " failed to retrieve content of revision " .
424
				$this->mRevision->getId() . "\n" );
425
			return false;
426
		}
427
428
		$this->mContentObject = $content;
429
		$this->mRevIdFetched = $this->mRevision->getId();
430
431
		ContentHandler::runLegacyHooks(
432
			'ArticleAfterFetchContentObject',
433
			[ &$this, &$this->mContentObject ],
434
			'1.21'
435
		);
436
437
		return $this->mContentObject;
438
	}
439
440
	/**
441
	 * Returns true if the currently-referenced revision is the current edit
442
	 * to this page (and it exists).
443
	 * @return bool
444
	 */
445
	public function isCurrent() {
446
		# If no oldid, this is the current version.
447
		if ( $this->getOldID() == 0 ) {
448
			return true;
449
		}
450
451
		return $this->mPage->exists() && $this->mRevision && $this->mRevision->isCurrent();
452
	}
453
454
	/**
455
	 * Get the fetched Revision object depending on request parameters or null
456
	 * on failure.
457
	 *
458
	 * @since 1.19
459
	 * @return Revision|null
460
	 */
461
	public function getRevisionFetched() {
462
		$this->fetchContentObject();
463
464
		return $this->mRevision;
465
	}
466
467
	/**
468
	 * Use this to fetch the rev ID used on page views
469
	 *
470
	 * @return int Revision ID of last article revision
471
	 */
472
	public function getRevIdFetched() {
473
		if ( $this->mRevIdFetched ) {
474
			return $this->mRevIdFetched;
475
		} else {
476
			return $this->mPage->getLatest();
477
		}
478
	}
479
480
	/**
481
	 * This is the default action of the index.php entry point: just view the
482
	 * page of the given title.
483
	 */
484
	public function view() {
485
		global $wgUseFileCache, $wgDebugToolbar;
486
487
		# Get variables from query string
488
		# As side effect this will load the revision and update the title
489
		# in a revision ID is passed in the request, so this should remain
490
		# the first call of this method even if $oldid is used way below.
491
		$oldid = $this->getOldID();
492
493
		$user = $this->getContext()->getUser();
494
		# Another whitelist check in case getOldID() is altering the title
495
		$permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $user );
496
		if ( count( $permErrors ) ) {
497
			wfDebug( __METHOD__ . ": denied on secondary read check\n" );
498
			throw new PermissionsError( 'read', $permErrors );
499
		}
500
501
		$outputPage = $this->getContext()->getOutput();
502
		# getOldID() may as well want us to redirect somewhere else
503
		if ( $this->mRedirectUrl ) {
504
			$outputPage->redirect( $this->mRedirectUrl );
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...
505
			wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
506
507
			return;
508
		}
509
510
		# If we got diff in the query, we want to see a diff page instead of the article.
511
		if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) {
512
			wfDebug( __METHOD__ . ": showing diff page\n" );
513
			$this->showDiffPage();
514
515
			return;
516
		}
517
518
		# Set page title (may be overridden by DISPLAYTITLE)
519
		$outputPage->setPageTitle( $this->getTitle()->getPrefixedText() );
520
521
		$outputPage->setArticleFlag( true );
522
		# Allow frames by default
523
		$outputPage->allowClickjacking();
524
525
		$parserCache = ParserCache::singleton();
526
527
		$parserOptions = $this->getParserOptions();
528
		# Render printable version, use printable version cache
529
		if ( $outputPage->isPrintable() ) {
530
			$parserOptions->setIsPrintable( true );
531
			$parserOptions->setEditSection( false );
532
		} elseif ( !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user ) ) {
533
			$parserOptions->setEditSection( false );
534
		}
535
536
		# Try client and file cache
537
		if ( !$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched() ) {
538
			# Try to stream the output from file cache
539
			if ( $wgUseFileCache && $this->tryFileCache() ) {
540
				wfDebug( __METHOD__ . ": done file cache\n" );
541
				# tell wgOut that output is taken care of
542
				$outputPage->disable();
543
				$this->mPage->doViewUpdates( $user, $oldid );
544
545
				return;
546
			}
547
		}
548
549
		# Should the parser cache be used?
550
		$useParserCache = $this->mPage->shouldCheckParserCache( $parserOptions, $oldid );
551
		wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
552
		if ( $user->getStubThreshold() ) {
553
			$this->getContext()->getStats()->increment( 'pcache_miss_stub' );
554
		}
555
556
		$this->showRedirectedFromHeader();
557
		$this->showNamespaceHeader();
558
559
		# Iterate through the possible ways of constructing the output text.
560
		# Keep going until $outputDone is set, or we run out of things to do.
561
		$pass = 0;
562
		$outputDone = false;
563
		$this->mParserOutput = false;
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...
564
565
		while ( !$outputDone && ++$pass ) {
566
			switch ( $pass ) {
567
				case 1:
568
					Hooks::run( 'ArticleViewHeader', [ &$this, &$outputDone, &$useParserCache ] );
569
					break;
570
				case 2:
571
					# Early abort if the page doesn't exist
572
					if ( !$this->mPage->exists() ) {
573
						wfDebug( __METHOD__ . ": showing missing article\n" );
574
						$this->showMissingArticle();
575
						$this->mPage->doViewUpdates( $user );
576
						return;
577
					}
578
579
					# Try the parser cache
580
					if ( $useParserCache ) {
581
						$this->mParserOutput = $parserCache->get( $this->mPage, $parserOptions );
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...
582
583
						if ( $this->mParserOutput !== false ) {
584
							if ( $oldid ) {
585
								wfDebug( __METHOD__ . ": showing parser cache contents for current rev permalink\n" );
586
								$this->setOldSubtitle( $oldid );
587
							} else {
588
								wfDebug( __METHOD__ . ": showing parser cache contents\n" );
589
							}
590
							$outputPage->addParserOutput( $this->mParserOutput );
591
							# Ensure that UI elements requiring revision ID have
592
							# the correct version information.
593
							$outputPage->setRevisionId( $this->mPage->getLatest() );
594
							# Preload timestamp to avoid a DB hit
595
							$cachedTimestamp = $this->mParserOutput->getTimestamp();
596
							if ( $cachedTimestamp !== null ) {
597
								$outputPage->setRevisionTimestamp( $cachedTimestamp );
598
								$this->mPage->setTimestamp( $cachedTimestamp );
599
							}
600
							$outputDone = true;
601
						}
602
					}
603
					break;
604
				case 3:
605
					# This will set $this->mRevision if needed
606
					$this->fetchContentObject();
607
608
					# Are we looking at an old revision
609
					if ( $oldid && $this->mRevision ) {
610
						$this->setOldSubtitle( $oldid );
611
612
						if ( !$this->showDeletedRevisionHeader() ) {
613
							wfDebug( __METHOD__ . ": cannot view deleted revision\n" );
614
							return;
615
						}
616
					}
617
618
					# Ensure that UI elements requiring revision ID have
619
					# the correct version information.
620
					$outputPage->setRevisionId( $this->getRevIdFetched() );
621
					# Preload timestamp to avoid a DB hit
622
					$outputPage->setRevisionTimestamp( $this->mPage->getTimestamp() );
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...
623
624
					# Pages containing custom CSS or JavaScript get special treatment
625
					if ( $this->getTitle()->isCssOrJsPage() || $this->getTitle()->isCssJsSubpage() ) {
626
						wfDebug( __METHOD__ . ": showing CSS/JS source\n" );
627
						$this->showCssOrJsPage();
628
						$outputDone = true;
629
					} elseif ( !Hooks::run( 'ArticleContentViewCustom',
630
							[ $this->fetchContentObject(), $this->getTitle(), $outputPage ] ) ) {
631
632
						# Allow extensions do their own custom view for certain pages
633
						$outputDone = true;
634
					} elseif ( !ContentHandler::runLegacyHooks(
635
						'ArticleViewCustom',
636
						[ $this->fetchContentObject(), $this->getTitle(), $outputPage ],
637
						'1.21'
638
					) ) {
639
						# Allow extensions do their own custom view for certain pages
640
						$outputDone = true;
641
					}
642
					break;
643
				case 4:
644
					# Run the parse, protected by a pool counter
645
					wfDebug( __METHOD__ . ": doing uncached parse\n" );
646
647
					$content = $this->getContentObject();
648
					$poolArticleView = new PoolWorkArticleView( $this->getPage(), $parserOptions,
649
						$this->getRevIdFetched(), $useParserCache, $content );
650
651
					if ( !$poolArticleView->execute() ) {
652
						$error = $poolArticleView->getError();
653
						if ( $error ) {
654
							$outputPage->clearHTML(); // for release() errors
655
							$outputPage->enableClientCache( false );
656
							$outputPage->setRobotPolicy( 'noindex,nofollow' );
657
658
							$errortext = $error->getWikiText( false, 'view-pool-error' );
659
							$outputPage->addWikiText( '<div class="errorbox">' . $errortext . '</div>' );
660
						}
661
						# Connection or timeout error
662
						return;
663
					}
664
665
					$this->mParserOutput = $poolArticleView->getParserOutput();
666
					$outputPage->addParserOutput( $this->mParserOutput );
667
					if ( $content->getRedirectTarget() ) {
668
						$outputPage->addSubtitle( "<span id=\"redirectsub\">" .
669
							$this->getContext()->msg( 'redirectpagesub' )->parse() . "</span>" );
670
					}
671
672
					# Don't cache a dirty ParserOutput object
673
					if ( $poolArticleView->getIsDirty() ) {
674
						$outputPage->setCdnMaxage( 0 );
675
						$outputPage->addHTML( "<!-- parser cache is expired, " .
676
							"sending anyway due to pool overload-->\n" );
677
					}
678
679
					$outputDone = true;
680
					break;
681
				# Should be unreachable, but just in case...
682
				default:
683
					break 2;
684
			}
685
		}
686
687
		# Get the ParserOutput actually *displayed* here.
688
		# Note that $this->mParserOutput is the *current*/oldid version output.
689
		$pOutput = ( $outputDone instanceof ParserOutput )
690
			? $outputDone // object fetched by hook
691
			: $this->mParserOutput;
692
693
		# Adjust title for main page & pages with displaytitle
694
		if ( $pOutput ) {
695
			$this->adjustDisplayTitle( $pOutput );
696
		}
697
698
		# For the main page, overwrite the <title> element with the con-
699
		# tents of 'pagetitle-view-mainpage' instead of the default (if
700
		# that's not empty).
701
		# This message always exists because it is in the i18n files
702
		if ( $this->getTitle()->isMainPage() ) {
703
			$msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
704
			if ( !$msg->isDisabled() ) {
705
				$outputPage->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
706
			}
707
		}
708
709
		# Check for any __NOINDEX__ tags on the page using $pOutput
710
		$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 689 can also be of type false; however, Article::getRobotPolicy() does only seem to accept object<ParserOutput>|null, did you maybe forget to handle an error condition?

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

Consider the follow example

<?php

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

    return false;
}

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

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