Completed
Branch master (9259dd)
by
unknown
27:26
created

MovePageForm::showForm()   F

Complexity

Conditions 45
Paths > 20000

Size

Total Lines 366
Code Lines 233

Duplication

Lines 50
Ratio 13.66 %

Importance

Changes 0
Metric Value
cc 45
eloc 233
nc 19906560
nop 1
dl 50
loc 366
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Implements Special:Movepage
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
 * @ingroup SpecialPage
22
 */
23
24
/**
25
 * A special page that allows users to change page titles
26
 *
27
 * @ingroup SpecialPage
28
 */
29
class MovePageForm extends UnlistedSpecialPage {
30
	/** @var Title */
31
	protected $oldTitle = null;
32
33
	/** @var Title */
34
	protected $newTitle;
35
36
	/** @var string Text input */
37
	protected $reason;
38
39
	// Checks
40
41
	/** @var bool */
42
	protected $moveTalk;
43
44
	/** @var bool */
45
	protected $deleteAndMove;
46
47
	/** @var bool */
48
	protected $moveSubpages;
49
50
	/** @var bool */
51
	protected $fixRedirects;
52
53
	/** @var bool */
54
	protected $leaveRedirect;
55
56
	/** @var bool */
57
	protected $moveOverShared;
58
59
	private $watch = false;
60
61
	public function __construct() {
62
		parent::__construct( 'Movepage' );
63
	}
64
65
	public function doesWrites() {
66
		return true;
67
	}
68
69
	public function execute( $par ) {
70
		$this->useTransactionalTimeLimit();
71
72
		$this->checkReadOnly();
73
74
		$this->setHeaders();
75
		$this->outputHeader();
76
77
		$request = $this->getRequest();
78
		$target = !is_null( $par ) ? $par : $request->getVal( 'target' );
79
80
		// Yes, the use of getVal() and getText() is wanted, see bug 20365
81
82
		$oldTitleText = $request->getVal( 'wpOldTitle', $target );
83
		$this->oldTitle = Title::newFromText( $oldTitleText );
84
85
		if ( !$this->oldTitle ) {
86
			// Either oldTitle wasn't passed, or newFromText returned null
87
			throw new ErrorPageError( 'notargettitle', 'notargettext' );
88
		}
89
		if ( !$this->oldTitle->exists() ) {
90
			throw new ErrorPageError( 'nopagetitle', 'nopagetext' );
91
		}
92
93
		$newTitleTextMain = $request->getText( 'wpNewTitleMain' );
94
		$newTitleTextNs = $request->getInt( 'wpNewTitleNs', $this->oldTitle->getNamespace() );
95
		// Backwards compatibility for forms submitting here from other sources
96
		// which is more common than it should be..
97
		$newTitleText_bc = $request->getText( 'wpNewTitle' );
98
		$this->newTitle = strlen( $newTitleText_bc ) > 0
99
			? Title::newFromText( $newTitleText_bc )
100
			: Title::makeTitleSafe( $newTitleTextNs, $newTitleTextMain );
101
102
		$user = $this->getUser();
103
104
		# Check rights
105
		$permErrors = $this->oldTitle->getUserPermissionsErrors( 'move', $user );
106
		if ( count( $permErrors ) ) {
107
			// Auto-block user's IP if the account was "hard" blocked
108
			DeferredUpdates::addCallableUpdate( function() use ( $user ) {
109
				$user->spreadAnyEditBlock();
110
			} );
111
			throw new PermissionsError( 'move', $permErrors );
112
		}
113
114
		$def = !$request->wasPosted();
115
116
		$this->reason = $request->getText( 'wpReason' );
117
		$this->moveTalk = $request->getBool( 'wpMovetalk', $def );
118
		$this->fixRedirects = $request->getBool( 'wpFixRedirects', $def );
119
		$this->leaveRedirect = $request->getBool( 'wpLeaveRedirect', $def );
120
		$this->moveSubpages = $request->getBool( 'wpMovesubpages' );
121
		$this->deleteAndMove = $request->getBool( 'wpDeleteAndMove' );
122
		$this->moveOverShared = $request->getBool( 'wpMoveOverSharedFile' );
123
		$this->watch = $request->getCheck( 'wpWatch' ) && $user->isLoggedIn();
124
125
		if ( 'submit' == $request->getVal( 'action' ) && $request->wasPosted()
126
			&& $user->matchEditToken( $request->getVal( 'wpEditToken' ) )
127
		) {
128
			$this->doSubmit();
129
		} else {
130
			$this->showForm( [] );
131
		}
132
	}
133
134
	/**
135
	 * Show the form
136
	 *
137
	 * @param array $err Error messages. Each item is an error message.
138
	 *    It may either be a string message name or array message name and
139
	 *    parameters, like the second argument to OutputPage::wrapWikiMsg().
140
	 */
141
	function showForm( $err ) {
142
		global $wgContLang;
143
144
		$this->getSkin()->setRelevantTitle( $this->oldTitle );
145
146
		$out = $this->getOutput();
147
		$out->setPageTitle( $this->msg( 'move-page', $this->oldTitle->getPrefixedText() ) );
148
		$out->addModules( 'mediawiki.special.movePage' );
149
		$out->addModuleStyles( 'mediawiki.special.movePage.styles' );
150
		$this->addHelpLink( 'Help:Moving a page' );
151
152
		$out->addWikiMsg( $this->getConfig()->get( 'FixDoubleRedirects' ) ?
153
			'movepagetext' :
154
			'movepagetext-noredirectfixer'
155
		);
156
157
		if ( $this->oldTitle->getNamespace() == NS_USER && !$this->oldTitle->isSubpage() ) {
158
			$out->wrapWikiMsg(
159
				"<div class=\"warningbox mw-moveuserpage-warning\">\n$1\n</div>",
160
				'moveuserpage-warning'
161
			);
162
		} elseif ( $this->oldTitle->getNamespace() == NS_CATEGORY ) {
163
			$out->wrapWikiMsg(
164
				"<div class=\"warningbox mw-movecategorypage-warning\">\n$1\n</div>",
165
				'movecategorypage-warning'
166
			);
167
		}
168
169
		$deleteAndMove = false;
170
		$moveOverShared = false;
171
172
		$newTitle = $this->newTitle;
173
174
		if ( !$newTitle ) {
175
			# Show the current title as a default
176
			# when the form is first opened.
177
			$newTitle = $this->oldTitle;
178
		} elseif ( !count( $err ) ) {
179
			# If a title was supplied, probably from the move log revert
180
			# link, check for validity. We can then show some diagnostic
181
			# information and save a click.
182
			$newerr = $this->oldTitle->isValidMoveOperation( $newTitle );
0 ignored issues
show
Deprecated Code introduced by
The method Title::isValidMoveOperation() has been deprecated with message: since 1.25, use MovePage's methods instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
183
			if ( is_array( $newerr ) ) {
184
				$err = $newerr;
185
			}
186
		}
187
188
		$user = $this->getUser();
189
190 View Code Duplication
		if ( count( $err ) == 1 && isset( $err[0][0] ) && $err[0][0] == 'articleexists'
191
			&& $newTitle->quickUserCan( 'delete', $user )
192
		) {
193
			$out->wrapWikiMsg(
194
				"<div class='warningbox'>\n$1\n</div>\n",
195
				[ 'delete_and_move_text', $newTitle->getPrefixedText() ]
196
			);
197
			$deleteAndMove = true;
198
			$err = [];
199
		}
200
201 View Code Duplication
		if ( count( $err ) == 1 && isset( $err[0][0] ) && $err[0][0] == 'file-exists-sharedrepo'
202
			&& $user->isAllowed( 'reupload-shared' )
203
		) {
204
			$out->wrapWikiMsg(
205
				"<div class='warningbox'>\n$1\n</div>\n",
206
				[
207
					'move-over-sharedrepo',
208
					$newTitle->getPrefixedText()
209
				]
210
			);
211
			$moveOverShared = true;
212
			$err = [];
213
		}
214
215
		$oldTalk = $this->oldTitle->getTalkPage();
216
		$oldTitleSubpages = $this->oldTitle->hasSubpages();
217
		$oldTitleTalkSubpages = $this->oldTitle->getTalkPage()->hasSubpages();
218
219
		$canMoveSubpage = ( $oldTitleSubpages || $oldTitleTalkSubpages ) &&
220
			!count( $this->oldTitle->getUserPermissionsErrors( 'move-subpages', $user ) );
221
222
		# We also want to be able to move assoc. subpage talk-pages even if base page
223
		# has no associated talk page, so || with $oldTitleTalkSubpages.
224
		$considerTalk = !$this->oldTitle->isTalkPage() &&
225
			( $oldTalk->exists()
226
				|| ( $oldTitleTalkSubpages && $canMoveSubpage ) );
227
228
		$dbr = wfGetDB( DB_SLAVE );
229
		if ( $this->getConfig()->get( 'FixDoubleRedirects' ) ) {
230
			$hasRedirects = $dbr->selectField( 'redirect', '1',
231
				[
232
					'rd_namespace' => $this->oldTitle->getNamespace(),
233
					'rd_title' => $this->oldTitle->getDBkey(),
234
				], __METHOD__ );
235
		} else {
236
			$hasRedirects = false;
237
		}
238
239
		if ( count( $err ) ) {
240
			$out->addHTML( "<div class='errorbox'>\n" );
241
			$action_desc = $this->msg( 'action-move' )->plain();
242
			$out->addWikiMsg( 'permissionserrorstext-withaction', count( $err ), $action_desc );
243
244
			if ( count( $err ) == 1 ) {
245
				$errMsg = $err[0];
246
				$errMsgName = array_shift( $errMsg );
247
248
				if ( $errMsgName == 'hookaborted' ) {
249
					$out->addHTML( "<p>{$errMsg[0]}</p>\n" );
250
				} else {
251
					$out->addWikiMsgArray( $errMsgName, $errMsg );
252
				}
253
			} else {
254
				$errStr = [];
255
256
				foreach ( $err as $errMsg ) {
257
					if ( $errMsg[0] == 'hookaborted' ) {
258
						$errStr[] = $errMsg[1];
259
					} else {
260
						$errMsgName = array_shift( $errMsg );
261
						$errStr[] = $this->msg( $errMsgName, $errMsg )->parse();
262
					}
263
				}
264
265
				$out->addHTML( '<ul><li>' . implode( "</li>\n<li>", $errStr ) . "</li></ul>\n" );
266
			}
267
			$out->addHTML( "</div>\n" );
268
		}
269
270
		if ( $this->oldTitle->isProtected( 'move' ) ) {
271
			# Is the title semi-protected?
272
			if ( $this->oldTitle->isSemiProtected( 'move' ) ) {
273
				$noticeMsg = 'semiprotectedpagemovewarning';
274
				$classes[] = 'mw-textarea-sprotected';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$classes was never initialized. Although not strictly required by PHP, it is generally a good practice to add $classes = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
275
			} else {
276
				# Then it must be protected based on static groups (regular)
277
				$noticeMsg = 'protectedpagemovewarning';
278
				$classes[] = 'mw-textarea-protected';
0 ignored issues
show
Coding Style Comprehensibility introduced by
$classes was never initialized. Although not strictly required by PHP, it is generally a good practice to add $classes = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
279
			}
280
			$out->addHTML( "<div class='mw-warning-with-logexcerpt'>\n" );
281
			$out->addWikiMsg( $noticeMsg );
282
			LogEventsList::showLogExtract(
283
				$out,
284
				'protect',
285
				$this->oldTitle,
286
				'',
287
				[ 'lim' => 1 ]
288
			);
289
			$out->addHTML( "</div>\n" );
290
		}
291
292
		// Byte limit (not string length limit) for wpReason and wpNewTitleMain
293
		// is enforced in the mediawiki.special.movePage module
294
295
		$immovableNamespaces = [];
296
		foreach ( array_keys( $this->getLanguage()->getNamespaces() ) as $nsId ) {
297
			if ( !MWNamespace::isMovable( $nsId ) ) {
298
				$immovableNamespaces[] = $nsId;
299
			}
300
		}
301
302
		$handler = ContentHandler::getForTitle( $this->oldTitle );
303
304
		$out->enableOOUI();
305
		$fields = [];
306
307
		$fields[] = new OOUI\FieldLayout(
308
			new MediaWiki\Widget\ComplexTitleInputWidget( [
309
				'id' => 'wpNewTitle',
310
				'namespace' => [
311
					'id' => 'wpNewTitleNs',
312
					'name' => 'wpNewTitleNs',
313
					'value' => $newTitle->getNamespace(),
314
					'exclude' => $immovableNamespaces,
315
				],
316
				'title' => [
317
					'id' => 'wpNewTitleMain',
318
					'name' => 'wpNewTitleMain',
319
					'value' => $wgContLang->recodeForEdit( $newTitle->getText() ),
320
					// Inappropriate, since we're expecting the user to input a non-existent page's title
321
					'suggestions' => false,
322
				],
323
				'infusable' => true,
324
			] ),
325
			[
326
				'label' => $this->msg( 'newtitle' )->text(),
327
				'align' => 'top',
328
			]
329
		);
330
331
		$fields[] = new OOUI\FieldLayout(
332
			new OOUI\TextInputWidget( [
333
				'name' => 'wpReason',
334
				'id' => 'wpReason',
335
				'maxLength' => 200,
336
				'infusable' => true,
337
				'value' => $this->reason,
338
			] ),
339
			[
340
				'label' => $this->msg( 'movereason' )->text(),
341
				'align' => 'top',
342
			]
343
		);
344
345
		if ( $considerTalk ) {
346
			$fields[] = new OOUI\FieldLayout(
347
				new OOUI\CheckboxInputWidget( [
348
					'name' => 'wpMovetalk',
349
					'id' => 'wpMovetalk',
350
					'value' => '1',
351
					'selected' => $this->moveTalk,
352
				] ),
353
				[
354
					'label' => $this->msg( 'movetalk' )->text(),
355
					'help' => new OOUI\HtmlSnippet( $this->msg( 'movepagetalktext' )->parseAsBlock() ),
356
					'align' => 'inline',
357
					'infusable' => true,
358
				]
359
			);
360
		}
361
362
		if ( $user->isAllowed( 'suppressredirect' ) ) {
363
			if ( $handler->supportsRedirects() ) {
364
				$isChecked = $this->leaveRedirect;
365
				$isDisabled = false;
366
			} else {
367
				$isChecked = false;
368
				$isDisabled = true;
369
			}
370
			$fields[] = new OOUI\FieldLayout(
371
				new OOUI\CheckboxInputWidget( [
372
					'name' => 'wpLeaveRedirect',
373
					'id' => 'wpLeaveRedirect',
374
					'value' => '1',
375
					'selected' => $isChecked,
376
					'disabled' => $isDisabled,
377
				] ),
378
				[
379
					'label' => $this->msg( 'move-leave-redirect' )->text(),
380
					'align' => 'inline',
381
				]
382
			);
383
		}
384
385 View Code Duplication
		if ( $hasRedirects ) {
386
			$fields[] = new OOUI\FieldLayout(
387
				new OOUI\CheckboxInputWidget( [
388
					'name' => 'wpFixRedirects',
389
					'id' => 'wpFixRedirects',
390
					'value' => '1',
391
					'selected' => $this->fixRedirects,
392
				] ),
393
				[
394
					'label' => $this->msg( 'fix-double-redirects' )->text(),
395
					'align' => 'inline',
396
				]
397
			);
398
		}
399
400
		if ( $canMoveSubpage ) {
401
			$maximumMovedPages = $this->getConfig()->get( 'MaximumMovedPages' );
402
			$fields[] = new OOUI\FieldLayout(
403
				new OOUI\CheckboxInputWidget( [
404
					'name' => 'wpMovesubpages',
405
					'id' => 'wpMovesubpages',
406
					'value' => '1',
407
					# Don't check the box if we only have talk subpages to
408
					# move and we aren't moving the talk page.
409
					'selected' => $this->moveSubpages && ( $this->oldTitle->hasSubpages() || $this->moveTalk ),
410
				] ),
411
				[
412
					'label' => new OOUI\HtmlSnippet(
413
						$this->msg(
414
							( $this->oldTitle->hasSubpages()
415
								? 'move-subpages'
416
								: 'move-talk-subpages' )
417
						)->numParams( $maximumMovedPages )->params( $maximumMovedPages )->parse()
418
					),
419
					'align' => 'inline',
420
				]
421
			);
422
		}
423
424
		# Don't allow watching if user is not logged in
425
		if ( $user->isLoggedIn() ) {
426
			$watchChecked = $user->isLoggedIn() && ( $this->watch || $user->getBoolOption( 'watchmoves' )
427
				|| $user->isWatched( $this->oldTitle ) );
428
			$fields[] = new OOUI\FieldLayout(
429
				new OOUI\CheckboxInputWidget( [
430
					'name' => 'wpWatch',
431
					'id' => 'watch', # ew
432
					'value' => '1',
433
					'selected' => $watchChecked,
434
				] ),
435
				[
436
					'label' => $this->msg( 'move-watch' )->text(),
437
					'align' => 'inline',
438
				]
439
			);
440
		}
441
442
		$hiddenFields = '';
443
		if ( $moveOverShared ) {
444
			$hiddenFields .= Html::hidden( 'wpMoveOverSharedFile', '1' );
445
		}
446
447 View Code Duplication
		if ( $deleteAndMove ) {
448
			$fields[] = new OOUI\FieldLayout(
449
				new OOUI\CheckboxInputWidget( [
450
					'name' => 'wpDeleteAndMove',
451
					'id' => 'wpDeleteAndMove',
452
					'value' => '1',
453
				] ),
454
				[
455
					'label' => $this->msg( 'delete_and_move_confirm' )->text(),
456
					'align' => 'inline',
457
				]
458
			);
459
		}
460
461
		$fields[] = new OOUI\FieldLayout(
462
			new OOUI\ButtonInputWidget( [
463
				'name' => 'wpMove',
464
				'value' => $this->msg( 'movepagebtn' )->text(),
465
				'label' => $this->msg( 'movepagebtn' )->text(),
466
				'flags' => [ 'constructive', 'primary' ],
467
				'type' => 'submit',
468
			] ),
469
			[
470
				'align' => 'top',
471
			]
472
		);
473
474
		$fieldset = new OOUI\FieldsetLayout( [
475
			'label' => $this->msg( 'move-page-legend' )->text(),
476
			'id' => 'mw-movepage-table',
477
			'items' => $fields,
478
		] );
479
480
		$form = new OOUI\FormLayout( [
481
			'method' => 'post',
482
			'action' => $this->getPageTitle()->getLocalURL( 'action=submit' ),
483
			'id' => 'movepage',
484
		] );
485
		$form->appendContent(
486
			$fieldset,
487
			new OOUI\HtmlSnippet(
488
				$hiddenFields .
489
				Html::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
490
				Html::hidden( 'wpEditToken', $user->getEditToken() )
491
			)
492
		);
493
494
		$out->addHTML(
495
			new OOUI\PanelLayout( [
496
				'classes' => [ 'movepage-wrapper' ],
497
				'expanded' => false,
498
				'padded' => true,
499
				'framed' => true,
500
				'content' => $form,
501
			] )
502
		);
503
504
		$this->showLogFragment( $this->oldTitle );
505
		$this->showSubpages( $this->oldTitle );
506
	}
507
508
	function doSubmit() {
509
		$user = $this->getUser();
510
511
		if ( $user->pingLimiter( 'move' ) ) {
512
			throw new ThrottledError;
513
		}
514
515
		$ot = $this->oldTitle;
516
		$nt = $this->newTitle;
517
518
		# don't allow moving to pages with # in
519
		if ( !$nt || $nt->hasFragment() ) {
520
			$this->showForm( [ [ 'badtitletext' ] ] );
521
522
			return;
523
		}
524
525
		# Show a warning if the target file exists on a shared repo
526
		if ( $nt->getNamespace() == NS_FILE
527
			&& !( $this->moveOverShared && $user->isAllowed( 'reupload-shared' ) )
528
			&& !RepoGroup::singleton()->getLocalRepo()->findFile( $nt )
529
			&& wfFindFile( $nt )
530
		) {
531
			$this->showForm( [ [ 'file-exists-sharedrepo' ] ] );
532
533
			return;
534
		}
535
536
		# Delete to make way if requested
537
		if ( $this->deleteAndMove ) {
538
			$permErrors = $nt->getUserPermissionsErrors( 'delete', $user );
539
			if ( count( $permErrors ) ) {
540
				# Only show the first error
541
				$this->showForm( $permErrors );
542
543
				return;
544
			}
545
546
			$reason = $this->msg( 'delete_and_move_reason', $ot )->inContentLanguage()->text();
547
548
			// Delete an associated image if there is
549
			if ( $nt->getNamespace() == NS_FILE ) {
550
				$file = wfLocalFile( $nt );
551
				$file->load( File::READ_LATEST );
552
				if ( $file->exists() ) {
553
					$file->delete( $reason, false, $user );
554
				}
555
			}
556
557
			$error = ''; // passed by ref
558
			$page = WikiPage::factory( $nt );
559
			$deleteStatus = $page->doDeleteArticleReal( $reason, false, 0, true, $error, $user );
560
			if ( !$deleteStatus->isGood() ) {
561
				$this->showForm( $deleteStatus->getErrorsArray() );
0 ignored issues
show
Deprecated Code introduced by
The method Status::getErrorsArray() has been deprecated with message: 1.25

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
562
563
				return;
564
			}
565
		}
566
567
		$handler = ContentHandler::getForTitle( $ot );
568
569
		if ( !$handler->supportsRedirects() ) {
570
			$createRedirect = false;
571
		} elseif ( $user->isAllowed( 'suppressredirect' ) ) {
572
			$createRedirect = $this->leaveRedirect;
573
		} else {
574
			$createRedirect = true;
575
		}
576
577
		# Do the actual move.
578
		$mp = new MovePage( $ot, $nt );
579
		$valid = $mp->isValidMove();
580
		if ( !$valid->isOK() ) {
581
			$this->showForm( $valid->getErrorsArray() );
0 ignored issues
show
Deprecated Code introduced by
The method Status::getErrorsArray() has been deprecated with message: 1.25

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
582
			return;
583
		}
584
585
		$permStatus = $mp->checkPermissions( $user, $this->reason );
586
		if ( !$permStatus->isOK() ) {
587
			$this->showForm( $permStatus->getErrorsArray() );
0 ignored issues
show
Deprecated Code introduced by
The method Status::getErrorsArray() has been deprecated with message: 1.25

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
588
			return;
589
		}
590
591
		$status = $mp->move( $user, $this->reason, $createRedirect );
592
		if ( !$status->isOK() ) {
593
			$this->showForm( $status->getErrorsArray() );
0 ignored issues
show
Deprecated Code introduced by
The method Status::getErrorsArray() has been deprecated with message: 1.25

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
594
			return;
595
		}
596
597
		if ( $this->getConfig()->get( 'FixDoubleRedirects' ) && $this->fixRedirects ) {
598
			DoubleRedirectJob::fixRedirects( 'move', $ot, $nt );
599
		}
600
601
		$out = $this->getOutput();
602
		$out->setPageTitle( $this->msg( 'pagemovedsub' ) );
603
604
		$linkRenderer = $this->getLinkRenderer();
605
		$oldLink = $linkRenderer->makeLink(
606
			$ot,
607
			null,
608
			[ 'id' => 'movepage-oldlink' ],
609
			[ 'redirect' => 'no' ]
610
		);
611
		$newLink = $linkRenderer->makeKnownLink(
612
			$nt,
613
			null,
614
			[ 'id' => 'movepage-newlink' ]
615
		);
616
		$oldText = $ot->getPrefixedText();
617
		$newText = $nt->getPrefixedText();
618
619
		if ( $ot->exists() ) {
620
			// NOTE: we assume that if the old title exists, it's because it was re-created as
621
			// a redirect to the new title. This is not safe, but what we did before was
622
			// even worse: we just determined whether a redirect should have been created,
623
			// and reported that it was created if it should have, without any checks.
624
			// Also note that isRedirect() is unreliable because of bug 37209.
625
			$msgName = 'movepage-moved-redirect';
626
		} else {
627
			$msgName = 'movepage-moved-noredirect';
628
		}
629
630
		$out->addHTML( $this->msg( 'movepage-moved' )->rawParams( $oldLink,
631
			$newLink )->params( $oldText, $newText )->parseAsBlock() );
632
		$out->addWikiMsg( $msgName );
633
634
		Hooks::run( 'SpecialMovepageAfterMove', [ &$this, &$ot, &$nt ] );
635
636
		# Now we move extra pages we've been asked to move: subpages and talk
637
		# pages.  First, if the old page or the new page is a talk page, we
638
		# can't move any talk pages: cancel that.
639
		if ( $ot->isTalkPage() || $nt->isTalkPage() ) {
640
			$this->moveTalk = false;
641
		}
642
643
		if ( count( $ot->getUserPermissionsErrors( 'move-subpages', $user ) ) ) {
644
			$this->moveSubpages = false;
645
		}
646
647
		/**
648
		 * Next make a list of id's.  This might be marginally less efficient
649
		 * than a more direct method, but this is not a highly performance-cri-
650
		 * tical code path and readable code is more important here.
651
		 *
652
		 * If the target namespace doesn't allow subpages, moving with subpages
653
		 * would mean that you couldn't move them back in one operation, which
654
		 * is bad.
655
		 * @todo FIXME: A specific error message should be given in this case.
656
		 */
657
658
		// @todo FIXME: Use Title::moveSubpages() here
659
		$dbr = wfGetDB( DB_MASTER );
660
		if ( $this->moveSubpages && (
661
			MWNamespace::hasSubpages( $nt->getNamespace() ) || (
662
				$this->moveTalk
663
					&& MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() )
664
			)
665
		) ) {
666
			$conds = [
667
				'page_title' . $dbr->buildLike( $ot->getDBkey() . '/', $dbr->anyString() )
668
					. ' OR page_title = ' . $dbr->addQuotes( $ot->getDBkey() )
669
			];
670
			$conds['page_namespace'] = [];
671
			if ( MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
672
				$conds['page_namespace'][] = $ot->getNamespace();
673
			}
674
			if ( $this->moveTalk &&
675
				MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() )
676
			) {
677
				$conds['page_namespace'][] = $ot->getTalkPage()->getNamespace();
678
			}
679
		} elseif ( $this->moveTalk ) {
680
			$conds = [
681
				'page_namespace' => $ot->getTalkPage()->getNamespace(),
682
				'page_title' => $ot->getDBkey()
683
			];
684
		} else {
685
			# Skip the query
686
			$conds = null;
687
		}
