From: Alexander Ebert Date: Mon, 14 Apr 2014 13:45:59 +0000 (+0200) Subject: Updated package validation X-Git-Tag: 2.1.0_Alpha_1~901 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=870b0938ec20bb875c2aa2211f299a0c97f963b1;p=GitHub%2FWoltLab%2FWCF.git Updated package validation --- diff --git a/wcfsetup/install/files/lib/acp/form/PackageStartInstallForm.class.php b/wcfsetup/install/files/lib/acp/form/PackageStartInstallForm.class.php index a3da2c78ac..5e711b7525 100755 --- a/wcfsetup/install/files/lib/acp/form/PackageStartInstallForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/PackageStartInstallForm.class.php @@ -137,14 +137,12 @@ class PackageStartInstallForm extends AbstractForm { throw new UserInputException('uploadPackage', 'uploadFailed'); } - if (PackageValidationManager::getInstance()->validate($this->uploadPackage['name'], false)) { - die("win"); + if (!PackageValidationManager::getInstance()->validate($this->uploadPackage['name'], false)) { + // TODO: do something + die("validation failed"); } - else { - die("failed"); - } - $this->archive = new PackageArchive($this->uploadPackage['name'], $this->package); - $this->validateArchive('uploadPackage'); + + $this->package = PackageValidationManager::getInstance()->getPackageValidationArchive()->getPackage(); } /** @@ -255,12 +253,12 @@ class PackageStartInstallForm extends AbstractForm { } // insert queue - $isApplication = $this->archive->getPackageInfo('isApplication'); + $isApplication = PackageValidationManager::getInstance()->getPackageValidationArchive()->getArchive()->getPackageInfo('isApplication'); $this->queue = PackageInstallationQueueEditor::create(array( 'processNo' => $processNo, 'userID' => WCF::getUser()->userID, - 'package' => $this->archive->getPackageInfo('name'), - 'packageName' => $this->archive->getLocalizedPackageInfo('packageName'), + 'package' => PackageValidationManager::getInstance()->getPackageValidationArchive()->getArchive()->getPackageInfo('name'), + 'packageName' => PackageValidationManager::getInstance()->getPackageValidationArchive()->getArchive()->getLocalizedPackageInfo('packageName'), 'packageID' => $packageID, 'archive' => $archive, 'action' => ($this->package != null ? 'update' : 'install'), diff --git a/wcfsetup/install/files/lib/acp/page/PackageInstallationConfirmPage.class.php b/wcfsetup/install/files/lib/acp/page/PackageInstallationConfirmPage.class.php index 3864f72b62..0fd64fa803 100644 --- a/wcfsetup/install/files/lib/acp/page/PackageInstallationConfirmPage.class.php +++ b/wcfsetup/install/files/lib/acp/page/PackageInstallationConfirmPage.class.php @@ -9,6 +9,7 @@ use wcf\system\package\PackageArchive; use wcf\system\package\PackageInstallationDispatcher; use wcf\system\WCF; use wcf\system\WCFACP; +use wcf\system\package\validation\PackageValidationManager; /** * Shows a confirmation page prior to start installing. @@ -102,6 +103,19 @@ class PackageInstallationConfirmPage extends AbstractPage { $this->packageInstallationDispatcher = new PackageInstallationDispatcher($this->queue); + // validate the package and all it's requirements + if (PackageValidationManager::getInstance()->validate($this->queue->archive, true)) { + die("success"); + } + else { + /*echo "
";
+			foreach (PackageValidationManager::getInstance()->getPackageValidationArchiveList() as $archive) {
+				echo '[' . $archive->getArchive()->getPackageInfo('name') . '] ' . $archive->getExceptionMessage() . "\n";
+			}
+			die("failed");*/
+			return;
+		}
+		
 		// get requirements
 		$this->requirements = $this->packageInstallationDispatcher->getArchive()->getRequirements();
 		$this->openRequirements = $this->packageInstallationDispatcher->getArchive()->getOpenRequirements();
@@ -157,10 +171,11 @@ class PackageInstallationConfirmPage extends AbstractPage {
 		
 		WCF::getTPL()->assign(array(
 			'archive' => $this->packageInstallationDispatcher->getArchive(),
-			'requiredPackages' => $this->requirements,
+			/*'requiredPackages' => $this->requirements,
 			'missingPackages' => $this->missingPackages,
 			'excludingPackages' => $this->packageInstallationDispatcher->getArchive()->getConflictedExcludingPackages(),
-			'excludedPackages' => $this->packageInstallationDispatcher->getArchive()->getConflictedExcludedPackages(),
+			'excludedPackages' => $this->packageInstallationDispatcher->getArchive()->getConflictedExcludedPackages(),*/
+			'packageValidationArchives' => PackageValidationManager::getInstance()->getPackageValidationArchiveList(),
 			'queue' => $this->queue,
 			'installingImportedStyle' => $this->installingImportedStyle
 		));
diff --git a/wcfsetup/install/files/lib/system/package/plugin/ACPTemplatePackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/package/plugin/ACPTemplatePackageInstallationPlugin.class.php
index f1d76d46a7..0f7afb9eb8 100644
--- a/wcfsetup/install/files/lib/system/package/plugin/ACPTemplatePackageInstallationPlugin.class.php
+++ b/wcfsetup/install/files/lib/system/package/plugin/ACPTemplatePackageInstallationPlugin.class.php
@@ -3,6 +3,7 @@ namespace wcf\system\package\plugin;
 use wcf\data\application\Application;
 use wcf\data\package\Package;
 use wcf\system\package\ACPTemplatesFileHandler;
+use wcf\system\package\PackageArchive;
 use wcf\system\WCF;
 
 /**
@@ -78,4 +79,25 @@ class ACPTemplatePackageInstallationPlugin extends AbstractPackageInstallationPl
 			parent::uninstall();
 		}
 	}
+	
+	/**
+	 * @see	\wcf\system\package\plugin\IPackageInstallationPlugin::isValid()
+	 */
+	public static function isValid(PackageArchive $archive, $instruction) {
+		if (preg_match('~\.(tar(\.gz)?|tgz)$~', $instruction)) {
+			// check if file actually exists
+			try {
+				if ($archive->getTar()->getIndexByFilename($instruction) === false) {
+					return false;
+				}
+			}
+			catch (\SystemException $e) {
+				return false;
+			}
+			
+			return true;
+		}
+		
+		return false;
+	}
 }
diff --git a/wcfsetup/install/files/lib/system/package/plugin/AbstractPackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/package/plugin/AbstractPackageInstallationPlugin.class.php
index e4f0b42067..0ab137abd3 100644
--- a/wcfsetup/install/files/lib/system/package/plugin/AbstractPackageInstallationPlugin.class.php
+++ b/wcfsetup/install/files/lib/system/package/plugin/AbstractPackageInstallationPlugin.class.php
@@ -1,6 +1,7 @@
 prepareStatement($sql);
 		$statement->execute(array($this->installation->getPackageID()));
 	}
