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