Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
78.46% covered (warning)
78.46%
51 / 65
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
DeleteBatch
78.46% covered (warning)
78.46%
51 / 65
50.00% covered (danger)
50.00%
1 / 2
22.61
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 execute
75.44% covered (warning)
75.44%
43 / 57
0.00% covered (danger)
0.00%
0 / 1
22.80
1<?php
2/**
3 * Deletes a batch of pages.
4 * Usage: php deleteBatch.php [-u <user>] [-r <reason>] [-i <interval>] [--by-id] [listfile]
5 * where
6 *   [listfile] is a file where each line contains the title of a page to be
7 *     deleted, standard input is used if listfile is not given.
8 *   <user> is the username
9 *   <reason> is the delete reason
10 *   <interval> is the number of seconds to sleep for after each delete
11 *
12 * @license GPL-2.0-or-later
13 * @file
14 * @ingroup Maintenance
15 */
16
17use MediaWiki\Maintenance\Maintenance;
18use MediaWiki\Title\Title;
19use MediaWiki\User\User;
20
21// @codeCoverageIgnoreStart
22require_once __DIR__ . '/Maintenance.php';
23// @codeCoverageIgnoreEnd
24
25/**
26 * Maintenance script to delete a batch of pages.
27 *
28 * @ingroup Maintenance
29 */
30class DeleteBatch extends Maintenance {
31
32    public function __construct() {
33        parent::__construct();
34        $this->addDescription( 'Deletes a batch of pages' );
35        $this->addOption( 'u', 'User to perform deletion', false, true );
36        $this->addOption( 'r', 'Reason to delete page', false, true );
37        $this->addOption( 'i', 'Interval to sleep (in seconds) between deletions' );
38        $this->addOption( 'by-id', 'Delete by page ID instead of by page name', false, false );
39        $this->addArg( 'listfile', 'File with titles to delete, separated by newlines. ' .
40            'If not given, stdin will be used.', false );
41    }
42
43    public function execute() {
44        # Change to current working directory
45        $oldCwd = getcwd();
46        chdir( $oldCwd );
47
48        # Options processing
49        $username = $this->getOption( 'u', false );
50        $reason = $this->getOption( 'r', '' );
51        $interval = $this->getOption( 'i', 0 );
52        $byId = $this->hasOption( 'by-id' );
53
54        if ( $username === false ) {
55            $user = User::newSystemUser( 'Delete page script', [ 'steal' => true ] );
56        } else {
57            $user = User::newFromName( $username );
58        }
59        if ( !$user ) {
60            $this->fatalError( "Invalid username" );
61        }
62
63        if ( $this->hasArg( 0 ) ) {
64            $file = fopen( $this->getArg( 0 ), 'r' );
65        } else {
66            $file = $this->getStdin();
67        }
68
69        # Setup
70        if ( !$file ) {
71            $this->fatalError( "Unable to read file, exiting" );
72        }
73
74        $services = $this->getServiceContainer();
75        $wikiPageFactory = $services->getWikiPageFactory();
76        $repoGroup = $services->getRepoGroup();
77        $delPageFactory = $services->getDeletePageFactory();
78
79        # Handle each entry
80        for ( $linenum = 1; !feof( $file ); $linenum++ ) {
81            $line = trim( fgets( $file ) );
82            if ( $line == '' ) {
83                continue;
84            }
85            if ( $byId === false ) {
86                $target = Title::newFromText( $line );
87                if ( $target === null ) {
88                    $this->output( "Invalid title '$line' on line $linenum\n" );
89                    continue;
90                }
91                if ( !$target->exists() ) {
92                    $this->output( "Skipping nonexistent page '$line'\n" );
93                    continue;
94                }
95            } else {
96                $target = Title::newFromID( (int)$line );
97                if ( $target === null ) {
98                    $this->output( "Invalid page ID '$line' on line $linenum\n" );
99                    continue;
100                }
101                if ( !$target->exists() ) {
102                    $this->output( "Skipping nonexistent page ID '$line'\n" );
103                    continue;
104                }
105            }
106            if ( $target->getNamespace() === NS_FILE ) {
107                $img = $repoGroup->findFile(
108                    $target, [ 'ignoreRedirect' => true ]
109                );
110                if ( $img && $img->isLocal() && !$img->deleteFile( $reason, $user ) ) {
111                    $this->output( " FAILED to delete associated file..." );
112                }
113            }
114            $page = $wikiPageFactory->newFromTitle( $target );
115            $delPage = $delPageFactory->newDeletePage( $page, $user );
116            $status = $delPage
117                ->forceImmediate( true )
118                ->deleteUnsafe( $reason );
119
120            if ( $status->isOK() ) {
121                $this->output( " Deleted $line!\n" );
122            } else {
123                $this->output( " FAILED to delete page $line\n" );
124                $this->error( $status );
125            }
126
127            if ( $interval ) {
128                sleep( $interval );
129            }
130            $this->waitForReplication();
131        }
132    }
133}
134
135// @codeCoverageIgnoreStart
136$maintClass = DeleteBatch::class;
137require_once RUN_MAINTENANCE_IF_MAIN;
138// @codeCoverageIgnoreEnd