Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 127
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
ImportTextFiles
0.00% covered (danger)
0.00%
0 / 124
0.00% covered (danger)
0.00%
0 / 2
870
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 111
0.00% covered (danger)
0.00%
0 / 1
812
1<?php
2/**
3 * Import pages from text files
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @ingroup Maintenance
22 */
23
24use MediaWiki\Revision\SlotRecord;
25use MediaWiki\Title\Title;
26use MediaWiki\User\User;
27
28require_once __DIR__ . '/Maintenance.php';
29
30/**
31 * Maintenance script which reads in text files
32 * and imports their content to a page of the wiki.
33 *
34 * @ingroup Maintenance
35 */
36class ImportTextFiles extends Maintenance {
37    public function __construct() {
38        parent::__construct();
39        $this->addDescription( 'Reads in text files and imports their content to pages of the wiki' );
40        $this->addOption( 'user', 'Username to which edits should be attributed. ' .
41            'Default: "Maintenance script"', false, true, 'u' );
42        $this->addOption( 'summary', 'Specify edit summary for the edits', false, true, 's' );
43        $this->addOption( 'use-timestamp', 'Use the modification date of the text file ' .
44            'as the timestamp for the edit' );
45        $this->addOption( 'overwrite', 'Overwrite existing pages. If --use-timestamp is passed, this ' .
46            'will only overwrite pages if the file has been modified since the page was last modified.' );
47        $this->addOption( 'prefix', 'A string to place in front of the file name', false, true, 'p' );
48        $this->addOption( 'bot', 'Mark edits as bot edits in the recent changes list.' );
49        $this->addOption( 'rc', 'Place revisions in RecentChanges.' );
50        $this->addArg( 'files', 'Files to import' );
51    }
52
53    public function execute() {
54        $userName = $this->getOption( 'user', false );
55        $summary = $this->getOption( 'summary', 'Imported from text file' );
56        $useTimestamp = $this->hasOption( 'use-timestamp' );
57        $rc = $this->hasOption( 'rc' );
58        $bot = $this->hasOption( 'bot' );
59        $overwrite = $this->hasOption( 'overwrite' );
60        $prefix = $this->getOption( 'prefix', '' );
61
62        // Get all the arguments. A loop is required since Maintenance doesn't
63        // support an arbitrary number of arguments.
64        $files = [];
65        $i = 0;
66        while ( $arg = $this->getArg( $i++ ) ) {
67            if ( file_exists( $arg ) ) {
68                $files[$arg] = file_get_contents( $arg );
69            } else {
70                // use glob to support the Windows shell, which doesn't automatically
71                // expand wildcards
72                $found = false;
73                foreach ( glob( $arg ) as $filename ) {
74                    $found = true;
75                    $files[$filename] = file_get_contents( $filename );
76                }
77                if ( !$found ) {
78                    $this->fatalError( "Fatal error: The file '$arg' does not exist!" );
79                }
80            }
81        }
82
83        $count = count( $files );
84        $this->output( "Importing $count pages...\n" );
85
86        if ( $userName === false ) {
87            $user = User::newSystemUser( User::MAINTENANCE_SCRIPT_USER, [ 'steal' => true ] );
88        } else {
89            $user = User::newFromName( $userName );
90        }
91
92        if ( !$user ) {
93            $this->fatalError( "Invalid username\n" );
94        }
95        if ( $user->isAnon() ) {
96            $user->addToDatabase();
97        }
98
99        $exit = 0;
100
101        $successCount = 0;
102        $failCount = 0;
103        $skipCount = 0;
104
105        $revLookup = $this->getServiceContainer()->getRevisionLookup();
106        foreach ( $files as $file => $text ) {
107            $pageName = $prefix . pathinfo( $file, PATHINFO_FILENAME );
108            $timestamp = $useTimestamp ? wfTimestamp( TS_UNIX, filemtime( $file ) ) : wfTimestampNow();
109
110            $title = Title::newFromText( $pageName );
111            // Have to check for # manually, since it gets interpreted as a fragment
112            if ( !$title || $title->hasFragment() ) {
113                $this->error( "Invalid title $pageName. Skipping.\n" );
114                $skipCount++;
115                continue;
116            }
117
118            $exists = $title->exists();
119            $oldRevID = $title->getLatestRevID();
120            $oldRevRecord = $oldRevID ? $revLookup->getRevisionById( $oldRevID ) : null;
121            $actualTitle = $title->getPrefixedText();
122
123            if ( $exists ) {
124                $touched = wfTimestamp( TS_UNIX, $title->getTouched() );
125                if ( !$overwrite ) {
126                    $this->output( "Title $actualTitle already exists. Skipping.\n" );
127                    $skipCount++;
128                    continue;
129                } elseif ( $useTimestamp && intval( $touched ) >= intval( $timestamp ) ) {
130                    $this->output( "File for title $actualTitle has not been modified since the " .
131                        "destination page was touched. Skipping.\n" );
132                    $skipCount++;
133                    continue;
134                }
135            }
136
137            $content = ContentHandler::makeContent( rtrim( $text ), $title );
138            $rev = new WikiRevision();
139            $rev->setContent( SlotRecord::MAIN, $content );
140            $rev->setTitle( $title );
141            $rev->setUserObj( $user );
142            $rev->setComment( $summary );
143            $rev->setTimestamp( $timestamp );
144
145            if ( $exists &&
146                $overwrite &&
147                $rev->getContent()->equals( $oldRevRecord->getContent( SlotRecord::MAIN ) )
148            ) {
149                $this->output( "File for title $actualTitle contains no changes from the current " .
150                    "revision. Skipping.\n" );
151                $skipCount++;
152                continue;
153            }
154
155            $status = $rev->importOldRevision();
156            $newId = $title->getLatestRevID();
157
158            if ( $status ) {
159                $action = $exists ? 'updated' : 'created';
160                $this->output( "Successfully $action $actualTitle\n" );
161                $successCount++;
162            } else {
163                $action = $exists ? 'update' : 'create';
164                $this->output( "Failed to $action $actualTitle\n" );
165                $failCount++;
166                $exit = 1;
167            }
168
169            // Create the RecentChanges entry if necessary
170            if ( $rc && $status ) {
171                if ( $exists ) {
172                    if ( is_object( $oldRevRecord ) ) {
173                        RecentChange::notifyEdit(
174                            $timestamp,
175                            $title,
176                            $rev->getMinor(),
177                            $user,
178                            $summary,
179                            $oldRevID,
180                            $oldRevRecord->getTimestamp(),
181                            $bot,
182                            '',
183                            $oldRevRecord->getSize(),
184                            $rev->getSize(),
185                            $newId,
186                            // the pages don't need to be patrolled
187                            1
188                        );
189                    }
190                } else {
191                    RecentChange::notifyNew(
192                        $timestamp,
193                        $title,
194                        $rev->getMinor(),
195                        $user,
196                        $summary,
197                        $bot,
198                        '',
199                        $rev->getSize(),
200                        $newId,
201                        1
202                    );
203                }
204            }
205        }
206
207        $this->output( "Done! $successCount succeeded, $skipCount skipped.\n" );
208        if ( $exit ) {
209            $this->fatalError( "Import failed with $failCount failed pages.\n", $exit );
210        }
211    }
212}
213
214$maintClass = ImportTextFiles::class;
215require_once RUN_MAINTENANCE_IF_MAIN;