These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | use BringYourOwnIdeas\Maintenance\Util\ComposerLoader; |
||
4 | use Packagist\Api\Client; |
||
5 | |||
6 | /** |
||
7 | * Task which does the actual checking of updates |
||
8 | * |
||
9 | * Originally from https://github.com/XploreNet/silverstripe-composerupdates |
||
10 | * |
||
11 | * @author Matt Dwen |
||
12 | * @license MIT |
||
13 | */ |
||
14 | class CheckComposerUpdatesTask extends BuildTask |
||
15 | { |
||
16 | /** |
||
17 | * @var string |
||
18 | */ |
||
19 | protected $title = 'Composer update checker'; |
||
20 | |||
21 | /** |
||
22 | * @var string |
||
23 | */ |
||
24 | protected $description = 'Checks if any composer dependencies can be updated.'; |
||
25 | |||
26 | private static $dependencies = [ |
||
0 ignored issues
–
show
|
|||
27 | 'PackagistClient' => '%$Packagist\\Api\\Client', |
||
28 | 'ComposerLoader' => '%$BringYourOwnIdeas\\Maintenance\\Util\\ComposerLoader', |
||
29 | ]; |
||
30 | |||
31 | /** |
||
32 | * Minimum required stability defined in the site composer.json |
||
33 | * |
||
34 | * @var string |
||
35 | */ |
||
36 | protected $minimumStability; |
||
37 | |||
38 | /** |
||
39 | * Whether or not to prefer stable packages |
||
40 | * |
||
41 | * @var bool |
||
42 | */ |
||
43 | protected $preferStable; |
||
44 | |||
45 | /** |
||
46 | * Known stability values |
||
47 | * |
||
48 | * @var array |
||
49 | */ |
||
50 | protected $stabilityOptions = array( |
||
51 | 'dev', |
||
52 | 'alpha', |
||
53 | 'beta', |
||
54 | 'rc', |
||
55 | 'stable' |
||
56 | ); |
||
57 | |||
58 | /** |
||
59 | * @var Client |
||
60 | */ |
||
61 | protected $packagistClient; |
||
62 | |||
63 | /** |
||
64 | * @var ComposerLoader |
||
65 | */ |
||
66 | protected $composerLoader; |
||
67 | |||
68 | /** |
||
69 | * Retrieve an array of primary composer dependencies from composer.json |
||
70 | * |
||
71 | * @return array |
||
72 | */ |
||
73 | protected function getPackages() |
||
74 | { |
||
75 | $composerJson = $this->getComposerLoader()->getJson(); |
||
76 | |||
77 | ini_set('display_errors', 1); |
||
78 | error_reporting(E_ALL); |
||
79 | |||
80 | // Set the stability parameters |
||
81 | $this->setMinimumStability( |
||
82 | isset($composerJson->{'minimum-stability'}) ? $composerJson->{'minimum-stability'} : 'stable' |
||
83 | ); |
||
84 | |||
85 | $this->setPreferStable( |
||
86 | isset($composerJson->{'prefer-stable'}) ? $composerJson->{'prefer-stable'} : true |
||
87 | ); |
||
88 | |||
89 | $packages = []; |
||
90 | foreach ($composerJson->require as $package => $version) { |
||
91 | // Ensure there's a / in the name, probably not an addon with it |
||
92 | if (!strpos($package, '/')) { |
||
93 | continue; |
||
94 | } |
||
95 | |||
96 | $packages[] = $package; |
||
97 | } |
||
98 | |||
99 | return $packages; |
||
100 | } |
||
101 | |||
102 | /** |
||
103 | * Return an array of all Composer dependencies from composer.lock |
||
104 | * |
||
105 | * Example: `['silverstripe/cms' => '3.4.1', ...]` |
||
106 | * |
||
107 | * @return array |
||
108 | */ |
||
109 | protected function getDependencies() |
||
110 | { |
||
111 | $packages = []; |
||
112 | foreach ($this->getComposerLoader()->getLock()->packages as $package) { |
||
113 | $packages[$package->name] = $package->version; |
||
114 | } |
||
115 | return $packages; |
||
116 | } |
||
117 | |||
118 | /** |
||
119 | * Check if an available version is better than the current version, |
||
120 | * considering stability requirements |
||
121 | * |
||
122 | * Returns FALSE if no update is available. |
||
123 | * Returns the best available version if an update is available. |
||
124 | * |
||
125 | * @param string $currentVersion |
||
126 | * @param string[] $availableVersions |
||
127 | * @return bool|string |
||
0 ignored issues
–
show
|
|||
128 | */ |
||
129 | protected function hasUpdate($currentVersion, $availableVersions) |
||
130 | { |
||
131 | $currentVersion = strtolower($currentVersion); |
||
132 | |||
133 | // Check there are some versions |
||
134 | if (count($availableVersions) < 1) { |
||
135 | return false; |
||
136 | } |
||
137 | |||
138 | // If this is dev-master, compare the hashes |
||
139 | if ($currentVersion === 'dev-master') { |
||
140 | return $this->hasUpdateOnDevMaster($availableVersions); |
||
141 | } |
||
142 | |||
143 | // Loop through each available version |
||
144 | $currentStability = $this->getStability($currentVersion); |
||
145 | $bestVersion = $currentVersion; |
||
146 | $bestStability = $currentStability; |
||
147 | $availableVersions = array_reverse($availableVersions, true); |
||
148 | foreach ($availableVersions as $version => $details) { |
||
149 | // Get the stability of the version |
||
150 | $versionStability = $this->getStability($version); |
||
151 | |||
152 | // Does this meet minimum stability |
||
153 | if (!$this->isStableEnough($this->getMinimumStability(), $versionStability)) { |
||
154 | continue; |
||
155 | } |
||
156 | |||
157 | if ($this->getPreferStable()) { |
||
158 | // A simple php version compare rules out the dumb stuff |
||
159 | if (version_compare($bestVersion, $version) !== -1) { |
||
160 | continue; |
||
161 | } |
||
162 | } else { |
||
163 | // We're doing a straight version compare |
||
164 | $pureBestVersion = $this->getPureVersion($bestVersion); |
||
165 | $pureVersion = $this->getPureVersion($version); |
||
166 | |||
167 | // Checkout the version |
||
168 | $continue = false; |
||
169 | switch (version_compare($pureBestVersion, $pureVersion)) { |
||
170 | case -1: |
||
171 | // The version is better, take it |
||
172 | break; |
||
173 | |||
174 | case 0: |
||
175 | // The version is the same. |
||
176 | // Do another straight version compare to rule out rc1 vs rc2 etc... |
||
177 | if ($bestStability == $versionStability) { |
||
178 | if (version_compare($bestVersion, $version) !== -1) { |
||
179 | $continue = true; |
||
180 | break; |
||
181 | } |
||
182 | } |
||
183 | break; |
||
184 | |||
185 | case 1: |
||
186 | // The version is worse, ignore it |
||
187 | $continue = true; |
||
188 | break; |
||
189 | } |
||
190 | |||
191 | if ($continue) { |
||
192 | continue; |
||
193 | } |
||
194 | } |
||
195 | |||
196 | $bestVersion = $version; |
||
197 | $bestStability = $versionStability; |
||
198 | } |
||
199 | |||
200 | if ($bestVersion !== $currentVersion || $bestStability !== $currentStability) { |
||
201 | if ($bestStability === 'stable') { |
||
202 | return $bestVersion; |
||
203 | } |
||
204 | |||
205 | return $bestVersion . '-' . $bestStability; |
||
206 | } |
||
207 | |||
208 | return false; |
||
209 | } |
||
210 | |||
211 | /** |
||
212 | * Check the latest hash on the dev-master branch, and return it if different to the local hash |
||
213 | * |
||
214 | * FALSE is returned if the hash is the same. |
||
215 | * |
||
216 | * @param $availableVersions |
||
217 | * @return bool|string |
||
218 | */ |
||
219 | protected function hasUpdateOnDevMaster($availableVersions) |
||
220 | { |
||
221 | // Get the dev-master version |
||
222 | $devMaster = $availableVersions['dev-master']; |
||
223 | |||
224 | // Sneak the name of the package |
||
225 | $packageName = $devMaster->getName(); |
||
226 | |||
227 | // Get the local package details |
||
228 | $localPackage = $this->getLocalPackage($packageName); |
||
229 | |||
230 | // What's the current hash? |
||
231 | $localHash = $localPackage->source->reference; |
||
232 | |||
233 | // What's the latest hash in the available versions |
||
234 | $remoteHash = $devMaster->getSource()->getReference(); |
||
235 | |||
236 | // return either the new hash or false |
||
237 | return ($localHash != $remoteHash) ? $remoteHash : false; |
||
238 | } |
||
239 | |||
240 | /** |
||
241 | * Return details from composer.lock for a specific package |
||
242 | * |
||
243 | * @param string $packageName |
||
244 | * @return object |
||
245 | * @throws Exception if package cannot be found in composer.lock |
||
246 | */ |
||
247 | protected function getLocalPackage($packageName) |
||
248 | { |
||
249 | foreach ($this->getComposerLoader()->getLock()->packages as $package) { |
||
250 | if ($package->name == $packageName) { |
||
251 | return $package; |
||
252 | } |
||
253 | } |
||
254 | |||
255 | throw new Exception('Cannot locate local package ' . $packageName); |
||
256 | } |
||
257 | |||
258 | /** |
||
259 | * Retrieve the pure numerical version |
||
260 | * |
||
261 | * @param string $version |
||
262 | * @return string|null |
||
263 | */ |
||
264 | protected function getPureVersion($version) |
||
265 | { |
||
266 | $matches = []; |
||
267 | |||
268 | preg_match("/^(\d+\\.)?(\d+\\.)?(\\*|\d+)/", $version, $matches); |
||
269 | |||
270 | if (count($matches) > 0) { |
||
271 | return $matches[0]; |
||
272 | } |
||
273 | |||
274 | return null; |
||
275 | } |
||
276 | |||
277 | /** |
||
278 | * Determine the stability of a given version |
||
279 | * |
||
280 | * @param string $version |
||
281 | * @return string |
||
282 | */ |
||
283 | protected function getStability($version) |
||
284 | { |
||
285 | $version = strtolower($version); |
||
286 | |||
287 | foreach ($this->getStabilityOptions() as $option) { |
||
288 | if (strpos($version, $option) !== false) { |
||
289 | return $option; |
||
290 | } |
||
291 | } |
||
292 | |||
293 | return 'stable'; |
||
294 | } |
||
295 | |||
296 | /** |
||
297 | * Return a numerical representation of a stability |
||
298 | * |
||
299 | * Higher is more stable |
||
300 | * |
||
301 | * @param string $stability |
||
302 | * @return int |
||
0 ignored issues
–
show
|
|||
303 | * @throws Exception If the stability is unknown |
||
304 | */ |
||
305 | protected function getStabilityIndex($stability) |
||
306 | { |
||
307 | $stability = strtolower($stability); |
||
308 | |||
309 | $index = array_search($stability, $this->getStabilityOptions(), true); |
||
310 | |||
311 | if ($index === false) { |
||
312 | throw new Exception("Unknown stability: $stability"); |
||
313 | } |
||
314 | |||
315 | return $index; |
||
316 | } |
||
317 | |||
318 | /** |
||
319 | * Check if a stability meets a given minimum requirement |
||
320 | * |
||
321 | * @param $currentStability |
||
322 | * @param $possibleStability |
||
323 | * @return bool |
||
324 | */ |
||
325 | protected function isStableEnough($currentStability, $possibleStability) |
||
326 | { |
||
327 | $minimumIndex = $this->getStabilityIndex($currentStability); |
||
328 | $possibleIndex = $this->getStabilityIndex($possibleStability); |
||
329 | |||
330 | return ($possibleIndex >= $minimumIndex); |
||
331 | } |
||
332 | |||
333 | /** |
||
334 | * Record package details in the database |
||
335 | * |
||
336 | * @param string $package Name of the Composer Package |
||
337 | * @param string $installed Currently installed version |
||
338 | * @param string|boolean $latest The latest available version |
||
339 | */ |
||
340 | protected function recordUpdate($package, $installed, $latest) |
||
341 | { |
||
342 | // Is there a record already for the package? If so find it. |
||
343 | $packages = Package::get()->filter(['Name' => $package]); |
||
344 | |||
345 | // Get the hash installed |
||
346 | $localPackage = $this->getLocalPackage($package); |
||
347 | $installedHash = $localPackage->source->reference; |
||
348 | |||
349 | // if there is already one use it otherwise create a new data object |
||
350 | if ($packages->count() > 0) { |
||
351 | $update = $packages->first(); |
||
352 | } else { |
||
353 | $update = Package::create(); |
||
354 | $update->Name = $package; |
||
355 | $update->Version = $installed; |
||
356 | $update->VersionHash = $installedHash; |
||
357 | } |
||
358 | |||
359 | // Set the new details and save it |
||
360 | $update->LatestVersion = $latest; |
||
361 | $update->write(); |
||
362 | } |
||
363 | |||
364 | /** |
||
365 | * runs the actual steps to verify if there are updates available |
||
366 | * |
||
367 | * @param SS_HTTPRequest $request |
||
368 | */ |
||
369 | public function run($request) |
||
370 | { |
||
371 | // Retrieve the packages |
||
372 | $packages = $this->getPackages(); |
||
373 | $dependencies = $this->getDependencies(); |
||
374 | |||
375 | // run through the packages and check each for updates |
||
376 | foreach ($packages as $package) { |
||
377 | // verify that we need to check this package. |
||
378 | if (!isset($dependencies[$package])) { |
||
379 | continue; |
||
380 | } else { |
||
381 | // get information about this package from packagist. |
||
382 | try { |
||
383 | $latest = $this->getPackagistClient()->get($package); |
||
384 | } catch (Guzzle\Http\Exception\ClientErrorResponseException $e) { |
||
0 ignored issues
–
show
The class
Guzzle\Http\Exception\ClientErrorResponseException does not exist. Did you forget a USE statement, or did you not list all dependencies?
Scrutinizer analyzes your It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.
Loading history...
|
|||
385 | SS_Log::log($e->getMessage(), SS_Log::WARN); |
||
386 | continue; |
||
387 | } |
||
388 | |||
389 | // Check if there is a newer version |
||
390 | $currentVersion = $dependencies[$package]; |
||
391 | $result = $this->hasUpdate($currentVersion, $latest->getVersions()); |
||
392 | |||
393 | // Check if there is a newer version and if so record the update |
||
394 | if ($result !== false) { |
||
395 | $this->recordUpdate($package, $currentVersion, $result); |
||
396 | } |
||
397 | } |
||
398 | } |
||
399 | |||
400 | // finished message |
||
401 | $this->message('The task finished running. You can find the updated information in the database now.'); |
||
402 | } |
||
403 | |||
404 | /** |
||
405 | * prints a message during the run of the task |
||
406 | * |
||
407 | * @param string $text |
||
408 | */ |
||
409 | protected function message($text) |
||
410 | { |
||
411 | if (!Director::is_cli()) { |
||
412 | $text = '<p>' . $text . '</p>'; |
||
413 | } |
||
414 | |||
415 | echo $text . PHP_EOL; |
||
416 | } |
||
417 | |||
418 | /** |
||
419 | * @param string $minimumStability |
||
420 | * @return $this |
||
421 | */ |
||
422 | public function setMinimumStability($minimumStability) |
||
423 | { |
||
424 | $this->minimumStability = $minimumStability; |
||
425 | return $this; |
||
426 | } |
||
427 | |||
428 | /** |
||
429 | * @return string |
||
430 | */ |
||
431 | public function getMinimumStability() |
||
432 | { |
||
433 | return $this->minimumStability; |
||
434 | } |
||
435 | |||
436 | /** |
||
437 | * @param bool $preferStable |
||
438 | * @return $this |
||
439 | */ |
||
440 | public function setPreferStable($preferStable) |
||
441 | { |
||
442 | $this->preferStable = $preferStable; |
||
443 | return $this; |
||
444 | } |
||
445 | |||
446 | /** |
||
447 | * @return bool |
||
448 | */ |
||
449 | public function getPreferStable() |
||
450 | { |
||
451 | return $this->preferStable; |
||
452 | } |
||
453 | |||
454 | /** |
||
455 | * @param array $stabilityOptions |
||
456 | * @return $this |
||
457 | */ |
||
458 | public function setStabilityOptions($stabilityOptions) |
||
459 | { |
||
460 | $this->stabilityOptions = $stabilityOptions; |
||
461 | return $this; |
||
462 | } |
||
463 | |||
464 | /** |
||
465 | * @return array |
||
466 | */ |
||
467 | public function getStabilityOptions() |
||
468 | { |
||
469 | return $this->stabilityOptions; |
||
470 | } |
||
471 | |||
472 | /** |
||
473 | * @return Client |
||
474 | */ |
||
475 | public function getPackagistClient() |
||
476 | { |
||
477 | return $this->packagistClient; |
||
478 | } |
||
479 | |||
480 | /** |
||
481 | * @param Client $packagistClient |
||
482 | */ |
||
483 | public function setPackagistClient(Client $packagistClient) |
||
484 | { |
||
485 | $this->packagistClient = $packagistClient; |
||
486 | return $this; |
||
487 | } |
||
488 | |||
489 | /** |
||
490 | * @param ComposerLoader $composerLoader |
||
491 | * @return $this |
||
492 | */ |
||
493 | public function setComposerLoader(ComposerLoader $composerLoader) |
||
494 | { |
||
495 | $this->composerLoader = $composerLoader; |
||
496 | return $this; |
||
497 | } |
||
498 | |||
499 | /** |
||
500 | * @return ComposerLoader |
||
501 | */ |
||
502 | public function getComposerLoader() |
||
503 | { |
||
504 | return $this->composerLoader; |
||
505 | } |
||
506 | } |
||
507 |
This check marks private properties in classes that are never used. Those properties can be removed.