Merge pull request #185 from getgrav/feature/common_backup

Feature/common backup
This commit is contained in:
Gertt
2015-04-28 23:49:51 +02:00
9 changed files with 210 additions and 85 deletions

2
.gitignore vendored
View File

@@ -7,6 +7,8 @@ vendor/
.sass-cache
# Grav Specific
backup/*
!backup/.*
cache/*
!cache/.*
assets/*

View File

@@ -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

0
backup/.gitkeep Normal file
View File

View File

@@ -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;
}

View File

@@ -0,0 +1,121 @@
<?php
namespace Grav\Common\Backup;
use Grav\Common\GravTrait;
use Grav\Common\Filesystem\Folder;
/**
* The ZipBackup class lets you create simple zip-backups of a grav site
*
* @author RocketTheme
* @license MIT
*/
class ZipBackup
{
use GravTrait;
protected static $ignorePaths = [
'backup',
'cache',
'images',
'logs'
];
protected static $ignoreFolders = [
'.git'
];
public static function backup($destination = null, callable $messager = null)
{
if (!$destination) {
$destination = self::getGrav()['locator']->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);
}
}

View File

@@ -72,6 +72,12 @@ class Config extends Data
'prefixes' => [
'' => ['logs']
]
],
'backup' => [
'type' => 'Stream',
'prefixes' => [
'' => ['backup']
]
]
];

View File

@@ -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 <cyan>%current%</cyan> files [<green>%bar%</green>] %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);
}
}

View File

@@ -19,6 +19,7 @@ class SandboxCommand extends Command
* @var array
*/
protected $directories = array(
'/backup',
'/cache',
'/logs',
'/images',

View File

@@ -1,51 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<defaultDocument>
<files>
<remove value="index.php" />
<add value="index.php" />
</files>
</defaultDocument>
<rewrite>
<rules>
<rule name="request_filename" stopProcessing="true">
<match url="." ignoreCase="false" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
</conditions>
<action type="Rewrite" url="index.php" />
</rule>
<rule name="user_accounts" stopProcessing="true">
<match url="^user/accounts/(.*)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="user_config" stopProcessing="true">
<match url="^user/config/(.*)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="user_error_redirect" stopProcessing="true">
<match url="^user/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="cache" stopProcessing="true">
<match url="^cache/(.*)" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="bin" stopProcessing="true">
<match url="^bin/(.*)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="system" stopProcessing="true">
<match url="^system/(.*)\.(txt|md|html|yaml|php|twig|sh|bat)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="vendor" stopProcessing="true">
<match url="^vendor/(.*)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<defaultDocument>
<files>
<remove value="index.php" />
<add value="index.php" />
</files>
</defaultDocument>
<rewrite>
<rules>
<rule name="request_filename" stopProcessing="true">
<match url="." ignoreCase="false" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
</conditions>
<action type="Rewrite" url="index.php" />
</rule>
<rule name="user_accounts" stopProcessing="true">
<match url="^user/accounts/(.*)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="user_config" stopProcessing="true">
<match url="^user/config/(.*)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="user_error_redirect" stopProcessing="true">
<match url="^user/(.*)\.(txt|md|html|php|yaml|json|twig|sh|bat)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="cache" stopProcessing="true">
<match url="^cache/(.*)" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="bin" stopProcessing="true">
<match url="^bin/(.*)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="backup" stopProcessing="true">
<match url="^backup/(.*)" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="system" stopProcessing="true">
<match url="^system/(.*)\.(txt|md|html|yaml|php|twig|sh|bat)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
<rule name="vendor" stopProcessing="true">
<match url="^vendor/(.*)$" ignoreCase="false" />
<action type="Redirect" url="error" redirectType="Permanent" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>