MediaWiki REL1_34
SpecialMovepage.php
Go to the documentation of this file.
1<?php
25
33 protected $oldTitle = null;
34
36 protected $newTitle;
37
39 protected $reason;
40
41 // Checks
42
44 protected $moveTalk;
45
47 protected $deleteAndMove;
48
50 protected $moveSubpages;
51
53 protected $fixRedirects;
54
56 protected $leaveRedirect;
57
59 protected $moveOverShared;
60
61 private $watch = false;
62
63 public function __construct() {
64 parent::__construct( 'Movepage' );
65 }
66
67 public function doesWrites() {
68 return true;
69 }
70
71 public function execute( $par ) {
73
74 $this->checkReadOnly();
75
76 $this->setHeaders();
77 $this->outputHeader();
78
79 $request = $this->getRequest();
80 $target = $par ?? $request->getVal( 'target' );
81
82 // Yes, the use of getVal() and getText() is wanted, see T22365
83
84 $oldTitleText = $request->getVal( 'wpOldTitle', $target );
85 $this->oldTitle = Title::newFromText( $oldTitleText );
86
87 if ( !$this->oldTitle ) {
88 // Either oldTitle wasn't passed, or newFromText returned null
89 throw new ErrorPageError( 'notargettitle', 'notargettext' );
90 }
91 if ( !$this->oldTitle->exists() ) {
92 throw new ErrorPageError( 'nopagetitle', 'nopagetext' );
93 }
94
95 $newTitleTextMain = $request->getText( 'wpNewTitleMain' );
96 $newTitleTextNs = $request->getInt( 'wpNewTitleNs', $this->oldTitle->getNamespace() );
97 // Backwards compatibility for forms submitting here from other sources
98 // which is more common than it should be..
99 $newTitleText_bc = $request->getText( 'wpNewTitle' );
100 $this->newTitle = strlen( $newTitleText_bc ) > 0
101 ? Title::newFromText( $newTitleText_bc )
102 : Title::makeTitleSafe( $newTitleTextNs, $newTitleTextMain );
103
104 $user = $this->getUser();
105
106 # Check rights
107 $permErrors = $this->oldTitle->getUserPermissionsErrors( 'move', $user );
108 if ( count( $permErrors ) ) {
109 // Auto-block user's IP if the account was "hard" blocked
110 DeferredUpdates::addCallableUpdate( function () use ( $user ) {
111 $user->spreadAnyEditBlock();
112 } );
113 throw new PermissionsError( 'move', $permErrors );
114 }
115
116 $def = !$request->wasPosted();
117
118 $this->reason = $request->getText( 'wpReason' );
119 $this->moveTalk = $request->getBool( 'wpMovetalk', $def );
120 $this->fixRedirects = $request->getBool( 'wpFixRedirects', $def );
121 $this->leaveRedirect = $request->getBool( 'wpLeaveRedirect', $def );
122 $this->moveSubpages = $request->getBool( 'wpMovesubpages' );
123 $this->deleteAndMove = $request->getBool( 'wpDeleteAndMove' );
124 $this->moveOverShared = $request->getBool( 'wpMoveOverSharedFile' );
125 $this->watch = $request->getCheck( 'wpWatch' ) && $user->isLoggedIn();
126
127 if ( $request->getVal( 'action' ) == 'submit' && $request->wasPosted()
128 && $user->matchEditToken( $request->getVal( 'wpEditToken' ) )
129 ) {
130 $this->doSubmit();
131 } else {
132 $this->showForm( [] );
133 }
134 }
135
144 function showForm( $err, $isPermError = false ) {
145 $this->getSkin()->setRelevantTitle( $this->oldTitle );
146
147 $out = $this->getOutput();
148 $out->setPageTitle( $this->msg( 'move-page', $this->oldTitle->getPrefixedText() ) );
149 $out->addModuleStyles( 'mediawiki.special' );
150 $out->addModules( 'mediawiki.misc-authed-ooui' );
151 $this->addHelpLink( 'Help:Moving a page' );
152
153 $handlerSupportsRedirects = ContentHandler::getForTitle( $this->oldTitle )
154 ->supportsRedirects();
155
156 if ( $this->getConfig()->get( 'FixDoubleRedirects' ) ) {
157 $out->addWikiMsg( 'movepagetext' );
158 } else {
159 $out->addWikiMsg( $handlerSupportsRedirects ?
160 'movepagetext-noredirectfixer' :
161 'movepagetext-noredirectsupport' );
162 }
163
164 if ( $this->oldTitle->getNamespace() == NS_USER && !$this->oldTitle->isSubpage() ) {
165 $out->wrapWikiMsg(
166 "<div class=\"warningbox mw-moveuserpage-warning\">\n$1\n</div>",
167 'moveuserpage-warning'
168 );
169 } elseif ( $this->oldTitle->getNamespace() == NS_CATEGORY ) {
170 $out->wrapWikiMsg(
171 "<div class=\"warningbox mw-movecategorypage-warning\">\n$1\n</div>",
172 'movecategorypage-warning'
173 );
174 }
175
176 $deleteAndMove = false;
177 $moveOverShared = false;
178
179 $user = $this->getUser();
180
182
183 if ( !$newTitle ) {
184 # Show the current title as a default
185 # when the form is first opened.
187 } elseif ( !count( $err ) ) {
188 # If a title was supplied, probably from the move log revert
189 # link, check for validity. We can then show some diagnostic
190 # information and save a click.
191 $mp = new MovePage( $this->oldTitle, $newTitle );
192 $status = $mp->isValidMove();
193 $status->merge( $mp->checkPermissions( $user, null ) );
194 if ( $status->getErrors() ) {
195 $err = $status->getErrorsArray();
196 }
197 }
198
199 if ( count( $err ) == 1 && isset( $err[0][0] ) && $err[0][0] == 'articleexists'
200 && MediaWikiServices::getInstance()->getPermissionManager()
201 ->quickUserCan( 'delete', $user, $newTitle )
202 ) {
203 $out->wrapWikiMsg(
204 "<div class='warningbox'>\n$1\n</div>\n",
205 [ 'delete_and_move_text', $newTitle->getPrefixedText() ]
206 );
207 $deleteAndMove = true;
208 $err = [];
209 }
210
211 if ( count( $err ) == 1 && isset( $err[0][0] ) && $err[0][0] == 'file-exists-sharedrepo'
212 && MediaWikiServices::getInstance()
214 ->userHasRight( $user, 'reupload-shared' )
215 ) {
216 $out->wrapWikiMsg(
217 "<div class='warningbox'>\n$1\n</div>\n",
218 [
219 'move-over-sharedrepo',
221 ]
222 );
223 $moveOverShared = true;
224 $err = [];
225 }
226
227 $oldTalk = $this->oldTitle->getTalkPage();
228 $oldTitleSubpages = $this->oldTitle->hasSubpages();
229 $oldTitleTalkSubpages = $this->oldTitle->getTalkPage()->hasSubpages();
230
231 $canMoveSubpage = ( $oldTitleSubpages || $oldTitleTalkSubpages ) &&
232 !count( $this->oldTitle->getUserPermissionsErrors( 'move-subpages', $user ) );
233
234 # We also want to be able to move assoc. subpage talk-pages even if base page
235 # has no associated talk page, so || with $oldTitleTalkSubpages.
236 $considerTalk = !$this->oldTitle->isTalkPage() &&
237 ( $oldTalk->exists()
238 || ( $oldTitleTalkSubpages && $canMoveSubpage ) );
239
241 if ( $this->getConfig()->get( 'FixDoubleRedirects' ) ) {
242 $hasRedirects = $dbr->selectField( 'redirect', '1',
243 [
244 'rd_namespace' => $this->oldTitle->getNamespace(),
245 'rd_title' => $this->oldTitle->getDBkey(),
246 ], __METHOD__ );
247 } else {
248 $hasRedirects = false;
249 }
250
251 if ( count( $err ) ) {
252 if ( $isPermError ) {
253 $action_desc = $this->msg( 'action-move' )->plain();
254 $errMsgHtml = $this->msg( 'permissionserrorstext-withaction',
255 count( $err ), $action_desc )->parseAsBlock();
256 } else {
257 $errMsgHtml = $this->msg( 'cannotmove', count( $err ) )->parseAsBlock();
258 }
259
260 if ( count( $err ) == 1 ) {
261 $errMsg = $err[0];
262 $errMsgName = array_shift( $errMsg );
263
264 if ( $errMsgName == 'hookaborted' ) {
265 $errMsgHtml .= "<p>{$errMsg[0]}</p>\n";
266 } else {
267 $errMsgHtml .= $this->msg( $errMsgName, $errMsg )->parseAsBlock();
268 }
269 } else {
270 $errStr = [];
271
272 foreach ( $err as $errMsg ) {
273 if ( $errMsg[0] == 'hookaborted' ) {
274 $errStr[] = $errMsg[1];
275 } else {
276 $errMsgName = array_shift( $errMsg );
277 $errStr[] = $this->msg( $errMsgName, $errMsg )->parse();
278 }
279 }
280
281 $errMsgHtml .= '<ul><li>' . implode( "</li>\n<li>", $errStr ) . "</li></ul>\n";
282 }
283 $out->addHTML( Html::errorBox( $errMsgHtml ) );
284 }
285
286 if ( $this->oldTitle->isProtected( 'move' ) ) {
287 # Is the title semi-protected?
288 if ( $this->oldTitle->isSemiProtected( 'move' ) ) {
289 $noticeMsg = 'semiprotectedpagemovewarning';
290 } else {
291 # Then it must be protected based on static groups (regular)
292 $noticeMsg = 'protectedpagemovewarning';
293 }
294 $out->addHTML( "<div class='mw-warning-with-logexcerpt'>\n" );
295 $out->addWikiMsg( $noticeMsg );
297 $out,
298 'protect',
299 $this->oldTitle,
300 '',
301 [ 'lim' => 1 ]
302 );
303 $out->addHTML( "</div>\n" );
304 }
305
306 // Length limit for wpReason and wpNewTitleMain is enforced in the
307 // mediawiki.special.movePage module
308
309 $immovableNamespaces = [];
310 $namespaceInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
311 foreach ( array_keys( $this->getLanguage()->getNamespaces() ) as $nsId ) {
312 if ( !$namespaceInfo->isMovable( $nsId ) ) {
313 $immovableNamespaces[] = $nsId;
314 }
315 }
316
317 $out->enableOOUI();
318 $fields = [];
319
320 $fields[] = new OOUI\FieldLayout(
321 new MediaWiki\Widget\ComplexTitleInputWidget( [
322 'id' => 'wpNewTitle',
323 'namespace' => [
324 'id' => 'wpNewTitleNs',
325 'name' => 'wpNewTitleNs',
326 'value' => $newTitle->getNamespace(),
327 'exclude' => $immovableNamespaces,
328 ],
329 'title' => [
330 'id' => 'wpNewTitleMain',
331 'name' => 'wpNewTitleMain',
332 'value' => $newTitle->getText(),
333 // Inappropriate, since we're expecting the user to input a non-existent page's title
334 'suggestions' => false,
335 ],
336 'infusable' => true,
337 ] ),
338 [
339 'label' => $this->msg( 'newtitle' )->text(),
340 'align' => 'top',
341 ]
342 );
343
344 // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
345 // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
346 // Unicode codepoints.
347 $fields[] = new OOUI\FieldLayout(
348 new OOUI\TextInputWidget( [
349 'name' => 'wpReason',
350 'id' => 'wpReason',
351 'maxLength' => CommentStore::COMMENT_CHARACTER_LIMIT,
352 'infusable' => true,
353 'value' => $this->reason,
354 ] ),
355 [
356 'label' => $this->msg( 'movereason' )->text(),
357 'align' => 'top',
358 ]
359 );
360
361 if ( $considerTalk ) {
362 $fields[] = new OOUI\FieldLayout(
363 new OOUI\CheckboxInputWidget( [
364 'name' => 'wpMovetalk',
365 'id' => 'wpMovetalk',
366 'value' => '1',
367 'selected' => $this->moveTalk,
368 ] ),
369 [
370 'label' => $this->msg( 'movetalk' )->text(),
371 'help' => new OOUI\HtmlSnippet( $this->msg( 'movepagetalktext' )->parseAsBlock() ),
372 'helpInline' => true,
373 'align' => 'inline',
374 'id' => 'wpMovetalk-field',
375 ]
376 );
377 }
378
379 if ( MediaWikiServices::getInstance()
381 ->userHasRight( $user, 'suppressredirect' )
382 ) {
383 if ( $handlerSupportsRedirects ) {
384 $isChecked = $this->leaveRedirect;
385 $isDisabled = false;
386 } else {
387 $isChecked = false;
388 $isDisabled = true;
389 }
390 $fields[] = new OOUI\FieldLayout(
391 new OOUI\CheckboxInputWidget( [
392 'name' => 'wpLeaveRedirect',
393 'id' => 'wpLeaveRedirect',
394 'value' => '1',
395 'selected' => $isChecked,
396 'disabled' => $isDisabled,
397 ] ),
398 [
399 'label' => $this->msg( 'move-leave-redirect' )->text(),
400 'align' => 'inline',
401 ]
402 );
403 }
404
405 if ( $hasRedirects ) {
406 $fields[] = new OOUI\FieldLayout(
407 new OOUI\CheckboxInputWidget( [
408 'name' => 'wpFixRedirects',
409 'id' => 'wpFixRedirects',
410 'value' => '1',
411 'selected' => $this->fixRedirects,
412 ] ),
413 [
414 'label' => $this->msg( 'fix-double-redirects' )->text(),
415 'align' => 'inline',
416 ]
417 );
418 }
419
420 if ( $canMoveSubpage ) {
421 $maximumMovedPages = $this->getConfig()->get( 'MaximumMovedPages' );
422 $fields[] = new OOUI\FieldLayout(
423 new OOUI\CheckboxInputWidget( [
424 'name' => 'wpMovesubpages',
425 'id' => 'wpMovesubpages',
426 'value' => '1',
427 'selected' => true, // T222953 Always check the box
428 ] ),
429 [
430 'label' => new OOUI\HtmlSnippet(
431 $this->msg(
432 ( $this->oldTitle->hasSubpages()
433 ? 'move-subpages'
434 : 'move-talk-subpages' )
435 )->numParams( $maximumMovedPages )->params( $maximumMovedPages )->parse()
436 ),
437 'align' => 'inline',
438 ]
439 );
440 }
441
442 # Don't allow watching if user is not logged in
443 if ( $user->isLoggedIn() ) {
444 $watchChecked = $user->isLoggedIn() && ( $this->watch || $user->getBoolOption( 'watchmoves' )
445 || $user->isWatched( $this->oldTitle ) );
446 $fields[] = new OOUI\FieldLayout(
447 new OOUI\CheckboxInputWidget( [
448 'name' => 'wpWatch',
449 'id' => 'watch', # ew
450 'value' => '1',
451 'selected' => $watchChecked,
452 ] ),
453 [
454 'label' => $this->msg( 'move-watch' )->text(),
455 'align' => 'inline',
456 ]
457 );
458 }
459
460 $hiddenFields = '';
461 if ( $moveOverShared ) {
462 $hiddenFields .= Html::hidden( 'wpMoveOverSharedFile', '1' );
463 }
464
465 if ( $deleteAndMove ) {
466 $fields[] = new OOUI\FieldLayout(
467 new OOUI\CheckboxInputWidget( [
468 'name' => 'wpDeleteAndMove',
469 'id' => 'wpDeleteAndMove',
470 'value' => '1',
471 ] ),
472 [
473 'label' => $this->msg( 'delete_and_move_confirm' )->text(),
474 'align' => 'inline',
475 ]
476 );
477 }
478
479 $fields[] = new OOUI\FieldLayout(
480 new OOUI\ButtonInputWidget( [
481 'name' => 'wpMove',
482 'value' => $this->msg( 'movepagebtn' )->text(),
483 'label' => $this->msg( 'movepagebtn' )->text(),
484 'flags' => [ 'primary', 'progressive' ],
485 'type' => 'submit',
486 ] ),
487 [
488 'align' => 'top',
489 ]
490 );
491
492 $fieldset = new OOUI\FieldsetLayout( [
493 'label' => $this->msg( 'move-page-legend' )->text(),
494 'id' => 'mw-movepage-table',
495 'items' => $fields,
496 ] );
497
498 $form = new OOUI\FormLayout( [
499 'method' => 'post',
500 'action' => $this->getPageTitle()->getLocalURL( 'action=submit' ),
501 'id' => 'movepage',
502 ] );
503 $form->appendContent(
504 $fieldset,
505 new OOUI\HtmlSnippet(
506 $hiddenFields .
507 Html::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
508 Html::hidden( 'wpEditToken', $user->getEditToken() )
509 )
510 );
511
512 $out->addHTML(
513 new OOUI\PanelLayout( [
514 'classes' => [ 'movepage-wrapper' ],
515 'expanded' => false,
516 'padded' => true,
517 'framed' => true,
518 'content' => $form,
519 ] )
520 );
521
522 $this->showLogFragment( $this->oldTitle );
523 $this->showSubpages( $this->oldTitle );
524 }
525
526 function doSubmit() {
527 $user = $this->getUser();
528 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
529
530 if ( $user->pingLimiter( 'move' ) ) {
531 throw new ThrottledError;
532 }
533
534 $ot = $this->oldTitle;
535 $nt = $this->newTitle;
536
537 # don't allow moving to pages with # in
538 if ( !$nt || $nt->hasFragment() ) {
539 $this->showForm( [ [ 'badtitletext' ] ] );
540
541 return;
542 }
543
544 $services = MediaWikiServices::getInstance();
545
546 # Show a warning if the target file exists on a shared repo
547 $repoGroup = $services->getRepoGroup();
548 if ( $nt->getNamespace() == NS_FILE
549 && !( $this->moveOverShared && $permissionManager->userHasRight( $user, 'reupload-shared' ) )
550 && !$repoGroup->getLocalRepo()->findFile( $nt )
551 && $repoGroup->findFile( $nt )
552 ) {
553 $this->showForm( [ [ 'file-exists-sharedrepo' ] ] );
554
555 return;
556 }
557
558 # Delete to make way if requested
559 if ( $this->deleteAndMove ) {
560 $permErrors = $permissionManager->getPermissionErrors( 'delete', $user, $nt );
561 if ( count( $permErrors ) ) {
562 # Only show the first error
563 $this->showForm( $permErrors, true );
564
565 return;
566 }
567
568 $page = WikiPage::factory( $nt );
569
570 // Small safety margin to guard against concurrent edits
571 if ( $page->isBatchedDelete( 5 ) ) {
572 $this->showForm( [ [ 'movepage-delete-first' ] ] );
573
574 return;
575 }
576
577 $reason = $this->msg( 'delete_and_move_reason', $ot )->inContentLanguage()->text();
578
579 // Delete an associated image if there is
580 if ( $nt->getNamespace() == NS_FILE ) {
581 $file = $repoGroup->getLocalRepo()->newFile( $nt );
582 $file->load( File::READ_LATEST );
583 if ( $file->exists() ) {
584 $file->delete( $reason, false, $user );
585 }
586 }
587
588 $error = ''; // passed by ref
589 $deleteStatus = $page->doDeleteArticleReal( $reason, false, 0, true, $error, $user );
590 if ( !$deleteStatus->isGood() ) {
591 $this->showForm( $deleteStatus->getErrorsArray() );
592
593 return;
594 }
595 }
596
597 $handler = ContentHandler::getForTitle( $ot );
598
599 if ( !$handler->supportsRedirects() ) {
600 $createRedirect = false;
601 } elseif ( $permissionManager->userHasRight( $user, 'suppressredirect' ) ) {
602 $createRedirect = $this->leaveRedirect;
603 } else {
604 $createRedirect = true;
605 }
606
607 # Do the actual move.
608 $mp = new MovePage( $ot, $nt );
609
610 # check whether the requested actions are permitted / possible
611 $userPermitted = $mp->checkPermissions( $user, $this->reason )->isOK();
612 if ( $ot->isTalkPage() || $nt->isTalkPage() ) {
613 $this->moveTalk = false;
614 }
615 if ( $this->moveSubpages ) {
616 $this->moveSubpages = $permissionManager->userCan( 'move-subpages', $user, $ot );
617 }
618
619 $status = $mp->moveIfAllowed( $user, $this->reason, $createRedirect );
620 if ( !$status->isOK() ) {
621 $this->showForm( $status->getErrorsArray(), !$userPermitted );
622 return;
623 }
624
625 if ( $this->getConfig()->get( 'FixDoubleRedirects' ) && $this->fixRedirects ) {
626 DoubleRedirectJob::fixRedirects( 'move', $ot, $nt );
627 }
628
629 $out = $this->getOutput();
630 $out->setPageTitle( $this->msg( 'pagemovedsub' ) );
631
633 $oldLink = $linkRenderer->makeLink(
634 $ot,
635 null,
636 [ 'id' => 'movepage-oldlink' ],
637 [ 'redirect' => 'no' ]
638 );
639 $newLink = $linkRenderer->makeKnownLink(
640 $nt,
641 null,
642 [ 'id' => 'movepage-newlink' ]
643 );
644 $oldText = $ot->getPrefixedText();
645 $newText = $nt->getPrefixedText();
646
647 if ( $ot->exists() ) {
648 // NOTE: we assume that if the old title exists, it's because it was re-created as
649 // a redirect to the new title. This is not safe, but what we did before was
650 // even worse: we just determined whether a redirect should have been created,
651 // and reported that it was created if it should have, without any checks.
652 // Also note that isRedirect() is unreliable because of T39209.
653 $msgName = 'movepage-moved-redirect';
654 } else {
655 $msgName = 'movepage-moved-noredirect';
656 }
657
658 $out->addHTML( $this->msg( 'movepage-moved' )->rawParams( $oldLink,
659 $newLink )->params( $oldText, $newText )->parseAsBlock() );
660 $out->addWikiMsg( $msgName );
661
662 // Avoid PHP 7.1 warning from passing $this by reference
663 $movePage = $this;
664 Hooks::run( 'SpecialMovepageAfterMove', [ &$movePage, &$ot, &$nt ] );
665
666 /*
667 * Now we move extra pages we've been asked to move: subpages and talk
668 * pages.
669 *
670 * First, make a list of id's. This might be marginally less efficient
671 * than a more direct method, but this is not a highly performance-cri-
672 * tical code path and readable code is more important here.
673 *
674 * If the target namespace doesn't allow subpages, moving with subpages
675 * would mean that you couldn't move them back in one operation, which
676 * is bad.
677 * @todo FIXME: A specific error message should be given in this case.
678 */
679
680 // @todo FIXME: Use Title::moveSubpages() here
681 $nsInfo = $services->getNamespaceInfo();
683 if ( $this->moveSubpages && (
684 $nsInfo->hasSubpages( $nt->getNamespace() ) || (
685 $this->moveTalk
686 && $nsInfo->hasSubpages( $nt->getTalkPage()->getNamespace() )
687 )
688 ) ) {
689 $conds = [
690 'page_title' . $dbr->buildLike( $ot->getDBkey() . '/', $dbr->anyString() )
691 . ' OR page_title = ' . $dbr->addQuotes( $ot->getDBkey() )
692 ];
693 $conds['page_namespace'] = [];
694 if ( $nsInfo->hasSubpages( $nt->getNamespace() ) ) {
695 $conds['page_namespace'][] = $ot->getNamespace();
696 }
697 if ( $this->moveTalk &&
698 $nsInfo->hasSubpages( $nt->getTalkPage()->getNamespace() )
699 ) {
700 $conds['page_namespace'][] = $ot->getTalkPage()->getNamespace();
701 }
702 } elseif ( $this->moveTalk ) {
703 $conds = [
704 'page_namespace' => $ot->getTalkPage()->getNamespace(),
705 'page_title' => $ot->getDBkey()
706 ];
707 } else {
708 # Skip the query
709 $conds = null;
710 }
711
712 $extraPages = [];
713 if ( !is_null( $conds ) ) {
714 $extraPages = TitleArray::newFromResult(
715 $dbr->select( 'page',
716 [ 'page_id', 'page_namespace', 'page_title' ],
717 $conds,
718 __METHOD__
719 )
720 );
721 }
722
723 $extraOutput = [];
724 $count = 1;
725 foreach ( $extraPages as $oldSubpage ) {
726 if ( $ot->equals( $oldSubpage ) || $nt->equals( $oldSubpage ) ) {
727 # Already did this one.
728 continue;
729 }
730
731 $newPageName = preg_replace(
732 '#^' . preg_quote( $ot->getDBkey(), '#' ) . '#',
733 StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # T23234
734 $oldSubpage->getDBkey()
735 );
736
737 if ( $oldSubpage->isSubpage() && ( $ot->isTalkPage() xor $nt->isTalkPage() ) ) {
738 // Moving a subpage from a subject namespace to a talk namespace or vice-versa
739 $newNs = $nt->getNamespace();
740 } elseif ( $oldSubpage->isTalkPage() ) {
741 $newNs = $nt->getTalkPage()->getNamespace();
742 } else {
743 $newNs = $nt->getSubjectPage()->getNamespace();
744 }
745
746 # T16385: we need makeTitleSafe because the new page names may
747 # be longer than 255 characters.
748 $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
749 if ( !$newSubpage ) {
750 $oldLink = $linkRenderer->makeKnownLink( $oldSubpage );
751 $extraOutput[] = $this->msg( 'movepage-page-unmoved' )->rawParams( $oldLink )
752 ->params( Title::makeName( $newNs, $newPageName ) )->escaped();
753 continue;
754 }
755
756 $mp = new MovePage( $oldSubpage, $newSubpage );
757 # This was copy-pasted from Renameuser, bleh.
758 if ( $newSubpage->exists() && !$mp->isValidMove()->isOK() ) {
759 $link = $linkRenderer->makeKnownLink( $newSubpage );
760 $extraOutput[] = $this->msg( 'movepage-page-exists' )->rawParams( $link )->escaped();
761 } else {
762 $status = $mp->moveIfAllowed( $user, $this->reason, $createRedirect );
763
764 if ( $status->isOK() ) {
765 if ( $this->fixRedirects ) {
766 DoubleRedirectJob::fixRedirects( 'move', $oldSubpage, $newSubpage );
767 }
768 $oldLink = $linkRenderer->makeLink(
769 $oldSubpage,
770 null,
771 [],
772 [ 'redirect' => 'no' ]
773 );
774
775 $newLink = $linkRenderer->makeKnownLink( $newSubpage );
776 $extraOutput[] = $this->msg( 'movepage-page-moved' )
777 ->rawParams( $oldLink, $newLink )->escaped();
778 ++$count;
779
780 $maximumMovedPages = $this->getConfig()->get( 'MaximumMovedPages' );
781 if ( $count >= $maximumMovedPages ) {
782 $extraOutput[] = $this->msg( 'movepage-max-pages' )
783 ->numParams( $maximumMovedPages )->escaped();
784 break;
785 }
786 } else {
787 $oldLink = $linkRenderer->makeKnownLink( $oldSubpage );
788 $newLink = $linkRenderer->makeLink( $newSubpage );
789 $extraOutput[] = $this->msg( 'movepage-page-unmoved' )
790 ->rawParams( $oldLink, $newLink )->escaped();
791 }
792 }
793 }
794
795 if ( $extraOutput !== [] ) {
796 $out->addHTML( "<ul>\n<li>" . implode( "</li>\n<li>", $extraOutput ) . "</li>\n</ul>" );
797 }
798
799 # Deal with watches (we don't watch subpages)
800 WatchAction::doWatchOrUnwatch( $this->watch, $ot, $user );
801 WatchAction::doWatchOrUnwatch( $this->watch, $nt, $user );
802 }
803
805 $moveLogPage = new LogPage( 'move' );
806 $out = $this->getOutput();
807 $out->addHTML( Xml::element( 'h2', null, $moveLogPage->getName()->text() ) );
808 LogEventsList::showLogExtract( $out, 'move', $title );
809 }
810
817 function showSubpages( $title ) {
818 $nsHasSubpages = MediaWikiServices::getInstance()->getNamespaceInfo()->
819 hasSubpages( $title->getNamespace() );
820 $subpages = $title->getSubpages();
821 $count = $subpages instanceof TitleArray ? $subpages->count() : 0;
822
823 $titleIsTalk = $title->isTalkPage();
824 $subpagesTalk = $title->getTalkPage()->getSubpages();
825 $countTalk = $subpagesTalk instanceof TitleArray ? $subpagesTalk->count() : 0;
826 $totalCount = $count + $countTalk;
827
828 if ( !$nsHasSubpages && $countTalk == 0 ) {
829 return;
830 }
831
832 $this->getOutput()->wrapWikiMsg(
833 '== $1 ==',
834 [ 'movesubpage', ( $titleIsTalk ? $count : $totalCount ) ]
835 );
836
837 if ( $nsHasSubpages ) {
838 $this->showSubpagesList( $subpages, $count, 'movesubpagetext', true );
839 }
840
841 if ( !$titleIsTalk && $countTalk > 0 ) {
842 $this->showSubpagesList( $subpagesTalk, $countTalk, 'movesubpagetalktext' );
843 }
844 }
845
846 function showSubpagesList( $subpages, $pagecount, $wikiMsg, $noSubpageMsg = false ) {
847 $out = $this->getOutput();
848
849 # No subpages.
850 if ( $pagecount == 0 && $noSubpageMsg ) {
851 $out->addWikiMsg( 'movenosubpage' );
852 return;
853 }
854
855 $out->addWikiMsg( $wikiMsg, $this->getLanguage()->formatNum( $pagecount ) );
856 $out->addHTML( "<ul>\n" );
857
858 $linkBatch = new LinkBatch( $subpages );
859 $linkBatch->setCaller( __METHOD__ );
860 $linkBatch->execute();
862
863 foreach ( $subpages as $subpage ) {
864 $link = $linkRenderer->makeLink( $subpage );
865 $out->addHTML( "<li>$link</li>\n" );
866 }
867 $out->addHTML( "</ul>\n" );
868 }
869
878 public function prefixSearchSubpages( $search, $limit, $offset ) {
879 return $this->prefixSearchString( $search, $limit, $offset );
880 }
881
882 protected function getGroupName() {
883 return 'pagetools';
884 }
885}
getPermissionManager()
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
static fixRedirects( $reason, $redirTitle, $destTitle=false)
Insert jobs into the job queue to fix redirects to the given title.
An error page which can definitely be safely rendered using the OutputPage.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition LinkBatch.php:34
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Class to simplify the use of log pages.
Definition LogPage.php:33
MediaWikiServices is the service locator for the application scope of MediaWiki.
A special page that allows users to change page titles.
execute( $par)
Default execute method Checks user permissions.
showSubpages( $title)
Show subpages of the page being moved.
doesWrites()
Indicates whether this special page may perform database writes.
showSubpagesList( $subpages, $pagecount, $wikiMsg, $noSubpageMsg=false)
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
showForm( $err, $isPermError=false)
Show the form.
showLogFragment( $title)
string $reason
Text input.
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
Handles the backend logic of moving a page from one title to another.
Definition MovePage.php:36
Show an error when a user tries to do something they do not have the necessary permissions for.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
getOutput()
Get the OutputPage being used for this instance.
getUser()
Shortcut to get the User executing this instance.
getSkin()
Shortcut to get the skin being used for this instance.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getConfig()
Shortcut to get main config object.
getRequest()
Get the WebRequest being used for this instance.
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
getPageTitle( $subpage=false)
Get a self-referential title object.
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
getLanguage()
Shortcut to get user's language.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
prefixSearchString( $search, $limit, $offset)
Perform a regular substring search for prefixSearchSubpages.
MediaWiki Linker LinkRenderer null $linkRenderer
Show an error when the user hits a rate limit.
The TitleArray class only exists to provide the newFromResult method at pre- sent.
static newFromResult( $res)
Represents a title within MediaWiki.
Definition Title.php:42
getNamespace()
Get the namespace index, i.e.
Definition Title.php:1037
getText()
Get the text form (spaces not underscores) of the main part.
Definition Title.php:995
getPrefixedText()
Get the prefixed title with spaces.
Definition Title.php:1818
Shortcut to construct a special page which is unlisted by default.
static doWatchOrUnwatch( $watch, Title $title, User $user)
Watch or unwatch a page.
const NS_USER
Definition Defines.php:71
const NS_FILE
Definition Defines.php:75
const NS_CATEGORY
Definition Defines.php:83
This class serves as a utility class for this extension.
const DB_REPLICA
Definition defines.php:25
const DB_MASTER
Definition defines.php:26
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42