+	
+	/**
+	 * @see	\wcf\system\package\plugin\IPackageInstallationPlugin::isValid()
+	 */
+	public static function isValid(PackageArchive $archive, $instruction) {
+		return true;
+	}
 }
diff --git a/wcfsetup/install/files/lib/system/package/plugin/AbstractXMLPackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/package/plugin/AbstractXMLPackageInstallationPlugin.class.php
index ab599a37ee..853344d3ef 100644
--- a/wcfsetup/install/files/lib/system/package/plugin/AbstractXMLPackageInstallationPlugin.class.php
+++ b/wcfsetup/install/files/lib/system/package/plugin/AbstractXMLPackageInstallationPlugin.class.php
@@ -2,6 +2,7 @@
 namespace wcf\system\package\plugin;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\exception\SystemException;
+use wcf\system\package\PackageArchive;
 use wcf\system\package\PackageInstallationDispatcher;
 use wcf\system\WCF;
 use wcf\util\FileUtil;
@@ -344,4 +345,25 @@ abstract class AbstractXMLPackageInstallationPlugin extends AbstractPackageInsta
 			return $showOrder;
 		}
 	}
+	
+	/**
+	 * @see	\wcf\system\package\plugin\IPackageInstallationPlugin::isValid()
+	 */
+	public static function isValid(PackageArchive $archive, $instruction) {
+		if (preg_match('~\.xml$~', $instruction)) {
+			// check if file actually exists
+			try {
+				if ($archive->getTar()->getIndexByFilename($instruction) === false) {
+					return false;
+				}
+			}
+			catch (\SystemException $e) {
+				return false;
+			}
+			
+			return true;
+		}
+		
+		return false;
+	}
 }
