* @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\TaskRunner\Rollback; use PrestaShop\Module\AutoUpgrade\Parameters\UpgradeFileNames; use PrestaShop\Module\AutoUpgrade\TaskRunner\AbstractTask; use PrestaShop\Module\AutoUpgrade\UpgradeTools\Database; use PrestaShop\Module\AutoUpgrade\UpgradeContainer; /** * Restores database from backup file. */ class RestoreDb extends AbstractTask { public function run() { $databaseTools = new Database($this->container->getDb()); $ignore_stats_table = array( _DB_PREFIX_ . 'connections', _DB_PREFIX_ . 'connections_page', _DB_PREFIX_ . 'connections_source', _DB_PREFIX_ . 'guest', _DB_PREFIX_ . 'statssearch', ); $startTime = time(); $listQuery = array(); // deal with running backup rest if exist if (file_exists($this->container->getProperty(UpgradeContainer::WORKSPACE_PATH) . DIRECTORY_SEPARATOR . UpgradeFileNames::QUERIES_TO_RESTORE_LIST)) { $listQuery = $this->container->getFileConfigurationStorage()->load(UpgradeFileNames::QUERIES_TO_RESTORE_LIST); } // deal with the next files stored in restoreDbFilenames $restoreDbFilenames = $this->container->getState()->getRestoreDbFilenames(); if (empty($listQuery) && count($restoreDbFilenames) > 0) { $currentDbFilename = array_shift($restoreDbFilenames); $this->container->getState()->setRestoreDbFilenames($restoreDbFilenames); if (!preg_match('#auto-backupdb_([0-9]{6})_#', $currentDbFilename, $match)) { $this->next = 'error'; $this->error = true; $this->logger->error($this->translator->trans('%s: File format does not match.', array($currentDbFilename), 'Modules.Autoupgrade.Admin')); return false; } $this->container->getState()->setDbStep($match[1]); $backupdb_path = $this->container->getProperty(UpgradeContainer::BACKUP_PATH) . DIRECTORY_SEPARATOR . $this->container->getState()->getRestoreName(); $dot_pos = strrpos($currentDbFilename, '.'); $fileext = substr($currentDbFilename, $dot_pos + 1); $requests = array(); $content = ''; $this->logger->debug($this->translator->trans( 'Opening backup database file %filename% in %extension% mode', array( '%filename%' => $currentDbFilename, '%extension%' => $fileext, ), 'Modules.Autoupgrade.Admin' )); switch ($fileext) { case 'bz': case 'bz2': $fp = bzopen($backupdb_path . DIRECTORY_SEPARATOR . $currentDbFilename, 'r'); if (is_resource($fp)) { while (!feof($fp)) { $content .= bzread($fp, 4096); } bzclose($fp); } break; case 'gz': $fp = gzopen($backupdb_path . DIRECTORY_SEPARATOR . $currentDbFilename, 'r'); if (is_resource($fp)) { while (!feof($fp)) { $content .= gzread($fp, 4096); } gzclose($fp); } break; default: $fp = fopen($backupdb_path . DIRECTORY_SEPARATOR . $currentDbFilename, 'r'); if (is_resource($fp)) { while (!feof($fp)) { $content .= fread($fp, 4096); } fclose($fp); } } $currentDbFilename = ''; if (empty($content)) { $this->logger->error($this->translator->trans('Database backup is empty.', array(), 'Modules.Autoupgrade.Admin')); $this->next = 'rollback'; return false; } // preg_match_all is better than preg_split (what is used in do Upgrade.php) // This way we avoid extra blank lines // option s (PCRE_DOTALL) added $listQuery = preg_split('/;[\n\r]+/Usm', $content); unset($content); // Get tables before backup if ($this->container->getState()->getDbStep() == '1') { $tables_after_restore = array(); foreach ($listQuery as $q) { if (preg_match('/`(?' . _DB_PREFIX_ . '[a-zA-Z0-9_-]+)`/', $q, $matches)) { if (isset($matches['table'])) { $tables_after_restore[$matches['table']] = $matches['table']; } } } $tables_after_restore = array_unique($tables_after_restore); $tables_before_restore = $databaseTools->getAllTables(); $tablesToRemove = array_diff($tables_before_restore, $tables_after_restore, $ignore_stats_table); if (!empty($tablesToRemove)) { $this->container->getFileConfigurationStorage()->save($tablesToRemove, UpgradeFileNames::DB_TABLES_TO_CLEAN_LIST); } } } /** @todo : error if listQuery is not an array (that can happen if toRestoreQueryList is empty for example) */ $time_elapsed = time() - $startTime; if (is_array($listQuery) && count($listQuery) > 0) { $this->container->getDb()->execute('SET SESSION sql_mode = \'\''); $this->container->getDb()->execute('SET FOREIGN_KEY_CHECKS=0'); do { if (count($listQuery) == 0) { if (file_exists($this->container->getProperty(UpgradeContainer::WORKSPACE_PATH) . DIRECTORY_SEPARATOR . UpgradeFileNames::QUERIES_TO_RESTORE_LIST)) { unlink($this->container->getProperty(UpgradeContainer::WORKSPACE_PATH) . DIRECTORY_SEPARATOR . UpgradeFileNames::QUERIES_TO_RESTORE_LIST); } $restoreDbFilenamesCount = count($this->container->getState()->getRestoreDbFilenames()); if ($restoreDbFilenamesCount) { $this->logger->info($this->translator->trans( 'Database restoration file %filename% done. %filescount% file(s) left...', array( '%filename%' => $this->container->getState()->getDbStep(), '%filescount%' => $restoreDbFilenamesCount, ), 'Modules.Autoupgrade.Admin' )); } else { $this->logger->info($this->translator->trans('Database restoration file %1$s done.', array($this->container->getState()->getDbStep()), 'Modules.Autoupgrade.Admin')); } $this->stepDone = true; $this->status = 'ok'; $this->next = 'restoreDb'; if ($restoreDbFilenamesCount === 0) { $this->next = 'rollbackComplete'; $this->logger->info($this->translator->trans('Database has been restored.', array(), 'Modules.Autoupgrade.Admin')); $databaseTools->cleanTablesAfterBackup($this->container->getFileConfigurationStorage()->load(UpgradeFileNames::DB_TABLES_TO_CLEAN_LIST)); $this->container->getFileConfigurationStorage()->clean(UpgradeFileNames::DB_TABLES_TO_CLEAN_LIST); } return true; } // filesForBackup already contains all the correct files if (count($listQuery) == 0) { continue; } $query = trim(array_shift($listQuery)); if (!empty($query)) { if (!$this->container->getDb()->execute($query, false)) { if (is_array($listQuery)) { $listQuery = array_unshift($listQuery, $query); } $this->logger->error($this->translator->trans('[SQL ERROR]', array(), 'Modules.Autoupgrade.Admin') . ' ' . $query . ' - ' . $this->container->getDb()->getMsgError()); $this->logger->info($this->translator->trans('Error during database restoration', array(), 'Modules.Autoupgrade.Admin')); $this->next = 'error'; $this->error = true; unlink($this->container->getProperty(UpgradeContainer::WORKSPACE_PATH) . DIRECTORY_SEPARATOR . UpgradeFileNames::QUERIES_TO_RESTORE_LIST); return false; } } // note : theses queries can be too big and can cause issues for display // else // $this->nextQuickInfo[] = '[OK] '.$query; $time_elapsed = time() - $startTime; } while ($time_elapsed < $this->container->getUpgradeConfiguration()->getTimePerCall()); $queries_left = count($listQuery); if ($queries_left > 0) { $this->container->getFileConfigurationStorage()->save($listQuery, UpgradeFileNames::QUERIES_TO_RESTORE_LIST); } elseif (file_exists($this->container->getProperty(UpgradeContainer::WORKSPACE_PATH) . DIRECTORY_SEPARATOR . UpgradeFileNames::QUERIES_TO_RESTORE_LIST)) { unlink($this->container->getProperty(UpgradeContainer::WORKSPACE_PATH) . DIRECTORY_SEPARATOR . UpgradeFileNames::QUERIES_TO_RESTORE_LIST); } $this->stepDone = false; $this->next = 'restoreDb'; $this->logger->info($this->translator->trans( '%numberqueries% queries left for file %filename%...', array( '%numberqueries%' => $queries_left, '%filename%' => $this->container->getState()->getDbStep(), ), 'Modules.Autoupgrade.Admin' )); unset($query, $listQuery); } else { $this->stepDone = true; $this->status = 'ok'; $this->next = 'rollbackComplete'; $this->logger->info($this->translator->trans('Database restoration done.', array(), 'Modules.Autoupgrade.Admin')); $databaseTools->cleanTablesAfterBackup($this->container->getFileConfigurationStorage()->load(UpgradeFileNames::DB_TABLES_TO_CLEAN_LIST)); $this->container->getFileConfigurationStorage()->clean(UpgradeFileNames::DB_TABLES_TO_CLEAN_LIST); } return true; } public function init() { // We don't need the whole core being instanciated, only the autoloader $this->container->initPrestaShopAutoloader(); // Loads the parameters.php file on PrestaShop 1.7, needed for accessing the database if (file_exists($this->container->getProperty(UpgradeContainer::PS_ROOT_PATH) . '/config/bootstrap.php')) { require_once $this->container->getProperty(UpgradeContainer::PS_ROOT_PATH) . '/config/bootstrap.php'; } } }