Index: trunk/phase3/includes/api/ApiMove.php |
— | — | @@ -73,6 +73,7 @@ |
74 | 74 | $this->dieUsageMsg(array('invalidtitle', $params['to'])); |
75 | 75 | $toTalk = $toTitle->getTalkPage(); |
76 | 76 | |
| 77 | + # Move the page |
77 | 78 | $hookErr = null; |
78 | 79 | $retval = $fromTitle->moveTo($toTitle, true, $params['reason'], !$params['noredirect']); |
79 | 80 | if($retval !== true) |
— | — | @@ -82,10 +83,9 @@ |
83 | 84 | if(!$params['noredirect'] || !$wgUser->isAllowed('suppressredirect')) |
84 | 85 | $r['redirectcreated'] = ''; |
85 | 86 | |
| 87 | + # Move the talk page |
86 | 88 | if($params['movetalk'] && $fromTalk->exists() && !$fromTitle->isTalkPage()) |
87 | 89 | { |
88 | | - // We need to move the talk page as well |
89 | | - $toTalk = $toTitle->getTalkPage(); |
90 | 90 | $retval = $fromTalk->moveTo($toTalk, true, $params['reason'], !$params['noredirect']); |
91 | 91 | if($retval === true) |
92 | 92 | { |
— | — | @@ -95,11 +95,27 @@ |
96 | 96 | // We're not gonna dieUsage() on failure, since we already changed something |
97 | 97 | else |
98 | 98 | { |
99 | | - $r['talkmove-error-code'] = ApiBase::$messageMap[reset($retval)]['code']; |
100 | | - $r['talkmove-error-info'] = ApiBase::$messageMap[reset($retval)]['info']; |
| 99 | + $parsed = $this->parseMsg(reset($retval)); |
| 100 | + $r['talkmove-error-code'] = $parsed['code']; |
| 101 | + $r['talkmove-error-info'] = $parsed['info']; |
101 | 102 | } |
102 | 103 | } |
103 | 104 | |
| 105 | + # Move subpages |
| 106 | + if($params['movesubpages']) |
| 107 | + { |
| 108 | + $r['subpages'] = $this->moveSubpages($fromTitle, $toTitle, |
| 109 | + $params['reason'], $params['noredirect']); |
| 110 | + $this->getResult()->setIndexedTagName($r['subpages'], 'subpage'); |
| 111 | + // TODO: Should we move talk subpages if moving the talk page failed? |
| 112 | + if($params['movetalk']) |
| 113 | + { |
| 114 | + $r['subpages-talk'] = $this->moveSubpages($fromTalk, $toTalk, |
| 115 | + $params['reason'], $params['noredirect']); |
| 116 | + $this->getResult()->setIndexedTagName($r['subpages-talk'], 'subpage'); |
| 117 | + } |
| 118 | + } |
| 119 | + |
104 | 120 | # Watch pages |
105 | 121 | if($params['watch'] || $wgUser->getOption('watchmoves')) |
106 | 122 | { |
— | — | @@ -113,6 +129,30 @@ |
114 | 130 | } |
115 | 131 | $this->getResult()->addValue(null, $this->getModuleName(), $r); |
116 | 132 | } |
| 133 | + |
| 134 | + public function moveSubpages($fromTitle, $toTitle, $reason, $noredirect) |
| 135 | + { |
| 136 | + $retval = array(); |
| 137 | + $success = $fromTitle->moveSubpages($toTitle, true, $reason, !$noredirect); |
| 138 | + if(isset($success[0])) |
| 139 | + return array('error' => $this->parseMsg($success)); |
| 140 | + else |
| 141 | + { |
| 142 | + // At least some pages could be moved |
| 143 | + // Report each of them separately |
| 144 | + foreach($success as $oldTitle => $newTitle) |
| 145 | + { |
| 146 | + $r = array('from' => $oldTitle); |
| 147 | + if(is_array($newTitle)) |
| 148 | + $r['error'] = $this->parseMsg(reset($newTitle)); |
| 149 | + else |
| 150 | + // Success |
| 151 | + $r['to'] = $newTitle; |
| 152 | + $retval[] = $r; |
| 153 | + } |
| 154 | + } |
| 155 | + return $retval; |
| 156 | + } |
117 | 157 | |
118 | 158 | public function mustBePosted() { return true; } |
119 | 159 | |
— | — | @@ -126,6 +166,7 @@ |
127 | 167 | 'token' => null, |
128 | 168 | 'reason' => null, |
129 | 169 | 'movetalk' => false, |
| 170 | + 'movesubpages' => false, |
130 | 171 | 'noredirect' => false, |
131 | 172 | 'watch' => false, |
132 | 173 | 'unwatch' => false |
— | — | @@ -140,6 +181,7 @@ |
141 | 182 | 'token' => 'A move token previously retrieved through prop=info', |
142 | 183 | 'reason' => 'Reason for the move (optional).', |
143 | 184 | 'movetalk' => 'Move the talk page, if it exists.', |
| 185 | + 'movesubpages' => 'Move subpages, if applicable', |
144 | 186 | 'noredirect' => 'Don\'t create a redirect', |
145 | 187 | 'watch' => 'Add the page and the redirect to your watchlist', |
146 | 188 | 'unwatch' => 'Remove the page and the redirect from your watchlist' |
Index: trunk/phase3/includes/api/ApiBase.php |
— | — | @@ -761,14 +761,28 @@ |
762 | 762 | |
763 | 763 | /** |
764 | 764 | * Output the error message related to a certain array |
765 | | - * @param array $error Element of a getUserPermissionsErrors() |
| 765 | + * @param array $error Element of a getUserPermissionsErrors()-style array |
766 | 766 | */ |
767 | 767 | public function dieUsageMsg($error) { |
| 768 | + $parsed = $this->parseMsg($error); |
| 769 | + $this->dieUsage($parsed['code'], $parsed['info']); |
| 770 | + } |
| 771 | + |
| 772 | + /** |
| 773 | + * Return the error message related to a certain array |
| 774 | + * @param array $error Element of a getUserPermissionsErrors()-style array |
| 775 | + * @return array('code' => code, 'info' => info) |
| 776 | + */ |
| 777 | + public function parseMsg($error) { |
768 | 778 | $key = array_shift($error); |
769 | 779 | if(isset(self::$messageMap[$key])) |
770 | | - $this->dieUsage(wfMsgReplaceArgs(self::$messageMap[$key]['info'], $error), wfMsgReplaceArgs(self::$messageMap[$key]['code'], $error)); |
| 780 | + return array( 'code' => |
| 781 | + wfMsgReplaceArgs(self::$messageMap[$key]['code'], $error), |
| 782 | + 'info' => |
| 783 | + wfMsgReplaceArgs(self::$messageMap[$key]['info'], $error) |
| 784 | + ); |
771 | 785 | // If the key isn't present, throw an "unknown error" |
772 | | - $this->dieUsageMsg(array('unknownerror', $key)); |
| 786 | + return $this->parseMsg(array('unknownerror', $key)); |
773 | 787 | } |
774 | 788 | |
775 | 789 | /** |
Index: trunk/phase3/includes/Title.php |
— | — | @@ -1613,10 +1613,36 @@ |
1614 | 1614 | } |
1615 | 1615 | |
1616 | 1616 | $db = wfGetDB( DB_SLAVE ); |
1617 | | - return $this->mHasSubpages = (bool)$db->selectField( 'page', '1', |
1618 | | - "page_namespace = {$this->mNamespace} AND page_title LIKE '" |
1619 | | - . $db->escapeLike( $this->mDbkeyform ) . "/%'", |
1620 | | - __METHOD__ |
| 1617 | + $subpages = $this->getSubpages( 1 ); |
| 1618 | + if( $subpages instanceof TitleArray ) |
| 1619 | + return $this->mHasSubpages = (bool)$subpages->count(); |
| 1620 | + return $this->mHasSubpages = false; |
| 1621 | + } |
| 1622 | + |
| 1623 | + /** |
| 1624 | + * Get all subpages of this page. |
| 1625 | + * @param $limit Maximum number of subpages to fetch; -1 for no limit |
| 1626 | + * @return mixed TitleArray, or empty array if this page's namespace |
| 1627 | + * doesn't allow subpages |
| 1628 | + */ |
| 1629 | + public function getSubpages($limit = -1) { |
| 1630 | + if( !MWNamespace::hasSubpages( $this->getNamespace() ) ) |
| 1631 | + return array(); |
| 1632 | + |
| 1633 | + $dbr = wfGetDB( DB_SLAVE ); |
| 1634 | + $conds['page_namespace'] = $this->getNamespace(); |
| 1635 | + $conds[] = 'page_title LIKE ' . $dbr->addQuotes( |
| 1636 | + $dbr->escapeLike( $this->getDBkey() ) . '/%' ); |
| 1637 | + $options = array(); |
| 1638 | + if( $limit > -1 ) |
| 1639 | + $options['LIMIT'] = $limit; |
| 1640 | + return $this->mSubpages = TitleArray::newFromResult( |
| 1641 | + $dbr->select( 'page', |
| 1642 | + array( 'page_id', 'page_namespace', 'page_title' ), |
| 1643 | + $conds, |
| 1644 | + __METHOD__, |
| 1645 | + $options |
| 1646 | + ) |
1621 | 1647 | ); |
1622 | 1648 | } |
1623 | 1649 | |
— | — | @@ -2940,6 +2966,67 @@ |
2941 | 2967 | } |
2942 | 2968 | |
2943 | 2969 | /** |
| 2970 | + * Move this page's subpages to be subpages of $nt |
| 2971 | + * @param $nt Title Move target |
| 2972 | + * @param $auth bool Whether $wgUser's permissions should be checked |
| 2973 | + * @param $reason string The reason for the move |
| 2974 | + * @param $createRedirect bool Whether to create redirects from the old subpages to the new ones |
| 2975 | + * Ignored if the user doesn't have the 'suppressredirect' right |
| 2976 | + * @return mixed array with old page titles as keys, and strings (new page titles) or |
| 2977 | + * arrays (errors) as values, or an error array with numeric indices if no pages were moved |
| 2978 | + */ |
| 2979 | + public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) { |
| 2980 | + global $wgUser, $wgMaximumMovedPages; |
| 2981 | + // Check permissions |
| 2982 | + if( !$this->userCan( 'move-subpages' ) ) |
| 2983 | + return array( 'cant-move-subpages' ); |
| 2984 | + // Do the source and target namespaces support subpages? |
| 2985 | + if( !MWNamespace::hasSubpages( $this->getNamespace() ) ) |
| 2986 | + return array( 'namespace-nosubpages', |
| 2987 | + MWNamespace::getCanonicalName( $this->getNamespace() ) ); |
| 2988 | + if( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) |
| 2989 | + return array( 'namespace-nosubpages', |
| 2990 | + MWNamespace::getCanonicalName( $nt->getNamespace() ) ); |
| 2991 | + |
| 2992 | + $subpages = $this->getSubpages($wgMaximumMovedPages + 1); |
| 2993 | + $retval = array(); |
| 2994 | + $count = 0; |
| 2995 | + foreach( $subpages as $oldSubpage ) { |
| 2996 | + $count++; |
| 2997 | + if( $count > $wgMaximumMovedPages ) { |
| 2998 | + $retval[$oldSubpage->getPrefixedTitle()] = |
| 2999 | + array( 'movepage-max-pages', |
| 3000 | + $wgMaximumMovedPages ); |
| 3001 | + break; |
| 3002 | + } |
| 3003 | + |
| 3004 | + if( $oldSubpage->getArticleId() == $this->getArticleId() ) |
| 3005 | + // When moving a page to a subpage of itself, |
| 3006 | + // don't move it twice |
| 3007 | + continue; |
| 3008 | + $newPageName = preg_replace( |
| 3009 | + '#^'.preg_quote( $this->getDBKey(), '#' ).'#', |
| 3010 | + $nt->getDBKey(), $oldSubpage->getDBKey() ); |
| 3011 | + if( $oldSubpage->isTalkPage() ) { |
| 3012 | + $newNs = $nt->getTalkPage()->getNamespace(); |
| 3013 | + } else { |
| 3014 | + $newNs = $nt->getSubjectPage()->getNamespace(); |
| 3015 | + } |
| 3016 | + # Bug 14385: we need makeTitleSafe because the new page names may |
| 3017 | + # be longer than 255 characters. |
| 3018 | + $newSubpage = Title::makeTitleSafe( $newNs, $newPageName ); |
| 3019 | + |
| 3020 | + $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect ); |
| 3021 | + if( $success === true ) { |
| 3022 | + $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText(); |
| 3023 | + } else { |
| 3024 | + $retval[$oldSubpage->getPrefixedText()] = $success; |
| 3025 | + } |
| 3026 | + } |
| 3027 | + return $retval; |
| 3028 | + } |
| 3029 | + |
| 3030 | + /** |
2944 | 3031 | * Checks if this page is just a one-rev redirect. |
2945 | 3032 | * Adds lock, so don't use just for light purposes. |
2946 | 3033 | * |
Index: trunk/phase3/RELEASE-NOTES |
— | — | @@ -176,12 +176,13 @@ |
177 | 177 | * (bug 17326) BREAKING CHANGE: Changed output format for iiprop=metadata |
178 | 178 | * (bug 17355) Added auwitheditsonly parameter to list=allusers |
179 | 179 | * (bug 17007) Added action=import |
180 | | -* BREAKING CHANGE: Removed rctitles parameter from list=recentchanges because of |
181 | | - performance concerns |
182 | | -* Listing (semi-)deleted revisions and log entries as well in prop=revisions and |
183 | | - list=logevents |
184 | | -* (bug 11430) BREAKING CHANGE: Modules may return fewer results than the limit |
185 | | - and still set a query-continue in some cases |
| 180 | +* BREAKING CHANGE: Removed rctitles parameter from list=recentchanges because |
| 181 | + of performance concerns |
| 182 | +* Listing (semi-)deleted revisions and log entries as well in prop=revisions |
| 183 | + and list=logevents |
| 184 | +* (bug 11430) BREAKING CHANGE: Modules may return fewer results than the |
| 185 | + limit and still set a query-continue in some cases |
| 186 | +* (bug 17357) Added movesubpages parameter to action=move |
186 | 187 | |
187 | 188 | === Languages updated in 1.15 === |
188 | 189 | |