Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
DeleteOrphanedRevisions
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
3 / 3
5
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 execute
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
3
 deleteRevs
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Delete revisions which refer to a nonexisting page.
4 * Sometimes manual deletion done in a rush leaves crap in the database.
5 *
6 * @license GPL-2.0-or-later
7 * @file
8 * @ingroup Maintenance
9 * @author Rob Church <robchur@gmail.com>
10 * @todo More efficient cleanup of text records
11 */
12
13// @codeCoverageIgnoreStart
14require_once __DIR__ . '/Maintenance.php';
15// @codeCoverageIgnoreEnd
16
17use MediaWiki\Maintenance\Maintenance;
18use Wikimedia\Rdbms\IDatabase;
19
20/**
21 * Maintenance script that deletes revisions which refer to a nonexisting page.
22 *
23 * @ingroup Maintenance
24 */
25class DeleteOrphanedRevisions extends Maintenance {
26    public function __construct() {
27        parent::__construct();
28        $this->addDescription(
29            'Maintenance script to delete revisions which refer to a nonexisting page' );
30        $this->addOption( 'report', 'Prints out a count of affected revisions but doesn\'t delete them' );
31    }
32
33    public function execute() {
34        $this->output( "Delete Orphaned Revisions\n" );
35
36        $report = $this->hasOption( 'report' );
37
38        $dbw = $this->getPrimaryDB();
39        $this->beginTransactionRound( __METHOD__ );
40
41        # Find all the orphaned revisions
42        $this->output( "Checking for orphaned revisions..." );
43        $revisions = $dbw->newSelectQueryBuilder()
44            ->select( 'rev_id' )
45            ->from( 'revision' )
46            ->leftJoin( 'page', null, 'rev_page = page_id' )
47            ->where( [ 'page_namespace' => null ] )
48            ->caller( __METHOD__ )
49            ->fetchFieldValues();
50
51        # Stash 'em all up for deletion (if needed)
52        $count = count( $revisions );
53        $this->output( "found {$count}.\n" );
54
55        # Nothing to do?
56        if ( $report || $count === 0 ) {
57            $this->commitTransactionRound( __METHOD__ );
58            return;
59        }
60
61        # Delete each revision
62        $this->output( "Deleting..." );
63        $this->deleteRevs( $revisions, $dbw );
64        $this->output( "done.\n" );
65
66        # Close the transaction and call the script to purge unused text records
67        $this->commitTransactionRound( __METHOD__ );
68        $this->purgeRedundantText( true );
69    }
70
71    /**
72     * Delete one or more revisions from the database
73     * Do this inside a transaction
74     *
75     * @param int[] $id Array of revision id values
76     * @param IDatabase $dbw Primary DB handle
77     */
78    private function deleteRevs( array $id, IDatabase $dbw ) {
79        $dbw->newDeleteQueryBuilder()
80            ->deleteFrom( 'revision' )
81            ->where( [ 'rev_id' => $id ] )
82            ->caller( __METHOD__ )->execute();
83
84        // Delete from ip_changes should a record exist.
85        $dbw->newDeleteQueryBuilder()
86            ->deleteFrom( 'ip_changes' )
87            ->where( [ 'ipc_rev_id' => $id ] )
88            ->caller( __METHOD__ )->execute();
89    }
90}
91
92// @codeCoverageIgnoreStart
93$maintClass = DeleteOrphanedRevisions::class;
94require_once RUN_MAINTENANCE_IF_MAIN;
95// @codeCoverageIgnoreEnd