MediaWiki master
namespaceDupes.php
Go to the documentation of this file.
1<?php
27// @codeCoverageIgnoreStart
28require_once __DIR__ . '/Maintenance.php';
29// @codeCoverageIgnoreEnd
30
41
49
54 private $resolvablePages = 0;
55
60 private $totalPages = 0;
61
66 private $resolvableLinks = 0;
67
72 private $totalLinks = 0;
73
79 private $deletedLinks = 0;
80
81 public function __construct() {
82 parent::__construct();
83 $this->addDescription( 'Find and fix pages affected by namespace addition/removal' );
84 $this->addOption( 'fix', 'Attempt to automatically fix errors and delete broken links' );
85 $this->addOption( 'merge', "Instead of renaming conflicts, do a history merge with " .
86 "the correct title" );
87 $this->addOption( 'add-suffix', "Dupes will be renamed with correct namespace with " .
88 "<text> appended after the article name", false, true );
89 $this->addOption( 'add-prefix', "Dupes will be renamed with correct namespace with " .
90 "<text> prepended before the article name", false, true );
91 $this->addOption( 'source-pseudo-namespace', "Move all pages with the given source " .
92 "prefix (with an implied colon following it). If --dest-namespace is not specified, " .
93 "the colon will be replaced with a hyphen.",
94 false, true );
95 $this->addOption( 'dest-namespace', "In combination with --source-pseudo-namespace, " .
96 "specify the namespace ID of the destination.", false, true );
97 $this->addOption( 'move-talk', "If this is specified, pages in the Talk namespace that " .
98 "begin with a conflicting prefix will be renamed, for example " .
99 "Talk:File:Foo -> File_Talk:Foo" );
100 }
101
102 public function execute() {
103 $options = [
104 'fix' => $this->hasOption( 'fix' ),
105 'merge' => $this->hasOption( 'merge' ),
106 'add-suffix' => $this->getOption( 'add-suffix', '' ),
107 'add-prefix' => $this->getOption( 'add-prefix', '' ),
108 'move-talk' => $this->hasOption( 'move-talk' ),
109 'source-pseudo-namespace' => $this->getOption( 'source-pseudo-namespace', '' ),
110 'dest-namespace' => intval( $this->getOption( 'dest-namespace', 0 ) )
111 ];
112
113 if ( $options['source-pseudo-namespace'] !== '' ) {
114 $retval = $this->checkPrefix( $options );
115 } else {
116 $retval = $this->checkAll( $options );
117 }
118
119 if ( $retval ) {
120 $this->output( "\nLooks good!\n" );
121 } else {
122 $this->output( "\nOh noeees\n" );
123 }
124 }
125
133 private function checkAll( $options ) {
134 $contLang = $this->getServiceContainer()->getContentLanguage();
135 $spaces = [];
136
137 // List interwikis first, so they'll be overridden
138 // by any conflicting local namespaces.
139 foreach ( $this->getInterwikiList() as $prefix ) {
140 $name = $contLang->ucfirst( $prefix );
141 $spaces[$name] = 0;
142 }
143
144 // Now pull in all canonical and alias namespaces...
145 foreach (
146 $this->getServiceContainer()->getNamespaceInfo()->getCanonicalNamespaces()
147 as $ns => $name
148 ) {
149 // This includes $wgExtraNamespaces
150 if ( $name !== '' ) {
151 $spaces[$name] = $ns;
152 }
153 }
154 foreach ( $contLang->getNamespaces() as $ns => $name ) {
155 if ( $name !== '' ) {
156 $spaces[$name] = $ns;
157 }
158 }
159 foreach ( $contLang->getNamespaceAliases() as $name => $ns ) {
160 $spaces[$name] = $ns;
161 }
162
163 // We'll need to check for lowercase keys as well,
164 // since we're doing case-sensitive searches in the db.
165 $capitalLinks = $this->getConfig()->get( MainConfigNames::CapitalLinks );
166 foreach ( $spaces as $name => $ns ) {
167 $moreNames = [];
168 $moreNames[] = $contLang->uc( $name );
169 $moreNames[] = $contLang->ucfirst( $contLang->lc( $name ) );
170 $moreNames[] = $contLang->ucwords( $name );
171 $moreNames[] = $contLang->ucwords( $contLang->lc( $name ) );
172 $moreNames[] = $contLang->ucwordbreaks( $name );
173 $moreNames[] = $contLang->ucwordbreaks( $contLang->lc( $name ) );
174 if ( !$capitalLinks ) {
175 foreach ( $moreNames as $altName ) {
176 $moreNames[] = $contLang->lcfirst( $altName );
177 }
178 $moreNames[] = $contLang->lcfirst( $name );
179 }
180 foreach ( array_unique( $moreNames ) as $altName ) {
181 if ( $altName !== $name ) {
182 $spaces[$altName] = $ns;
183 }
184 }
185 }
186
187 // Sort by namespace index, and if there are two with the same index,
188 // break the tie by sorting by name
189 $origSpaces = $spaces;
190 uksort( $spaces, static function ( $a, $b ) use ( $origSpaces ) {
191 return $origSpaces[$a] <=> $origSpaces[$b]
192 ?: $a <=> $b;
193 } );
194
195 $ok = true;
196 foreach ( $spaces as $name => $ns ) {
197 $ok = $this->checkNamespace( $ns, $name, $options ) && $ok;
198 }
199
200 $this->output(
201 "{$this->totalPages} pages to fix, " .
202 "{$this->resolvablePages} were resolvable.\n\n"
203 );
204
205 foreach ( $spaces as $name => $ns ) {
206 if ( $ns != 0 ) {
207 /* Fix up link destinations for non-interwiki links only.
208 *
209 * For example if a page has [[Foo:Bar]] and then a Foo namespace
210 * is introduced, pagelinks needs to be updated to have
211 * page_namespace = NS_FOO.
212 *
213 * If instead an interwiki prefix was introduced called "Foo",
214 * the link should instead be moved to the iwlinks table. If a new
215 * language is introduced called "Foo", or if there is a pagelink
216 * [[fr:Bar]] when interlanguage magic links are turned on, the
217 * link would have to be moved to the langlinks table. Let's put
218 * those cases in the too-hard basket for now. The consequences are
219 * not especially severe.
220 * @fixme Handle interwiki links, and pagelinks to Category:, File:
221 * which probably need reparsing.
222 */
223
224 $this->checkLinkTable( 'pagelinks', 'pl', $ns, $name, $options );
225 $this->checkLinkTable( 'templatelinks', 'tl', $ns, $name, $options );
226
227 // The redirect table has interwiki links randomly mixed in, we
228 // need to filter those out. For example [[w:Foo:Bar]] would
229 // have rd_interwiki=w and rd_namespace=0, which would match the
230 // query for a conflicting namespace "Foo" if filtering wasn't done.
231 $this->checkLinkTable( 'redirect', 'rd', $ns, $name, $options,
232 [ 'rd_interwiki' => '' ] );
233 }
234 }
235
236 $this->output(
237 "{$this->totalLinks} links to fix, " .
238 "{$this->resolvableLinks} were resolvable, " .
239 "{$this->deletedLinks} were deleted.\n"
240 );
241
242 return $ok;
243 }
244
248 private function getInterwikiList() {
249 $result = $this->getServiceContainer()->getInterwikiLookup()->getAllPrefixes();
250 return array_column( $result, 'iw_prefix' );
251 }
252
261 private function checkNamespace( $ns, $name, $options ) {
262 $targets = $this->getTargetList( $ns, $name, $options );
263 $count = $targets->numRows();
264 $this->totalPages += $count;
265 if ( $count == 0 ) {
266 return true;
267 }
268
269 $dryRunNote = $options['fix'] ? '' : ' DRY RUN ONLY';
270
271 $ok = true;
272 foreach ( $targets as $row ) {
273 // Find the new title and determine the action to take
274
275 $newTitle = $this->getDestinationTitle(
276 $ns, $name, $row->page_namespace, $row->page_title );
277 $logStatus = false;
278 if ( !$newTitle ) {
279 if ( $options['add-prefix'] == '' && $options['add-suffix'] == '' ) {
280 $logStatus = 'invalid title and --add-prefix not specified';
281 $action = 'abort';
282 } else {
283 $action = 'alternate';
284 }
285 } elseif ( $newTitle->exists( IDBAccessObject::READ_LATEST ) ) {
286 if ( $options['merge'] ) {
287 if ( $this->canMerge( $row->page_id, $newTitle, $logStatus ) ) {
288 $action = 'merge';
289 } else {
290 $action = 'abort';
291 }
292 } elseif ( $options['add-prefix'] == '' && $options['add-suffix'] == '' ) {
293 $action = 'abort';
294 $logStatus = 'dest title exists and --add-prefix not specified';
295 } else {
296 $action = 'alternate';
297 }
298 } else {
299 $action = 'move';
300 $logStatus = 'no conflict';
301 }
302 if ( $action === 'alternate' ) {
303 [ $ns, $dbk ] = $this->getDestination( $ns, $name, $row->page_namespace,
304 $row->page_title );
305 $newTitle = $this->getAlternateTitle( $ns, $dbk, $options );
306 if ( !$newTitle ) {
307 $action = 'abort';
308 $logStatus = 'alternate title is invalid';
309 } elseif ( $newTitle->exists() ) {
310 $action = 'abort';
311 $logStatus = 'alternate title conflicts';
312 } else {
313 $action = 'move';
314 $logStatus = 'alternate';
315 }
316 }
317
318 // Take the action or log a dry run message
319
320 $logTitle = "id={$row->page_id} ns={$row->page_namespace} dbk={$row->page_title}";
321 $pageOK = true;
322
323 switch ( $action ) {
324 case 'abort':
325 $this->output( "$logTitle *** $logStatus\n" );
326 $pageOK = false;
327 break;
328 case 'move':
329 $this->output( "$logTitle -> " .
330 $newTitle->getPrefixedDBkey() . " ($logStatus)$dryRunNote\n" );
331
332 if ( $options['fix'] ) {
333 $pageOK = $this->movePage( $row->page_id, $newTitle );
334 }
335 break;
336 case 'merge':
337 $this->output( "$logTitle => " .
338 $newTitle->getPrefixedDBkey() . " (merge)$dryRunNote\n" );
339
340 if ( $options['fix'] ) {
341 $pageOK = $this->mergePage( $row, $newTitle );
342 }
343 break;
344 }
345
346 if ( $pageOK ) {
347 $this->resolvablePages++;
348 } else {
349 $ok = false;
350 }
351 }
352
353 return $ok;
354 }
355
365 private function checkLinkTable( $table, $fieldPrefix, $ns, $name, $options,
366 $extraConds = []
367 ) {
368 $dbw = $this->getPrimaryDB();
369
370 $batchConds = [];
371 $fromField = "{$fieldPrefix}_from";
372 $batchSize = 100;
373 $sqb = $dbw->newSelectQueryBuilder()
374 ->select( $fromField )
375 ->where( $extraConds )
376 ->limit( $batchSize );
377
378 $linksMigration = $this->getServiceContainer()->getLinksMigration();
379 if ( isset( $linksMigration::$mapping[$table] ) ) {
380 $sqb->queryInfo( $linksMigration->getQueryInfo( $table ) );
381 [ $namespaceField, $titleField ] = $linksMigration->getTitleFields( $table );
382 $schemaMigrationStage = $linksMigration::$mapping[$table]['config'] === -1
384 : $this->getConfig()->get( $linksMigration::$mapping[$table]['config'] );
385 $linkTargetLookup = $this->getServiceContainer()->getLinkTargetLookup();
386 $targetIdField = $linksMigration::$mapping[$table]['target_id'];
387 } else {
388 $sqb->table( $table );
389 $namespaceField = "{$fieldPrefix}_namespace";
390 $titleField = "{$fieldPrefix}_title";
391 $sqb->fields( [ $namespaceField, $titleField ] );
392 // Variables only used for links migration, init only
393 $schemaMigrationStage = -1;
394 $linkTargetLookup = null;
395 $targetIdField = '';
396 }
397 $sqb->andWhere( [
398 $namespaceField => 0,
399 $dbw->expr( $titleField, IExpression::LIKE, new LikeValue( "$name:", $dbw->anyString() ) ),
400 ] )
401 ->orderBy( [ $titleField, $fromField ] )
402 ->caller( __METHOD__ );
403
404 $updateRowsPerQuery = $this->getConfig()->get( MainConfigNames::UpdateRowsPerQuery );
405 while ( true ) {
406 $res = ( clone $sqb )
407 ->andWhere( $batchConds )
408 ->fetchResultSet();
409 if ( $res->numRows() == 0 ) {
410 break;
411 }
412
413 $rowsToDeleteIfStillExists = [];
414
415 foreach ( $res as $row ) {
416 $logTitle = "from={$row->$fromField} ns={$row->$namespaceField} " .
417 "dbk={$row->$titleField}";
418 $destTitle = $this->getDestinationTitle(
419 $ns, $name, $row->$namespaceField, $row->$titleField );
420 $this->totalLinks++;
421 if ( !$destTitle ) {
422 $this->output( "$table $logTitle *** INVALID\n" );
423 continue;
424 }
425 $this->resolvableLinks++;
426 if ( !$options['fix'] ) {
427 $this->output( "$table $logTitle -> " .
428 $destTitle->getPrefixedDBkey() . " DRY RUN\n" );
429 continue;
430 }
431
432 if ( isset( $linksMigration::$mapping[$table] ) ) {
433 $setValue = [];
434 if ( $schemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) {
435 $setValue[$targetIdField] = $linkTargetLookup->acquireLinkTargetId( $destTitle, $dbw );
436 }
437 if ( $schemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) {
438 $setValue["{$fieldPrefix}_namespace"] = $destTitle->getNamespace();
439 $setValue["{$fieldPrefix}_title"] = $destTitle->getDBkey();
440 }
441 $whereCondition = $linksMigration->getLinksConditions(
442 $table,
443 new TitleValue( 0, $row->$titleField )
444 );
445 $deleteCondition = $linksMigration->getLinksConditions(
446 $table,
447 new TitleValue( (int)$row->$namespaceField, $row->$titleField )
448 );
449 } else {
450 $setValue = [
451 $namespaceField => $destTitle->getNamespace(),
452 $titleField => $destTitle->getDBkey()
453 ];
454 $whereCondition = [
455 $namespaceField => 0,
456 $titleField => $row->$titleField
457 ];
458 $deleteCondition = [
459 $namespaceField => $row->$namespaceField,
460 $titleField => $row->$titleField,
461 ];
462 }
463
464 $dbw->newUpdateQueryBuilder()
465 ->update( $table )
466 ->ignore()
467 ->set( $setValue )
468 ->where( [ $fromField => $row->$fromField ] )
469 ->andWhere( $whereCondition )
470 ->caller( __METHOD__ )
471 ->execute();
472
473 // In case there is a key conflict on UPDATE IGNORE the row needs deletion
474 $rowsToDeleteIfStillExists[] = array_merge( [ $fromField => $row->$fromField ], $deleteCondition );
475
476 $this->output( "$table $logTitle -> " .
477 $destTitle->getPrefixedDBkey() . "\n"
478 );
479 }
480
481 if ( $options['fix'] && count( $rowsToDeleteIfStillExists ) > 0 ) {
482 $affectedRows = 0;
483 $deleteBatches = array_chunk( $rowsToDeleteIfStillExists, $updateRowsPerQuery );
484 foreach ( $deleteBatches as $deleteBatch ) {
485 $dbw->newDeleteQueryBuilder()
486 ->deleteFrom( $table )
487 ->where( $dbw->factorConds( $deleteBatch ) )
488 ->caller( __METHOD__ )
489 ->execute();
490 $affectedRows += $dbw->affectedRows();
491 if ( count( $deleteBatches ) > 1 ) {
492 $this->waitForReplication();
493 }
494 }
495
496 $this->deletedLinks += $affectedRows;
497 $this->resolvableLinks -= $affectedRows;
498 }
499
500 $batchConds = [
501 $dbw->buildComparison( '>', [
502 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable rows contains at least one item
503 $titleField => $row->$titleField,
504 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable rows contains at least one item
505 $fromField => $row->$fromField,
506 ] )
507 ];
508
509 $this->waitForReplication();
510 }
511 }
512
520 private function checkPrefix( $options ) {
521 $prefix = $options['source-pseudo-namespace'];
522 $ns = $options['dest-namespace'];
523 $this->output( "Checking prefix \"$prefix\" vs namespace $ns\n" );
524
525 return $this->checkNamespace( $ns, $prefix, $options );
526 }
527
538 private function getTargetList( $ns, $name, $options ) {
539 $dbw = $this->getPrimaryDB();
540
541 if (
542 $options['move-talk'] &&
543 $this->getServiceContainer()->getNamespaceInfo()->isSubject( $ns )
544 ) {
545 $checkNamespaces = [ NS_MAIN, NS_TALK ];
546 } else {
547 $checkNamespaces = NS_MAIN;
548 }
549
550 return $dbw->newSelectQueryBuilder()
551 ->select( [ 'page_id', 'page_title', 'page_namespace' ] )
552 ->from( 'page' )
553 ->where( [
554 'page_namespace' => $checkNamespaces,
555 $dbw->expr( 'page_title', IExpression::LIKE, new LikeValue( "$name:", $dbw->anyString() ) ),
556 ] )
557 ->caller( __METHOD__ )->fetchResultSet();
558 }
559
568 private function getDestination( $ns, $name, $sourceNs, $sourceDbk ) {
569 $dbk = substr( $sourceDbk, strlen( "$name:" ) );
570 if ( $ns <= 0 ) {
571 // An interwiki or an illegal namespace like "Special" or "Media"
572 // try an alternate encoding with '-' for ':'
573 $dbk = "$name-" . $dbk;
574 $ns = 0;
575 }
576 $destNS = $ns;
577 $nsInfo = $this->getServiceContainer()->getNamespaceInfo();
578 if ( $sourceNs == NS_TALK && $nsInfo->isSubject( $ns ) ) {
579 // This is an associated talk page moved with the --move-talk feature.
580 $destNS = $nsInfo->getTalk( $destNS );
581 }
582 return [ $destNS, $dbk ];
583 }
584
593 private function getDestinationTitle( $ns, $name, $sourceNs, $sourceDbk ) {
594 [ $destNS, $dbk ] = $this->getDestination( $ns, $name, $sourceNs, $sourceDbk );
595 $newTitle = Title::makeTitleSafe( $destNS, $dbk );
596 if ( !$newTitle || !$newTitle->canExist() ) {
597 return false;
598 }
599 return $newTitle;
600 }
601
611 private function getAlternateTitle( $ns, $dbk, $options ) {
612 $prefix = $options['add-prefix'];
613 $suffix = $options['add-suffix'];
614 if ( $prefix == '' && $suffix == '' ) {
615 return false;
616 }
617 $newDbk = $prefix . $dbk . $suffix;
618 return Title::makeTitleSafe( $ns, $newDbk );
619 }
620
628 private function movePage( $id, LinkTarget $newLinkTarget ) {
629 $dbw = $this->getPrimaryDB();
630
631 $dbw->newUpdateQueryBuilder()
632 ->update( 'page' )
633 ->set( [
634 "page_namespace" => $newLinkTarget->getNamespace(),
635 "page_title" => $newLinkTarget->getDBkey(),
636 ] )
637 ->where( [
638 "page_id" => $id,
639 ] )
640 ->caller( __METHOD__ )
641 ->execute();
642
643 // Update *_from_namespace in links tables
644 $fromNamespaceTables = [
645 [ 'templatelinks', 'tl', [ 'tl_target_id' ] ],
646 [ 'imagelinks', 'il', [ 'il_to' ] ]
647 ];
648 if ( $this->getConfig()->get( MainConfigNames::PageLinksSchemaMigrationStage ) & SCHEMA_COMPAT_WRITE_OLD ) {
649 $fromNamespaceTables[] = [ 'pagelinks', 'pl', [ 'pl_namespace', 'pl_title' ] ];
650 } else {
651 $fromNamespaceTables[] = [ 'pagelinks', 'pl', [ 'pl_target_id' ] ];
652 }
653 $updateRowsPerQuery = $this->getConfig()->get( MainConfigNames::UpdateRowsPerQuery );
654 foreach ( $fromNamespaceTables as [ $table, $fieldPrefix, $additionalPrimaryKeyFields ] ) {
655 $fromField = "{$fieldPrefix}_from";
656 $fromNamespaceField = "{$fieldPrefix}_from_namespace";
657
658 $res = $dbw->newSelectQueryBuilder()
659 ->select( $additionalPrimaryKeyFields )
660 ->from( $table )
661 ->where( [ $fromField => $id ] )
662 ->andWhere( $dbw->expr( $fromNamespaceField, '!=', $newLinkTarget->getNamespace() ) )
663 ->caller( __METHOD__ )
664 ->fetchResultSet();
665 if ( !$res ) {
666 continue;
667 }
668
669 $updateConds = [];
670 foreach ( $res as $row ) {
671 $updateConds[] = array_merge( [ $fromField => $id ], (array)$row );
672 }
673 $updateBatches = array_chunk( $updateConds, $updateRowsPerQuery );
674 foreach ( $updateBatches as $updateBatch ) {
675 $dbw->newUpdateQueryBuilder()
676 ->update( $table )
677 ->set( [ $fromNamespaceField => $newLinkTarget->getNamespace() ] )
678 ->where( $dbw->factorConds( $updateBatch ) )
679 ->caller( __METHOD__ )
680 ->execute();
681 if ( count( $updateBatches ) > 1 ) {
682 $this->waitForReplication();
683 }
684 }
685 }
686
687 return true;
688 }
689
702 private function canMerge( $id, LinkTarget $linkTarget, &$logStatus ) {
703 $revisionLookup = $this->getServiceContainer()->getRevisionLookup();
704 $latestDest = $revisionLookup->getRevisionByTitle( $linkTarget, 0,
705 IDBAccessObject::READ_LATEST );
706 $latestSource = $revisionLookup->getRevisionByPageId( $id, 0,
707 IDBAccessObject::READ_LATEST );
708 if ( $latestSource->getTimestamp() > $latestDest->getTimestamp() ) {
709 $logStatus = 'cannot merge since source is later';
710 return false;
711 } else {
712 return true;
713 }
714 }
715
723 private function mergePage( $row, Title $newTitle ) {
724 $dbw = $this->getPrimaryDB();
725 $updateRowsPerQuery = $this->getConfig()->get( MainConfigNames::UpdateRowsPerQuery );
726
727 $id = $row->page_id;
728
729 // Construct the WikiPage object we will need later, while the
730 // page_id still exists. Note that this cannot use makeTitleSafe(),
731 // we are deliberately constructing an invalid title.
732 $sourceTitle = Title::makeTitle( $row->page_namespace, $row->page_title );
733 $sourceTitle->resetArticleID( $id );
734 $wikiPage = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $sourceTitle );
735 $wikiPage->loadPageData( IDBAccessObject::READ_LATEST );
736
737 $destId = $newTitle->getArticleID();
738 $this->beginTransaction( $dbw, __METHOD__ );
739 $revIds = $dbw->newSelectQueryBuilder()
740 ->select( 'rev_id' )
741 ->from( 'revision' )
742 ->where( [ 'rev_page' => $id ] )
743 ->caller( __METHOD__ )
744 ->fetchFieldValues();
745 $updateBatches = array_chunk( array_map( 'intval', $revIds ), $updateRowsPerQuery );
746 foreach ( $updateBatches as $updateBatch ) {
747 $dbw->newUpdateQueryBuilder()
748 ->update( 'revision' )
749 ->set( [ 'rev_page' => $destId ] )
750 ->where( [ 'rev_id' => $updateBatch ] )
751 ->caller( __METHOD__ )
752 ->execute();
753 if ( count( $updateBatches ) > 1 ) {
754 $this->waitForReplication();
755 }
756 }
757
758 $dbw->newDeleteQueryBuilder()
759 ->deleteFrom( 'page' )
760 ->where( [ 'page_id' => $id ] )
761 ->caller( __METHOD__ )
762 ->execute();
763
764 $this->commitTransaction( $dbw, __METHOD__ );
765
766 /* Call LinksDeletionUpdate to delete outgoing links from the old title,
767 * and update category counts.
768 *
769 * Calling external code with a fake broken Title is a fairly dubious
770 * idea. It's necessary because it's quite a lot of code to duplicate,
771 * but that also makes it fragile since it would be easy for someone to
772 * accidentally introduce an assumption of title validity to the code we
773 * are calling.
774 */
775 DeferredUpdates::addUpdate( new LinksDeletionUpdate( $wikiPage ) );
776 DeferredUpdates::doUpdates();
777
778 return true;
779 }
780}
781
782// @codeCoverageIgnoreStart
783$maintClass = NamespaceDupes::class;
784require_once RUN_MAINTENANCE_IF_MAIN;
785// @codeCoverageIgnoreEnd
const SCHEMA_COMPAT_WRITE_OLD
Definition Defines.php:276
const NS_MAIN
Definition Defines.php:65
const MIGRATION_NEW
Definition Defines.php:317
const SCHEMA_COMPAT_WRITE_NEW
Definition Defines.php:280
const NS_TALK
Definition Defines.php:66
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
beginTransaction(IDatabase $dbw, $fname)
Begin a transaction on a DB.
commitTransaction(IDatabase $dbw, $fname)
Commit the transaction on a DB handle and wait for replica DBs to catch up.
output( $out, $channel=null)
Throw some output to the user.
waitForReplication()
Wait for replica DBs to catch up.
hasOption( $name)
Checks to see if a particular option was set.
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.
Defer callable updates to run later in the PHP process.
Update object handling the cleanup of links tables after a page was deleted.
A class containing constants representing the names of configuration variables.
Represents the target of a wiki link.
Represents a title within MediaWiki.
Definition Title.php:78
canExist()
Can this title represent a page in the wiki's database?
Definition Title.php:1211
exists( $flags=0)
Check if page exists.
Definition Title.php:3138
getArticleID( $flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition Title.php:2585
getPrefixedDBkey()
Get the prefixed database key form.
Definition Title.php:1846
Maintenance script that checks for articles to fix after adding/deleting namespaces.
execute()
Do the actual work.
__construct()
Default constructor.
Content of like value.
Definition LikeValue.php:14
Represents the target of a wiki link.
getNamespace()
Get the namespace index.
getDBkey()
Get the main part of the link target, in canonical database form.
Interface for database access objects.
Result wrapper for grabbing data queried from an IDatabase object.
$maintClass