use Cli\Services\AppLocator;
use Cli\Services\ArtisanRunner;
use Cli\Services\BackupZip;
+use Cli\Services\Directories;
use Cli\Services\EnvironmentLoader;
use Cli\Services\InteractiveConsole;
use Cli\Services\MySqlRunner;
+use Cli\Services\Paths;
use Cli\Services\ProgramRunner;
use Cli\Services\RequirementsValidator;
-use RecursiveDirectoryIterator;
-use RecursiveIteratorIterator;
+use Exception;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
/**
* @throws CommandError
- * @throws \Exception
+ * @throws Exception
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$interactions = new InteractiveConsole($this->getHelper('question'), $input, $output);
- $output->writeln("<info>Warning!</info>");
- $output->writeln("<info>- A restore operation will overwrite and remove files & content from an existing instance.</info>");
- $output->writeln("<info>- Any existing tables within the configured database will be dropped.</info>");
- $output->writeln("<info>- You should only restore into an instance of the same or newer BookStack version.</info>");
- $output->writeln("<info>- This command won't handle, restore or address any server configuration.</info>");
+ $output->writeln("<warn>Warning!</warn>");
+ $output->writeln("<warn>- A restore operation will overwrite and remove files & content from an existing instance.</warn>");
+ $output->writeln("<warn>- Any existing tables within the configured database will be dropped.</warn>");
+ $output->writeln("<warn>- You should only restore into an instance of the same or newer BookStack version.</warn>");
+ $output->writeln("<warn>- This command won't handle, restore or address any server configuration.</warn>");
$appDir = AppLocator::require($input->getOption('app-directory'));
$output->writeln("<info>Checking system requirements...</info>");
}
if (!$hasContent) {
- throw new CommandError("Provided ZIP backup [{$zipPath}] does not have any expected restore-able content.");
+ throw new CommandError("Provided ZIP backup [{$zipPath}] does not have any expected restorable content.");
}
$output->writeln("<info>The checked elements will be restored into [{$appDir}].</info>");
- $output->writeln("<info>Existing content may be overwritten.</info>");
+ $output->writeln("<warn>Existing content will be overwritten.</warn>");
if (!$interactions->confirm("Do you want to continue?")) {
$output->writeln("<info>Stopping restore operation.</info>");
}
$output->writeln("<info>Extracting ZIP into temporary directory...</info>");
- $extractDir = $appDir . DIRECTORY_SEPARATOR . 'restore-temp-' . time();
+ $extractDir = Paths::join($appDir, 'restore-temp-' . time());
if (!mkdir($extractDir)) {
throw new CommandError("Could not create temporary extraction directory at [{$extractDir}].");
}
$artisan->run(['view:clear']);
$output->writeln("<info>Cleaning up extract directory...</info>");
- $this->deleteDirectoryAndContents($extractDir);
+ Directories::delete($extractDir);
- $output->writeln("<info>\nRestore operation complete!</info>");
+ $output->writeln("<success>\nRestore operation complete!</success>");
+ $output->writeln("<info>You may need to fix file/folder permissions so that the webserver has</info>");
+ $output->writeln("<info>the required read/write access to the necessary directories & files.</info>");
return Command::SUCCESS;
}
{
$oldEnv = EnvironmentLoader::load($extractDir);
$currentEnv = EnvironmentLoader::load($appDir);
- $envContents = file_get_contents($extractDir . DIRECTORY_SEPARATOR . '.env');
- $appEnvPath = $appDir . DIRECTORY_SEPARATOR . '.env';
+ $envContents = file_get_contents(Paths::join($extractDir, '.env'));
+ $appEnvPath = Paths::real(Paths::join($appDir, '.env'));
$mysqlCurrent = MySqlRunner::fromEnvOptions($currentEnv);
$mysqlOld = MySqlRunner::fromEnvOptions($oldEnv);
$currentEnvDbLines = array_values(array_filter(explode("\n", $currentEnvContents), function (string $line) {
return str_starts_with($line, 'DB_');
}));
- $oldEnvLines = array_values(array_filter(explode("\n", $currentEnvContents), function (string $line) {
+ $oldEnvLines = array_values(array_filter(explode("\n", $envContents), function (string $line) {
return !str_starts_with($line, 'DB_');
}));
$envContents = implode("\n", [
$returnData['new_url'] = $changedUrl;
}
- file_put_contents($appDir . DIRECTORY_SEPARATOR . '.env', $envContents);
+ file_put_contents($appEnvPath, $envContents);
return $returnData;
}
protected function restoreFolder(string $folderSubPath, string $appDir, string $extractDir): void
{
- $fullAppFolderPath = $appDir . DIRECTORY_SEPARATOR . $folderSubPath;
- $this->deleteDirectoryAndContents($fullAppFolderPath);
- rename($extractDir . DIRECTORY_SEPARATOR . $folderSubPath, $fullAppFolderPath);
- }
-
- protected function deleteDirectoryAndContents(string $dir)
- {
- $files = new RecursiveIteratorIterator(
- new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
- RecursiveIteratorIterator::CHILD_FIRST
- );
-
- foreach ($files as $fileinfo) {
- $path = $fileinfo->getRealPath();
- $fileinfo->isDir() ? rmdir($path) : unlink($path);
- }
-
- rmdir($dir);
+ $fullAppFolderPath = Paths::real(Paths::join($appDir, $folderSubPath));
+ Directories::delete($fullAppFolderPath);
+ Directories::move(Paths::join($extractDir, $folderSubPath), $fullAppFolderPath);
}
protected function restoreDatabase(string $appDir, string $extractDir): void
{
- $dbDump = $extractDir . DIRECTORY_SEPARATOR . 'db.sql';
+ $dbDump = Paths::join($extractDir, 'db.sql');
$currentEnv = EnvironmentLoader::load($appDir);
$mysql = MySqlRunner::fromEnvOptions($currentEnv);
file_put_contents($dropSqlTempFile, $mysql->dropTablesSql());
$mysql->importSqlFile($dropSqlTempFile);
-
+ // Import MySQL dump
$mysql->importSqlFile($dbDump);
}
}