diff --git a/wcfsetup/install/files/lib/system/package/plugin/FilePackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/package/plugin/FilePackageInstallationPlugin.class.php
index 75fe6fbd04..02faf929e2 100644
--- a/wcfsetup/install/files/lib/system/package/plugin/FilePackageInstallationPlugin.class.php
+++ b/wcfsetup/install/files/lib/system/package/plugin/FilePackageInstallationPlugin.class.php
@@ -3,6 +3,7 @@ namespace wcf\system\package\plugin;
 use wcf\data\application\Application;
 use wcf\data\package\Package;
 use wcf\system\package\FilesFileHandler;
+use wcf\system\package\PackageArchive;
 use wcf\system\package\PackageInstallationDispatcher;
 use wcf\system\WCF;
 use wcf\util\StyleUtil;
@@ -106,4 +107,25 @@ class FilePackageInstallationPlugin extends AbstractPackageInstallationPlugin {
 			parent::uninstall();
 		}
 	}
+	
+	/**
+	 * @see	\wcf\system\package\plugin\IPackageInstallationPlugin::isValid()
+	 */
+	public static function isValid(PackageArchive $archive, $instruction) {
+		if (preg_match('~\.(tar(\.gz)?|tgz)$~', $instruction)) {
+			// check if file actually exists
+			try {
+				if ($archive->getTar()->getIndexByFilename($instruction) === false) {
+					return false;
+				}
+			}
+			catch (\SystemException $e) {
+				return false;
+			}
+			
+			return true;
+		}
+		
+		return false;
+	}
 }
diff --git a/wcfsetup/install/files/lib/system/package/plugin/IPackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/package/plugin/IPackageInstallationPlugin.class.php
index d2fb5e5e89..616324c2c1 100644
--- a/wcfsetup/install/files/lib/system/package/plugin/IPackageInstallationPlugin.class.php
+++ b/wcfsetup/install/files/lib/system/package/plugin/IPackageInstallationPlugin.class.php
@@ -1,5 +1,6 @@
 installation->getArchive()->getTar()->extractToString($fileindex);
 	}
+	
+	/**
+	 * @see	\wcf\system\package\plugin\IPackageInstallationPlugin::isValid()
+	 */
+	public static function isValid(PackageArchive $archive, $instruction) {
+		if (preg_match('~\.sql$~', $instruction)) {
+			// check if file actually exists
+			try {
+				if ($archive->getTar()->getIndexByFilename($instruction) === false) {
+					return false;
+				}
+			}
+			catch (\SystemException $e) {
+				return false;
+			}
+			
+			return true;
+		}
+		
+		return false;
+	}
 }
diff --git a/wcfsetup/install/files/lib/system/package/plugin/TemplatePackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/package/plugin/TemplatePackageInstallationPlugin.class.php
index f221b11eba..b8a7b856e4 100644
--- a/wcfsetup/install/files/lib/system/package/plugin/TemplatePackageInstallationPlugin.class.php
+++ b/wcfsetup/install/files/lib/system/package/plugin/TemplatePackageInstallationPlugin.class.php
@@ -2,6 +2,7 @@
 namespace wcf\system\package\plugin;
 use wcf\data\application\Application;
 use wcf\data\package\Package;
+use wcf\system\package\PackageArchive;
 use wcf\system\package\TemplatesFileHandler;
 use wcf\system\WCF;
 
@@ -80,4 +81,25 @@ class TemplatePackageInstallationPlugin extends AbstractPackageInstallationPlugi
 			parent::uninstall();
 		}
 	}
+	
+	/**
+	 * @see	\wcf\system\package\plugin\IPackageInstallationPlugin::isValid()
+	 */
+	public static function isValid(PackageArchive $archive, $instruction) {
+		if (preg_match('~\.(tar(\.gz)?|tgz)$~', $instruction)) {
+			// check if file actually exists
+			try {
+				if ($archive->getTar()->getIndexByFilename($instruction) === false) {
+					return false;
+				}
+			}
+			catch (\SystemException $e) {
+				return false;
+			}
+			
+			return true;
+		}
+		
+		return false;
+	}
 }
