Commit | Line | Data |
---|---|---|
11ade432 AE |
1 | <?php |
2 | namespace wcf\system\package; | |
11ade432 AE |
3 | use wcf\data\package\update\server\PackageUpdateServer; |
4 | use wcf\data\package\update\server\PackageUpdateServerEditor; | |
5 | use wcf\data\package\update\version\PackageUpdateVersionEditor; | |
6 | use wcf\data\package\update\version\PackageUpdateVersionList; | |
931f6597 TB |
7 | use wcf\data\package\update\PackageUpdateEditor; |
8 | use wcf\data\package\update\PackageUpdateList; | |
9 | use wcf\data\package\Package; | |
3aa69672 | 10 | use wcf\system\cache\builder\PackageUpdateCacheBuilder; |
11ade432 | 11 | use wcf\system\database\util\PreparedStatementConditionBuilder; |
dd3ee2d2 | 12 | use wcf\system\exception\HTTPUnauthorizedException; |
11ade432 | 13 | use wcf\system\exception\SystemException; |
dd3ee2d2 | 14 | use wcf\system\package\PackageUpdateUnauthorizedException; |
411d6f1a | 15 | use wcf\system\Regex; |
adf0710f | 16 | use wcf\system\SingletonFactory; |
2bc9f31d | 17 | use wcf\system\WCF; |
bfefbb8a | 18 | use wcf\util\HTTPRequest; |
11ade432 AE |
19 | use wcf\util\XML; |
20 | ||
21 | /** | |
22 | * Provides functions to manage package updates. | |
23 | * | |
24 | * @author Alexander Ebert | |
ca4ba303 | 25 | * @copyright 2001-2014 WoltLab GmbH |
11ade432 AE |
26 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> |
27 | * @package com.woltlab.wcf | |
28 | * @subpackage system.package | |
9f959ced | 29 | * @category Community Framework |
11ade432 | 30 | */ |
adf0710f | 31 | class PackageUpdateDispatcher extends SingletonFactory { |
11ade432 AE |
32 | /** |
33 | * Refreshes the package database. | |
9f959ced | 34 | * |
411d6f1a | 35 | * @param array<integer> $packageUpdateServerIDs |
70e6f4e6 | 36 | * @param boolean $ignoreCache |
11ade432 | 37 | */ |
70e6f4e6 | 38 | public function refreshPackageDatabase(array $packageUpdateServerIDs = array(), $ignoreCache = false) { |
11ade432 AE |
39 | // get update server data |
40 | $updateServers = PackageUpdateServer::getActiveUpdateServers($packageUpdateServerIDs); | |
41 | ||
42 | // loop servers | |
3aa69672 | 43 | $refreshedPackageLists = false; |
11ade432 | 44 | foreach ($updateServers as $updateServer) { |
70e6f4e6 | 45 | if ($ignoreCache || $updateServer->lastUpdateTime < TIME_NOW - 600) { |
a1517a25 AE |
46 | $errorMessage = ''; |
47 | ||
11ade432 | 48 | try { |
adf0710f | 49 | $this->getPackageUpdateXML($updateServer); |
3aa69672 | 50 | $refreshedPackageLists = true; |
11ade432 AE |
51 | } |
52 | catch (SystemException $e) { | |
a1517a25 AE |
53 | $errorMessage = $e->getMessage(); |
54 | } | |
55 | catch (PackageUpdateUnauthorizedException $e) { | |
56 | $reply = $e->getRequest()->getReply(); | |
8fe14bd6 | 57 | $errorMessage = reset($reply['httpHeaders']); |
a1517a25 AE |
58 | } |
59 | ||
60 | if ($errorMessage) { | |
11ade432 AE |
61 | // save error status |
62 | $updateServerEditor = new PackageUpdateServerEditor($updateServer); | |
63 | $updateServerEditor->update(array( | |
64 | 'status' => 'offline', | |
a1517a25 | 65 | 'errorMessage' => $errorMessage |
11ade432 AE |
66 | )); |
67 | } | |
68 | } | |
69 | } | |
3aa69672 AE |
70 | |
71 | if ($refreshedPackageLists) { | |
72 | PackageUpdateCacheBuilder::getInstance()->reset(); | |
73 | } | |
11ade432 AE |
74 | } |
75 | ||
76 | /** | |
77 | * Gets the package_update.xml from an update server. | |
78 | * | |
0ad90fc3 | 79 | * @param \wcf\data\package\update\server\PackageUpdateServer $updateServer |
11ade432 | 80 | */ |
adf0710f | 81 | protected function getPackageUpdateXML(PackageUpdateServer $updateServer) { |
bfefbb8a TD |
82 | $authData = $updateServer->getAuthData(); |
83 | $settings = array(); | |
84 | if ($authData) $settings['auth'] = $authData; | |
3c6ab36b | 85 | |
411d6f1a | 86 | $postData = array( |
bfefbb8a | 87 | 'lastUpdateTime' => $updateServer->lastUpdateTime |
411d6f1a AE |
88 | ); |
89 | ||
90 | // append auth code if set and update server resolves to woltlab.com | |
91 | if (PACKAGE_SERVER_AUTH_CODE && Regex::compile('^https?://[a-z]+.woltlab.com/')->match($updateServer->serverURL)) { | |
92 | $postData['authCode'] = PACKAGE_SERVER_AUTH_CODE; | |
93 | } | |
94 | ||
95 | $request = new HTTPRequest($updateServer->serverURL, $settings, $postData); | |
11ade432 | 96 | |
bfefbb8a TD |
97 | try { |
98 | $request->execute(); | |
99 | $reply = $request->getReply(); | |
11ade432 | 100 | } |
dd3ee2d2 AE |
101 | catch (HTTPUnauthorizedException $e) { |
102 | throw new PackageUpdateUnauthorizedException($request, $updateServer); | |
103 | } | |
bfefbb8a TD |
104 | catch (SystemException $e) { |
105 | $reply = $request->getReply(); | |
106 | ||
79f2d1eb AE |
107 | $statusCode = (is_array($reply['statusCode'])) ? reset($reply['statusCode']) : $reply['statusCode']; |
108 | throw new SystemException(WCF::getLanguage()->get('wcf.acp.package.update.error.listNotFound') . ' ('.$statusCode.')'); | |
11ade432 AE |
109 | } |
110 | ||
111 | // parse given package update xml | |
adf0710f | 112 | $allNewPackages = $this->parsePackageUpdateXML($reply['body']); |
bfefbb8a | 113 | unset($request, $reply); |
11ade432 AE |
114 | |
115 | // save packages | |
15fa2802 | 116 | if (!empty($allNewPackages)) { |
adf0710f | 117 | $this->savePackageUpdates($allNewPackages, $updateServer->packageUpdateServerID); |
11ade432 AE |
118 | } |
119 | unset($allNewPackages); | |
120 | ||
121 | // update server status | |
122 | $updateServerEditor = new PackageUpdateServerEditor($updateServer); | |
123 | $updateServerEditor->update(array( | |
124 | 'lastUpdateTime' => TIME_NOW, | |
125 | 'status' => 'online', | |
126 | 'errorMessage' => '' | |
127 | )); | |
128 | } | |
129 | ||
11ade432 AE |
130 | /** |
131 | * Parses a stream containing info from a packages_update.xml. | |
9f959ced | 132 | * |
11ade432 AE |
133 | * @param string $content |
134 | * @return array $allNewPackages | |
135 | */ | |
adf0710f | 136 | protected function parsePackageUpdateXML($content) { |
11ade432 AE |
137 | // load xml document |
138 | $xml = new XML(); | |
139 | $xml->loadXML('packageUpdateServer.xml', $content); | |
140 | $xpath = $xml->xpath(); | |
141 | ||
142 | // loop through <package> tags inside the <section> tag. | |
143 | $allNewPackages = array(); | |
b4f1ea02 | 144 | $packages = $xpath->query('/ns:section/ns:package'); |
11ade432 AE |
145 | foreach ($packages as $package) { |
146 | if (!Package::isValidPackageName($package->getAttribute('name'))) { | |
4fe0b42b | 147 | throw new SystemException("'".$package->getAttribute('name')."' is not a valid package name."); |
11ade432 AE |
148 | } |
149 | ||
adf0710f | 150 | $allNewPackages[$package->getAttribute('name')] = $this->parsePackageUpdateXMLBlock($xpath, $package); |
11ade432 AE |
151 | } |
152 | ||
153 | return $allNewPackages; | |
154 | } | |
155 | ||
156 | /** | |
157 | * Parses the xml stucture from a packages_update.xml. | |
9f959ced | 158 | * |
11ade432 AE |
159 | * @param \DOMXPath $xpath |
160 | * @param \DOMNode $package | |
161 | */ | |
adf0710f | 162 | protected function parsePackageUpdateXMLBlock(\DOMXPath $xpath, \DOMNode $package) { |
11ade432 AE |
163 | // define default values |
164 | $packageInfo = array( | |
11ade432 AE |
165 | 'author' => '', |
166 | 'authorURL' => '', | |
411d6f1a AE |
167 | 'isApplication' => 0, |
168 | 'packageDescription' => '', | |
11ade432 AE |
169 | 'versions' => array() |
170 | ); | |
171 | ||
172 | // parse package information | |
173 | $elements = $xpath->query('./ns:packageinformation/*', $package); | |
174 | foreach ($elements as $element) { | |
175 | switch ($element->tagName) { | |
176 | case 'packagename': | |
177 | $packageInfo['packageName'] = $element->nodeValue; | |
178 | break; | |
179 | ||
180 | case 'packagedescription': | |
181 | $packageInfo['packageDescription'] = $element->nodeValue; | |
182 | break; | |
183 | ||
aac1247e MS |
184 | case 'isapplication': |
185 | $packageInfo['isApplication'] = intval($element->nodeValue); | |
11ade432 | 186 | break; |
11ade432 AE |
187 | } |
188 | } | |
189 | ||
190 | // parse author information | |
191 | $elements = $xpath->query('./ns:authorinformation/*', $package); | |
192 | foreach ($elements as $element) { | |
193 | switch ($element->tagName) { | |
194 | case 'author': | |
195 | $packageInfo['author'] = $element->nodeValue; | |
196 | break; | |
197 | ||
198 | case 'authorurl': | |
199 | $packageInfo['authorURL'] = $element->nodeValue; | |
200 | break; | |
201 | } | |
202 | } | |
203 | ||
204 | // parse versions | |
205 | $elements = $xpath->query('./ns:versions/ns:version', $package); | |
206 | foreach ($elements as $element) { | |
207 | $versionNo = $element->getAttribute('name'); | |
411d6f1a | 208 | $packageInfo['versions'][$versionNo] = array( |
507f6f5b AE |
209 | 'isAccessible' => ($element->getAttribute('accessible') == 'true' ? true : false), |
210 | 'isCritical' => ($element->getAttribute('critical') == 'true' ? true : false) | |
411d6f1a | 211 | ); |
11ade432 AE |
212 | |
213 | $children = $xpath->query('child::*', $element); | |
214 | foreach ($children as $child) { | |
215 | switch ($child->tagName) { | |
216 | case 'fromversions': | |
217 | $fromversions = $xpath->query('child::*', $child); | |
218 | foreach ($fromversions as $fromversion) { | |
219 | $packageInfo['versions'][$versionNo]['fromversions'][] = $fromversion->nodeValue; | |
220 | } | |
221 | break; | |
222 | ||
11ade432 AE |
223 | case 'timestamp': |
224 | $packageInfo['versions'][$versionNo]['packageDate'] = $child->nodeValue; | |
225 | break; | |
226 | ||
227 | case 'file': | |
228 | $packageInfo['versions'][$versionNo]['file'] = $child->nodeValue; | |
229 | break; | |
230 | ||
231 | case 'requiredpackages': | |
232 | $requiredPackages = $xpath->query('child::*', $child); | |
233 | foreach ($requiredPackages as $requiredPackage) { | |
234 | $minVersion = $requiredPackage->getAttribute('minversion'); | |
235 | $required = $requiredPackage->nodeValue; | |
236 | ||
237 | $packageInfo['versions'][$versionNo]['requiredPackages'][$required] = array(); | |
238 | if (!empty($minVersion)) { | |
239 | $packageInfo['versions'][$versionNo]['requiredPackages'][$required]['minversion'] = $minVersion; | |
240 | } | |
241 | } | |
242 | break; | |
243 | ||
411d6f1a AE |
244 | case 'optionalpackages': |
245 | $packageInfo['versions'][$versionNo]['optionalPackages'] = array(); | |
246 | ||
247 | $optionalPackages = $xpath->query('child::*', $child); | |
248 | foreach ($optionalPackages as $optionalPackage) { | |
249 | $packageInfo['versions'][$versionNo]['optionalPackages'][] = $optionalPackage->nodeValue; | |
250 | } | |
251 | break; | |
252 | ||
11ade432 AE |
253 | case 'excludedpackages': |
254 | $excludedpackages = $xpath->query('child::*', $child); | |
411d6f1a AE |
255 | foreach ($excludedpackages as $excludedPackage) { |
256 | $exclusion = $excludedPackage->nodeValue; | |
257 | $version = $excludedPackage->getAttribute('version'); | |
11ade432 AE |
258 | |
259 | $packageInfo['versions'][$versionNo]['excludedPackages'][$exclusion] = array(); | |
260 | if (!empty($version)) { | |
261 | $packageInfo['versions'][$versionNo]['excludedPackages'][$exclusion]['version'] = $version; | |
262 | } | |
263 | } | |
264 | break; | |
411d6f1a AE |
265 | |
266 | case 'license': | |
267 | $packageInfo['versions'][$versionNo]['license'] = array( | |
268 | 'license' => $child->nodeValue, | |
269 | 'licenseURL' => ($child->hasAttribute('url') ? $child->getAttribute('url') : '') | |
270 | ); | |
271 | break; | |
11ade432 AE |
272 | } |
273 | } | |
274 | } | |
275 | ||
276 | return $packageInfo; | |
277 | } | |
278 | ||
279 | /** | |
280 | * Updates information parsed from a packages_update.xml into the database. | |
281 | * | |
39bea7dd | 282 | * @param array $allNewPackages |
11ade432 AE |
283 | * @param integer $packageUpdateServerID |
284 | */ | |
adf0710f | 285 | protected function savePackageUpdates(array &$allNewPackages, $packageUpdateServerID) { |
11ade432 AE |
286 | // find existing packages and delete them |
287 | // get existing packages | |
288 | $existingPackages = array(); | |
289 | $packageUpdateList = new PackageUpdateList(); | |
290 | $packageUpdateList->getConditionBuilder()->add("package_update.packageUpdateServerID = ? AND package_update.package IN (?)", array($packageUpdateServerID, array_keys($allNewPackages))); | |
11ade432 AE |
291 | $packageUpdateList->readObjects(); |
292 | $tmp = $packageUpdateList->getObjects(); | |
293 | ||
294 | foreach ($tmp as $packageUpdate) { | |
295 | $existingPackages[$packageUpdate->package] = $packageUpdate; | |
296 | } | |
297 | ||
298 | // get existing versions | |
299 | $existingPackageVersions = array(); | |
15fa2802 | 300 | if (!empty($existingPackages)) { |
11ade432 AE |
301 | // get package update ids |
302 | $packageUpdateIDs = array(); | |
303 | foreach ($existingPackages as $packageUpdate) { | |
304 | $packageUpdateIDs[] = $packageUpdate->packageUpdateID; | |
305 | } | |
306 | ||
307 | // get version list | |
308 | $versionList = new PackageUpdateVersionList(); | |
309 | $versionList->getConditionBuilder()->add("package_update_version.packageUpdateID IN (?)", array($packageUpdateIDs)); | |
11ade432 AE |
310 | $versionList->readObjects(); |
311 | $tmp = $versionList->getObjects(); | |
312 | ||
313 | foreach ($tmp as $version) { | |
314 | if (!isset($existingPackageVersions[$version->packageUpdateID])) $existingPackageVersions[$version->packageUpdateID] = array(); | |
315 | $existingPackageVersions[$version->packageUpdateID][$version->packageVersion] = $version; | |
316 | } | |
317 | } | |
318 | ||
319 | // insert updates | |
411d6f1a | 320 | $excludedPackagesParameters = $fromversionParameters = $insertParameters = $optionalInserts = $requirementInserts = array(); |
11ade432 AE |
321 | foreach ($allNewPackages as $identifier => $packageData) { |
322 | if (isset($existingPackages[$identifier])) { | |
323 | $packageUpdateID = $existingPackages[$identifier]->packageUpdateID; | |
324 | ||
325 | // update database entry | |
326 | $packageUpdateEditor = new PackageUpdateEditor($existingPackages[$identifier]); | |
327 | $packageUpdateEditor->update(array( | |
328 | 'packageName' => $packageData['packageName'], | |
329 | 'packageDescription' => $packageData['packageDescription'], | |
330 | 'author' => $packageData['author'], | |
331 | 'authorURL' => $packageData['authorURL'], | |
411d6f1a | 332 | 'isApplication' => $packageData['isApplication'] |
11ade432 AE |
333 | )); |
334 | } | |
335 | else { | |
336 | // create new database entry | |
337 | $packageUpdate = PackageUpdateEditor::create(array( | |
338 | 'packageUpdateServerID' => $packageUpdateServerID, | |
339 | 'package' => $identifier, | |
340 | 'packageName' => $packageData['packageName'], | |
341 | 'packageDescription' => $packageData['packageDescription'], | |
342 | 'author' => $packageData['author'], | |
343 | 'authorURL' => $packageData['authorURL'], | |
411d6f1a | 344 | 'isApplication' => $packageData['isApplication'] |
11ade432 AE |
345 | )); |
346 | ||
347 | $packageUpdateID = $packageUpdate->packageUpdateID; | |
348 | } | |
349 | ||
350 | // register version(s) of this update package. | |
351 | if (isset($packageData['versions'])) { | |
352 | foreach ($packageData['versions'] as $packageVersion => $versionData) { | |
353 | if (isset($versionData['file'])) $packageFile = $versionData['file']; | |
354 | else $packageFile = ''; | |
355 | ||
356 | if (isset($existingPackageVersions[$packageUpdateID]) && isset($existingPackageVersions[$packageUpdateID][$packageVersion])) { | |
357 | $packageUpdateVersionID = $existingPackageVersions[$packageUpdateID][$packageVersion]->packageUpdateVersionID; | |
358 | ||
359 | // update database entry | |
360 | $versionEditor = new PackageUpdateVersionEditor($existingPackageVersions[$packageUpdateID][$packageVersion]); | |
361 | $versionEditor->update(array( | |
411d6f1a AE |
362 | 'filename' => $packageFile, |
363 | 'isAccessible' => ($versionData['isAccessible'] ? 1 : 0), | |
507f6f5b | 364 | 'isCritical' => ($versionData['isCritical'] ? 1 : 0), |
b4f1ea02 AE |
365 | 'license' => (isset($versionData['license']['license']) ? $versionData['license']['license'] : ''), |
366 | 'licenseURL' => (isset($versionData['license']['license']) ? $versionData['license']['licenseURL'] : ''), | |
507f6f5b | 367 | 'packageDate' => $versionData['packageDate'] |
11ade432 AE |
368 | )); |
369 | } | |
370 | else { | |
371 | // create new database entry | |
372 | $version = PackageUpdateVersionEditor::create(array( | |
411d6f1a | 373 | 'filename' => $packageFile, |
b4f1ea02 AE |
374 | 'license' => (isset($versionData['license']['license']) ? $versionData['license']['license'] : ''), |
375 | 'licenseURL' => (isset($versionData['license']['license']) ? $versionData['license']['licenseURL'] : ''), | |
411d6f1a | 376 | 'isAccessible' => ($versionData['isAccessible'] ? 1 : 0), |
507f6f5b | 377 | 'isCritical' => ($versionData['isCritical'] ? 1 : 0), |
11ade432 | 378 | 'packageDate' => $versionData['packageDate'], |
11ade432 | 379 | 'packageUpdateID' => $packageUpdateID, |
507f6f5b | 380 | 'packageVersion' => $packageVersion |
11ade432 AE |
381 | )); |
382 | ||
383 | $packageUpdateVersionID = $version->packageUpdateVersionID; | |
384 | } | |
385 | ||
386 | // register requirement(s) of this update package version. | |
387 | if (isset($versionData['requiredPackages'])) { | |
388 | foreach ($versionData['requiredPackages'] as $requiredIdentifier => $required) { | |
389 | $requirementInserts[] = array( | |
390 | 'packageUpdateVersionID' => $packageUpdateVersionID, | |
391 | 'package' => $requiredIdentifier, | |
392 | 'minversion' => (isset($required['minversion']) ? $required['minversion'] : '') | |
393 | ); | |
394 | } | |
395 | } | |
396 | ||
411d6f1a AE |
397 | // register optional packages of this update package version |
398 | if (isset($versionData['optionalPackages'])) { | |
399 | foreach ($versionData['optionalPackages'] as $optionalPackage) { | |
400 | $optionalInserts[] = array( | |
401 | 'packageUpdateVersionID' => $packageUpdateVersionID, | |
402 | 'package' => $optionalPackage | |
403 | ); | |
404 | } | |
405 | } | |
406 | ||
11ade432 AE |
407 | // register excluded packages of this update package version. |
408 | if (isset($versionData['excludedPackages'])) { | |
409 | foreach ($versionData['excludedPackages'] as $excludedIdentifier => $exclusion) { | |
410 | $excludedPackagesParameters[] = array( | |
411 | 'packageUpdateVersionID' => $packageUpdateVersionID, | |
412 | 'excludedPackage' => $excludedIdentifier, | |
413 | 'excludedPackageVersion' => (isset($exclusion['version']) ? $exclusion['version'] : '') | |
414 | ); | |
415 | } | |
416 | } | |
417 | ||
418 | // register fromversions of this update package version. | |
419 | if (isset($versionData['fromversions'])) { | |
420 | foreach ($versionData['fromversions'] as $fromversion) { | |
421 | $fromversionInserts[] = array( | |
422 | 'packageUpdateVersionID' => $packageUpdateVersionID, | |
423 | 'fromversion' => $fromversion | |
424 | ); | |
425 | } | |
426 | } | |
427 | } | |
428 | } | |
429 | } | |
430 | ||
431 | // save requirements, excluded packages and fromversions | |
432 | // use multiple inserts to save some queries | |
433 | if (!empty($requirementInserts)) { | |
434 | // clear records | |
435 | $sql = "DELETE pur FROM wcf".WCF_N."_package_update_requirement pur | |
436 | LEFT JOIN wcf".WCF_N."_package_update_version puv | |
437 | ON (puv.packageUpdateVersionID = pur.packageUpdateVersionID) | |
438 | LEFT JOIN wcf".WCF_N."_package_update pu | |
439 | ON (pu.packageUpdateID = puv.packageUpdateID) | |
440 | WHERE pu.packageUpdateServerID = ?"; | |
441 | $statement = WCF::getDB()->prepareStatement($sql); | |
442 | $statement->execute(array($packageUpdateServerID)); | |
443 | ||
444 | // insert requirements | |
445 | $sql = "INSERT INTO wcf".WCF_N."_package_update_requirement | |
446 | (packageUpdateVersionID, package, minversion) | |
447 | VALUES (?, ?, ?)"; | |
448 | $statement = WCF::getDB()->prepareStatement($sql); | |
5a17f03d | 449 | WCF::getDB()->beginTransaction(); |
11ade432 AE |
450 | foreach ($requirementInserts as $requirement) { |
451 | $statement->execute(array( | |
452 | $requirement['packageUpdateVersionID'], | |
453 | $requirement['package'], | |
454 | $requirement['minversion'] | |
455 | )); | |
456 | } | |
5a17f03d | 457 | WCF::getDB()->commitTransaction(); |
11ade432 AE |
458 | } |
459 | ||
411d6f1a AE |
460 | if (!empty($optionalInserts)) { |
461 | // clear records | |
462 | $sql = "DELETE puo FROM wcf".WCF_N."_package_update_optional puo | |
463 | LEFT JOIN wcf".WCF_N."_package_update_version puv | |
464 | ON (puv.packageUpdateVersionID = puo.packageUpdateVersionID) | |
465 | LEFT JOIN wcf".WCF_N."_package_update pu | |
466 | ON (pu.packageUpdateID = puv.packageUpdateID) | |
467 | WHERE pu.packageUpdateServerID = ?"; | |
468 | $statement = WCF::getDB()->prepareStatement($sql); | |
469 | $statement->execute(array($packageUpdateServerID)); | |
470 | ||
471 | // insert requirements | |
472 | $sql = "INSERT INTO wcf".WCF_N."_package_update_optional | |
473 | (packageUpdateVersionID, package) | |
b4f1ea02 | 474 | VALUES (?, ?)"; |
411d6f1a | 475 | $statement = WCF::getDB()->prepareStatement($sql); |
5a17f03d | 476 | WCF::getDB()->beginTransaction(); |
507f6f5b | 477 | foreach ($optionalInserts as $requirement) { |
411d6f1a AE |
478 | $statement->execute(array( |
479 | $requirement['packageUpdateVersionID'], | |
480 | $requirement['package'] | |
481 | )); | |
482 | } | |
5a17f03d | 483 | WCF::getDB()->commitTransaction(); |
411d6f1a AE |
484 | } |
485 | ||
6286572b | 486 | if (!empty($excludedPackagesParameters)) { |
11ade432 AE |
487 | // clear records |
488 | $sql = "DELETE pue FROM wcf".WCF_N."_package_update_exclusion pue | |
489 | LEFT JOIN wcf".WCF_N."_package_update_version puv | |
490 | ON (puv.packageUpdateVersionID = pue.packageUpdateVersionID) | |
491 | LEFT JOIN wcf".WCF_N."_package_update pu | |
492 | ON (pu.packageUpdateID = puv.packageUpdateID) | |
493 | WHERE pu.packageUpdateServerID = ?"; | |
494 | $statement = WCF::getDB()->prepareStatement($sql); | |
495 | $statement->execute(array($packageUpdateServerID)); | |
496 | ||
497 | // insert excludes | |
498 | $sql = "INSERT INTO wcf".WCF_N."_package_update_exclusion | |
499 | (packageUpdateVersionID, excludedPackage, excludedPackageVersion) | |
500 | VALUES (?, ?, ?)"; | |
501 | $statement = WCF::getDB()->prepareStatement($sql); | |
5a17f03d | 502 | WCF::getDB()->beginTransaction(); |
6286572b | 503 | foreach ($excludedPackagesParameters as $excludedPackage) { |
11ade432 AE |
504 | $statement->execute(array( |
505 | $excludedPackage['packageUpdateVersionID'], | |
506 | $excludedPackage['excludedPackage'], | |
507 | $excludedPackage['excludedPackageVersion'] | |
508 | )); | |
509 | } | |
5a17f03d | 510 | WCF::getDB()->commitTransaction(); |
11ade432 AE |
511 | } |
512 | ||
513 | if (!empty($fromversionInserts)) { | |
514 | // clear records | |
515 | $sql = "DELETE puf FROM wcf".WCF_N."_package_update_fromversion puf | |
516 | LEFT JOIN wcf".WCF_N."_package_update_version puv | |
517 | ON (puv.packageUpdateVersionID = puf.packageUpdateVersionID) | |
518 | LEFT JOIN wcf".WCF_N."_package_update pu | |
519 | ON (pu.packageUpdateID = puv.packageUpdateID) | |
520 | WHERE pu.packageUpdateServerID = ?"; | |
521 | $statement = WCF::getDB()->prepareStatement($sql); | |
522 | $statement->execute(array($packageUpdateServerID)); | |
523 | ||
524 | // insert excludes | |
525 | $sql = "INSERT INTO wcf".WCF_N."_package_update_fromversion | |
526 | (packageUpdateVersionID, fromversion) | |
527 | VALUES (?, ?)"; | |
528 | $statement = WCF::getDB()->prepareStatement($sql); | |
5a17f03d | 529 | WCF::getDB()->beginTransaction(); |
11ade432 AE |
530 | foreach ($fromversionInserts as $fromversion) { |
531 | $statement->execute(array( | |
532 | $fromversion['packageUpdateVersionID'], | |
533 | $fromversion['fromversion'] | |
534 | )); | |
535 | } | |
5a17f03d | 536 | WCF::getDB()->commitTransaction(); |
11ade432 AE |
537 | } |
538 | } | |
539 | ||
540 | /** | |
541 | * Returns a list of available updates for installed packages. | |
542 | * | |
543 | * @param boolean $removeRequirements | |
755b2532 | 544 | * @param boolean $removeOlderMinorReleases |
39bea7dd | 545 | * @return array |
11ade432 | 546 | */ |
755b2532 | 547 | public function getAvailableUpdates($removeRequirements = true, $removeOlderMinorReleases = false) { |
11ade432 AE |
548 | $updates = array(); |
549 | ||
550 | // get update server data | |
551 | $updateServers = PackageUpdateServer::getActiveUpdateServers(); | |
552 | $packageUpdateServerIDs = array_keys($updateServers); | |
553 | if (empty($packageUpdateServerIDs)) return $updates; | |
554 | ||
555 | // get existing packages and their versions | |
556 | $existingPackages = array(); | |
3536d2fe | 557 | $sql = "SELECT packageID, package, packageDescription, packageName, |
07c78f25 | 558 | packageVersion, packageDate, author, authorURL, isApplication |
11ade432 AE |
559 | FROM wcf".WCF_N."_package"; |
560 | $statement = WCF::getDB()->prepareStatement($sql); | |
561 | $statement->execute(); | |
562 | while ($row = $statement->fetchArray()) { | |
563 | $existingPackages[$row['package']][] = $row; | |
564 | } | |
f92aaf6a | 565 | if (empty($existingPackages)) return $updates; |
11ade432 AE |
566 | |
567 | // get all update versions | |
568 | $conditions = new PreparedStatementConditionBuilder(); | |
569 | $conditions->add("pu.packageUpdateServerID IN (?)", array($packageUpdateServerIDs)); | |
570 | $conditions->add("package IN (SELECT DISTINCT package FROM wcf".WCF_N."_package)"); | |
571 | ||
572 | $sql = "SELECT pu.packageUpdateID, pu.packageUpdateServerID, pu.package, | |
eb7cdd16 | 573 | puv.packageUpdateVersionID, puv.isCritical, puv.packageDate, puv.filename, puv.packageVersion |
11ade432 AE |
574 | FROM wcf".WCF_N."_package_update pu |
575 | LEFT JOIN wcf".WCF_N."_package_update_version puv | |
3536d2fe | 576 | ON (puv.packageUpdateID = pu.packageUpdateID AND puv.isAccessible = 1) |
11ade432 AE |
577 | ".$conditions; |
578 | $statement = WCF::getDB()->prepareStatement($sql); | |
579 | $statement->execute($conditions->getParameters()); | |
580 | while ($row = $statement->fetchArray()) { | |
581 | // test version | |
582 | foreach ($existingPackages[$row['package']] as $existingVersion) { | |
583 | if (Package::compareVersion($existingVersion['packageVersion'], $row['packageVersion'], '<')) { | |
584 | // package data | |
585 | if (!isset($updates[$existingVersion['packageID']])) { | |
586 | $existingVersion['versions'] = array(); | |
587 | $updates[$existingVersion['packageID']] = $existingVersion; | |
588 | } | |
589 | ||
590 | // version data | |
591 | if (!isset($updates[$existingVersion['packageID']]['versions'][$row['packageVersion']])) { | |
592 | $updates[$existingVersion['packageID']]['versions'][$row['packageVersion']] = array( | |
eb7cdd16 | 593 | 'isCritical' => $row['isCritical'], |
11ade432 AE |
594 | 'packageDate' => $row['packageDate'], |
595 | 'packageVersion' => $row['packageVersion'], | |
596 | 'servers' => array() | |
597 | ); | |
598 | } | |
599 | ||
600 | // server data | |
601 | $updates[$existingVersion['packageID']]['versions'][$row['packageVersion']]['servers'][] = array( | |
602 | 'packageUpdateID' => $row['packageUpdateID'], | |
603 | 'packageUpdateServerID' => $row['packageUpdateServerID'], | |
604 | 'packageUpdateVersionID' => $row['packageUpdateVersionID'], | |
605 | 'filename' => $row['filename'] | |
606 | ); | |
607 | } | |
608 | } | |
609 | } | |
610 | ||
611 | // sort package versions | |
612 | // and remove old versions | |
613 | foreach ($updates as $packageID => $data) { | |
3c6ab36b | 614 | uksort($updates[$packageID]['versions'], array('wcf\data\package\Package', 'compareVersion')); |
11ade432 AE |
615 | $updates[$packageID]['version'] = end($updates[$packageID]['versions']); |
616 | } | |
617 | ||
aac1247e | 618 | // remove requirements of application packages |
11ade432 AE |
619 | if ($removeRequirements) { |
620 | foreach ($existingPackages as $identifier => $instances) { | |
621 | foreach ($instances as $instance) { | |
aac1247e | 622 | if ($instance['isApplication'] && isset($updates[$instance['packageID']])) { |
adf0710f | 623 | $updates = $this->removeUpdateRequirements($updates, $updates[$instance['packageID']]['version']['servers'][0]['packageUpdateVersionID']); |
11ade432 AE |
624 | } |
625 | } | |
626 | } | |
627 | } | |
628 | ||
755b2532 AE |
629 | // remove older minor releases from list, e.g. only display 1.0.2, even if 1.0.1 is available |
630 | if ($removeOlderMinorReleases) { | |
631 | foreach ($updates as &$updateData) { | |
632 | $highestVersions = array(); | |
633 | foreach ($updateData['versions'] as $versionNumber => $dummy) { | |
634 | if (preg_match('~^(\d+\.\d+)\.~', $versionNumber, $matches)) { | |
635 | $major = $matches[1]; | |
636 | if (isset($highestVersions[$major])) { | |
637 | if (version_compare($highestVersions[$major], $versionNumber, '<')) { | |
638 | // version is newer, discard current version | |
639 | unset($updateData['versions'][$highestVersions[$major]]); | |
640 | $highestVersions[$major] = $versionNumber; | |
641 | } | |
642 | else { | |
643 | // version is lower, discard | |
644 | unset($updateData['versions'][$versionNumber]); | |
645 | } | |
646 | } | |
647 | else { | |
648 | $highestVersions[$major] = $versionNumber; | |
649 | } | |
650 | } | |
651 | } | |
652 | } | |
653 | unset($updateData); | |
654 | } | |
655 | ||
11ade432 AE |
656 | return $updates; |
657 | } | |
658 | ||
659 | /** | |
660 | * Removes unnecessary updates of requirements from the list of available updates. | |
661 | * | |
662 | * @param array $updates | |
39bea7dd | 663 | * @param integer $packageUpdateVersionID |
11ade432 AE |
664 | * @return array $updates |
665 | */ | |
adf0710f | 666 | protected function removeUpdateRequirements(array $updates, $packageUpdateVersionID) { |
11ade432 AE |
667 | $sql = "SELECT pur.package, pur.minversion, p.packageID |
668 | FROM wcf".WCF_N."_package_update_requirement pur | |
669 | LEFT JOIN wcf".WCF_N."_package p | |
670 | ON (p.package = pur.package) | |
671 | WHERE pur.packageUpdateVersionID = ?"; | |
672 | $statement = WCF::getDB()->prepareStatement($sql); | |
673 | $statement->execute(array($packageUpdateVersionID)); | |
674 | while ($row = $statement->fetchArray()) { | |
675 | if (isset($updates[$row['packageID']])) { | |
adf0710f | 676 | $updates = $this->removeUpdateRequirements($updates, $updates[$row['packageID']]['version']['servers'][0]['packageUpdateVersionID']); |
11ade432 AE |
677 | if (Package::compareVersion($row['minversion'], $updates[$row['packageID']]['version']['packageVersion'], '>=')) { |
678 | unset($updates[$row['packageID']]); | |
679 | } | |
680 | } | |
681 | } | |
682 | ||
683 | return $updates; | |
684 | } | |
685 | ||
9f959ced MS |
686 | /** |
687 | * Creates a new package installation scheduler. | |
688 | * | |
689 | * @param array $selectedPackages | |
0ad90fc3 | 690 | * @return \wcf\system\package\PackageInstallationScheduler |
9f959ced | 691 | */ |
3536d2fe AE |
692 | public function prepareInstallation(array $selectedPackages) { |
693 | return new PackageInstallationScheduler($selectedPackages); | |
11ade432 AE |
694 | } |
695 | ||
696 | /** | |
697 | * Gets package update versions of a package. | |
698 | * | |
699 | * @param string $package package identifier | |
700 | * @param string $version package version | |
701 | * @return array package update versions | |
702 | */ | |
adf0710f | 703 | public function getPackageUpdateVersions($package, $version = '') { |
11ade432 AE |
704 | // get newest package version |
705 | if (empty($version)) { | |
adf0710f | 706 | $version = $this->getNewestPackageVersion($package); |
11ade432 AE |
707 | } |
708 | ||
709 | // get versions | |
710 | $versions = array(); | |
3ae522dd | 711 | $sql = "SELECT puv.*, pu.*, pus.loginUsername, pus.loginPassword |
11ade432 AE |
712 | FROM wcf".WCF_N."_package_update_version puv |
713 | LEFT JOIN wcf".WCF_N."_package_update pu | |
714 | ON (pu.packageUpdateID = puv.packageUpdateID) | |
715 | LEFT JOIN wcf".WCF_N."_package_update_server pus | |
716 | ON (pus.packageUpdateServerID = pu.packageUpdateServerID) | |
717 | WHERE pu.package = ? | |
718 | AND puv.packageVersion = ?"; | |
719 | $statement = WCF::getDB()->prepareStatement($sql); | |
720 | $statement->execute(array( | |
721 | $package, | |
722 | $version | |
723 | )); | |
724 | while ($row = $statement->fetchArray()) { | |
725 | $versions[] = $row; | |
726 | } | |
727 | ||
15fa2802 | 728 | if (empty($versions)) { |
4fe0b42b | 729 | throw new SystemException("Can not find package '".$package."' in version '".$version."'"); |
11ade432 AE |
730 | } |
731 | ||
732 | return $versions; | |
733 | } | |
734 | ||
735 | /** | |
736 | * Returns the newest available version of a package. | |
737 | * | |
738 | * @param string $package package identifier | |
739 | * @return string newest package version | |
740 | */ | |
adf0710f | 741 | public function getNewestPackageVersion($package) { |
11ade432 AE |
742 | // get all versions |
743 | $versions = array(); | |
744 | $sql = "SELECT packageVersion | |
745 | FROM wcf".WCF_N."_package_update_version | |
746 | WHERE packageUpdateID IN ( | |
747 | SELECT packageUpdateID | |
748 | FROM wcf".WCF_N."_package_update | |
749 | WHERE package = ? | |
750 | )"; | |
751 | $statement = WCF::getDB()->prepareStatement($sql); | |
752 | $statement->execute(array($package)); | |
753 | while ($row = $statement->fetchArray()) { | |
754 | $versions[$row['packageVersion']] = $row['packageVersion']; | |
755 | } | |
756 | ||
757 | // sort by version number | |
c09e5db4 | 758 | usort($versions, array('wcf\data\package\Package', 'compareVersion')); |
11ade432 AE |
759 | |
760 | // take newest (last) | |
761 | return array_pop($versions); | |
762 | } | |
763 | ||
764 | /** | |
765 | * Stores the filename of a download in session. | |
766 | * | |
39bea7dd MS |
767 | * @param string $package package identifier |
768 | * @param string $version package version | |
769 | * @param string $filename | |
11ade432 | 770 | */ |
adf0710f | 771 | public function cacheDownload($package, $version, $filename) { |
11ade432 AE |
772 | $cachedDownloads = WCF::getSession()->getVar('cachedPackageUpdateDownloads'); |
773 | if (!is_array($cachedDownloads)) { | |
774 | $cachedDownloads = array(); | |
775 | } | |
776 | ||
777 | // store in session | |
778 | $cachedDownloads[$package.'@'.$version] = $filename; | |
779 | WCF::getSession()->register('cachedPackageUpdateDownloads', $cachedDownloads); | |
780 | } | |
781 | } |