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