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