Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 99
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
ImportFiles
0.00% covered (danger)
0.00%
0 / 96
0.00% covered (danger)
0.00%
0 / 5
552
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
30
 getFileList
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
30
 error
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 processFile
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 1
132
1<?php
2
3/**
4 * Import images as NSFileRepo file.
5 * Therefor the part of the filename till first underscore
6 * is used as namespace.
7 *
8 * Expampe:
9 * A file 'ABC_My_File.png' will be uploaded to the wiki as 'ABC:My File.png'
10 */
11
12require_once dirname( dirname( dirname( __DIR__ ) ) ) . '/maintenance/Maintenance.php';
13
14use MediaWiki\MediaWikiServices;
15
16class ImportFiles extends Maintenance {
17
18    /**
19     * @var string
20     */
21    protected $src = '';
22
23    /**
24     * @var array
25     */
26    protected $errors = [];
27
28    /**
29     *
30     */
31    public function __construct() {
32        $this->addOption( 'overwrite', 'Overwrite existing files?' );
33        $this->addOption( 'dry', 'Dry run. Do not actually upload files to the repo' );
34        $this->addOption( 'summary', 'A summary for all file uploads' );
35        $this->addOption( 'comment', 'A comment for all file uploads' );
36        $this->addOption( 'verbose', 'More verbose output' );
37        $this->addArg( 'dir', 'Path to the directory containing images to be imported' );
38    }
39
40    /**
41     * @return void
42     */
43    public function execute() {
44        $this->src = $this->getArg( 0 );
45
46        $files = $this->getFileList();
47
48        $processedFiles = 0;
49        foreach ( $files as $fileName => $file ) {
50            if ( $file instanceof SplFileInfo !== true ) {
51                $this->error( 'Could not process list item: '
52                        . $fileName . ' '
53                        . var_export( $file, true )
54                );
55                continue;
56            }
57            $this->output( 'Processing ' . $file->getPathname() . " ... \n" );
58            $mResult = $this->processFile( $file );
59            if ( $mResult !== true ) {
60                $this->error( " ... error: $mResult\n\n" );
61            } else {
62                $this->output( " ... done.\n\n" );
63                $processedFiles++;
64            }
65        }
66
67        $this->output( "$processedFiles file(s) processed.\n" );
68        $this->output( count( $this->errors ) . " errors(s) occurred.\n" );
69        if ( count( $this->errors ) > 0 ) {
70            $this->output(
71                implode( "\n", $this->errors )
72            );
73        }
74    }
75
76    /**
77     * @return array
78     */
79    public function getFileList() {
80        global $wgFileExtensions;
81        $fileExtensions = array_map( 'strtolower', $wgFileExtensions );
82
83        $realPath = realPath( $this->src );
84        $this->output( 'Fetching file list from "' . $realPath . '"' );
85
86        $iterator = new RecursiveIteratorIterator(
87            new RecursiveDirectoryIterator( $realPath ),
88            RecursiveIteratorIterator::SELF_FIRST
89        );
90
91        $files = [];
92        foreach ( $iterator as $realPath => $file ) {
93            if ( $file instanceof SplFileInfo === false ) {
94                $this->error( 'Not a valid SplFileInfo object: ' . $realPath );
95            }
96            if ( !empty( $fileExtensions ) ) {
97                $fileExt = strtolower( $file->getExtension() );
98                if ( !in_array( $fileExt,  $fileExtensions ) ) {
99                    continue;
100                }
101            }
102            $files[$file->getPathname()] = $file;
103        }
104
105        ksort( $files, SORT_NATURAL );
106        $fileCount = count( $files );
107        $this->output( " ... found $fileCount file(s)\n" );
108
109        return $files;
110    }
111
112    /**
113     * Throw an error to the user. Doesn't respect --quiet, so don't use
114     * this for non-error output
115     *
116     * @param string $error String: the error to display
117     * @param int $die Int: if > 0, go ahead and die out using this int as the code
118     */
119    public function error( $error, $die = 0 ) {
120        $this->errors[] = $error;
121        parent::error( $error, $die );
122    }
123
124    /**
125     * @param SplFileInfo $file
126     * @return bool
127     */
128    public function processFile( $file ) {
129        $filename = $file->getFileName();
130        $services = MediaWikiServices::getInstance();
131
132        // NSFileRep: Use the text till first '_' as namespace
133        $pos = strpos( $filename, '_' );
134        if ( $pos !== false ) {
135            $namespace = substr( $filename, 0, $pos );
136            if ( $namespace !== false ) {
137                $filename = str_replace( $namespace . '_', $namespace . ':', $filename );
138            }
139        }
140
141        // MediaWiki normalizes multiple spaces/undescores into one single score/underscore
142        $filename = str_replace( ' ', '_', $filename );
143        $filename = preg_replace( '#(_)+#si', '_', $filename );
144
145        $targetTitle = Title::makeTitle( NS_FILE, $filename );
146        $repo = $services->getRepoGroup()->getLocalRepo();
147
148        $this->output( "Using target title {$targetTitle->getPrefixedDBkey()} " );
149
150        $repoFile = $repo->newFile( $targetTitle );
151        if ( $repoFile->exists() ) {
152            if ( !$this->hasOption( 'overwrite' ) ) {
153                $this->output( "File '{$repoFile->getName()}' already exists. Skipping...\n" );
154                return true;
155            } else {
156                $this->output( "File '{$repoFile->getName()}' already exists. Overwriting...\n" );
157            }
158        }
159
160        /*
161         * The following code is almost a dirext copy of
162         * <mediawiki>/maintenance/importImages.php
163         */
164        $commentText = $this->getOption( 'comment', '' );
165
166        if ( !$this->hasOption( 'dry' ) ) {
167            $mwProps = new MWFileProps( $services->getMimeAnalyzer() );
168            $props = $mwProps->getPropsFromPath( $file->getPathname(), true );
169            $flags = 0;
170            $publishOptions = [];
171            $handler = MediaHandler::getHandler( $props['mime'] );
172            if ( $handler ) {
173                $metadata = \Wikimedia\AtEase\AtEase::quietCall( 'unserialize', $props['metadata'] );
174
175                $publishOptions['headers'] = $handler->getContentHeaders( $metadata );
176            } else {
177                $publishOptions['headers'] = [];
178            }
179            $archive = $repoFile->publish( $file->getPathname(), $flags, $publishOptions );
180            if ( !$archive->isGood() ) {
181                $this->output( "failed. (" .
182                    $archive->getMessage( false, false, 'en' )->text() .
183                    ")\n" );
184            }
185        }
186
187        $commentText = SpecialUpload::getInitialPageText( $commentText, '' );
188        $summary = $this->getOption( 'summary', '' );
189        $user = User::newSystemUser( 'Maintenance script', [ 'steal' => true ] );
190
191        if ( $this->hasOption( 'dry' ) ) {
192            $this->output( "done.\n" );
193        } elseif ( $repoFile->recordUpload3( $archive->value, $summary, $commentText, $user, $props ) ) {
194            $this->output( "done.\n" );
195        }
196
197        if ( $this->hasOption( 'verbose' ) ) {
198            $this->output( "Canonical Filename: {$repoFile->getName()}\n" );
199            $this->output( "Canonical URL: {$repoFile->getCanonicalUrl()}" );
200        }
201
202        return true;
203    }
204}
205
206$maintClass = ImportFiles::class;
207require_once RUN_MAINTENANCE_IF_MAIN;