688
689
		$extraPages = [];
690
		if ( !is_null( $conds ) ) {
691
			$extraPages = TitleArray::newFromResult(
692
				$dbr->select( 'page',
0 ignored issues
show
Bug introduced by
It seems like $dbr->select('page', arr...'), $conds, __METHOD__) targeting DatabaseBase::select() can also be of type boolean; however, TitleArray::newFromResult() does only seem to accept object<ResultWrapper>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
693
					[ 'page_id', 'page_namespace', 'page_title' ],
694
					$conds,
695
					__METHOD__
696
				)
697
			);
698
		}
699
700
		$extraOutput = [];
701
		$count = 1;
702
		foreach ( $extraPages as $oldSubpage ) {
0 ignored issues
show
Bug introduced by
The expression $extraPages of type object<TitleArrayFromResult>|null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
703
			if ( $ot->equals( $oldSubpage ) || $nt->equals( $oldSubpage ) ) {
704
				# Already did this one.
705
				continue;
706
			}
707
708
			$newPageName = preg_replace(
709
				'#^' . preg_quote( $ot->getDBkey(), '#' ) . '#',
710
				StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234
711
				$oldSubpage->getDBkey()
712
			);
713
714
			if ( $oldSubpage->isSubpage() && ( $ot->isTalkPage() xor $nt->isTalkPage() ) ) {
715
				// Moving a subpage from a subject namespace to a talk namespace or vice-versa
716
				$newNs = $nt->getNamespace();
717
			} elseif ( $oldSubpage->isTalkPage() ) {
718
				$newNs = $nt->getTalkPage()->getNamespace();
719
			} else {
720
				$newNs = $nt->getSubjectPage()->getNamespace();
721
			}
722
723
			# Bug 14385: we need makeTitleSafe because the new page names may
724
			# be longer than 255 characters.
725
			$newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
726
			if ( !$newSubpage ) {
727
				$oldLink = $linkRenderer->makeKnownLink( $oldSubpage );
728
				$extraOutput[] = $this->msg( 'movepage-page-unmoved' )->rawParams( $oldLink )
729
					->params( Title::makeName( $newNs, $newPageName ) )->escaped();
730
				continue;
731
			}
732
733
			# This was copy-pasted from Renameuser, bleh.
734
			if ( $newSubpage->exists() && !$oldSubpage->isValidMoveTarget( $newSubpage ) ) {
735
				$link = $linkRenderer->makeKnownLink( $newSubpage );
736
				$extraOutput[] = $this->msg( 'movepage-page-exists' )->rawParams( $link )->escaped();
737
			} else {
738
				$success = $oldSubpage->moveTo( $newSubpage, true, $this->reason, $createRedirect );
739
740
				if ( $success === true ) {
741
					if ( $this->fixRedirects ) {
742
						DoubleRedirectJob::fixRedirects( 'move', $oldSubpage, $newSubpage );
743
					}
744
					$oldLink = $linkRenderer->makeLink(
745
						$oldSubpage,
746
						null,
747
						[],
748
						[ 'redirect' => 'no' ]
749
					);
750
751
					$newLink = $linkRenderer->makeKnownLink( $newSubpage );
752
					$extraOutput[] = $this->msg( 'movepage-page-moved' )
753
						->rawParams( $oldLink, $newLink )->escaped();
754
					++$count;
755
756
					$maximumMovedPages = $this->getConfig()->get( 'MaximumMovedPages' );
757
					if ( $count >= $maximumMovedPages ) {
758
						$extraOutput[] = $this->msg( 'movepage-max-pages' )
759
							->numParams( $maximumMovedPages )->escaped();
760
						break;
761
					}
762
				} else {
763
					$oldLink = $linkRenderer->makeKnownLink( $oldSubpage );
764
					$newLink = $linkRenderer->makeLink( $newSubpage );
765
					$extraOutput[] = $this->msg( 'movepage-page-unmoved' )
766
						->rawParams( $oldLink, $newLink )->escaped();
767
				}
768
			}
769
		}
770
771
		if ( $extraOutput !== [] ) {
772
			$out->addHTML( "<ul>\n<li>" . implode( "</li>\n<li>", $extraOutput ) . "</li>\n</ul>" );
773
		}
774
775
		# Deal with watches (we don't watch subpages)
776
		WatchAction::doWatchOrUnwatch( $this->watch, $ot, $user );
777
		WatchAction::doWatchOrUnwatch( $this->watch, $nt, $user );
778
	}
