From 6d21fe7f5bba6cbeb4cc9383ad9c235b3087ac9c Mon Sep 17 00:00:00 2001 From: SamBrishes Date: Thu, 14 May 2020 17:35:46 +0200 Subject: [PATCH] Add Backup Features Backup Features as described in #605 and #1137 --- bl-plugins/backup/js/backup.js | 33 +++++ bl-plugins/backup/languages/de_AT.json | 25 ++++ bl-plugins/backup/languages/de_CH.json | 15 ++- bl-plugins/backup/languages/de_DE.json | 15 ++- bl-plugins/backup/languages/en.json | 17 ++- bl-plugins/backup/plugin.php | 162 ++++++++++++++++++++++--- 6 files changed, 247 insertions(+), 20 deletions(-) create mode 100644 bl-plugins/backup/js/backup.js create mode 100644 bl-plugins/backup/languages/de_AT.json diff --git a/bl-plugins/backup/js/backup.js b/bl-plugins/backup/js/backup.js new file mode 100644 index 00000000..acc444b4 --- /dev/null +++ b/bl-plugins/backup/js/backup.js @@ -0,0 +1,33 @@ +jQuery(document).ready(function($) { + $('#backupFile').change(function() { + var file = this.files.length >= 1? this.files[0]: null; + + // Build Form Data + var url = $('#jsform').attr("action") || window.location.href; + var form = new FormData(); + form.append("tokenCSRF", $('[name="tokenCSRF"]').val()); + form.append("backupFile", file); + + // Apply Form + $.ajax({ + url: url, + data: form, + type: "POST", + dataType: "json", + mimeType: "multipart/form-data", + contentType: false, + processData: false, + error: function(jqXHR, status, error) { + var data = jqXHR.responseJSON; + console.log(error); + var alert = $("
").addClass("alert alert-danger").text(data.message); + + $("#jsform .alert:not(.alert-primary)").remove(); + $("#jstokenCSRF").after(alert); + }, + success: function(data, status, jqXHR) { + window.location.reload(); + } + }); + }); +}); diff --git a/bl-plugins/backup/languages/de_AT.json b/bl-plugins/backup/languages/de_AT.json new file mode 100644 index 00000000..131fe034 --- /dev/null +++ b/bl-plugins/backup/languages/de_AT.json @@ -0,0 +1,25 @@ +{ + "plugin-data": + { + "name": "Backup", + "description": "Einfach Backups erstellen und wieder einspielen." + }, + "create-backup": "Backup erstellen", + "upload-backup": "Backup hochladen", + "download": "Backup herunterladen", + "restore-backup": "Backup wiederherstellen", + "delete-backup": "Backup löschen", + "there-are-no-backups-for-the-moment": "Es gibt keine Backups.", + "you-do-not-have-sufficient-permissions": "Du hast nicht die notwendigen Berechtigungen", + "the-backup-was-created-successfully": "Das Backup wurde erfolgreich erstellt.", + "the-backup-file-could-not-be-created": "Das Backup konnte nicht erstellt werden.", + "the-backup-'%s'-could-be-restored-successfully": "Das Backup '%s' konnte erfolgreich wiederhergestellt werden.", + "the-backup-'%s'-could-not-be-restored": "Das Backup '%s' konnte nicht wiederhergestellt werden.", + "the-backup-'%s'-could-be-deleted-successfully": "Das Backup '%s' konnte erfolgreich gelöscht werden.", + "the-backup-'%s'-could-not-be-deleted": "Das Backup '%s' konnte nicht gelöscht werden.", + "the-passed-file-is-not-a-valid-zip-archive": "Die angegebene Datei ist kein gültiges ZIP Archiv.", + "the-passed-file-does-not-end-with-zip": "Die angegebene Datei endet nicht mit .zip.", + "the-passed-file-could-not-be-validated": "Die angegebene Datei konnte nicht geprüft werden.", + "the-passed-file-is-not-a-valid-backup-archive": "Die angegebene Datei ist kein gültiges Backup Archiv.", + "the-backup-file-could-be-uploaded-successfully": "Die Backup Datei konnte erfolgreich hochgeladen werden." +} diff --git a/bl-plugins/backup/languages/de_CH.json b/bl-plugins/backup/languages/de_CH.json index 850588ba..131fe034 100644 --- a/bl-plugins/backup/languages/de_CH.json +++ b/bl-plugins/backup/languages/de_CH.json @@ -5,8 +5,21 @@ "description": "Einfach Backups erstellen und wieder einspielen." }, "create-backup": "Backup erstellen", + "upload-backup": "Backup hochladen", "download": "Backup herunterladen", "restore-backup": "Backup wiederherstellen", "delete-backup": "Backup löschen", - "there-are-no-backups-for-the-moment": "Es gibt keine Backups." + "there-are-no-backups-for-the-moment": "Es gibt keine Backups.", + "you-do-not-have-sufficient-permissions": "Du hast nicht die notwendigen Berechtigungen", + "the-backup-was-created-successfully": "Das Backup wurde erfolgreich erstellt.", + "the-backup-file-could-not-be-created": "Das Backup konnte nicht erstellt werden.", + "the-backup-'%s'-could-be-restored-successfully": "Das Backup '%s' konnte erfolgreich wiederhergestellt werden.", + "the-backup-'%s'-could-not-be-restored": "Das Backup '%s' konnte nicht wiederhergestellt werden.", + "the-backup-'%s'-could-be-deleted-successfully": "Das Backup '%s' konnte erfolgreich gelöscht werden.", + "the-backup-'%s'-could-not-be-deleted": "Das Backup '%s' konnte nicht gelöscht werden.", + "the-passed-file-is-not-a-valid-zip-archive": "Die angegebene Datei ist kein gültiges ZIP Archiv.", + "the-passed-file-does-not-end-with-zip": "Die angegebene Datei endet nicht mit .zip.", + "the-passed-file-could-not-be-validated": "Die angegebene Datei konnte nicht geprüft werden.", + "the-passed-file-is-not-a-valid-backup-archive": "Die angegebene Datei ist kein gültiges Backup Archiv.", + "the-backup-file-could-be-uploaded-successfully": "Die Backup Datei konnte erfolgreich hochgeladen werden." } diff --git a/bl-plugins/backup/languages/de_DE.json b/bl-plugins/backup/languages/de_DE.json index 850588ba..131fe034 100644 --- a/bl-plugins/backup/languages/de_DE.json +++ b/bl-plugins/backup/languages/de_DE.json @@ -5,8 +5,21 @@ "description": "Einfach Backups erstellen und wieder einspielen." }, "create-backup": "Backup erstellen", + "upload-backup": "Backup hochladen", "download": "Backup herunterladen", "restore-backup": "Backup wiederherstellen", "delete-backup": "Backup löschen", - "there-are-no-backups-for-the-moment": "Es gibt keine Backups." + "there-are-no-backups-for-the-moment": "Es gibt keine Backups.", + "you-do-not-have-sufficient-permissions": "Du hast nicht die notwendigen Berechtigungen", + "the-backup-was-created-successfully": "Das Backup wurde erfolgreich erstellt.", + "the-backup-file-could-not-be-created": "Das Backup konnte nicht erstellt werden.", + "the-backup-'%s'-could-be-restored-successfully": "Das Backup '%s' konnte erfolgreich wiederhergestellt werden.", + "the-backup-'%s'-could-not-be-restored": "Das Backup '%s' konnte nicht wiederhergestellt werden.", + "the-backup-'%s'-could-be-deleted-successfully": "Das Backup '%s' konnte erfolgreich gelöscht werden.", + "the-backup-'%s'-could-not-be-deleted": "Das Backup '%s' konnte nicht gelöscht werden.", + "the-passed-file-is-not-a-valid-zip-archive": "Die angegebene Datei ist kein gültiges ZIP Archiv.", + "the-passed-file-does-not-end-with-zip": "Die angegebene Datei endet nicht mit .zip.", + "the-passed-file-could-not-be-validated": "Die angegebene Datei konnte nicht geprüft werden.", + "the-passed-file-is-not-a-valid-backup-archive": "Die angegebene Datei ist kein gültiges Backup Archiv.", + "the-backup-file-could-be-uploaded-successfully": "Die Backup Datei konnte erfolgreich hochgeladen werden." } diff --git a/bl-plugins/backup/languages/en.json b/bl-plugins/backup/languages/en.json index a9f5a578..066efa24 100644 --- a/bl-plugins/backup/languages/en.json +++ b/bl-plugins/backup/languages/en.json @@ -5,8 +5,21 @@ "description": "The simple way to backup your Bludit." }, "create-backup": "Create Backup", + "upload-backup": "Upload Backup", "download": "Download", "restore-backup": "Restore Backup", "delete-backup": "Delete Backup", - "there-are-no-backups-for-the-moment": "There are no backups for the moment" -} \ No newline at end of file + "there-are-no-backups-for-the-moment": "There are no backups for the moment", + "you-do-not-have-sufficient-permissions": "You do not have sufficient permissions", + "the-backup-was-created-successfully": "The Backup was created successfully.", + "the-backup-file-could-not-be-created": "The Backup file could not be created.", + "the-backup-'%s'-could-be-restored-successfully": "The Backup '%s' could be restored successfully.", + "the-backup-'%s'-could-not-be-restored": "The Backup '%s' could not be restored.", + "the-backup-'%s'-could-be-deleted-successfully": "The Backup '%s' could be deleted successfully.", + "the-backup-'%s'-could-not-be-deleted": "The Backup '%s' could not be deleted.", + "the-passed-file-is-not-a-valid-zip-archive": "The passed file is not a valid ZIP Archive.", + "the-passed-file-does-not-end-with-zip": "The passed file does not ent with .zip.", + "the-passed-file-could-not-be-validated": "The passed file could not be validated.", + "the-passed-file-is-not-a-valid-backup-archive": "The passed file is not a valid backup archive.", + "the-backup-file-could-be-uploaded-successfully": "The backup file could be uploaded successfully." +} diff --git a/bl-plugins/backup/plugin.php b/bl-plugins/backup/plugin.php index 0d6f7064..4eb080a8 100644 --- a/bl-plugins/backup/plugin.php +++ b/bl-plugins/backup/plugin.php @@ -12,6 +12,12 @@ class pluginBackup extends Plugin { // This variable define if the extension zip is loaded private $zip = false; + // The last request status + private $last_status = null; + + // The last request message + private $last_message = null; + public function init() { // Disable default form buttons (Save and Cancel) @@ -19,6 +25,37 @@ class pluginBackup extends Plugin { // Check for zip extension installed $this->zip = extension_loaded('zip'); + + // Get Last Message + if (empty($_POST) && !empty(Session::get("BACKUP-MESSAGE"))) { + $this->last_status = Session::get("BACKUP-STATUS"); + $this->last_message = Session::get("BACKUP-MESSAGE"); + unset($_SESSION["s_BACKUP-STATUS"]); + unset($_SESSION["s_BACKUP-MESSAGE"]); + } + } + + protected function response($status, $message) + { + // Return JSON object for AJAX requests + if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strcasecmp($_SERVER['HTTP_X_REQUESTED_WITH'], "xmlhttprequest") === 0) { + $http = array( + 200 => "200 OK", + 400 => "400 Bad Request", + 415 => "415 Unsupported Media Type" + ); + header("HTTP/1.1 " . $http[$status]); + print(json_encode(array( + "status" => $status < 400, + "message" => $message + ))); + die(); + } + + // Store in Session + Session::set("BACKUP-STATUS", $status < 400); + Session::set("BACKUP-MESSAGE", $message); + return $status < 400; } public function post() @@ -31,7 +68,24 @@ class pluginBackup extends Plugin { return $this->deleteBackup($_POST['deleteBackup']); } - return true; + if (isset($_FILES['backupFile'])) { + return $this->uploadBackup($_FILES['backupFile']); + } + + return false; + } + + public function adminHead() + { + global $url; + + if ($url->slug() !== "configure-plugin/pluginBackup") { + return false; + } + + $html = $this->includeJS('backup.js'); + + return $html; } public function adminSidebar() @@ -55,26 +109,42 @@ class pluginBackup extends Plugin { if (empty($backups)) { $html .= ''; + $html .= ''; } - $html .= '
'; + if($this->last_status !== null) { + $html .= ''; + } + + $html .= '
'; + $html .= '
'; $html .= ''; $html .= '
'; + $html .= '
'; + if ($this->zip) { + $html .= ''; + $html .= ''; + } + $html .= '
'; + $html .= '
'; $html .= '
'; foreach ($backups as $backup) { $filename = pathinfo($backup,PATHINFO_FILENAME); $basename = pathinfo($backup,PATHINFO_BASENAME); + list($name, $count) = array_pad(explode(".", $filename, 2), 2, 0); + $html .= '
'; - $html .= '

