* @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) */ namespace PrestaShop\Module\AutoUpgrade\Task\Miscellaneous; use Exception; use PrestaShop\Module\AutoUpgrade\Exceptions\ZipActionException; use PrestaShop\Module\AutoUpgrade\Parameters\UpgradeConfiguration; use PrestaShop\Module\AutoUpgrade\Parameters\UpgradeConfigurationStorage; use PrestaShop\Module\AutoUpgrade\Parameters\UpgradeFileNames; use PrestaShop\Module\AutoUpgrade\Task\AbstractTask; use PrestaShop\Module\AutoUpgrade\Task\ExitCode; use PrestaShop\Module\AutoUpgrade\UpgradeContainer; use PrestaShop\Module\AutoUpgrade\Upgrader; use RuntimeException; use Symfony\Component\Filesystem\Exception\FileNotFoundException; use Symfony\Component\Filesystem\Exception\IOException; /** * update configuration after validating the new values. */ class UpdateConfig extends AbstractTask { /** * Data being passed by CLI entry point * * @var array */ protected $cliParameters; /** * @throws Exception */ public function run(): int { // nothing next $this->next = ''; // Was coming from AdminSelfUpgrade::currentParams before $configurationData = $this->getConfigurationData(); $config = []; // update channel if (isset($configurationData['channel'])) { $config['channel'] = $configurationData['channel']; $config['archive.filename'] = Upgrader::DEFAULT_FILENAME; // Switch on default theme if major upgrade (i.e: 1.6 -> 1.7) $config['PS_AUTOUP_CHANGE_DEFAULT_THEME'] = ($configurationData['channel'] === 'major'); } if (isset($configurationData['private_release_link'], $configurationData['private_release_md5'])) { $config['channel'] = 'private'; $config['private_release_link'] = $configurationData['private_release_link']; $config['private_release_md5'] = $configurationData['private_release_md5']; $config['private_allow_major'] = $configurationData['private_allow_major']; } if (!empty($configurationData['archive_prestashop'])) { $file = $configurationData['archive_prestashop']; $fullFilePath = $this->container->getProperty(UpgradeContainer::DOWNLOAD_PATH) . DIRECTORY_SEPARATOR . $file; if (!file_exists($fullFilePath)) { $this->setErrorFlag(); $this->logger->info($this->translator->trans('File %s does not exist. Unable to select that channel.', [$file])); return ExitCode::FAIL; } try { $targetVersion = $this->extractPrestashopVersionFromZip($fullFilePath); } catch (Exception $exception) { $this->setErrorFlag(); $this->logger->info($this->translator->trans('Unable to retrieve version from zip: %s.', [$exception->getMessage()])); return ExitCode::FAIL; } $xmlFile = $configurationData['archive_xml']; $fullXmlPath = $this->container->getProperty(UpgradeContainer::DOWNLOAD_PATH) . DIRECTORY_SEPARATOR . $xmlFile; if (!empty($xmlFile) && !file_exists($fullXmlPath)) { $this->setErrorFlag(); $this->logger->info($this->translator->trans('File %s does not exist. Unable to select that channel.', [$xmlFile])); return ExitCode::FAIL; } $xmlVersion = $this->getXmlVersion($fullXmlPath); if ($xmlVersion !== $targetVersion) { $this->error = true; $this->logger->info($this->translator->trans('Prestashop version detected in the xml (%s) does not match the zip version (%s).', [$xmlVersion, $targetVersion])); return ExitCode::FAIL; } $config['channel'] = 'archive'; $config['archive.filename'] = $configurationData['archive_prestashop']; $config['archive.version_num'] = $targetVersion; $config['archive.xml'] = $configurationData['archive_xml']; $this->logger->info($this->translator->trans('Upgrade process will use archive.')); } if (isset($configurationData['directory_num'])) { $config['channel'] = 'directory'; if (empty($configurationData['directory_num']) || strpos($configurationData['directory_num'], '.') === false) { $this->setErrorFlag(); $this->logger->info($this->translator->trans('Version number is missing. Unable to select that channel.')); return ExitCode::FAIL; } $config['directory.version_num'] = $configurationData['directory_num']; } foreach (UpgradeConfiguration::UPGRADE_CONST_KEYS as $key) { if (!isset($configurationData[$key])) { continue; } // The PS_DISABLE_OVERRIDES variable must only be updated on the database side if ($key === 'PS_DISABLE_OVERRIDES') { UpgradeConfiguration::updatePSDisableOverrides((bool) $configurationData[$key]); } else { $config[$key] = $configurationData[$key]; } } if (!$this->writeConfig($config)) { $this->setErrorFlag(); $this->logger->info($this->translator->trans('Error on saving configuration')); return ExitCode::FAIL; } return ExitCode::SUCCESS; } /** * @param array $parameters */ public function inputCliParameters($parameters): void { $this->cliParameters = $parameters; } /** * @return array */ protected function getConfigurationData(): array { if (null !== $this->cliParameters) { return $this->getCLIParams(); } return $this->getRequestParams(); } /** * @return array */ protected function getCLIParams(): array { if (empty($this->cliParameters)) { throw new \RuntimeException('Empty CLI parameters - did CLI entry point failed to provide data?'); } return $this->cliParameters; } /** * @return array */ protected function getRequestParams(): array { return empty($_REQUEST['params']) ? [] : $_REQUEST['params']; } /** * update module configuration (saved in file UpgradeFiles::configFilename) with $new_config. * * @param array $config * * @return bool true if success * * @throws Exception */ private function writeConfig(array $config): bool { if (!$this->container->getFileConfigurationStorage()->exists(UpgradeFileNames::CONFIG_FILENAME) && !empty($config['channel'])) { $this->container->getUpgrader()->channel = $config['channel']; $this->container->getUpgrader()->checkPSVersion(); $this->container->getState()->setInstallVersion($this->container->getUpgrader()->version_num); $this->container->getState()->setOriginVersion($this->container->getProperty(UpgradeContainer::PS_VERSION)); } $this->container->getUpgradeConfiguration()->merge($config); $this->logger->info($this->translator->trans('Configuration successfully updated.') . ' ' . $this->translator->trans('This page will now be reloaded and the module will check if a new version is available.') . ''); return (new UpgradeConfigurationStorage($this->container->getProperty(UpgradeContainer::WORKSPACE_PATH) . DIRECTORY_SEPARATOR))->save($this->container->getUpgradeConfiguration(), UpgradeFileNames::CONFIG_FILENAME); } /** * @throws ZipActionException * @throws Exception */ private function extractPrestashopVersionFromZip(string $zipFile): string { $internalZipFileName = 'prestashop.zip'; $versionFile = 'install/install_version.php'; if (!file_exists($zipFile)) { throw new FileNotFoundException("Unable to find $zipFile file"); } $zip = $this->container->getZipAction()->open($zipFile); $internalZipContent = $this->container->getZipAction()->extractFileFromArchive($zip, $internalZipFileName); $zip->close(); $tempInternalZipPath = $this->createTemporaryFile($internalZipContent); $internalZip = $this->container->getZipAction()->open($tempInternalZipPath); $fileContent = $this->container->getZipAction()->extractFileFromArchive($internalZip, $versionFile); $internalZip->close(); @unlink($tempInternalZipPath); return $this->extractVersionFromContent($fileContent); } /** * @throws IOException */ private function createTemporaryFile(string $content): string { $tempFilePath = tempnam(sys_get_temp_dir(), 'internal_zip_'); if (file_put_contents($tempFilePath, $content) === false) { throw new IOException('Unable to create temporary file'); } return $tempFilePath; } /** * @throws RuntimeException */ private function extractVersionFromContent(string $content): string { $pattern = "/define\\('_PS_INSTALL_VERSION_', '([\\d.]+)'\\);/"; if (preg_match($pattern, $content, $matches)) { return $matches[1]; } else { throw new RuntimeException('Unable to extract version from content'); } } private function getXmlVersion(string $xmlPath): string { $xml = @simplexml_load_file($xmlPath); return $xml->ps_root_dir['version']; } }