From 81bf8d0a3a1af467739d5fae79678032628ba4e4 Mon Sep 17 00:00:00 2001 From: Roan Kattouw Date: Mon, 9 Feb 2009 14:16:51 +0000 Subject: [PATCH] * API: (bug 17357) Add subpage moving to the API * Added Title::moveSubpages() as a backend function ** Didn't update SpecialMovepage to use this; left to braver souls * Fix handling of errors on talk page move (will backport) --- RELEASE-NOTES | 13 +++--- includes/Title.php | 95 ++++++++++++++++++++++++++++++++++++++-- includes/api/ApiBase.php | 20 +++++++-- includes/api/ApiMove.php | 50 +++++++++++++++++++-- 4 files changed, 161 insertions(+), 17 deletions(-) diff --git a/RELEASE-NOTES b/RELEASE-NOTES index cc1a57d951..cb58432e62 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -176,12 +176,13 @@ it from source control: http://www.mediawiki.org/wiki/Download_from_SVN * (bug 17326) BREAKING CHANGE: Changed output format for iiprop=metadata * (bug 17355) Added auwitheditsonly parameter to list=allusers * (bug 17007) Added action=import -* BREAKING CHANGE: Removed rctitles parameter from list=recentchanges because of - performance concerns -* Listing (semi-)deleted revisions and log entries as well in prop=revisions and - list=logevents -* (bug 11430) BREAKING CHANGE: Modules may return fewer results than the limit - and still set a query-continue in some cases +* BREAKING CHANGE: Removed rctitles parameter from list=recentchanges because + of performance concerns +* Listing (semi-)deleted revisions and log entries as well in prop=revisions + and list=logevents +* (bug 11430) BREAKING CHANGE: Modules may return fewer results than the + limit and still set a query-continue in some cases +* (bug 17357) Added movesubpages parameter to action=move === Languages updated in 1.15 === diff --git a/includes/Title.php b/includes/Title.php index 50cd6c7994..d7b57ef8bb 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -1613,10 +1613,36 @@ class Title { } $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__ + $subpages = $this->getSubpages( 1 ); + if( $subpages instanceof TitleArray ) + return $this->mHasSubpages = (bool)$subpages->count(); + return $this->mHasSubpages = false; + } + + /** + * Get all subpages of this page. + * @param $limit Maximum number of subpages to fetch; -1 for no limit + * @return mixed TitleArray, or empty array if this page's namespace + * doesn't allow subpages + */ + public function getSubpages($limit = -1) { + if( !MWNamespace::hasSubpages( $this->getNamespace() ) ) + return array(); + + $dbr = wfGetDB( DB_SLAVE ); + $conds['page_namespace'] = $this->getNamespace(); + $conds[] = 'page_title LIKE ' . $dbr->addQuotes( + $dbr->escapeLike( $this->getDBkey() ) . '/%' ); + $options = array(); + if( $limit > -1 ) + $options['LIMIT'] = $limit; + return $this->mSubpages = TitleArray::newFromResult( + $dbr->select( 'page', + array( 'page_id', 'page_namespace', 'page_title' ), + $conds, + __METHOD__, + $options + ) ); } @@ -2939,6 +2965,67 @@ class Title { } + /** + * Move this page's subpages to be subpages of $nt + * @param $nt Title Move target + * @param $auth bool Whether $wgUser's permissions should be checked + * @param $reason string The reason for the move + * @param $createRedirect bool Whether to create redirects from the old subpages to the new ones + * Ignored if the user doesn't have the 'suppressredirect' right + * @return mixed array with old page titles as keys, and strings (new page titles) or + * arrays (errors) as values, or an error array with numeric indices if no pages were moved + */ + public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) { + global $wgUser, $wgMaximumMovedPages; + // Check permissions + if( !$this->userCan( 'move-subpages' ) ) + return array( 'cant-move-subpages' ); + // Do the source and target namespaces support subpages? + if( !MWNamespace::hasSubpages( $this->getNamespace() ) ) + return array( 'namespace-nosubpages', + MWNamespace::getCanonicalName( $this->getNamespace() ) ); + if( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) + return array( 'namespace-nosubpages', + MWNamespace::getCanonicalName( $nt->getNamespace() ) ); + + $subpages = $this->getSubpages($wgMaximumMovedPages + 1); + $retval = array(); + $count = 0; + foreach( $subpages as $oldSubpage ) { + $count++; + if( $count > $wgMaximumMovedPages ) { + $retval[$oldSubpage->getPrefixedTitle()] = + array( 'movepage-max-pages', + $wgMaximumMovedPages ); + break; + } + + if( $oldSubpage->getArticleId() == $this->getArticleId() ) + // When moving a page to a subpage of itself, + // don't move it twice + continue; + $newPageName = preg_replace( + '#^'.preg_quote( $this->getDBKey(), '#' ).'#', + $nt->getDBKey(), $oldSubpage->getDBKey() ); + if( $oldSubpage->isTalkPage() ) { + $newNs = $nt->getTalkPage()->getNamespace(); + } else { + $newNs = $nt->getSubjectPage()->getNamespace(); + } + # Bug 14385: we need makeTitleSafe because the new page names may + # be longer than 255 characters. + $newSubpage = Title::makeTitleSafe( $newNs, $newPageName ); + + $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect ); + if( $success === true ) { + $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText(); + } else { + $retval[$oldSubpage->getPrefixedText()] = $success; + } + } + return $retval; + } + /** * Checks if this page is just a one-rev redirect. * Adds lock, so don't use just for light purposes. diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index 643a74182c..2be4c54d69 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -761,14 +761,28 @@ abstract class ApiBase { /** * Output the error message related to a certain array - * @param array $error Element of a getUserPermissionsErrors() + * @param array $error Element of a getUserPermissionsErrors()-style array */ public function dieUsageMsg($error) { + $parsed = $this->parseMsg($error); + $this->dieUsage($parsed['code'], $parsed['info']); + } + + /** + * Return the error message related to a certain array + * @param array $error Element of a getUserPermissionsErrors()-style array + * @return array('code' => code, 'info' => info) + */ + public function parseMsg($error) { $key = array_shift($error); if(isset(self::$messageMap[$key])) - $this->dieUsage(wfMsgReplaceArgs(self::$messageMap[$key]['info'], $error), wfMsgReplaceArgs(self::$messageMap[$key]['code'], $error)); + return array( 'code' => + wfMsgReplaceArgs(self::$messageMap[$key]['code'], $error), + 'info' => + wfMsgReplaceArgs(self::$messageMap[$key]['info'], $error) + ); // If the key isn't present, throw an "unknown error" - $this->dieUsageMsg(array('unknownerror', $key)); + return $this->parseMsg(array('unknownerror', $key)); } /** diff --git a/includes/api/ApiMove.php b/includes/api/ApiMove.php index cba5f91617..1045f3e57d 100644 --- a/includes/api/ApiMove.php +++ b/includes/api/ApiMove.php @@ -73,6 +73,7 @@ class ApiMove extends ApiBase { $this->dieUsageMsg(array('invalidtitle', $params['to'])); $toTalk = $toTitle->getTalkPage(); + # Move the page $hookErr = null; $retval = $fromTitle->moveTo($toTitle, true, $params['reason'], !$params['noredirect']); if($retval !== true) @@ -82,10 +83,9 @@ class ApiMove extends ApiBase { if(!$params['noredirect'] || !$wgUser->isAllowed('suppressredirect')) $r['redirectcreated'] = ''; + # Move the talk page if($params['movetalk'] && $fromTalk->exists() && !$fromTitle->isTalkPage()) { - // We need to move the talk page as well - $toTalk = $toTitle->getTalkPage(); $retval = $fromTalk->moveTo($toTalk, true, $params['reason'], !$params['noredirect']); if($retval === true) { @@ -95,8 +95,24 @@ class ApiMove extends ApiBase { // We're not gonna dieUsage() on failure, since we already changed something else { - $r['talkmove-error-code'] = ApiBase::$messageMap[reset($retval)]['code']; - $r['talkmove-error-info'] = ApiBase::$messageMap[reset($retval)]['info']; + $parsed = $this->parseMsg(reset($retval)); + $r['talkmove-error-code'] = $parsed['code']; + $r['talkmove-error-info'] = $parsed['info']; + } + } + + # Move subpages + if($params['movesubpages']) + { + $r['subpages'] = $this->moveSubpages($fromTitle, $toTitle, + $params['reason'], $params['noredirect']); + $this->getResult()->setIndexedTagName($r['subpages'], 'subpage'); + // TODO: Should we move talk subpages if moving the talk page failed? + if($params['movetalk']) + { + $r['subpages-talk'] = $this->moveSubpages($fromTalk, $toTalk, + $params['reason'], $params['noredirect']); + $this->getResult()->setIndexedTagName($r['subpages-talk'], 'subpage'); } } @@ -113,6 +129,30 @@ class ApiMove extends ApiBase { } $this->getResult()->addValue(null, $this->getModuleName(), $r); } + + public function moveSubpages($fromTitle, $toTitle, $reason, $noredirect) + { + $retval = array(); + $success = $fromTitle->moveSubpages($toTitle, true, $reason, !$noredirect); + if(isset($success[0])) + return array('error' => $this->parseMsg($success)); + else + { + // At least some pages could be moved + // Report each of them separately + foreach($success as $oldTitle => $newTitle) + { + $r = array('from' => $oldTitle); + if(is_array($newTitle)) + $r['error'] = $this->parseMsg(reset($newTitle)); + else + // Success + $r['to'] = $newTitle; + $retval[] = $r; + } + } + return $retval; + } public function mustBePosted() { return true; } @@ -126,6 +166,7 @@ class ApiMove extends ApiBase { 'token' => null, 'reason' => null, 'movetalk' => false, + 'movesubpages' => false, 'noredirect' => false, 'watch' => false, 'unwatch' => false @@ -140,6 +181,7 @@ class ApiMove extends ApiBase { 'token' => 'A move token previously retrieved through prop=info', 'reason' => 'Reason for the move (optional).', 'movetalk' => 'Move the talk page, if it exists.', + 'movesubpages' => 'Move subpages, if applicable', 'noredirect' => 'Don\'t create a redirect', 'watch' => 'Add the page and the redirect to your watchlist', 'unwatch' => 'Remove the page and the redirect from your watchlist' -- 2.20.1