Merge branch 'origin/formBuilder' into reactions
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / package / PackageArchive.class.php
1 <?php
2 declare(strict_types=1);
3 namespace wcf\system\package;
4 use wcf\data\package\Package;
5 use wcf\system\database\util\PreparedStatementConditionBuilder;
6 use wcf\system\package\validation\PackageValidationException;
7 use wcf\system\io\Tar;
8 use wcf\system\WCF;
9 use wcf\util\DateUtil;
10 use wcf\util\FileUtil;
11 use wcf\util\XML;
12
13 /**
14 * Represents the archive of a package.
15 *
16 * @author Marcel Werk
17 * @copyright 2001-2018 WoltLab GmbH
18 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
19 * @package WoltLabSuite\Core\System\Package
20 */
21 class PackageArchive {
22 /**
23 * path to package archive
24 * @var string
25 */
26 protected $archive;
27
28 /**
29 * package object of an existing package
30 * @var Package
31 */
32 protected $package;
33
34 /**
35 * tar archive object
36 * @var Tar
37 */
38 protected $tar;
39
40 /**
41 * general package information
42 * @var array
43 */
44 protected $packageInfo = [];
45
46 /**
47 * author information
48 * @var array
49 */
50 protected $authorInfo = [];
51
52 /**
53 * list of requirements
54 * @var array
55 */
56 protected $requirements = [];
57
58 /**
59 * list of optional packages
60 * @var array
61 */
62 protected $optionals = [];
63
64 /**
65 * list of excluded packages
66 * @var array
67 */
68 protected $excludedPackages = [];
69
70 /**
71 * list of compatible API versions
72 * @var integer[]
73 */
74 protected $compatibility = [];
75
76 /**
77 * list of instructions
78 * @var mixed[][]
79 */
80 protected $instructions = [
81 'install' => [],
82 'update' => []
83 ];
84
85 /**
86 * default name of the package.xml file
87 * @var string
88 */
89 const INFO_FILE = 'package.xml';
90
91 /**
92 * Creates a new PackageArchive object.
93 *
94 * @param string $archive
95 * @param Package $package
96 */
97 public function __construct($archive, Package $package = null) {
98 $this->archive = $archive; // be careful: this is a string within this class,
99 // but an object in the packageStartInstallForm.class!
100 $this->package = $package;
101 }
102
103 /**
104 * Sets associated package object.
105 *
106 * @param Package $package
107 */
108 public function setPackage(Package $package) {
109 $this->package = $package;
110 }
111
112 /**
113 * Returns the name of the package archive.
114 *
115 * @return string
116 */
117 public function getArchive() {
118 return $this->archive;
119 }
120
121 /**
122 * Returns the object of the package archive.
123 *
124 * @return Tar
125 */
126 public function getTar() {
127 return $this->tar;
128 }
129
130 /**
131 * Opens the package archive and reads package information.
132 */
133 public function openArchive() {
134 // check whether archive exists and is a TAR archive
135 if (!file_exists($this->archive)) {
136 throw new PackageValidationException(PackageValidationException::FILE_NOT_FOUND, ['archive' => $this->archive]);
137 }
138
139 // open archive and read package information
140 $this->tar = new Tar($this->archive);
141 $this->readPackageInfo();
142 }
143
144 /**
145 * Extracts information about this package (parses package.xml).
146 */
147 protected function readPackageInfo() {
148 // search package.xml in package archive
149 // throw error message if not found
150 if ($this->tar->getIndexByFilename(self::INFO_FILE) === false) {
151 throw new PackageValidationException(PackageValidationException::MISSING_PACKAGE_XML, ['archive' => $this->archive]);
152 }
153
154 // extract package.xml, parse XML
155 // and compile an array with XML::getElementTree()
156 $xml = new XML();
157 try {
158 $xml->loadXML(self::INFO_FILE, $this->tar->extractToString(self::INFO_FILE));
159 }
160 catch (\Exception $e) { // bugfix to avoid file caching problems
161 $xml->loadXML(self::INFO_FILE, $this->tar->extractToString(self::INFO_FILE));
162 }
163
164 // parse xml
165 $xpath = $xml->xpath();
166 /** @var \DOMElement $package */
167 $package = $xpath->query('/ns:package')->item(0);
168
169 // package name
170 $packageName = $package->getAttribute('name');
171 if (!Package::isValidPackageName($packageName)) {
172 // package name is not a valid package identifier
173 throw new PackageValidationException(PackageValidationException::INVALID_PACKAGE_NAME, ['packageName' => $packageName]);
174 }
175
176 $this->packageInfo['name'] = $packageName;
177
178 // get package information
179 $packageInformation = $xpath->query('./ns:packageinformation', $package)->item(0);
180 $elements = $xpath->query('child::*', $packageInformation);
181 /** @var \DOMElement $element */
182 foreach ($elements as $element) {
183 switch ($element->tagName) {
184 case 'packagename':
185 case 'packagedescription':
186 case 'readme':
187 case 'license':
188 if (!isset($this->packageInfo[$element->tagName])) $this->packageInfo[$element->tagName] = [];
189
190 $languageCode = 'default';
191 if ($element->hasAttribute('language')) {
192 $languageCode = $element->getAttribute('language');
193 }
194
195 // fix case-sensitive names
196 $name = $element->tagName;
197 if ($name == 'packagename') $name = 'packageName';
198 else if ($name == 'packagedescription') $name = 'packageDescription';
199
200 $this->packageInfo[$name][$languageCode] = $element->nodeValue;
201 break;
202
203 case 'isapplication':
204 $this->packageInfo['isApplication'] = intval($element->nodeValue);
205 break;
206
207 case 'applicationdirectory':
208 if (preg_match('~^[a-z0-9\-\_]+$~', $element->nodeValue)) {
209 $this->packageInfo['applicationDirectory'] = $element->nodeValue;
210 }
211 break;
212
213 case 'packageurl':
214 $this->packageInfo['packageURL'] = $element->nodeValue;
215 break;
216
217 case 'version':
218 if (!Package::isValidVersion($element->nodeValue)) {
219 throw new PackageValidationException(PackageValidationException::INVALID_PACKAGE_VERSION, ['packageVersion' => $element->nodeValue]);
220 }
221
222 $this->packageInfo['version'] = $element->nodeValue;
223 break;
224
225 case 'date':
226 DateUtil::validateDate($element->nodeValue);
227
228 $this->packageInfo['date'] = @strtotime($element->nodeValue);
229 break;
230 }
231 }
232
233 // get author information
234 $authorInformation = $xpath->query('./ns:authorinformation', $package)->item(0);
235 $elements = $xpath->query('child::*', $authorInformation);
236 foreach ($elements as $element) {
237 $tagName = ($element->tagName == 'authorurl') ? 'authorURL' : $element->tagName;
238 $this->authorInfo[$tagName] = $element->nodeValue;
239 }
240
241 // get required packages
242 $elements = $xpath->query('child::ns:requiredpackages/ns:requiredpackage', $package);
243 foreach ($elements as $element) {
244 if (!Package::isValidPackageName($element->nodeValue)) {
245 throw new PackageValidationException(PackageValidationException::INVALID_PACKAGE_NAME, ['packageName' => $element->nodeValue]);
246 }
247
248 // read attributes
249 $data = ['name' => $element->nodeValue];
250 $attributes = $xpath->query('attribute::*', $element);
251 foreach ($attributes as $attribute) {
252 $data[$attribute->name] = $attribute->value;
253 }
254
255 $this->requirements[$element->nodeValue] = $data;
256 }
257
258 // get optional packages
259 $elements = $xpath->query('child::ns:optionalpackages/ns:optionalpackage', $package);
260 foreach ($elements as $element) {
261 if (!Package::isValidPackageName($element->nodeValue)) {
262 throw new PackageValidationException(PackageValidationException::INVALID_PACKAGE_NAME, ['packageName' => $element->nodeValue]);
263 }
264
265 // read attributes
266 $data = ['name' => $element->nodeValue];
267 $attributes = $xpath->query('attribute::*', $element);
268 foreach ($attributes as $attribute) {
269 $data[$attribute->name] = $attribute->value;
270 }
271
272 $this->optionals[] = $data;
273 }
274
275 // get excluded packages
276 $elements = $xpath->query('child::ns:excludedpackages/ns:excludedpackage', $package);
277 foreach ($elements as $element) {
278 if (!Package::isValidPackageName($element->nodeValue)) {
279 throw new PackageValidationException(PackageValidationException::INVALID_PACKAGE_NAME, ['packageName' => $element->nodeValue]);
280 }
281
282 // read attributes
283 $data = ['name' => $element->nodeValue];
284 $attributes = $xpath->query('attribute::*', $element);
285 foreach ($attributes as $attribute) {
286 $data[$attribute->name] = $attribute->value;
287 }
288
289 $this->excludedPackages[] = $data;
290 }
291
292 // get api compatibility
293 $elements = $xpath->query('child::ns:compatibility/ns:api', $package);
294 foreach ($elements as $element) {
295 if (!$element->hasAttribute('version')) continue;
296
297 $version = $element->getAttribute('version');
298 if (!preg_match('~^(?:201[7-9]|20[2-9][0-9])$~', $version)) {
299 throw new PackageValidationException(PackageValidationException::INVALID_API_VERSION, ['version' => $version]);
300 }
301
302 $this->compatibility[] = $version;
303 }
304
305 // get instructions
306 $elements = $xpath->query('./ns:instructions', $package);
307 foreach ($elements as $element) {
308 $instructionData = [];
309 $instructions = $xpath->query('./ns:instruction', $element);
310 /** @var \DOMElement $instruction */
311 foreach ($instructions as $instruction) {
312 $data = [];
313 $attributes = $xpath->query('attribute::*', $instruction);
314 foreach ($attributes as $attribute) {
315 $data[$attribute->name] = $attribute->value;
316 }
317
318 $instructionData[] = [
319 'attributes' => $data,
320 'pip' => $instruction->getAttribute('type'),
321 'value' => $instruction->nodeValue
322 ];
323 }
324
325 $fromVersion = $element->getAttribute('fromversion');
326 $type = $element->getAttribute('type');
327
328 if ($type == 'install') {
329 $this->instructions['install'] = $instructionData;
330 }
331 else {
332 $this->instructions['update'][$fromVersion] = $instructionData;
333 }
334 }
335
336 // add com.woltlab.wcf to package requirements
337 if (!isset($this->requirements['com.woltlab.wcf']) && $this->packageInfo['name'] != 'com.woltlab.wcf') {
338 $this->requirements['com.woltlab.wcf'] = ['name' => 'com.woltlab.wcf'];
339 }
340
341 // during installations, `Package::$packageVersion` can be `null` which causes issues
342 // in `PackageArchive::filterUpdateInstructions()`; as update instructions are not needed
343 // for installations, not filtering update instructions is okay
344 if ($this->package !== null && $this->package->packageVersion !== null) {
345 $this->filterUpdateInstructions();
346 }
347
348 // set default values
349 if (!isset($this->packageInfo['isApplication'])) $this->packageInfo['isApplication'] = 0;
350 if (!isset($this->packageInfo['packageURL'])) $this->packageInfo['packageURL'] = '';
351 }
352
353 /**
354 * Filters update instructions.
355 */
356 protected function filterUpdateInstructions() {
357 $validFromVersion = null;
358 foreach ($this->instructions['update'] as $fromVersion => $update) {
359 if (Package::checkFromversion($this->package->packageVersion, $fromVersion)) {
360 $validFromVersion = $fromVersion;
361 break;
362 }
363 }
364
365 if ($validFromVersion === null) {
366 $this->instructions['update'] = [];
367 }
368 else {
369 $this->instructions['update'] = $this->instructions['update'][$validFromVersion];
370 }
371 }
372
373 /**
374 * Downloads the package archive.
375 *
376 * @return string path to the dowloaded file
377 */
378 public function downloadArchive() {
379 $prefix = 'package';
380
381 // file transfer via hypertext transfer protocol.
382 $this->archive = FileUtil::downloadFileFromHttp($this->archive, $prefix);
383
384 // unzip tar
385 $this->archive = self::unzipPackageArchive($this->archive);
386
387 return $this->archive;
388 }
389
390 /**
391 * Closes and deletes the tar archive of this package.
392 */
393 public function deleteArchive() {
394 if ($this->tar instanceof Tar) {
395 $this->tar->close();
396 }
397
398 @unlink($this->archive);
399 }
400
401 /**
402 * Returns true if the package archive supports a new installation.
403 *
404 * @return boolean
405 */
406 public function isValidInstall() {
407 return !empty($this->instructions['install']);
408 }
409
410 /**
411 * Checks if the new package is compatible with
412 * the package that is about to be updated.
413 *
414 * @param Package $package
415 * @return boolean isValidUpdate
416 */
417 public function isValidUpdate(Package $package = null) {
418 if ($this->package === null && $package !== null) {
419 $this->setPackage($package);
420
421 // re-evaluate update data
422 $this->filterUpdateInstructions();
423 }
424
425 // Check name of the installed package against the name of the update. Both must be identical.
426 if ($this->packageInfo['name'] != $this->package->package) {
427 return false;
428 }
429
430 // Check if the version number of the installed package is lower than the version number to which
431 // it's about to be updated.
432 if (Package::compareVersion($this->packageInfo['version'], $this->package->packageVersion) != 1) {
433 return false;
434 }
435
436 // Check if the package provides an instructions block for the update from the installed package version
437 if (empty($this->instructions['update'])) {
438 return false;
439 }
440
441 return true;
442 }
443
444 /**
445 * Checks if the current package is already installed, as it is not
446 * possible to install non-applications multiple times within the
447 * same environment.
448 *
449 * @return boolean
450 */
451 public function isAlreadyInstalled() {
452 $sql = "SELECT COUNT(*)
453 FROM wcf".WCF_N."_package
454 WHERE package = ?";
455 $statement = WCF::getDB()->prepareStatement($sql);
456 $statement->execute([$this->packageInfo['name']]);
457
458 return $statement->fetchSingleColumn() > 0;
459 }
460
461 /**
462 * Returns true if the package is an application and has an unique abbreviation.
463 *
464 * @return boolean
465 */
466 public function hasUniqueAbbreviation() {
467 if (!$this->packageInfo['isApplication']) {
468 return true;
469 }
470
471 $sql = "SELECT COUNT(*)
472 FROM wcf".WCF_N."_package
473 WHERE isApplication = ?
474 AND package LIKE ?";
475 $statement = WCF::getDB()->prepareStatement($sql);
476 $statement->execute([
477 1,
478 '%.'.Package::getAbbreviation($this->packageInfo['name'])
479 ]);
480
481 return $statement->fetchSingleColumn() > 0;
482 }
483
484 /**
485 * Returns information about the author of this package archive.
486 *
487 * @param string $name name of the requested information
488 * @return string
489 */
490 public function getAuthorInfo($name) {
491 if (isset($this->authorInfo[$name])) return $this->authorInfo[$name];
492 return null;
493 }
494
495 /**
496 * Returns information about this package.
497 *
498 * @param string $name name of the requested information
499 * @return mixed
500 */
501 public function getPackageInfo($name) {
502 if (isset($this->packageInfo[$name])) return $this->packageInfo[$name];
503 return null;
504 }
505
506 /**
507 * Returns a localized information about this package.
508 *
509 * @param string $name
510 * @return string
511 */
512 public function getLocalizedPackageInfo($name) {
513 if (isset($this->packageInfo[$name][WCF::getLanguage()->getFixedLanguageCode()])) {
514 return $this->packageInfo[$name][WCF::getLanguage()->getFixedLanguageCode()];
515 }
516 else if (isset($this->packageInfo[$name]['default'])) {
517 return $this->packageInfo[$name]['default'];
518 }
519
520 if (!empty($this->packageInfo[$name])) {
521 return reset($this->packageInfo[$name]);
522 }
523
524 return '';
525 }
526
527 /**
528 * Returns a list of all requirements of this package.
529 *
530 * @return array
531 */
532 public function getRequirements() {
533 return $this->requirements;
534 }
535
536 /**
537 * Returns a list of all delivered optional packages of this package.
538 *
539 * @return array
540 */
541 public function getOptionals() {
542 return $this->optionals;
543 }
544
545 /**
546 * Returns a list of excluded packages.
547 *
548 * @return array
549 */
550 public function getExcludedPackages() {
551 return $this->excludedPackages;
552 }
553
554 /**
555 * Returns the list of compatible API versions.
556 *
557 * @return integer[]
558 */
559 public function getCompatibleVersions() {
560 return $this->compatibility;
561 }
562
563 /**
564 * Returns the package installation instructions.
565 *
566 * @return array
567 */
568 public function getInstallInstructions() {
569 return $this->instructions['install'];
570 }
571
572 /**
573 * Returns the package update instructions.
574 *
575 * @return array
576 */
577 public function getUpdateInstructions() {
578 return $this->instructions['update'];
579 }
580
581 /**
582 * Checks which package requirements do already exist in right version.
583 * Returns a list with all existing requirements.
584 *
585 * @return array
586 */
587 public function getAllExistingRequirements() {
588 $existingRequirements = [];
589 $existingPackages = [];
590 if ($this->package !== null) {
591 $sql = "SELECT package.*
592 FROM wcf".WCF_N."_package_requirement requirement
593 LEFT JOIN wcf".WCF_N."_package package
594 ON (package.packageID = requirement.requirement)
595 WHERE requirement.packageID = ?";
596 $statement = WCF::getDB()->prepareStatement($sql);
597 $statement->execute([$this->package->packageID]);
598 while ($row = $statement->fetchArray()) {
599 $existingRequirements[$row['package']] = $row;
600 }
601 }
602
603 // build sql
604 $packageNames = [];
605 $requirements = $this->getRequirements();
606 foreach ($requirements as $requirement) {
607 if (isset($existingRequirements[$requirement['name']])) {
608 $existingPackages[$requirement['name']] = [];
609 $existingPackages[$requirement['name']][$existingRequirements[$requirement['name']]['packageID']] = $existingRequirements[$requirement['name']];
610 }
611 else {
612 $packageNames[] = $requirement['name'];
613 }
614 }
615
616 // check whether the required packages do already exist
617 if (!empty($packageNames)) {
618 $conditions = new PreparedStatementConditionBuilder();
619 $conditions->add("package.package IN (?)", [$packageNames]);
620
621 $sql = "SELECT package.*
622 FROM wcf".WCF_N."_package package
623 ".$conditions;
624 $statement = WCF::getDB()->prepareStatement($sql);
625 $statement->execute($conditions->getParameters());
626 while ($row = $statement->fetchArray()) {
627 // check required package version
628 if (isset($requirements[$row['package']]['minversion']) && Package::compareVersion($row['packageVersion'], $requirements[$row['package']]['minversion']) == -1) {
629 continue;
630 }
631
632 if (!isset($existingPackages[$row['package']])) {
633 $existingPackages[$row['package']] = [];
634 }
635
636 $existingPackages[$row['package']][$row['packageID']] = $row;
637 }
638 }
639
640 return $existingPackages;
641 }
642
643 /**
644 * Checks which package requirements do already exist in database.
645 * Returns a list with the existing requirements.
646 *
647 * @return array
648 */
649 public function getExistingRequirements() {
650 // build sql
651 $packageNames = [];
652 foreach ($this->requirements as $requirement) {
653 $packageNames[] = $requirement['name'];
654 }
655
656 // check whether the required packages do already exist
657 $existingPackages = [];
658 if (!empty($packageNames)) {
659 $conditions = new PreparedStatementConditionBuilder();
660 $conditions->add("package IN (?)", [$packageNames]);
661
662 $sql = "SELECT *
663 FROM wcf".WCF_N."_package
664 ".$conditions;
665 $statement = WCF::getDB()->prepareStatement($sql);
666 $statement->execute($conditions->getParameters());
667 while ($row = $statement->fetchArray()) {
668 if (!isset($existingPackages[$row['package']])) {
669 $existingPackages[$row['package']] = [];
670 }
671
672 $existingPackages[$row['package']][$row['packageVersion']] = $row;
673 }
674
675 // sort multiple packages by version number
676 foreach ($existingPackages as $packageName => $instances) {
677 uksort($instances, [Package::class, 'compareVersion']);
678
679 // get package with highest version number (get last package)
680 $existingPackages[$packageName] = array_pop($instances);
681 }
682 }
683
684 return $existingPackages;
685 }
686
687 /**
688 * Returns a list of all open requirements of this package.
689 *
690 * @return array
691 */
692 public function getOpenRequirements() {
693 // get all existing requirements
694 $existingPackages = $this->getExistingRequirements();
695
696 // check for open requirements
697 $openRequirements = [];
698 foreach ($this->requirements as $requirement) {
699 if (isset($existingPackages[$requirement['name']])) {
700 // package does already exist
701 // maybe an update is necessary
702 if (isset($requirement['minversion'])) {
703 if (Package::compareVersion($existingPackages[$requirement['name']]['packageVersion'], $requirement['minversion']) >= 0) {
704 // package does already exist in needed version
705 // skip installation of requirement
706 continue;
707 }
708 else {
709 $requirement['existingVersion'] = $existingPackages[$requirement['name']]['packageVersion'];
710 }
711 }
712 else {
713 continue;
714 }
715
716 $requirement['packageID'] = $existingPackages[$requirement['name']]['packageID'];
717 $requirement['action'] = 'update';
718 }
719 else {
720 // package does not exist
721 // new installation is necessary
722 $requirement['packageID'] = 0;
723 $requirement['action'] = 'install';
724 }
725
726 $openRequirements[$requirement['name']] = $requirement;
727 }
728
729 return $openRequirements;
730 }
731
732 /**
733 * Extracts the requested file in the package archive to the temp folder
734 * and returns the path to the extracted file.
735 *
736 * @param string $filename
737 * @param string $tempPrefix
738 * @return string
739 * @throws PackageValidationException
740 */
741 public function extractTar($filename, $tempPrefix = 'package_') {
742 // search the requested tar archive in our package archive.
743 // throw error message if not found.
744 if (($fileIndex = $this->tar->getIndexByFilename($filename)) === false) {
745 throw new PackageValidationException(PackageValidationException::FILE_NOT_FOUND, [
746 'archive' => $this->archive,
747 'targetArchive' => $filename
748 ]);
749 }
750
751 // requested tar archive was found
752 $fileInfo = $this->tar->getFileInfo($fileIndex);
753 $filename = FileUtil::getTemporaryFilename($tempPrefix, preg_replace('!^.*?(\.(?:tar\.gz|tgz|tar))$!i', '\\1', $fileInfo['filename']));
754 $this->tar->extract($fileIndex, $filename);
755
756 return $filename;
757 }
758
759 /**
760 * Unzips compressed package archives and returns the temporary file name.
761 *
762 * @param string $archive filename
763 * @return string
764 */
765 public static function unzipPackageArchive($archive) {
766 if (!FileUtil::isURL($archive)) {
767 $tar = new Tar($archive);
768 $tar->close();
769 if ($tar->isZipped()) {
770 $tmpName = FileUtil::getTemporaryFilename('package_');
771 if (FileUtil::uncompressFile($archive, $tmpName)) {
772 return $tmpName;
773 }
774 }
775 }
776
777 return $archive;
778 }
779
780 /**
781 * Returns a list of packages which exclude this package.
782 *
783 * @return Package[]
784 */
785 public function getConflictedExcludingPackages() {
786 $conflictedPackages = [];
787 $sql = "SELECT package.*, package_exclusion.*
788 FROM wcf".WCF_N."_package_exclusion package_exclusion
789 LEFT JOIN wcf".WCF_N."_package package
790 ON (package.packageID = package_exclusion.packageID)
791 WHERE excludedPackage = ?";
792 $statement = WCF::getDB()->prepareStatement($sql);
793 $statement->execute([$this->packageInfo['name']]);
794 while ($row = $statement->fetchArray()) {
795 if (!empty($row['excludedPackageVersion'])) {
796 if (Package::compareVersion($this->packageInfo['version'], $row['excludedPackageVersion'], '<')) {
797 continue;
798 }
799 }
800
801 $conflictedPackages[$row['packageID']] = new Package(null, $row);
802 }
803
804 return $conflictedPackages;
805 }
806
807 /**
808 * Returns a list of packages which are excluded by this package.
809 *
810 * @return Package[]
811 */
812 public function getConflictedExcludedPackages() {
813 $conflictedPackages = [];
814 if (!empty($this->excludedPackages)) {
815 $excludedPackages = [];
816 foreach ($this->excludedPackages as $excludedPackageData) {
817 $excludedPackages[$excludedPackageData['name']] = $excludedPackageData['version'];
818 }
819
820 $conditions = new PreparedStatementConditionBuilder();
821 $conditions->add("package IN (?)", [array_keys($excludedPackages)]);
822
823 $sql = "SELECT *
824 FROM wcf".WCF_N."_package
825 ".$conditions;
826 $statement = WCF::getDB()->prepareStatement($sql);
827 $statement->execute($conditions->getParameters());
828 while ($row = $statement->fetchArray()) {
829 if (!empty($excludedPackages[$row['package']])) {
830 if (Package::compareVersion($row['packageVersion'], $excludedPackages[$row['package']], '<')) {
831 continue;
832 }
833 $row['excludedPackageVersion'] = $excludedPackages[$row['package']];
834 }
835
836 $conflictedPackages[$row['packageID']] = new Package(null, $row);
837 }
838 }
839
840 return $conflictedPackages;
841 }
842
843 /**
844 * Returns a list of instructions for installation or update.
845 *
846 * @param string $type
847 * @return array
848 */
849 public function getInstructions($type) {
850 if (isset($this->instructions[$type])) {
851 return $this->instructions[$type];
852 }
853
854 return null;
855 }
856
857 /**
858 * Returns a list of php requirements for current package.
859 *
860 * @return mixed[][]
861 * @deprecated 3.0
862 */
863 public function getPhpRequirements() {
864 return [];
865 }
866 }