Issues (4122)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/page/Article.php (19 issues)

Upgrade to new PHP Analysis Engine

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

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

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

Consider the follow example

<?php

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

    return false;
}

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

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