2 namespace wcf\system\package\validation
;
3 use wcf\data\package\Package
;
4 use wcf\data\package\PackageCache
;
5 use wcf\system\package\PackageArchive
;
8 * Recursively validates the package archive and it's delivered requirements.
10 * @author Alexander Ebert
11 * @copyright 2001-2014 WoltLab GmbH
12 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
13 * @package com.woltlab.wcf
14 * @subpackage system.package.validation
15 * @category Community Framework
17 class PackageValidationArchive
implements \RecursiveIterator
{
19 * package archive object
20 * @var \wcf\system\package\PackageArchive
22 protected $archive = null;
25 * list of direct requirements delivered by this package
26 * @var array<\wcf\system\package\validation\PackageValidationArchive>
28 protected $children = array();
37 * exception occured during validation
40 protected $exception = null;
43 * associated package object
44 * @var \wcf\data\package\Package
46 protected $package = null;
49 * parent package validation archive object
50 * @var \wcf\system\package\validation\PackageValidationArchive
52 protected $parent = null;
58 private $position = 0;
61 * Creates a new package validation archive instance.
63 * @param string $archive
64 * @param \wcf\system\package\validation\PackageValidationArchive $parent
65 * @param integer $depth
67 public function __construct($archive, PackageValidationArchive
$parent = null, $depth = 0) {
68 $this->archive
= new PackageArchive($archive);
69 $this->parent
= $parent;
70 $this->depth
= $depth;
74 * Validates this package and optionally it's delivered requirements. Unless you turn on
75 * $deepInspection, this will only check if the archive is theoretically usable to install
76 * or update. This means that neither exclusions nor dependencies will be checked.
78 * @param boolean $deepInspection
81 public function validate($deepInspection, $requiredVersion = '') {
83 // try to read archive
84 $this->archive
->openArchive();
86 // check if package is installable or suitable for an update
87 $this->validateInstructions($requiredVersion, $deepInspection);
89 catch (\Exception
$e) {
90 $this->exception
= $e;
95 if ($deepInspection) {
97 PackageValidationManager
::getInstance()->addVirtualPackage($this->archive
->getPackageInfo('name'), $this->archive
->getPackageInfo('version'));
99 // check for exclusions
100 // TODO: exclusions are not checked for testing purposes
101 // REMOVE THIS BEFORE *ANY* PUBLIC RELEASE
102 if (WCF_VERSION
!= '2.1.0 Alpha 1 (Typhoon)') {
103 $this->validateExclusion();
106 // traverse open requirements
107 foreach ($this->archive
->getOpenRequirements() as $requirement) {
108 $virtualPackageVersion = PackageValidationManager
::getInstance()->getVirtualPackage($requirement['name']);
109 if ($virtualPackageVersion === null || Package
::compareVersion($virtualPackageVersion, $requirement['minversion'], '<')) {
110 if (empty($requirement['file'])) {
111 throw new PackageValidationException(PackageValidationException
::MISSING_REQUIREMENT
, array(
112 'packageName' => $requirement['name'],
113 'packageVersion' => $requirement['minversion']
117 $archive = $this->archive
->extractTar($requirement['file']);
119 $index = count($this->children
);
120 $this->children
[$index] = new PackageValidationArchive($archive, $this, $this->depth +
1);
121 if (!$this->children
[$index]->validate(true, $requirement['minversion'])) {
125 PackageValidationManager
::getInstance()->addVirtualPackage(
126 $this->children
[$index]->getArchive()->getPackageInfo('name'),
127 $this->children
[$index]->getArchive()->getPackageInfo('version')
132 catch (PackageValidationException
$e) {
133 $this->exception
= $e;
144 * Validates if the package has suitable install or update instructions. Setting $deepInspection
145 * to true will cause every single instruction to be validated against the corresponding PIP.
147 * Please be aware that unknown PIPs will be silently ignored and will not cause any error!
149 * @param string $requiredVersion
150 * @param boolean $deepInspection
152 protected function validateInstructions($requiredVersion, $deepInspection) {
153 $package = $this->getPackage();
155 // delivered package does not provide the minimum required version
156 if (Package
::compareVersion($requiredVersion, $this->archive
->getPackageInfo('version'), '>')) {
157 throw new PackageValidationException(PackageValidationException
::INSUFFICIENT_VERSION
, array(
158 'packageName' => $package->packageName
,
159 'packageVersion' => $package->packageVersion
,
160 'deliveredPackageVersion' => $this->archive
->getPackageInfo('version')
164 // package is not installed yet
165 if ($package === null) {
166 $instructions = $this->archive
->getInstallInstructions();
167 if (empty($instructions)) {
168 throw new PackageValidationException(PackageValidationException
::NO_INSTALL_PATH
, array('packageName' => $this->archive
->getPackageInfo('name')));
171 if ($deepInspection) {
172 $this->validatePackageInstallationPlugins('install', $instructions);
176 // package is already installed, check update path
177 if (!$this->archive
->isValidUpdate($package)) {
178 throw new PackageValidationException(PackageValidationException
::NO_UPDATE_PATH
, array(
179 'packageName' => $package->packageName
,
180 'packageVersion' => $package->packageVersion
,
181 'deliveredPackageVersion' => $this->archive
->getPackageInfo('version')
185 if ($deepInspection) {
186 $this->validatePackageInstallationPlugins('update', $this->archive
->getUpdateInstructions());
192 * Validates install or update instructions against the corresponding PIP, unknown PIPs will be silently ignored.
194 * @param string $type
195 * @param array<array> $instructions
197 protected function validatePackageInstallationPlugins($type, array $instructions) {
198 for ($i = 0, $length = count($instructions); $i < $length; $i++
) {
199 $instruction = $instructions[$i];
200 if (!PackageValidationManager
::getInstance()->validatePackageInstallationPluginInstruction($this->archive
, $instruction['pip'], $instruction['value'])) {
201 throw new PackageValidationException(PackageValidationException
::MISSING_INSTRUCTION_FILE
, array(
202 'pip' => $instruction['pip'],
204 'value' => $instruction['value']
211 * Validates if an installed package excludes the current package and vice versa.
213 protected function validateExclusion() {
214 $excludingPackages = $this->archive
->getConflictedExcludingPackages();
215 if (!empty($excludingPackages)) {
216 throw new PackageValidationException(PackageValidationException
::EXCLUDING_PACKAGES
, array('packages' => $excludingPackages));
219 $excludedPackages = $this->archive
->getConflictedExcludedPackages();
220 if (!empty($excludedPackages)) {
221 throw new PackageValidationException(PackageValidationException
::EXCLUDED_PACKAGES
, array('packages' => $excludedPackages));
226 * Returns the exception message.
230 public function getExceptionMessage() {
231 if ($this->exception
=== null) {
235 if ($this->exception
instanceof PackageValidationException
) {
236 return $this->exception
->getErrorMessage();
239 return $this->exception
->getMessage();
243 * Returns the package archive object.
245 * @return \wcf\system\package\PackageArchive
247 public function getArchive() {
248 return $this->archive
;
252 * Returns the package object based on the package archive's package identifier or null
253 * if the package isn't already installed.
255 * @return \wcf\data\package\Package
257 public function getPackage() {
258 if ($this->package
=== null) {
259 $this->package
= PackageCache
::getInstance()->getPackageByIdentifier($this->archive
->getPackageInfo('name'));
262 return $this->package
;
266 * Returns nesting depth.
270 public function getDepth() {
275 * Sets the children of this package validation archive.
277 * @param array<\wcf\system\package\validation\PackageValidationArchive> $children
279 public function setChildren(array $children) {
280 $this->children
= $children;
284 * @see \Iterator::rewind()
286 public function rewind() {
291 * @see \Iterator::valid()
293 public function valid() {
294 return isset($this->children
[$this->position
]);
298 * @see \Iterator::next()
300 public function next() {
305 * @see \Iterator::current()
307 public function current() {
308 return $this->children
[$this->position
];
312 * @see \Iterator::key()
314 public function key() {
315 return $this->position
;
319 * @see \RecursiveIterator::getChildren()
321 public function getChildren() {
322 return $this->children
[$this->position
];
326 * @see \RecursiveIterator::hasChildren()
328 public function hasChildren() {
329 return count($this->children
) > 0;