diff --git a/wcfsetup/install/files/lib/system/package/validation/PackageValidationArchive.class.php b/wcfsetup/install/files/lib/system/package/validation/PackageValidationArchive.class.php
index b9825fec3c..40587c3d10 100644
--- a/wcfsetup/install/files/lib/system/package/validation/PackageValidationArchive.class.php
+++ b/wcfsetup/install/files/lib/system/package/validation/PackageValidationArchive.class.php
@@ -27,12 +27,30 @@ class PackageValidationArchive implements \RecursiveIterator {
 	 */
 	protected $children = array();
 	
+	/**
+	 * nesting depth
+	 * @var	integer
+	 */
+	protected $depth = 0;
+	
 	/**
 	 * exception occured during validation
 	 * @var	\Exception
 	 */
 	protected $exception = null;
 	
+	/**
+	 * associated package object
+	 * @var	\wcf\data\package\Package
+	 */
+	protected $package = null;
+	
+	/**
+	 * parent package validation archive object
+	 * @var	\wcf\system\package\validation\PackageValidationArchive
+	 */
+	protected $parent = null;
+	
 	/**
 	 * children pointer
 	 * @var	integer
@@ -42,10 +60,14 @@ class PackageValidationArchive implements \RecursiveIterator {
 	/**
 	 * Creates a new package validation archive instance.
 	 * 
-	 * @param	string		$archive
+	 * @param	string								$archive
+	 * @param	\wcf\system\package\validation\PackageValidationArchive		$parent
+	 * @param	integer								$depth
 	 */
-	public function __construct($archive) {
+	public function __construct($archive, PackageValidationArchive $parent = null, $depth = 0) {
 		$this->archive = new PackageArchive($archive);
+		$this->parent = $parent;
+		$this->depth = $depth;
 	}
 	
 	/**
@@ -62,7 +84,7 @@ class PackageValidationArchive implements \RecursiveIterator {
 			$this->archive->openArchive();
 			
 			// check if package is installable or suitable for an update
-			$this->validateInstructions($requiredVersion);
+			$this->validateInstructions($requiredVersion, $deepInspection);
 		}
 		catch (\Exception $e) {
 			$this->exception = $e;
@@ -81,22 +103,28 @@ class PackageValidationArchive implements \RecursiveIterator {
 				
 				// traverse open requirements
 				foreach ($this->archive->getOpenRequirements() as $requirement) {
-					if (empty($requirement['file'])) {
-						throw new PackageValidationException(PackageValidationException::MISSING_REQUIREMENT, array(
-							'packageName' => $requirement['name'],
-							'packageVersion' => $requirement['minversion']
-						));
-					}
-					
-					$archive = $this->archive->extractTar($requirement->file);
-					
-					$index = count($this->children);
-					$this->children[$index] = new PackageValidationArchive($archive);
-					if (!$this->children[$index]->validate(true, $requirement['minversion'])) {
-						return false;
+					$virtualPackageVersion = PackageValidationManager::getInstance()->getVirtualPackage($requirement['name']);
+					if ($virtualPackageVersion === null || Package::compareVersion($virtualPackageVersion, $requirement['minversion'], '<')) {
+						if (empty($requirement['file'])) {
+							throw new PackageValidationException(PackageValidationException::MISSING_REQUIREMENT, array(
+								'packageName' => $requirement['name'],
+								'packageVersion' => $requirement['minversion']
+							));
+						}
+						
+						$archive = $this->archive->extractTar($requirement['file']);
+						
+						$index = count($this->children);
+						$this->children[$index] = new PackageValidationArchive($archive, $this, $this->depth + 1);
+						if (!$this->children[$index]->validate(true, $requirement['minversion'])) {
+							return false;
+						}
+						
+						PackageValidationManager::getInstance()->addVirtualPackage(
+							$this->children[$index]->getArchive()->getPackageInfo('name'),
+							$this->children[$index]->getArchive()->getPackageInfo('version')
+						);
 					}
-					
-					PackageValidationManager::getInstance()->addVirtualPackage($this->archive->getPackageInfo('name'), $this->archive->getPackageInfo('version'));
 				}
 			}
 			catch (PackageValidationException $e) {
@@ -110,8 +138,8 @@ class PackageValidationArchive implements \RecursiveIterator {
 		
 	}
 	
-	protected function validateInstructions($requiredVersion) {
-		$package = PackageCache::getInstance()->getPackageByIdentifier($this->archive->getPackageInfo('name'));
+	protected function validateInstructions($requiredVersion, $deepInspection) {
+		$package = $this->getPackage();
 		
 		// delivered package does not provide the minimum required version
 		if (Package::compareVersion($requiredVersion, $this->archive->getPackageInfo('version'), '>')) {
@@ -124,9 +152,12 @@ class PackageValidationArchive implements \RecursiveIterator {
 		
 		// package is not installed yet
 		if ($package === null) {
-			if (empty($this->archive->getInstallInstructions())) {
+			$instructions = $this->archive->getInstallInstructions();
+			if (empty($instructions)) {
 				throw new PackageValidationException(PackageValidationException::NO_INSTALL_PATH, array('packageName' => $this->archive->getPackageInfo('name')));
 			}
+			
+			$this->validatePackageInstallationPlugins('install', $instructions);
 		}
 		else {
 			// package is already installed, check update path
@@ -137,6 +168,22 @@ class PackageValidationArchive implements \RecursiveIterator {
 					'deliveredPackageVersion' => $this->archive->getPackageInfo('version')
 				));
 			}
+			
+			$this->validatePackageInstallationPlugins('update', $this->archive->getUpdateInstructions());
+		}
+		exit;
+	}
+	
+	protected function validatePackageInstallationPlugins($type, array $instructions) {
+		for ($i = 0, $length = count($instructions); $i < $length; $i++) {
+			$instruction = $instructions[$i];
+			if (!PackageValidationManager::getInstance()->validatePackageInstallationPluginInstruction($this->archive, $instruction['pip'], $instruction['value'])) {
+				throw new PackageValidationException(PackageValidationException::MISSING_INSTRUCTION_FILE, array(
+					'pip' => $instruction['pip'],
+					'type' => $type,
+					'value' => $instruction['value']
+				));
+			}
 		}
 	}
 	
@@ -147,7 +194,7 @@ class PackageValidationArchive implements \RecursiveIterator {
 		}
 		
 		$excludedPackages = $this->archive->getConflictedExcludedPackages();
-		if (!empty($excludingPackages)) {
+		if (!empty($excludedPackages)) {
 			throw new PackageValidationException(PackageValidationException::EXCLUDED_PACKAGES, array('packages' => $excludedPackages));
 		}
 	}
@@ -169,6 +216,36 @@ class PackageValidationArchive implements \RecursiveIterator {
 		return $this->exception->getMessage();
 	}
 	
+	public function getArchive() {
+		return $this->archive;
+	}
+	
+	public function getPackage() {
+		if ($this->package === null) {
+			$this->package = PackageCache::getInstance()->getPackageByIdentifier($this->archive->getPackageInfo('name'));
+		}
+		
+		return $this->package;
+	}
+	
+	/**
+	 * Returns nesting depth.
+	 * 
+	 * @return	integer
+	 */
+	public function getDepth() {
+		return $this->depth;
+	}
+	
+	/**
+	 * Sets the children of this package validation archive.
+	 * 
+	 * @param	array<\wcf\system\package\validation\PackageValidationArchive>		$children
+	 */
+	public function setChildren(array $children) {
+		$this->children = $children;
+	}
+	
 	/**
 	 * @see	\Iterator::rewind()
 	 */
diff --git a/wcfsetup/install/files/lib/system/package/validation/PackageValidationException.class.php b/wcfsetup/install/files/lib/system/package/validation/PackageValidationException.class.php
index 7484cc8daf..44c97a46d4 100644
--- a/wcfsetup/install/files/lib/system/package/validation/PackageValidationException.class.php
+++ b/wcfsetup/install/files/lib/system/package/validation/PackageValidationException.class.php
@@ -76,6 +76,18 @@ class PackageValidationException extends SystemException {
 	 */
 	const INSUFFICIENT_VERSION = 9;
 	
+	/**
+	 * requirement is set but neither installed nor provided, expects the details 'packageName' and 'packageVersion'
+	 * @var	integer
+	 */
+	const MISSING_REQUIREMENT = 10;
+	
+	/**
+	 * file reference for a package installation plugin is missing, expects the details 'pip', 'type' and 'value'
+	 * @var	integer
+	 */
+	const MISSING_INSTRUCTION_FILE = 11;
+	
 	/**
 	 * Creates a new PackageArchiveValidationException.
 	 * 
@@ -103,7 +115,7 @@ class PackageValidationException extends SystemException {
 	 * @return	string
 	 */
 	public function getErrorMessage() {
-		return WCF::getLanguage()->getDynamicVariable('wcf.package.validation.errorCode.' . $this->getCode(), $this->getDetails());
+		return WCF::getLanguage()->getDynamicVariable('wcf.acp.package.validation.errorCode.' . $this->getCode(), $this->getDetails());
 	}
 	
 	/**
diff --git a/wcfsetup/install/files/lib/system/package/validation/PackageValidationManager.class.php b/wcfsetup/install/files/lib/system/package/validation/PackageValidationManager.class.php
index 4e679eaed4..c599971b11 100644
--- a/wcfsetup/install/files/lib/system/package/validation/PackageValidationManager.class.php
+++ b/wcfsetup/install/files/lib/system/package/validation/PackageValidationManager.class.php
@@ -2,6 +2,8 @@
 namespace wcf\system\package\validation;
 use wcf\data\package\Package;
 use wcf\system\SingletonFactory;
+use wcf\data\package\installation\plugin\PackageInstallationPluginList;
+use wcf\system\package\PackageArchive;
 
 /**
  * Manages recursive validation of package archives.
@@ -14,6 +16,12 @@ use wcf\system\SingletonFactory;
  * @category	Community Framework
  */
 class PackageValidationManager extends SingletonFactory {
+	/**
+	 * list of known package installation plugins
+	 * @var	array
+	 */
+	protected $packageInstallationPlugins = array();
+	
 	/**
 	 * package validation archive object
 	 * @var	\wcf\system\package\validation\PackageValidationArchive
@@ -26,6 +34,17 @@ class PackageValidationManager extends SingletonFactory {
 	 */
 	protected $virtualPackageList = array();
 	
+	/**
+	 * @see	\wcf\system\SingletonFactory::init()
+	 */
+	protected function init() {
+		$pipList = new PackageInstallationPluginList();
+		$pipList->readObjects();
+		foreach ($pipList as $pip) {
+			$this->packageInstallationPlugins[$pip->pluginName] = $pip->className;
+		}
+	}
+	
 	/**
 	 * Validates given archive for existance and ability to be installed/updated. If you set the
 	 * second parameter $deepInspection to "false", the system will only check if the archive
@@ -40,7 +59,7 @@ class PackageValidationManager extends SingletonFactory {
 		$this->virtualPackageList = array();
 		$this->packageValidationArchive = new PackageValidationArchive($archive);
 		
-		return $this->packageValidationArchive->validate();
+		return $this->packageValidationArchive->validate($deepInspection);
 	}
 	
 	/**
@@ -78,11 +97,31 @@ class PackageValidationManager extends SingletonFactory {
 	 * @param	string		$package
 	 * @return	string
 	 */
-	public function geVirtualPackageVersion($package) {
+	public function getVirtualPackage($package) {
 		if (isset($this->virtualPackageList[$package])) {
 			return $this->virtualPackageList[$package];
 		}
 		
 		return null;
 	}
+	
+	/**
+	 * Returns the iteratable package archive list.
+	 * 
+	 * @return	\RecursiveIteratorIterator
+	 */
+	public function getPackageValidationArchiveList() {
+		$packageValidationArchive = new PackageValidationArchive('');
+		$packageValidationArchive->setChildren(array($this->packageValidationArchive));
+		
+		return new \RecursiveIteratorIterator($packageValidationArchive, \RecursiveIteratorIterator::SELF_FIRST);
+	}
+	
+	public function validatePackageInstallationPluginInstruction(PackageArchive $archive, $pip, $instruction) {
+		if (isset($this->packageInstallationPlugins[$pip])) {
+			return call_user_func(array($this->packageInstallationPlugins[$pip], 'isValid'), $archive, $instruction);
+		}
+		echo "(default success)\n";
+		return true;
+	}
 }
diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml
index 3c429a1143..faff0f4261 100644
--- a/wcfsetup/install/lang/de.xml
+++ b/wcfsetup/install/lang/de.xml
@@ -991,6 +991,11 @@ GmbH=Gesellschaft mit beschränkter Haftung]]>
 		
 		
 		
+		
+		
+		{foreach from=$packages item=package}
  • „{$package}“ ({$package->package})
  • {/foreach}]]>
    + +
  • Die Datei wurde dem Archiv nicht hinzugefügt
  • Die Datei existiert, jedoch sind der Dateiname und die Angabe in den Anweisungen abweichend (Tippfehler)
  • ]]>