Merge branch '6.0'
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / package / PackageInstallationScheduler.class.php
CommitLineData
11ade432 1<?php
a9229942 2
11ade432 3namespace wcf\system\package;
a9229942 4
d344d364
MW
5use GuzzleHttp\Exception\ClientException;
6use GuzzleHttp\Psr7\Request;
7use GuzzleHttp\RequestOptions;
11ade432 8use wcf\data\package\Package;
3536d2fe 9use wcf\data\package\PackageCache;
a9229942
TD
10use wcf\data\package\update\PackageUpdate;
11use wcf\data\package\update\server\PackageUpdateServer;
11ade432 12use wcf\system\database\util\PreparedStatementConditionBuilder;
437e4e37 13use wcf\system\exception\NamedUserException;
11ade432 14use wcf\system\exception\SystemException;
d344d364 15use wcf\system\io\HttpFactory;
89f91c4a
AE
16use wcf\system\package\exception\IncoherentUpdatePath;
17use wcf\system\package\exception\UnknownUpdatePath;
11ade432
AE
18use wcf\system\WCF;
19use wcf\util\FileUtil;
20
21/**
22 * Contains business logic related to preparation of package installations.
a9229942
TD
23 *
24 * @author Alexander Ebert
25 * @copyright 2001-2019 WoltLab GmbH
26 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
11ade432 27 */
37c8012e 28final class PackageInstallationScheduler
a9229942
TD
29{
30 /**
31 * stack of package installations / updates
32 * @var array
33 */
34 protected $packageInstallationStack = [];
35
36 /**
37 * list of package update servers
38 * @var PackageUpdateServer[]
39 */
40 protected $packageUpdateServers = [];
41
42 /**
43 * list of packages to update or install
44 * @var array
45 */
46 protected $selectedPackages = [];
47
48 /**
49 * virtual package versions
50 * @var array
51 */
52 protected $virtualPackageVersions = [];
53
54 /**
55 * Creates a new instance of PackageInstallationScheduler
56 *
57 * @param string[] $selectedPackages
58 */
59 public function __construct(array $selectedPackages)
60 {
61 $this->selectedPackages = $selectedPackages;
62 $this->packageUpdateServers = PackageUpdateServer::getActiveUpdateServers();
63 }
64
65 /**
66 * Builds the stack of package installations / updates.
67 *
68 * @param bool $validateInstallInstructions
69 */
70 public function buildPackageInstallationStack($validateInstallInstructions = false)
71 {
72 foreach ($this->selectedPackages as $package => $version) {
73 $this->tryToInstallPackage($package, $version, true, $validateInstallInstructions);
74 }
75 }
76
77 /**
78 * Tries to install a new package. Checks the virtual package version list.
79 *
80 * @param string $package package identifier
81 * @param string $minversion preferred package version
82 * @param bool $installOldVersion true, if you want to install the package in the given minversion and not in the newest version
83 * @param bool $validateInstallInstructions
84 */
85 protected function tryToInstallPackage(
86 $package,
87 $minversion = '',
88 $installOldVersion = false,
89 $validateInstallInstructions = false
90 ) {
91 // check virtual package version
92 if (isset($this->virtualPackageVersions[$package])) {
93 if (
94 !empty($minversion) && Package::compareVersion(
95 $this->virtualPackageVersions[$package],
96 $minversion,
97 '<'
98 )
99 ) {
100 $stackPosition = -1;
101 // remove installation of older version
102 foreach ($this->packageInstallationStack as $key => $value) {
103 if ($value['package'] == $package) {
104 $stackPosition = $key;
105 break;
106 }
107 }
108
109 // install newer version
110 $this->installPackage(
111 $package,
112 ($installOldVersion ? $minversion : ''),
113 $stackPosition,
114 $validateInstallInstructions
115 );
116 }
117 } else {
118 // check if package is already installed
119 $packageID = PackageCache::getInstance()->getPackageID($package);
120 if ($packageID === null) {
121 // package is missing -> install
122 $this->installPackage(
123 $package,
124 ($installOldVersion ? $minversion : ''),
125 -1,
126 $validateInstallInstructions
127 );
128 } else {
129 $package = PackageCache::getInstance()->getPackage($packageID);
130 if (!empty($minversion) && Package::compareVersion($package->packageVersion, $minversion, '<')) {
131 $this->updatePackage($packageID, ($installOldVersion ? $minversion : ''));
132 }
133 }
134 }
135 }
136
137 /**
138 * Installs a new package.
139 *
140 * @param string $package package identifier
141 * @param string $version package version
142 * @param int $stackPosition
143 * @param bool $validateInstallInstructions
144 */
145 protected function installPackage(
146 $package,
147 $version = '',
148 $stackPosition = -1,
149 $validateInstallInstructions = false
150 ) {
151 // get package update versions
152 $packageUpdateVersions = PackageUpdateDispatcher::getInstance()->getPackageUpdateVersions($package, $version);
153
154 // resolve requirements
155 $this->resolveRequirements($packageUpdateVersions[0]['packageUpdateVersionID']);
156
157 // download package
158 $download = $this->downloadPackage($package, $packageUpdateVersions, $validateInstallInstructions);
159
160 // add to stack
161 $data = [
162 'packageName' => $packageUpdateVersions[0]['packageName'],
163 'packageVersion' => $packageUpdateVersions[0]['packageVersion'],
164 'package' => $package,
165 'packageID' => 0,
166 'archive' => $download,
167 'action' => 'install',
168 ];
169 if ($stackPosition == -1) {
170 $this->packageInstallationStack[] = $data;
171 } else {
172 $this->packageInstallationStack[$stackPosition] = $data;
173 }
174
175 // update virtual versions
176 $this->virtualPackageVersions[$package] = $packageUpdateVersions[0]['packageVersion'];
177 }
178
179 /**
180 * Resolves the package requirements of an package update.
181 * Starts the installation or update to higher version of required packages.
182 *
183 * @param int $packageUpdateVersionID
184 */
185 protected function resolveRequirements($packageUpdateVersionID)
186 {
187 // resolve requirements
188 $requiredPackages = [];
189 $requirementsCache = [];
190 $sql = "SELECT *
7be0858b 191 FROM wcf1_package_update_requirement
a9229942 192 WHERE packageUpdateVersionID = ?";
7be0858b 193 $statement = WCF::getDB()->prepare($sql);
a9229942
TD
194 $statement->execute([$packageUpdateVersionID]);
195 while ($row = $statement->fetchArray()) {
196 $requiredPackages[] = $row['package'];
197 $requirementsCache[] = $row;
198 }
199
200 if (!empty($requiredPackages)) {
201 // find installed packages
202 $conditions = new PreparedStatementConditionBuilder();
203 $conditions->add("package IN (?)", [$requiredPackages]);
204
205 $installedPackages = [];
7be0858b
TD
206 $sql = "SELECT packageID,
207 package,
208 packageVersion
209 FROM wcf1_package
210 {$conditions}";
211 $statement = WCF::getDB()->prepare($sql);
a9229942
TD
212 $statement->execute($conditions->getParameters());
213 while ($row = $statement->fetchArray()) {
214 if (!isset($installedPackages[$row['package']])) {
215 $installedPackages[$row['package']] = [];
216 }
217 $installedPackages[$row['package']][$row['packageID']] = ($this->virtualPackageVersions[$row['packageID']] ?? $row['packageVersion']);
218 }
219
220 // check installed / missing packages
221 foreach ($requirementsCache as $row) {
222 if (isset($installedPackages[$row['package']])) {
223 // package already installed -> check version
224 // sort multiple instances by version number
93356aca 225 \uasort($installedPackages[$row['package']], Package::compareVersion(...));
a9229942
TD
226
227 $packageID = 0;
228 foreach ($installedPackages[$row['package']] as $packageID => $packageVersion) {
229 if (
230 empty($row['minversion']) || Package::compareVersion(
231 $row['minversion'],
232 $packageVersion,
233 '<='
234 )
235 ) {
236 continue 2;
237 }
238 }
239
240 // package version too low -> update necessary
241 $this->updatePackage($packageID, $row['minversion']);
242 } else {
243 $this->tryToInstallPackage($row['package'], $row['minversion']);
244 }
245 }
246 }
247 }
248
249 /**
250 * Tries to download a package from available update servers.
251 *
252 * @param string $package package identifier
253 * @param array $packageUpdateVersions package update versions
254 * @param bool $validateInstallInstructions
255 * @return string tmp filename of a downloaded package
256 * @throws PackageUpdateUnauthorizedException
257 * @throws SystemException
258 */
259 protected function downloadPackage($package, $packageUpdateVersions, $validateInstallInstructions = false)
260 {
261 // get download from cache
262 if ($filename = $this->getCachedDownload($package, $packageUpdateVersions[0]['packageVersion'])) {
263 return $filename;
264 }
265
266 // download file
267 foreach ($packageUpdateVersions as $packageUpdateVersion) {
268 // get auth data
269 $authData = $this->getAuthData($packageUpdateVersion);
d344d364
MW
270 $options = [];
271 if (!empty($authData)) {
272 $options[RequestOptions::AUTH] = [
273 $authData['username'],
274 $authData['password'],
275 ];
276 }
277 $client = HttpFactory::makeClient($options);
a9229942
TD
278
279 if ($packageUpdateVersion['filename']) {
d344d364
MW
280 $request = new Request(
281 'POST',
a9229942 282 $packageUpdateVersion['filename'],
d344d364
MW
283 ['Content-Type' => 'application/x-www-form-urlencoded'],
284 \http_build_query(
285 ['apiVersion' => PackageUpdate::API_VERSION],
286 '',
287 '&',
288 \PHP_QUERY_RFC1738
289 )
a9229942
TD
290 );
291 } else {
8f2e9f66
TD
292 $parameters = [
293 'apiVersion' => PackageUpdate::API_VERSION,
294 'packageName' => $packageUpdateVersion['package'],
295 'packageVersion' => $packageUpdateVersion['packageVersion'],
296 ];
297 if ($this->packageUpdateServers[$packageUpdateVersion['packageUpdateServerID']]->isTrustedServer()) {
298 $parameters['instanceId'] = \hash_hmac('sha256', 'api.woltlab.com', \WCF_UUID);
299 }
300
d344d364
MW
301 $request = new Request(
302 'POST',
a9229942 303 $this->packageUpdateServers[$packageUpdateVersion['packageUpdateServerID']]->getDownloadURL(),
d344d364
MW
304 ['Content-Type' => 'application/x-www-form-urlencoded'],
305 \http_build_query(
306 $parameters,
307 '',
308 '&',
309 \PHP_QUERY_RFC1738
310 )
a9229942
TD
311 );
312 }
313
314 try {
d344d364
MW
315 $response = $client->send($request);
316 } catch (ClientException $e) {
a9229942 317 throw new PackageUpdateUnauthorizedException(
d344d364
MW
318 $e->getResponse()->getStatusCode(),
319 $e->getResponse()->getHeaders(),
320 $e->getResponse()->getBody(),
a9229942
TD
321 $this->packageUpdateServers[$packageUpdateVersion['packageUpdateServerID']],
322 $packageUpdateVersion
323 );
324 }
325
a9229942 326 // check response
d344d364 327 if ($response->getStatusCode() !== 200) {
a9229942
TD
328 throw new SystemException(WCF::getLanguage()->getDynamicVariable(
329 'wcf.acp.package.error.downloadFailed',
330 ['__downloadPackage' => $package]
d344d364 331 ) . ' (' . $response->getBody() . ')');
a9229942
TD
332 }
333
334 // write content to tmp file
335 $filename = FileUtil::getTemporaryFilename('package_');
d344d364
MW
336 \file_put_contents($filename, $response->getBody());
337 unset($response);
a9229942
TD
338
339 // test package
340 $archive = new PackageArchive($filename);
341 $archive->openArchive();
342
343 // check install instructions
344 if ($validateInstallInstructions) {
345 $installInstructions = $archive->getInstallInstructions();
346 if (empty($installInstructions)) {
347 throw new SystemException("Package '" . $archive->getLocalizedPackageInfo('packageName') . "' (" . $archive->getPackageInfo('name') . ") does not contain valid installation instructions.");
348 }
349 }
350
351 $archive->getTar()->close();
352
353 // cache download in session
354 PackageUpdateDispatcher::getInstance()->cacheDownload(
355 $package,
356 $packageUpdateVersion['packageVersion'],
357 $filename
358 );
359
360 return $filename;
361 }
362
363 return false;
364 }
365
366 /**
367 * Returns a list of excluded packages.
368 *
369 * @return array
370 */
371 public function getExcludedPackages()
372 {
373 $excludedPackages = [];
374
375 if (!empty($this->packageInstallationStack)) {
376 $packageInstallations = [];
377 $packageIdentifier = [];
378 foreach ($this->packageInstallationStack as $packageInstallation) {
379 $packageInstallation['newVersion'] = ($packageInstallation['action'] == 'update' ? $packageInstallation['toVersion'] : $packageInstallation['packageVersion']);
380 $packageInstallations[] = $packageInstallation;
381 $packageIdentifier[] = $packageInstallation['package'];
382 }
383
384 // check exclusions of the new packages
385 // get package update ids
386 $conditions = new PreparedStatementConditionBuilder();
387 $conditions->add("package IN (?)", [$packageIdentifier]);
388
7be0858b
TD
389 $sql = "SELECT packageUpdateID,
390 package
391 FROM wcf1_package_update
392 {$conditions}";
393 $statement = WCF::getDB()->prepare($sql);
a9229942
TD
394 $statement->execute($conditions->getParameters());
395 while ($row = $statement->fetchArray()) {
396 foreach ($packageInstallations as $key => $packageInstallation) {
397 if ($packageInstallation['package'] == $row['package']) {
398 $packageInstallations[$key]['packageUpdateID'] = $row['packageUpdateID'];
399 }
400 }
401 }
402
403 // get exclusions of the new packages
404 // build conditions
405 $conditions = '';
406 $statementParameters = [];
407 foreach ($packageInstallations as $packageInstallation) {
408 if (!empty($conditions)) {
409 $conditions .= ' OR ';
410 }
411 $conditions .= "(packageUpdateID = ? AND packageVersion = ?)";
412 $statementParameters[] = $packageInstallation['packageUpdateID'];
413 $statementParameters[] = $packageInstallation['newVersion'];
414 }
415
7be0858b
TD
416 $sql = "SELECT package.*,
417 package_update_exclusion.*,
a9229942
TD
418 package_update.packageUpdateID,
419 package_update.package
7be0858b
TD
420 FROM wcf1_package_update_exclusion package_update_exclusion
421 LEFT JOIN wcf1_package_update_version package_update_version
c240c98a 422 ON package_update_version.packageUpdateVersionID = package_update_exclusion.packageUpdateVersionID
7be0858b 423 LEFT JOIN wcf1_package_update package_update
c240c98a 424 ON package_update.packageUpdateID = package_update_version.packageUpdateID
7be0858b 425 LEFT JOIN wcf1_package package
c240c98a 426 ON package.package = package_update_exclusion.excludedPackage
a9229942
TD
427 WHERE package_update_exclusion.packageUpdateVersionID IN (
428 SELECT packageUpdateVersionID
7be0858b
TD
429 FROM wcf1_package_update_version
430 WHERE {$conditions}
a9229942
TD
431 )
432 AND package.package IS NOT NULL";
7be0858b 433 $statement = WCF::getDB()->prepare($sql);
a9229942
TD
434 $statement->execute($statementParameters);
435 while ($row = $statement->fetchArray()) {
436 foreach ($packageInstallations as $key => $packageInstallation) {
437 if ($packageInstallation['package'] == $row['package']) {
438 if (!isset($packageInstallations[$key]['excludedPackages'])) {
439 $packageInstallations[$key]['excludedPackages'] = [];
440 }
441 $packageInstallations[$key]['excludedPackages'][$row['excludedPackage']] = [
442 'package' => $row['excludedPackage'],
443 'version' => $row['excludedPackageVersion'],
444 ];
445
446 // check version
1996ec7a 447 if (
448 $row['excludedPackageVersion'] !== '*'
449 && Package::compareVersion($row['packageVersion'], $row['excludedPackageVersion'], '<')
450 ) {
451 continue;
a9229942
TD
452 }
453
454 $excludedPackages[] = [
455 'package' => $row['package'],
456 'packageName' => $packageInstallations[$key]['packageName'],
457 'packageVersion' => $packageInstallations[$key]['newVersion'],
458 'action' => $packageInstallations[$key]['action'],
459 'conflict' => 'newPackageExcludesExistingPackage',
460 'existingPackage' => $row['excludedPackage'],
461 'existingPackageName' => WCF::getLanguage()->get($row['packageName']),
462 'existingPackageVersion' => $row['packageVersion'],
463 ];
464 }
465 }
466 }
467
468 // check excluded packages of the existing packages
469 $conditions = new PreparedStatementConditionBuilder();
470 $conditions->add("excludedPackage IN (?)", [$packageIdentifier]);
471
7be0858b
TD
472 $sql = "SELECT package.*,
473 package_exclusion.*
474 FROM wcf1_package_exclusion package_exclusion
475 LEFT JOIN wcf1_package package
c240c98a 476 ON package.packageID = package_exclusion.packageID
7be0858b
TD
477 {$conditions}";
478 $statement = WCF::getDB()->prepare($sql);
a9229942
TD
479 $statement->execute($conditions->getParameters());
480 while ($row = $statement->fetchArray()) {
13b11e4c 481 foreach ($packageInstallations as $packageInstallation) {
a9229942
TD
482 if ($packageInstallation['package'] == $row['excludedPackage']) {
483 if (!empty($row['excludedPackageVersion'])) {
484 // check version
485 if (
1996ec7a 486 $row['excludedPackageVersion'] !== "*"
487 && Package::compareVersion(
a9229942
TD
488 $packageInstallation['newVersion'],
489 $row['excludedPackageVersion'],
490 '<'
491 )
492 ) {
493 continue;
494 }
495
496 // search exclusing package in stack
497 foreach ($packageInstallations as $packageUpdate) {
498 if ($packageUpdate['packageID'] == $row['packageID']) {
499 // check new exclusions
500 if (
501 !isset($packageUpdate['excludedPackages']) || !isset($packageUpdate['excludedPackages'][$row['excludedPackage']]) || (!empty($packageUpdate['excludedPackages'][$row['excludedPackage']]['version']) && Package::compareVersion(
502 $packageInstallation['newVersion'],
503 $packageUpdate['excludedPackages'][$row['excludedPackage']]['version'],
504 '<'
505 ))
506 ) {
507 continue 2;
508 }
509 }
510 }
511 }
512
513 $excludedPackages[] = [
514 'package' => $row['excludedPackage'],
515 'packageName' => $packageInstallation['packageName'],
516 'packageVersion' => $packageInstallation['newVersion'],
517 'action' => $packageInstallation['action'],
518 'conflict' => 'existingPackageExcludesNewPackage',
519 'existingPackage' => $row['package'],
520 'existingPackageName' => WCF::getLanguage()->get($row['packageName']),
521 'existingPackageVersion' => $row['packageVersion'],
522 ];
523 }
524 }
525 }
526 }
527
528 return $excludedPackages;
529 }
530
531 /**
532 * Returns the stack of package installations.
533 *
534 * @return array
535 */
536 public function getPackageInstallationStack()
537 {
538 return $this->packageInstallationStack;
539 }
540
541 /**
542 * Updates an existing package.
543 *
544 * @param int $packageID
545 * @param string $version
546 */
547 protected function updatePackage($packageID, $version)
548 {
549 // get package info
550 $package = PackageCache::getInstance()->getPackage($packageID);
551
552 // get current package version
553 $packageVersion = $package->packageVersion;
554 if (isset($this->virtualPackageVersions[$packageID])) {
555 $packageVersion = $this->virtualPackageVersions[$packageID];
556 // check virtual package version
557 if (Package::compareVersion($packageVersion, $version, '>=')) {
558 // virtual package version is greater than requested version
559 // skip package update
560 return;
561 }
562 }
563
564 // get highest version of the required major release
565 if (\preg_match('/(\d+\.\d+\.)/', $version, $match)) {
566 $sql = "SELECT DISTINCT packageVersion
7be0858b 567 FROM wcf1_package_update_version
a9229942
TD
568 WHERE packageUpdateID IN (
569 SELECT packageUpdateID
7be0858b 570 FROM wcf1_package_update
a9229942
TD
571 WHERE package = ?
572 )
573 AND packageVersion LIKE ?";
7be0858b 574 $statement = WCF::getDB()->prepare($sql);
a9229942
TD
575 $statement->execute([
576 $package->package,
577 $match[1] . '%',
578 ]);
579 $packageVersions = $statement->fetchAll(\PDO::FETCH_COLUMN);
580
581 $count = \count($packageVersions);
582 if ($count > 1) {
583 // sort by version number
93356aca 584 \usort($packageVersions, Package::compareVersion(...));
a9229942
TD
585
586 // get highest version
587 $version = \array_pop($packageVersions);
588 } elseif ($count === 1 && $version !== $packageVersions[0]) {
589 // This may happen if there is a compatible but newer version of the required
590 // version, that also happens to be the only available version. For example,
591 // "5.2.0" is requested but there is only "5.2.0 pl 1".
592 $version = $packageVersions[0];
593 }
594 }
595
596 // get all fromversion
597 $fromversions = [];
7be0858b
TD
598 $sql = "SELECT puv.packageVersion,
599 puf.fromversion
600 FROM wcf1_package_update_fromversion puf
601 LEFT JOIN wcf1_package_update_version puv
c240c98a 602 ON puv.packageUpdateVersionID = puf.packageUpdateVersionID
a9229942
TD
603 WHERE puf.packageUpdateVersionID IN (
604 SELECT packageUpdateVersionID
7be0858b 605 FROM wcf1_package_update_version
a9229942
TD
606 WHERE packageUpdateID IN (
607 SELECT packageUpdateID
7be0858b 608 FROM wcf1_package_update
a9229942
TD
609 WHERE package = ?
610 )
611 )";
7be0858b 612 $statement = WCF::getDB()->prepare($sql);
a9229942
TD
613 $statement->execute([$package->package]);
614 while ($row = $statement->fetchArray()) {
615 if (!isset($fromversions[$row['packageVersion']])) {
616 $fromversions[$row['packageVersion']] = [];
617 }
618 $fromversions[$row['packageVersion']][$row['fromversion']] = $row['fromversion'];
619 }
620
621 // sort by version number
93356aca 622 \uksort($fromversions, Package::compareVersion(...));
a9229942
TD
623
624 // find shortest update thread
89f91c4a
AE
625 try {
626 $updateThread = $this->findShortestUpdateThread($package->package, $fromversions, $packageVersion, $version);
627 } catch (IncoherentUpdatePath | UnknownUpdatePath $e) {
628 throw new NamedUserException($e->getMessage(), 0, $e);
629 }
a9229942
TD
630
631 // process update thread
632 foreach ($updateThread as $fromversion => $toVersion) {
633 $packageUpdateVersions = PackageUpdateDispatcher::getInstance()->getPackageUpdateVersions(
634 $package->package,
635 $toVersion
636 );
637
638 // resolve requirements
639 $this->resolveRequirements($packageUpdateVersions[0]['packageUpdateVersionID']);
640
641 // download package
642 $download = $this->downloadPackage($package->package, $packageUpdateVersions);
643
644 // add to stack
645 $this->packageInstallationStack[] = [
646 'packageName' => $package->getName(),
647 'fromversion' => $fromversion,
648 'toVersion' => $toVersion,
649 'package' => $package->package,
650 'packageID' => $packageID,
651 'archive' => $download,
652 'action' => 'update',
653 ];
654
655 // update virtual versions
656 $this->virtualPackageVersions[$packageID] = $toVersion;
657 }
658 }
659
660 /**
661 * Determines intermediate update steps using a backtracking algorithm in case there is no direct upgrade possible.
662 *
663 * @param string $package package identifier
664 * @param array $fromversions list of all fromversions
665 * @param string $currentVersion current package version
666 * @param string $newVersion new package version
667 * @return array list of update steps (old version => new version, old version => new version, ...)
668 * @throws SystemException
669 */
670 protected function findShortestUpdateThread($package, $fromversions, $currentVersion, $newVersion)
671 {
672 if (!isset($fromversions[$newVersion])) {
89f91c4a 673 throw new UnknownUpdatePath($package, $currentVersion, $newVersion);
a9229942
TD
674 }
675
676 // find direct update
677 foreach ($fromversions[$newVersion] as $fromversion) {
678 if (Package::checkFromversion($currentVersion, $fromversion)) {
679 return [$currentVersion => $newVersion];
680 }
681 }
682
683 // find intermediate update
684 $packageVersions = \array_keys($fromversions);
685 $updateThreadList = [];
686 foreach ($fromversions[$newVersion] as $fromversion) {
687 $innerUpdateThreadList = [];
688 // find matching package versions
689 foreach ($packageVersions as $packageVersion) {
690 if (
691 Package::checkFromversion($packageVersion, $fromversion) && Package::compareVersion(
692 $packageVersion,
693 $currentVersion,
694 '>'
695 ) && Package::compareVersion($packageVersion, $newVersion, '<')
696 ) {
93615390
AE
697 try {
698 $innerUpdateThreadList[] = $this->findShortestUpdateThread(
699 $package,
700 $fromversions,
701 $currentVersion,
702 $packageVersion
703 ) + [$packageVersion => $newVersion];
704 } catch (IncoherentUpdatePath $e) {
705 // Ignore issues caused by multiple split update paths
706 // where the first path has incoherent, but other valid
707 // paths exist.
708 continue;
709 }
a9229942
TD
710 }
711 }
712
713 if (!empty($innerUpdateThreadList)) {
714 // sort by length
93356aca 715 \usort($innerUpdateThreadList, $this->compareUpdateThreadLists(...));
a9229942
TD
716
717 // add to thread list
718 $updateThreadList[] = \array_shift($innerUpdateThreadList);
719 }
720 }
721
722 if (empty($updateThreadList)) {
89f91c4a 723 throw new IncoherentUpdatePath($package, $currentVersion, $newVersion);
a9229942
TD
724 }
725
726 // sort by length
93356aca 727 \usort($updateThreadList, $this->compareUpdateThreadLists(...));
a9229942
TD
728
729 // take shortest
730 return \array_shift($updateThreadList);
731 }
732
733 /**
734 * Compares the length of two updates threads.
a9229942 735 */
df03dfc7 736 protected function compareUpdateThreadLists(array $updateThreadListA, array $updateThreadListB): int
a9229942 737 {
df03dfc7 738 return \count($updateThreadListA) <=> \count($updateThreadListB);
a9229942
TD
739 }
740
741 /**
742 * Returns the filename of downloads stored in session or null if no stored downloads exist.
743 *
744 * @param string $package package identifier
745 * @param string $version package version
746 * @return string|bool
747 */
748 protected function getCachedDownload($package, $version)
749 {
750 $cachedDownloads = WCF::getSession()->getVar('cachedPackageUpdateDownloads');
751 if (isset($cachedDownloads[$package . '@' . $version]) && @\file_exists($cachedDownloads[$package . '@' . $version])) {
752 return $cachedDownloads[$package . '@' . $version];
753 }
754
755 return false;
756 }
757
758 /**
759 * Returns stored auth data the update server with given data.
760 *
761 * @param array $data
762 * @return array
763 */
764 protected function getAuthData(array $data)
765 {
766 $updateServer = new PackageUpdateServer(null, $data);
767
768 return $updateServer->getAuthData();
769 }
11ade432 770}