diff --git a/Configuration/EasyBackupConfiguration.php b/Configuration/EasyBackupConfiguration.php index f59ce11..872e4ef 100755 --- a/Configuration/EasyBackupConfiguration.php +++ b/Configuration/EasyBackupConfiguration.php @@ -30,4 +30,9 @@ public function getBackupDir(): string { return (string) $this->find('setting_backup_dir'); } + + public function getPathsToBeBackuped(): string + { + return (string) $this->find('setting_paths_to_backup'); + } } diff --git a/Controller/EasyBackupController.php b/Controller/EasyBackupController.php index 011ee45..bb67084 100755 --- a/Controller/EasyBackupController.php +++ b/Controller/EasyBackupController.php @@ -55,7 +55,7 @@ final class EasyBackupController extends AbstractController public function __construct(string $dataDirectory, EasyBackupConfiguration $configuration) { - $this->kimaiRootPath = dirname(dirname($dataDirectory)) . '/'; + $this->kimaiRootPath = dirname(dirname($dataDirectory)).DIRECTORY_SEPARATOR; $this->configuration = $configuration; $this->dbUrl = $_ENV['DATABASE_URL']; $this->filesystem = new Filesystem(); @@ -63,7 +63,7 @@ public function __construct(string $dataDirectory, EasyBackupConfiguration $conf private function getBackupDirectory(): string { - return $this->kimaiRootPath . $this->configuration->getBackupDir(); + return $this->kimaiRootPath.$this->configuration->getBackupDir(); } /** @@ -83,8 +83,11 @@ public function indexAction(): Response $filesAndDirs = array_diff($files, ['.', '..', self::GITIGNORE_NAME]); foreach ($filesAndDirs as $fileOrDir) { - if (is_file($backupDir . $fileOrDir)) { - $filesizeInMb = round(filesize($backupDir . $fileOrDir) / 1048576, 2); + /* Make sure that only files are listet which match our wanted regex */ + + if (is_file($backupDir.$fileOrDir) + && preg_match(self::REGEX_BACKUP_ZIP_NAME, $fileOrDir) == 1) { + $filesizeInMb = round(filesize($backupDir.$fileOrDir) / 1048576, 2); $existingBackups[$fileOrDir] = $filesizeInMb; } } @@ -107,7 +110,7 @@ public function createBackupAction(): Response $backupName = date(self::BACKUP_NAME_DATE_FORMAT); $backupDir = $this->getBackupDirectory(); - $pluginBackupDir = $backupDir . $backupName . '/'; + $pluginBackupDir = $backupDir.$backupName.DIRECTORY_SEPARATOR; // Create the backup folder @@ -115,7 +118,7 @@ public function createBackupAction(): Response // If not yet existing, create a .gitignore to exclude the backup files. - $gitignoreFullPath = $backupDir . self::GITIGNORE_NAME; + $gitignoreFullPath = $backupDir.self::GITIGNORE_NAME; if (!$this->filesystem->exists($gitignoreFullPath)) { $this->filesystem->touch($gitignoreFullPath); @@ -124,7 +127,7 @@ public function createBackupAction(): Response // Save the specific kimai version and git head - $readMeFile = $pluginBackupDir . self::README_FILENAME; + $readMeFile = $pluginBackupDir.self::README_FILENAME; $this->filesystem->touch($readMeFile); $manifest = [ 'git' => 'not available', @@ -141,27 +144,11 @@ public function createBackupAction(): Response // Backing up files and directories - $arrayOfPathsToBackup = [ - '.env', - 'config/packages/local.yaml', - 'var/data/', - 'var/plugins/', - 'templates/invoice', - ]; - - // Per default %kimai.invoice.documents% is: - // var/plugins/DemoBundle/Resources/invoices/ - // var/invoices/ - // templates/invoice/renderer/ - - $arrayOfPathsToBackup = array_merge( - $arrayOfPathsToBackup, - $this->getParameter('kimai.invoice.documents') - ); + $arrayOfPathsToBackup = explode(PHP_EOL, $this->configuration->getPathsToBeBackuped()); foreach ($arrayOfPathsToBackup as $filename) { - $sourceFile = $this->kimaiRootPath . $filename; - $targetFile = $pluginBackupDir . $filename; + $sourceFile = $this->kimaiRootPath.$filename; + $targetFile = $pluginBackupDir.$filename; if ($this->filesystem->exists($sourceFile)) { if (is_dir($sourceFile)) { @@ -171,13 +158,15 @@ public function createBackupAction(): Response if (is_file($sourceFile)) { $this->filesystem->copy($sourceFile, $targetFile); } + + // Todo: Add error messages } } - $sqlDumpName = $pluginBackupDir . self::SQL_DUMP_FILENAME; + $sqlDumpName = $pluginBackupDir.self::SQL_DUMP_FILENAME; $this->backupDatabase($sqlDumpName); - $backupZipName = $backupDir . $backupName . '.zip'; + $backupZipName = $backupDir.$backupName.'.zip'; $this->zipData($pluginBackupDir, $backupZipName); @@ -194,7 +183,7 @@ public function createBackupAction(): Response /** * @Route(path="/download", name="download", methods={"GET"}) - * @param Request $request + * * @return Response */ public function downloadAction(Request $request): Response @@ -204,7 +193,7 @@ public function downloadAction(Request $request): Response // Validate the given user input (filename) if (preg_match(self::REGEX_BACKUP_ZIP_NAME, $backupName)) { - $zipNameAbsolute = $this->getBackupDirectory() . $backupName; + $zipNameAbsolute = $this->getBackupDirectory().$backupName; if ($this->filesystem->exists($zipNameAbsolute)) { $response = new Response(file_get_contents($zipNameAbsolute)); @@ -225,7 +214,7 @@ public function downloadAction(Request $request): Response /** * @Route(path="/delete", name="delete", methods={"GET"}) - * @param Request $request + * * @return \Symfony\Component\HttpFoundation\Response */ public function deleteAction(Request $request) @@ -235,7 +224,7 @@ public function deleteAction(Request $request) // Validate the given user input (filename) if (preg_match(self::REGEX_BACKUP_ZIP_NAME, $dirname)) { - $path = $this->getBackupDirectory() . $dirname; + $path = $this->getBackupDirectory().$dirname; if ($this->filesystem->exists($path)) { $this->filesystem->remove($path); @@ -285,7 +274,7 @@ private function backupDatabase(string $sqlDumpName) $this->filesystem->touch($sqlDumpName); foreach ($outputArr as $line) { - $this->filesystem->appendToFile($sqlDumpName, $line . "\n"); + $this->filesystem->appendToFile($sqlDumpName, $line."\n"); } } } @@ -302,17 +291,17 @@ private function zipData($source, $destination) $files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source), \RecursiveIteratorIterator::SELF_FIRST); foreach ($files as $file) { - // Ignore "." and ".." folders - if( in_array(substr($file, strrpos($file, '/')+1), array('.', '..')) ) + if (in_array(substr($file, strrpos($file, DIRECTORY_SEPARATOR) + 1), ['.', '..'])) { continue; + } $file = realpath($file); + if (is_dir($file) === true) { - $zip->addEmptyDir(str_replace($source . '/', '', $file . '/')); - + $zip->addEmptyDir(str_replace($source.DIRECTORY_SEPARATOR, '', $file.DIRECTORY_SEPARATOR)); } elseif (is_file($file) === true) { - $zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file)); + $zip->addFromString(str_replace($source.DIRECTORY_SEPARATOR, '', $file), file_get_contents($file)); } } } elseif (is_file($source) === true) { @@ -337,7 +326,7 @@ private function checkStatus() { $status = []; - $path = $this->kimaiRootPath . 'var'; + $path = $this->kimaiRootPath.'var'; $status["Path '$path' readable"] = is_readable($path); $status["Path '$path' writable"] = is_writable($path); $status["PHP extension 'zip' loaded"] = extension_loaded('zip'); @@ -347,7 +336,7 @@ private function checkStatus() $status[$cmd] = exec($cmd); $cmd = $this->configuration->getMysqlDumpCommand(); - $cmd = explode(' ', $cmd)[0] . ' --version'; + $cmd = explode(' ', $cmd)[0].' --version'; $status[$cmd] = exec($cmd); return $status; @@ -356,9 +345,9 @@ private function checkStatus() private function getKimaiVersion(bool $full = false): string { if ($full) { - return Constants::SOFTWARE . ' - ' . Constants::VERSION . ' ' . Constants::STATUS; + return Constants::SOFTWARE.' - '.Constants::VERSION.' '.Constants::STATUS; } - return Constants::VERSION . ' ' . Constants::STATUS; + return Constants::VERSION.' '.Constants::STATUS; } } diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 51134de..7dbf466 100755 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -24,6 +24,17 @@ public function getConfigTreeBuilder() /** @var ArrayNodeDefinition $rootNode */ $rootNode = $treeBuilder->getRootNode(); + $arrayOfPathsToBackup = [ + '.env', + 'config/packages/local.yaml', + 'var/data/', + 'var/plugins/', + 'var/invoices/', + 'templates/invoice/', + 'var/export/', + 'templates/export/', + ]; + $rootNode ->addDefaultsIfNotSet() ->children() @@ -33,6 +44,9 @@ public function getConfigTreeBuilder() ->scalarNode('setting_backup_dir') ->defaultValue('var/easy_backup/') ->end() + ->scalarNode('setting_paths_to_backup') + ->defaultValue(implode(PHP_EOL, $arrayOfPathsToBackup)) + ->end() ->end() ->end(); diff --git a/EventSubscriber/SystemConfigurationSubscriber.php b/EventSubscriber/SystemConfigurationSubscriber.php index dbe0a78..2a6a4ed 100755 --- a/EventSubscriber/SystemConfigurationSubscriber.php +++ b/EventSubscriber/SystemConfigurationSubscriber.php @@ -13,6 +13,7 @@ use App\Form\Model\Configuration; use App\Form\Model\SystemConfiguration as SystemConfigurationModel; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\Extension\Core\Type\TextType; class SystemConfigurationSubscriber implements EventSubscriberInterface @@ -42,6 +43,12 @@ public function onSystemConfiguration(SystemConfigurationEvent $event) ->setTranslationDomain('system-configuration') ->setRequired(false) ->setType(TextType::class), + (new Configuration()) + ->setName('easy_backup.setting_paths_to_backup') + ->setLabel('easy_backup.setting_paths_to_backup') + ->setTranslationDomain('system-configuration') + ->setRequired(false) + ->setType(TextareaType::class), ]) ); } diff --git a/Readme.md b/Readme.md index 0aa15d5..71605ee 100755 --- a/Readme.md +++ b/Readme.md @@ -57,15 +57,21 @@ Make sure its writable by your webserver! We don't use the recommended ### What files are backed up? -Currently backuped directories and files are: +Currently per default backuped directories (incl. sub directories) and files are: ``` .env config/packages/local.yaml var/data/ var/plugins/ -%kimai.invoice.documents% +var/invoices/ +templates/invoice +var/export/ +templates/export/ ``` +You are free to edit this list via the Kimai settings page. Place each filename or paths in a seperate line. Make sure that there are no empty lines. Root path is your kimai installation path. + +![Update the paths to your needs](https://github.com/mxgross/EasyBackupBundle/blob/dev/screenshot_files_and_paths_to_be_backed_up.JPG?raw=true) According to the [backup docu](https://www.kimai.org/documentation/backups.html) the Kimai version should be saved to. Also the current git head. @@ -83,6 +89,12 @@ Per default it is ``` /usr/bin/mysqldump --user={user} --password={password} --host={host} --port={port} --single-transaction --force {database} ``` + +On a windows system with XAMPP as webserver the command could look like this +``` +C:\xampp\mysql\bin\mysqldump --user={user} --password={password} --host={host} --port={port} --single-transaction --force {database} +``` + You can remove or add parameters here if you need to. The variables in the curly braces will be replaced during the execution of the backup. All information for these variables are gathered from the DATABASE_URL defined in the .env file. ``` # DATABASE_URL=mysql://user:password@host:port/database @@ -101,5 +113,10 @@ By default, this are assigned to all users with the role `ROLE_SUPER_ADMIN`. **Please adjust the permission settings in your user administration.** ## Restore -Currently the plugin has no automation to restore a backup. You have to do it by hand. Just copy the backuped directories and files into your Kimai2 installation. Some hints can be found here: [Official Kimai2 backup and restore docu](https://www.kimai.org/documentation/backups.html) +Currently the plugin has no automation to restore a backup per click. You have to do it by hand. Just copy the backuped directories and files into your Kimai2 installation. Some hints can be found here: [Official Kimai2 backup and restore docu](https://www.kimai.org/documentation/backups.html) +If you are using a mysql/mariadb database you can import the backuped .sql file with tools like phpMyAdmin or by the command +``` +mysql -u username -p database_name < file.sql +``` +For additional information please lookup the mysql commands documentation. diff --git a/Resources/translations/system-configuration.de.xliff b/Resources/translations/system-configuration.de.xliff index 287c5a5..bf581f0 100755 --- a/Resources/translations/system-configuration.de.xliff +++ b/Resources/translations/system-configuration.de.xliff @@ -14,6 +14,10 @@ label.easy_backup.setting_backup_dir Backup Verzeichnis + + label.easy_backup.setting_paths_to_backup + Zu sichernde Dateien und Pfade + diff --git a/Resources/translations/system-configuration.en.xliff b/Resources/translations/system-configuration.en.xliff index 32f9251..6b67e64 100755 --- a/Resources/translations/system-configuration.en.xliff +++ b/Resources/translations/system-configuration.en.xliff @@ -14,6 +14,10 @@ label.easy_backup.setting_backup_dir Backup directory + + label.easy_backup.setting_paths_to_backup + Files and paths to backup + diff --git a/screenshot_files_and_paths_to_be_backed_up.JPG b/screenshot_files_and_paths_to_be_backed_up.JPG new file mode 100644 index 0000000..f5846e5 Binary files /dev/null and b/screenshot_files_and_paths_to_be_backed_up.JPG differ