diff --git a/.gitignore b/.gitignore index d436576ff..da248e548 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ vendor/ .sass-cache # Grav Specific +backup/* +!backup/.* cache/* !cache/.* assets/* diff --git a/.htaccess b/.htaccess index ce48b2674..ecf1097c0 100644 --- a/.htaccess +++ b/.htaccess @@ -44,7 +44,7 @@ RewriteRule .* index.php [L] ## Begin - Security # Block all direct access for these folders -RewriteRule ^(cache|bin|logs)/(.*) error [L] +RewriteRule ^(cache|bin|logs|backup)/(.*) error [L] # Block access to specific file types for these folders RewriteRule ^(system|user|vendor)/(.*)\.(txt|md|html|yaml|php|twig|sh|bat)$ error [L] ## End - Security diff --git a/backup/.gitkeep b/backup/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/nginx.conf b/nginx.conf index 12a48054a..afa450c23 100644 --- a/nginx.conf +++ b/nginx.conf @@ -25,7 +25,7 @@ http { index index.php; if (!-e $request_filename){ rewrite ^(.*)$ /index.php last; } } - + location /images/ { # Serve images as static } @@ -44,6 +44,10 @@ http { rewrite ^/bin/(.*)$ /error redirect; } + location /backup { + rewrite ^/backup/(.*) /error redirect; + } + location /system { rewrite ^/system/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$ /error redirect; } diff --git a/system/src/Grav/Common/Backup/ZipBackup.php b/system/src/Grav/Common/Backup/ZipBackup.php new file mode 100644 index 000000000..d275d21eb --- /dev/null +++ b/system/src/Grav/Common/Backup/ZipBackup.php @@ -0,0 +1,121 @@ +findResource('backup://', true); + + if (!$destination) + throw new \RuntimeException('The backup folder is missing.'); + + Folder::mkdir($destination); + } + + $name = self::getGrav()['config']->get('site.title', basename(GRAV_ROOT)); + + if (is_dir($destination)) { + $date = date('YmdHis', time()); + $filename = $name . '-' . $date . '.zip'; + $destination = rtrim($destination, DS) . DS . $filename; + } + + $messager && $messager([ + 'type' => 'message', + 'level' => 'info', + 'message' => 'Creating new Backup "' . $destination . '"' + ]); + $messager && $messager([ + 'type' => 'message', + 'level' => 'info', + 'message' => '' + ]); + + $zip = new \ZipArchive(); + $zip->open($destination, \ZipArchive::CREATE); + + static::folderToZip(GRAV_ROOT, $zip, strlen(rtrim(GRAV_ROOT, DS) . DS), $messager); + + $messager && $messager([ + 'type' => 'progress', + 'percentage' => false, + 'complete' => true + ]); + + $messager && $messager([ + 'type' => 'message', + 'level' => 'info', + 'message' => '' + ]); + $messager && $messager([ + 'type' => 'message', + 'level' => 'info', + 'message' => 'Saving and compressing archive...' + ]); + + $zip->close(); + + return $destination; + } + + /** + * @param $folder + * @param $zipFile + * @param $exclusiveLength + * @param $messager + */ + private static function folderToZip($folder, \ZipArchive &$zipFile, $exclusiveLength, callable $messager = null) + { + $handle = opendir($folder); + while (false !== $f = readdir($handle)) { + if ($f != '.' && $f != '..') { + $filePath = "$folder/$f"; + // Remove prefix from file path before add to zip. + $localPath = substr($filePath, $exclusiveLength); + + if (in_array($f, static::$ignoreFolders) || in_array($localPath, static::$ignorePaths)) { + continue; + } + + if (is_file($filePath)) { + $zipFile->addFile($filePath, $localPath); + + $messager && $messager([ + 'type' => 'progress', + 'percentage' => false, + 'complete' => false + ]); + } elseif (is_dir($filePath)) { + // Add sub-directory. + $zipFile->addEmptyDir($localPath); + static::folderToZip($filePath, $zipFile, $exclusiveLength, $messager); + } + } + } + closedir($handle); + } +} diff --git a/system/src/Grav/Common/Config/Config.php b/system/src/Grav/Common/Config/Config.php index 1fe373a40..60f443108 100644 --- a/system/src/Grav/Common/Config/Config.php +++ b/system/src/Grav/Common/Config/Config.php @@ -72,6 +72,12 @@ class Config extends Data 'prefixes' => [ '' => ['logs'] ] + ], + 'backup' => [ + 'type' => 'Stream', + 'prefixes' => [ + '' => ['backup'] + ] ] ]; diff --git a/system/src/Grav/Console/Cli/BackupCommand.php b/system/src/Grav/Console/Cli/BackupCommand.php index 050301762..46590bf39 100644 --- a/system/src/Grav/Console/Cli/BackupCommand.php +++ b/system/src/Grav/Console/Cli/BackupCommand.php @@ -7,6 +7,7 @@ use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Grav\Common\Backup\ZipBackup; /** * Class BackupCommand @@ -23,6 +24,10 @@ class BackupCommand extends Command * @var */ protected $progress; + /** + * @var + */ + protected $output; /** * @@ -52,6 +57,8 @@ class BackupCommand extends Command */ protected function execute(InputInterface $input, OutputInterface $output) { + $this->output = $output; + $output->getFormatter()->setStyle('red', new OutputFormatterStyle('red')); $output->getFormatter()->setStyle('cyan', new OutputFormatterStyle('cyan')); $output->getFormatter()->setStyle('green', new OutputFormatterStyle('green')); @@ -60,25 +67,10 @@ class BackupCommand extends Command $this->progress = new ProgressBar($output); $this->progress->setFormat('Archiving %current% files [%bar%] %elapsed:6s% %memory:6s%'); - $name = basename($this->source); - $dir = dirname($this->source); - $date = date('YmdHis', time()); - $filename = $name . '-' . $date . '.zip'; - $destination = ($input->getArgument('destination')) ? $input->getArgument('destination') : ROOT_DIR; - $destination = rtrim($destination, DS) . DS . $filename; - $output->writeln(''); - $output->writeln('Creating new Backup "' . $destination . '"'); - $this->progress->start(); + ZipBackup::backup($destination, [$this, 'output']); - $zip = new \ZipArchive(); - $zip->open($destination, \ZipArchive::CREATE); - $zip->addEmptyDir($name); - - $this->folderToZip($this->source, $zip, strlen($dir . DS), $this->progress); - $zip->close(); - $this->progress->finish(); $output->writeln(''); $output->writeln(''); @@ -90,25 +82,20 @@ class BackupCommand extends Command * @param $exclusiveLength * @param $progress */ - private static function folderToZip($folder, \ZipArchive &$zipFile, $exclusiveLength, ProgressBar $progress) + public function output($args) { - $handle = opendir($folder); - while (false !== $f = readdir($handle)) { - if ($f != '.' && $f != '..') { - $filePath = "$folder/$f"; - // Remove prefix from file path before add to zip. - $localPath = substr($filePath, $exclusiveLength); - if (is_file($filePath)) { - $zipFile->addFile($filePath, $localPath); - $progress->advance(); - } elseif (is_dir($filePath)) { - // Add sub-directory. - $zipFile->addEmptyDir($localPath); - self::folderToZip($filePath, $zipFile, $exclusiveLength, $progress); + switch ($args['type']) { + case 'message': + $this->output->writeln($args['message']); + break; + case 'progress': + if ($args['complete']) { + $this->progress->finish(); + } else { + $this->progress->advance(); } - } + break; } - closedir($handle); } } diff --git a/system/src/Grav/Console/Cli/SandboxCommand.php b/system/src/Grav/Console/Cli/SandboxCommand.php index d57c25e8b..43cfc230c 100644 --- a/system/src/Grav/Console/Cli/SandboxCommand.php +++ b/system/src/Grav/Console/Cli/SandboxCommand.php @@ -19,6 +19,7 @@ class SandboxCommand extends Command * @var array */ protected $directories = array( + '/backup', '/cache', '/logs', '/images', diff --git a/web.config b/web.config index 85d2c1b1b..c3dd31424 100644 --- a/web.config +++ b/web.config @@ -1,51 +1,55 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +