51 $this->oldTitle->getUserPermissionsErrors(
'move',
$user ),
52 $this->oldTitle->getUserPermissionsErrors(
'edit',
$user ),
53 $this->newTitle->getUserPermissionsErrors(
'move-target',
$user ),
54 $this->newTitle->getUserPermissionsErrors(
'edit',
$user )
59 foreach ( $errors
as $error ) {
60 call_user_func_array( [
$status,
'fatal' ], $error );
66 $status->fatal(
'spamprotectiontext' );
69 $tp = $this->newTitle->getTitleProtection();
70 if ( $tp !==
false && !
$user->isAllowed( $tp[
'permission'] ) ) {
71 $status->fatal(
'cantmove-titleprotected' );
75 [ $this->oldTitle, $this->newTitle,
$user, $reason,
$status ]
92 if ( $this->oldTitle->equals( $this->newTitle ) ) {
95 if ( !$this->oldTitle->isMovable() ) {
96 $status->fatal(
'immobile-source-namespace', $this->oldTitle->getNsText() );
98 if ( $this->newTitle->isExternal() ) {
99 $status->fatal(
'immobile-target-namespace-iw' );
101 if ( !$this->newTitle->isMovable() ) {
102 $status->fatal(
'immobile-target-namespace', $this->newTitle->getNsText() );
105 $oldid = $this->oldTitle->getArticleID();
107 if ( strlen( $this->newTitle->getDBkey() ) < 1 ) {
108 $status->fatal(
'articleexists' );
111 ( $this->oldTitle->getDBkey() ==
'' ) ||
113 ( $this->newTitle->getDBkey() ==
'' )
115 $status->fatal(
'badarticleerror' );
118 # The move is allowed only if (1) the target doesn't exist, or
119 # (2) the target is a redirect to the source, and has no history
120 # (so we can undo bad moves right after they're done).
122 $status->fatal(
'articleexists' );
127 $this->oldTitle->getContentModel() !== $this->newTitle->getContentModel() ) {
138 'content-not-allowed-here',
140 $this->newTitle->getPrefixedText()
145 if ( $this->oldTitle->inNamespace(
NS_FILE ) ) {
149 if ( $this->newTitle->inNamespace(
NS_FILE ) && !$this->oldTitle->inNamespace(
NS_FILE ) ) {
150 $status->fatal(
'nonfile-cannot-move-to-file' );
154 Hooks::run(
'MovePageIsValidMove', [ $this->oldTitle, $this->newTitle,
$status ] );
167 $file->load( File::READ_LATEST );
168 if ( $file->exists() ) {
170 $status->fatal(
'imageinvalidfilename' );
173 $status->fatal(
'imagetypemismatch' );
177 if ( !$this->newTitle->inNamespace(
NS_FILE ) ) {
178 $status->fatal(
'imagenocrossnamespace' );
192 # Is it an existing file?
193 if ( $this->newTitle->inNamespace(
NS_FILE ) ) {
195 $file->load( File::READ_LATEST );
196 if ( $file->exists() ) {
197 wfDebug( __METHOD__ .
": file exists\n" );
201 # Is it a redirect with no history?
202 if ( !$this->newTitle->isSingleRevRedirect() ) {
203 wfDebug( __METHOD__ .
": not a one-rev redirect\n" );
206 # Get the article text
208 if ( !is_object(
$rev ) ) {
211 $content =
$rev->getContent();
212 # Does the redirect point to the source?
213 # Or is it a broken self-redirect, usually caused by namespace collisions?
214 $redirTitle = $content ? $content->getRedirectTarget() :
null;
217 if ( $redirTitle->getPrefixedDBkey() !== $this->oldTitle->getPrefixedDBkey() &&
218 $redirTitle->getPrefixedDBkey() !== $this->newTitle->getPrefixedDBkey() ) {
219 wfDebug( __METHOD__ .
": redirect points to other page\n" );
225 # Fail safe (not a redirect after all. strange.)
226 wfDebug( __METHOD__ .
": failsafe: database says " . $this->newTitle->getPrefixedDBkey() .
227 " is a redirect, but it doesn't contain a valid redirect.\n" );
243 Hooks::run(
'TitleMove', [ $this->oldTitle, $this->newTitle,
$user ] );
248 if ( $this->oldTitle->getNamespace() ==
NS_FILE ) {
250 $file->load( File::READ_LATEST );
251 if ( $file->exists() ) {
252 $status = $file->move( $this->newTitle );
262 $dbw->startAtomic( __METHOD__ );
264 Hooks::run(
'TitleMoveStarting', [ $this->oldTitle, $this->newTitle,
$user ] );
267 $protected = $this->oldTitle->isProtected();
270 $nullRevision = $this->
moveToInternal( $user, $this->newTitle, $reason, $createRedirect,
277 $prefixes = $dbw->select(
279 [
'cl_sortkey_prefix',
'cl_to' ],
280 [
'cl_from' => $pageid ],
283 if ( $this->newTitle->getNamespace() ==
NS_CATEGORY ) {
285 } elseif ( $this->newTitle->getNamespace() ==
NS_FILE ) {
290 foreach ( $prefixes
as $prefixRow ) {
291 $prefix = $prefixRow->cl_sortkey_prefix;
292 $catTo = $prefixRow->cl_to;
293 $dbw->update(
'categorylinks',
296 $this->newTitle->getCategorySortkey( $prefix ) ),
299 'cl_timestamp=cl_timestamp' ],
301 'cl_from' => $pageid,
307 $redirid = $this->oldTitle->getArticleID();
310 # Protect the redirect title as the title used to be...
314 [
'pr_page' => $pageid ],
319 foreach (
$res as $row ) {
321 'pr_page' => $redirid,
322 'pr_type' => $row->pr_type,
323 'pr_level' => $row->pr_level,
324 'pr_cascade' => $row->pr_cascade,
325 'pr_user' => $row->pr_user,
326 'pr_expiry' => $row->pr_expiry
329 $dbw->insert(
'page_restrictions', $rowsInsert, __METHOD__, [
'IGNORE' ] );
334 $this->oldTitle->getPrefixedText(),
335 $this->newTitle->getPrefixedText()
336 )->inContentLanguage()->text();
338 $comment .=
wfMessage(
'colon-separator' )->inContentLanguage()->text() . $reason;
342 $insertedPrIds = $dbw->select(
345 [
'pr_page' => $redirid ],
348 $logRelationsValues = [];
349 foreach ( $insertedPrIds
as $prid ) {
350 $logRelationsValues[] = $prid->pr_id;
355 $logEntry->setTarget( $this->newTitle );
356 $logEntry->setComment( $comment );
357 $logEntry->setPerformer(
$user );
358 $logEntry->setParameters( [
359 '4::oldtitle' => $this->oldTitle->getPrefixedText(),
361 $logEntry->setRelations( [
'pr_id' => $logRelationsValues ] );
362 $logEntry->setTags( $changeTags );
363 $logId = $logEntry->insert();
364 $logEntry->publish( $logId );
368 if ( $this->oldTitle->getNamespace() != $this->newTitle->getNamespace() ) {
369 $dbw->update(
'pagelinks',
370 [
'pl_from_namespace' => $this->newTitle->getNamespace() ],
371 [
'pl_from' => $pageid ],
374 $dbw->update(
'templatelinks',
375 [
'tl_from_namespace' => $this->newTitle->getNamespace() ],
376 [
'tl_from' => $pageid ],
379 $dbw->update(
'imagelinks',
380 [
'il_from_namespace' => $this->newTitle->getNamespace() ],
381 [
'il_from' => $pageid ],
387 $oldtitle = $this->oldTitle->getDBkey();
388 $newtitle = $this->newTitle->getDBkey();
391 if ( $oldsnamespace != $newsnamespace || $oldtitle != $newtitle ) {
392 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
393 $store->duplicateAllAssociatedEntries( $this->oldTitle, $this->newTitle );
397 'TitleMoveCompleting',
398 [ $this->oldTitle, $this->newTitle,
399 $user, $pageid, $redirid, $reason, $nullRevision ]
402 $dbw->endAtomic( __METHOD__ );
443 array $changeTags = []
445 if ( $nt->exists() ) {
446 $moveOverRedirect =
true;
447 $logType =
'move_redir';
449 $moveOverRedirect =
false;
453 if ( $moveOverRedirect ) {
455 'delete_and_move_reason',
456 $this->oldTitle->getPrefixedText()
457 )->inContentLanguage()->text();
460 $status = $newpage->doDeleteArticleReal(
475 $nt->resetArticleID(
false );
478 if ( $createRedirect ) {
479 if ( $this->oldTitle->getNamespace() ==
NS_CATEGORY
480 && !
wfMessage(
'category-move-redirect-override' )->inContentLanguage()->isDisabled()
483 wfMessage(
'category-move-redirect-override' )
484 ->params( $nt->getPrefixedText() )->inContentLanguage()->plain() );
487 $redirectContent = $contentHandler->makeRedirectContent( $nt,
488 wfMessage(
'move-redirect-text' )->inContentLanguage()->
plain() );
493 $redirectContent =
null;
498 $contentModel = $this->oldTitle->getContentModel();
500 $defaultContentModelChanging = ( $oldDefault !== $newDefault
501 && $oldDefault === $contentModel );
504 $oldid = $this->oldTitle->getArticleID();
508 $logEntry->setPerformer(
$user );
509 $logEntry->setTarget( $logTitle );
510 $logEntry->setComment( $reason );
511 $logEntry->setParameters( [
512 '4::target' => $nt->getPrefixedText(),
513 '5::noredir' => $redirectContent ?
'0' :
'1',
518 $comment = $formatter->getPlainActionText();
520 $comment .=
wfMessage(
'colon-separator' )->inContentLanguage()->text() . $reason;
526 $oldcountable = $oldpage->isCountable();
530 # Save a null revision in the page's history notifying of the move
532 if ( !is_object( $nullRevision ) ) {
533 throw new MWException(
'No valid null revision produced in ' . __METHOD__ );
536 $nullRevId = $nullRevision->insertOn( $dbw );
537 $logEntry->setAssociatedRevId( $nullRevId );
539 # Change the name of the target page:
540 $dbw->update(
'page',
542 'page_namespace' => $nt->getNamespace(),
543 'page_title' => $nt->getDBkey(),
545 [
'page_id' => $oldid ],
549 if ( !$redirectContent ) {
554 $this->oldTitle->resetArticleID( 0 );
555 $nt->resetArticleID( $oldid );
558 $newpage->updateRevisionOn( $dbw, $nullRevision );
561 [ $newpage, $nullRevision, $nullRevision->getParentId(),
$user ] );
563 $newpage->doEditUpdates( $nullRevision,
$user,
564 [
'changed' =>
false,
'moved' =>
true,
'oldcountable' => $oldcountable ] );
567 if ( $defaultContentModelChanging ) {
570 [
'rev_content_model' => $contentModel ],
571 [
'rev_page' => $nt->getArticleID(),
'rev_content_model IS NULL' ],
578 # Recreate the redirect, this time in the other direction.
579 if ( $redirectContent ) {
582 $newid = $redirectArticle->insertOn( $dbw );
584 $this->oldTitle->resetArticleID( $newid );
586 'title' => $this->oldTitle,
588 'user_text' =>
$user->getName(),
589 'user' =>
$user->getId(),
590 'comment' => $comment,
591 'content' => $redirectContent ] );
592 $redirectRevId = $redirectRevision->
insertOn( $dbw );
593 $redirectArticle->updateRevisionOn( $dbw, $redirectRevision, 0 );
596 [ $redirectArticle, $redirectRevision,
false,
$user ] );
598 $redirectArticle->doEditUpdates( $redirectRevision,
$user, [
'created' =>
true ] );
605 $logid = $logEntry->insert();
607 $logEntry->setTags( $changeTags );
608 $logEntry->publish( $logid );
610 return $nullRevision;