779
780
	function showLogFragment( $title ) {
781
		$moveLogPage = new LogPage( 'move' );
782
		$out = $this->getOutput();
783
		$out->addHTML( Xml::element( 'h2', null, $moveLogPage->getName()->text() ) );
784
		LogEventsList::showLogExtract( $out, 'move', $title );
785
	}
786
787
	function showSubpages( $title ) {
788
		if ( !MWNamespace::hasSubpages( $title->getNamespace() ) ) {
789
			return;
790
		}
791
792
		$subpages = $title->getSubpages();
793
		$count = $subpages instanceof TitleArray ? $subpages->count() : 0;
794
795
		$out = $this->getOutput();
796
		$out->wrapWikiMsg( '== $1 ==', [ 'movesubpage', $count ] );
797
798
		# No subpages.
799
		if ( $count == 0 ) {
800
			$out->addWikiMsg( 'movenosubpage' );
801
802
			return;
803
		}
804
805
		$out->addWikiMsg( 'movesubpagetext', $this->getLanguage()->formatNum( $count ) );
806
		$out->addHTML( "<ul>\n" );
807
808
		$linkRenderer = $this->getLinkRenderer();
809
		foreach ( $subpages as $subpage ) {
810
			$link = $linkRenderer->makeLink( $subpage );
811
			$out->addHTML( "<li>$link</li>\n" );
812
		}
813
		$out->addHTML( "</ul>\n" );
814
	}
815
816
	/**
817
	 * Return an array of subpages beginning with $search that this special page will accept.
818
	 *
819
	 * @param string $search Prefix to search for
820
	 * @param int $limit Maximum number of results to return (usually 10)
821
	 * @param int $offset Number of results to skip (usually 0)
822
	 * @return string[] Matching subpages
823
	 */
824
	public function prefixSearchSubpages( $search, $limit, $offset ) {
825
		return $this->prefixSearchString( $search, $limit, $offset );
826
	}
827
828
	protected function getGroupName() {
829
		return 'pagetools';
830
	}
831
}
832