Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.89% covered (success)
91.89%
34 / 37
75.00% covered (warning)
75.00%
6 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
CommonsHelperConfigRetriever
91.89% covered (success)
91.89%
34 / 37
75.00% covered (warning)
75.00%
6 / 8
15.12
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 retrieveConfiguration
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
6.01
 getConfigWikitext
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getConfigWikiUrl
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 buildCommonsHelperConfigUrl
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 sendApiRequest
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
2.09
 getQueryParamTitle
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getHostWithoutTopLevelDomain
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace FileImporter\Remote\MediaWiki;
4
5use FileImporter\Data\SourceUrl;
6use FileImporter\Exceptions\HttpRequestException;
7use FileImporter\Exceptions\ImportException;
8use FileImporter\Exceptions\LocalizedImportException;
9use FileImporter\Services\Http\HttpRequestExecutor;
10use MediaWiki\MediaWikiServices;
11
12/**
13 * @license GPL-2.0-or-later
14 */
15class CommonsHelperConfigRetriever {
16
17    /**
18     * @var \Config
19     */
20    private $mainConfig;
21
22    /**
23     * @var HttpRequestExecutor
24     */
25    private $httpRequestExecutor;
26
27    /**
28     * @var string
29     */
30    private $configServer;
31
32    /**
33     * @var string
34     */
35    private $configBasePageName;
36
37    /**
38     * @var string|null
39     */
40    private $configWikitext = null;
41
42    /**
43     * @var string|null
44     */
45    private $configWikiUrl = null;
46
47    /**
48     * @param HttpRequestExecutor $httpRequestExecutor
49     * @param string $configServer Full domain including schema, e.g. "https://www.mediawiki.org"
50     * @param string $configBasePageName Base page name, e.g. "Extension:FileImporter/Data/"
51     */
52    public function __construct(
53        HttpRequestExecutor $httpRequestExecutor,
54        $configServer,
55        $configBasePageName
56    ) {
57        // TODO: Inject?
58        $this->mainConfig = MediaWikiServices::getInstance()->getMainConfig();
59
60        $this->httpRequestExecutor = $httpRequestExecutor;
61        $this->configServer = $configServer;
62        $this->configBasePageName = $configBasePageName;
63    }
64
65    /**
66     * @param SourceUrl $sourceUrl
67     *
68     * @return bool True if a config was found
69     * @throws ImportException e.g. when the config page doesn't exist
70     */
71    public function retrieveConfiguration( SourceUrl $sourceUrl ) {
72        $response = $this->sendApiRequest( $sourceUrl );
73
74        if ( !isset( $response['query']['pages'] ) ||
75            count( $response['query']['pages'] ) !== 1
76        ) {
77            return false;
78        }
79
80        $currPage = end( $response['query']['pages'] );
81
82        if ( array_key_exists( 'missing', $currPage ) ) {
83            return false;
84        }
85
86        if ( array_key_exists( 'revisions', $currPage ) ) {
87            $latestRevision = end( $currPage['revisions'] );
88            if ( array_key_exists( 'content', $latestRevision ) ) {
89                $this->configWikiUrl = $this->buildCommonsHelperConfigUrl( $sourceUrl );
90                $this->configWikitext = $latestRevision['content'];
91                return true;
92            }
93        }
94
95        throw new LocalizedImportException( 'fileimporter-commonshelper-retrieval-failed' );
96    }
97
98    /**
99     * @return string|null
100     */
101    public function getConfigWikitext() {
102        return $this->configWikitext;
103    }
104
105    /**
106     * @return string|null
107     */
108    public function getConfigWikiUrl() {
109        return $this->configWikiUrl;
110    }
111
112    /**
113     * @param SourceUrl $sourceUrl
114     *
115     * @return string
116     */
117    private function buildCommonsHelperConfigUrl( SourceUrl $sourceUrl ) {
118        $title = $this->getQueryParamTitle( $sourceUrl );
119
120        // We assume the wiki holding the config pages uses the same configuration.
121        $articlePath = str_replace( '$1', $title, $this->mainConfig->get( 'ArticlePath' ) );
122
123        return $this->configServer . $articlePath;
124    }
125
126    /**
127     * @param SourceUrl $sourceUrl
128     *
129     * @return array[]
130     * @throws ImportException when the request failed
131     */
132    private function sendApiRequest( SourceUrl $sourceUrl ) {
133        // We assume the wiki holding the config pages uses the same configuration.
134        $scriptPath = $this->mainConfig->get( 'ScriptPath' );
135        $apiUrl = $this->configServer . $scriptPath . '/api.php';
136        $apiParameters = [
137            'action' => 'query',
138            'format' => 'json',
139            'titles' => $this->getQueryParamTitle( $sourceUrl ),
140            'prop' => 'revisions',
141            'formatversion' => 2,
142            'rvprop' => 'content',
143            'rvlimit' => 1,
144            'rvdir' => 'older'
145        ];
146
147        try {
148            $request = $this->httpRequestExecutor->execute( $apiUrl, $apiParameters );
149        } catch ( HttpRequestException $e ) {
150            throw new LocalizedImportException( [ 'fileimporter-api-failedtogetinfo',
151                $apiUrl ], $e );
152        }
153
154        return json_decode( $request->getContent(), true );
155    }
156
157    /**
158     * @param SourceUrl $sourceUrl
159     *
160     * @return string
161     */
162    private function getQueryParamTitle( SourceUrl $sourceUrl ) {
163        $domain = $this->getHostWithoutTopLevelDomain( $sourceUrl );
164
165        if ( ctype_alpha( $domain ) ) {
166            // Default to "www.mediawiki", even when the source URL was "https://mediawiki.org/…"
167            $domain = 'www.' . $domain;
168        }
169
170        return str_replace( ' ', '_', $this->configBasePageName ) . $domain;
171    }
172
173    /**
174     * @param SourceUrl $sourceUrl
175     *
176     * @return string Full host with all subdomains, but without the top-level domain (if a
177     *  top-level domain was given), e.g. "en.wikipedia".
178     */
179    private function getHostWithoutTopLevelDomain( SourceUrl $sourceUrl ) {
180        $domain = $sourceUrl->getHost();
181
182        // Reuse the original configuration pages for test imports from the Beta cluster
183        $domain = str_replace( '.beta.wmflabs.org', '.org', $domain );
184
185        return preg_replace( '/\.\w+$/', '', $domain );
186    }
187
188}