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