Index: trunk/phase3/includes/User.php |
— | — | @@ -497,12 +497,21 @@ |
498 | 498 | */ |
499 | 499 | static function isUsableName( $name ) { |
500 | 500 | global $wgReservedUsernames; |
501 | | - return |
502 | | - // Must be a valid username, obviously ;) |
503 | | - self::isValidUserName( $name ) && |
| 501 | + // Must be a valid username, obviously ;) |
| 502 | + if ( !self::isValidUserName( $name ) ) { |
| 503 | + return false; |
| 504 | + } |
504 | 505 | |
505 | | - // Certain names may be reserved for batch processes. |
506 | | - !in_array( $name, $wgReservedUsernames ); |
| 506 | + // Certain names may be reserved for batch processes. |
| 507 | + foreach ( $wgReservedUsernames as $reserved ) { |
| 508 | + if ( substr( $reserved, 0, 4 ) == 'msg:' ) { |
| 509 | + $reserved = wfMsgForContent( substr( $reserved, 4 ) ); |
| 510 | + } |
| 511 | + if ( $reserved == $name ) { |
| 512 | + return false; |
| 513 | + } |
| 514 | + } |
| 515 | + return true; |
507 | 516 | } |
508 | 517 | |
509 | 518 | /** |
Index: trunk/phase3/includes/DefaultSettings.php |
— | — | @@ -1595,6 +1595,7 @@ |
1596 | 1596 | 'html_cache_update' => 'HTMLCacheUpdateJob', // backwards-compatible |
1597 | 1597 | 'sendMail' => 'EmaillingJob', |
1598 | 1598 | 'enotifNotify' => 'EnotifNotifyJob', |
| 1599 | + 'fixDoubleRedirect' => 'DoubleRedirectJob', |
1599 | 1600 | ); |
1600 | 1601 | |
1601 | 1602 | /** |
— | — | @@ -3078,6 +3079,7 @@ |
3079 | 3080 | 'Conversion script', // Used for the old Wikipedia software upgrade |
3080 | 3081 | 'Maintenance script', // Maintenance scripts which perform editing, image import script |
3081 | 3082 | 'Template namespace initialisation script', // Used in 1.2->1.3 upgrade |
| 3083 | + 'msg:double-redirect-fixer', // Automatic double redirect fix |
3082 | 3084 | ); |
3083 | 3085 | |
3084 | 3086 | /** |
Index: trunk/phase3/includes/JobQueue.php |
— | — | @@ -163,7 +163,10 @@ |
164 | 164 | $job = Job::factory( $row->job_cmd, $title, Job::extractBlob( $row->job_params ), $row->job_id ); |
165 | 165 | |
166 | 166 | // Remove any duplicates it may have later in the queue |
| 167 | + // Deadlock prone section |
| 168 | + $dbw->begin(); |
167 | 169 | $dbw->delete( 'job', $job->insertFields(), __METHOD__ ); |
| 170 | + $dbw->commit(); |
168 | 171 | |
169 | 172 | wfProfileOut( __METHOD__ ); |
170 | 173 | return $job; |
— | — | @@ -213,12 +216,23 @@ |
214 | 217 | * @param $jobs array of Job objects |
215 | 218 | */ |
216 | 219 | static function batchInsert( $jobs ) { |
217 | | - if( count( $jobs ) ) { |
218 | | - $dbw = wfGetDB( DB_MASTER ); |
| 220 | + if( !count( $jobs ) ) { |
| 221 | + return; |
| 222 | + } |
| 223 | + $dbw = wfGetDB( DB_MASTER ); |
| 224 | + $rows = array(); |
| 225 | + foreach( $jobs as $job ) { |
| 226 | + $rows[] = $job->insertFields(); |
| 227 | + if ( count( $rows ) >= 50 ) { |
| 228 | + # Do a small transaction to avoid slave lag |
| 229 | + $dbw->begin(); |
| 230 | + $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' ); |
| 231 | + $dbw->commit(); |
| 232 | + $rows = array(); |
| 233 | + } |
| 234 | + } |
| 235 | + if ( $rows ) { |
219 | 236 | $dbw->begin(); |
220 | | - foreach( $jobs as $job ) { |
221 | | - $rows[] = $job->insertFields(); |
222 | | - } |
223 | 237 | $dbw->insert( 'job', $rows, __METHOD__, 'IGNORE' ); |
224 | 238 | $dbw->commit(); |
225 | 239 | } |
— | — | @@ -288,6 +302,10 @@ |
289 | 303 | } |
290 | 304 | } |
291 | 305 | |
| 306 | + protected function setLastError( $error ) { |
| 307 | + $this->error = $error; |
| 308 | + } |
| 309 | + |
292 | 310 | function getLastError() { |
293 | 311 | return $this->error; |
294 | 312 | } |
Index: trunk/phase3/includes/AutoLoader.php |
— | — | @@ -43,6 +43,7 @@ |
44 | 44 | '_DiffOp' => 'includes/DifferenceEngine.php', |
45 | 45 | 'DjVuImage' => 'includes/DjVuImage.php', |
46 | 46 | 'DoubleReplacer' => 'includes/StringUtils.php', |
| 47 | + 'DoubleRedirectJob' => 'includes/DoubleRedirectJob.php', |
47 | 48 | 'Dump7ZipOutput' => 'includes/Export.php', |
48 | 49 | 'DumpBZip2Output' => 'includes/Export.php', |
49 | 50 | 'DumpFileOutput' => 'includes/Export.php', |
Index: trunk/phase3/includes/specials/SpecialMovepage.php |
— | — | @@ -17,36 +17,35 @@ |
18 | 18 | } |
19 | 19 | |
20 | 20 | $target = isset( $par ) ? $par : $wgRequest->getVal( 'target' ); |
21 | | - $oldTitle = $wgRequest->getText( 'wpOldTitle', $target ); |
22 | | - $newTitle = $wgRequest->getText( 'wpNewTitle' ); |
| 21 | + $oldTitleText = $wgRequest->getText( 'wpOldTitle', $target ); |
| 22 | + $newTitleText = $wgRequest->getText( 'wpNewTitle' ); |
23 | 23 | |
24 | | - # Variables beginning with 'o' for old article 'n' for new article |
25 | | - $ot = Title::newFromText( $oldTitle ); |
26 | | - $nt = Title::newFromText( $newTitle ); |
| 24 | + $oldTitle = Title::newFromText( $oldTitleText ); |
| 25 | + $newTitle = Title::newFromText( $newTitleText ); |
27 | 26 | |
28 | | - if( is_null( $ot ) ) { |
| 27 | + if( is_null( $oldTitle ) ) { |
29 | 28 | $wgOut->showErrorPage( 'notargettitle', 'notargettext' ); |
30 | 29 | return; |
31 | 30 | } |
32 | | - if( !$ot->exists() ) { |
| 31 | + if( !$oldTitle->exists() ) { |
33 | 32 | $wgOut->showErrorPage( 'nopagetitle', 'nopagetext' ); |
34 | 33 | return; |
35 | 34 | } |
36 | 35 | |
37 | 36 | # Check rights |
38 | | - $permErrors = $ot->getUserPermissionsErrors( 'move', $wgUser ); |
| 37 | + $permErrors = $oldTitle->getUserPermissionsErrors( 'move', $wgUser ); |
39 | 38 | if( !empty( $permErrors ) ) { |
40 | 39 | $wgOut->showPermissionsErrorPage( $permErrors ); |
41 | 40 | return; |
42 | 41 | } |
43 | 42 | |
44 | | - $f = new MovePageForm( $ot, $nt ); |
| 43 | + $form = new MovePageForm( $oldTitle, $newTitle ); |
45 | 44 | |
46 | 45 | if ( 'submit' == $action && $wgRequest->wasPosted() |
47 | 46 | && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { |
48 | | - $f->doSubmit(); |
| 47 | + $form->doSubmit(); |
49 | 48 | } else { |
50 | | - $f->showForm( '' ); |
| 49 | + $form->showForm( '' ); |
51 | 50 | } |
52 | 51 | } |
53 | 52 | |
— | — | @@ -56,7 +55,7 @@ |
57 | 56 | */ |
58 | 57 | class MovePageForm { |
59 | 58 | var $oldTitle, $newTitle, $reason; # Text input |
60 | | - var $moveTalk, $deleteAndMove, $moveSubpages; |
| 59 | + var $moveTalk, $deleteAndMove, $moveSubpages, $fixRedirects; |
61 | 60 | |
62 | 61 | private $watch = false; |
63 | 62 | |
— | — | @@ -73,17 +72,17 @@ |
74 | 73 | } |
75 | 74 | $this->moveSubpages = $wgRequest->getBool( 'wpMovesubpages', false ); |
76 | 75 | $this->deleteAndMove = $wgRequest->getBool( 'wpDeleteAndMove' ) && $wgRequest->getBool( 'wpConfirm' ); |
| 76 | + $this->fixRedirects = $wgRequest->getBool( 'wpFixRedirects', true ); |
77 | 77 | $this->watch = $wgRequest->getCheck( 'wpWatch' ); |
78 | 78 | } |
79 | 79 | |
80 | 80 | function showForm( $err, $hookErr = '' ) { |
81 | 81 | global $wgOut, $wgUser; |
82 | 82 | |
83 | | - $ot = $this->oldTitle; |
84 | | - $sk = $wgUser->getSkin(); |
| 83 | + $skin = $wgUser->getSkin(); |
85 | 84 | |
86 | | - $oldTitleLink = $sk->makeLinkObj( $ot ); |
87 | | - $oldTitle = $ot->getPrefixedText(); |
| 85 | + $oldTitleLink = $skin->makeLinkObj( $this->oldTitle ); |
| 86 | + $oldTitle = $this->oldTitle->getPrefixedText(); |
88 | 87 | |
89 | 88 | $wgOut->setPagetitle( wfMsg( 'move-page', $oldTitle ) ); |
90 | 89 | $wgOut->setSubtitle( wfMsg( 'move-page-backlink', $oldTitleLink ) ); |
— | — | @@ -99,7 +98,7 @@ |
100 | 99 | # If a title was supplied, probably from the move log revert |
101 | 100 | # link, check for validity. We can then show some diagnostic |
102 | 101 | # information and save a click. |
103 | | - $newerr = $ot->isValidMoveOperation( $nt ); |
| 102 | + $newerr = $this->oldTitle->isValidMoveOperation( $nt ); |
104 | 103 | if( is_string( $newerr ) ) { |
105 | 104 | $err = $newerr; |
106 | 105 | } |
— | — | @@ -127,9 +126,16 @@ |
128 | 127 | $confirm = false; |
129 | 128 | } |
130 | 129 | |
131 | | - $oldTalk = $ot->getTalkPage(); |
132 | | - $considerTalk = ( !$ot->isTalkPage() && $oldTalk->exists() ); |
| 130 | + $oldTalk = $this->oldTitle->getTalkPage(); |
| 131 | + $considerTalk = ( !$this->oldTitle->isTalkPage() && $oldTalk->exists() ); |
133 | 132 | |
| 133 | + $dbr = wfGetDB( DB_SLAVE ); |
| 134 | + $hasRedirects = $dbr->selectField( 'redirect', '1', |
| 135 | + array( |
| 136 | + 'rd_namespace' => $this->oldTitle->getNamespace(), |
| 137 | + 'rd_title' => $this->oldTitle->getDBkey(), |
| 138 | + ) , __METHOD__ ); |
| 139 | + |
134 | 140 | if ( $considerTalk ) { |
135 | 141 | $wgOut->addWikiMsg( 'movepagetalktext' ); |
136 | 142 | } |
— | — | @@ -190,28 +196,41 @@ |
191 | 197 | ); |
192 | 198 | } |
193 | 199 | |
194 | | - if( ($ot->hasSubpages() || $ot->getTalkPage()->hasSubpages()) |
195 | | - && $ot->userCan( 'move-subpages' ) ) { |
| 200 | + if ( $hasRedirects ) { |
196 | 201 | $wgOut->addHTML( " |
197 | 202 | <tr> |
198 | 203 | <td></td> |
| 204 | + <td class='mw-input' >" . |
| 205 | + Xml::checkLabel( wfMsg( 'fix-double-redirects' ), 'wpFixRedirects', |
| 206 | + 'wpFixRedirects', $this->fixRedirects ) . |
| 207 | + "</td> |
| 208 | + </td>" |
| 209 | + ); |
| 210 | + } |
| 211 | + |
| 212 | + if( ($this->oldTitle->hasSubpages() || $this->oldTitle->getTalkPage()->hasSubpages()) |
| 213 | + && $this->oldTitle->userCan( 'move-subpages' ) ) { |
| 214 | + $wgOut->addHTML( " |
| 215 | + <tr> |
| 216 | + <td></td> |
199 | 217 | <td class=\"mw-input\">" . |
200 | 218 | Xml::checkLabel( wfMsgHtml( |
201 | | - $ot->hasSubpages() |
| 219 | + $this->oldTitle->hasSubpages() |
202 | 220 | ? 'move-subpages' |
203 | 221 | : 'move-talk-subpages' |
204 | 222 | ), |
205 | 223 | 'wpMovesubpages', 'wpMovesubpages', |
206 | 224 | # Don't check the box if we only have talk subpages to |
207 | 225 | # move and we aren't moving the talk page. |
208 | | - $this->moveSubpages && ($ot->hasSubpages() || $this->moveTalk) |
| 226 | + $this->moveSubpages && ($this->oldTitle->hasSubpages() || $this->moveTalk) |
209 | 227 | ) . |
210 | 228 | "</td> |
211 | 229 | </tr>" |
212 | 230 | ); |
213 | 231 | } |
214 | 232 | |
215 | | - $watchChecked = $this->watch || $wgUser->getBoolOption( 'watchmoves' ) || $ot->userIsWatching(); |
| 233 | + $watchChecked = $this->watch || $wgUser->getBoolOption( 'watchmoves' ) |
| 234 | + || $this->oldTitle->userIsWatching(); |
216 | 235 | $wgOut->addHTML( " |
217 | 236 | <tr> |
218 | 237 | <td></td> |
— | — | @@ -233,7 +252,7 @@ |
234 | 253 | "\n" |
235 | 254 | ); |
236 | 255 | |
237 | | - $this->showLogFragment( $ot, $wgOut ); |
| 256 | + $this->showLogFragment( $this->oldTitle, $wgOut ); |
238 | 257 | |
239 | 258 | } |
240 | 259 | |
— | — | @@ -277,6 +296,10 @@ |
278 | 297 | return; |
279 | 298 | } |
280 | 299 | |
| 300 | + if ( $this->fixRedirects ) { |
| 301 | + DoubleRedirectJob::fixRedirects( 'move', $ot, $nt ); |
| 302 | + } |
| 303 | + |
281 | 304 | wfRunHooks( 'SpecialMovepageAfterMove', array( &$this , &$ot , &$nt ) ) ; |
282 | 305 | |
283 | 306 | $wgOut->setPagetitle( wfMsg( 'pagemovedsub' ) ); |
— | — | @@ -358,40 +381,43 @@ |
359 | 382 | continue; |
360 | 383 | } |
361 | 384 | |
362 | | - $oldPage = Title::newFromRow( $row ); |
| 385 | + $oldSubpage = Title::newFromRow( $row ); |
363 | 386 | $newPageName = preg_replace( |
364 | 387 | '#^'.preg_quote( $ot->getDBKey(), '#' ).'#', |
365 | 388 | $nt->getDBKey(), |
366 | | - $oldPage->getDBKey() |
| 389 | + $oldSubpage->getDBKey() |
367 | 390 | ); |
368 | | - if( $oldPage->isTalkPage() ) { |
| 391 | + if( $oldSubpage->isTalkPage() ) { |
369 | 392 | $newNs = $nt->getTalkPage()->getNamespace(); |
370 | 393 | } else { |
371 | 394 | $newNs = $nt->getSubjectPage()->getNamespace(); |
372 | 395 | } |
373 | 396 | # Bug 14385: we need makeTitleSafe because the new page names may |
374 | 397 | # be longer than 255 characters. |
375 | | - $newPage = Title::makeTitleSafe( $newNs, $newPageName ); |
376 | | - if( !$newPage ) { |
377 | | - $oldLink = $skin->makeKnownLinkObj( $oldPage ); |
| 398 | + $newSubpage = Title::makeTitleSafe( $newNs, $newPageName ); |
| 399 | + if( !$newSubpage ) { |
| 400 | + $oldLink = $skin->makeKnownLinkObj( $oldSubpage ); |
378 | 401 | $extraOutput []= wfMsgHtml( 'movepage-page-unmoved', $oldLink, |
379 | 402 | htmlspecialchars(Title::makeName( $newNs, $newPageName ))); |
380 | 403 | continue; |
381 | 404 | } |
382 | 405 | |
383 | 406 | # This was copy-pasted from Renameuser, bleh. |
384 | | - if ( $newPage->exists() && !$oldPage->isValidMoveTarget( $newPage ) ) { |
385 | | - $link = $skin->makeKnownLinkObj( $newPage ); |
| 407 | + if ( $newSubpage->exists() && !$oldSubpage->isValidMoveTarget( $newSubpage ) ) { |
| 408 | + $link = $skin->makeKnownLinkObj( $newSubpage ); |
386 | 409 | $extraOutput []= wfMsgHtml( 'movepage-page-exists', $link ); |
387 | 410 | } else { |
388 | | - $success = $oldPage->moveTo( $newPage, true, $this->reason ); |
| 411 | + $success = $oldSubpage->moveTo( $newSubpage, true, $this->reason ); |
389 | 412 | if( $success === true ) { |
390 | | - $oldLink = $skin->makeKnownLinkObj( $oldPage, '', 'redirect=no' ); |
391 | | - $newLink = $skin->makeKnownLinkObj( $newPage ); |
| 413 | + if ( $this->fixRedirects ) { |
| 414 | + DoubleRedirectJob::fixRedirects( 'move', $oldSubpage, $newSubpage ); |
| 415 | + } |
| 416 | + $oldLink = $skin->makeKnownLinkObj( $oldSubpage, '', 'redirect=no' ); |
| 417 | + $newLink = $skin->makeKnownLinkObj( $newSubpage ); |
392 | 418 | $extraOutput []= wfMsgHtml( 'movepage-page-moved', $oldLink, $newLink ); |
393 | 419 | } else { |
394 | | - $oldLink = $skin->makeKnownLinkObj( $oldPage ); |
395 | | - $newLink = $skin->makeLinkObj( $newPage ); |
| 420 | + $oldLink = $skin->makeKnownLinkObj( $oldSubpage ); |
| 421 | + $newLink = $skin->makeLinkObj( $newSubpage ); |
396 | 422 | $extraOutput []= wfMsgHtml( 'movepage-page-unmoved', $oldLink, $newLink ); |
397 | 423 | } |
398 | 424 | } |
Index: trunk/phase3/RELEASE-NOTES |
— | — | @@ -194,6 +194,7 @@ |
195 | 195 | * Special:Recentchangeslinked now includes changes to transcluded pages and |
196 | 196 | displayed images; also, the "Show changes to pages linked" checkbox now works on |
197 | 197 | category pages too, showing all links that are not categorizations |
| 198 | +* (bug 4578) Automatically fix redirects broken by a page move |
198 | 199 | |
199 | 200 | === Bug fixes in 1.13 === |
200 | 201 | |
Index: trunk/phase3/languages/messages/MessagesEn.php |
— | — | @@ -1871,6 +1871,9 @@ |
1872 | 1872 | 'doubleredirectstext' => 'This page lists pages which redirect to other redirect pages. |
1873 | 1873 | Each row contains links to the first and second redirect, as well as the target of the second redirect, which is usually "real" target page, which the first redirect should point to.', |
1874 | 1874 | |
| 1875 | +'double-redirect-fixed-move' => '[[$1]] has been moved, it is now just a redirect to [[$2]]', |
| 1876 | +'double-redirect-fixer' => 'Redirect fixer', |
| 1877 | + |
1875 | 1878 | 'brokenredirects' => 'Broken redirects', |
1876 | 1879 | 'brokenredirects-summary' => '', # do not translate or duplicate this message to other languages |
1877 | 1880 | 'brokenredirectstext' => 'The following redirects link to non-existent pages:', |
— | — | @@ -2500,6 +2503,7 @@ |
2501 | 2504 | 'imagenocrossnamespace' => 'Cannot move file to non-file namespace', |
2502 | 2505 | 'imagetypemismatch' => 'The new file extension does not match its type', |
2503 | 2506 | 'imageinvalidfilename' => 'The target file name is invalid', |
| 2507 | +'fix-double-redirects' => 'Update any redirects that point to the original title', |
2504 | 2508 | |
2505 | 2509 | # Export |
2506 | 2510 | 'export' => 'Export pages', |