Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 67 |
|
0.00% |
0 / 13 |
CRAP | |
0.00% |
0 / 1 |
ExtDistProvider | |
0.00% |
0 / 67 |
|
0.00% |
0 / 13 |
462 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
setLogger | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
factory | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getProviderFor | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
getEnabledBranches | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
substituteUrlVariables | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
getCacheDuration | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
hasBranch | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getBranchSha | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getBranches | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
20 | |||
getExpectedTarballName | |
0.00% |
0 / 2 |
|
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% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
fetchRepositoryList | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
getSourceURL | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\ExtensionDistributor\Providers; |
4 | |
5 | use MediaWiki\MediaWikiServices; |
6 | use Psr\Log\LoggerAwareInterface; |
7 | use Psr\Log\LoggerInterface; |
8 | use 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 | */ |
22 | abstract 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 | } |