SpecialMovepage: Don't change button text when moving over existing page
[lhc/web/wiklou.git] / includes / specials / SpecialMovepage.php
1 <?php
2 /**
3 * Implements Special:Movepage
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup SpecialPage
22 */
23
24 /**
25 * A special page that allows users to change page titles
26 *
27 * @ingroup SpecialPage
28 */
29 class MovePageForm extends UnlistedSpecialPage {
30 /** @var Title */
31 protected $oldTitle = null;
32
33 /** @var Title */
34 protected $newTitle;
35
36
37 /** @var string Text input */
38 protected $reason;
39
40 // Checks
41
42 /** @var bool */
43 protected $moveTalk;
44
45 /** @var bool */
46 protected $deleteAndMove;
47
48 /** @var bool */
49 protected $moveSubpages;
50
51 /** @var bool */
52 protected $fixRedirects;
53
54 /** @var bool */
55 protected $leaveRedirect;
56
57 /** @var bool */
58 protected $moveOverShared;
59
60 private $watch = false;
61
62 public function __construct() {
63 parent::__construct( 'Movepage' );
64 }
65
66 public function execute( $par ) {
67 $this->useTransactionalTimeLimit();
68
69 $this->checkReadOnly();
70
71 $this->setHeaders();
72 $this->outputHeader();
73
74 $request = $this->getRequest();
75 $target = !is_null( $par ) ? $par : $request->getVal( 'target' );
76
77 // Yes, the use of getVal() and getText() is wanted, see bug 20365
78
79 $oldTitleText = $request->getVal( 'wpOldTitle', $target );
80 $this->oldTitle = Title::newFromText( $oldTitleText );
81
82 if ( !$this->oldTitle ) {
83 // Either oldTitle wasn't passed, or newFromText returned null
84 throw new ErrorPageError( 'notargettitle', 'notargettext' );
85 }
86 if ( !$this->oldTitle->exists() ) {
87 throw new ErrorPageError( 'nopagetitle', 'nopagetext' );
88 }
89
90 $newTitleTextMain = $request->getText( 'wpNewTitleMain' );
91 $newTitleTextNs = $request->getInt( 'wpNewTitleNs', $this->oldTitle->getNamespace() );
92 // Backwards compatibility for forms submitting here from other sources
93 // which is more common than it should be..
94 $newTitleText_bc = $request->getText( 'wpNewTitle' );
95 $this->newTitle = strlen( $newTitleText_bc ) > 0
96 ? Title::newFromText( $newTitleText_bc )
97 : Title::makeTitleSafe( $newTitleTextNs, $newTitleTextMain );
98
99 $user = $this->getUser();
100
101 # Check rights
102 $permErrors = $this->oldTitle->getUserPermissionsErrors( 'move', $user );
103 if ( count( $permErrors ) ) {
104 // Auto-block user's IP if the account was "hard" blocked
105 DeferredUpdates::addCallableUpdate( function() use ( $user ) {
106 $user->spreadAnyEditBlock();
107 } );
108 throw new PermissionsError( 'move', $permErrors );
109 }
110
111 $def = !$request->wasPosted();
112
113 $this->reason = $request->getText( 'wpReason' );
114 $this->moveTalk = $request->getBool( 'wpMovetalk', $def );
115 $this->fixRedirects = $request->getBool( 'wpFixRedirects', $def );
116 $this->leaveRedirect = $request->getBool( 'wpLeaveRedirect', $def );
117 $this->moveSubpages = $request->getBool( 'wpMovesubpages', false );
118 $this->deleteAndMove = $request->getBool( 'wpDeleteAndMove' ) && $request->getBool( 'wpConfirm' );
119 $this->moveOverShared = $request->getBool( 'wpMoveOverSharedFile', false );
120 $this->watch = $request->getCheck( 'wpWatch' ) && $user->isLoggedIn();
121
122 if ( 'submit' == $request->getVal( 'action' ) && $request->wasPosted()
123 && $user->matchEditToken( $request->getVal( 'wpEditToken' ) )
124 ) {
125 $this->doSubmit();
126 } else {
127 $this->showForm( array() );
128 }
129 }
130
131 /**
132 * Show the form
133 *
134 * @param array $err Error messages. Each item is an error message.
135 * It may either be a string message name or array message name and
136 * parameters, like the second argument to OutputPage::wrapWikiMsg().
137 */
138 function showForm( $err ) {
139 global $wgContLang;
140
141 $this->getSkin()->setRelevantTitle( $this->oldTitle );
142
143 $out = $this->getOutput();
144 $out->setPageTitle( $this->msg( 'move-page', $this->oldTitle->getPrefixedText() ) );
145 $out->addModules( 'mediawiki.special.movePage' );
146 $out->addModuleStyles( 'mediawiki.special.movePage.styles' );
147 $this->addHelpLink( 'Help:Moving a page' );
148
149 if ( $this->oldTitle->getNamespace() == NS_USER && !$this->oldTitle->isSubpage() ) {
150 $out->wrapWikiMsg(
151 "<div class=\"error mw-moveuserpage-warning\">\n$1\n</div>",
152 'moveuserpage-warning'
153 );
154 } elseif ( $this->oldTitle->getNamespace() == NS_CATEGORY ) {
155 $out->wrapWikiMsg(
156 "<div class=\"error mw-movecategorypage-warning\">\n$1\n</div>",
157 'movecategorypage-warning'
158 );
159 }
160
161 $out->addWikiMsg( $this->getConfig()->get( 'FixDoubleRedirects' ) ?
162 'movepagetext' :
163 'movepagetext-noredirectfixer'
164 );
165 $submitVar = 'wpMove';
166 $confirm = false;
167
168 $newTitle = $this->newTitle;
169
170 if ( !$newTitle ) {
171 # Show the current title as a default
172 # when the form is first opened.
173 $newTitle = $this->oldTitle;
174 } elseif ( !count( $err ) ) {
175 # If a title was supplied, probably from the move log revert
176 # link, check for validity. We can then show some diagnostic
177 # information and save a click.
178 $newerr = $this->oldTitle->isValidMoveOperation( $newTitle );
179 if ( is_array( $newerr ) ) {
180 $err = $newerr;
181 }
182 }
183
184 $user = $this->getUser();
185
186 if ( count( $err ) == 1 && isset( $err[0][0] ) && $err[0][0] == 'articleexists'
187 && $newTitle->quickUserCan( 'delete', $user )
188 ) {
189 $out->addWikiMsg( 'delete_and_move_text', $newTitle->getPrefixedText() );
190 $submitVar = 'wpDeleteAndMove';
191 $confirm = true;
192 $err = array();
193 }
194
195 if ( count( $err ) == 1 && isset( $err[0][0] ) && $err[0][0] == 'file-exists-sharedrepo'
196 && $user->isAllowed( 'reupload-shared' )
197 ) {
198 $out->addWikiMsg( 'move-over-sharedrepo', $newTitle->getPrefixedText() );
199 $submitVar = 'wpMoveOverSharedFile';
200 $err = array();
201 }
202
203 $oldTalk = $this->oldTitle->getTalkPage();
204 $oldTitleSubpages = $this->oldTitle->hasSubpages();
205 $oldTitleTalkSubpages = $this->oldTitle->getTalkPage()->hasSubpages();
206
207 $canMoveSubpage = ( $oldTitleSubpages || $oldTitleTalkSubpages ) &&
208 !count( $this->oldTitle->getUserPermissionsErrors( 'move-subpages', $user ) );
209
210 # We also want to be able to move assoc. subpage talk-pages even if base page
211 # has no associated talk page, so || with $oldTitleTalkSubpages.
212 $considerTalk = !$this->oldTitle->isTalkPage() &&
213 ( $oldTalk->exists()
214 || ( $oldTitleTalkSubpages && $canMoveSubpage ) );
215
216 $dbr = wfGetDB( DB_SLAVE );
217 if ( $this->getConfig()->get( 'FixDoubleRedirects' ) ) {
218 $hasRedirects = $dbr->selectField( 'redirect', '1',
219 array(
220 'rd_namespace' => $this->oldTitle->getNamespace(),
221 'rd_title' => $this->oldTitle->getDBkey(),
222 ), __METHOD__ );
223 } else {
224 $hasRedirects = false;
225 }
226
227 if ( count( $err ) ) {
228 $out->addHTML( "<div class='error'>\n" );
229 $action_desc = $this->msg( 'action-move' )->plain();
230 $out->addWikiMsg( 'permissionserrorstext-withaction', count( $err ), $action_desc );
231
232 if ( count( $err ) == 1 ) {
233 $errMsg = $err[0];
234 $errMsgName = array_shift( $errMsg );
235
236 if ( $errMsgName == 'hookaborted' ) {
237 $out->addHTML( "<p>{$errMsg[0]}</p>\n" );
238 } else {
239 $out->addWikiMsgArray( $errMsgName, $errMsg );
240 }
241 } else {
242 $errStr = array();
243
244 foreach ( $err as $errMsg ) {
245 if ( $errMsg[0] == 'hookaborted' ) {
246 $errStr[] = $errMsg[1];
247 } else {
248 $errMsgName = array_shift( $errMsg );
249 $errStr[] = $this->msg( $errMsgName, $errMsg )->parse();
250 }
251 }
252
253 $out->addHTML( '<ul><li>' . implode( "</li>\n<li>", $errStr ) . "</li></ul>\n" );
254 }
255 $out->addHTML( "</div>\n" );
256 }
257
258 if ( $this->oldTitle->isProtected( 'move' ) ) {
259 # Is the title semi-protected?
260 if ( $this->oldTitle->isSemiProtected( 'move' ) ) {
261 $noticeMsg = 'semiprotectedpagemovewarning';
262 $classes[] = 'mw-textarea-sprotected';
263 } else {
264 # Then it must be protected based on static groups (regular)
265 $noticeMsg = 'protectedpagemovewarning';
266 $classes[] = 'mw-textarea-protected';
267 }
268 $out->addHTML( "<div class='mw-warning-with-logexcerpt'>\n" );
269 $out->addWikiMsg( $noticeMsg );
270 LogEventsList::showLogExtract(
271 $out,
272 'protect',
273 $this->oldTitle,
274 '',
275 array( 'lim' => 1 )
276 );
277 $out->addHTML( "</div>\n" );
278 }
279
280 // Byte limit (not string length limit) for wpReason and wpNewTitleMain
281 // is enforced in the mediawiki.special.movePage module
282
283 $immovableNamespaces = array();
284 foreach ( array_keys( $this->getLanguage()->getNamespaces() ) as $nsId ) {
285 if ( !MWNamespace::isMovable( $nsId ) ) {
286 $immovableNamespaces[] = $nsId;
287 }
288 }
289
290 $handler = ContentHandler::getForTitle( $this->oldTitle );
291
292 $out->enableOOUI();
293 $fields = array();
294
295 $fields[] = new OOUI\FieldLayout(
296 new MediaWiki\Widget\ComplexTitleInputWidget( array(
297 'id' => 'wpNewTitle',
298 'namespace' => array(
299 'id' => 'wpNewTitleNs',
300 'name' => 'wpNewTitleNs',
301 'value' => $newTitle->getNamespace(),
302 'exclude' => $immovableNamespaces,
303 ),
304 'title' => array(
305 'id' => 'wpNewTitleMain',
306 'name' => 'wpNewTitleMain',
307 'value' => $wgContLang->recodeForEdit( $newTitle->getText() ),
308 // Inappropriate, since we're expecting the user to input a non-existent page's title
309 'suggestions' => false,
310 ),
311 'infusable' => true,
312 ) ),
313 array(
314 'label' => $this->msg( 'newtitle' )->text(),
315 'align' => 'top',
316 )
317 );
318
319 $fields[] = new OOUI\FieldLayout(
320 new OOUI\TextInputWidget( array(
321 'name' => 'wpReason',
322 'id' => 'wpReason',
323 'maxLength' => 200,
324 'infusable' => true,
325 'value' => $this->reason,
326 ) ),
327 array(
328 'label' => $this->msg( 'movereason' )->text(),
329 'align' => 'top',
330 )
331 );
332
333 if ( $considerTalk ) {
334 $fields[] = new OOUI\FieldLayout(
335 new OOUI\CheckboxInputWidget( array(
336 'name' => 'wpMovetalk',
337 'id' => 'wpMovetalk',
338 'value' => '1',
339 'selected' => $this->moveTalk,
340 ) ),
341 array(
342 'label' => $this->msg( 'movetalk' )->text(),
343 'help' => new OOUI\HtmlSnippet( $this->msg( 'movepagetalktext' )->parseAsBlock() ),
344 'align' => 'inline',
345 'infusable' => true,
346 )
347 );
348 }
349
350 if ( $user->isAllowed( 'suppressredirect' ) ) {
351 if ( $handler->supportsRedirects() ) {
352 $isChecked = $this->leaveRedirect;
353 $isDisabled = false;
354 } else {
355 $isChecked = false;
356 $isDisabled = true;
357 }
358 $fields[] = new OOUI\FieldLayout(
359 new OOUI\CheckboxInputWidget( array(
360 'name' => 'wpLeaveRedirect',
361 'id' => 'wpLeaveRedirect',
362 'value' => '1',
363 'selected' => $isChecked,
364 'disabled' => $isDisabled,
365 ) ),
366 array(
367 'label' => $this->msg( 'move-leave-redirect' )->text(),
368 'align' => 'inline',
369 )
370 );
371 }
372
373 if ( $hasRedirects ) {
374 $fields[] = new OOUI\FieldLayout(
375 new OOUI\CheckboxInputWidget( array(
376 'name' => 'wpFixRedirects',
377 'id' => 'wpFixRedirects',
378 'value' => '1',
379 'selected' => $this->fixRedirects,
380 ) ),
381 array(
382 'label' => $this->msg( 'fix-double-redirects' )->text(),
383 'align' => 'inline',
384 )
385 );
386 }
387
388 if ( $canMoveSubpage ) {
389 $maximumMovedPages = $this->getConfig()->get( 'MaximumMovedPages' );
390 $fields[] = new OOUI\FieldLayout(
391 new OOUI\CheckboxInputWidget( array(
392 'name' => 'wpMovesubpages',
393 'id' => 'wpMovesubpages',
394 'value' => '1',
395 # Don't check the box if we only have talk subpages to
396 # move and we aren't moving the talk page.
397 'selected' => $this->moveSubpages && ( $this->oldTitle->hasSubpages() || $this->moveTalk ),
398 ) ),
399 array(
400 'label' => new OOUI\HtmlSnippet(
401 $this->msg(
402 ( $this->oldTitle->hasSubpages()
403 ? 'move-subpages'
404 : 'move-talk-subpages' )
405 )->numParams( $maximumMovedPages )->params( $maximumMovedPages )->parse()
406 ),
407 'align' => 'inline',
408 )
409 );
410 }
411
412 # Don't allow watching if user is not logged in
413 if ( $user->isLoggedIn() ) {
414 $watchChecked = $user->isLoggedIn() && ( $this->watch || $user->getBoolOption( 'watchmoves' )
415 || $user->isWatched( $this->oldTitle ) );
416 $fields[] = new OOUI\FieldLayout(
417 new OOUI\CheckboxInputWidget( array(
418 'name' => 'wpWatch',
419 'id' => 'watch', # ew
420 'value' => '1',
421 'selected' => $watchChecked,
422 ) ),
423 array(
424 'label' => $this->msg( 'move-watch' )->text(),
425 'align' => 'inline',
426 )
427 );
428 }
429
430 if ( $confirm ) {
431 $fields[] = new OOUI\FieldLayout(
432 new OOUI\CheckboxInputWidget( array(
433 'name' => 'wpConfirm',
434 'id' => 'wpConfirm',
435 'value' => '1',
436 ) ),
437 array(
438 'label' => $this->msg( 'delete_and_move_confirm' )->text(),
439 'align' => 'inline',
440 )
441 );
442 }
443
444 $fields[] = new OOUI\FieldLayout(
445 new OOUI\ButtonInputWidget( array(
446 'name' => $submitVar,
447 'value' => $this->msg( 'movepagebtn' )->text(),
448 'label' => $this->msg( 'movepagebtn' )->text(),
449 'flags' => array( 'constructive', 'primary' ),
450 'type' => 'submit',
451 ) ),
452 array(
453 'align' => 'top',
454 )
455 );
456
457 $fieldset = new OOUI\FieldsetLayout( array(
458 'label' => $this->msg( 'move-page-legend' )->text(),
459 'id' => 'mw-movepage-table',
460 'items' => $fields,
461 ) );
462
463 $form = new OOUI\FormLayout( array(
464 'method' => 'post',
465 'action' => $this->getPageTitle()->getLocalURL( 'action=submit' ),
466 'id' => 'movepage',
467 ) );
468 $form->appendContent(
469 $fieldset,
470 new OOUI\HtmlSnippet(
471 Html::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
472 Html::hidden( 'wpEditToken', $user->getEditToken() )
473 )
474 );
475
476 $out->addHTML(
477 new OOUI\PanelLayout( array(
478 'classes' => array( 'movepage-wrapper' ),
479 'expanded' => false,
480 'padded' => true,
481 'framed' => true,
482 'content' => $form,
483 ) )
484 );
485
486 $this->showLogFragment( $this->oldTitle );
487 $this->showSubpages( $this->oldTitle );
488 }
489
490 function doSubmit() {
491 $user = $this->getUser();
492
493 if ( $user->pingLimiter( 'move' ) ) {
494 throw new ThrottledError;
495 }
496
497 $ot = $this->oldTitle;
498 $nt = $this->newTitle;
499
500 # don't allow moving to pages with # in
501 if ( !$nt || $nt->hasFragment() ) {
502 $this->showForm( array( array( 'badtitletext' ) ) );
503
504 return;
505 }
506
507 # Show a warning if the target file exists on a shared repo
508 if ( $nt->getNamespace() == NS_FILE
509 && !( $this->moveOverShared && $user->isAllowed( 'reupload-shared' ) )
510 && !RepoGroup::singleton()->getLocalRepo()->findFile( $nt )
511 && wfFindFile( $nt )
512 ) {
513 $this->showForm( array( array( 'file-exists-sharedrepo' ) ) );
514
515 return;
516 }
517
518 # Delete to make way if requested
519 if ( $this->deleteAndMove ) {
520 $permErrors = $nt->getUserPermissionsErrors( 'delete', $user );
521 if ( count( $permErrors ) ) {
522 # Only show the first error
523 $this->showForm( $permErrors );
524
525 return;
526 }
527
528 $reason = $this->msg( 'delete_and_move_reason', $ot )->inContentLanguage()->text();
529
530 // Delete an associated image if there is
531 if ( $nt->getNamespace() == NS_FILE ) {
532 $file = wfLocalFile( $nt );
533 $file->load( File::READ_LATEST );
534 if ( $file->exists() ) {
535 $file->delete( $reason, false, $user );
536 }
537 }
538
539 $error = ''; // passed by ref
540 $page = WikiPage::factory( $nt );
541 $deleteStatus = $page->doDeleteArticleReal( $reason, false, 0, true, $error, $user );
542 if ( !$deleteStatus->isGood() ) {
543 $this->showForm( $deleteStatus->getErrorsArray() );
544
545 return;
546 }
547 }
548
549 $handler = ContentHandler::getForTitle( $ot );
550
551 if ( !$handler->supportsRedirects() ) {
552 $createRedirect = false;
553 } elseif ( $user->isAllowed( 'suppressredirect' ) ) {
554 $createRedirect = $this->leaveRedirect;
555 } else {
556 $createRedirect = true;
557 }
558
559 # Do the actual move.
560 $mp = new MovePage( $ot, $nt );
561 $valid = $mp->isValidMove();
562 if ( !$valid->isOK() ) {
563 $this->showForm( $valid->getErrorsArray() );
564 return;
565 }
566
567 $permStatus = $mp->checkPermissions( $user, $this->reason );
568 if ( !$permStatus->isOK() ) {
569 $this->showForm( $permStatus->getErrorsArray() );
570 return;
571 }
572
573 $status = $mp->move( $user, $this->reason, $createRedirect );
574 if ( !$status->isOK() ) {
575 $this->showForm( $status->getErrorsArray() );
576 return;
577 }
578
579 if ( $this->getConfig()->get( 'FixDoubleRedirects' ) && $this->fixRedirects ) {
580 DoubleRedirectJob::fixRedirects( 'move', $ot, $nt );
581 }
582
583 $out = $this->getOutput();
584 $out->setPageTitle( $this->msg( 'pagemovedsub' ) );
585
586 $oldLink = Linker::link(
587 $ot,
588 null,
589 array( 'id' => 'movepage-oldlink' ),
590 array( 'redirect' => 'no' )
591 );
592 $newLink = Linker::linkKnown(
593 $nt,
594 null,
595 array( 'id' => 'movepage-newlink' )
596 );
597 $oldText = $ot->getPrefixedText();
598 $newText = $nt->getPrefixedText();
599
600 if ( $ot->exists() ) {
601 // NOTE: we assume that if the old title exists, it's because it was re-created as
602 // a redirect to the new title. This is not safe, but what we did before was
603 // even worse: we just determined whether a redirect should have been created,
604 // and reported that it was created if it should have, without any checks.
605 // Also note that isRedirect() is unreliable because of bug 37209.
606 $msgName = 'movepage-moved-redirect';
607 } else {
608 $msgName = 'movepage-moved-noredirect';
609 }
610
611 $out->addHTML( $this->msg( 'movepage-moved' )->rawParams( $oldLink,
612 $newLink )->params( $oldText, $newText )->parseAsBlock() );
613 $out->addWikiMsg( $msgName );
614
615 Hooks::run( 'SpecialMovepageAfterMove', array( &$this, &$ot, &$nt ) );
616
617 # Now we move extra pages we've been asked to move: subpages and talk
618 # pages. First, if the old page or the new page is a talk page, we
619 # can't move any talk pages: cancel that.
620 if ( $ot->isTalkPage() || $nt->isTalkPage() ) {
621 $this->moveTalk = false;
622 }
623
624 if ( count( $ot->getUserPermissionsErrors( 'move-subpages', $user ) ) ) {
625 $this->moveSubpages = false;
626 }
627
628 /**
629 * Next make a list of id's. This might be marginally less efficient
630 * than a more direct method, but this is not a highly performance-cri-
631 * tical code path and readable code is more important here.
632 *
633 * If the target namespace doesn't allow subpages, moving with subpages
634 * would mean that you couldn't move them back in one operation, which
635 * is bad.
636 * @todo FIXME: A specific error message should be given in this case.
637 */
638
639 // @todo FIXME: Use Title::moveSubpages() here
640 $dbr = wfGetDB( DB_MASTER );
641 if ( $this->moveSubpages && (
642 MWNamespace::hasSubpages( $nt->getNamespace() ) || (
643 $this->moveTalk
644 && MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() )
645 )
646 ) ) {
647 $conds = array(
648 'page_title' . $dbr->buildLike( $ot->getDBkey() . '/', $dbr->anyString() )
649 . ' OR page_title = ' . $dbr->addQuotes( $ot->getDBkey() )
650 );
651 $conds['page_namespace'] = array();
652 if ( MWNamespace::hasSubpages( $nt->getNamespace() ) ) {
653 $conds['page_namespace'][] = $ot->getNamespace();
654 }
655 if ( $this->moveTalk &&
656 MWNamespace::hasSubpages( $nt->getTalkPage()->getNamespace() )
657 ) {
658 $conds['page_namespace'][] = $ot->getTalkPage()->getNamespace();
659 }
660 } elseif ( $this->moveTalk ) {
661 $conds = array(
662 'page_namespace' => $ot->getTalkPage()->getNamespace(),
663 'page_title' => $ot->getDBkey()
664 );
665 } else {
666 # Skip the query
667 $conds = null;
668 }
669
670 $extraPages = array();
671 if ( !is_null( $conds ) ) {
672 $extraPages = TitleArray::newFromResult(
673 $dbr->select( 'page',
674 array( 'page_id', 'page_namespace', 'page_title' ),
675 $conds,
676 __METHOD__
677 )
678 );
679 }
680
681 $extraOutput = array();
682 $count = 1;
683 foreach ( $extraPages as $oldSubpage ) {
684 if ( $ot->equals( $oldSubpage ) || $nt->equals( $oldSubpage ) ) {
685 # Already did this one.
686 continue;
687 }
688
689 $newPageName = preg_replace(
690 '#^' . preg_quote( $ot->getDBkey(), '#' ) . '#',
691 StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234
692 $oldSubpage->getDBkey()
693 );
694
695 if ( $oldSubpage->isSubpage() && ( $ot->isTalkPage() xor $nt->isTalkPage() ) ) {
696 // Moving a subpage from a subject namespace to a talk namespace or vice-versa
697 $newNs = $nt->getNamespace();
698 } elseif ( $oldSubpage->isTalkPage() ) {
699 $newNs = $nt->getTalkPage()->getNamespace();
700 } else {
701 $newNs = $nt->getSubjectPage()->getNamespace();
702 }
703
704 # Bug 14385: we need makeTitleSafe because the new page names may
705 # be longer than 255 characters.
706 $newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
707 if ( !$newSubpage ) {
708 $oldLink = Linker::linkKnown( $oldSubpage );
709 $extraOutput[] = $this->msg( 'movepage-page-unmoved' )->rawParams( $oldLink )
710 ->params( Title::makeName( $newNs, $newPageName ) )->escaped();
711 continue;
712 }
713
714 # This was copy-pasted from Renameuser, bleh.
715 if ( $newSubpage->exists() && !$oldSubpage->isValidMoveTarget( $newSubpage ) ) {
716 $link = Linker::linkKnown( $newSubpage );
717 $extraOutput[] = $this->msg( 'movepage-page-exists' )->rawParams( $link )->escaped();
718 } else {
719 $success = $oldSubpage->moveTo( $newSubpage, true, $this->reason, $createRedirect );
720
721 if ( $success === true ) {
722 if ( $this->fixRedirects ) {
723 DoubleRedirectJob::fixRedirects( 'move', $oldSubpage, $newSubpage );
724 }
725 $oldLink = Linker::link(
726 $oldSubpage,
727 null,
728 array(),
729 array( 'redirect' => 'no' )
730 );
731
732 $newLink = Linker::linkKnown( $newSubpage );
733 $extraOutput[] = $this->msg( 'movepage-page-moved' )
734 ->rawParams( $oldLink, $newLink )->escaped();
735 ++$count;
736
737 $maximumMovedPages = $this->getConfig()->get( 'MaximumMovedPages' );
738 if ( $count >= $maximumMovedPages ) {
739 $extraOutput[] = $this->msg( 'movepage-max-pages' )
740 ->numParams( $maximumMovedPages )->escaped();
741 break;
742 }
743 } else {
744 $oldLink = Linker::linkKnown( $oldSubpage );
745 $newLink = Linker::link( $newSubpage );
746 $extraOutput[] = $this->msg( 'movepage-page-unmoved' )
747 ->rawParams( $oldLink, $newLink )->escaped();
748 }
749 }
750 }
751
752 if ( $extraOutput !== array() ) {
753 $out->addHTML( "<ul>\n<li>" . implode( "</li>\n<li>", $extraOutput ) . "</li>\n</ul>" );
754 }
755
756 # Deal with watches (we don't watch subpages)
757 WatchAction::doWatchOrUnwatch( $this->watch, $ot, $user );
758 WatchAction::doWatchOrUnwatch( $this->watch, $nt, $user );
759 }
760
761 function showLogFragment( $title ) {
762 $moveLogPage = new LogPage( 'move' );
763 $out = $this->getOutput();
764 $out->addHTML( Xml::element( 'h2', null, $moveLogPage->getName()->text() ) );
765 LogEventsList::showLogExtract( $out, 'move', $title );
766 }
767
768 function showSubpages( $title ) {
769 if ( !MWNamespace::hasSubpages( $title->getNamespace() ) ) {
770 return;
771 }
772
773 $subpages = $title->getSubpages();
774 $count = $subpages instanceof TitleArray ? $subpages->count() : 0;
775
776 $out = $this->getOutput();
777 $out->wrapWikiMsg( '== $1 ==', array( 'movesubpage', $count ) );
778
779 # No subpages.
780 if ( $count == 0 ) {
781 $out->addWikiMsg( 'movenosubpage' );
782
783 return;
784 }
785
786 $out->addWikiMsg( 'movesubpagetext', $this->getLanguage()->formatNum( $count ) );
787 $out->addHTML( "<ul>\n" );
788
789 foreach ( $subpages as $subpage ) {
790 $link = Linker::link( $subpage );
791 $out->addHTML( "<li>$link</li>\n" );
792 }
793 $out->addHTML( "</ul>\n" );
794 }
795
796 protected function getGroupName() {
797 return 'pagetools';
798 }
799 }