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  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='warningbox 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',
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 
632  $linkRenderer = $this->getLinkRenderer();
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();
682  $dbr = wfGetDB( DB_MASTER );
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 
804  function showLogFragment( $title ) {
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();
861  $linkRenderer = $this->getLinkRenderer();
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 }
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:142
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
Shortcut to construct a special page which is unlisted by default.
string $reason
Text input.
getText()
Get the text form (spaces not underscores) of the main part.
Definition: Title.php:998
static escapeRegexReplacement( $string)
Escape a string to make it suitable for inclusion in a preg_replace() replacement parameter...
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
showSubpagesList( $subpages, $pagecount, $wikiMsg, $noSubpageMsg=false)
getOutput()
Get the OutputPage being used for this instance.
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1858
const COMMENT_CHARACTER_LIMIT
Maximum length of a comment in UTF-8 characters.
A helper class for throttling authentication attempts.
The TitleArray class only exists to provide the newFromResult method at pre- sent.
Definition: TitleArray.php:35
const DB_MASTER
Definition: defines.php:26
Class to simplify the use of log pages.
Definition: LogPage.php:33
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition: LinkBatch.php:34
getPermissionManager()
static errorBox( $html, $heading='', $className='')
Return an error box.
Definition: Html.php:739
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
prefixSearchString( $search, $limit, $offset)
Perform a regular substring search for prefixSearchSubpages.
An error page which can definitely be safely rendered using the OutputPage.
static doWatchOrUnwatch( $watch, Title $title, User $user)
Watch or unwatch a page.
Definition: WatchAction.php:91
showForm( $err, $isPermError=false)
Show the form.
static fixRedirects( $reason, $redirTitle, $destTitle=false)
Insert jobs into the job queue to fix redirects to the given title.
const NS_CATEGORY
Definition: Defines.php:74
getSkin()
Shortcut to get the skin being used for this instance.
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes! ...
showLogFragment( $title)
static newFromResult( $res)
Definition: TitleArray.php:42
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:1040
const NS_FILE
Definition: Defines.php:66
static getForTitle(Title $title)
Returns the appropriate ContentHandler singleton for the given title.
showSubpages( $title)
Show subpages of the page being moved.
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:612
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
getUser()
Shortcut to get the User executing this instance.
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:802
A special page that allows users to change page titles.
getConfig()
Shortcut to get main config object.
Show an error when a user tries to do something they do not have the necessary permissions for...
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:41
getLanguage()
Shortcut to get user&#39;s language.
const DB_REPLICA
Definition: defines.php:25
getRequest()
Get the WebRequest being used for this instance.
static makeName( $ns, $title, $fragment='', $interwiki='', $canonicalNamespace=false)
Make a prefixed DB key from a DB key and a namespace index.
Definition: Title.php:813
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
getPageTitle( $subpage=false)
Get a self-referential title object.
Show an error when the user hits a rate limit.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
MediaWiki Linker LinkRenderer null $linkRenderer
Definition: SpecialPage.php:67
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:319