* @package WoltLabSuite\Core\System\Package */ class PackageInstallationDispatcher { /** * current installation type * @var string */ protected $action = ''; /** * instance of PackageArchive * @var PackageArchive */ public $archive; /** * instance of PackageInstallationNodeBuilder * @var PackageInstallationNodeBuilder */ public $nodeBuilder; /** * instance of Package * @var Package */ public $package; /** * instance of PackageInstallationQueue * @var PackageInstallationQueue */ public $queue; /** * default name of the config file * @var string */ const CONFIG_FILE = 'app.config.inc.php'; /** * data of previous package in queue * @var string[] */ protected $previousPackageData; /** * Creates a new instance of PackageInstallationDispatcher. * * @param PackageInstallationQueue $queue */ public function __construct(PackageInstallationQueue $queue) { $this->queue = $queue; $this->nodeBuilder = new PackageInstallationNodeBuilder($this); $this->action = $this->queue->action; } /** * Sets data of previous package in queue. * * @param string[] $packageData */ public function setPreviousPackage(array $packageData) { $this->previousPackageData = $packageData; } /** * Installs node components and returns next node. * * @param string $node * @return PackageInstallationStep * @throws SystemException */ public function install($node) { $nodes = $this->nodeBuilder->getNodeData($node); if (empty($nodes)) { // guard against possible issues with empty instruction blocks, including // these blocks that contain no valid instructions at all (e.g. typo from // copy & paste) throw new SystemException("Failed to retrieve nodes for identifier '".$node."', the query returned no results."); } // invoke node-specific actions $step = null; foreach ($nodes as $data) { $nodeData = unserialize($data['nodeData']); switch ($data['nodeType']) { case 'package': $step = $this->installPackage($nodeData); break; case 'pip': $step = $this->executePIP($nodeData); break; case 'optionalPackages': $step = $this->selectOptionalPackages($node, $nodeData); break; default: die("Unknown node type: '".$data['nodeType']."'"); break; } if ($step->splitNode()) { $this->nodeBuilder->cloneNode($node, $data['sequenceNo']); break; } } // mark node as completed $this->nodeBuilder->completeNode($node); // assign next node $node = $this->nodeBuilder->getNextNode($node); $step->setNode($node); // perform post-install/update actions if ($node == '') { // update "last update time" option $sql = "UPDATE wcf".WCF_N."_option SET optionValue = ? WHERE optionName = ?"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute([ TIME_NOW, 'last_update_time' ]); // update options.inc.php OptionEditor::resetCache(); if ($this->action == 'install') { // save localized package infos $this->saveLocalizedPackageInfos(); // remove all cache files after WCFSetup if (!PACKAGE_ID) { CacheHandler::getInstance()->flushAll(); $sql = "UPDATE wcf".WCF_N."_option SET optionValue = ? WHERE optionName = ?"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute([ StringUtil::getUUID(), 'wcf_uuid' ]); if (file_exists(WCF_DIR . 'cookiePrefix.txt')) { $statement->execute([ COOKIE_PREFIX, 'cookie_prefix' ]); @unlink(WCF_DIR . 'cookiePrefix.txt'); } $user = new User(1); $statement->execute([ $user->username, 'mail_from_name' ]); $statement->execute([ $user->email, 'mail_from_address' ]); $statement->execute([ $user->email, 'mail_admin_address' ]); try { $statement->execute([ bin2hex(\random_bytes(20)), 'signature_secret' ]); } catch (CryptoException $e) { // ignore, the secret will stay empty and crypto operations // depending on it will fail } if (WCF::getSession()->getVar('__wcfSetup_developerMode')) { $statement->execute([ 1, 'enable_debug_mode' ]); $statement->execute([ 'public', 'exception_privacy' ]); $statement->execute([ 'debug', 'mail_send_method' ]); $statement->execute([ 1, 'enable_developer_tools' ]); foreach (DevtoolsSetup::getInstance()->getOptionOverrides() as $optionName => $optionValue) { $statement->execute([ $optionValue, $optionName ]); } foreach (DevtoolsSetup::getInstance()->getUsers() as $newUser) { (new UserAction([], 'create', [ 'data' => [ 'email' => $newUser['email'], 'password' => $newUser['password'], 'username' => $newUser['username'] ], 'groups' => [ 1, 3 ] ]))->executeAction(); } if (($importPath = DevtoolsSetup::getInstance()->getDevtoolsImportPath()) !== '') { (new DevtoolsProjectAction([], 'quickSetup', [ 'path' => $importPath ]))->executeAction(); } } // update options.inc.php OptionEditor::resetCache(); } // rebuild application paths ApplicationHandler::rebuild(); } // remove template listener cache TemplateListenerCodeCacheBuilder::getInstance()->reset(); // reset language cache LanguageFactory::getInstance()->clearCache(); LanguageFactory::getInstance()->deleteLanguageCache(); // reset stylesheets StyleHandler::resetStylesheets(); // clear user storage UserStorageHandler::getInstance()->clear(); // rebuild config files for affected applications $sql = "SELECT package.packageID FROM wcf".WCF_N."_package_installation_queue queue, wcf".WCF_N."_package package WHERE queue.processNo = ? AND package.packageID = queue.packageID AND package.isApplication = ?"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute([ $this->queue->processNo, 1 ]); while ($row = $statement->fetchArray()) { Package::writeConfigFile($row['packageID']); } EventHandler::getInstance()->fireAction($this, 'postInstall'); // remove archives $sql = "SELECT archive FROM wcf".WCF_N."_package_installation_queue WHERE processNo = ?"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute([$this->queue->processNo]); while ($row = $statement->fetchArray()) { @unlink($row['archive']); } // delete queues $sql = "DELETE FROM wcf".WCF_N."_package_installation_queue WHERE processNo = ?"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute([$this->queue->processNo]); } return $step; } /** * Returns current package archive. * * @return PackageArchive */ public function getArchive() { if ($this->archive === null) { // check if we're doing an iterative update of the same package if ($this->previousPackageData !== null && $this->getPackage()->package == $this->previousPackageData['package']) { if (Package::compareVersion($this->getPackage()->packageVersion, $this->previousPackageData['packageVersion'], '<')) { // fake package to simulate the package version required by current archive $this->getPackage()->setPackageVersion($this->previousPackageData['packageVersion']); } } $this->archive = new PackageArchive($this->queue->archive, $this->getPackage()); if (FileUtil::isURL($this->archive->getArchive())) { // get return value and update entry in // package_installation_queue with this value $archive = $this->archive->downloadArchive(); $queueEditor = new PackageInstallationQueueEditor($this->queue); $queueEditor->update(['archive' => $archive]); } $this->archive->openArchive(); } return $this->archive; } /** * Installs current package. * * @param mixed[] $nodeData * @return PackageInstallationStep * @throws SystemException */ protected function installPackage(array $nodeData) { $installationStep = new PackageInstallationStep(); // check requirements if (!empty($nodeData['requirements'])) { foreach ($nodeData['requirements'] as $package => $requirementData) { // get existing package if ($requirementData['packageID']) { $sql = "SELECT packageName, packageVersion FROM wcf".WCF_N."_package WHERE packageID = ?"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute([$requirementData['packageID']]); } else { // try to find matching package $sql = "SELECT packageName, packageVersion FROM wcf".WCF_N."_package WHERE package = ?"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute([$package]); } $row = $statement->fetchArray(); // package is required but not available if ($row === false) { throw new SystemException("Package '".$package."' is required by '".$nodeData['packageName']."', but is neither installed nor shipped."); } // check version requirements if ($requirementData['minVersion']) { if (Package::compareVersion($row['packageVersion'], $requirementData['minVersion']) < 0) { throw new SystemException("Package '".$nodeData['packageName']."' requires package '".$row['packageName']."' in version '".$requirementData['minVersion']."', but only version '".$row['packageVersion']."' is installed"); } } } } unset($nodeData['requirements']); // update package if ($this->queue->packageID) { $packageEditor = new PackageEditor(new Package($this->queue->packageID)); unset($nodeData['installDate']); $packageEditor->update($nodeData); // delete old excluded packages $sql = "DELETE FROM wcf".WCF_N."_package_exclusion WHERE packageID = ?"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute([$this->queue->packageID]); // delete old compatibility versions $sql = "DELETE FROM wcf".WCF_N."_package_compatibility WHERE packageID = ?"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute([$this->queue->packageID]); // delete old requirements and dependencies $sql = "DELETE FROM wcf".WCF_N."_package_requirement WHERE packageID = ?"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute([$this->queue->packageID]); } else { // create package entry $package = PackageEditor::create($nodeData); // update package id for current queue $queueEditor = new PackageInstallationQueueEditor($this->queue); $queueEditor->update(['packageID' => $package->packageID]); // reload queue $this->queue = new PackageInstallationQueue($this->queue->queueID); $this->package = null; if ($package->isApplication) { $host = str_replace(RouteHandler::getProtocol(), '', RouteHandler::getHost()); $path = RouteHandler::getPath(['acp']); // insert as application ApplicationEditor::create([ 'domainName' => $host, 'domainPath' => $path, 'cookieDomain' => $host, 'packageID' => $package->packageID ]); } } // save excluded packages if (count($this->getArchive()->getExcludedPackages())) { $sql = "INSERT INTO wcf".WCF_N."_package_exclusion (packageID, excludedPackage, excludedPackageVersion) VALUES (?, ?, ?)"; $statement = WCF::getDB()->prepareStatement($sql); foreach ($this->getArchive()->getExcludedPackages() as $excludedPackage) { $statement->execute([ $this->queue->packageID, $excludedPackage['name'], !empty($excludedPackage['version']) ? $excludedPackage['version'] : '' ]); } } // save compatible versions if (!empty($this->getArchive()->getCompatibleVersions())) { $sql = "INSERT INTO wcf".WCF_N."_package_compatibility (packageID, version) VALUES (?, ?)"; $statement = WCF::getDB()->prepareStatement($sql); foreach ($this->getArchive()->getCompatibleVersions() as $version) { $statement->execute([ $this->queue->packageID, $version ]); } } // insert requirements and dependencies $requirements = $this->getArchive()->getAllExistingRequirements(); if (!empty($requirements)) { $sql = "INSERT INTO wcf".WCF_N."_package_requirement (packageID, requirement) VALUES (?, ?)"; $statement = WCF::getDB()->prepareStatement($sql); foreach ($requirements as $identifier => $possibleRequirements) { $requirement = array_shift($possibleRequirements); $statement->execute([ $this->queue->packageID, $requirement['packageID'] ]); } } if ($this->getPackage()->isApplication && $this->getPackage()->package != 'com.woltlab.wcf' && $this->getAction() == 'install') { if (empty($this->getPackage()->packageDir)) { $document = $this->promptPackageDir(); if ($document !== null && $document instanceof FormDocument) { $installationStep->setDocument($document); } $installationStep->setSplitNode(); } } return $installationStep; } /** * Saves the localized package info. */ protected function saveLocalizedPackageInfos() { $package = new Package($this->queue->packageID); // localize package information $sql = "INSERT INTO wcf".WCF_N."_language_item (languageID, languageItem, languageItemValue, languageCategoryID, packageID) VALUES (?, ?, ?, ?, ?)"; $statement = WCF::getDB()->prepareStatement($sql); // get language list $languageList = new LanguageList(); $languageList->readObjects(); // workaround for WCFSetup if (!PACKAGE_ID) { $sql = "SELECT * FROM wcf".WCF_N."_language_category WHERE languageCategory = ?"; $statement2 = WCF::getDB()->prepareStatement($sql); $statement2->execute(['wcf.acp.package']); $languageCategory = $statement2->fetchObject(LanguageCategory::class); } else { $languageCategory = LanguageFactory::getInstance()->getCategory('wcf.acp.package'); } // save package name $this->saveLocalizedPackageInfo($statement, $languageList, $languageCategory, $package, 'packageName'); // save package description $this->saveLocalizedPackageInfo($statement, $languageList, $languageCategory, $package, 'packageDescription'); // update description and name $packageEditor = new PackageEditor($package); $packageEditor->update([ 'packageDescription' => 'wcf.acp.package.packageDescription.package'.$this->queue->packageID, 'packageName' => 'wcf.acp.package.packageName.package'.$this->queue->packageID ]); } /** * Saves a localized package info. * * @param PreparedStatement $statement * @param LanguageList $languageList * @param LanguageCategory $languageCategory * @param Package $package * @param string $infoName */ protected function saveLocalizedPackageInfo(PreparedStatement $statement, $languageList, LanguageCategory $languageCategory, Package $package, $infoName) { $infoValues = $this->getArchive()->getPackageInfo($infoName); // get default value for languages without specified information $defaultValue = ''; if (isset($infoValues['default'])) { $defaultValue = $infoValues['default']; } else if (isset($infoValues['en'])) { // fallback to English $defaultValue = $infoValues['en']; } else if (isset($infoValues[WCF::getLanguage()->getFixedLanguageCode()])) { // fallback to the language of the current user $defaultValue = $infoValues[WCF::getLanguage()->getFixedLanguageCode()]; } else if ($infoName == 'packageName') { // fallback to the package identifier for the package name $defaultValue = $this->getArchive()->getPackageInfo('name'); } foreach ($languageList as $language) { $value = $defaultValue; if (isset($infoValues[$language->languageCode])) { $value = $infoValues[$language->languageCode]; } $statement->execute([ $language->languageID, 'wcf.acp.package.'.$infoName.'.package'.$package->packageID, $value, $languageCategory->languageCategoryID, 1 ]); } } /** * Executes a package installation plugin. * * @param mixed[] $nodeData * @return PackageInstallationStep * @throws SystemException */ protected function executePIP(array $nodeData) { $step = new PackageInstallationStep(); // fetch all pips associated with current PACKAGE_ID and include pips // previously installed by current installation queue $sql = "SELECT pluginName, className FROM wcf".WCF_N."_package_installation_plugin WHERE pluginName = ?"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute([$nodeData['pip']]); $row = $statement->fetchArray(); // PIP is unknown if (!$row || (strcmp($nodeData['pip'], $row['pluginName']) !== 0)) { throw new SystemException("unable to find package installation plugin '".$nodeData['pip']."'"); } // valdidate class definition $className = $row['className']; if (!class_exists($className)) { throw new SystemException("unable to find class '".$className."'"); } // set default value if (empty($nodeData['value'])) { $defaultValue = call_user_func([$className, 'getDefaultFilename']); if ($defaultValue) { $nodeData['value'] = $defaultValue; } } $plugin = new $className($this, $nodeData); if (!($plugin instanceof IPackageInstallationPlugin)) { throw new ImplementationException($className, IPackageInstallationPlugin::class); } // execute PIP $document = null; try { $document = $plugin->{$this->action}(); } catch (SplitNodeException $e) { $step->setSplitNode(); } if ($document !== null && ($document instanceof FormDocument)) { $step->setDocument($document); $step->setSplitNode(); } return $step; } /** * Displays a list to select optional packages or installs selection. * * @param string $currentNode * @param array $nodeData * @return PackageInstallationStep */ protected function selectOptionalPackages($currentNode, array $nodeData) { $installationStep = new PackageInstallationStep(); $document = $this->promptOptionalPackages($nodeData); if ($document !== null && $document instanceof FormDocument) { $installationStep->setDocument($document); $installationStep->setSplitNode(); } // insert new nodes for each package else if (is_array($document)) { // get target child node $node = $currentNode; $queue = $this->queue; $shiftNodes = false; foreach ($nodeData as $package) { if (in_array($package['package'], $document)) { // ignore uninstallable packages if (!$package['isInstallable']) { continue; } if (!$shiftNodes) { $this->nodeBuilder->shiftNodes($currentNode, 'tempNode'); $shiftNodes = true; } $queue = PackageInstallationQueueEditor::create([ 'parentQueueID' => $queue->queueID, 'processNo' => $this->queue->processNo, 'userID' => WCF::getUser()->userID, 'package' => $package['package'], 'packageName' => $package['packageName'], 'archive' => $package['archive'], 'action' => $queue->action ]); $installation = new PackageInstallationDispatcher($queue); $installation->nodeBuilder->setParentNode($node); $installation->nodeBuilder->buildNodes(); $node = $installation->nodeBuilder->getCurrentNode(); } else { // remove archive @unlink($package['archive']); } } // shift nodes if ($shiftNodes) { $this->nodeBuilder->shiftNodes('tempNode', $node); } } return $installationStep; } /** * Extracts files from .tar(.gz) archive and installs them * * @param string $targetDir * @param string $sourceArchive * @param IFileHandler $fileHandler * @return Installer */ public function extractFiles($targetDir, $sourceArchive, $fileHandler = null) { return new Installer($targetDir, $sourceArchive, $fileHandler); } /** * Returns current package. * * @return \wcf\data\package\Package */ public function getPackage() { if ($this->package === null) { $this->package = new Package($this->queue->packageID); } return $this->package; } /** * Prompts for a text input for package directory (applies for applications only) * * @return FormDocument */ protected function promptPackageDir() { // check for pre-defined directories originating from WCFSetup $directory = WCF::getSession()->getVar('__wcfSetup_directories'); if ($directory !== null) { $abbreviation = Package::getAbbreviation($this->getPackage()->package); $directory = isset($directory[$abbreviation]) ? $directory[$abbreviation] : null; } if ($directory === null && !PackageInstallationFormManager::findForm($this->queue, 'packageDir')) { $container = new GroupFormElementContainer(); $packageDir = new TextInputFormElement($container); $packageDir->setName('packageDir'); $packageDir->setLabel(WCF::getLanguage()->get('wcf.acp.package.packageDir.input')); // check if there are packages installed in a parent // directory of WCF, or if packages are below it $sql = "SELECT packageDir FROM wcf".WCF_N."_package WHERE packageDir <> ''"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute(); $isParent = null; while ($column = $statement->fetchColumn()) { if ($isParent !== null) { continue; } if (preg_match('~^\.\./[^\.]~', $column)) { $isParent = false; } else if (mb_strpos($column, '.') !== 0) { $isParent = true; } } $defaultPath = WCF_DIR; if ($isParent === false) { $defaultPath = dirname(WCF_DIR); } $defaultPath = FileUtil::addTrailingSlash(FileUtil::unifyDirSeparator($defaultPath)) . Package::getAbbreviation($this->getPackage()->package) . '/'; $packageDir->setValue($defaultPath); $container->appendChild($packageDir); $document = new FormDocument('packageDir'); $document->appendContainer($container); PackageInstallationFormManager::registerForm($this->queue, $document); return $document; } else { if ($directory !== null) { $document = null; $packageDir = $directory; } else { $document = PackageInstallationFormManager::getForm($this->queue, 'packageDir'); $document->handleRequest(); $packageDir = FileUtil::addTrailingSlash(FileUtil::getRealPath(FileUtil::unifyDirSeparator($document->getValue('packageDir')))); if ($packageDir === '/') $packageDir = ''; } if ($packageDir !== null) { // validate package dir if ($document !== null && file_exists($packageDir . 'global.php')) { $document->setError('packageDir', WCF::getLanguage()->get('wcf.acp.package.packageDir.notAvailable')); return $document; } // set package dir $packageEditor = new PackageEditor($this->getPackage()); $packageEditor->update([ 'packageDir' => FileUtil::getRelativePath(WCF_DIR, $packageDir) ]); // determine domain path, in some environments (e.g. ISPConfig) the $_SERVER paths are // faked and differ from the real filesystem path if (PACKAGE_ID) { $wcfDomainPath = ApplicationHandler::getInstance()->getWCF()->domainPath; } else { $sql = "SELECT domainPath FROM wcf".WCF_N."_application WHERE packageID = ?"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute([1]); $row = $statement->fetchArray(); $wcfDomainPath = $row['domainPath']; } $documentRoot = substr(FileUtil::unifyDirSeparator(WCF_DIR), 0, -strlen(FileUtil::unifyDirSeparator($wcfDomainPath))); $domainPath = FileUtil::getRelativePath($documentRoot, $packageDir); if ($domainPath === './') { // `FileUtil::getRelativePath()` returns `./` if both paths lead to the same directory $domainPath = '/'; } $domainPath = FileUtil::addLeadingSlash($domainPath); // update application path $application = new Application($this->getPackage()->packageID); $applicationEditor = new ApplicationEditor($application); $applicationEditor->update(['domainPath' => $domainPath]); // create directory and set permissions @mkdir($packageDir, 0777, true); FileUtil::makeWritable($packageDir); } return null; } } /** * Prompts a selection of optional packages. * * @param string[][] $packages * @return mixed */ protected function promptOptionalPackages(array $packages) { if (!PackageInstallationFormManager::findForm($this->queue, 'optionalPackages')) { $container = new MultipleSelectionFormElementContainer(); $container->setName('optionalPackages'); $container->setLabel(WCF::getLanguage()->get('wcf.acp.package.optionalPackages')); $container->setDescription(WCF::getLanguage()->get('wcf.acp.package.optionalPackages.description')); foreach ($packages as $package) { $optionalPackage = new MultipleSelectionFormElement($container); $optionalPackage->setName('optionalPackages'); $optionalPackage->setLabel($package['packageName']); $optionalPackage->setValue($package['package']); $optionalPackage->setDescription($package['packageDescription']); if (!$package['isInstallable']) { $optionalPackage->setDisabledMessage(WCF::getLanguage()->get('wcf.acp.package.install.optionalPackage.missingRequirements')); } $container->appendChild($optionalPackage); } $document = new FormDocument('optionalPackages'); $document->appendContainer($container); PackageInstallationFormManager::registerForm($this->queue, $document); return $document; } else { $document = PackageInstallationFormManager::getForm($this->queue, 'optionalPackages'); $document->handleRequest(); return $document->getValue('optionalPackages'); } } /** * Returns current package id. * * @return integer */ public function getPackageID() { return $this->queue->packageID; } /** * Returns current package name. * * @return string package name * @since 3.0 */ public function getPackageName() { return $this->queue->packageName; } /** * Returns current package installation type. * * @return string */ public function getAction() { return $this->action; } /** * Opens the package installation queue and * starts the installation, update or uninstallation of the first entry. * * @param integer $parentQueueID * @param integer $processNo */ public static function openQueue($parentQueueID = 0, $processNo = 0) { $conditions = new PreparedStatementConditionBuilder(); $conditions->add("userID = ?", [WCF::getUser()->userID]); $conditions->add("parentQueueID = ?", [$parentQueueID]); if ($processNo != 0) $conditions->add("processNo = ?", [$processNo]); $conditions->add("done = ?", [0]); $sql = "SELECT * FROM wcf".WCF_N."_package_installation_queue ".$conditions." ORDER BY queueID ASC"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute($conditions->getParameters()); $packageInstallation = $statement->fetchArray(); if (!isset($packageInstallation['queueID'])) { $url = LinkHandler::getInstance()->getLink('PackageList'); HeaderUtil::redirect($url); exit; } else { $url = LinkHandler::getInstance()->getLink('PackageInstallationConfirm', [], 'queueID='.$packageInstallation['queueID']); HeaderUtil::redirect($url); exit; } } /** * Checks the package installation queue for outstanding entries. * * @return integer */ public static function checkPackageInstallationQueue() { $sql = "SELECT queueID FROM wcf".WCF_N."_package_installation_queue WHERE userID = ? AND parentQueueID = 0 AND done = 0 ORDER BY queueID ASC"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute([WCF::getUser()->userID]); $row = $statement->fetchArray(); if (!$row) { return 0; } return $row['queueID']; } /** * Executes post-setup actions. */ public function completeSetup() { // remove archives $sql = "SELECT archive FROM wcf".WCF_N."_package_installation_queue WHERE processNo = ?"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute([$this->queue->processNo]); while ($row = $statement->fetchArray()) { @unlink($row['archive']); } // delete queues $sql = "DELETE FROM wcf".WCF_N."_package_installation_queue WHERE processNo = ?"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute([$this->queue->processNo]); // clear language files once whole installation is completed LanguageEditor::deleteLanguageFiles(); // reset all caches CacheHandler::getInstance()->flushAll(); } /** * Updates queue information. */ public function updatePackage() { if (empty($this->queue->packageName)) { $queueEditor = new PackageInstallationQueueEditor($this->queue); $queueEditor->update([ 'packageName' => $this->getArchive()->getLocalizedPackageInfo('packageName') ]); // reload queue $this->queue = new PackageInstallationQueue($this->queue->queueID); } } /** * Validates specific php requirements. * * @param array $requirements * @return mixed[][] */ public static function validatePHPRequirements(array $requirements) { $errors = []; // validate php version if (isset($requirements['version'])) { $passed = false; if (version_compare(PHP_VERSION, $requirements['version'], '>=')) { $passed = true; } if (!$passed) { $errors['version'] = [ 'required' => $requirements['version'], 'installed' => PHP_VERSION ]; } } // validate extensions if (isset($requirements['extensions'])) { foreach ($requirements['extensions'] as $extension) { $passed = extension_loaded($extension) ? true : false; if (!$passed) { $errors['extension'][] = [ 'extension' => $extension ]; } } } // validate settings if (isset($requirements['settings'])) { foreach ($requirements['settings'] as $setting => $value) { $iniValue = ini_get($setting); $passed = self::compareSetting($setting, $value, $iniValue); if (!$passed) { $errors['setting'][] = [ 'setting' => $setting, 'required' => $value, 'installed' => ($iniValue === false) ? '(unknown)' : $iniValue ]; } } } // validate functions if (isset($requirements['functions'])) { foreach ($requirements['functions'] as $function) { $function = mb_strtolower($function); $passed = self::functionExists($function); if (!$passed) { $errors['function'][] = [ 'function' => $function ]; } } } // validate classes if (isset($requirements['classes'])) { foreach ($requirements['classes'] as $class) { $passed = false; // see: http://de.php.net/manual/en/language.oop5.basic.php if (preg_match('~[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*.~', $class)) { $globalClass = '\\'.$class; if (class_exists($globalClass, false)) { $passed = true; } } if (!$passed) { $errors['class'][] = [ 'class' => $class ]; } } } return $errors; } /** * Validates if an function exists and is not blacklisted by suhosin extension. * * @param string $function * @return boolean * @see http://de.php.net/manual/en/function.function-exists.php#77980 */ protected static function functionExists($function) { if (extension_loaded('suhosin')) { $blacklist = @ini_get('suhosin.executor.func.blacklist'); if (!empty($blacklist)) { $blacklist = explode(',', $blacklist); foreach ($blacklist as $disabledFunction) { $disabledFunction = mb_strtolower(StringUtil::trim($disabledFunction)); if ($function == $disabledFunction) { return false; } } } } return function_exists($function); } /** * Compares settings, converting values into compareable ones. * * @param string $setting * @param string $value * @param mixed $compareValue * @return boolean */ protected static function compareSetting($setting, $value, $compareValue) { if ($compareValue === false) return false; $value = mb_strtolower($value); $trueValues = ['1', 'on', 'true']; $falseValues = ['0', 'off', 'false']; // handle values considered as 'true' if (in_array($value, $trueValues)) { return $compareValue ? true : false; } // handle values considered as 'false' else if (in_array($value, $falseValues)) { return (!$compareValue) ? true : false; } else if (!is_numeric($value)) { $compareValue = self::convertShorthandByteValue($compareValue); $value = self::convertShorthandByteValue($value); } return ($compareValue >= $value) ? true : false; } /** * Converts shorthand byte values into an integer representing bytes. * * @param string $value * @return integer * @see http://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes */ protected static function convertShorthandByteValue($value) { // convert into bytes $lastCharacter = mb_substr($value, -1); switch ($lastCharacter) { // gigabytes case 'g': return (int)$value * 1073741824; break; // megabytes case 'm': return (int)$value * 1048576; break; // kilobytes case 'k': return (int)$value * 1024; break; default: return $value; break; } } }