MediaWiki master
namespaceDupes.php
Go to the documentation of this file.
1<?php
27require_once __DIR__ . '/Maintenance.php';
28
38
46
51 private $resolvablePages = 0;
52
57 private $totalPages = 0;
58
63 private $resolvableLinks = 0;
64
69 private $totalLinks = 0;
70
76 private $deletedLinks = 0;
77
78 public function __construct() {
79 parent::__construct();
80 $this->addDescription( 'Find and fix pages affected by namespace addition/removal' );
81 $this->addOption( 'fix', 'Attempt to automatically fix errors and delete broken links' );
82 $this->addOption( 'merge', "Instead of renaming conflicts, do a history merge with " .
83 "the correct title" );
84 $this->addOption( 'add-suffix', "Dupes will be renamed with correct namespace with " .
85 "<text> appended after the article name", false, true );
86 $this->addOption( 'add-prefix', "Dupes will be renamed with correct namespace with " .
87 "<text> prepended before the article name", false, true );
88 $this->addOption( 'source-pseudo-namespace', "Move all pages with the given source " .
89 "prefix (with an implied colon following it). If --dest-namespace is not specified, " .
90 "the colon will be replaced with a hyphen.",
91 false, true );
92 $this->addOption( 'dest-namespace', "In combination with --source-pseudo-namespace, " .
93 "specify the namespace ID of the destination.", false, true );
94 $this->addOption( 'move-talk', "If this is specified, pages in the Talk namespace that " .
95 "begin with a conflicting prefix will be renamed, for example " .
96 "Talk:File:Foo -> File_Talk:Foo" );
97 }
98
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 $this->fatalError( "Unsafe to run at this time. See: T364546\n" );
114
115 if ( $options['source-pseudo-namespace'] !== '' ) {
116 $retval = $this->checkPrefix( $options );
117 } else {
118 $retval = $this->checkAll( $options );
119 }
120
121 if ( $retval ) {
122 $this->output( "\nLooks good!\n" );
123 } else {
124 $this->output( "\nOh noeees\n" );
125 }
126 }
127
135 private function checkAll( $options ) {
136 $contLang = $this->getServiceContainer()->getContentLanguage();
137 $spaces = [];
138
139 // List interwikis first, so they'll be overridden
140 // by any conflicting local namespaces.
141 foreach ( $this->getInterwikiList() as $prefix ) {
142 $name = $contLang->ucfirst( $prefix );
143 $spaces[$name] = 0;
144 }
145
146 // Now pull in all canonical and alias namespaces...
147 foreach (
148 $this->getServiceContainer()->getNamespaceInfo()->getCanonicalNamespaces()
149 as $ns => $name
150 ) {
151 // This includes $wgExtraNamespaces
152 if ( $name !== '' ) {
153 $spaces[$name] = $ns;
154 }
155 }
156 foreach ( $contLang->getNamespaces() as $ns => $name ) {
157 if ( $name !== '' ) {
158 $spaces[$name] = $ns;
159 }
160 }
161 foreach ( $contLang->getNamespaceAliases() as $name => $ns ) {
162 $spaces[$name] = $ns;
163 }
164
165 // We'll need to check for lowercase keys as well,
166 // since we're doing case-sensitive searches in the db.
167 $capitalLinks = $this->getConfig()->get( MainConfigNames::CapitalLinks );
168 foreach ( $spaces as $name => $ns ) {
169 $moreNames = [];
170 $moreNames[] = $contLang->uc( $name );
171 $moreNames[] = $contLang->ucfirst( $contLang->lc( $name ) );
172 $moreNames[] = $contLang->ucwords( $name );
173 $moreNames[] = $contLang->ucwords( $contLang->lc( $name ) );
174 $moreNames[] = $contLang->ucwordbreaks( $name );
175 $moreNames[] = $contLang->ucwordbreaks( $contLang->lc( $name ) );
176 if ( !$capitalLinks ) {
177 foreach ( $moreNames as $altName ) {
178 $moreNames[] = $contLang->lcfirst( $altName );
179 }
180 $moreNames[] = $contLang->lcfirst( $name );
181 }
182 foreach ( array_unique( $moreNames ) as $altName ) {
183 if ( $altName !== $name ) {
184 $spaces[$altName] = $ns;
185 }
186 }
187 }
188
189 // Sort by namespace index, and if there are two with the same index,
190 // break the tie by sorting by name
191 $origSpaces = $spaces;
192 uksort( $spaces, static function ( $a, $b ) use ( $origSpaces ) {
193 return $origSpaces[$a] <=> $origSpaces[$b]
194 ?: $a <=> $b;
195 } );
196
197 $ok = true;
198 foreach ( $spaces as $name => $ns ) {
199 $ok = $this->checkNamespace( $ns, $name, $options ) && $ok;
200 }
201
202 $this->output(
203 "{$this->totalPages} pages to fix, " .
204 "{$this->resolvablePages} were resolvable.\n\n"
205 );
206
207 foreach ( $spaces as $name => $ns ) {
208 if ( $ns != 0 ) {
209 /* Fix up link destinations for non-interwiki links only.
210 *
211 * For example if a page has [[Foo:Bar]] and then a Foo namespace
212 * is introduced, pagelinks needs to be updated to have
213 * page_namespace = NS_FOO.
214 *
215 * If instead an interwiki prefix was introduced called "Foo",
216 * the link should instead be moved to the iwlinks table. If a new
217 * language is introduced called "Foo", or if there is a pagelink
218 * [[fr:Bar]] when interlanguage magic links are turned on, the
219 * link would have to be moved to the langlinks table. Let's put
220 * those cases in the too-hard basket for now. The consequences are
221 * not especially severe.
222 * @fixme Handle interwiki links, and pagelinks to Category:, File:
223 * which probably need reparsing.
224 */
225
226 $this->checkLinkTable( 'pagelinks', 'pl', $ns, $name, $options );
227 $this->checkLinkTable( 'templatelinks', 'tl', $ns, $name, $options );
228
229 // The redirect table has interwiki links randomly mixed in, we
230 // need to filter those out. For example [[w:Foo:Bar]] would
231 // have rd_interwiki=w and rd_namespace=0, which would match the
232 // query for a conflicting namespace "Foo" if filtering wasn't done.
233 $this->checkLinkTable( 'redirect', 'rd', $ns, $name, $options,
234 [ 'rd_interwiki' => '' ] );
235 }
236 }
237
238 $this->output(
239 "{$this->totalLinks} links to fix, " .
240 "{$this->resolvableLinks} were resolvable, " .
241 "{$this->deletedLinks} were deleted.\n"
242 );
243
244 return $ok;
245 }
246
250 private function getInterwikiList() {
251 $result = $this->getServiceContainer()->getInterwikiLookup()->getAllPrefixes();
252 return array_column( $result, 'iw_prefix' );
253 }
254
263 private function checkNamespace( $ns, $name, $options ) {
264 $targets = $this->getTargetList( $ns, $name, $options );
265 $count = $targets->numRows();
266 $this->totalPages += $count;
267 if ( $count == 0 ) {
268 return true;
269 }
270
271 $dryRunNote = $options['fix'] ? '' : ' DRY RUN ONLY';
272
273 $ok = true;
274 foreach ( $targets as $row ) {
275 // Find the new title and determine the action to take
276
277 $newTitle = $this->getDestinationTitle(
278 $ns, $name, $row->page_namespace, $row->page_title );
279 $logStatus = false;
280 if ( !$newTitle ) {
281 if ( $options['add-prefix'] == '' && $options['add-suffix'] == '' ) {
282 $logStatus = 'invalid title and --add-prefix not specified';
283 $action = 'abort';
284 } else {
285 $action = 'alternate';
286 }
287 } elseif ( $newTitle->exists() ) {
288 if ( $options['merge'] ) {
289 if ( $this->canMerge( $row->page_id, $newTitle, $logStatus ) ) {
290 $action = 'merge';
291 } else {
292 $action = 'abort';
293 }
294 } elseif ( $options['add-prefix'] == '' && $options['add-suffix'] == '' ) {
295 $action = 'abort';
296 $logStatus = 'dest title exists and --add-prefix not specified';
297 } else {
298 $action = 'alternate';
299 }
300 } else {
301 $action = 'move';
302 $logStatus = 'no conflict';
303 }
304 if ( $action === 'alternate' ) {
305 [ $ns, $dbk ] = $this->getDestination( $ns, $name, $row->page_namespace,
306 $row->page_title );
307 $newTitle = $this->getAlternateTitle( $ns, $dbk, $options );
308 if ( !$newTitle ) {
309 $action = 'abort';
310 $logStatus = 'alternate title is invalid';
311 } elseif ( $newTitle->exists() ) {
312 $action = 'abort';
313 $logStatus = 'alternate title conflicts';
314 } else {
315 $action = 'move';
316 $logStatus = 'alternate';
317 }
318 }
319
320 // Take the action or log a dry run message
321
322 $logTitle = "id={$row->page_id} ns={$row->page_namespace} dbk={$row->page_title}";
323 $pageOK = true;
324
325 switch ( $action ) {
326 case 'abort':
327 $this->output( "$logTitle *** $logStatus\n" );
328 $pageOK = false;
329 break;
330 case 'move':
331 $this->output( "$logTitle -> " .
332 $newTitle->getPrefixedDBkey() . " ($logStatus)$dryRunNote\n" );
333
334 if ( $options['fix'] ) {
335 $pageOK = $this->movePage( $row->page_id, $newTitle );
336 }
337 break;
338 case 'merge':
339 $this->output( "$logTitle => " .
340 $newTitle->getPrefixedDBkey() . " (merge)$dryRunNote\n" );
341
342 if ( $options['fix'] ) {
343 $pageOK = $this->mergePage( $row, $newTitle );
344 }
345 break;
346 }
347
348 if ( $pageOK ) {
349 $this->resolvablePages++;
350 } else {
351 $ok = false;
352 }
353 }
354
355 return $ok;
356 }
357
367 private function checkLinkTable( $table, $fieldPrefix, $ns, $name, $options,
368 $extraConds = []
369 ) {
370 $dbw = $this->getPrimaryDB();
371
372 $batchConds = [];
373 $fromField = "{$fieldPrefix}_from";
374 $batchSize = 100;
375 $sqb = $dbw->newSelectQueryBuilder()
376 ->select( $fromField )
377 ->where( $extraConds )
378 ->limit( $batchSize );
379
380 $linksMigration = $this->getServiceContainer()->getLinksMigration();
381 if ( isset( $linksMigration::$mapping[$table] ) ) {
382 $sqb->queryInfo( $linksMigration->getQueryInfo( $table ) );
383 [ $namespaceField, $titleField ] = $linksMigration->getTitleFields( $table );
384 $schemaMigrationStage = $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; try an alternate encoding with '-' for ':'
572 $dbk = "$name-" . $dbk;
573 }
574 $destNS = $ns;
575 $nsInfo = $this->getServiceContainer()->getNamespaceInfo();
576 if ( $sourceNs == NS_TALK && $nsInfo->isSubject( $ns ) ) {
577 // This is an associated talk page moved with the --move-talk feature.
578 $destNS = $nsInfo->getTalk( $destNS );
579 }
580 return [ $destNS, $dbk ];
581 }
582
591 private function getDestinationTitle( $ns, $name, $sourceNs, $sourceDbk ) {
592 [ $destNS, $dbk ] = $this->getDestination( $ns, $name, $sourceNs, $sourceDbk );
593 $newTitle = Title::makeTitleSafe( $destNS, $dbk );
594 if ( !$newTitle || !$newTitle->canExist() ) {
595 return false;
596 }
597 return $newTitle;
598 }
599
609 private function getAlternateTitle( $ns, $dbk, $options ) {
610 $prefix = $options['add-prefix'];
611 $suffix = $options['add-suffix'];
612 if ( $prefix == '' && $suffix == '' ) {
613 return false;
614 }
615 $newDbk = $prefix . $dbk . $suffix;
616 return Title::makeTitleSafe( $ns, $newDbk );
617 }
618
626 private function movePage( $id, LinkTarget $newLinkTarget ) {
627 $dbw = $this->getPrimaryDB();
628
629 $dbw->newUpdateQueryBuilder()
630 ->update( 'page' )
631 ->set( [
632 "page_namespace" => $newLinkTarget->getNamespace(),
633 "page_title" => $newLinkTarget->getDBkey(),
634 ] )
635 ->where( [
636 "page_id" => $id,
637 ] )
638 ->caller( __METHOD__ )
639 ->execute();
640
641 // Update *_from_namespace in links tables
642 $fromNamespaceTables = [
643 [ 'pagelinks', 'pl', [ 'pl_namespace', 'pl_title' ] ],
644 [ 'templatelinks', 'tl', [ 'tl_target_id' ] ],
645 [ 'imagelinks', 'il', [ 'il_to' ] ]
646 ];
647 $updateRowsPerQuery = $this->getConfig()->get( MainConfigNames::UpdateRowsPerQuery );
648 foreach ( $fromNamespaceTables as [ $table, $fieldPrefix, $additionalPrimaryKeyFields ] ) {
649 $fromField = "{$fieldPrefix}_from";
650 $fromNamespaceField = "{$fieldPrefix}_from_namespace";
651
652 $res = $dbw->newSelectQueryBuilder()
653 ->select( $additionalPrimaryKeyFields )
654 ->from( $table )
655 ->where( [ $fromField => $id ] )
656 ->andWhere( $dbw->expr( $fromNamespaceField, '!=', $newLinkTarget->getNamespace() ) )
657 ->caller( __METHOD__ )
658 ->fetchResultSet();
659 if ( !$res ) {
660 continue;
661 }
662
663 $updateConds = [];
664 foreach ( $res as $row ) {
665 $updateConds[] = array_merge( [ $fromField => $id ], (array)$row );
666 }
667 $updateBatches = array_chunk( $updateConds, $updateRowsPerQuery );
668 foreach ( $updateBatches as $updateBatch ) {
669 $dbw->newUpdateQueryBuilder()
670 ->update( $table )
671 ->set( [ $fromNamespaceField => $newLinkTarget->getNamespace() ] )
672 ->where( $dbw->factorConds( $updateBatch ) )
673 ->caller( __METHOD__ )
674 ->execute();
675 if ( count( $updateBatches ) > 1 ) {
676 $this->waitForReplication();
677 }
678 }
679 }
680
681 return true;
682 }
683
696 private function canMerge( $id, LinkTarget $linkTarget, &$logStatus ) {
697 $revisionLookup = $this->getServiceContainer()->getRevisionLookup();
698 $latestDest = $revisionLookup->getRevisionByTitle( $linkTarget, 0,
699 IDBAccessObject::READ_LATEST );
700 $latestSource = $revisionLookup->getRevisionByPageId( $id, 0,
701 IDBAccessObject::READ_LATEST );
702 if ( $latestSource->getTimestamp() > $latestDest->getTimestamp() ) {
703 $logStatus = 'cannot merge since source is later';
704 return false;
705 } else {
706 return true;
707 }
708 }
709
717 private function mergePage( $row, Title $newTitle ) {
718 $dbw = $this->getPrimaryDB();
719 $updateRowsPerQuery = $this->getConfig()->get( MainConfigNames::UpdateRowsPerQuery );
720
721 $id = $row->page_id;
722
723 // Construct the WikiPage object we will need later, while the
724 // page_id still exists. Note that this cannot use makeTitleSafe(),
725 // we are deliberately constructing an invalid title.
726 $sourceTitle = Title::makeTitle( $row->page_namespace, $row->page_title );
727 $sourceTitle->resetArticleID( $id );
728 $wikiPage = $this->getServiceContainer()->getWikiPageFactory()->newFromTitle( $sourceTitle );
729 $wikiPage->loadPageData( IDBAccessObject::READ_LATEST );
730
731 $destId = $newTitle->getArticleID();
732 $this->beginTransaction( $dbw, __METHOD__ );
733 $revIds = $dbw->newSelectQueryBuilder()
734 ->select( 'rev_id' )
735 ->from( 'revision' )
736 ->where( [ 'rev_page' => $id ] )
737 ->caller( __METHOD__ )
738 ->fetchFieldValues();
739 $updateBatches = array_chunk( array_map( 'intval', $revIds ), $updateRowsPerQuery );
740 foreach ( $updateBatches as $updateBatch ) {
741 $dbw->newUpdateQueryBuilder()
742 ->update( 'revision' )
743 ->set( [ 'rev_page' => $destId ] )
744 ->where( [ 'rev_id' => $updateBatch ] )
745 ->caller( __METHOD__ )
746 ->execute();
747 if ( count( $updateBatches ) > 1 ) {
748 $this->waitForReplication();
749 }
750 }
751
752 $dbw->newDeleteQueryBuilder()
753 ->deleteFrom( 'page' )
754 ->where( [ 'page_id' => $id ] )
755 ->caller( __METHOD__ )
756 ->execute();
757
758 $this->commitTransaction( $dbw, __METHOD__ );
759
760 /* Call LinksDeletionUpdate to delete outgoing links from the old title,
761 * and update category counts.
762 *
763 * Calling external code with a fake broken Title is a fairly dubious
764 * idea. It's necessary because it's quite a lot of code to duplicate,
765 * but that also makes it fragile since it would be easy for someone to
766 * accidentally introduce an assumption of title validity to the code we
767 * are calling.
768 */
769 DeferredUpdates::addUpdate( new LinksDeletionUpdate( $wikiPage ) );
770 DeferredUpdates::doUpdates();
771
772 return true;
773 }
774}
775
776$maintClass = NamespaceDupes::class;
777require_once RUN_MAINTENANCE_IF_MAIN;
const SCHEMA_COMPAT_WRITE_OLD
Definition Defines.php:275
const NS_MAIN
Definition Defines.php:65
const SCHEMA_COMPAT_WRITE_NEW
Definition Defines.php:279
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.
fatalError( $msg, $exitCode=1)
Output a message and terminate the current script.
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:1212
exists( $flags=0)
Check if page exists.
Definition Title.php:3145
getArticleID( $flags=0)
Get the article ID for this Title from the link cache, adding it if necessary.
Definition Title.php:2590
getPrefixedDBkey()
Get the prefixed database key form.
Definition Title.php:1849
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.
Result wrapper for grabbing data queried from an IDatabase object.
$maintClass