'.Date::format($filename, BACKUP_DATE_FORMAT, 'F j, Y, g:i a').'

'; + $html .= '

'.Date::format($name, BACKUP_DATE_FORMAT, 'F j, Y, g:i a').($count > 0? " ($count)": "").'

'; // Allow download if a zip file if ($this->zip) { $html .= ' '.$L->get('download').''; } $html .= ''; - $html .= ''; + $html .= ''; $html .= '
'; $html .= '
'; } @@ -116,6 +186,8 @@ class pluginBackup extends Plugin { public function createBackup() { + global $L; + // Current backup directory $currentDate = Date::current(BACKUP_DATE_FORMAT); $backupDir = $this->workspace().$currentDate; @@ -134,38 +206,96 @@ class pluginBackup extends Plugin { } } - return true; + if (file_exists($backupDir.'.zip')) { + return $this->response(200, $L->get("The Backup was created successfully.")); + } + + return $this->response(400, $L->get("The Backup file could not be created.")); } public function restoreBackup($filename) { + global $L; + // Remove current files foreach ($this->directoriesToBackup as $dir) { Filesystem::deleteRecursive($dir); } // Recover backuped files - // Zip format if ($this->zip) { + // Zip format $tmp = $this->workspace().$filename.'.zip'; - return Filesystem::unzip($tmp, PATH_CONTENT); + $status = Filesystem::unzip($tmp, PATH_CONTENT); + } else { + // Directory format + $tmp = $this->workspace().$filename; + $status = Filesystem::copyRecursive($tmp, PATH_CONTENT); } - // Directory format - $tmp = $this->workspace().$filename; - return Filesystem::copyRecursive($tmp, PATH_CONTENT); + if ($status) { + return $this->response(200, sprintf($L->get("The Backup '%s' could be restored successfully."), $filename)); + } + return $this->response(400, sprintf($L->get("The Backup '%s' could not be restored."), $filename)); } public function deleteBackup($filename) { - // Zip format + global $L; + if ($this->zip) { + // Zip format $tmp = $this->workspace().$filename.'.zip'; - return Filesystem::rmfile($tmp); + $status = Filesystem::rmfile($tmp); + } else { + // Directory format + $tmp = $this->workspace().$filename; + $status = Filesystem::deleteRecursive($tmp); } - // Directory format - $tmp = $this->workspace().$filename; - return Filesystem::deleteRecursive($tmp); + if ($status) { + return $this->response(200, sprintf($L->get("The Backup '%s' could be deleted successfully."), $filename)); + } + return $this->response(400, sprintf($L->get("The Backup '%s' could not be deleted."), $filename)); + } + + public function uploadBackup($backup) + { + global $L; + + // Check File Type + if ($backup["type"] !== "application/zip" && $backup["type"] !== "application/x-zip-compressed") { + return $this->response(415, $L->get("The passed file is not a valid ZIP Archive.")); + } + + // Check File Extension + if (stripos($backup["name"], ".zip") !== (strlen($backup["name"]) - 4)) { + return $this->response(415, $L->get("The passed file does not end with .zip.")); + } + + // Check ZIP extension + if(!$this->zip) { + return $this->response(400, $L->get("The passed file could not be validated.")); + } + + // Validate ZIP File + $zip = new ZipArchive(); + $zip->open($backup["tmp_name"]); + if($zip->addEmptyDir("databases") || $zip->addEmptyDir("pages") || $zip->addEmptyDir("uploads")) { + $zip->close(); + return $this->response(415, $L->get("The passed file is not a valid backup archive.")); + } + $zip->close(); + + // File Name + $name = $backup["name"]; + $count = 0; + while (file_exists($this->workspace() . $name)) { + $name = substr($backup["name"], 0, -4) . "." . ++$count . ".zip"; + } + + // Move File to Backup Directory + Filesystem::mv($backup["tmp_name"], $this->workspace() . $name); + return $this->response(200, $L->get("The backup file could be uploaded successfully.")); } }