30require_once __DIR__ .
'/TableCleanup.php';
39 private string $prefix;
42 parent::__construct();
43 $this->
addDescription(
'Script to clean up broken, unparseable titles' );
44 $this->
addOption(
'prefix',
"Broken pages will be renamed to titles with " .
45 "<prefix> prepended before the article name. Defaults to 'Broken'",
false,
true );
53 $this->prefix = $this->
getOption(
'prefix',
'Broken' ) .
"/";
59 $title = Title::newFromText( $this->prefix );
60 if ( !$title || !$title->canExist() || $title->getInterwiki() || $title->getNamespace() !== 0 ) {
61 $this->
fatalError(
"Invalid prefix {$this->prefix}. Must be a valid mainspace title." );
70 $display = Title::makeName( $row->page_namespace, $row->page_title );
72 $title = Title::newFromText( $verified );
76 && $title->getNamespace() == $row->page_namespace
77 && $title->getDBkey() === $row->page_title
85 if ( $row->page_namespace ==
NS_FILE && $this->fileExists( $row->page_title ) ) {
86 $this->
output(
"file $row->page_title needs cleanup, please run cleanupImages.php.\n" );
88 } elseif ( $title ===
null ) {
89 $this->
output(
"page $row->page_id ($display) is illegal.\n" );
93 $this->
output(
"page $row->page_id ($display) doesn't match self.\n" );
107 $row = $dbr->newSelectQueryBuilder()
110 ->where( [
'img_name' => $name ] )
111 ->caller( __METHOD__ )
114 return $row !==
false;
121 $legalChars = Title::legalChars();
122 $legalizedUnprefixed = preg_replace_callback(
"/([^$legalChars])/",
123 [ $this,
'hexChar' ],
125 if ( $legalizedUnprefixed ==
'.' ) {
126 $legalizedUnprefixed =
'(dot)';
128 if ( $legalizedUnprefixed ==
'_' ) {
129 $legalizedUnprefixed =
'(space)';
131 $ns = (int)$row->page_namespace;
135 $namespaceName = $namespaceInfo->getCanonicalName( $ns );
136 if ( $namespaceName ===
false ) {
137 $namespaceName =
"NS$ns";
139 $legalizedUnprefixed =
"$namespaceName:$legalizedUnprefixed";
141 $legalized = $this->prefix . $legalizedUnprefixed;
143 $title = Title::newFromText( $legalized );
145 if ( $title ===
null ) {
150 $legalizedUnprefixed = preg_replace_callback(
'!([^A-Za-z0-9_\\-:])!',
151 [ $this,
'hexChar' ],
154 $legalized = $this->prefix . $legalizedUnprefixed;
155 $title = Title::newFromText( $legalized );
158 if ( $title ===
null ) {
160 $clean = $this->prefix .
'id:' . $row->page_id;
161 $this->
output(
"Couldn't legalize; form '$legalized' still invalid; using '$clean'\n" );
162 $title = Title::newFromText( $clean );
163 } elseif ( $title->exists() ) {
164 $clean = $this->prefix .
'id:' . $row->page_id;
165 $this->
output(
"Legalized for '$legalized' exists; using '$clean'\n" );
166 $title = Title::newFromText( $clean );
169 if ( !$title || $title->exists() ) {
173 $this->
error(
"Destination page {$title->getText()} is invalid or already exists, skipping." );
177 $dest = $title->getDBkey();
178 if ( $this->dryrun ) {
179 $this->
output(
"DRY RUN: would rename $row->page_id ($row->page_namespace," .
180 "'$row->page_title') to (0,'$dest')\n" );
182 $this->
output(
"renaming $row->page_id ($row->page_namespace," .
183 "'$row->page_title') to ($row->page_namespace,'$dest')\n" );
185 ->newUpdateQueryBuilder()
187 ->set( [
'page_title' => $dest,
'page_namespace' => 0 ] )
188 ->where( [
'page_id' => $row->page_id ] )
189 ->caller( __METHOD__ )->execute();
199 if ( $title->
exists( IDBAccessObject::READ_LATEST ) || $titleImpossible ) {
200 if ( $titleImpossible ) {
206 $ns = (int)$row->page_namespace;
207 # If a page is saved in the main namespace with a namespace prefix then try to move it into
208 # that namespace. If there's no conflict then it will succeed. Otherwise it will hit the condition
209 # } else if ($ns !== 0) { and be moved to Broken/Namespace:Title
210 # whereas without this check it would just go to Broken/Title
215 # Old cleanupTitles could move articles there. See T25147.
216 # or a page could be stored as (0, "Special:Foo") in which case the $titleImpossible
217 # condition would be true and we've already added a prefix so pretend we're in mainspace
218 # and don't add another
223 # Namespace which no longer exists. Put the page in the main namespace
224 # since we don't have any idea of the old namespace name. See T70501.
225 # We build the new title ourself rather than relying on getDBKey() because
226 # that will return Special:BadTitle
228 if ( !$namespaceInfo->exists( $ns ) ) {
229 $clean =
"{$this->prefix}NS$ns:$row->page_title";
231 } elseif ( !$titleImpossible && !$title->
exists() ) {
234 } elseif ( $ns !== 0 ) {
236 $nsName = $namespaceInfo->getCanonicalName( $ns );
237 $clean =
"{$this->prefix}$nsName:{$prior}";
240 $clean = $this->prefix . $prior;
242 $verified = Title::makeTitleSafe( $ns, $clean );
243 if ( !$verified || $verified->exists() ) {
244 $lastResort =
"{$this->prefix}id: {$row->page_id}";
245 $this->
output(
"Couldn't legalize; form '$clean' exists; using '$lastResort'\n" );
246 $verified = Title::makeTitleSafe( $ns, $lastResort );
247 if ( !$verified || $verified->exists() ) {
251 $this->
error(
"Destination page $lastResort invalid or already exists." );
261 if ( $this->dryrun ) {
262 $this->
output(
"DRY RUN: would rename $row->page_id ($row->page_namespace," .
263 "'$row->page_title') to ($ns,'$dest')\n" );
265 $this->
output(
"renaming $row->page_id ($row->page_namespace," .
266 "'$row->page_title') to ($ns,'$dest')\n" );
268 ->newUpdateQueryBuilder()
271 'page_namespace' => $ns,
272 'page_title' => $dest
274 ->where( [
'page_id' => $row->page_id ] )
275 ->caller( __METHOD__ )->execute();
282require_once RUN_MAINTENANCE_IF_MAIN;
error( $err, $die=0)
Throw an error to the user.
output( $out, $channel=null)
Throw some output to the user.
getServiceContainer()
Returns the main service container.
addDescription( $text)
Set the description text.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
getOption( $name, $default=null)
Get an option, or return the default.
fatalError( $msg, $exitCode=1)
Output a message and terminate the current script.
Generic class to cleanup a database table.
Maintenance script to clean up broken, unparseable titles.
moveInconsistentPage( $row, Title $title)
__construct()
Default constructor.
execute()
Do the actual work.All child classes will need to implement thisbool|null|void True for success,...