From c75649cf5a104f5f560213d1d13e1fd7e2259782 Mon Sep 17 00:00:00 2001 From: Aryeh Gregor Date: Fri, 23 May 2008 01:11:17 +0000 Subject: [PATCH] Re-commit cleaned-up version of r33565 (move subpages along with page). * Move subpages checkbox is not shown unless the current page or its talk page actually has subpages. * Move subpages checkbox is shown if subpages are disabled for the current namespace, provided that the talk page has subpages (but then it has no effect unless the "move talk" box is also checked). * Unnecessary ampersands expunged, superfluous ternary operators excised. * Crazy 1=0 query removed. * Limit of maximum number of pages to move at once added (default 1000). One niggling annoyance is that if the maximum number of pages is hit due to the base page having too many subpages, the talk page probably won't be moved due to the order that results are returned, which is kind of unexpected. Also, make sure the queries don't cause MySQL 4 to explode -- MySQL sometimes has problems with OR, especially older versions. Other than that, should be production-ready, I think. --- RELEASE-NOTES | 3 + includes/DefaultSettings.php | 32 ++--- includes/SpecialMovepage.php | 189 ++++++++++++++++++++---------- includes/Title.php | 28 +++++ languages/messages/MessagesEn.php | 8 +- maintenance/language/messages.inc | 8 +- 6 files changed, 185 insertions(+), 83 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index a199243fed..c15849e339 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -45,6 +45,8 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN displayed on the recent changes special pages. * The "createpage" permission is no longer required when uploading if the target image page already exists. +* $wgMaximumMovedPages restricts the number of pages that can be moved at once + (default 1000) with the new subpage-move functionality of Special:Movepage. === New features in 1.13 === @@ -121,6 +123,7 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN * AutoAuthenticate hook renamed to UserLoadFromSession * (bug 13232) importScript(), importStylesheet() funcs available to custom JS * (bug 13095) Search by first letters or digits in [[Special:Categories]] +* Users moving a page can now move all subpages automatically as well === Bug fixes in 1.13 === diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index 39cdf90b8e..2aa35fa2cd 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -3159,18 +3159,17 @@ $wgParserConf = array( ); /** - * Hooks that are used for outputting exceptions - * Format is: - * $wgExceptionHooks[] = $funcname + * Hooks that are used for outputting exceptions. Format is: + * $wgExceptionHooks[] = $funcname * or: - * $wgExceptionHooks[] = array( $class, $funcname ) + * $wgExceptionHooks[] = array( $class, $funcname ) * Hooks should return strings or false */ $wgExceptionHooks = array(); /** - * Page property link table invalidation lists. - * Should only be set by extensions. + * Page property link table invalidation lists. Should only be set by exten- + * sions. */ $wgPagePropLinkInvalidations = array( 'hiddencat' => 'categorylinks', @@ -3183,22 +3182,27 @@ $wgPagePropLinkInvalidations = array( $wgMaxRedirectLinksRetrieved = 500; /** - * Maximum number of calls to expensive parser functions - * such as PAGESINCATEGORY. + * Maximum number of calls per parse to expensive parser functions such as + * PAGESINCATEGORY. */ $wgExpensiveParserFunctionLimit = 100; +/** + * Maximum number of pages to move at once when moving subpages with a page. + */ +$wgMaximumMovedPages = 1000; + /** * Array of namespaces to generate a sitemap for when the - * maintenance/generateSitemap.php script is run, or false - * if one is to be generated for all namespaces. + * maintenance/generateSitemap.php script is run, or false if one is to be ge- + * nerated for all namespaces. */ $wgSitemapNamespaces = false; /** - * If user doesn't specify any edit summary when making a an edit, - * MediaWiki will try to automatically create one. This feature can - * be disabled by setting this variable false. + * If user doesn't specify any edit summary when making a an edit, MediaWiki + * will try to automatically create one. This feature can be disabled by set- + * ting this variable false. */ -$wgUseAutomaticEditSummaries = true; \ No newline at end of file +$wgUseAutomaticEditSummaries = true; diff --git a/includes/SpecialMovepage.php b/includes/SpecialMovepage.php index 2b00fc04a6..34e314e967 100644 --- a/includes/SpecialMovepage.php +++ b/includes/SpecialMovepage.php @@ -30,9 +30,7 @@ function wfSpecialMovepage( $par = null ) { $f = new MovePageForm( $par ); - if ( 'success' == $action ) { - $f->showSuccess(); - } else if ( 'submit' == $action && $wgRequest->wasPosted() + if ( 'submit' == $action && $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { $f->doSubmit(); } else { @@ -46,7 +44,7 @@ function wfSpecialMovepage( $par = null ) { */ class MovePageForm { var $oldTitle, $newTitle, $reason; # Text input - var $moveTalk, $deleteAndMove; + var $moveTalk, $deleteAndMove, $moveSubpages; private $watch = false; @@ -61,12 +59,13 @@ class MovePageForm { } else { $this->moveTalk = $wgRequest->getBool( 'wpMovetalk', true ); } + $this->moveSubpages = $wgRequest->getBool( 'wpMovesubpages', false ); $this->deleteAndMove = $wgRequest->getBool( 'wpDeleteAndMove' ) && $wgRequest->getBool( 'wpConfirm' ); $this->watch = $wgRequest->getCheck( 'wpWatch' ); } function showForm( $err, $hookErr = '' ) { - global $wgOut, $wgUser; + global $wgOut, $wgUser, $wgNamespacesWithSubpages; $ot = Title::newFromURL( $this->oldTitle ); if( is_null( $ot ) ) { @@ -180,7 +179,7 @@ class MovePageForm { " ); - if ( $considerTalk ) { + if( $considerTalk ) { $wgOut->addHTML( " @@ -191,6 +190,26 @@ class MovePageForm { ); } + if( $ot->hasSubpages() || $ot->getTalkPage()->hasSubpages() ) { + $wgOut->addHTML( " + + + " . + Xml::checkLabel( wfMsgHtml( + $ot->hasSubpages() + ? 'move-subpages' + : 'move-talk-subpages' + ), + 'wpMovesubpages', 'wpMovesubpages', + # Don't check the box if we only have talk subpages to + # move and we aren't moving the talk page. + $this->moveSubpages && ($ot->hasSubpages() || $this->moveTalk) + ) . + " + " + ); + } + $watchChecked = $this->watch || $wgUser->getBoolOption( 'watchmoves' ) || $ot->userIsWatching(); $wgOut->addHTML( " @@ -266,79 +285,119 @@ class MovePageForm { wfRunHooks( 'SpecialMovepageAfterMove', array( &$this , &$ot , &$nt ) ) ; - # Move the talk page if relevant, if it exists, and if we've been told to - $ott = $ot->getTalkPage(); - if( $ott->exists() ) { - if( $this->moveTalk && !$ot->isTalkPage() && !$nt->isTalkPage() ) { - $ntt = $nt->getTalkPage(); - - # Attempt the move - $error = $ott->moveTo( $ntt, true, $this->reason ); - if ( $error === true ) { - $talkmoved = 1; - wfRunHooks( 'SpecialMovepageAfterMove', array( &$this , &$ott , &$ntt ) ); - } else { - $talkmoved = $error; - } + $wgOut->setPagetitle( wfMsg( 'pagemovedsub' ) ); + + $oldUrl = $ot->getFullUrl( 'redirect=no' ); + $newUrl = $nt->getFullUrl(); + $oldText = $ot->getPrefixedText(); + $newText = $nt->getPrefixedText(); + $oldLink = "[$oldUrl $oldText]"; + $newLink = "[$newUrl $newText]"; + + $wgOut->addWikiMsg( 'movepage-moved', $oldLink, $newLink, $oldText, $newText ); + + # Now we move extra pages we've been asked to move: subpages and talk + # pages. First, if the old page or the new page is a talk page, we + # can't move any talk pages: cancel that. + if( $ot->isTalkPage() || $nt->isTalkPage() ) { + $this->moveTalk = false; + } + + # Next make a list of id's. This might be marginally less efficient + # than a more direct method, but this is not a highly performance-cri- + # tical code path and readable code is more important here. + # + # Note: this query works nicely on MySQL 5, but the optimizer in MySQL + # 4 might get confused. If so, consider rewriting as a UNION. + $dbr = wfGetDB( DB_SLAVE ); + if( $this->moveSubpages ) { + $conds = array( + 'page_title LIKE '.$dbr->addQuotes( $dbr->escapeLike( $ot->getDBkey() ) . '/%' ) + .' OR page_title = ' . $dbr->addQuotes( $ot->getDBkey() ) + ); + if( $this->moveTalk ) { + $conds['page_namespace'] = array( $ot->getNamespace(), + MWNamespace::getTalk($ot->getNamespace()) ); } else { - # Stay silent on the subject of talk. - $talkmoved = ''; + $conds['page_namespace'] = $ot->getNamespace(); } } else { - $talkmoved = 'notalkpage'; + if( $this->moveTalk ) { + $conds = array( + 'page_namespace' => MWNamespace::getTalk($ot->getNamespace()), + 'page_title' => $ot->getDBKey() + ); + } else { + # Skip the query + $conds = null; + } } - # Deal with watches - if( $this->watch ) { - $wgUser->addWatch( $ot ); - $wgUser->addWatch( $nt ); - } else { - $wgUser->removeWatch( $ot ); - $wgUser->removeWatch( $nt ); + if( !is_null( $conds ) ) { + $extrapages = $dbr->select( + 'page', + array( 'page_id', 'page_namespace', 'page_title' ), + $conds, + __METHOD__ + ); } - # Give back result to user. - $titleObj = SpecialPage::getTitleFor( 'Movepage' ); - $success = $titleObj->getFullURL( - 'action=success&oldtitle=' . wfUrlencode( $ot->getPrefixedText() ) . - '&newtitle=' . wfUrlencode( $nt->getPrefixedText() ) . - '&talkmoved='.$talkmoved ); - - $wgOut->redirect( $success ); - } - - function showSuccess() { - global $wgOut, $wgRequest, $wgUser; + global $wgMaximumMovedPages, $wgLang; + $extraOutput = array(); + $skin = $wgUser->getSkin(); + $count = 1; + foreach( $extrapages as $row ) { + if( $row->page_id == $ot->getArticleId() ) { + # Already did this one. + continue; + } - $old = Title::newFromText( $wgRequest->getVal( 'oldtitle' ) ); - $new = Title::newFromText( $wgRequest->getVal( 'newtitle' ) ); + $oldPage = Title::newFromRow( $row ); + $newPageName = preg_replace( + '#^'.preg_quote( $ot->getDBKey(), '#' ).'#', + $nt->getDBKey(), + $oldPage->getDBKey() + ); + # The following line is an atrocious hack. Kill it with fire. + $newNs = $nt->getNamespace() + ($oldPage->getNamespace() & 1); + $newPage = Title::makeTitle( $newNs, $newPageName ); + + # This was copy-pasted from Renameuser, bleh. + if ( $newPage->exists() && !$oldPage->isValidMoveTarget( $newPage ) ) { + $link = $skin->makeKnownLinkObj( $newPage ); + $extraOutput []= wfMsgHtml( 'movepage-page-exists', $link ); + } else { + $success = $oldPage->moveTo( $newPage, true, $this->reason ); + if( $success === true ) { + $oldLink = $skin->makeKnownLinkObj( $oldPage, '', 'redirect=no' ); + $newLink = $skin->makeKnownLinkObj( $newPage ); + $extraOutput []= wfMsgHtml( 'movepage-page-moved', $oldLink, $newLink ); + } else { + $oldLink = $skin->makeKnownLinkObj( $oldPage ); + $newLink = $skin->makeLinkObj( $newPage ); + $extraOutput []= wfMsgHtml( 'movepage-page-unmoved', $oldLink, $newLink ); + } + } - if( is_null( $old ) || is_null( $new ) ) { - throw new ErrorPageError( 'badtitle', 'badtitletext' ); + ++$count; + if( $count >= $wgMaximumMovedPages ) { + $extraOutput []= wfMsgHtml( 'movepage-max-pages', $wgLang->formatNum( $wgMaximumMovedPages ) ); + break; + } } - $wgOut->setPagetitle( wfMsg( 'pagemovedsub' ) ); - - $talkmoved = $wgRequest->getVal( 'talkmoved' ); - $oldUrl = $old->getFullUrl( 'redirect=no' ); - $newUrl = $new->getFullUrl(); - $oldText = $old->getPrefixedText(); - $newText = $new->getPrefixedText(); - $oldLink = "[$oldUrl $oldText]"; - $newLink = "[$newUrl $newText]"; - - $s = wfMsgNoTrans( 'movepage-moved', $oldLink, $newLink, $oldText, $newText ); + if( $extraOutput !== array() ) { + $wgOut->addHTML( "" ); + } - if ( $talkmoved == 1 ) { - $s .= "\n\n" . wfMsgNoTrans( 'talkpagemoved' ); - } elseif( 'articleexists' == $talkmoved ) { - $s .= "\n\n" . wfMsgNoTrans( 'talkexists' ); + # Deal with watches (we don't watch subpages) + if( $this->watch ) { + $wgUser->addWatch( $ot ); + $wgUser->addWatch( $nt ); } else { - if( !$old->isTalkPage() && $talkmoved != 'notalkpage' ) { - $s .= "\n\n" . wfMsgNoTrans( 'talkpagenotmoved', wfMsgNoTrans( $talkmoved ) ); - } + $wgUser->removeWatch( $ot ); + $wgUser->removeWatch( $nt ); } - $wgOut->addWikiText( $s ); } function showLogFragment( $title, &$out ) { diff --git a/includes/Title.php b/includes/Title.php index b8b5e1e56b..fa80f68a0b 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -1502,6 +1502,34 @@ class Title { } } + /** + * Does this have subpages? (Warning, usually requires an extra DB query.) + * @return bool + */ + public function hasSubpages() { + global $wgNamespacesWithSubpages; + + if( !isset( $wgNamespacesWithSubpages[$this->mNamespace] ) ) { + # Duh + return false; + } + + # We dynamically add a member variable for the purpose of this method + # alone to cache the result. There's no point in having it hanging + # around uninitialized in every Title object; therefore we only add it + # if needed and don't declare it statically. + if( isset( $this->mHasSubpages ) ) { + return $this->mHasSubpages; + } + + $db = wfGetDB( DB_SLAVE ); + return $this->mHasSubpages = (bool)$db->selectField( 'page', '1', + "page_namespace = {$this->mNamespace} AND page_title LIKE '" + . $db->escapeLike( $this->mDbkeyform ) . "/%'", + __METHOD__ + ); + } + /** * Could this page contain custom CSS or JavaScript, based * on the title? diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index 270cb2312e..99e259527d 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -2426,8 +2426,12 @@ Please choose another name.', Please merge them manually.'''", 'movedto' => 'moved to', 'movetalk' => 'Move associated talk page', -'talkpagemoved' => 'The corresponding talk page was also moved.', -'talkpagenotmoved' => 'The corresponding talk page was not moved.', +'move-subpages' => 'Move all subpages, if applicable', +'move-talk-subpages' => 'Move all subpages of talk page, if applicable', +'movepage-page-exists' => 'The page $1 already exists and cannot be automatically overwritten.', +'movepage-page-moved' => 'The page $1 has been moved to $2.', +'movepage-page-unmoved' => 'The page $1 could not be moved to $2.', +'movepage-max-pages' => 'The maximum of $1 pages has been moved and no more will be moved automatically.', '1movedto2' => '[[$1]] moved to [[$2]]', '1movedto2_redir' => '[[$1]] moved to [[$2]] over redirect', 'movelogpage' => 'Move log', diff --git a/maintenance/language/messages.inc b/maintenance/language/messages.inc index 3742abcb47..0bd412e007 100644 --- a/maintenance/language/messages.inc +++ b/maintenance/language/messages.inc @@ -1659,8 +1659,12 @@ $wgMessageStructure = array( 'talkexists', 'movedto', 'movetalk', - 'talkpagemoved', - 'talkpagenotmoved', + 'move-subpages', + 'move-talk-subpages', + 'movepage-page-exists', + 'movepage-page-unmoved', + 'movepage-page-moved', + 'movepage-max-pages', '1movedto2', '1movedto2_redir', 'movelogpage', -- 2.20.1