2 namespace wcf\system\package
;
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\PackageUpdateEditor
;
7 use wcf\data\package\Package
;
8 use wcf\system\cache\builder\PackageUpdateCacheBuilder
;
9 use wcf\system\database\util\PreparedStatementConditionBuilder
;
10 use wcf\system\exception\HTTPUnauthorizedException
;
11 use wcf\system\exception\SystemException
;
12 use wcf\system\io\RemoteFile
;
13 use wcf\system\SingletonFactory
;
15 use wcf\util\HTTPRequest
;
20 * Provides functions to manage package updates.
22 * @author Alexander Ebert
23 * @copyright 2001-2017 WoltLab GmbH
24 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
25 * @package WoltLabSuite\Core\System\Package
27 class PackageUpdateDispatcher
extends SingletonFactory
{
28 protected $hasAuthCode = false;
29 protected $purchasedVersions = [
35 * Refreshes the package database.
37 * @param integer[] $packageUpdateServerIDs
38 * @param boolean $ignoreCache
40 public function refreshPackageDatabase(array $packageUpdateServerIDs = [], $ignoreCache = false) {
41 // get update server data
42 $tmp = PackageUpdateServer
::getActiveUpdateServers($packageUpdateServerIDs);
46 $foundWoltLabServer = false;
47 $requirePurchasedVersions = false;
48 foreach ($tmp as $updateServer) {
49 if ($ignoreCache ||
$updateServer->lastUpdateTime
< TIME_NOW
- 600) {
50 if (preg_match('~^https?://(?:update|store)\.woltlab\.com\/~', $updateServer->serverURL
)) {
51 $requirePurchasedVersions = true;
53 // move a woltlab.com update server to the front of the queue to probe for SSL support
54 if (!$foundWoltLabServer) {
55 array_unshift($updateServers, $updateServer);
56 $foundWoltLabServer = true;
62 $updateServers[] = $updateServer;
66 if ($requirePurchasedVersions && PACKAGE_SERVER_AUTH_CODE
) {
67 $this->getPurchasedVersions();
71 $refreshedPackageLists = false;
72 foreach ($updateServers as $updateServer) {
76 $this->getPackageUpdateXML($updateServer);
77 $refreshedPackageLists = true;
79 catch (SystemException
$e) {
80 $errorMessage = $e->getMessage();
82 catch (PackageUpdateUnauthorizedException
$e) {
83 $reply = $e->getRequest()->getReply();
84 list($errorMessage) = reset($reply['httpHeaders']);
89 $updateServerEditor = new PackageUpdateServerEditor($updateServer);
90 $updateServerEditor->update([
91 'status' => 'offline',
92 'errorMessage' => $errorMessage
97 if ($refreshedPackageLists) {
98 PackageUpdateCacheBuilder
::getInstance()->reset();
102 protected function getPurchasedVersions() {
103 if (!RemoteFile
::supportsSSL()) {
107 $request = new HTTPRequest(
108 'https://api.woltlab.com/1.0/customer/license/list.json',
110 ['authCode' => PACKAGE_SERVER_AUTH_CODE
]
115 $reply = JSON
::decode($request->getReply()['body']);
116 if ($reply['status'] == 200) {
117 $this->hasAuthCode
= true;
118 $this->purchasedVersions
= [
119 'woltlab' => (isset($reply['woltlab']) ?
$reply['woltlab'] : []),
120 'pluginstore' => (isset($reply['pluginstore']) ?
$reply['pluginstore'] : [])
124 catch (SystemException
$e) {
130 * Fetches the package_update.xml from an update server.
132 * @param PackageUpdateServer $updateServer
133 * @param boolean $forceHTTP
134 * @throws PackageUpdateUnauthorizedException
135 * @throws SystemException
137 protected function getPackageUpdateXML(PackageUpdateServer
$updateServer, $forceHTTP = false) {
139 $authData = $updateServer->getAuthData();
140 if ($authData) $settings['auth'] = $authData;
142 $secureConnection = $updateServer->attemptSecureConnection();
143 if ($secureConnection && !$forceHTTP) $settings['timeout'] = 5;
145 $request = new HTTPRequest($updateServer->getListURL($forceHTTP), $settings);
147 if ($updateServer->apiVersion
== '2.1') {
148 // skip etag check for WoltLab servers when an auth code is provided
149 if (!preg_match('~^https?://(?:update|store)\.woltlab\.com\/~', $updateServer->serverURL
) ||
!PACKAGE_SERVER_AUTH_CODE
) {
150 $metaData = $updateServer->getMetaData();
151 if (isset($metaData['list']['etag'])) $request->addHeader('if-none-match', $metaData['list']['etag']);
152 if (isset($metaData['list']['lastModified'])) $request->addHeader('if-modified-since', $metaData['list']['lastModified']);
158 $reply = $request->getReply();
160 catch (HTTPUnauthorizedException
$e) {
161 throw new PackageUpdateUnauthorizedException($request, $updateServer);
163 catch (SystemException
$e) {
164 $reply = $request->getReply();
166 $statusCode = is_array($reply['statusCode']) ?
reset($reply['statusCode']) : $reply['statusCode'];
167 // status code 0 is a connection timeout
168 if (!$statusCode && $secureConnection) {
169 if (preg_match('~https?://(?:update|store)\.woltlab\.com\/~', $updateServer->serverURL
)) {
170 // woltlab.com servers are most likely to be available, thus we assume that SSL connections are dropped
171 RemoteFile
::disableSSL();
175 $this->getPackageUpdateXML($updateServer, true);
179 throw new SystemException(WCF
::getLanguage()->get('wcf.acp.package.update.error.listNotFound') . ' ('.$statusCode.')');
182 // parse given package update xml
183 $allNewPackages = false;
184 if ($updateServer->apiVersion
== '2.0' ||
$reply['statusCode'] != 304) {
185 $allNewPackages = $this->parsePackageUpdateXML($updateServer, $reply['body']);
189 'lastUpdateTime' => TIME_NOW
,
190 'status' => 'online',
194 // check if server indicates support for a newer API
195 if ($updateServer->apiVersion
== '2.0' && !empty($reply['httpHeaders']['wcf-update-server-api'])) {
196 $apiVersions = explode(' ', reset($reply['httpHeaders']['wcf-update-server-api']));
197 if (in_array('2.1', $apiVersions)) {
198 $data['apiVersion'] = '2.1';
203 if ($updateServer->apiVersion
== '2.1' ||
(isset($data['apiVersion']) && $data['apiVersion'] == '2.1')) {
204 if (empty($reply['httpHeaders']['etag']) && empty($reply['httpHeaders']['last-modified'])) {
205 throw new SystemException("Missing required HTTP headers 'etag' and 'last-modified'.");
207 else if (empty($reply['httpHeaders']['wcf-update-server-ssl'])) {
208 throw new SystemException("Missing required HTTP header 'wcf-update-server-ssl'.");
211 $metaData['list'] = [];
212 if (!empty($reply['httpHeaders']['etag'])) $metaData['list']['etag'] = reset($reply['httpHeaders']['etag']);
213 if (!empty($reply['httpHeaders']['last-modified'])) $metaData['list']['lastModified'] = reset($reply['httpHeaders']['last-modified']);
215 $metaData['ssl'] = (reset($reply['httpHeaders']['wcf-update-server-ssl']) == 'true') ?
true : false;
217 $data['metaData'] = serialize($metaData);
219 unset($request, $reply);
221 if ($allNewPackages !== false) {
222 // purge package list
223 $sql = "DELETE FROM wcf".WCF_N
."_package_update
224 WHERE packageUpdateServerID = ?";
225 $statement = WCF
::getDB()->prepareStatement($sql);
226 $statement->execute([$updateServer->packageUpdateServerID
]);
229 if (!empty($allNewPackages)) {
230 $this->savePackageUpdates($allNewPackages, $updateServer->packageUpdateServerID
);
232 unset($allNewPackages);
235 // update server status
236 $updateServerEditor = new PackageUpdateServerEditor($updateServer);
237 $updateServerEditor->update($data);
241 * Parses a stream containing info from a packages_update.xml.
243 * @param PackageUpdateServer $updateServer
244 * @param string $content
246 * @throws SystemException
248 protected function parsePackageUpdateXML(PackageUpdateServer
$updateServer, $content) {
251 $xml->loadXML('packageUpdateServer.xml', $content);
252 $xpath = $xml->xpath();
254 $allNewPackages = [];
255 $packages = $xpath->query('/ns:section/ns:package');
256 /** @var \DOMElement $package */
257 foreach ($packages as $package) {
258 if (!Package
::isValidPackageName($package->getAttribute('name'))) {
259 throw new SystemException("'".$package->getAttribute('name')."' is not a valid package name.");
262 $allNewPackages[$package->getAttribute('name')] = $this->parsePackageUpdateXMLBlock($updateServer, $xpath, $package);
265 return $allNewPackages;
269 * Parses the xml structure from a packages_update.xml.
271 * @param PackageUpdateServer $updateServer
272 * @param \DOMXPath $xpath
273 * @param \DOMElement $package
276 protected function parsePackageUpdateXMLBlock(PackageUpdateServer
$updateServer, \DOMXPath
$xpath, \DOMElement
$package) {
277 // define default values
281 'isApplication' => 0,
282 'packageDescription' => '',
286 // parse package information
287 $elements = $xpath->query('./ns:packageinformation/*', $package);
288 foreach ($elements as $element) {
289 switch ($element->tagName
) {
291 $packageInfo['packageName'] = $element->nodeValue
;
294 case 'packagedescription':
295 $packageInfo['packageDescription'] = $element->nodeValue
;
298 case 'isapplication':
299 $packageInfo['isApplication'] = intval($element->nodeValue
);
304 // parse author information
305 $elements = $xpath->query('./ns:authorinformation/*', $package);
306 foreach ($elements as $element) {
307 switch ($element->tagName
) {
309 $packageInfo['author'] = $element->nodeValue
;
313 $packageInfo['authorURL'] = $element->nodeValue
;
319 if ($this->hasAuthCode
) {
320 if (preg_match('~^https?://update\.woltlab\.com~', $updateServer->serverURL
)) {
323 else if (preg_match('~^https?://store\.woltlab\.com~', $updateServer->serverURL
)) {
324 $key = 'pluginstore';
329 $elements = $xpath->query('./ns:versions/ns:version', $package);
330 /** @var \DOMElement $element */
331 foreach ($elements as $element) {
332 $versionNo = $element->getAttribute('name');
334 $isAccessible = ($element->getAttribute('accessible') == 'true') ?
1 : 0;
335 if ($key && $element->getAttribute('requireAuth') == 'true') {
336 $packageName = $package->getAttribute('name');
337 if (isset($this->purchasedVersions
[$key][$packageName])) {
338 if ($this->purchasedVersions
[$key][$packageName] == '*') {
342 $isAccessible = (Package
::compareVersion($versionNo, $this->purchasedVersions
[$key][$packageName] . '99', '<=') ?
1 : 0);
350 $packageInfo['versions'][$versionNo] = ['isAccessible' => $isAccessible];
352 $children = $xpath->query('child::*', $element);
353 /** @var \DOMElement $child */
354 foreach ($children as $child) {
355 switch ($child->tagName
) {
357 $fromversions = $xpath->query('child::*', $child);
358 foreach ($fromversions as $fromversion) {
359 $packageInfo['versions'][$versionNo]['fromversions'][] = $fromversion->nodeValue
;
364 $packageInfo['versions'][$versionNo]['packageDate'] = $child->nodeValue
;
368 $packageInfo['versions'][$versionNo]['file'] = $child->nodeValue
;
371 case 'requiredpackages':
372 $requiredPackages = $xpath->query('child::*', $child);
374 /** @var \DOMElement $requiredPackage */
375 foreach ($requiredPackages as $requiredPackage) {
376 $minVersion = $requiredPackage->getAttribute('minversion');
377 $required = $requiredPackage->nodeValue
;
379 $packageInfo['versions'][$versionNo]['requiredPackages'][$required] = [];
380 if (!empty($minVersion)) {
381 $packageInfo['versions'][$versionNo]['requiredPackages'][$required]['minversion'] = $minVersion;
386 case 'optionalpackages':
387 $packageInfo['versions'][$versionNo]['optionalPackages'] = [];
389 $optionalPackages = $xpath->query('child::*', $child);
390 foreach ($optionalPackages as $optionalPackage) {
391 $packageInfo['versions'][$versionNo]['optionalPackages'][] = $optionalPackage->nodeValue
;
395 case 'excludedpackages':
396 $excludedpackages = $xpath->query('child::*', $child);
397 /** @var \DOMElement $excludedPackage */
398 foreach ($excludedpackages as $excludedPackage) {
399 $exclusion = $excludedPackage->nodeValue
;
400 $version = $excludedPackage->getAttribute('version');
402 $packageInfo['versions'][$versionNo]['excludedPackages'][$exclusion] = [];
403 if (!empty($version)) {
404 $packageInfo['versions'][$versionNo]['excludedPackages'][$exclusion]['version'] = $version;
410 $packageInfo['versions'][$versionNo]['license'] = [
411 'license' => $child->nodeValue
,
412 'licenseURL' => $child->hasAttribute('url') ?
$child->getAttribute('url') : ''
423 * Updates information parsed from a packages_update.xml into the database.
425 * @param array $allNewPackages
426 * @param integer $packageUpdateServerID
428 protected function savePackageUpdates(array &$allNewPackages, $packageUpdateServerID) {
430 $excludedPackagesParameters = $fromversionParameters = $insertParameters = $optionalInserts = $requirementInserts = [];
431 WCF
::getDB()->beginTransaction();
432 foreach ($allNewPackages as $identifier => $packageData) {
433 // create new database entry
434 $packageUpdate = PackageUpdateEditor
::create([
435 'packageUpdateServerID' => $packageUpdateServerID,
436 'package' => $identifier,
437 'packageName' => $packageData['packageName'],
438 'packageDescription' => $packageData['packageDescription'],
439 'author' => $packageData['author'],
440 'authorURL' => $packageData['authorURL'],
441 'isApplication' => $packageData['isApplication']
444 $packageUpdateID = $packageUpdate->packageUpdateID
;
446 // register version(s) of this update package.
447 if (isset($packageData['versions'])) {
448 foreach ($packageData['versions'] as $packageVersion => $versionData) {
449 if (isset($versionData['file'])) $packageFile = $versionData['file'];
450 else $packageFile = '';
452 // create new database entry
453 $version = PackageUpdateVersionEditor
::create([
454 'filename' => $packageFile,
455 'license' => isset($versionData['license']['license']) ?
$versionData['license']['license'] : '',
456 'licenseURL' => isset($versionData['license']['license']) ?
$versionData['license']['licenseURL'] : '',
457 'isAccessible' => $versionData['isAccessible'] ?
1 : 0,
458 'packageDate' => $versionData['packageDate'],
459 'packageUpdateID' => $packageUpdateID,
460 'packageVersion' => $packageVersion
463 $packageUpdateVersionID = $version->packageUpdateVersionID
;
465 // register requirement(s) of this update package version.
466 if (isset($versionData['requiredPackages'])) {
467 foreach ($versionData['requiredPackages'] as $requiredIdentifier => $required) {
468 $requirementInserts[] = [
469 'packageUpdateVersionID' => $packageUpdateVersionID,
470 'package' => $requiredIdentifier,
471 'minversion' => isset($required['minversion']) ?
$required['minversion'] : ''
476 // register optional packages of this update package version
477 if (isset($versionData['optionalPackages'])) {
478 foreach ($versionData['optionalPackages'] as $optionalPackage) {
479 $optionalInserts[] = [
480 'packageUpdateVersionID' => $packageUpdateVersionID,
481 'package' => $optionalPackage
486 // register excluded packages of this update package version.
487 if (isset($versionData['excludedPackages'])) {
488 foreach ($versionData['excludedPackages'] as $excludedIdentifier => $exclusion) {
489 $excludedPackagesParameters[] = [
490 'packageUpdateVersionID' => $packageUpdateVersionID,
491 'excludedPackage' => $excludedIdentifier,
492 'excludedPackageVersion' => isset($exclusion['version']) ?
$exclusion['version'] : ''
497 // register fromversions of this update package version.
498 if (isset($versionData['fromversions'])) {
499 foreach ($versionData['fromversions'] as $fromversion) {
500 $fromversionInserts[] = [
501 'packageUpdateVersionID' => $packageUpdateVersionID,
502 'fromversion' => $fromversion
509 WCF
::getDB()->commitTransaction();
511 // save requirements, excluded packages and fromversions
512 // insert requirements
513 if (!empty($requirementInserts)) {
514 $sql = "INSERT INTO wcf".WCF_N
."_package_update_requirement
515 (packageUpdateVersionID, package, minversion)
517 $statement = WCF
::getDB()->prepareStatement($sql);
518 WCF
::getDB()->beginTransaction();
519 foreach ($requirementInserts as $requirement) {
520 $statement->execute([
521 $requirement['packageUpdateVersionID'],
522 $requirement['package'],
523 $requirement['minversion']
526 WCF
::getDB()->commitTransaction();
530 if (!empty($optionalInserts)) {
531 $sql = "INSERT INTO wcf".WCF_N
."_package_update_optional
532 (packageUpdateVersionID, package)
534 $statement = WCF
::getDB()->prepareStatement($sql);
535 WCF
::getDB()->beginTransaction();
536 foreach ($optionalInserts as $requirement) {
537 $statement->execute([
538 $requirement['packageUpdateVersionID'],
539 $requirement['package']
542 WCF
::getDB()->commitTransaction();
546 if (!empty($excludedPackagesParameters)) {
547 $sql = "INSERT INTO wcf".WCF_N
."_package_update_exclusion
548 (packageUpdateVersionID, excludedPackage, excludedPackageVersion)
550 $statement = WCF
::getDB()->prepareStatement($sql);
551 WCF
::getDB()->beginTransaction();
552 foreach ($excludedPackagesParameters as $excludedPackage) {
553 $statement->execute([
554 $excludedPackage['packageUpdateVersionID'],
555 $excludedPackage['excludedPackage'],
556 $excludedPackage['excludedPackageVersion']
559 WCF
::getDB()->commitTransaction();
562 // insert fromversions
563 if (!empty($fromversionInserts)) {
564 $sql = "INSERT INTO wcf".WCF_N
."_package_update_fromversion
565 (packageUpdateVersionID, fromversion)
567 $statement = WCF
::getDB()->prepareStatement($sql);
568 WCF
::getDB()->beginTransaction();
569 foreach ($fromversionInserts as $fromversion) {
570 $statement->execute([
571 $fromversion['packageUpdateVersionID'],
572 $fromversion['fromversion']
575 WCF
::getDB()->commitTransaction();
580 * Returns a list of available updates for installed packages.
582 * @param boolean $removeRequirements
583 * @param boolean $removeOlderMinorReleases
586 public function getAvailableUpdates($removeRequirements = true, $removeOlderMinorReleases = false) {
589 // get update server data
590 $updateServers = PackageUpdateServer
::getActiveUpdateServers();
591 $packageUpdateServerIDs = array_keys($updateServers);
592 if (empty($packageUpdateServerIDs)) return $updates;
594 // get existing packages and their versions
595 $existingPackages = [];
596 $sql = "SELECT packageID, package, packageDescription, packageName,
597 packageVersion, packageDate, author, authorURL, isApplication
598 FROM wcf".WCF_N
."_package";
599 $statement = WCF
::getDB()->prepareStatement($sql);
600 $statement->execute();
601 while ($row = $statement->fetchArray()) {
602 $existingPackages[$row['package']][] = $row;
604 if (empty($existingPackages)) return $updates;
606 // get all update versions
607 $conditions = new PreparedStatementConditionBuilder();
608 $conditions->add("pu.packageUpdateServerID IN (?)", [$packageUpdateServerIDs]);
609 $conditions->add("package IN (SELECT DISTINCT package FROM wcf".WCF_N
."_package)");
611 $sql = "SELECT pu.packageUpdateID, pu.packageUpdateServerID, pu.package,
612 puv.packageUpdateVersionID, puv.packageDate, puv.filename, puv.packageVersion
613 FROM wcf".WCF_N
."_package_update pu
614 LEFT JOIN wcf".WCF_N
."_package_update_version puv
615 ON (puv.packageUpdateID = pu.packageUpdateID AND puv.isAccessible = 1)
617 $statement = WCF
::getDB()->prepareStatement($sql);
618 $statement->execute($conditions->getParameters());
619 while ($row = $statement->fetchArray()) {
621 foreach ($existingPackages[$row['package']] as $existingVersion) {
622 if (Package
::compareVersion($existingVersion['packageVersion'], $row['packageVersion'], '<')) {
624 if (!isset($updates[$existingVersion['packageID']])) {
625 $existingVersion['versions'] = [];
626 $updates[$existingVersion['packageID']] = $existingVersion;
630 if (!isset($updates[$existingVersion['packageID']]['versions'][$row['packageVersion']])) {
631 $updates[$existingVersion['packageID']]['versions'][$row['packageVersion']] = [
632 'packageDate' => $row['packageDate'],
633 'packageVersion' => $row['packageVersion'],
639 $updates[$existingVersion['packageID']]['versions'][$row['packageVersion']]['servers'][] = [
640 'packageUpdateID' => $row['packageUpdateID'],
641 'packageUpdateServerID' => $row['packageUpdateServerID'],
642 'packageUpdateVersionID' => $row['packageUpdateVersionID'],
643 'filename' => $row['filename']
649 // sort package versions
650 // and remove old versions
651 foreach ($updates as $packageID => $data) {
652 uksort($updates[$packageID]['versions'], ['wcf\data\package\Package', 'compareVersion']);
653 $updates[$packageID]['version'] = end($updates[$packageID]['versions']);
656 // remove requirements of application packages
657 if ($removeRequirements) {
658 foreach ($existingPackages as $identifier => $instances) {
659 foreach ($instances as $instance) {
660 if ($instance['isApplication'] && isset($updates[$instance['packageID']])) {
661 $updates = $this->removeUpdateRequirements($updates, $updates[$instance['packageID']]['version']['servers'][0]['packageUpdateVersionID']);
667 // remove older minor releases from list, e.g. only display 1.0.2, even if 1.0.1 is available
668 if ($removeOlderMinorReleases) {
669 foreach ($updates as &$updateData) {
670 $highestVersions = [];
671 foreach ($updateData['versions'] as $versionNumber => $dummy) {
672 if (preg_match('~^(\d+\.\d+)\.~', $versionNumber, $matches)) {
673 $major = $matches[1];
674 if (isset($highestVersions[$major])) {
675 if (Package
::compareVersion($highestVersions[$major], $versionNumber, '<')) {
676 // version is newer, discard current version
677 unset($updateData['versions'][$highestVersions[$major]]);
678 $highestVersions[$major] = $versionNumber;
681 // version is lower, discard
682 unset($updateData['versions'][$versionNumber]);
686 $highestVersions[$major] = $versionNumber;
698 * Removes unnecessary updates of requirements from the list of available updates.
700 * @param array $updates
701 * @param integer $packageUpdateVersionID
702 * @return array $updates
704 protected function removeUpdateRequirements(array $updates, $packageUpdateVersionID) {
705 $sql = "SELECT pur.package, pur.minversion, p.packageID
706 FROM wcf".WCF_N
."_package_update_requirement pur
707 LEFT JOIN wcf".WCF_N
."_package p
708 ON (p.package = pur.package)
709 WHERE pur.packageUpdateVersionID = ?";
710 $statement = WCF
::getDB()->prepareStatement($sql);
711 $statement->execute([$packageUpdateVersionID]);
712 while ($row = $statement->fetchArray()) {
713 if (isset($updates[$row['packageID']])) {
714 $updates = $this->removeUpdateRequirements($updates, $updates[$row['packageID']]['version']['servers'][0]['packageUpdateVersionID']);
715 if (Package
::compareVersion($row['minversion'], $updates[$row['packageID']]['version']['packageVersion'], '>=')) {
716 unset($updates[$row['packageID']]);
725 * Creates a new package installation scheduler.
727 * @param array $selectedPackages
728 * @return PackageInstallationScheduler
730 public function prepareInstallation(array $selectedPackages) {
731 return new PackageInstallationScheduler($selectedPackages);
735 * Returns package update versions of the specified package.
737 * @param string $package package identifier
738 * @param string $version package version
739 * @return array package update versions
740 * @throws SystemException
742 public function getPackageUpdateVersions($package, $version = '') {
743 // get newest package version
744 if (empty($version)) {
745 $version = $this->getNewestPackageVersion($package);
749 $sql = "SELECT puv.*, pu.*, pus.loginUsername, pus.loginPassword
750 FROM wcf".WCF_N
."_package_update_version puv
751 LEFT JOIN wcf".WCF_N
."_package_update pu
752 ON (pu.packageUpdateID = puv.packageUpdateID)
753 LEFT JOIN wcf".WCF_N
."_package_update_server pus
754 ON (pus.packageUpdateServerID = pu.packageUpdateServerID)
756 AND puv.packageVersion = ?
757 AND puv.isAccessible = ?
758 AND pus.isDisabled = ?";
759 $statement = WCF
::getDB()->prepareStatement($sql);
760 $statement->execute([
766 $versions = $statement->fetchAll(\PDO
::FETCH_ASSOC
);
768 if (empty($versions)) {
769 throw new SystemException("Cannot find package '".$package."' in version '".$version."'");
776 * Returns the newest available version of a package.
778 * @param string $package package identifier
779 * @return string newest package version
781 public function getNewestPackageVersion($package) {
784 $sql = "SELECT packageVersion
785 FROM wcf".WCF_N
."_package_update_version
786 WHERE packageUpdateID IN (
787 SELECT packageUpdateID
788 FROM wcf".WCF_N
."_package_update
791 $statement = WCF
::getDB()->prepareStatement($sql);
792 $statement->execute([$package]);
793 while ($row = $statement->fetchArray()) {
794 $versions[$row['packageVersion']] = $row['packageVersion'];
797 // sort by version number
798 usort($versions, [Package
::class, 'compareVersion']);
800 // take newest (last)
801 return array_pop($versions);
805 * Stores the filename of a download in session.
807 * @param string $package package identifier
808 * @param string $version package version
809 * @param string $filename
811 public function cacheDownload($package, $version, $filename) {
812 $cachedDownloads = WCF
::getSession()->getVar('cachedPackageUpdateDownloads');
813 if (!is_array($cachedDownloads)) {
814 $cachedDownloads = [];
818 $cachedDownloads[$package.'@'.$version] = $filename;
819 WCF
::getSession()->register('cachedPackageUpdateDownloads', $cachedDownloads);