Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 50
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
EraseArchivedFile
0.00% covered (danger)
0.00%
0 / 50
0.00% covered (danger)
0.00%
0 / 4
210
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
72
 scrubAllVersions
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 scrubVersion
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * Delete archived (non-current) files from storage
4 *
5 * @license GPL-2.0-or-later
6 * @file
7 * @ingroup Maintenance
8 */
9
10use MediaWiki\FileRepo\File\ArchivedFile;
11use MediaWiki\FileRepo\File\FileSelectQueryBuilder;
12use MediaWiki\Maintenance\Maintenance;
13
14// @codeCoverageIgnoreStart
15require_once __DIR__ . '/Maintenance.php';
16// @codeCoverageIgnoreEnd
17
18/**
19 * Maintenance script to delete archived (non-current) files from storage.
20 *
21 * @todo Maybe add some simple logging
22 *
23 * @ingroup Maintenance
24 * @since 1.22
25 */
26class EraseArchivedFile extends Maintenance {
27    public function __construct() {
28        parent::__construct();
29        $this->addDescription( 'Erases traces of deleted files.' );
30        $this->addOption( 'delete', 'Perform the deletion' );
31        $this->addOption( 'filename', 'File name', false, true );
32        $this->addOption( 'filekey', 'File storage key (with extension) or "*"', true, true );
33    }
34
35    public function execute() {
36        if ( !$this->hasOption( 'delete' ) ) {
37            $this->output( "Use --delete to actually confirm this script\n" );
38        }
39
40        $filekey = $this->getOption( 'filekey' );
41        $filename = $this->getOption( 'filename' );
42
43        if ( $filekey === '*' ) {
44            // all versions by name
45            if ( $filename === null || $filename === '' ) {
46                $this->fatalError( "Missing --filename parameter." );
47            }
48            $afile = false;
49        } else {
50            // specified version
51            $dbw = $this->getPrimaryDB();
52            $queryBuilder = FileSelectQueryBuilder::newForArchivedFile( $dbw );
53            $queryBuilder->where( [ 'fa_storage_group' => 'deleted', 'fa_storage_key' => $filekey ] );
54            $row = $queryBuilder->caller( __METHOD__ )->fetchRow();
55
56            if ( !$row ) {
57                $this->fatalError( "No deleted file exists with key '$filekey'." );
58            }
59            $filename = $row->fa_name;
60            $afile = ArchivedFile::newFromRow( $row );
61        }
62
63        $file = $this->getServiceContainer()->getRepoGroup()->getLocalRepo()->newFile( $filename );
64        if ( $file->exists() ) {
65            $this->fatalError( "File '$filename' is still a public file, use the delete form.\n" );
66        }
67
68        $this->output( "Purging all thumbnails for file '$filename'..." );
69        $file->purgeCache();
70        $this->output( "done.\n" );
71
72        if ( $afile instanceof ArchivedFile ) {
73            $this->scrubVersion( $afile );
74        } else {
75            $this->output( "Finding deleted versions of file '$filename'...\n" );
76            $this->scrubAllVersions( $filename );
77            $this->output( "Done\n" );
78        }
79    }
80
81    protected function scrubAllVersions( string $name ) {
82        $dbw = $this->getPrimaryDB();
83        $queryBuilder = FileSelectQueryBuilder::newForArchivedFile( $dbw );
84        $queryBuilder->where( [ 'fa_name' => $name, 'fa_storage_group' => 'deleted' ] );
85        $res = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
86        foreach ( $res as $row ) {
87            $this->scrubVersion( ArchivedFile::newFromRow( $row ) );
88        }
89    }
90
91    protected function scrubVersion( ArchivedFile $archivedFile ) {
92        $key = $archivedFile->getStorageKey();
93        $name = $archivedFile->getName();
94        $ts = $archivedFile->getTimestamp();
95        $repo = $this->getServiceContainer()->getRepoGroup()->getLocalRepo();
96        $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key;
97        if ( $this->hasOption( 'delete' ) ) {
98            $status = $repo->getBackend()->delete( [ 'src' => $path ] );
99            if ( $status->isOK() ) {
100                $this->output( "Deleted version '$key' ($ts) of file '$name'\n" );
101            } else {
102                $this->output( "Failed to delete version '$key' ($ts) of file '$name'\n" );
103                $this->error( $status );
104            }
105        } else {
106            $this->output( "Would delete version '{$key}' ({$ts}) of file '$name'\n" );
107        }
108    }
109}
110
111// @codeCoverageIgnoreStart
112$maintClass = EraseArchivedFile::class;
113require_once RUN_MAINTENANCE_IF_MAIN;
114// @codeCoverageIgnoreEnd