diff --git a/.tmp/.gitignore b/.tmp/.gitignore new file mode 100644 index 000000000..a3a0c8b5f --- /dev/null +++ b/.tmp/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index c3fa63fd2..2b3ae8064 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,11 +6,11 @@ WORKDIR /var/www/html # Update packages and install dependencies RUN apk upgrade --no-cache && \ - apk add --no-cache sqlite-dev libpng libpng-dev libjpeg-turbo libjpeg-turbo-dev freetype freetype-dev curl autoconf libgomp icu-dev nginx dcron tzdata imagemagick imagemagick-dev && \ + apk add --no-cache sqlite-dev libpng libpng-dev libjpeg-turbo libjpeg-turbo-dev freetype freetype-dev curl autoconf libgomp icu-dev nginx dcron tzdata imagemagick imagemagick-dev libzip-dev && \ docker-php-ext-install pdo pdo_sqlite && \ docker-php-ext-enable pdo pdo_sqlite && \ docker-php-ext-configure gd --with-freetype --with-jpeg && \ - docker-php-ext-install -j$(nproc) gd intl && \ + docker-php-ext-install -j$(nproc) gd intl zip && \ apk add --no-cache --virtual .build-deps $PHPIZE_DEPS && \ pecl install imagick && \ docker-php-ext-enable imagick && \ diff --git a/README.md b/README.md index 93da5076d..e6353c984 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ See instructions to run Wallos below. - intl - openssl - sqlite3 + - zip #### Docker diff --git a/endpoints/db/backup.php b/endpoints/db/backup.php new file mode 100644 index 000000000..4a168fd24 --- /dev/null +++ b/endpoints/db/backup.php @@ -0,0 +1,68 @@ + false, + "message" => translate('session_expired', $i18n) + ])); +} + +function addFolderToZip($dir, $zipArchive, $zipdir = ''){ + if (is_dir($dir)) { + if ($dh = opendir($dir)) { + //Add the directory + if(!empty($zipdir)) $zipArchive->addEmptyDir($zipdir); + while (($file = readdir($dh)) !== false) { + // Skip '.' and '..' + if ($file == "." || $file == "..") { + continue; + } + //If it's a folder, run the function again! + if(is_dir($dir . $file)){ + $newdir = $dir . $file . '/'; + addFolderToZip($newdir, $zipArchive, $zipdir . $file . '/'); + }else{ + //Add the files + $zipArchive->addFile($dir . $file, $zipdir . $file); + } + } + } + } else { + die(json_encode([ + "success" => false, + "message" => "Directory does not exist: $dir" + ])); + } +} + +$zip = new ZipArchive(); +$zipname = "../../.tmp/backup.zip"; + +if ($zip->open($zipname, ZipArchive::CREATE)!==TRUE) { + die(json_encode([ + "success" => false, + "message" => translate('cannot_open_zip', $i18n) + ])); +} + +addFolderToZip('../../db/', $zip); +addFolderToZip('../../images/uploads/', $zip); + +$numberOfFilesAdded = $zip->numFiles; + +if ($zip->close() === false) { + die(json_encode([ + "success" => false, + "message" => "Failed to finalize the zip file" + ])); +} else { + die(json_encode([ + "success" => true, + "message" => "Zip file created successfully", + "numFiles" => $numberOfFilesAdded + ])); +} + + +?> \ No newline at end of file diff --git a/endpoints/db/restore.php b/endpoints/db/restore.php new file mode 100644 index 000000000..ff66eb427 --- /dev/null +++ b/endpoints/db/restore.php @@ -0,0 +1,111 @@ + false, + "message" => translate('session_expired', $i18n) + ])); +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + if (isset($_FILES['file'])) { + $file = $_FILES['file']; + $fileTmpName = $file['tmp_name']; + $fileError = $file['error']; + + if ($fileError === 0) { + // Handle the uploaded file here + // The uploaded file will be stored as restore.zip + $fileDestination = '../../.tmp/restore.zip'; + move_uploaded_file($fileTmpName, $fileDestination); + + // Unzip the uploaded file + $zip = new ZipArchive(); + if ($zip->open($fileDestination) === true) { + $zip->extractTo('../../.tmp/restore/'); + $zip->close(); + } + + // Check if wallos.db file exists in the restore folder + if (file_exists('../../.tmp/restore/wallos.db')) { + // Replace the wallos.db file in the db directory with the wallos.db file in the restore directory + if (file_exists('../../db/wallos.db')) { + unlink('../../db/wallos.db'); + } + rename('../../.tmp/restore/wallos.db', '../../db/wallos.db'); + + // Check if restore/logos/ directory exists + if (file_exists('../../.tmp/restore/logos/')) { + // Delete the files and folders in the uploaded logos directory + $dir = '../../images/uploads/logos/'; + + // Create recursive directory iterator + $di = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS); + + // Create recursive iterator iterator in Child First Order + $ri = new RecursiveIteratorIterator($di, RecursiveIteratorIterator::CHILD_FIRST); + + // For each item in the recursive iterator + foreach ( $ri as $file ) { + // If the item is a directory + if ( $file->isDir() ) { + // Remove the directory + rmdir($file->getPathname()); + } else { + // If the item is a file + // Remove the file + unlink($file->getPathname()); + } + } + + // Copy the contents of restore/logos/ directory to the ../../images/uploads/logos/ directory + $dir = new RecursiveDirectoryIterator('../../.tmp/restore/logos/'); + $ite = new RecursiveIteratorIterator($dir); + $allowedExtensions = ['png', 'jpg', 'jpeg', 'gif', 'webp']; + + foreach ($ite as $filePath) { + if (in_array(pathinfo($filePath, PATHINFO_EXTENSION), $allowedExtensions)) { + $destination = str_replace('../../.tmp/restore/', '../../images/uploads/', $filePath); + $destinationDir = pathinfo($destination, PATHINFO_DIRNAME); + + if (!is_dir($destinationDir)) { + mkdir($destinationDir, 0755, true); + } + + copy($filePath, $destination); + } + } + } + + echo json_encode([ + "success" => true, + "message" => "File uploaded and wallos.db exists" + ]); + } else { + die(json_encode([ + "success" => false, + "message" => "wallos.db does not exist in the backup file" + ])); + } + + + } else { + echo json_encode([ + "success" => false, + "message" => "Failed to upload file" + ]); + } + } else { + echo json_encode([ + "success" => false, + "message" => "No file uploaded" + ]); + } +} else { + echo json_encode([ + "success" => false, + "message" => "Invalid request method" + ]); +} +?> \ No newline at end of file diff --git a/endpoints/subscriptions/export.php b/endpoints/subscriptions/export.php deleted file mode 100644 index 2b8df91d0..000000000 --- a/endpoints/subscriptions/export.php +++ /dev/null @@ -1,48 +0,0 @@ - false, - "message" => translate('session_expired', $i18n) - ])); -} - -require_once '../../includes/getdbkeys.php'; - -$query = "SELECT * FROM subscriptions"; - -$result = $db->query($query); -if ($result) { - $subscriptions = array(); - while ($row = $result->fetchArray(SQLITE3_ASSOC)) { - // Map foreign keys to their corresponding values - $row['currency'] = $currencies[$row['currency_id']]; - $row['payment_method'] = $payment_methods[$row['payment_method_id']]; - $row['payer_user'] = $members[$row['payer_user_id']]; - $row['category'] = $categories[$row['category_id']]; - $row['cycle'] = $cycles[$row['cycle']]; - $row['frequency'] = $frequencies[$row['frequency']]; - - $subscriptions[] = $row; - } - - // Output JSON - $json = json_encode($subscriptions, JSON_PRETTY_PRINT); - - // Set headers for file download - header('Content-Type: application/json'); - header('Content-Disposition: attachment; filename="subscriptions.json"'); - header('Pragma: no-cache'); - header('Expires: 0'); - - // Output JSON for download - echo $json; -} else { - echo json_encode(array('error' => 'Failed to fetch subscriptions.')); -} - -?> \ No newline at end of file diff --git a/includes/footer.php b/includes/footer.php index b15b6186e..55c8038a0 100644 --- a/includes/footer.php +++ b/includes/footer.php @@ -24,5 +24,11 @@
+ close(); + } + ?> + \ No newline at end of file diff --git a/includes/i18n/de.php b/includes/i18n/de.php index c7cd0d5e6..eafe60f47 100644 --- a/includes/i18n/de.php +++ b/includes/i18n/de.php @@ -166,8 +166,10 @@ "add" => "Hinzufügen", "save" => "Speichern", "reset" => "Zurücksetzen", - "export_subscriptions" => "Abonnements exportieren", - "export_to_json" => "Nach JSON exportieren", + "backup_and_restore" => "Backup und Wiederherstellung", + "backup" => "Backup", + "restore" => "Wiederherstellen", + "restore_info" => "Durch die Wiederherstellung der Datenbank werden alle aktuellen Daten überschrieben. Nach der Wiederherstellung werden Sie abgemeldet.", // Filters menu "filter" => "Filter", "clear" => "Leeren", diff --git a/includes/i18n/el.php b/includes/i18n/el.php index bae9ed335..0255a8a12 100644 --- a/includes/i18n/el.php +++ b/includes/i18n/el.php @@ -166,8 +166,10 @@ "add" => "Προσθήκη", "save" => "Αποθήκευση", "reset" => "Επαναφορά", - "export_subscriptions" => "Εξαγωγή συνδρομών", - "export_to_json" => "Εξαγωγή σε JSON", + "backup_and_restore" => "Αντίγραφο ασφαλείας και επαναφορά", + "backup" => "Αντίγραφο ασφαλείας", + "restore" => "Επαναφορά", + "restore_info" => "Η επαναφορά της βάσης δεδομένων θα ακυρώσει όλα τα τρέχοντα δεδομένα. Μετά την επαναφορά θα αποσυνδεθείτε.", // Filters menu "filter" => "Φίλτρο", "clear" => "Καθαρισμός", diff --git a/includes/i18n/en.php b/includes/i18n/en.php index a48f2410e..92f42dd78 100644 --- a/includes/i18n/en.php +++ b/includes/i18n/en.php @@ -166,8 +166,10 @@ "add" => "Add", "save" => "Save", "reset" => "Reset", - "export_subscriptions" => "Export Subscriptions", - "export_to_json" => "Export to JSON", + "backup_and_restore" => "Backup and Restore", + "backup" => "Backup", + "restore" => "Restore", + "restore_info" => "Restoring the database will override all current data. You will be signed out after the restore.", // Filters menu "filter" => "Filter", "clear" => "Clear", diff --git a/includes/i18n/es.php b/includes/i18n/es.php index 0be9babd5..01f5190f3 100644 --- a/includes/i18n/es.php +++ b/includes/i18n/es.php @@ -166,8 +166,10 @@ "add" => "Agregar", "save" => "Guardar", "reset" => "Restablecer", - "export_subscriptions" => "Exportar suscripciones", - "export_to_json" => "Exportar a JSON", + "backup_and_restore" => "Copia de Seguridad y Restauración", + "backup" => "Copia de Seguridad", + "restore" => "Restaurar", + "restore_info" => "La restauración de la base de datos anulará todos los datos actuales. Se cerrará la sesión después de la restauración.", // Filters menu "filter" => "Filtrar", "clear" => "Limpiar", diff --git a/includes/i18n/fr.php b/includes/i18n/fr.php index c661ea2af..89588dcb8 100644 --- a/includes/i18n/fr.php +++ b/includes/i18n/fr.php @@ -166,8 +166,10 @@ "add" => "Ajouter", "save" => "Enregistrer", "reset" => "Réinitialiser", - "export_subscriptions" => "Exporter les abonnements", - "export_to_json" => "Exporter en JSON", + "backup_and_restore" => "Sauvegarde et restauration", + "backup" => "Sauvegarde", + "restore" => "Restauration", + "restore_info" => "La restauration de la base de données annulera toutes les données actuelles. Vous serez déconnecté après la restauration.", // Menu des filtes "filter" => "Filtre", "clear" => "Effacer", diff --git a/includes/i18n/it.php b/includes/i18n/it.php index d1204e2c7..7918f991b 100644 --- a/includes/i18n/it.php +++ b/includes/i18n/it.php @@ -171,9 +171,10 @@ 'add' => 'Aggiungi', 'save' => 'Salva', "reset" => 'Ripristina', - 'export_subscriptions' => 'Esporta abbonamenti', - 'export_to_json' => 'Esporta in JSON', - + "backup_and_restore" => 'Backup e ripristino', + "backup" => 'Backup', + "restore" => 'Ripristina', + "restore_info" => "Il ripristino del database annullerà tutti i dati correnti. Al termine del ripristino, l'utente verrà disconnesso.", // Filters 'filter' => 'Filtra', 'clear' => 'Pulisci', diff --git a/includes/i18n/jp.php b/includes/i18n/jp.php index 2a4d5438c..c5a658384 100644 --- a/includes/i18n/jp.php +++ b/includes/i18n/jp.php @@ -166,8 +166,10 @@ "add" => "追加", "save" => "保存", "reset" => "リセット", - "export_subscriptions" => "購読をエクスポート", - "export_to_json" => "JSONにエクスポート", + "backup_and_restore" => "バックアップとリストア", + "backup" => "バックアップ", + "restore" => "リストア", + "restore_info" => "データベースをリストアすると、現在のデータがすべて上書きされます。リストア後はサインアウトされます。", // Filters menu "filter" => "フィルタ", "clear" => "クリア", diff --git a/includes/i18n/pl.php b/includes/i18n/pl.php index 7b9cfe9b5..2f535953b 100644 --- a/includes/i18n/pl.php +++ b/includes/i18n/pl.php @@ -166,8 +166,10 @@ "add" => "Dodaj", "save" => "Zapisz", "reset" => "Resetuj", - "export_subscriptions" => "Eksportuj subskrypcje", - "export_to_json" => "Eksportuj do JSON", + "backup_and_restore" => "Kopia zapasowa i przywracanie", + "backup" => "Kopia zapasowa", + "restore" => "Przywróć", + "restore_info" => "Przywrócenie bazy danych zastąpi wszystkie bieżące dane. Po przywróceniu zostaniesz wylogowany.", // Filters menu "filter" => "Filtr", "clear" => "Wyczyść", diff --git a/includes/i18n/pt.php b/includes/i18n/pt.php index 00b3146be..e97a75612 100644 --- a/includes/i18n/pt.php +++ b/includes/i18n/pt.php @@ -166,8 +166,10 @@ "add" => "Adicionar", "save" => "Guardar", "reset" => "Repor", - "export_subscriptions" => "Exportar Subscrições", - "export_to_json" => "Exportar para JSON", + "backup_and_restore" => "Backup e Restauro", + "backup" => "Backup", + "restore" => "Restauro", + "restore_info" => "O restauro da base de dados apagará todos os dados actuais. A sua sessão irá terminar após o restauro.", // Filters menu "filter" => "Filtro", "clear" => "Limpar", diff --git a/includes/i18n/pt_br.php b/includes/i18n/pt_br.php index 2ee754bc1..045e7e8eb 100644 --- a/includes/i18n/pt_br.php +++ b/includes/i18n/pt_br.php @@ -164,8 +164,10 @@ "add" => "Adicionar", "save" => "Salvar", "reset" => "Redefinir", - "export_subscriptions" => "Exportar assinaturas", - "export_to_json" => "Exportar para JSON", + "backup_and_restore" => "Backup e Restauração", + "backup" => "Backup", + "restore" => "Restaurar", + "restore_info" => "A restauração do banco de dados substituirá todos os dados atuais. Você será desconectado após a restauração.", // Filters menu "filter" => "Filtrar", "clear" => "Limpar", diff --git a/includes/i18n/sr.php b/includes/i18n/sr.php index 3306079db..8c2ca71ee 100644 --- a/includes/i18n/sr.php +++ b/includes/i18n/sr.php @@ -166,8 +166,10 @@ "add" => "Додај", "save" => "Сачувај", "reset" => "Ресетуј", - "export_subscriptions" => "Извоз претплата", - "export_to_json" => "Извоз у JSON формат", + "backup_and_restore" => "Бекап и ресторе", + "backup" => "Бекап", + "restore" => "Ресторе", + "restore_info" => "Враћање базе података ће заменити све тренутне податке. Бићете одјављени након враћања.", // Мени са филтерима "filter" => "Филтер", "clear" => "Очисти", diff --git a/includes/i18n/sr_lat.php b/includes/i18n/sr_lat.php index 99db3d479..6a27817fc 100644 --- a/includes/i18n/sr_lat.php +++ b/includes/i18n/sr_lat.php @@ -166,8 +166,10 @@ "add" => "Dodaj", "save" => "Sačuvaj", "reset" => "Resetuj", - "export_subscriptions" => "Izvezi pretplate", - "export_to_json" => "Izvezi u JSON format", + "backup_and_restore" => "Backup i restore", + "backup" => "Backup", + "restore" => "Restore", + "restore_info" => "Vraćanje baze podataka će zameniti sve trenutne podatke. Bićete odjavljeni nakon vraćanja.", // Meni sa filterima "filter" => "Filter", "clear" => "Očisti", diff --git a/includes/i18n/tr.php b/includes/i18n/tr.php index d29c3c133..e38077ffe 100644 --- a/includes/i18n/tr.php +++ b/includes/i18n/tr.php @@ -166,8 +166,10 @@ "add" => "Ekle", "save" => "Kaydet", "reset" => "Sıfırla", - "export_subscriptions" => "Abonelikleri Dışa Aktar", - "export_to_json" => "JSON'a dışa aktar", + "backup_and_restore" => "Yedekle ve Geri Yükle", + "backup" => "Yedekle", + "restore" => "Geri Yükle", + "restore_info" => "Veritabanının geri yüklenmesi tüm mevcut verileri geçersiz kılacaktır. Geri yüklemeden sonra oturumunuz kapatılacaktır.", // Filters menu "filter" => "Filtre", "clear" => "Temizle", diff --git a/includes/i18n/zh_cn.php b/includes/i18n/zh_cn.php index e43fa659d..2d929e245 100644 --- a/includes/i18n/zh_cn.php +++ b/includes/i18n/zh_cn.php @@ -173,9 +173,10 @@ "add" => "添加", "save" => "保存", "reset" => "重置", - "export_subscriptions" => "导出订阅", - "export_to_json" => "导出为 JSON", - + "backup_and_restore" => "备份和恢复", + "backup" => "备份", + "restore" => "恢复", + "restore_info" => "还原数据库将覆盖所有当前数据。还原后,您将退出登录。", // Filters menu "filter" => "筛选", "clear" => "清除", diff --git a/includes/i18n/zh_tw.php b/includes/i18n/zh_tw.php index c21fd9ba5..a895c6684 100644 --- a/includes/i18n/zh_tw.php +++ b/includes/i18n/zh_tw.php @@ -166,8 +166,10 @@ "add" => "新增", "save" => "儲存", "reset" => "重設", - "export_subscriptions" => "匯出訂閱", - "export_to_json" => "匯出為 JSON 檔案", + "backup_and_restore" => "備份與還原", + "backup" => "備份", + "restore" => "還原", + "restore_info" => "復原資料庫將覆蓋所有目前資料。 恢復後您將被註銷。", // Filters menu "filter" => "篩選", "clear" => "清除", diff --git a/includes/version.php b/includes/version.php index 09db156b5..44ad6d2d7 100644 --- a/includes/version.php +++ b/includes/version.php @@ -1,3 +1,3 @@ diff --git a/scripts/settings.js b/scripts/settings.js index 96193cdaf..8a566d3b7 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -1008,8 +1008,53 @@ function setHideDisabled() { storeSettingsOnDB('hide_disabled', value); } -function exportToJson() { - window.location.href = "endpoints/subscriptions/export.php"; +function backupDB() { + fetch('endpoints/db/backup.php') + .then(response => response.json()) + .then(data => { + if (data.success) { + const link = document.createElement('a'); + link.href = '.tmp/backup.zip'; + link.download = 'backup.zip'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } else { + showErrorMessage(data.errorMessage); + } + }) + .catch(error => showErrorMessage(error)); +} + +function openRestoreDBFileSelect() { + document.getElementById('restoreDBFile').click(); +}; + +function restoreDB() { + const input = document.getElementById('restoreDBFile'); + const file = input.files[0]; + + if (!file) { + console.error('No file selected'); + return; + } + + const formData = new FormData(); + formData.append('file', file); + + fetch('endpoints/db/restore.php', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + console.log('Database restored successfully'); + } else { + console.error('Failed to restore database:', data.message); + } + }) + .catch(error => console.error('Error:', error)); } function saveCategorySorting() { diff --git a/settings.php b/settings.php index cce58699a..1a69eb84b 100644 --- a/settings.php +++ b/settings.php @@ -681,11 +681,23 @@
-

+

-
- -
+
+
+ +
+
+ + +
+
+
+

+ + +

+