Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
11.94% covered (danger)
11.94%
8 / 67
55.56% covered (warning)
55.56%
5 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
ImportStreamSource
12.12% covered (danger)
12.12%
8 / 66
55.56% covered (warning)
55.56%
5 / 9
484.77
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
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 newFromUpload
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
110
 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 * @license GPL-2.0-or-later
9 * @file
10 * @ingroup SpecialPage
11 */
12
13namespace MediaWiki\Import;
14
15use MediaWiki\MainConfigNames;
16use MediaWiki\MediaWikiServices;
17use MediaWiki\Status\Status;
18
19/**
20 * Imports a XML dump from a file (either from file upload, files on disk, or HTTP)
21 * @ingroup SpecialPage
22 */
23class ImportStreamSource implements ImportSource {
24    /** @var resource */
25    private $mHandle;
26
27    /**
28     * @param resource $handle
29     */
30    public function __construct( $handle ) {
31        $this->mHandle = $handle;
32    }
33
34    /**
35     * @return bool
36     */
37    public function atEnd() {
38        return feof( $this->mHandle );
39    }
40
41    /**
42     * @return string
43     */
44    public function readChunk() {
45        return fread( $this->mHandle, 32768 );
46    }
47
48    /**
49     * @return bool
50     */
51    public function isSeekable() {
52        return stream_get_meta_data( $this->mHandle )['seekable'] ?? false;
53    }
54
55    /**
56     * @param int $offset
57     * @return int
58     */
59    public function seek( int $offset ) {
60        return fseek( $this->mHandle, $offset );
61    }
62
63    /**
64     * @param string $filename
65     * @return Status
66     */
67    public static function newFromFile( $filename ) {
68        // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
69        $file = @fopen( $filename, 'rt' );
70        if ( !$file ) {
71            return Status::newFatal( "importcantopen" );
72        }
73        return Status::newGood( new ImportStreamSource( $file ) );
74    }
75
76    /**
77     * @param string $fieldname
78     * @return Status
79     */
80    public static function newFromUpload( $fieldname = "xmlimport" ) {
81        // phpcs:ignore MediaWiki.Usage.SuperGlobalsUsage.SuperGlobals
82        $upload =& $_FILES[$fieldname];
83
84        if ( $upload === null || !$upload['name'] ) {
85            return Status::newFatal( 'importnofile' );
86        }
87        if ( !empty( $upload['error'] ) ) {
88            switch ( $upload['error'] ) {
89                case UPLOAD_ERR_INI_SIZE:
90                    // The uploaded file exceeds the upload_max_filesize directive in php.ini.
91                    return Status::newFatal( 'importuploaderrorsize' );
92                case UPLOAD_ERR_FORM_SIZE:
93                    // The uploaded file exceeds the MAX_FILE_SIZE directive that
94                    // was specified in the HTML form.
95                    // FIXME This is probably never used since that directive was removed in 8e91c520?
96                    return Status::newFatal( 'importuploaderrorsize' );
97                case UPLOAD_ERR_PARTIAL:
98                    // The uploaded file was only partially uploaded
99                    return Status::newFatal( 'importuploaderrorpartial' );
100                case UPLOAD_ERR_NO_TMP_DIR:
101                    // Missing a temporary folder.
102                    return Status::newFatal( 'importuploaderrortemp' );
103                // Other error codes get the generic 'importnofile' error message below
104            }
105
106        }
107        $fname = $upload['tmp_name'];
108        if ( is_uploaded_file( $fname ) || defined( 'MW_PHPUNIT_TEST' ) ) {
109            return self::newFromFile( $fname );
110        } else {
111            return Status::newFatal( 'importnofile' );
112        }
113    }
114
115    /**
116     * @param string $url
117     * @param string $method
118     * @return Status
119     */
120    public static function newFromURL( $url, $method = 'GET' ) {
121        $httpImportTimeout = MediaWikiServices::getInstance()->getMainConfig()->get(
122            MainConfigNames::HTTPImportTimeout );
123        wfDebug( __METHOD__ . ": opening $url" );
124        # Use the standard HTTP fetch function; it times out
125        # quicker and sorts out user-agent problems which might
126        # otherwise prevent importing from large sites, such
127        # as the Wikimedia cluster, etc.
128        $data = MediaWikiServices::getInstance()->getHttpRequestFactory()->request(
129            $method,
130            $url,
131            [
132                'followRedirects' => true,
133                'timeout' => $httpImportTimeout
134            ],
135            __METHOD__
136        );
137        if ( $data !== null ) {
138            $file = tmpfile();
139            fwrite( $file, $data );
140            fflush( $file );
141            fseek( $file, 0 );
142            return Status::newGood( new ImportStreamSource( $file ) );
143        } else {
144            return Status::newFatal( 'importcantopen' );
145        }
146    }
147
148    /**
149     * @param string $interwiki
150     * @param string $page
151     * @param bool $history
152     * @param bool $templates
153     * @param int $pageLinkDepth
154     * @return Status
155     */
156    public static function newFromInterwiki( $interwiki, $page, $history = false,
157        $templates = false, $pageLinkDepth = 0
158    ) {
159        if ( $page == '' ) {
160            return Status::newFatal( 'import-noarticle' );
161        }
162
163        # Look up the first interwiki prefix, and let the foreign site handle
164        # subsequent interwiki prefixes
165        $firstIwPrefix = strtok( $interwiki, ':' );
166        $interwikiLookup = MediaWikiServices::getInstance()->getInterwikiLookup();
167        $firstIw = $interwikiLookup->fetch( $firstIwPrefix );
168        if ( !$firstIw ) {
169            return Status::newFatal( 'importbadinterwiki' );
170        }
171
172        $additionalIwPrefixes = strtok( '' );
173        if ( $additionalIwPrefixes ) {
174            $additionalIwPrefixes .= ':';
175        }
176        # Have to do a DB-key replacement ourselves; otherwise spaces get
177        # URL-encoded to +, which is wrong in this case. Similar to logic in
178        # Title::getLocalURL
179        $link = $firstIw->getURL( strtr( "{$additionalIwPrefixes}Special:Export/$page",
180            ' ', '_' ) );
181
182        $params = [];
183        if ( $history ) {
184            $params['history'] = 1;
185        }
186        if ( $templates ) {
187            $params['templates'] = 1;
188        }
189        if ( $pageLinkDepth ) {
190            $params['pagelink-depth'] = $pageLinkDepth;
191        }
192
193        $url = wfAppendQuery( $link, $params );
194        # For interwikis, use POST to avoid redirects.
195        return self::newFromURL( $url, "POST" );
196    }
197}
198
199/** @deprecated class alias since 1.46 */
200class_alias( ImportStreamSource::class, 'ImportStreamSource' );