MediaWiki  master
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()
213  ->getPermissionManager()
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 
240  $dbr = wfGetDB( DB_REPLICA );
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  '@phan-var array[] $err';
253  if ( $isPermError ) {
254  $action_desc = $this->msg( 'action-move' )->plain();
255  $errMsgHtml = $this->msg( 'permissionserrorstext-withaction',
256  count( $err ), $action_desc )->parseAsBlock();
257  } else {
258  $errMsgHtml = $this->msg( 'cannotmove', count( $err ) )->parseAsBlock();
259  }
260 
261  if ( count( $err ) == 1 ) {
262  $errMsg = $err[0];
263  $errMsgName = array_shift( $errMsg );
264 
265  if ( $errMsgName == 'hookaborted' ) {
266  $errMsgHtml .= "<p>{$errMsg[0]}</p>\n";
267  } else {
268  $errMsgHtml .= $this->msg( $errMsgName, $errMsg )->parseAsBlock();
269  }
270  } else {
271  $errStr = [];
272 
273  foreach ( $err as $errMsg ) {
274  if ( $errMsg[0] == 'hookaborted' ) {
275  $errStr[] = $errMsg[1];
276  } else {
277  $errMsgName = array_shift( $errMsg );
278  $errStr[] = $this->msg( $errMsgName, $errMsg )->parse();
279  }
280  }
281 
282  $errMsgHtml .= '<ul><li>' . implode( "</li>\n<li>", $errStr ) . "</li></ul>\n";
283  }
284  $out->addHTML( Html::errorBox( $errMsgHtml ) );
285  }
286 
287  if ( $this->oldTitle->isProtected( 'move' ) ) {
288  # Is the title semi-protected?
289  if ( $this->oldTitle->isSemiProtected( 'move' ) ) {
290  $noticeMsg = 'semiprotectedpagemovewarning';
291  } else {
292  # Then it must be protected based on static groups (regular)
293  $noticeMsg = 'protectedpagemovewarning';
294  }
295  $out->addHTML( "<div class='warningbox mw-warning-with-logexcerpt'>\n" );
296  $out->addWikiMsg( $noticeMsg );
298  $out,
299  'protect',
300  $this->oldTitle,
301  '',
302  [ 'lim' => 1 ]
303  );
304  $out->addHTML( "</div>\n" );
305  }
306 
307  // Length limit for wpReason and wpNewTitleMain is enforced in the
308  // mediawiki.special.movePage module
309 
310  $immovableNamespaces = [];
311  $namespaceInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
312  foreach ( array_keys( $this->getLanguage()->getNamespaces() ) as $nsId ) {
313  if ( !$namespaceInfo->isMovable( $nsId ) ) {
314  $immovableNamespaces[] = $nsId;
315  }
316  }
317 
318  $out->enableOOUI();
319  $fields = [];
320 
321  $fields[] = new OOUI\FieldLayout(
322  new MediaWiki\Widget\ComplexTitleInputWidget( [
323  'id' => 'wpNewTitle',
324  'namespace' => [
325  'id' => 'wpNewTitleNs',
326  'name' => 'wpNewTitleNs',
327  'value' => $newTitle->getNamespace(),
328  'exclude' => $immovableNamespaces,
329  ],
330  'title' => [
331  'id' => 'wpNewTitleMain',
332  'name' => 'wpNewTitleMain',
333  'value' => $newTitle->getText(),
334  // Inappropriate, since we're expecting the user to input a non-existent page's title
335  'suggestions' => false,
336  ],
337  'infusable' => true,
338  ] ),
339  [
340  'label' => $this->msg( 'newtitle' )->text(),
341  'align' => 'top',
342  ]
343  );
344 
345  // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
346  // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
347  // Unicode codepoints.
348  $fields[] = new OOUI\FieldLayout(
349  new OOUI\TextInputWidget( [
350  'name' => 'wpReason',
351  'id' => 'wpReason',
353  'infusable' => true,
354  'value' => $this->reason,
355  ] ),
356  [
357  'label' => $this->msg( 'movereason' )->text(),
358  'align' => 'top',
359  ]
360  );
361 
362  if ( $considerTalk ) {
363  $fields[] = new OOUI\FieldLayout(
364  new OOUI\CheckboxInputWidget( [
365  'name' => 'wpMovetalk',
366  'id' => 'wpMovetalk',
367  'value' => '1',
368  'selected' => $this->moveTalk,
369  ] ),
370  [
371  'label' => $this->msg( 'movetalk' )->text(),
372  'help' => new OOUI\HtmlSnippet( $this->msg( 'movepagetalktext' )->parseAsBlock() ),
373  'helpInline' => true,
374  'align' => 'inline',
375  'id' => 'wpMovetalk-field',
376  ]
377  );
378  }
379 
380  if ( MediaWikiServices::getInstance()
382  ->userHasRight( $user, 'suppressredirect' )
383  ) {
384  if ( $handlerSupportsRedirects ) {
385  $isChecked = $this->leaveRedirect;
386  $isDisabled = false;
387  } else {
388  $isChecked = false;
389  $isDisabled = true;
390  }
391  $fields[] = new OOUI\FieldLayout(
392  new OOUI\CheckboxInputWidget( [
393  'name' => 'wpLeaveRedirect',
394  'id' => 'wpLeaveRedirect',
395  'value' => '1',
396  'selected' => $isChecked,
397  'disabled' => $isDisabled,
398  ] ),
399  [
400  'label' => $this->msg( 'move-leave-redirect' )->text(),
401  'align' => 'inline',
402  ]
403  );
404  }
405 
406  if ( $hasRedirects ) {
407  $fields[] = new OOUI\FieldLayout(
408  new OOUI\CheckboxInputWidget( [
409  'name' => 'wpFixRedirects',
410  'id' => 'wpFixRedirects',
411  'value' => '1',
412  'selected' => $this->fixRedirects,
413  ] ),
414  [
415  'label' => $this->msg( 'fix-double-redirects' )->text(),
416  'align' => 'inline',
417  ]
418  );
419  }
420 
421  if ( $canMoveSubpage ) {
422  $maximumMovedPages = $this->getConfig()->get( 'MaximumMovedPages' );
423  $fields[] = new OOUI\FieldLayout(
424  new OOUI\CheckboxInputWidget( [
425  'name' => 'wpMovesubpages',
426  'id' => 'wpMovesubpages',
427  'value' => '1',
428  'selected' => true, // T222953 Always check the box
429  ] ),
430  [
431  'label' => new OOUI\HtmlSnippet(
432  $this->msg(
433  ( $this->oldTitle->hasSubpages()
434  ? 'move-subpages'
435  : 'move-talk-subpages' )
436  )->numParams( $maximumMovedPages )->params( $maximumMovedPages )->parse()
437  ),
438  'align' => 'inline',
439  ]
440  );
441  }
442 
443  # Don't allow watching if user is not logged in
444  if ( $user->isLoggedIn() ) {
445  $watchChecked = $user->isLoggedIn() && ( $this->watch || $user->getBoolOption( 'watchmoves' )
446  || $user->isWatched( $this->oldTitle ) );
447  $fields[] = new OOUI\FieldLayout(
448  new OOUI\CheckboxInputWidget( [
449  'name' => 'wpWatch',
450  'id' => 'watch', # ew
451  'value' => '1',
452  'selected' => $watchChecked,
453  ] ),
454  [
455  'label' => $this->msg( 'move-watch' )->text(),
456  'align' => 'inline',
457  ]
458  );
459  }
460 
461  $hiddenFields = '';
462  if ( $moveOverShared ) {
463  $hiddenFields .= Html::hidden( 'wpMoveOverSharedFile', '1' );
464  }
465 
466  if ( $deleteAndMove ) {
467  $fields[] = new OOUI\FieldLayout(
468  new OOUI\CheckboxInputWidget( [
469  'name' => 'wpDeleteAndMove',
470  'id' => 'wpDeleteAndMove',
471  'value' => '1',
472  ] ),
473  [
474  'label' => $this->msg( 'delete_and_move_confirm' )->text(),
475  'align' => 'inline',
476  ]
477  );
478  }
479 
480  $fields[] = new OOUI\FieldLayout(
481  new OOUI\ButtonInputWidget( [
482  'name' => 'wpMove',
483  'value' => $this->msg( 'movepagebtn' )->text(),
484  'label' => $this->msg( 'movepagebtn' )->text(),
485  'flags' => [ 'primary', 'progressive' ],
486  'type' => 'submit',
487  ] ),
488  [
489  'align' => 'top',
490  ]
491  );
492 
493  $fieldset = new OOUI\FieldsetLayout( [
494  'label' => $this->msg( 'move-page-legend' )->text(),
495  'id' => 'mw-movepage-table',
496  'items' => $fields,
497  ] );
498 
499  $form = new OOUI\FormLayout( [
500  'method' => 'post',
501  'action' => $this->getPageTitle()->getLocalURL( 'action=submit' ),
502  'id' => 'movepage',
503  ] );
504  $form->appendContent(
505  $fieldset,
506  new OOUI\HtmlSnippet(
507  $hiddenFields .
508  Html::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
509  Html::hidden( 'wpEditToken', $user->getEditToken() )
510  )
511  );
512 
513  $out->addHTML(
514  new OOUI\PanelLayout( [
515  'classes' => [ 'movepage-wrapper' ],
516  'expanded' => false,
517  'padded' => true,
518  'framed' => true,
519  'content' => $form,
520  ] )
521  );
522 
523  $this->showLogFragment( $this->oldTitle );
524  $this->showSubpages( $this->oldTitle );
525  }
526 
527  function doSubmit() {
528  $user = $this->getUser();
529  $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
530 
531  if ( $user->pingLimiter( 'move' ) ) {
532  throw new ThrottledError;
533  }
534 
535  $ot = $this->oldTitle;
536  $nt = $this->newTitle;
537 
538  # don't allow moving to pages with # in
539  if ( !$nt || $nt->hasFragment() ) {
540  $this->showForm( [ [ 'badtitletext' ] ] );
541 
542  return;
543  }
544 
545  $services = MediaWikiServices::getInstance();
546 
547  # Show a warning if the target file exists on a shared repo
548  $repoGroup = $services->getRepoGroup();
549  if ( $nt->getNamespace() == NS_FILE
550  && !( $this->moveOverShared && $permissionManager->userHasRight( $user, 'reupload-shared' ) )
551  && !$repoGroup->getLocalRepo()->findFile( $nt )
552  && $repoGroup->findFile( $nt )
553  ) {
554  $this->showForm( [ [ 'file-exists-sharedrepo' ] ] );
555 
556  return;
557  }
558 
559  # Delete to make way if requested
560  if ( $this->deleteAndMove ) {
561  $permErrors = $permissionManager->getPermissionErrors( 'delete', $user, $nt );
562  if ( count( $permErrors ) ) {
563  # Only show the first error
564  $this->showForm( $permErrors, true );
565 
566  return;
567  }
568 
569  $page = WikiPage::factory( $nt );
570 
571  // Small safety margin to guard against concurrent edits
572  if ( $page->isBatchedDelete( 5 ) ) {
573  $this->showForm( [ [ 'movepage-delete-first' ] ] );
574 
575  return;
576  }
577 
578  $reason = $this->msg( 'delete_and_move_reason', $ot )->inContentLanguage()->text();
579 
580  // Delete an associated image if there is
581  if ( $nt->getNamespace() == NS_FILE ) {
582  $file = $repoGroup->getLocalRepo()->newFile( $nt );
583  $file->load( File::READ_LATEST );
584  if ( $file->exists() ) {
585  $file->delete( $reason, false, $user );
586  }
587  }
588 
589  $error = ''; // passed by ref
590  $deleteStatus = $page->doDeleteArticleReal( $reason, false, 0, true, $error, $user );
591  if ( !$deleteStatus->isGood() ) {
592  $this->showForm( $deleteStatus->getErrorsArray() );
593 
594  return;
595  }
596  }
597 
598  $handler = ContentHandler::getForTitle( $ot );
599 
600  if ( !$handler->supportsRedirects() ) {
601  $createRedirect = false;
602  } elseif ( $permissionManager->userHasRight( $user, 'suppressredirect' ) ) {
603  $createRedirect = $this->leaveRedirect;
604  } else {
605  $createRedirect = true;
606  }
607 
608  # Do the actual move.
609  $mp = new MovePage( $ot, $nt );
610 
611  # check whether the requested actions are permitted / possible
612  $userPermitted = $mp->checkPermissions( $user, $this->reason )->isOK();
613  if ( $ot->isTalkPage() || $nt->isTalkPage() ) {
614  $this->moveTalk = false;
615  }
616  if ( $this->moveSubpages ) {
617  $this->moveSubpages = $permissionManager->userCan( 'move-subpages', $user, $ot );
618  }
619 
620  $status = $mp->moveIfAllowed( $user, $this->reason, $createRedirect );
621  if ( !$status->isOK() ) {
622  $this->showForm( $status->getErrorsArray(), !$userPermitted );
623  return;
624  }
625 
626  if ( $this->getConfig()->get( 'FixDoubleRedirects' ) && $this->fixRedirects ) {
627  DoubleRedirectJob::fixRedirects( 'move', $ot, $nt );
628  }
629 
630  $out = $this->getOutput();
631  $out->setPageTitle( $this->msg( 'pagemovedsub' ) );
632 
633  $linkRenderer = $this->getLinkRenderer();
634  $oldLink = $linkRenderer->makeLink(
635  $ot,
636  null,
637  [ 'id' => 'movepage-oldlink' ],
638  [ 'redirect' => 'no' ]
639  );
640  $newLink = $linkRenderer->makeKnownLink(
641  $nt,
642  null,
643  [ 'id' => 'movepage-newlink' ]
644  );
645  $oldText = $ot->getPrefixedText();
646  $newText = $nt->getPrefixedText();
647 
648  if ( $ot->exists() ) {
649  // NOTE: we assume that if the old title exists, it's because it was re-created as
650  // a redirect to the new title. This is not safe, but what we did before was
651  // even worse: we just determined whether a redirect should have been created,
652  // and reported that it was created if it should have, without any checks.
653  // Also note that isRedirect() is unreliable because of T39209.
654  $msgName = 'movepage-moved-redirect';
655  } else {
656  $msgName = 'movepage-moved-noredirect';
657  }
658 
659  $out->addHTML( $this->msg( 'movepage-moved' )->rawParams( $oldLink,
660  $newLink )->params( $oldText, $newText )->parseAsBlock() );
661  $out->addWikiMsg( $msgName );
662 
663  // Avoid PHP 7.1 warning from passing $this by reference
664  $movePage = $this;
665  Hooks::run( 'SpecialMovepageAfterMove', [ &$movePage, &$ot, &$nt ] );
666 
667  /*
668  * Now we move extra pages we've been asked to move: subpages and talk
669  * pages.
670  *
671  * First, make a list of id's. This might be marginally less efficient
672  * than a more direct method, but this is not a highly performance-cri-
673  * tical code path and readable code is more important here.
674  *
675  * If the target namespace doesn't allow subpages, moving with subpages
676  * would mean that you couldn't move them back in one operation, which
677  * is bad.
678  * @todo FIXME: A specific error message should be given in this case.
679  */
680 
681  // @todo FIXME: Use Title::moveSubpages() here
682  $nsInfo = $services->getNamespaceInfo();
683  $dbr = wfGetDB( DB_MASTER );
684  if ( $this->moveSubpages && (
685  $nsInfo->hasSubpages( $nt->getNamespace() ) || (
686  $this->moveTalk
687  && $nsInfo->hasSubpages( $nt->getTalkPage()->getNamespace() )
688  )
689  ) ) {
690  $conds = [
691  'page_title' . $dbr->buildLike( $ot->getDBkey() . '/', $dbr->anyString() )
692  . ' OR page_title = ' . $dbr->addQuotes( $ot->getDBkey() )
693  ];
694  $conds['page_namespace'] = [];
695  if ( $nsInfo->hasSubpages( $nt->getNamespace() ) ) {
696  $conds['page_namespace'][] = $ot->getNamespace();
697  }
698  if ( $this->moveTalk &&
699  $nsInfo->hasSubpages( $nt->getTalkPage()->getNamespace() )
700  ) {
701  $conds['page_namespace'][] = $ot->getTalkPage()->getNamespace();
702  }
703  } elseif ( $this->moveTalk ) {
704  $conds = [
705  'page_namespace' => $ot->getTalkPage()->getNamespace(),
706  'page_title' => $ot->getDBkey()
707  ];
708  } else {
709  # Skip the query
710  $conds = null;
711  }
712 
713  $extraPages = [];
714  if ( $conds !== null ) {
715  $extraPages = TitleArray::newFromResult(
716  $dbr->select( 'page',
717  [ 'page_id', 'page_namespace', 'page_title' ],
718  $conds,
719  __METHOD__
720  )
721  );
722  }
723 
724  $extraOutput = [];
725  $count = 1;
726  foreach ( $extraPages as $oldSubpage ) {
727  if ( $ot->equals( $oldSubpage ) || $nt->equals( $oldSubpage ) ) {
728  # Already did this one.
729  continue;
730  }
731 
732  $newPageName = preg_replace(
733  '#^' . preg_quote( $ot->getDBkey(), '#' ) . '#',
734  StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # T23234
735  $oldSubpage->getDBkey()
736  );
737 
738  if ( $oldSubpage->isSubpage() && ( $ot->isTalkPage() xor $nt->isTalkPage() ) ) {
739  // Moving a subpage from a subject namespace to a talk namespace or vice-versa
740  $newNs = $nt->getNamespace();
741  } elseif ( $oldSubpage->isTalkPage() ) {
742  $newNs = $nt->getTalkPage()->getNamespace();
743  } else {
744  $newNs = $nt->getSubjectPage()->getNamespace();
745  }
746 
747  # T16385: we need makeTitleSafe because the new page names may
748  # be longer than 255 characters.
749  $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
750  if ( !$newSubpage ) {
751  $oldLink = $linkRenderer->makeKnownLink( $oldSubpage );
752  $extraOutput[] = $this->msg( 'movepage-page-unmoved' )->rawParams( $oldLink )
753  ->params( Title::makeName( $newNs, $newPageName ) )->escaped();
754  continue;
755  }
756 
757  $mp = new MovePage( $oldSubpage, $newSubpage );
758  # This was copy-pasted from Renameuser, bleh.
759  if ( $newSubpage->exists() && !$mp->isValidMove()->isOK() ) {
760  $link = $linkRenderer->makeKnownLink( $newSubpage );
761  $extraOutput[] = $this->msg( 'movepage-page-exists' )->rawParams( $link )->escaped();
762  } else {
763  $status = $mp->moveIfAllowed( $user, $this->reason, $createRedirect );
764 
765  if ( $status->isOK() ) {
766  if ( $this->fixRedirects ) {
767  DoubleRedirectJob::fixRedirects( 'move', $oldSubpage, $newSubpage );
768  }
769  $oldLink = $linkRenderer->makeLink(
770  $oldSubpage,
771  null,
772  [],
773  [ 'redirect' => 'no' ]
774  );
775 
776  $newLink = $linkRenderer->makeKnownLink( $newSubpage );
777  $extraOutput[] = $this->msg( 'movepage-page-moved' )
778  ->rawParams( $oldLink, $newLink )->escaped();
779  ++$count;
780 
781  $maximumMovedPages = $this->getConfig()->get( 'MaximumMovedPages' );
782  if ( $count >= $maximumMovedPages ) {
783  $extraOutput[] = $this->msg( 'movepage-max-pages' )
784  ->numParams( $maximumMovedPages )->escaped();
785  break;
786  }
787  } else {
788  $oldLink = $linkRenderer->makeKnownLink( $oldSubpage );
789  $newLink = $linkRenderer->makeLink( $newSubpage );
790  $extraOutput[] = $this->msg( 'movepage-page-unmoved' )
791  ->rawParams( $oldLink, $newLink )->escaped();
792  }
793  }
794  }
795 
796  if ( $extraOutput !== [] ) {
797  $out->addHTML( "<ul>\n<li>" . implode( "</li>\n<li>", $extraOutput ) . "</li>\n</ul>" );
798  }
799 
800  # Deal with watches (we don't watch subpages)
801  WatchAction::doWatchOrUnwatch( $this->watch, $ot, $user );
802  WatchAction::doWatchOrUnwatch( $this->watch, $nt, $user );
803  }
804 
805  function showLogFragment( $title ) {
806  $moveLogPage = new LogPage( 'move' );
807  $out = $this->getOutput();
808  $out->addHTML( Xml::element( 'h2', null, $moveLogPage->getName()->text() ) );
809  LogEventsList::showLogExtract( $out, 'move', $title );
810  }
811 
818  function showSubpages( $title ) {
819  $nsHasSubpages = MediaWikiServices::getInstance()->getNamespaceInfo()->
820  hasSubpages( $title->getNamespace() );
821  $subpages = $title->getSubpages();
822  $count = $subpages instanceof TitleArray ? $subpages->count() : 0;
823 
824  $titleIsTalk = $title->isTalkPage();
825  $subpagesTalk = $title->getTalkPage()->getSubpages();
826  $countTalk = $subpagesTalk instanceof TitleArray ? $subpagesTalk->count() : 0;
827  $totalCount = $count + $countTalk;
828 
829  if ( !$nsHasSubpages && $countTalk == 0 ) {
830  return;
831  }
832 
833  $this->getOutput()->wrapWikiMsg(
834  '== $1 ==',
835  [ 'movesubpage', ( $titleIsTalk ? $count : $totalCount ) ]
836  );
837 
838  if ( $nsHasSubpages ) {
839  $this->showSubpagesList( $subpages, $count, 'movesubpagetext', true );
840  }
841 
842  if ( !$titleIsTalk && $countTalk > 0 ) {
843  $this->showSubpagesList( $subpagesTalk, $countTalk, 'movesubpagetalktext' );
844  }
845  }
846 
847  function showSubpagesList( $subpages, $pagecount, $wikiMsg, $noSubpageMsg = false ) {
848  $out = $this->getOutput();
849 
850  # No subpages.
851  if ( $pagecount == 0 && $noSubpageMsg ) {
852  $out->addWikiMsg( 'movenosubpage' );
853  return;
854  }
855 
856  $out->addWikiMsg( $wikiMsg, $this->getLanguage()->formatNum( $pagecount ) );
857  $out->addHTML( "<ul>\n" );
858 
859  $linkBatch = new LinkBatch( $subpages );
860  $linkBatch->setCaller( __METHOD__ );
861  $linkBatch->execute();
862  $linkRenderer = $this->getLinkRenderer();
863 
864  foreach ( $subpages as $subpage ) {
865  $link = $linkRenderer->makeLink( $subpage );
866  $out->addHTML( "<li>$link</li>\n" );
867  }
868  $out->addHTML( "</ul>\n" );
869  }
870 
879  public function prefixSearchSubpages( $search, $limit, $offset ) {
880  return $this->prefixSearchString( $search, $limit, $offset );
881  }
882 
883  protected function getGroupName() {
884  return 'pagetools';
885  }
886 }
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:672
SpecialPage\msg
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:792
MovePageForm\$newTitle
Title $newTitle
Definition: SpecialMovepage.php:36
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:317
Title\makeName
static makeName( $ns, $title, $fragment='', $interwiki='', $canonicalNamespace=false)
Make a prefixed DB key from a DB key and a namespace index.
Definition: Title.php:811
MovePageForm\showSubpages
showSubpages( $title)
Show subpages of the page being moved.
Definition: SpecialMovepage.php:818
LinkBatch
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition: LinkBatch.php:35
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:719
TitleArray\newFromResult
static newFromResult( $res)
Definition: TitleArray.php:42
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:130
UnlistedSpecialPage
Shortcut to construct a special page which is unlisted by default.
Definition: UnlistedSpecialPage.php:29
MovePageForm\$oldTitle
Title $oldTitle
Definition: SpecialMovepage.php:33
MovePageForm\$moveOverShared
bool $moveOverShared
Definition: SpecialMovepage.php:59
MovePageForm\getGroupName
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
Definition: SpecialMovepage.php:883
Title\getPrefixedText
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1853
MovePageForm\prefixSearchSubpages
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
Definition: SpecialMovepage.php:879
StringUtils\escapeRegexReplacement
static escapeRegexReplacement( $string)
Escape a string to make it suitable for inclusion in a preg_replace() replacement parameter.
Definition: StringUtils.php:343
NS_FILE
const NS_FILE
Definition: Defines.php:66
$file
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
ContentHandler\getForTitle
static getForTitle(Title $title)
Returns the appropriate ContentHandler singleton for the given title.
Definition: ContentHandler.php:201
SpecialPage\getSkin
getSkin()
Shortcut to get the skin being used for this instance.
Definition: SpecialPage.php:739
SpecialPage\useTransactionalTimeLimit
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition: SpecialPage.php:894
PermissionsError
Show an error when a user tries to do something they do not have the necessary permissions for.
Definition: PermissionsError.php:30
SpecialPage\getLanguage
getLanguage()
Shortcut to get user's language.
Definition: SpecialPage.php:749
MovePageForm\$moveSubpages
bool $moveSubpages
Definition: SpecialMovepage.php:50
$dbr
$dbr
Definition: testCompression.php:52
MovePageForm\showForm
showForm( $err, $isPermError=false)
Show the form.
Definition: SpecialMovepage.php:144
MovePageForm\$leaveRedirect
bool $leaveRedirect
Definition: SpecialMovepage.php:56
SpecialPage\addHelpLink
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition: SpecialPage.php:828
SpecialPage\prefixSearchString
prefixSearchString( $search, $limit, $offset)
Perform a regular substring search for prefixSearchSubpages.
Definition: SpecialPage.php:501
MovePageForm\doSubmit
doSubmit()
Definition: SpecialMovepage.php:527
SpecialPage\getConfig
getConfig()
Shortcut to get main config object.
Definition: SpecialPage.php:758
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:141
Title\getNamespace
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:1035
MovePageForm\$fixRedirects
bool $fixRedirects
Definition: SpecialMovepage.php:53
MovePage
Handles the backend logic of moving a page from one title to another.
Definition: MovePage.php:36
getPermissionManager
getPermissionManager()
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2562
LogPage
Class to simplify the use of log pages.
Definition: LogPage.php:33
MediaWiki
A helper class for throttling authentication attempts.
MovePageForm\$deleteAndMove
bool $deleteAndMove
Definition: SpecialMovepage.php:47
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:41
ThrottledError
Show an error when the user hits a rate limit.
Definition: ThrottledError.php:27
WatchAction\doWatchOrUnwatch
static doWatchOrUnwatch( $watch, Title $title, User $user)
Watch or unwatch a page.
Definition: WatchAction.php:91
$title
$title
Definition: testCompression.php:36
SpecialPage\setHeaders
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
Definition: SpecialPage.php:537
SpecialPage\getUser
getUser()
Shortcut to get the User executing this instance.
Definition: SpecialPage.php:729
MovePageForm
A special page that allows users to change page titles.
Definition: SpecialMovepage.php:31
LogEventsList\showLogExtract
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Definition: LogEventsList.php:624
MovePageForm\__construct
__construct()
Definition: SpecialMovepage.php:63
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:74
DB_MASTER
const DB_MASTER
Definition: defines.php:26
MovePageForm\$reason
string $reason
Text input.
Definition: SpecialMovepage.php:39
DoubleRedirectJob\fixRedirects
static fixRedirects( $reason, $redirTitle, $destTitle=false)
Insert jobs into the job queue to fix redirects to the given title.
Definition: DoubleRedirectJob.php:63
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:610
SpecialPage\getRequest
getRequest()
Get the WebRequest being used for this instance.
Definition: SpecialPage.php:709
MovePageForm\doesWrites
doesWrites()
Indicates whether this special page may perform database writes.
Definition: SpecialMovepage.php:67
MovePageForm\showSubpagesList
showSubpagesList( $subpages, $pagecount, $wikiMsg, $noSubpageMsg=false)
Definition: SpecialMovepage.php:847
CommentStore\COMMENT_CHARACTER_LIMIT
const COMMENT_CHARACTER_LIMIT
Maximum length of a comment in UTF-8 characters.
Definition: CommentStore.php:37
SpecialPage\getLinkRenderer
getLinkRenderer()
Definition: SpecialPage.php:904
Title
Represents a title within MediaWiki.
Definition: Title.php:42
TitleArray
The TitleArray class only exists to provide the newFromResult method at pre- sent.
Definition: TitleArray.php:35
NS_USER
const NS_USER
Definition: Defines.php:62
MovePageForm\$watch
$watch
Definition: SpecialMovepage.php:61
SpecialPage\checkReadOnly
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
Definition: SpecialPage.php:328
SpecialPage\$linkRenderer
MediaWiki Linker LinkRenderer null $linkRenderer
Definition: SpecialPage.php:67
ErrorPageError
An error page which can definitely be safely rendered using the OutputPage.
Definition: ErrorPageError.php:27
MovePageForm\showLogFragment
showLogFragment( $title)
Definition: SpecialMovepage.php:805
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
Definition: DeferredUpdates.php:125
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
MovePageForm\$moveTalk
bool $moveTalk
Definition: SpecialMovepage.php:44
Title\getText
getText()
Get the text form (spaces not underscores) of the main part.
Definition: Title.php:996
SpecialPage\outputHeader
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
Definition: SpecialPage.php:639
MovePageForm\execute
execute( $par)
Default execute method Checks user permissions.
Definition: SpecialMovepage.php:71