Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 67
0.00% covered (danger)
0.00%
0 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
ExtDistProvider
0.00% covered (danger)
0.00%
0 / 67
0.00% covered (danger)
0.00%
0 / 13
462
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
12
 setLogger
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 factory
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getProviderFor
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getEnabledBranches
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 substituteUrlVariables
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 getCacheDuration
n/a
0 / 0
n/a
0 / 0
0
 hasBranch
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getBranchSha
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getBranches
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
20
 getExpectedTarballName
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getTarballLocation
n/a
0 / 0
n/a
0 / 0
0
 fetchBranches
n/a
0 / 0
n/a
0 / 0
0
 getRepositoryList
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 fetchRepositoryList
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getSourceURL
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace MediaWiki\Extension\ExtensionDistributor\Providers;
4
5use MediaWiki\MediaWikiServices;
6use Psr\Log\LoggerAwareInterface;
7use Psr\Log\LoggerInterface;
8use Psr\Log\NullLogger;
9
10/**
11 * Base ExtensionDistributor provider
12 *
13 * @author Legoktm
14 *
15 * Parameters that apply to all providers:
16 *  proxy (optional) - HTTP proxy used in requests
17 *  apiUrl - API endpoint to use
18 *  tarballUrl - Where tarballs are stored
19 *  sourceUrl - URL to the VCS repository
20 *  'repoType' - Either "skins" or "extensions"
21 */
22abstract class ExtDistProvider implements LoggerAwareInterface {
23
24    public const EXTENSIONS = 'extensions';
25    public const SKINS = 'skins';
26
27    /**
28     * @var string|bool Proxy url, false if no proxy
29     */
30    protected $proxy = false;
31
32    protected $tarballUrl;
33    protected $tarballName;
34    protected $apiUrl;
35    protected $repoType;
36    protected $sourceUrl = false;
37    /**
38     * @var LoggerInterface
39     */
40    protected $logger;
41
42    /**
43     * @var array Instance cache of repo name => branches
44     */
45    private $branches = [];
46
47    /**
48     * @param array $options
49     */
50    public function __construct( array $options ) {
51        if ( isset( $options['proxy'] ) ) {
52            $this->proxy = $options['proxy'];
53        }
54        $this->tarballUrl = $options['tarballUrl'];
55        $this->tarballName = $options['tarballName'];
56        $this->apiUrl = $options['apiUrl'];
57        $this->repoType = $options['repoType'];
58        if ( isset( $options['sourceUrl'] ) ) {
59            $this->sourceUrl = $options['sourceUrl'];
60        }
61        $this->setLogger( new NullLogger() );
62    }
63
64    public function setLogger( LoggerInterface $logger ) {
65        $this->logger = $logger;
66    }
67
68    /**
69     * Main entry point
70     *
71     * @param array $options
72     * @return ExtDistProvider
73     */
74    public static function factory( array $options ) {
75        return new $options['class']( $options );
76    }
77
78    /**
79     * Get the provider configured for the given type
80     *
81     * @param string $type
82     * @return ExtDistProvider|null
83     */
84    public static function getProviderFor( $type ) {
85        global $wgExtDistAPIConfig;
86
87        if ( !$wgExtDistAPIConfig ) {
88            return null;
89        }
90        return self::factory(
91            $wgExtDistAPIConfig + [ 'repoType' => $type ]
92        );
93    }
94
95    /**
96     * List of branches enabled by configuration
97     *
98     * @return array
99     */
100    protected function getEnabledBranches() {
101        global $wgExtDistSnapshotRefs;
102        return $wgExtDistSnapshotRefs;
103    }
104
105    /**
106     * Quick helper for subclasses to replace $EXT, $REF, $SHA, and $TYPE
107     *
108     * @param string $url
109     * @param string $ext
110     * @param string $version
111     * @param string $hash
112     * @return string
113     */
114    protected function substituteUrlVariables( $url, $ext = '', $version = '', $hash = '' ) {
115        return str_replace(
116            [ '$EXT', '$REF', '$SHA', '$TYPE' ],
117            [
118                rawurlencode( $ext ), rawurlencode( $version ),
119                rawurlencode( $hash ), rawurlencode( $this->repoType )
120            ],
121            $url
122        );
123    }
124
125    /**
126     * How long we cache the list of branches for
127     *
128     * @return int
129     */
130    abstract protected function getCacheDuration();
131
132    /**
133     * Does $name have a branch named $version?
134     *
135     * @param string $name
136     * @param string $version
137     * @return bool
138     */
139    public function hasBranch( $name, $version ) {
140        $branches = $this->getBranches( $name );
141        return isset( $branches[$version] );
142    }
143
144    /**
145     * Returns short sha hash for the branch. Callers should have
146     * called hasBranch first.
147     *
148     * If you need the full hash, use getBranches directly.
149     *
150     * @param string $name
151     * @param string $version
152     * @return string
153     */
154    public function getBranchSha( $name, $version ) {
155        $branches = $this->getBranches( $name );
156        return substr( $branches[$version], 0, 7 );
157    }
158
159    /**
160     * Return an array of validated branch names the
161     * repo has.
162     *
163     * @param string $name
164     * @return array branch name => sha1
165     */
166    public function getBranches( $name ) {
167        // We'll probably call this function multiple
168        // times, so use instance cache
169        if ( isset( $this->branches[$name] ) ) {
170            return $this->branches[$name];
171        }
172
173        // If a new release branch is added, the repos probably also
174        // got that branch, so vary cache on that
175        $branches = $this->getEnabledBranches();
176        sort( $branches );
177        $confHash = md5( serialize( $branches ) );
178
179        $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
180        $data = $cache->getWithSetCallback(
181            $cache->makeGlobalKey( "extdist-branches", $this->repoType, $name, $confHash ),
182            $this->getCacheDuration(),
183            function () use ( $name ) {
184                return $this->fetchBranches( $name );
185            }
186        );
187
188        $enabled = [];
189        foreach ( $data as $branch => $sha ) {
190            if ( in_array( $branch, $this->getEnabledBranches() ) ) {
191                $enabled[$branch] = $sha;
192            }
193        }
194
195        $this->branches[$name] = $enabled;
196
197        return $enabled;
198    }
199
200    /**
201     * What do we expect the tarball name to be when downloaded
202     *
203     * @param string $ext
204     * @param string $version
205     * @return string
206     */
207    public function getExpectedTarballName( $ext, $version ) {
208        $shortHash = $this->getBranchSha( $ext, $version );
209        return $this->substituteUrlVariables( $this->tarballName, $ext, $version, $shortHash );
210    }
211
212    /**
213     * Returns a fully qualified URL pointing to the location of the
214     * tarball, given the repo name and version.
215     *
216     * @param string $ext
217     * @param string $version
218     * @return string
219     */
220    abstract public function getTarballLocation( $ext, $version );
221
222    /**
223     * Should return an array of branches that
224     * the repo has, don't validate whether they
225     * are in $wgExtDistSnapshotRefs
226     *
227     * @param string $name
228     * @return array
229     */
230    abstract protected function fetchBranches( $name );
231
232    /**
233     * Get a list of repository names
234     *
235     * @return array
236     */
237    public function getRepositoryList() {
238        $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
239
240        return $cache->getWithSetCallback(
241            $cache->makeGlobalKey( "extdist-list", $this->repoType ),
242            $cache::TTL_HOUR,
243            function () {
244                return $this->fetchRepositoryList();
245            }
246        );
247    }
248
249    /**
250     * Default implementation that requires a
251     * list of repository names to be available
252     * at $wgExtDistListFile.
253     *
254     * @return array
255     */
256    protected function fetchRepositoryList() {
257        global $wgExtDistListFile;
258        $extList = [];
259
260        $httpFactory = MediaWikiServices::getInstance()->getHttpRequestFactory();
261        $res = $httpFactory->get( $wgExtDistListFile, [], __METHOD__ );
262        if ( $res ) {
263            $extList = array_filter( array_map( 'trim', explode( "\n", $res ) ) );
264        }
265
266        return $extList;
267    }
268
269    /**
270     * Get the URL to the source VCS repository
271     *
272     * @param string $name
273     * @return bool|string false if not configured
274     */
275    public function getSourceURL( $name ) {
276        if ( $this->sourceUrl !== false ) {
277            return $this->substituteUrlVariables( $this->sourceUrl, $name );
278        } else {
279            return false;
280        }
281    }
282}