|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
use Composer\Package\AliasPackage; |
|
4
|
|
|
use Composer\Package\CompletePackage; |
|
5
|
|
|
use Guzzle\Http\Exception\ClientErrorResponseException; |
|
6
|
|
|
use SilverStripe\Elastica\ElasticaService; |
|
7
|
|
|
use Packagist\Api\Result\Package; |
|
8
|
|
|
use Composer\Package\Version\VersionParser; |
|
9
|
|
|
|
|
10
|
|
|
/** |
|
11
|
|
|
* Updates all add-ons from Packagist. |
|
12
|
|
|
*/ |
|
13
|
|
|
class AddonUpdater { |
|
14
|
|
|
|
|
15
|
|
|
/** |
|
16
|
|
|
* @var PackagistService |
|
17
|
|
|
*/ |
|
18
|
|
|
private $packagist; |
|
19
|
|
|
|
|
20
|
|
|
/** |
|
21
|
|
|
* @var SilverStripe\Elastica\ElasticaService |
|
22
|
|
|
*/ |
|
23
|
|
|
private $elastica; |
|
24
|
|
|
|
|
25
|
|
|
/** |
|
26
|
|
|
* @var ResqueService |
|
27
|
|
|
*/ |
|
28
|
|
|
private $resque; |
|
29
|
|
|
|
|
30
|
|
|
/** |
|
31
|
|
|
* @var SilverStripeVersion[] |
|
32
|
|
|
*/ |
|
33
|
|
|
private $silverstripes; |
|
34
|
|
|
|
|
35
|
|
|
public function __construct( |
|
36
|
|
|
PackagistService $packagist, |
|
37
|
|
|
ElasticaService $elastica, |
|
38
|
|
|
ResqueService $resque |
|
39
|
|
|
) { |
|
40
|
|
|
$this->packagist = $packagist; |
|
41
|
|
|
$this->elastica = $elastica; |
|
42
|
|
|
$this->resque = $resque; |
|
43
|
|
|
|
|
44
|
|
|
$this->setSilverStripeVersions(SilverStripeVersion::get()); |
|
45
|
|
|
} |
|
46
|
|
|
|
|
47
|
|
|
/** |
|
48
|
|
|
* Updates all add-ons. |
|
49
|
|
|
* |
|
50
|
|
|
* @param Boolean Clear existing addons before updating them. |
|
51
|
|
|
* Will also clear their search index, and cascade the delete for associated data. |
|
52
|
|
|
* @param Array Limit to specific addons, using their name incl. vendor prefix. |
|
53
|
|
|
*/ |
|
54
|
|
|
public function update($clear = false, $limitAddons = null) |
|
55
|
|
|
{ |
|
56
|
|
|
if ($clear && !$limitAddons) { |
|
57
|
|
|
Addon::get()->removeAll(); |
|
58
|
|
|
AddonAuthor::get()->removeAll(); |
|
59
|
|
|
AddonKeyword::get()->removeAll(); |
|
60
|
|
|
AddonLink::get()->removeAll(); |
|
61
|
|
|
AddonVendor::get()->removeAll(); |
|
62
|
|
|
AddonVersion::get()->removeAll(); |
|
63
|
|
|
} |
|
64
|
|
|
|
|
65
|
|
|
// This call to packagist can be expensive. Requests are served from a cache if usePackagistCache() returns true |
|
66
|
|
|
$cache = SS_Cache::factory('addons'); |
|
67
|
|
|
if ($this->usePackagistCache() && $packages = $cache->load('packagist')) { |
|
68
|
|
|
$packages = unserialize($packages); |
|
69
|
|
|
} else { |
|
70
|
|
|
$packages = $this->packagist->getPackages(); |
|
71
|
|
|
$cache->save(serialize($packages), 'packagist'); |
|
72
|
|
|
} |
|
73
|
|
|
$this->elastica->startBulkIndex(); |
|
74
|
|
|
|
|
75
|
|
|
foreach ($packages as $package) { |
|
76
|
|
|
/** @var Packagist\Api\Result\Package $package */ |
|
77
|
|
|
|
|
78
|
|
|
$isAbandoned = (property_exists($package, 'abandoned') && $package->abandoned); |
|
|
|
|
|
|
79
|
|
|
$name = $package->getName(); |
|
80
|
|
|
$versions = $package->getVersions(); |
|
81
|
|
|
|
|
82
|
|
|
if ($limitAddons && !in_array($name, $limitAddons)) { |
|
83
|
|
|
continue; |
|
84
|
|
|
} |
|
85
|
|
|
|
|
86
|
|
|
$addon = Addon::get()->filter('Name', $name)->first(); |
|
87
|
|
|
|
|
88
|
|
|
if (!$addon) { |
|
89
|
|
|
if ($isAbandoned) { |
|
90
|
|
|
echo ' - Skipping abandoned package: ' . $name, PHP_EOL; |
|
91
|
|
|
continue; |
|
92
|
|
|
} |
|
93
|
|
|
|
|
94
|
|
|
$addon = new Addon(); |
|
95
|
|
|
$addon->Name = $name; |
|
|
|
|
|
|
96
|
|
|
$addon->write(); |
|
97
|
|
|
} |
|
98
|
|
|
|
|
99
|
|
|
if ($isAbandoned) { |
|
100
|
|
|
echo ' - Removing abandoned package: ' . $name, PHP_EOL; |
|
101
|
|
|
$addon->delete(); |
|
102
|
|
|
continue; |
|
103
|
|
|
} |
|
104
|
|
|
|
|
105
|
|
|
usort($versions, function ($a, $b) { |
|
106
|
|
|
return version_compare($a->getVersionNormalized(), $b->getVersionNormalized()); |
|
107
|
|
|
}); |
|
108
|
|
|
|
|
109
|
|
|
$this->updateAddon($addon, $package, $versions); |
|
|
|
|
|
|
110
|
|
|
} |
|
111
|
|
|
|
|
112
|
|
|
$this->elastica->endBulkIndex(); |
|
113
|
|
|
} |
|
114
|
|
|
|
|
115
|
|
|
|
|
116
|
|
|
|
|
117
|
|
|
/** |
|
118
|
|
|
* Check whether or not we should contact packagist or use a cached version. This allows to speed up the task |
|
119
|
|
|
* during development. |
|
120
|
|
|
* |
|
121
|
|
|
* @return bool |
|
122
|
|
|
*/ |
|
123
|
|
|
protected function usePackagistCache() { |
|
124
|
|
|
return Director::isDev(); |
|
125
|
|
|
} |
|
126
|
|
|
|
|
127
|
|
|
private function updateAddon(Addon $addon, Package $package, array $versions) { |
|
128
|
|
|
if (!$addon->VendorID) { |
|
|
|
|
|
|
129
|
|
|
$vendor = AddonVendor::get()->filter('Name', $addon->VendorName())->first(); |
|
130
|
|
|
|
|
131
|
|
|
if (!$vendor) { |
|
132
|
|
|
$vendor = new AddonVendor(); |
|
133
|
|
|
$vendor->Name = $addon->VendorName(); |
|
|
|
|
|
|
134
|
|
|
$vendor->write(); |
|
135
|
|
|
} |
|
136
|
|
|
|
|
137
|
|
|
$addon->VendorID = $vendor->ID; |
|
|
|
|
|
|
138
|
|
|
} |
|
139
|
|
|
|
|
140
|
|
|
$addon->Type = str_replace('silverstripe-', '', $package->getType()); |
|
|
|
|
|
|
141
|
|
|
$addon->Description = $package->getDescription(); |
|
|
|
|
|
|
142
|
|
|
$addon->Released = strtotime($package->getTime()); |
|
|
|
|
|
|
143
|
|
|
$addon->Repository = $package->getRepository(); |
|
|
|
|
|
|
144
|
|
|
$addon->Downloads = $package->getDownloads()->getTotal(); |
|
|
|
|
|
|
145
|
|
|
$addon->DownloadsMonthly = $package->getDownloads()->getMonthly(); |
|
|
|
|
|
|
146
|
|
|
$addon->Favers = $package->getFavers(); |
|
|
|
|
|
|
147
|
|
|
|
|
148
|
|
|
foreach ($versions as $version) { |
|
149
|
|
|
$this->updateVersion($addon, $version); |
|
150
|
|
|
} |
|
151
|
|
|
|
|
152
|
|
|
// If there is no build, then queue one up if the add-on requires |
|
153
|
|
|
// one. |
|
154
|
|
|
if (!$addon->BuildQueued) { |
|
|
|
|
|
|
155
|
|
|
if (!$addon->BuiltAt) { |
|
|
|
|
|
|
156
|
|
|
$this->resque->queue('first_build', 'BuildAddonJob', array('id' => $addon->ID)); |
|
157
|
|
|
$addon->BuildQueued = true; |
|
|
|
|
|
|
158
|
|
|
} else { |
|
159
|
|
|
$built = (int) $addon->obj('BuiltAt')->format('U'); |
|
160
|
|
|
|
|
161
|
|
|
foreach ($versions as $version) { |
|
162
|
|
|
if (strtotime($version->getTime()) > $built) { |
|
163
|
|
|
$this->resque->queue('update', 'BuildAddonJob', array('id' => $addon->ID)); |
|
164
|
|
|
$addon->BuildQueued = true; |
|
|
|
|
|
|
165
|
|
|
|
|
166
|
|
|
break; |
|
167
|
|
|
} |
|
168
|
|
|
} |
|
169
|
|
|
} |
|
170
|
|
|
} |
|
171
|
|
|
|
|
172
|
|
|
$addon->LastUpdated = time(); |
|
|
|
|
|
|
173
|
|
|
$addon->write(); |
|
174
|
|
|
} |
|
175
|
|
|
|
|
176
|
|
|
private function updateVersion(Addon $addon, Version $package) { |
|
177
|
|
|
$version = null; |
|
178
|
|
|
|
|
179
|
|
|
if ($addon->isInDB()) { |
|
180
|
|
|
$version = $addon->Versions()->filter('Version', $package->getVersionNormalized())->first(); |
|
|
|
|
|
|
181
|
|
|
} |
|
182
|
|
|
|
|
183
|
|
|
if (!$version) { |
|
184
|
|
|
$version = new AddonVersion(); |
|
185
|
|
|
} |
|
186
|
|
|
|
|
187
|
|
|
$version->Name = $package->getName(); |
|
188
|
|
|
$version->Type = str_replace('silverstripe-', '', $package->getType()); |
|
189
|
|
|
$version->Description = $package->getDescription(); |
|
190
|
|
|
$version->Released = strtotime($package->getTime()); |
|
191
|
|
|
$keywords = $package->getKeywords(); |
|
192
|
|
|
|
|
193
|
|
|
if ($keywords) { |
|
194
|
|
|
foreach ($keywords as $keyword) { |
|
195
|
|
|
$keyword = AddonKeyword::get_by_name($keyword); |
|
196
|
|
|
|
|
197
|
|
|
$addon->Keywords()->add($keyword); |
|
|
|
|
|
|
198
|
|
|
$version->Keywords()->add($keyword); |
|
199
|
|
|
} |
|
200
|
|
|
} |
|
201
|
|
|
|
|
202
|
|
|
$version->Version = $package->getVersionNormalized(); |
|
203
|
|
|
$version->PrettyVersion = $package->getVersion(); |
|
204
|
|
|
|
|
205
|
|
|
$stability = VersionParser::parseStability($package->getVersion()); |
|
206
|
|
|
$isDev = $stability === 'dev'; |
|
207
|
|
|
$version->Development = $isDev; |
|
208
|
|
|
|
|
209
|
|
|
$version->SourceType = $package->getSource()->getType(); |
|
210
|
|
|
$version->SourceUrl = $package->getSource()->getUrl(); |
|
211
|
|
|
$version->SourceReference = $package->getSource()->getReference(); |
|
212
|
|
|
|
|
213
|
|
|
if($package->getDist()) { |
|
214
|
|
|
$version->DistType = $package->getDist()->getType(); |
|
215
|
|
|
$version->DistUrl = $package->getDist()->getUrl(); |
|
216
|
|
|
$version->DistReference = $package->getDist()->getReference(); |
|
217
|
|
|
$version->DistChecksum = $package->getDist()->getShasum(); |
|
218
|
|
|
} |
|
219
|
|
|
|
|
220
|
|
|
$version->Extra = $package->getExtra(); |
|
221
|
|
|
$version->Homepage = $package->getHomepage(); |
|
222
|
|
|
$version->License = $package->getLicense(); |
|
223
|
|
|
// $version->Support = $package->getSupport(); |
|
224
|
|
|
|
|
225
|
|
|
$addon->Versions()->add($version); |
|
|
|
|
|
|
226
|
|
|
|
|
227
|
|
|
$this->updateLinks($version, $package); |
|
228
|
|
|
$this->updateCompatibility($addon, $version, $package); |
|
229
|
|
|
$this->updateAuthors($version, $package); |
|
230
|
|
|
} |
|
231
|
|
|
|
|
232
|
|
|
private function updateLinks(AddonVersion $version, CompletePackage $package) { |
|
233
|
|
|
$getLink = function ($name, $type) use ($version) { |
|
234
|
|
|
$link = null; |
|
235
|
|
|
|
|
236
|
|
|
if ($version->isInDB()) { |
|
237
|
|
|
$link = $version->Links()->filter('Name', $name)->filter('Type', $type)->first(); |
|
|
|
|
|
|
238
|
|
|
} |
|
239
|
|
|
|
|
240
|
|
|
if (!$link) { |
|
241
|
|
|
$link = new AddonLink(); |
|
242
|
|
|
$link->Name = $name; |
|
|
|
|
|
|
243
|
|
|
$link->Type = $type; |
|
|
|
|
|
|
244
|
|
|
} |
|
245
|
|
|
|
|
246
|
|
|
return $link; |
|
247
|
|
|
}; |
|
248
|
|
|
|
|
249
|
|
|
$types = array( |
|
250
|
|
|
'require' => 'getRequire', |
|
251
|
|
|
'require-dev' => 'getRequireDev', |
|
252
|
|
|
'provide' => 'getProvide', |
|
253
|
|
|
'conflict' => 'getConflict', |
|
254
|
|
|
'replace' => 'getReplace' |
|
255
|
|
|
); |
|
256
|
|
|
|
|
257
|
|
|
foreach ($types as $type => $method) { |
|
258
|
|
|
if ($linked = $package->$method()) foreach ($linked as $link => $constraint) { |
|
259
|
|
|
$name = $link; |
|
260
|
|
|
$addon = Addon::get()->filter('Name', $name)->first(); |
|
261
|
|
|
|
|
262
|
|
|
$local = $getLink($name, $type); |
|
263
|
|
|
$local->Constraint = $constraint; |
|
264
|
|
|
|
|
265
|
|
|
if ($addon) { |
|
266
|
|
|
$local->TargetID = $addon->ID; |
|
267
|
|
|
} |
|
268
|
|
|
|
|
269
|
|
|
$version->Links()->add($local); |
|
|
|
|
|
|
270
|
|
|
} |
|
271
|
|
|
} |
|
272
|
|
|
|
|
273
|
|
|
//to-do api have no method to get this. |
|
274
|
|
|
/*$suggested = $package->getSuggests(); |
|
275
|
|
|
|
|
276
|
|
|
if ($suggested) foreach ($suggested as $package => $description) { |
|
277
|
|
|
$link = $getLink($package, 'suggest'); |
|
278
|
|
|
$link->Description = $description; |
|
279
|
|
|
|
|
280
|
|
|
$version->Links()->add($link); |
|
281
|
|
|
}*/ |
|
282
|
|
|
} |
|
283
|
|
|
|
|
284
|
|
|
private function updateCompatibility(Addon $addon, AddonVersion $version, CompletePackage $package) { |
|
285
|
|
|
$require = null; |
|
286
|
|
|
|
|
287
|
|
|
if($package->getRequire()) foreach ($package->getRequire() as $name => $link) { |
|
|
|
|
|
|
288
|
|
|
if((string)$link == 'self.version') continue; |
|
289
|
|
|
|
|
290
|
|
|
if ($name == 'silverstripe/framework') { |
|
291
|
|
|
$require = $link; |
|
292
|
|
|
break; |
|
293
|
|
|
} |
|
294
|
|
|
|
|
295
|
|
|
if ($name == 'silverstripe/cms') { |
|
296
|
|
|
$require = $link; |
|
297
|
|
|
} |
|
298
|
|
|
} |
|
299
|
|
|
|
|
300
|
|
|
if (!$require) { |
|
301
|
|
|
return; |
|
302
|
|
|
} |
|
303
|
|
|
|
|
304
|
|
|
$addon->CompatibleVersions()->removeAll(); |
|
|
|
|
|
|
305
|
|
|
$version->CompatibleVersions()->removeAll(); |
|
|
|
|
|
|
306
|
|
|
|
|
307
|
|
|
foreach ($this->getSilverStripeVersions() as $silverStripeVersion) { |
|
308
|
|
|
/** @var SilverStripeVersion $silverStripeVersion */ |
|
309
|
|
|
try { |
|
310
|
|
|
if ($silverStripeVersion->getConstraintValidity($require)) { |
|
311
|
|
|
$addon->CompatibleVersions()->add($silverStripeVersion); |
|
|
|
|
|
|
312
|
|
|
$version->CompatibleVersions()->add($silverStripeVersion); |
|
|
|
|
|
|
313
|
|
|
} |
|
314
|
|
|
} catch (Exception $e) { |
|
315
|
|
|
// An exception here shouldn't prevent further updates. |
|
316
|
|
|
Debug::log($addon->Name . "\t" . $addon->ID . "\t" . $e->getMessage()); |
|
|
|
|
|
|
317
|
|
|
} |
|
318
|
|
|
} |
|
319
|
|
|
} |
|
320
|
|
|
|
|
321
|
|
|
private function updateAuthors(AddonVersion $version, CompletePackage $package) { |
|
322
|
|
|
if ($package->getAuthors()) foreach ($package->getAuthors() as $details) { |
|
323
|
|
|
$author = null; |
|
324
|
|
|
|
|
325
|
|
|
if (!$details->getName() && !$details->getEmail()) { |
|
326
|
|
|
continue; |
|
327
|
|
|
} |
|
328
|
|
|
|
|
329
|
|
|
if ($details->getEmail()) { |
|
330
|
|
|
$author = AddonAuthor::get()->filter('Email', $details->getEmail())->first(); |
|
331
|
|
|
} |
|
332
|
|
|
|
|
333
|
|
|
if (!$author && $details->getHomepage()) { |
|
334
|
|
|
$author = AddonAuthor::get() |
|
335
|
|
|
->filter('Name', $details->getName()) |
|
336
|
|
|
->filter('Homepage', $details->getHomepage()) |
|
337
|
|
|
->first(); |
|
338
|
|
|
} |
|
339
|
|
|
|
|
340
|
|
|
if (!$author && $details->getName()) { |
|
341
|
|
|
$author = AddonAuthor::get() |
|
342
|
|
|
->filter('Name', $details->getName()) |
|
343
|
|
|
->filter('Versions.Addon.Name', $package->getName()) |
|
344
|
|
|
->first(); |
|
345
|
|
|
} |
|
346
|
|
|
|
|
347
|
|
|
if (!$author) { |
|
348
|
|
|
$author = new AddonAuthor(); |
|
349
|
|
|
} |
|
350
|
|
|
|
|
351
|
|
|
if($details->getName()) $author->Name = $details->getName(); |
|
352
|
|
|
if($details->getEmail()) $author->Email = $details->getEmail(); |
|
353
|
|
|
if($details->getHomepage()) $author->Homepage = $details->getHomepage(); |
|
354
|
|
|
|
|
355
|
|
|
//to-do not supported by API |
|
356
|
|
|
//if(isset($details['role'])) $author->Role = $details['role']; |
|
357
|
|
|
|
|
358
|
|
|
$version->Authors()->add($author->write()); |
|
|
|
|
|
|
359
|
|
|
} |
|
360
|
|
|
} |
|
361
|
|
|
|
|
362
|
|
|
/** |
|
363
|
|
|
* Get the list of SilverStripe versions |
|
364
|
|
|
* |
|
365
|
|
|
* @return DataList |
|
366
|
|
|
*/ |
|
367
|
|
|
public function getSilverStripeVersions() |
|
368
|
|
|
{ |
|
369
|
|
|
return $this->silverstripes; |
|
370
|
|
|
} |
|
371
|
|
|
|
|
372
|
|
|
/** |
|
373
|
|
|
* Set the list of SilverStripeVersions |
|
374
|
|
|
* |
|
375
|
|
|
* @param DataList $versions |
|
376
|
|
|
* @return $this |
|
377
|
|
|
*/ |
|
378
|
|
|
public function setSilverStripeVersions(DataList $versions) |
|
379
|
|
|
{ |
|
380
|
|
|
$this->silverstripes = $versions; |
|
|
|
|
|
|
381
|
|
|
return $this; |
|
382
|
|
|
} |
|
383
|
|
|
} |
|
384
|
|
|
|
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.