Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
88 / 88 |
|
100.00% |
5 / 5 |
CRAP | |
100.00% |
1 / 1 |
| CleanupCaps | |
100.00% |
88 / 88 |
|
100.00% |
5 / 5 |
20 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
| execute | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
2 | |||
| processRowToUppercase | |
100.00% |
35 / 35 |
|
100.00% |
1 / 1 |
7 | |||
| processRowToLowercase | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
6 | |||
| movePage | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
4 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * Clean up broken page links when somebody turns on $wgCapitalLinks. |
| 4 | * |
| 5 | * Usage: php cleanupCaps.php [--dry-run] |
| 6 | * Options: |
| 7 | * --dry-run don't actually try moving them |
| 8 | * |
| 9 | * Copyright © 2005 Brooke Vibber <bvibber@wikimedia.org> |
| 10 | * https://www.mediawiki.org/ |
| 11 | * |
| 12 | * @license GPL-2.0-or-later |
| 13 | * @file |
| 14 | * @author Brooke Vibber <bvibber@wikimedia.org> |
| 15 | * @ingroup Maintenance |
| 16 | */ |
| 17 | |
| 18 | use MediaWiki\Title\Title; |
| 19 | use MediaWiki\User\User; |
| 20 | |
| 21 | // @codeCoverageIgnoreStart |
| 22 | require_once __DIR__ . '/TableCleanup.php'; |
| 23 | // @codeCoverageIgnoreEnd |
| 24 | |
| 25 | /** |
| 26 | * Maintenance script to clean up broken page links when somebody turns |
| 27 | * on or off $wgCapitalLinks. |
| 28 | * |
| 29 | * @ingroup Maintenance |
| 30 | */ |
| 31 | class CleanupCaps extends TableCleanup { |
| 32 | |
| 33 | /** @var User */ |
| 34 | private $user; |
| 35 | /** @var int */ |
| 36 | private $namespace; |
| 37 | |
| 38 | public function __construct() { |
| 39 | parent::__construct(); |
| 40 | $this->addDescription( 'Script to cleanup capitalization' ); |
| 41 | $this->addOption( 'namespace', 'Namespace number to run caps cleanup on', false, true ); |
| 42 | } |
| 43 | |
| 44 | public function execute() { |
| 45 | $this->user = User::newSystemUser( 'Conversion script', [ 'steal' => true ] ); |
| 46 | |
| 47 | $this->namespace = intval( $this->getOption( 'namespace', 0 ) ); |
| 48 | |
| 49 | if ( |
| 50 | $this->getServiceContainer()->getNamespaceInfo()-> |
| 51 | isCapitalized( $this->namespace ) |
| 52 | ) { |
| 53 | $this->output( "Will be moving pages to first letter capitalized titles\n" ); |
| 54 | $callback = 'processRowToUppercase'; |
| 55 | } else { |
| 56 | $this->output( "Will be moving pages to first letter lowercase titles\n" ); |
| 57 | $callback = 'processRowToLowercase'; |
| 58 | } |
| 59 | |
| 60 | $this->dryrun = $this->hasOption( 'dry-run' ); |
| 61 | |
| 62 | $this->runTable( [ |
| 63 | 'table' => 'page', |
| 64 | 'conds' => [ 'page_namespace' => $this->namespace ], |
| 65 | 'index' => 'page_id', |
| 66 | 'callback' => $callback ] ); |
| 67 | } |
| 68 | |
| 69 | protected function processRowToUppercase( \stdClass $row ) { |
| 70 | $current = Title::makeTitle( $row->page_namespace, $row->page_title ); |
| 71 | // Set the ID of the page because Title::exists will return false |
| 72 | // unless the Article ID is already known, because Title::canExist will be false |
| 73 | // when $wgCapitalLinks is true and the old title is in lower case. |
| 74 | $current->mArticleID = $row->page_id; |
| 75 | $display = $current->getPrefixedText(); |
| 76 | $lower = $row->page_title; |
| 77 | $upper = $this->getServiceContainer()->getContentLanguage()->ucfirst( $row->page_title ); |
| 78 | if ( $upper == $lower ) { |
| 79 | $this->output( "\"$display\" already uppercase.\n" ); |
| 80 | |
| 81 | $this->progress( 0 ); |
| 82 | return; |
| 83 | } |
| 84 | |
| 85 | $target = Title::makeTitle( $row->page_namespace, $upper ); |
| 86 | if ( $target->exists() ) { |
| 87 | // Prefix "CapsCleanup" to bypass the conflict |
| 88 | $target = Title::newFromText( 'CapsCleanup/' . $display ); |
| 89 | } |
| 90 | $ok = $this->movePage( |
| 91 | $current, |
| 92 | // @phan-suppress-next-line PhanTypeMismatchArgumentNullable target is always valid |
| 93 | $target, |
| 94 | 'Converting page title to first-letter uppercase', |
| 95 | false |
| 96 | ); |
| 97 | if ( !$ok ) { |
| 98 | $this->progress( 0 ); |
| 99 | return; |
| 100 | } |
| 101 | |
| 102 | $this->progress( 1 ); |
| 103 | if ( $row->page_namespace == $this->namespace ) { |
| 104 | // We need to fetch the existence of the talk page from the DB directly because |
| 105 | // Title::exists will return false if Title::canExist returns false. Title::canExist will |
| 106 | // return false if $wgCapitalLinks is true and the old title is in lowercase form. |
| 107 | $namespaceInfo = $this->getServiceContainer()->getNamespaceInfo(); |
| 108 | if ( $namespaceInfo->canHaveTalkPage( $current ) ) { |
| 109 | $talkNamespace = $namespaceInfo->getTalk( $row->page_namespace ); |
| 110 | $talkPageExists = $this->getReplicaDB()->newSelectQueryBuilder() |
| 111 | ->select( '1' ) |
| 112 | ->from( 'page' ) |
| 113 | ->where( [ 'page_title' => $row->page_title, 'page_namespace' => $talkNamespace ] ) |
| 114 | ->caller( __METHOD__ ) |
| 115 | ->fetchField(); |
| 116 | if ( $talkPageExists ) { |
| 117 | $row->page_namespace = $talkNamespace; |
| 118 | $this->processRowToUppercase( $row ); |
| 119 | } |
| 120 | } |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | protected function processRowToLowercase( \stdClass $row ) { |
| 125 | $current = Title::makeTitle( $row->page_namespace, $row->page_title ); |
| 126 | $display = $current->getPrefixedText(); |
| 127 | $upper = $row->page_title; |
| 128 | $lower = $this->getServiceContainer()->getContentLanguage()->lcfirst( $row->page_title ); |
| 129 | if ( $upper == $lower ) { |
| 130 | $this->output( "\"$display\" already lowercase.\n" ); |
| 131 | |
| 132 | $this->progress( 0 ); |
| 133 | return; |
| 134 | } |
| 135 | |
| 136 | $target = Title::makeTitle( $row->page_namespace, $lower ); |
| 137 | if ( $target->exists() ) { |
| 138 | $targetDisplay = $target->getPrefixedText(); |
| 139 | $this->output( "\"$display\" skipped; \"$targetDisplay\" already exists\n" ); |
| 140 | |
| 141 | $this->progress( 0 ); |
| 142 | return; |
| 143 | } |
| 144 | |
| 145 | $ok = $this->movePage( $current, $target, 'Converting page titles to lowercase', true ); |
| 146 | if ( $ok !== true ) { |
| 147 | $this->progress( 0 ); |
| 148 | } |
| 149 | |
| 150 | $this->progress( 1 ); |
| 151 | if ( $row->page_namespace == $this->namespace ) { |
| 152 | $talk = $current->getTalkPage(); |
| 153 | if ( $talk->exists() ) { |
| 154 | $row->page_namespace = $talk->getNamespace(); |
| 155 | $this->processRowToLowercase( $row ); |
| 156 | } |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | /** |
| 161 | * @param Title $current |
| 162 | * @param Title $target |
| 163 | * @param string $reason |
| 164 | * @param bool $createRedirect |
| 165 | * @return bool Success |
| 166 | */ |
| 167 | private function movePage( Title $current, Title $target, $reason, $createRedirect ) { |
| 168 | $display = $current->getPrefixedText(); |
| 169 | $targetDisplay = $target->getPrefixedText(); |
| 170 | |
| 171 | if ( $this->dryrun ) { |
| 172 | $this->output( "\"$display\" -> \"$targetDisplay\": DRY RUN, NOT MOVED\n" ); |
| 173 | $ok = 'OK'; |
| 174 | } else { |
| 175 | $mp = $this->getServiceContainer()->getMovePageFactory() |
| 176 | ->newMovePage( $current, $target ); |
| 177 | $status = $mp->move( $this->user, $reason, $createRedirect ); |
| 178 | $ok = $status->isOK() ? 'OK' : 'FAILED'; |
| 179 | $this->output( "\"$display\" -> \"$targetDisplay\": $ok\n" ); |
| 180 | if ( !$status->isOK() ) { |
| 181 | $this->error( $status ); |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | return $ok === 'OK'; |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | // @codeCoverageIgnoreStart |
| 190 | $maintClass = CleanupCaps::class; |
| 191 | require_once RUN_MAINTENANCE_IF_MAIN; |
| 192 | // @codeCoverageIgnoreEnd |