Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
15.62% covered (danger)
15.62%
10 / 64
55.56% covered (warning)
55.56%
5 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
ImportStreamSource
15.62% covered (danger)
15.62%
10 / 64
55.56% covered (warning)
55.56%
5 / 9
400.42
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 atEnd
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 readChunk
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isSeekable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 seek
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 newFromFile
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
2.02
 newFromUpload
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
90
 newFromURL
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
6
 newFromInterwiki
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
56
1<?php
2/**
3 * MediaWiki page data importer.
4 *
5 * Copyright © 2003,2005 Brooke Vibber <bvibber@wikimedia.org>
6 * https://www.mediawiki.org/
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 * http://www.gnu.org/copyleft/gpl.html
22 *
23 * @file
24 * @ingroup SpecialPage
25 */
26
27use MediaWiki\MainConfigNames;
28use MediaWiki\MediaWikiServices;
29use MediaWiki\Status\Status;
30use Wikimedia\AtEase\AtEase;
31
32/**
33 * Imports a XML dump from a file (either from file upload, files on disk, or HTTP)
34 * @ingroup SpecialPage
35 */
36class ImportStreamSource implements ImportSource {
37    /** @var resource */
38    private $mHandle;
39
40    /**
41     * @param resource $handle
42     */
43    public function __construct( $handle ) {
44        $this->mHandle = $handle;
45    }
46
47    /**
48     * @return bool
49     */
50    public function atEnd() {
51        return feof( $this->mHandle );
52    }
53
54    /**
55     * @return string
56     */
57    public function readChunk() {
58        return fread( $this->mHandle, 32768 );
59    }
60
61    /**
62     * @return bool
63     */
64    public function isSeekable() {
65        return stream_get_meta_data( $this->mHandle )['seekable'] ?? false;
66    }
67
68    /**
69     * @param int $offset
70     * @return int
71     */
72    public function seek( int $offset ) {
73        return fseek( $this->mHandle, $offset );
74    }
75
76    /**
77     * @param string $filename
78     * @return Status
79     */
80    public static function newFromFile( $filename ) {
81        AtEase::suppressWarnings();
82        $file = fopen( $filename, 'rt' );
83        AtEase::restoreWarnings();
84        if ( !$file ) {
85            return Status::newFatal( "importcantopen" );
86        }
87        return Status::newGood( new ImportStreamSource( $file ) );
88    }
89
90    /**
91     * @param string $fieldname
92     * @return Status
93     */
94    public static function newFromUpload( $fieldname = "xmlimport" ) {
95        // phpcs:ignore MediaWiki.Usage.SuperGlobalsUsage.SuperGlobals
96        $upload =& $_FILES[$fieldname];
97
98        if ( $upload === null || !$upload['name'] ) {
99            return Status::newFatal( 'importnofile' );
100        }
101        if ( !empty( $upload['error'] ) ) {
102            switch ( $upload['error'] ) {
103                case UPLOAD_ERR_INI_SIZE:
104                    // The uploaded file exceeds the upload_max_filesize directive in php.ini.
105                    return Status::newFatal( 'importuploaderrorsize' );
106                case UPLOAD_ERR_FORM_SIZE:
107                    // The uploaded file exceeds the MAX_FILE_SIZE directive that
108                    // was specified in the HTML form.
109                    // FIXME This is probably never used since that directive was removed in 8e91c520?
110                    return Status::newFatal( 'importuploaderrorsize' );
111                case UPLOAD_ERR_PARTIAL:
112                    // The uploaded file was only partially uploaded
113                    return Status::newFatal( 'importuploaderrorpartial' );
114                case UPLOAD_ERR_NO_TMP_DIR:
115                    // Missing a temporary folder.
116                    return Status::newFatal( 'importuploaderrortemp' );
117                // Other error codes get the generic 'importnofile' error message below
118            }
119
120        }
121        $fname = $upload['tmp_name'];
122        if ( is_uploaded_file( $fname ) ) {
123            return self::newFromFile( $fname );
124        } else {
125            return Status::newFatal( 'importnofile' );
126        }
127    }
128
129    /**
130     * @param string $url
131     * @param string $method
132     * @return Status
133     */
134    public static function newFromURL( $url, $method = 'GET' ) {
135        $httpImportTimeout = MediaWikiServices::getInstance()->getMainConfig()->get(
136            MainConfigNames::HTTPImportTimeout );
137        wfDebug( __METHOD__ . ": opening $url" );
138        # Use the standard HTTP fetch function; it times out
139        # quicker and sorts out user-agent problems which might
140        # otherwise prevent importing from large sites, such
141        # as the Wikimedia cluster, etc.
142        $data = MediaWikiServices::getInstance()->getHttpRequestFactory()->request(
143            $method,
144            $url,
145            [
146                'followRedirects' => true,
147                'timeout' => $httpImportTimeout
148            ],
149            __METHOD__
150        );
151        if ( $data !== null ) {
152            $file = tmpfile();
153            fwrite( $file, $data );
154            fflush( $file );
155            fseek( $file, 0 );
156            return Status::newGood( new ImportStreamSource( $file ) );
157        } else {
158            return Status::newFatal( 'importcantopen' );
159        }
160    }
161
162    /**
163     * @param string $interwiki
164     * @param string $page
165     * @param bool $history
166     * @param bool $templates
167     * @param int $pageLinkDepth
168     * @return Status
169     */
170    public static function newFromInterwiki( $interwiki, $page, $history = false,
171        $templates = false, $pageLinkDepth = 0
172    ) {
173        if ( $page == '' ) {
174            return Status::newFatal( 'import-noarticle' );
175        }
176
177        # Look up the first interwiki prefix, and let the foreign site handle
178        # subsequent interwiki prefixes
179        $firstIwPrefix = strtok( $interwiki, ':' );
180        $interwikiLookup = MediaWikiServices::getInstance()->getInterwikiLookup();
181        $firstIw = $interwikiLookup->fetch( $firstIwPrefix );
182        if ( !$firstIw ) {
183            return Status::newFatal( 'importbadinterwiki' );
184        }
185
186        $additionalIwPrefixes = strtok( '' );
187        if ( $additionalIwPrefixes ) {
188            $additionalIwPrefixes .= ':';
189        }
190        # Have to do a DB-key replacement ourselves; otherwise spaces get
191        # URL-encoded to +, which is wrong in this case. Similar to logic in
192        # Title::getLocalURL
193        $link = $firstIw->getURL( strtr( "{$additionalIwPrefixes}Special:Export/$page",
194            ' ', '_' ) );
195
196        $params = [];
197        if ( $history ) {
198            $params['history'] = 1;
199        }
200        if ( $templates ) {
201            $params['templates'] = 1;
202        }
203        if ( $pageLinkDepth ) {
204            $params['pagelink-depth'] = $pageLinkDepth;
205        }
206
207        $url = wfAppendQuery( $link, $params );
208        # For interwikis, use POST to avoid redirects.
209        return self::newFromURL( $url, "POST" );
210    }
211}