From 6cb922faa7e5e2ca707e10f8ffd6fd830c2ccc03 Mon Sep 17 00:00:00 2001 From: Marek Miklewicz Date: Sat, 4 Jul 2026 15:02:24 +0200 Subject: [PATCH] Initial import of db-manager --- .gitignore | 1 + hooks/user_txt.html | 1 + images/user_icon.svg | 1 + install.sh | 11 + lang/en.php | 194 ++ lang/pl.php | 184 + php.ini | 1961 +++++++++++ plugin-settings.conf | 42 + plugin.conf | 6 + scripts/backup.sh | 437 +++ scripts/install.sh | 95 + scripts/restore.sh | 723 ++++ scripts/uninstall.sh | 18 + scripts/worker.sh | 104 + tests/test_always_enable.php | 51 + tests/test_install_always_enable.sh | 32 + uninstall.sh | 11 + user/enhanced.css | 569 ++++ user/evolution.css | 569 ++++ user/index.html | 4795 +++++++++++++++++++++++++++ 20 files changed, 9805 insertions(+) create mode 100644 .gitignore create mode 100644 hooks/user_txt.html create mode 100644 images/user_icon.svg create mode 100755 install.sh create mode 100644 lang/en.php create mode 100644 lang/pl.php create mode 100644 php.ini create mode 100644 plugin-settings.conf create mode 100644 plugin.conf create mode 100755 scripts/backup.sh create mode 100755 scripts/install.sh create mode 100755 scripts/restore.sh create mode 100755 scripts/uninstall.sh create mode 100755 scripts/worker.sh create mode 100644 tests/test_always_enable.php create mode 100644 tests/test_install_always_enable.sh create mode 100755 uninstall.sh create mode 100644 user/enhanced.css create mode 100644 user/evolution.css create mode 100755 user/index.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8fce603 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +data/ diff --git a/hooks/user_txt.html b/hooks/user_txt.html new file mode 100644 index 0000000..0557feb --- /dev/null +++ b/hooks/user_txt.html @@ -0,0 +1 @@ +Kopie Zapasowe Baz Danych diff --git a/images/user_icon.svg b/images/user_icon.svg new file mode 100644 index 0000000..295bd1f --- /dev/null +++ b/images/user_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..5a6fbdb --- /dev/null +++ b/install.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -eu + +PLUGIN_DIR="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$PLUGIN_DIR/scripts/install.sh" + +if [ ! -x "$SCRIPT" ]; then + chmod 755 "$SCRIPT" 2>/dev/null || true +fi + +exec "$SCRIPT" diff --git a/lang/en.php b/lang/en.php new file mode 100644 index 0000000..46601a3 --- /dev/null +++ b/lang/en.php @@ -0,0 +1,194 @@ + 'MySQL/MariaDB - Database Restore', + 'Manager kopii zapasowych baz danych' => 'Database backup manager', + 'Kopie zapasowe udostepniane przez HITME.PL' => 'Backups provided by HITME.PL', + 'Kopie zapasowe uzytkownika' => 'User backups', + 'Backupy udostepniane przez HITME.PL' => 'Backups Provided by HITME.PL', + 'Backupy Uzytkownika' => 'User Backups', + 'Tryb przywracania baz danych' => 'Database Restore Mode', + 'Nadpisanie istniejacej bazy danych (obecne dzialanie)' => 'Overwrite existing database (current behavior)', + 'Przywrocenie wskazanej bazy do nowej bazy danych' => 'Restore selected database into a new database', + 'Nazwa docelowej bazy danych' => 'Target database name', + 'Status zadan' => 'Job Status', + 'Katalogi kopii zapasowych' => 'Backup Directories', + 'Kalendarz backupow' => 'Backup Calendar', + 'Kalendarz backupow uzytkownika' => 'User Backup Calendar', + 'Wybierz backup i baze' => 'Select Backup and Database', + 'Data rozpoczecia generowania backupow' => 'Backup generation start time', + 'Wybierz aktywna date w kalendarzu.' => 'Select an active date in the calendar.', + 'Wybierz godzine backupu, aby zobaczyc liste baz.' => 'Select backup time to see database list.', + 'Godzina backupu:' => 'Backup time:', + 'Miesiac:' => 'Month:', + 'Brak aktywnych dat backupu dla tego uzytkownika.' => 'No active backup dates for this user.', + 'Brak backupow uzytkownika.' => 'No user backups.', + 'Brak kopii zapasowych uzytkownika' => 'No user backups.', + 'Brak dostepnych Kopii zapasowych uzytkownika' => 'No available user backups.', + 'Brak kopii zapasowych użytkownika dostępnych do przywrócenia - najpierw utwórz kopię zapasową na zakładce Wykonaj Backup Baz danych' => 'No user backups are available for restore. First create a backup in the "Create Database Backup" tab.', + 'Brak baz dla wybranego backupu.' => 'No databases for the selected backup.', + 'Brak baz danych uzytkownika do backupu.' => 'No user databases available for backup.', + 'Data Rozpoczecia' => 'Start Date', + 'Nazwa Bazy' => 'Database Name', + 'Data Backupu' => 'Backup Date', + 'Rodzaj przywracanej bazy' => 'Restored backup type', + 'Tryb Pracy' => 'Work mode', + 'Tryb przywracania' => 'Restore mode', + 'Status' => 'Status', + 'Akcje' => 'Actions', + 'Kopia bazy udostepnianej przez HITME.PL' => 'HITME.PL-provided database backup', + 'Kopia bazy uzytkownika' => 'User database backup', + 'Nadpisanie istniejacej bazy' => 'Overwrite existing database', + 'Przywrocenie kopii do nowej bazy' => 'Restore backup to a new database', + 'Nie dotyczy' => 'Not applicable', + 'Tworzenie Kopii bazy danych' => 'Database backup creation', + 'Przywracanie bazy danych - Nadpisanie oryginalnej bazy danych' => 'Database restore - overwrite original database', + 'Przywracanie bazy danych - Do nowej bazy danych' => 'Database restore - to a new database', + 'Baza danych' => 'Database', + 'Rozmiar' => 'Size', + 'Data i godzina backupu' => 'Backup date and time', + 'Wykonaj Backup' => 'Create Backup', + 'Wykonaj backupy wszystkich zaznaczonych baz' => 'Create backups for all selected databases', + 'Wykonaj Backup Baz danych' => 'Create Database Backup', + 'Przywróć kopię zapasową' => 'Restore backup', + 'Przywroc' => 'Restore', + 'Backup przed przywroceniem:' => 'Pre-restore backup:', + 'Backup przed przywroceniem backupow uzytkownika:' => 'Pre-restore backup for user backups:', + 'Katalog backupow uzytkownika:' => 'User backup directory:', + 'Log' => 'Log', + 'Pokaż logi' => 'Show logs', + 'Zapisz log' => 'Download Log', + 'Pobierz' => 'Download', + 'Pobierz kopię zapasową' => 'Download backup copy', + 'Usun' => 'Delete', + 'Usun log' => 'Delete Log', + 'Anuluj' => 'Cancel', + 'Przywroc backup przed przywroceniem' => 'Restore Pre-restore Backup', + 'Usun backup przed przywroceniem' => 'Delete Pre-restore Backup', + 'Przywróć poprzednią wersję bazy danych' => 'Restore previous database version', + 'Usuń backup poprzedniej wersji bazy danych' => 'Delete backup of previous database version', + 'Podglad na zywo (odswiezanie co 3s)' => 'Live preview (refresh every 3s)', + 'Data rozpoczecia:' => 'Start time:', + 'Baza danych:' => 'Database:', + 'Backup (data katalogu):' => 'Backup (folder date):', + 'Rodzaj przywracanej bazy:' => 'Restored backup type:', + 'Tryb przywracania:' => 'Restore mode:', + 'Koniecznosc sanityzacji:' => 'Sanitization required:', + 'Status:' => 'Status:', + 'Postep:' => 'Progress:', + 'Kopiuj do schowka' => 'Copy to clipboard', + 'Kopiuj' => 'Copy', + 'Zamknij' => 'Close', + 'Potwierdzenie operacji' => 'Confirm Operation', + 'Uwaga:' => 'Warning:', + 'Tak, kontynuuj' => 'Yes, continue', + 'Nie' => 'No', + 'Poprzedni miesiac' => 'Previous month', + 'Nastepny miesiac' => 'Next month', + 'Backupy HITME.PL:' => 'HITME.PL backups:', + 'Backupy uzytkownika:' => 'User backups:', + 'Backup przed przywroceniem (HITME.PL):' => 'Pre-restore backup (HITME.PL):', + 'Backup przed przywroceniem (backupy uzytkownika):' => 'Pre-restore backup (user backups):', + 'Oczekuje' => 'Pending', + 'W trakcie' => 'In progress', + 'Zakonczone' => 'Completed', + 'Blad' => 'Error', + 'Anulowane' => 'Canceled', + 'Nieznany' => 'Unknown', + 'Brak zadan do wyswietlenia.' => 'No jobs to display.', + 'Nieprawidlowy token CSRF.' => 'Invalid CSRF token.', + 'Brak katalogu HITME_SQL_BACKUP_DIR.' => 'Missing HITME_SQL_BACKUP_DIR directory.', + 'ENABLE_PRE_RESTORE_BACKUP ma niepoprawna wartosc (uzywam true/false).' => 'ENABLE_PRE_RESTORE_BACKUP has an invalid value (expected true/false).', + 'ENABLE_USER_PRE_RESTORE_BACKUP ma niepoprawna wartosc (uzywam true/false).' => 'ENABLE_USER_PRE_RESTORE_BACKUP has an invalid value (expected true/false).', + 'SECURE_BACKUP ma niepoprawna wartosc (uzywam true/false).' => 'SECURE_BACKUP has an invalid value (expected true/false).', + 'SECURE_RESTORE ma niepoprawna wartosc (uzywam true/false).' => 'SECURE_RESTORE has an invalid value (expected true/false).', + 'Nie wybrano poprawnej daty backupu.' => 'No valid backup date selected.', + 'Nie wybrano poprawnej godziny backupu.' => 'No valid backup time selected.', + 'Nie wybrano poprawnej bazy danych.' => 'No valid database selected.', + 'Tworzenie kopii zapasowej' => 'Backup creation', + 'Przywracanie kopii zapasowej' => 'Backup restore', + 'oczekuje w kolejce' => 'pending in queue', + 'jest w trakcie' => 'is in progress', + 'Aktualnie trwa tworzenie lub przywracanie kopii zapasowej - zaczekaj na zakończenie aktywnego zadania i ponów próbę. Aktywne zadanie: %s dla bazy %s (%s).' + => 'A backup or restore task is currently active. Wait until it finishes and try again. Active task: %s for database %s (%s).', + 'Dla bazy %s istnieje juz aktywne zadanie (%s, %s). Zaczekaj na jego zakonczenie i ponow probe.' + => 'There is already an active task for database %s (%s, %s). Wait until it completes and try again.', + 'Nie mozna zablokowac kolejki zadan. Sprobuj ponownie.' + => 'Could not lock the jobs queue. Try again.', + 'Nie mozna utworzyc katalogu zadan.' => 'Cannot create jobs directory.', + 'Nie znaleziono pliku dump dla wybranej bazy.' => 'Selected database dump file was not found.', + 'Nie udalo sie zapisac pliku zadania.' => 'Failed to save job file.', + 'Zadanie przywracania bazy dodane do kolejki. Otrzymasz powiadomienie po zakonczeniu.' => 'Database restore job has been queued. You will be notified after completion.', + 'Aby wykonac backup zbiorczy zaznacz co najmniej dwie bazy.' => 'Select at least two databases for bulk backup.', + 'Nie wybrano baz do backupu.' => 'No databases selected for backup.', + 'Nieprawidlowa baza danych: ' => 'Invalid database: ', + 'W trybie przywracania do nowej bazy tworzenie backupow uzytkownika jest ukryte.' => 'In restore-to-new-database mode, creating user backups is hidden.', + 'Nazwa docelowej bazy danych jest nieprawidlowa. Musi zaczynac sie od nazwy uzytkownika i znaku _.' => 'Target database name is invalid. It must start with the DirectAdmin username and underscore.', + 'Wskazana docelowa baza danych nie istnieje. Utworz ja recznie w DirectAdmin.' => 'The selected target database does not exist. Create it manually in DirectAdmin.', + 'Nie udalo sie sprawdzic, czy docelowa baza danych jest pusta.' => 'Could not verify whether the target database is empty.', + 'Docelowa baza danych nie jest pusta. Usun dane przed kontynuowaniem.' => 'Target database is not empty. Remove data before continuing.', + 'Docelowa baza danych musi byc inna niz zrodlowa baza z backupu.' => 'Target database must be different from the source backup database.', + 'Nie mozna utworzyc katalogu backupu uzytkownika.' => 'Cannot create user backup directory.', + 'Nie udalo sie zapisac pliku zadania backupu dla bazy ' => 'Failed to save backup job file for database ', + 'Dodano zadanie backupu bazy danych.' => 'Database backup job added.', + 'Dodano zadania backupu %d baz danych.' => 'Backup jobs added for %d databases.', + 'Nie wybrano poprawnej daty backupu uzytkownika.' => 'No valid user backup date selected.', + 'Nie wybrano poprawnej godziny backupu uzytkownika.' => 'No valid user backup time selected.', + 'Zadanie przywracania backupu uzytkownika dodane do kolejki.' => 'User backup restore job has been queued.', + 'Nie znaleziono zadania.' => 'Job not found.', + 'Brak nazwy bazy w zadaniu.' => 'Missing database name in job.', + 'Backup przed przywroceniem nie istnieje.' => 'Pre-restore backup does not exist.', + 'Nieprawidlowa sciezka backupu przed przywroceniem.' => 'Invalid pre-restore backup path.', + 'Dodano zadanie przywracania backupu przed przywroceniem.' => 'Pre-restore backup restore job has been queued.', + 'Nie udalo sie usunac backupu przed przywroceniem.' => 'Failed to delete pre-restore backup.', + 'Backup przed przywroceniem zostal usuniety.' => 'Pre-restore backup deleted.', + 'Nieprawidlowa sciezka backupu uzytkownika.' => 'Invalid user backup path.', + 'Nieprawidlowa nazwa pliku backupu uzytkownika.' => 'Invalid user backup filename.', + 'Pobieranie backupow HITME.SQL jest wylaczone.' => 'Downloading backups from HITME_SQL_BACKUP_DIR is disabled.', + 'Sekcja backupow uzytkownika jest wylaczona.' => 'User backup section is disabled.', + 'Opcja zarzadzania bazami danych jest niedostepna - skontaktuj sie z supportem aby uzyskac wiecej informacji' + => 'Database management is unavailable - contact support for more information.', + 'Usuwanie logow i wpisow zadan jest zablokowane przez konfiguracje.' => 'Deleting logs and job entries is blocked by configuration.', + 'Nieprawidlowa nazwa pliku backupu.' => 'Invalid backup filename.', + 'Nieprawidlowa sciezka backupu.' => 'Invalid backup path.', + 'Nie udalo sie usunac backupu uzytkownika.' => 'Failed to delete user backup.', + 'Backup uzytkownika zostal usuniety.' => 'User backup has been deleted.', + 'Nie mozna pobrac pliku backupu (naglowki zostaly juz wyslane).' => 'Cannot download backup file (headers already sent).', + 'Nieprawidlowe zadanie.' => 'Invalid job.', + 'Zadanie zostalo anulowane.' => 'Job has been canceled.', + 'Zadanie nie jest w trakcie.' => 'Job is not in progress.', + 'Nie znaleziono procesu do anulowania.' => 'No process found to cancel.', + 'Brak obsugi posix_kill na serwerze.' => 'posix_kill is not available on the server.', + 'Nie udalo sie anulowac zadania.' => 'Failed to cancel job.', + 'Wybrane zadania zostaly usuniete.' => 'Selected jobs have been deleted.', + 'Nie wybrano zadnych zadan.' => 'No jobs selected.', + 'Zadanie zostalo usuniete.' => 'Job has been deleted.', + 'Nie znaleziono logu.' => 'Log not found.', + 'Log nie istnieje.\n' => "Log does not exist.\n", + 'Data rozpoczecia: ' => 'Start time: ', + 'Baza danych: ' => 'Database: ', + 'Backup (data katalogu): ' => 'Backup (folder date): ', + 'Rodzaj przywracanej bazy: ' => 'Restored backup type: ', + 'Tryb przywracania: ' => 'Restore mode: ', + 'Koniecznosc Sanityzacji: Tak' => 'Sanitization required: Yes', + 'Koniecznosc Sanityzacji: Nie' => 'Sanitization required: No', + 'Koniecznosc Sanityzacji: ' => 'Sanitization required: ', + 'Status: ' => 'Status: ', + 'Postep: ' => 'Progress: ', + 'Przywracanie backupu moze nadpisac obecne dane bazy. Czy kontynuowac?' => 'Restoring backup may overwrite current database data. Continue?', + 'Przywracanie backupu uzytkownika moze nadpisac obecne dane bazy. Czy kontynuowac?' => 'Restoring user backup may overwrite current database data. Continue?', + 'Przywrocenie backupu sprzed przywrocenia moze nadpisac obecne dane. Czy kontynuowac?' => 'Restoring pre-restore backup may overwrite current data. Continue?', + 'Usuniecie backupu sprzed przywrocenia jest nieodwracalne. Czy kontynuowac?' => 'Deleting pre-restore backup is irreversible. Continue?', + 'Usuniesz backup uzytkownika. Tej operacji nie mozna cofnac. Czy kontynuowac?' => 'You will delete a user backup. This operation cannot be undone. Continue?', + 'Usuniesz log i wpis zadania. Tej operacji nie mozna cofnac. Czy kontynuowac?' => 'You will delete the log and the job entry. This operation cannot be undone. Continue?', + 'Czy kontynuowac operacje?' => 'Continue operation?', + 'Przywrócenie backupu sprzed przywrócenia może nadpisać obecne dane. Czy kontynuować?' => 'Restoring pre-restore backup may overwrite current data. Continue?', + 'Usunięcie backupu sprzed przywrócenia jest nieodwracalne. Czy kontynuować?' => 'Deleting pre-restore backup is irreversible. Continue?', + 'Usuniesz backup użytkownika. Tej operacji nie można cofnąć. Czy kontynuować?' => 'You will delete a user backup. This operation cannot be undone. Continue?', + 'Usuniesz log i wpis zadania. Tej operacji nie można cofnąć. Czy kontynuować?' => 'You will delete the log and the job entry. This operation cannot be undone. Continue?', + 'Czy kontynuować operację?' => 'Continue operation?', + 'UWAGA: Czy na pewno anulowac zadanie?' => 'WARNING: Are you sure you want to cancel this job?', + 'UWAGA: Przywrocenie backupu sprzed przywrocenia moze nadpisac obecne dane. Kontynuowac?' => 'WARNING: Restoring the pre-restore backup may overwrite current data. Continue?', + 'UWAGA: Usuniecie backupu sprzed przywrocenia jest nieodwracalne. Kontynuowac?' => 'WARNING: Deleting the pre-restore backup is irreversible. Continue?', + 'UWAGA: Usuniesz log i wpis zadania. Tej operacji nie mozna cofnac. Kontynuowac?' => 'WARNING: You are about to delete the log and job entry. This action cannot be undone. Continue?', + 'UWAGA: Rozpoczynasz przywracanie backupu bazy danych. Operacja moze nadpisac obecne dane. Kontynuowac?' => 'WARNING: You are starting a database restore. This operation may overwrite current data. Continue?', +]; diff --git a/lang/pl.php b/lang/pl.php new file mode 100644 index 0000000..cad77e5 --- /dev/null +++ b/lang/pl.php @@ -0,0 +1,184 @@ + 'Opcja zarządzania bazami danych jest niedostępna - skontaktuj się z supportem aby uzyskać więcej informacji', + 'Brak kopii zapasowych użytkownika dostępnych do przywrócenia - najpierw utwórz kopię zapasową na zakładce Wykonaj Backup Baz danych' + => 'Brak kopii zapasowych użytkownika dostępnych do przywrócenia - najpierw utwórz kopię zapasową na zakładce Wykonaj Backup Baz danych', + 'Kopie zapasowe udostepniane przez HITME.PL' => 'Kopie zapasowe udostępniane przez HITME.PL', + 'Backupy udostepniane przez HITME.PL' => 'Backupy udostępniane przez HITME.PL', + 'Kopie zapasowe uzytkownika' => 'Kopie zapasowe użytkownika', + 'Backupy Uzytkownika' => 'Backupy Użytkownika', + 'Kalendarz backupow uzytkownika' => 'Kalendarz backupów użytkownika', + 'Brak aktywnych dat backupu dla tego uzytkownika.' => 'Brak aktywnych dat backupu dla tego użytkownika.', + 'Brak backupow uzytkownika.' => 'Brak backupów użytkownika.', + 'Brak kopii zapasowych uzytkownika' => 'Brak kopii zapasowych użytkownika', + 'Brak dostepnych Kopii zapasowych uzytkownika' => 'Brak dostępnych kopii zapasowych użytkownika', + 'Brak baz danych uzytkownika do backupu.' => 'Brak baz danych użytkownika do backupu.', + 'Kopia bazy uzytkownika' => 'Kopia bazy użytkownika', + 'Kopia bazy udostepnianej przez HITME.PL' => 'Kopia bazy udostępnianej przez HITME.PL', + 'Nadpisanie istniejacej bazy danych (obecne dzialanie)' => 'Nadpisanie istniejącej bazy danych (obecne działanie)', + 'Nadpisanie istniejacej bazy' => 'Nadpisanie istniejącej bazy', + 'Przywrocenie wskazanej bazy do nowej bazy danych' => 'Przywrócenie wskazanej bazy do nowej bazy danych', + 'Przywrocenie kopii do nowej bazy' => 'Przywrócenie kopii do nowej bazy', + 'Przywroc' => 'Przywróć', + 'Przywroc backup przed przywroceniem' => 'Przywróć backup przed przywróceniem', + 'Usun backup przed przywroceniem' => 'Usuń backup przed przywróceniem', + 'Backup przed przywroceniem:' => 'Backup przed przywróceniem:', + 'Backup przed przywroceniem backupow uzytkownika:' => 'Backup przed przywróceniem backupów użytkownika:', + 'Katalog backupow uzytkownika:' => 'Katalog backupów użytkownika:', + 'Podglad na zywo (odswiezanie co 3s)' => 'Podgląd na żywo (odświeżanie co 3s)', + 'Data rozpoczecia generowania backupow' => 'Data rozpoczęcia generowania backupów', + 'Data rozpoczecia:' => 'Data rozpoczęcia:', + 'Data Rozpoczecia' => 'Data Rozpoczęcia', + 'Data i godzina backupu' => 'Data i godzina backupu', + 'Wybierz godzine backupu, aby zobaczyc liste baz.' => 'Wybierz godzinę backupu, aby zobaczyć listę baz.', + 'Wybierz aktywna date w kalendarzu.' => 'Wybierz aktywną datę w kalendarzu.', + 'Miesiac:' => 'Miesiąc:', + 'Poprzedni miesiac' => 'Poprzedni miesiąc', + 'Nastepny miesiac' => 'Następny miesiąc', + 'Status zadan' => 'Status zadań', + 'Brak zadan do wyswietlenia.' => 'Brak zadań do wyświetlenia.', + 'Nie wybrano zadnych zadan.' => 'Nie wybrano żadnych zadań.', + 'Koniecznosc sanityzacji:' => 'Konieczność sanityzacji:', + 'Koniecznosc Sanityzacji: Tak' => 'Konieczność Sanityzacji: Tak', + 'Koniecznosc Sanityzacji: Nie' => 'Konieczność Sanityzacji: Nie', + 'Koniecznosc Sanityzacji: ' => 'Konieczność Sanityzacji: ', + 'Postep:' => 'Postęp:', + 'Zakonczone' => 'Zakończone', + 'Blad' => 'Błąd', + 'Nieprawidlowy token CSRF.' => 'Nieprawidłowy token CSRF.', + 'Nieprawidlowe zadanie.' => 'Nieprawidłowe zadanie.', + 'Nieprawidlowa baza danych: ' => 'Nieprawidłowa baza danych: ', + 'Nieprawidlowa nazwa pliku backupu uzytkownika.' => 'Nieprawidłowa nazwa pliku backupu użytkownika.', + 'Nieprawidlowa sciezka backupu uzytkownika.' => 'Nieprawidłowa ścieżka backupu użytkownika.', + 'Nieprawidlowa nazwa pliku backupu.' => 'Nieprawidłowa nazwa pliku backupu.', + 'Nieprawidlowa sciezka backupu.' => 'Nieprawidłowa ścieżka backupu.', + 'Nieprawidlowa sciezka backupu przed przywroceniem.' => 'Nieprawidłowa ścieżka backupu przed przywróceniem.', + 'Nazwa docelowej bazy danych jest nieprawidlowa. Musi zaczynac sie od nazwy uzytkownika i znaku _.' + => 'Nazwa docelowej bazy danych jest nieprawidłowa. Musi zaczynać się od nazwy użytkownika i znaku _.', + 'Wskazana docelowa baza danych nie istnieje. Utworz ja recznie w DirectAdmin.' + => 'Wskazana docelowa baza danych nie istnieje. Utwórz ją ręcznie w DirectAdmin.', + 'Nie udalo sie sprawdzic, czy docelowa baza danych jest pusta.' + => 'Nie udało się sprawdzić, czy docelowa baza danych jest pusta.', + 'Docelowa baza danych musi byc inna niz zrodlowa baza z backupu.' + => 'Docelowa baza danych musi być inna niż źródłowa baza z backupu.', + 'W trybie przywracania do nowej bazy tworzenie backupow uzytkownika jest ukryte.' + => 'W trybie przywracania do nowej bazy tworzenie backupów użytkownika jest ukryte.', + 'Nie mozna utworzyc katalogu zadan.' => 'Nie można utworzyć katalogu zadań.', + 'Nie mozna utworzyc katalogu backupu uzytkownika.' => 'Nie można utworzyć katalogu backupu użytkownika.', + 'Nie mozna pobrac pliku backupu (naglowki zostaly juz wyslane).' + => 'Nie można pobrać pliku backupu (nagłówki zostały już wysłane).', + 'Nie udalo sie zapisac pliku zadania.' => 'Nie udało się zapisać pliku zadania.', + 'Nie udalo sie zapisac pliku zadania backupu dla bazy ' => 'Nie udało się zapisać pliku zadania backupu dla bazy ', + 'Zadanie przywracania bazy dodane do kolejki. Otrzymasz powiadomienie po zakonczeniu.' + => 'Zadanie przywracania bazy dodane do kolejki. Otrzymasz powiadomienie po zakończeniu.', + 'Zadanie przywracania backupu uzytkownika dodane do kolejki.' + => 'Zadanie przywracania backupu użytkownika dodane do kolejki.', + 'Backup przed przywroceniem nie istnieje.' => 'Backup przed przywróceniem nie istnieje.', + 'Dodano zadanie przywracania backupu przed przywroceniem.' => 'Dodano zadanie przywracania backupu przed przywróceniem.', + 'Nie udalo sie usunac backupu przed przywroceniem.' => 'Nie udało się usunąć backupu przed przywróceniem.', + 'Backup przed przywroceniem zostal usuniety.' => 'Backup przed przywróceniem został usunięty.', + 'Nie udalo sie usunac backupu uzytkownika.' => 'Nie udało się usunąć backupu użytkownika.', + 'Backup uzytkownika zostal usuniety.' => 'Backup użytkownika został usunięty.', + 'Pobieranie backupow HITME.SQL jest wylaczone.' => 'Pobieranie backupów HITME.SQL jest wyłączone.', + 'Sekcja backupow uzytkownika jest wylaczona.' => 'Sekcja backupów użytkownika jest wyłączona.', + 'Usuwanie logow i wpisow zadan jest zablokowane przez konfiguracje.' => 'Usuwanie logów i wpisów zadań jest zablokowane przez konfigurację.', + 'Brak obsugi posix_kill na serwerze.' => 'Brak obsługi posix_kill na serwerze.', + 'Nie udalo sie anulowac zadania.' => 'Nie udało się anulować zadania.', + 'Wybrane zadania zostaly usuniete.' => 'Wybrane zadania zostały usunięte.', + 'Zadanie zostalo usuniete.' => 'Zadanie zostało usunięte.', + 'Zadanie zostalo anulowane.' => 'Zadanie zostało anulowane.', + 'Czy kontynuowac operacje?' => 'Czy kontynuować operację?', + 'Przywracanie backupu moze nadpisac obecne dane bazy. Czy kontynuowac?' + => 'Przywracanie backupu może nadpisać obecne dane bazy. Czy kontynuować?', + 'Przywracanie backupu uzytkownika moze nadpisac obecne dane bazy. Czy kontynuowac?' + => 'Przywracanie backupu użytkownika może nadpisać obecne dane bazy. Czy kontynuować?', + 'Przywrocenie backupu sprzed przywrocenia moze nadpisac obecne dane. Czy kontynuowac?' + => 'Przywrócenie backupu sprzed przywrócenia może nadpisać obecne dane. Czy kontynuować?', + 'Usuniecie backupu sprzed przywrocenia jest nieodwracalne. Czy kontynuowac?' + => 'Usunięcie backupu sprzed przywrócenia jest nieodwracalne. Czy kontynuować?', + 'Usuniesz backup uzytkownika. Tej operacji nie mozna cofnac. Czy kontynuowac?' + => 'Usuniesz backup użytkownika. Tej operacji nie można cofnąć. Czy kontynuować?', + 'Usuniesz log i wpis zadania. Tej operacji nie mozna cofnac. Czy kontynuowac?' + => 'Usuniesz log i wpis zadania. Tej operacji nie można cofnąć. Czy kontynuować?', + 'UWAGA: Czy na pewno anulowac zadanie?' => 'UWAGA: Czy na pewno anulować zadanie?', + 'UWAGA: Przywrocenie backupu sprzed przywrocenia moze nadpisac obecne dane. Kontynuowac?' + => 'UWAGA: Przywrócenie backupu sprzed przywrócenia może nadpisać obecne dane. Kontynuować?', + 'UWAGA: Usuniecie backupu sprzed przywrocenia jest nieodwracalne. Kontynuowac?' + => 'UWAGA: Usunięcie backupu sprzed przywrócenia jest nieodwracalne. Kontynuować?', + 'UWAGA: Usuniesz log i wpis zadania. Tej operacji nie mozna cofnac. Kontynuowac?' + => 'UWAGA: Usuniesz log i wpis zadania. Tej operacji nie można cofnąć. Kontynuować?', + 'UWAGA: Rozpoczynasz przywracanie backupu bazy danych. Operacja moze nadpisac obecne dane. Kontynuowac?' + => 'UWAGA: Rozpoczynasz przywracanie backupu bazy danych. Operacja może nadpisać obecne dane. Kontynuować?', + 'uzytkownika' => 'użytkownika', + 'Uzytkownika' => 'Użytkownika', + 'backupow' => 'backupów', + 'Backupow' => 'Backupów', + 'przywroceniem' => 'przywróceniem', + 'przywrocenia' => 'przywrócenia', + 'przywrocic' => 'przywrócić', + 'zrodlowa' => 'źródłowa', + 'zrodlowej' => 'źródłowej', + 'zaczynac' => 'zaczynać', + 'nieprawidlowa' => 'nieprawidłowa', + 'Nieprawidlowa' => 'Nieprawidłowa', + 'nieprawidlowe' => 'nieprawidłowe', + 'Nieprawidlowe' => 'Nieprawidłowe', + 'nieprawidlowy' => 'nieprawidłowy', + 'Nieprawidlowy' => 'Nieprawidłowy', + 'sciezka' => 'ścieżka', + 'Sciezka' => 'Ścieżka', + 'wyswietlenia' => 'wyświetlenia', + 'zadnych' => 'żadnych', + 'zadan' => 'zadań', + 'udalo' => 'udało', + 'zapisac' => 'zapisać', + 'sprawdzic' => 'sprawdzić', + 'utworzyc' => 'utworzyć', + 'mozna' => 'można', + 'zostalo' => 'zostało', + 'zostaly' => 'zostały', + 'usuniety' => 'usunięty', + 'usuniete' => 'usunięte', + 'cofnac' => 'cofnąć', + 'kontynuowac' => 'kontynuować', + 'miesiac' => 'miesiąc', + 'Miesiac' => 'Miesiąc', + 'nastepny' => 'następny', + 'Nastepny' => 'Następny', + 'rozpoczecia' => 'rozpoczęcia', + 'Rozpoczecia' => 'Rozpoczęcia', + 'postep' => 'postęp', + 'Postep' => 'Postęp', + 'koniecznosc' => 'konieczność', + 'Koniecznosc' => 'Konieczność', + 'odswiezanie' => 'odświeżanie', + 'Podglad' => 'Podgląd', + 'Blad' => 'Błąd', + 'zakonczone' => 'zakończone', + 'Zakonczone' => 'Zakończone', + 'recznie' => 'ręcznie', + 'powiadomienie po zakonczeniu' => 'powiadomienie po zakończeniu', + 'naglowki' => 'nagłówki', + 'wartosc' => 'wartość', + 'obslugi' => 'obsługi', + ]; + } + uksort($replacements, static function ($a, $b) { + return strlen((string)$b) <=> strlen((string)$a); + }); + return str_replace(array_keys($replacements), array_values($replacements), $text); +} + +$pl = []; +foreach ($en as $key => $_value) { + $pl[$key] = pl_normalize_text((string)$key); +} + +return $pl; diff --git a/php.ini b/php.ini new file mode 100644 index 0000000..468b8fc --- /dev/null +++ b/php.ini @@ -0,0 +1,1961 @@ +[PHP] + +;;;;;;;;;;;;;;;;;;; +; About php.ini ; +;;;;;;;;;;;;;;;;;;; +; PHP's initialization file, generally called php.ini, is responsible for +; configuring many of the aspects of PHP's behavior. + +; PHP attempts to find and load this configuration from a number of locations. +; The following is a summary of its search order: +; 1. SAPI module specific location. +; 2. The PHPRC environment variable. +; 3. A number of predefined registry keys on Windows +; 4. Current working directory (except CLI) +; 5. The web server's directory (for SAPI modules), or directory of PHP +; (otherwise in Windows) +; 6. The directory from the --with-config-file-path compile time option, or the +; Windows directory (usually C:\windows) +; See the PHP docs for more specific information. +; https://php.net/configuration.file + +; The syntax of the file is extremely simple. Whitespace and lines +; beginning with a semicolon are silently ignored (as you probably guessed). +; Section headers (e.g. [Foo]) are also silently ignored, even though +; they might mean something in the future. + +; Directives following the section heading [PATH=/www/mysite] only +; apply to PHP files in the /www/mysite directory. Directives +; following the section heading [HOST=www.example.com] only apply to +; PHP files served from www.example.com. Directives set in these +; special sections cannot be overridden by user-defined INI files or +; at runtime. Currently, [PATH=] and [HOST=] sections only work under +; CGI/FastCGI. +; https://php.net/ini.sections + +; Directives are specified using the following syntax: +; directive = value +; Directive names are *case sensitive* - foo=bar is different from FOO=bar. +; Directives are variables used to configure PHP or PHP extensions. +; There is no name validation. If PHP can't find an expected +; directive because it is not set or is mistyped, a default value will be used. + +; The value can be a string, a number, a PHP constant (e.g. E_ALL or M_PI), one +; of the INI constants (On, Off, True, False, Yes, No and None) or an expression +; (e.g. E_ALL & ~E_NOTICE), a quoted string ("bar"), or a reference to a +; previously set variable or directive (e.g. ${foo}) + +; Expressions in the INI file are limited to bitwise operators and parentheses: +; | bitwise OR +; ^ bitwise XOR +; & bitwise AND +; ~ bitwise NOT +; ! boolean NOT + +; Boolean flags can be turned on using the values 1, On, True or Yes. +; They can be turned off using the values 0, Off, False or No. + +; An empty string can be denoted by simply not writing anything after the equal +; sign, or by using the None keyword: + +; foo = ; sets foo to an empty string +; foo = None ; sets foo to an empty string +; foo = "None" ; sets foo to the string 'None' + +; If you use constants in your value, and these constants belong to a +; dynamically loaded extension (either a PHP extension or a Zend extension), +; you may only use these constants *after* the line that loads the extension. + +;;;;;;;;;;;;;;;;;;; +; About this file ; +;;;;;;;;;;;;;;;;;;; +; PHP comes packaged with two INI files. One that is recommended to be used +; in production environments and one that is recommended to be used in +; development environments. + +; php.ini-production contains settings which hold security, performance and +; best practices at its core. But please be aware, these settings may break +; compatibility with older or less security-conscious applications. We +; recommending using the production ini in production and testing environments. + +; php.ini-development is very similar to its production variant, except it is +; much more verbose when it comes to errors. We recommend using the +; development version only in development environments, as errors shown to +; application users can inadvertently leak otherwise secure information. + +; This is the php.ini-production INI file. + +;;;;;;;;;;;;;;;;;;; +; Quick Reference ; +;;;;;;;;;;;;;;;;;;; + +; The following are all the settings which are different in either the production +; or development versions of the INIs with respect to PHP's default behavior. +; Please see the actual settings later in the document for more details as to why +; we recommend these changes in PHP's behavior. + +; display_errors +; Default Value: On +; Development Value: On +; Production Value: Off + +; display_startup_errors +; Default Value: On +; Development Value: On +; Production Value: Off + +; error_reporting +; Default Value: E_ALL +; Development Value: E_ALL +; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT + +; log_errors +; Default Value: Off +; Development Value: On +; Production Value: On + +; max_input_time +; Default Value: -1 (Unlimited) +; Development Value: 60 (60 seconds) +; Production Value: 60 (60 seconds) + +; output_buffering +; Default Value: Off +; Development Value: 4096 +; Production Value: 4096 + +; register_argc_argv +; Default Value: On +; Development Value: Off +; Production Value: Off + +; request_order +; Default Value: None +; Development Value: "GP" +; Production Value: "GP" + +; session.gc_divisor +; Default Value: 100 +; Development Value: 1000 +; Production Value: 1000 + +; session.sid_bits_per_character +; Default Value: 4 +; Development Value: 5 +; Production Value: 5 + +; session.sid_length +; Default Value: 32 +; Development Value: 26 +; Production Value: 26 + +; short_open_tag +; Default Value: On +; Development Value: Off +; Production Value: Off + +; variables_order +; Default Value: "EGPCS" +; Development Value: "GPCS" +; Production Value: "GPCS" + +; zend.assertions +; Default Value: 1 +; Development Value: 1 +; Production Value: -1 + +; zend.exception_ignore_args +; Default Value: Off +; Development Value: Off +; Production Value: On + +; zend.exception_string_param_max_len +; Default Value: 15 +; Development Value: 15 +; Production Value: 0 + +;;;;;;;;;;;;;;;;;;;; +; php.ini Options ; +;;;;;;;;;;;;;;;;;;;; +; Name for user-defined php.ini (.htaccess) files. Default is ".user.ini" +;user_ini.filename = ".user.ini" + +; To disable this feature set this option to an empty value +;user_ini.filename = + +; TTL for user-defined php.ini files (time-to-live) in seconds. Default is 300 seconds (5 minutes) +;user_ini.cache_ttl = 300 + +;;;;;;;;;;;;;;;;;;;; +; Language Options ; +;;;;;;;;;;;;;;;;;;;; + +; Enable the PHP scripting language engine under Apache. +; https://php.net/engine +engine = On + +; This directive determines whether or not PHP will recognize code between +; tags as PHP source which should be processed as such. It is +; generally recommended that should be used and that this feature +; should be disabled, as enabling it may result in issues when generating XML +; documents, however this remains supported for backward compatibility reasons. +; Note that this directive does not control the would work. +; https://php.net/syntax-highlighting +;highlight.string = #DD0000 +;highlight.comment = #FF9900 +;highlight.keyword = #007700 +;highlight.default = #0000BB +;highlight.html = #000000 + +; If enabled, the request will be allowed to complete even if the user aborts +; the request. Consider enabling it if executing long requests, which may end up +; being interrupted by the user or a browser timing out. PHP's default behavior +; is to disable this feature. +; https://php.net/ignore-user-abort +;ignore_user_abort = On + +; Determines the size of the realpath cache to be used by PHP. This value should +; be increased on systems where PHP opens many files to reflect the quantity of +; the file operations performed. +; Note: if open_basedir is set, the cache is disabled +; https://php.net/realpath-cache-size +;realpath_cache_size = 4096k + +; Duration of time, in seconds for which to cache realpath information for a given +; file or directory. For systems with rarely changing files, consider increasing this +; value. +; https://php.net/realpath-cache-ttl +;realpath_cache_ttl = 120 + +; Enables or disables the circular reference collector. +; https://php.net/zend.enable-gc +zend.enable_gc = On + +; If enabled, scripts may be written in encodings that are incompatible with +; the scanner. CP936, Big5, CP949 and Shift_JIS are the examples of such +; encodings. To use this feature, mbstring extension must be enabled. +;zend.multibyte = Off + +; Allows to set the default encoding for the scripts. This value will be used +; unless "declare(encoding=...)" directive appears at the top of the script. +; Only affects if zend.multibyte is set. +;zend.script_encoding = + +; Allows to include or exclude arguments from stack traces generated for exceptions. +; In production, it is recommended to turn this setting on to prohibit the output +; of sensitive information in stack traces +; Default Value: Off +; Development Value: Off +; Production Value: On +zend.exception_ignore_args = On + +; Allows setting the maximum string length in an argument of a stringified stack trace +; to a value between 0 and 1000000. +; This has no effect when zend.exception_ignore_args is enabled. +; Default Value: 15 +; Development Value: 15 +; Production Value: 0 +; In production, it is recommended to set this to 0 to reduce the output +; of sensitive information in stack traces. +zend.exception_string_param_max_len = 0 + +;;;;;;;;;;;;;;;;; +; Miscellaneous ; +;;;;;;;;;;;;;;;;; + +; Decides whether PHP may expose the fact that it is installed on the server +; (e.g. by adding its signature to the Web server header). It is no security +; threat in any way, but it makes it possible to determine whether you use PHP +; on your server or not. +; https://php.net/expose-php +expose_php = Off + +;;;;;;;;;;;;;;;;;;; +; Resource Limits ; +;;;;;;;;;;;;;;;;;;; + +; Maximum execution time of each script, in seconds +; https://php.net/max-execution-time +; Note: This directive is hardcoded to 0 for the CLI SAPI +max_execution_time = 30 + +; Maximum amount of time each script may spend parsing request data. It's a good +; idea to limit this time on productions servers in order to eliminate unexpectedly +; long running scripts. +; Note: This directive is hardcoded to -1 for the CLI SAPI +; Default Value: -1 (Unlimited) +; Development Value: 60 (60 seconds) +; Production Value: 60 (60 seconds) +; https://php.net/max-input-time +max_input_time = 60 + +; Maximum input variable nesting level +; https://php.net/max-input-nesting-level +;max_input_nesting_level = 64 + +; How many GET/POST/COOKIE input variables may be accepted +;max_input_vars = 1000 + +; How many multipart body parts (combined input variable and file uploads) may +; be accepted. +; Default Value: -1 (Sum of max_input_vars and max_file_uploads) +;max_multipart_body_parts = 1500 + +; Maximum amount of memory a script may consume +; https://php.net/memory-limit +memory_limit = 128M + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Error handling and logging ; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; This directive informs PHP of which errors, warnings and notices you would like +; it to take action for. The recommended way of setting values for this +; directive is through the use of the error level constants and bitwise +; operators. The error level constants are below here for convenience as well as +; some common settings and their meanings. +; By default, PHP is set to take action on all errors, notices and warnings EXCEPT +; those related to E_NOTICE and E_STRICT, which together cover best practices and +; recommended coding standards in PHP. For performance reasons, this is the +; recommend error reporting setting. Your production server shouldn't be wasting +; resources complaining about best practices and coding standards. That's what +; development servers and development settings are for. +; Note: The php.ini-development file has this setting as E_ALL. This +; means it pretty much reports everything which is exactly what you want during +; development and early testing. +; +; Error Level Constants: +; E_ALL - All errors and warnings +; E_ERROR - fatal run-time errors +; E_RECOVERABLE_ERROR - almost fatal run-time errors +; E_WARNING - run-time warnings (non-fatal errors) +; E_PARSE - compile-time parse errors +; E_NOTICE - run-time notices (these are warnings which often result +; from a bug in your code, but it's possible that it was +; intentional (e.g., using an uninitialized variable and +; relying on the fact it is automatically initialized to an +; empty string) +; E_STRICT - run-time notices, enable to have PHP suggest changes +; to your code which will ensure the best interoperability +; and forward compatibility of your code +; E_CORE_ERROR - fatal errors that occur during PHP's initial startup +; E_CORE_WARNING - warnings (non-fatal errors) that occur during PHP's +; initial startup +; E_COMPILE_ERROR - fatal compile-time errors +; E_COMPILE_WARNING - compile-time warnings (non-fatal errors) +; E_USER_ERROR - user-generated error message +; E_USER_WARNING - user-generated warning message +; E_USER_NOTICE - user-generated notice message +; E_DEPRECATED - warn about code that will not work in future versions +; of PHP +; E_USER_DEPRECATED - user-generated deprecation warnings +; +; Common Values: +; E_ALL (Show all errors, warnings and notices including coding standards.) +; E_ALL & ~E_NOTICE (Show all errors, except for notices) +; E_ALL & ~E_NOTICE & ~E_STRICT (Show all errors, except for notices and coding standards warnings.) +; E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR (Show only errors) +; Default Value: E_ALL +; Development Value: E_ALL +; Production Value: E_ALL & ~E_DEPRECATED & ~E_STRICT +; https://php.net/error-reporting +error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT + +; This directive controls whether or not and where PHP will output errors, +; notices and warnings too. Error output is very useful during development, but +; it could be very dangerous in production environments. Depending on the code +; which is triggering the error, sensitive information could potentially leak +; out of your application such as database usernames and passwords or worse. +; For production environments, we recommend logging errors rather than +; sending them to STDOUT. +; Possible Values: +; Off = Do not display any errors +; stderr = Display errors to STDERR (affects only CGI/CLI binaries!) +; On or stdout = Display errors to STDOUT +; Default Value: On +; Development Value: On +; Production Value: Off +; https://php.net/display-errors +display_errors = Off + +; The display of errors which occur during PHP's startup sequence are handled +; separately from display_errors. We strongly recommend you set this to 'off' +; for production servers to avoid leaking configuration details. +; Default Value: On +; Development Value: On +; Production Value: Off +; https://php.net/display-startup-errors +display_startup_errors = Off + +; Besides displaying errors, PHP can also log errors to locations such as a +; server-specific log, STDERR, or a location specified by the error_log +; directive found below. While errors should not be displayed on productions +; servers they should still be monitored and logging is a great way to do that. +; Default Value: Off +; Development Value: On +; Production Value: On +; https://php.net/log-errors +log_errors = On + +; Do not log repeated messages. Repeated errors must occur in same file on same +; line unless ignore_repeated_source is set true. +; https://php.net/ignore-repeated-errors +ignore_repeated_errors = Off + +; Ignore source of message when ignoring repeated messages. When this setting +; is On you will not log errors with repeated messages from different files or +; source lines. +; https://php.net/ignore-repeated-source +ignore_repeated_source = Off + +; If this parameter is set to Off, then memory leaks will not be shown (on +; stdout or in the log). This is only effective in a debug compile, and if +; error reporting includes E_WARNING in the allowed list +; https://php.net/report-memleaks +report_memleaks = On + +; This setting is off by default. +;report_zend_debug = 0 + +; Turn off normal error reporting and emit XML-RPC error XML +; https://php.net/xmlrpc-errors +;xmlrpc_errors = 0 + +; An XML-RPC faultCode +;xmlrpc_error_number = 0 + +; When PHP displays or logs an error, it has the capability of formatting the +; error message as HTML for easier reading. This directive controls whether +; the error message is formatted as HTML or not. +; Note: This directive is hardcoded to Off for the CLI SAPI +; https://php.net/html-errors +;html_errors = On + +; If html_errors is set to On *and* docref_root is not empty, then PHP +; produces clickable error messages that direct to a page describing the error +; or function causing the error in detail. +; You can download a copy of the PHP manual from https://php.net/docs +; and change docref_root to the base URL of your local copy including the +; leading '/'. You must also specify the file extension being used including +; the dot. PHP's default behavior is to leave these settings empty, in which +; case no links to documentation are generated. +; Note: Never use this feature for production boxes. +; https://php.net/docref-root +; Examples +;docref_root = "/phpmanual/" + +; https://php.net/docref-ext +;docref_ext = .html + +; String to output before an error message. PHP's default behavior is to leave +; this setting blank. +; https://php.net/error-prepend-string +; Example: +;error_prepend_string = "" + +; String to output after an error message. PHP's default behavior is to leave +; this setting blank. +; https://php.net/error-append-string +; Example: +;error_append_string = "" + +; Log errors to specified file. PHP's default behavior is to leave this value +; empty. +; https://php.net/error-log +; Example: +;error_log = php_errors.log +; Log errors to syslog (Event Log on Windows). +;error_log = syslog + +; The syslog ident is a string which is prepended to every message logged +; to syslog. Only used when error_log is set to syslog. +;syslog.ident = php + +; The syslog facility is used to specify what type of program is logging +; the message. Only used when error_log is set to syslog. +;syslog.facility = user + +; Set this to disable filtering control characters (the default). +; Some loggers only accept NVT-ASCII, others accept anything that's not +; control characters. If your logger accepts everything, then no filtering +; is needed at all. +; Allowed values are: +; ascii (all printable ASCII characters and NL) +; no-ctrl (all characters except control characters) +; all (all characters) +; raw (like "all", but messages are not split at newlines) +; https://php.net/syslog.filter +;syslog.filter = ascii + +;windows.show_crt_warning +; Default value: 0 +; Development value: 0 +; Production value: 0 + +;;;;;;;;;;;;;;;;; +; Data Handling ; +;;;;;;;;;;;;;;;;; + +; The separator used in PHP generated URLs to separate arguments. +; PHP's default setting is "&". +; https://php.net/arg-separator.output +; Example: +;arg_separator.output = "&" + +; List of separator(s) used by PHP to parse input URLs into variables. +; PHP's default setting is "&". +; NOTE: Every character in this directive is considered as separator! +; https://php.net/arg-separator.input +; Example: +;arg_separator.input = ";&" + +; This directive determines which super global arrays are registered when PHP +; starts up. G,P,C,E & S are abbreviations for the following respective super +; globals: GET, POST, COOKIE, ENV and SERVER. There is a performance penalty +; paid for the registration of these arrays and because ENV is not as commonly +; used as the others, ENV is not recommended on productions servers. You +; can still get access to the environment variables through getenv() should you +; need to. +; Default Value: "EGPCS" +; Development Value: "GPCS" +; Production Value: "GPCS"; +; https://php.net/variables-order +variables_order = "GPCS" + +; This directive determines which super global data (G,P & C) should be +; registered into the super global array REQUEST. If so, it also determines +; the order in which that data is registered. The values for this directive +; are specified in the same manner as the variables_order directive, +; EXCEPT one. Leaving this value empty will cause PHP to use the value set +; in the variables_order directive. It does not mean it will leave the super +; globals array REQUEST empty. +; Default Value: None +; Development Value: "GP" +; Production Value: "GP" +; https://php.net/request-order +request_order = "GP" + +; This directive determines whether PHP registers $argv & $argc each time it +; runs. $argv contains an array of all the arguments passed to PHP when a script +; is invoked. $argc contains an integer representing the number of arguments +; that were passed when the script was invoked. These arrays are extremely +; useful when running scripts from the command line. When this directive is +; enabled, registering these variables consumes CPU cycles and memory each time +; a script is executed. For performance reasons, this feature should be disabled +; on production servers. +; Note: This directive is hardcoded to On for the CLI SAPI +; Default Value: On +; Development Value: Off +; Production Value: Off +; https://php.net/register-argc-argv +register_argc_argv = Off + +; When enabled, the ENV, REQUEST and SERVER variables are created when they're +; first used (Just In Time) instead of when the script starts. If these +; variables are not used within a script, having this directive on will result +; in a performance gain. The PHP directive register_argc_argv must be disabled +; for this directive to have any effect. +; https://php.net/auto-globals-jit +auto_globals_jit = On + +; Whether PHP will read the POST data. +; This option is enabled by default. +; Most likely, you won't want to disable this option globally. It causes $_POST +; and $_FILES to always be empty; the only way you will be able to read the +; POST data will be through the php://input stream wrapper. This can be useful +; to proxy requests or to process the POST data in a memory efficient fashion. +; https://php.net/enable-post-data-reading +;enable_post_data_reading = Off + +; Maximum size of POST data that PHP will accept. +; Its value may be 0 to disable the limit. It is ignored if POST data reading +; is disabled through enable_post_data_reading. +; https://php.net/post-max-size +post_max_size = 64M + +; Automatically add files before PHP document. +; https://php.net/auto-prepend-file +auto_prepend_file = + +; Automatically add files after PHP document. +; https://php.net/auto-append-file +auto_append_file = + +; By default, PHP will output a media type using the Content-Type header. To +; disable this, simply set it to be empty. +; +; PHP's built-in default media type is set to text/html. +; https://php.net/default-mimetype +default_mimetype = "text/html" + +; PHP's default character set is set to UTF-8. +; https://php.net/default-charset +default_charset = "UTF-8" + +; PHP internal character encoding is set to empty. +; If empty, default_charset is used. +; https://php.net/internal-encoding +;internal_encoding = + +; PHP input character encoding is set to empty. +; If empty, default_charset is used. +; https://php.net/input-encoding +;input_encoding = + +; PHP output character encoding is set to empty. +; If empty, default_charset is used. +; See also output_buffer. +; https://php.net/output-encoding +;output_encoding = + +;;;;;;;;;;;;;;;;;;;;;;;;; +; Paths and Directories ; +;;;;;;;;;;;;;;;;;;;;;;;;; + +; UNIX: "/path1:/path2" +;include_path = ".:/php/includes" +; +; Windows: "\path1;\path2" +;include_path = ".;c:\php\includes" +; +; PHP's default setting for include_path is ".;/path/to/php/pear" +; https://php.net/include-path + +; The root of the PHP pages, used only if nonempty. +; if PHP was not compiled with FORCE_REDIRECT, you SHOULD set doc_root +; if you are running php as a CGI under any web server (other than IIS) +; see documentation for security issues. The alternate is to use the +; cgi.force_redirect configuration below +; https://php.net/doc-root +doc_root = + +; The directory under which PHP opens the script using /~username used only +; if nonempty. +; https://php.net/user-dir +user_dir = + +; Directory in which the loadable extensions (modules) reside. +; https://php.net/extension-dir +;extension_dir = "./" +; On windows: +;extension_dir = "ext" + +; Directory where the temporary files should be placed. +; Defaults to the system default (see sys_get_temp_dir) +;sys_temp_dir = "/tmp" + +; Whether or not to enable the dl() function. The dl() function does NOT work +; properly in multithreaded servers, such as IIS or Zeus, and is automatically +; disabled on them. +; https://php.net/enable-dl +enable_dl = Off + +; cgi.force_redirect is necessary to provide security running PHP as a CGI under +; most web servers. Left undefined, PHP turns this on by default. You can +; turn it off here AT YOUR OWN RISK +; **You CAN safely turn this off for IIS, in fact, you MUST.** +; https://php.net/cgi.force-redirect +;cgi.force_redirect = 1 + +; if cgi.nph is enabled it will force cgi to always sent Status: 200 with +; every request. PHP's default behavior is to disable this feature. +;cgi.nph = 1 + +; if cgi.force_redirect is turned on, and you are not running under Apache or Netscape +; (iPlanet) web servers, you MAY need to set an environment variable name that PHP +; will look for to know it is OK to continue execution. Setting this variable MAY +; cause security issues, KNOW WHAT YOU ARE DOING FIRST. +; https://php.net/cgi.redirect-status-env +;cgi.redirect_status_env = + +; cgi.fix_pathinfo provides *real* PATH_INFO/PATH_TRANSLATED support for CGI. PHP's +; previous behaviour was to set PATH_TRANSLATED to SCRIPT_FILENAME, and to not grok +; what PATH_INFO is. For more information on PATH_INFO, see the cgi specs. Setting +; this to 1 will cause PHP CGI to fix its paths to conform to the spec. A setting +; of zero causes PHP to behave as before. Default is 1. You should fix your scripts +; to use SCRIPT_FILENAME rather than PATH_TRANSLATED. +; https://php.net/cgi.fix-pathinfo +;cgi.fix_pathinfo=1 + +; if cgi.discard_path is enabled, the PHP CGI binary can safely be placed outside +; of the web tree and people will not be able to circumvent .htaccess security. +;cgi.discard_path=1 + +; FastCGI under IIS supports the ability to impersonate +; security tokens of the calling client. This allows IIS to define the +; security context that the request runs under. mod_fastcgi under Apache +; does not currently support this feature (03/17/2002) +; Set to 1 if running under IIS. Default is zero. +; https://php.net/fastcgi.impersonate +;fastcgi.impersonate = 1 + +; Disable logging through FastCGI connection. PHP's default behavior is to enable +; this feature. +;fastcgi.logging = 0 + +; cgi.rfc2616_headers configuration option tells PHP what type of headers to +; use when sending HTTP response code. If set to 0, PHP sends Status: header that +; is supported by Apache. When this option is set to 1, PHP will send +; RFC2616 compliant header. +; Default is zero. +; https://php.net/cgi.rfc2616-headers +;cgi.rfc2616_headers = 0 + +; cgi.check_shebang_line controls whether CGI PHP checks for line starting with #! +; (shebang) at the top of the running script. This line might be needed if the +; script support running both as stand-alone script and via PHP CGI<. PHP in CGI +; mode skips this line and ignores its content if this directive is turned on. +; https://php.net/cgi.check-shebang-line +;cgi.check_shebang_line=1 + +;;;;;;;;;;;;;;;; +; File Uploads ; +;;;;;;;;;;;;;;;; + +; Whether to allow HTTP file uploads. +; https://php.net/file-uploads +file_uploads = On + +; Temporary directory for HTTP uploaded files (will use system default if not +; specified). +; https://php.net/upload-tmp-dir +;upload_tmp_dir = + +; Maximum allowed size for uploaded files. +; https://php.net/upload-max-filesize +upload_max_filesize = 64M + +; Maximum number of files that can be uploaded via a single request +max_file_uploads = 20 + +;;;;;;;;;;;;;;;;;; +; Fopen wrappers ; +;;;;;;;;;;;;;;;;;; + +; Whether to allow the treatment of URLs (like http:// or ftp://) as files. +; https://php.net/allow-url-fopen +allow_url_fopen = On + +; Whether to allow include/require to open URLs (like https:// or ftp://) as files. +; https://php.net/allow-url-include +allow_url_include = Off + +; Define the anonymous ftp password (your email address). PHP's default setting +; for this is empty. +; https://php.net/from +;from="john@doe.com" + +; Define the User-Agent string. PHP's default setting for this is empty. +; https://php.net/user-agent +;user_agent="PHP" + +; Default timeout for socket based streams (seconds) +; https://php.net/default-socket-timeout +default_socket_timeout = 60 + +; If your scripts have to deal with files from Macintosh systems, +; or you are running on a Mac and need to deal with files from +; unix or win32 systems, setting this flag will cause PHP to +; automatically detect the EOL character in those files so that +; fgets() and file() will work regardless of the source of the file. +; https://php.net/auto-detect-line-endings +;auto_detect_line_endings = Off + +;;;;;;;;;;;;;;;;;;;;;; +; Dynamic Extensions ; +;;;;;;;;;;;;;;;;;;;;;; + +; If you wish to have an extension loaded automatically, use the following +; syntax: +; +; extension=modulename +; +; For example: +; +; extension=mysqli +; +; When the extension library to load is not located in the default extension +; directory, You may specify an absolute path to the library file: +; +; extension=/path/to/extension/mysqli.so +; +; Note : The syntax used in previous PHP versions ('extension=.so' and +; 'extension='php_.dll') is supported for legacy reasons and may be +; deprecated in a future PHP major version. So, when it is possible, please +; move to the new ('extension=) syntax. +; +; Notes for Windows environments : +; +; - Many DLL files are located in the ext/ +; extension folders as well as the separate PECL DLL download. +; Be sure to appropriately set the extension_dir directive. +; +;extension=bz2 + +; The ldap extension must be before curl if OpenSSL 1.0.2 and OpenLDAP is used +; otherwise it results in segfault when unloading after using SASL. +; See https://github.com/php/php-src/issues/8620 for more info. +;extension=ldap + +;extension=curl +;extension=ffi +;extension=ftp +;extension=fileinfo +;extension=gd +;extension=gettext +;extension=gmp +;extension=intl +;extension=imap +;extension=mbstring +;extension=exif ; Must be after mbstring as it depends on it +;extension=mysqli +;extension=oci8_12c ; Use with Oracle Database 12c Instant Client +;extension=oci8_19 ; Use with Oracle Database 19 Instant Client +;extension=odbc +;extension=openssl +;extension=pdo_firebird +;extension=pdo_mysql +;extension=pdo_oci +;extension=pdo_odbc +;extension=pdo_pgsql +;extension=pdo_sqlite +;extension=pgsql +;extension=shmop + +; The MIBS data available in the PHP distribution must be installed. +; See https://www.php.net/manual/en/snmp.installation.php +;extension=snmp + +;extension=soap +;extension=sockets +;extension=sodium +;extension=sqlite3 +;extension=tidy +;extension=xsl +;extension=zip + +;zend_extension=opcache + +;;;;;;;;;;;;;;;;;;; +; Module Settings ; +;;;;;;;;;;;;;;;;;;; + +[CLI Server] +; Whether the CLI web server uses ANSI color coding in its terminal output. +cli_server.color = On + +[Date] +; Defines the default timezone used by the date functions +; https://php.net/date.timezone +date.timezone = "Europe/Warsaw" + +; https://php.net/date.default-latitude +;date.default_latitude = 31.7667 + +; https://php.net/date.default-longitude +;date.default_longitude = 35.2333 + +; https://php.net/date.sunrise-zenith +;date.sunrise_zenith = 90.833333 + +; https://php.net/date.sunset-zenith +;date.sunset_zenith = 90.833333 + +[filter] +; https://php.net/filter.default +;filter.default = unsafe_raw + +; https://php.net/filter.default-flags +;filter.default_flags = + +[iconv] +; Use of this INI entry is deprecated, use global input_encoding instead. +; If empty, default_charset or input_encoding or iconv.input_encoding is used. +; The precedence is: default_charset < input_encoding < iconv.input_encoding +;iconv.input_encoding = + +; Use of this INI entry is deprecated, use global internal_encoding instead. +; If empty, default_charset or internal_encoding or iconv.internal_encoding is used. +; The precedence is: default_charset < internal_encoding < iconv.internal_encoding +;iconv.internal_encoding = + +; Use of this INI entry is deprecated, use global output_encoding instead. +; If empty, default_charset or output_encoding or iconv.output_encoding is used. +; The precedence is: default_charset < output_encoding < iconv.output_encoding +; To use an output encoding conversion, iconv's output handler must be set +; otherwise output encoding conversion cannot be performed. +;iconv.output_encoding = + +[imap] +; rsh/ssh logins are disabled by default. Use this INI entry if you want to +; enable them. Note that the IMAP library does not filter mailbox names before +; passing them to rsh/ssh command, thus passing untrusted data to this function +; with rsh/ssh enabled is insecure. +;imap.enable_insecure_rsh=0 + +[intl] +;intl.default_locale = +; This directive allows you to produce PHP errors when some error +; happens within intl functions. The value is the level of the error produced. +; Default is 0, which does not produce any errors. +;intl.error_level = E_WARNING +;intl.use_exceptions = 0 + +[sqlite3] +; Directory pointing to SQLite3 extensions +; https://php.net/sqlite3.extension-dir +;sqlite3.extension_dir = + +; SQLite defensive mode flag (only available from SQLite 3.26+) +; When the defensive flag is enabled, language features that allow ordinary +; SQL to deliberately corrupt the database file are disabled. This forbids +; writing directly to the schema, shadow tables (eg. FTS data tables), or +; the sqlite_dbpage virtual table. +; https://www.sqlite.org/c3ref/c_dbconfig_defensive.html +; (for older SQLite versions, this flag has no use) +;sqlite3.defensive = 1 + +[Pcre] +; PCRE library backtracking limit. +; https://php.net/pcre.backtrack-limit +;pcre.backtrack_limit=100000 + +; PCRE library recursion limit. +; Please note that if you set this value to a high number you may consume all +; the available process stack and eventually crash PHP (due to reaching the +; stack size limit imposed by the Operating System). +; https://php.net/pcre.recursion-limit +;pcre.recursion_limit=100000 + +; Enables or disables JIT compilation of patterns. This requires the PCRE +; library to be compiled with JIT support. +;pcre.jit=1 + +[Pdo] +; Whether to pool ODBC connections. Can be one of "strict", "relaxed" or "off" +; https://php.net/pdo-odbc.connection-pooling +;pdo_odbc.connection_pooling=strict + +[Pdo_mysql] +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +pdo_mysql.default_socket= + +[Phar] +; https://php.net/phar.readonly +;phar.readonly = On + +; https://php.net/phar.require-hash +;phar.require_hash = On + +;phar.cache_list = + +[mail function] +; For Win32 only. +; https://php.net/smtp +SMTP = localhost +; https://php.net/smtp-port +smtp_port = 25 + +; For Win32 only. +; https://php.net/sendmail-from +;sendmail_from = me@example.com + +; For Unix only. You may supply arguments as well (default: "sendmail -t -i"). +; https://php.net/sendmail-path +;sendmail_path = + +; Force the addition of the specified parameters to be passed as extra parameters +; to the sendmail binary. These parameters will always replace the value of +; the 5th parameter to mail(). +;mail.force_extra_parameters = + +; Add X-PHP-Originating-Script: that will include uid of the script followed by the filename +mail.add_x_header = On + +; Use mixed LF and CRLF line separators to keep compatibility with some +; RFC 2822 non conformant MTA. +mail.mixed_lf_and_crlf = Off + +; The path to a log file that will log all mail() calls. Log entries include +; the full path of the script, line number, To address and headers. +;mail.log = +; Log mail to syslog (Event Log on Windows). +;mail.log = syslog + +[ODBC] +; https://php.net/odbc.default-db +;odbc.default_db = Not yet implemented + +; https://php.net/odbc.default-user +;odbc.default_user = Not yet implemented + +; https://php.net/odbc.default-pw +;odbc.default_pw = Not yet implemented + +; Controls the ODBC cursor model. +; Default: SQL_CURSOR_STATIC (default). +;odbc.default_cursortype + +; Allow or prevent persistent links. +; https://php.net/odbc.allow-persistent +odbc.allow_persistent = On + +; Check that a connection is still valid before reuse. +; https://php.net/odbc.check-persistent +odbc.check_persistent = On + +; Maximum number of persistent links. -1 means no limit. +; https://php.net/odbc.max-persistent +odbc.max_persistent = -1 + +; Maximum number of links (persistent + non-persistent). -1 means no limit. +; https://php.net/odbc.max-links +odbc.max_links = -1 + +; Handling of LONG fields. Returns number of bytes to variables. 0 means +; passthru. +; https://php.net/odbc.defaultlrl +odbc.defaultlrl = 4096 + +; Handling of binary data. 0 means passthru, 1 return as is, 2 convert to char. +; See the documentation on odbc_binmode and odbc_longreadlen for an explanation +; of odbc.defaultlrl and odbc.defaultbinmode +; https://php.net/odbc.defaultbinmode +odbc.defaultbinmode = 1 + +[MySQLi] + +; Maximum number of persistent links. -1 means no limit. +; https://php.net/mysqli.max-persistent +mysqli.max_persistent = -1 + +; Allow accessing, from PHP's perspective, local files with LOAD DATA statements +; https://php.net/mysqli.allow_local_infile +mysqli.allow_local_infile = Off + +; It allows the user to specify a folder where files that can be sent via LOAD DATA +; LOCAL can exist. It is ignored if mysqli.allow_local_infile is enabled. +;mysqli.local_infile_directory = + +; Allow or prevent persistent links. +; https://php.net/mysqli.allow-persistent +mysqli.allow_persistent = On + +; Maximum number of links. -1 means no limit. +; https://php.net/mysqli.max-links +mysqli.max_links = -1 + +; Default port number for mysqli_connect(). If unset, mysqli_connect() will use +; the $MYSQL_TCP_PORT or the mysql-tcp entry in /etc/services or the +; compile-time value defined MYSQL_PORT (in that order). Win32 will only look +; at MYSQL_PORT. +; https://php.net/mysqli.default-port +mysqli.default_port = 3306 + +; Default socket name for local MySQL connects. If empty, uses the built-in +; MySQL defaults. +; https://php.net/mysqli.default-socket +mysqli.default_socket = + +; Default host for mysqli_connect() (doesn't apply in safe mode). +; https://php.net/mysqli.default-host +mysqli.default_host = + +; Default user for mysqli_connect() (doesn't apply in safe mode). +; https://php.net/mysqli.default-user +mysqli.default_user = + +; Default password for mysqli_connect() (doesn't apply in safe mode). +; Note that this is generally a *bad* idea to store passwords in this file. +; *Any* user with PHP access can run 'echo get_cfg_var("mysqli.default_pw") +; and reveal this password! And of course, any users with read access to this +; file will be able to reveal the password as well. +; https://php.net/mysqli.default-pw +mysqli.default_pw = + +; If this option is enabled, closing a persistent connection will rollback +; any pending transactions of this connection, before it is put back +; into the persistent connection pool. +;mysqli.rollback_on_cached_plink = Off + +[mysqlnd] +; Enable / Disable collection of general statistics by mysqlnd which can be +; used to tune and monitor MySQL operations. +mysqlnd.collect_statistics = On + +; Enable / Disable collection of memory usage statistics by mysqlnd which can be +; used to tune and monitor MySQL operations. +mysqlnd.collect_memory_statistics = Off + +; Records communication from all extensions using mysqlnd to the specified log +; file. +; https://php.net/mysqlnd.debug +;mysqlnd.debug = + +; Defines which queries will be logged. +;mysqlnd.log_mask = 0 + +; Default size of the mysqlnd memory pool, which is used by result sets. +;mysqlnd.mempool_default_size = 16000 + +; Size of a pre-allocated buffer used when sending commands to MySQL in bytes. +;mysqlnd.net_cmd_buffer_size = 2048 + +; Size of a pre-allocated buffer used for reading data sent by the server in +; bytes. +;mysqlnd.net_read_buffer_size = 32768 + +; Timeout for network requests in seconds. +;mysqlnd.net_read_timeout = 31536000 + +; SHA-256 Authentication Plugin related. File with the MySQL server public RSA +; key. +;mysqlnd.sha256_server_public_key = + +[OCI8] + +; Connection: Enables privileged connections using external +; credentials (OCI_SYSOPER, OCI_SYSDBA) +; https://php.net/oci8.privileged-connect +;oci8.privileged_connect = Off + +; Connection: The maximum number of persistent OCI8 connections per +; process. Using -1 means no limit. +; https://php.net/oci8.max-persistent +;oci8.max_persistent = -1 + +; Connection: The maximum number of seconds a process is allowed to +; maintain an idle persistent connection. Using -1 means idle +; persistent connections will be maintained forever. +; https://php.net/oci8.persistent-timeout +;oci8.persistent_timeout = -1 + +; Connection: The number of seconds that must pass before issuing a +; ping during oci_pconnect() to check the connection validity. When +; set to 0, each oci_pconnect() will cause a ping. Using -1 disables +; pings completely. +; https://php.net/oci8.ping-interval +;oci8.ping_interval = 60 + +; Connection: Set this to a user chosen connection class to be used +; for all pooled server requests with Oracle Database Resident +; Connection Pooling (DRCP). To use DRCP, this value should be set to +; the same string for all web servers running the same application, +; the database pool must be configured, and the connection string must +; specify to use a pooled server. +;oci8.connection_class = + +; High Availability: Using On lets PHP receive Fast Application +; Notification (FAN) events generated when a database node fails. The +; database must also be configured to post FAN events. +;oci8.events = Off + +; Tuning: This option enables statement caching, and specifies how +; many statements to cache. Using 0 disables statement caching. +; https://php.net/oci8.statement-cache-size +;oci8.statement_cache_size = 20 + +; Tuning: Enables row prefetching and sets the default number of +; rows that will be fetched automatically after statement execution. +; https://php.net/oci8.default-prefetch +;oci8.default_prefetch = 100 + +; Tuning: Sets the amount of LOB data that is internally returned from +; Oracle Database when an Oracle LOB locator is initially retrieved as +; part of a query. Setting this can improve performance by reducing +; round-trips. +; https://php.net/oci8.prefetch-lob-size +; oci8.prefetch_lob_size = 0 + +; Compatibility. Using On means oci_close() will not close +; oci_connect() and oci_new_connect() connections. +; https://php.net/oci8.old-oci-close-semantics +;oci8.old_oci_close_semantics = Off + +[PostgreSQL] +; Allow or prevent persistent links. +; https://php.net/pgsql.allow-persistent +pgsql.allow_persistent = On + +; Detect broken persistent links always with pg_pconnect(). +; Auto reset feature requires a little overheads. +; https://php.net/pgsql.auto-reset-persistent +pgsql.auto_reset_persistent = Off + +; Maximum number of persistent links. -1 means no limit. +; https://php.net/pgsql.max-persistent +pgsql.max_persistent = -1 + +; Maximum number of links (persistent+non persistent). -1 means no limit. +; https://php.net/pgsql.max-links +pgsql.max_links = -1 + +; Ignore PostgreSQL backends Notice message or not. +; Notice message logging require a little overheads. +; https://php.net/pgsql.ignore-notice +pgsql.ignore_notice = 0 + +; Log PostgreSQL backends Notice message or not. +; Unless pgsql.ignore_notice=0, module cannot log notice message. +; https://php.net/pgsql.log-notice +pgsql.log_notice = 0 + +[bcmath] +; Number of decimal digits for all bcmath functions. +; https://php.net/bcmath.scale +bcmath.scale = 0 + +[browscap] +; https://php.net/browscap +;browscap = extra/browscap.ini + +[Session] +; Handler used to store/retrieve data. +; https://php.net/session.save-handler +session.save_handler = files + +; Argument passed to save_handler. In the case of files, this is the path +; where data files are stored. Note: Windows users have to change this +; variable in order to use PHP's session functions. +; +; The path can be defined as: +; +; session.save_path = "N;/path" +; +; where N is an integer. Instead of storing all the session files in +; /path, what this will do is use subdirectories N-levels deep, and +; store the session data in those directories. This is useful if +; your OS has problems with many files in one directory, and is +; a more efficient layout for servers that handle many sessions. +; +; NOTE 1: PHP will not create this directory structure automatically. +; You can use the script in the ext/session dir for that purpose. +; NOTE 2: See the section on garbage collection below if you choose to +; use subdirectories for session storage +; +; The file storage module creates files using mode 600 by default. +; You can change that by using +; +; session.save_path = "N;MODE;/path" +; +; where MODE is the octal representation of the mode. Note that this +; does not overwrite the process's umask. +; https://php.net/session.save-path +;session.save_path = "/tmp" + +; Whether to use strict session mode. +; Strict session mode does not accept an uninitialized session ID, and +; regenerates the session ID if the browser sends an uninitialized session ID. +; Strict mode protects applications from session fixation via a session adoption +; vulnerability. It is disabled by default for maximum compatibility, but +; enabling it is encouraged. +; https://wiki.php.net/rfc/strict_sessions +session.use_strict_mode = 0 + +; Whether to use cookies. +; https://php.net/session.use-cookies +session.use_cookies = 1 + +; https://php.net/session.cookie-secure +;session.cookie_secure = + +; This option forces PHP to fetch and use a cookie for storing and maintaining +; the session id. We encourage this operation as it's very helpful in combating +; session hijacking when not specifying and managing your own session id. It is +; not the be-all and end-all of session hijacking defense, but it's a good start. +; https://php.net/session.use-only-cookies +session.use_only_cookies = 1 + +; Name of the session (used as cookie name). +; https://php.net/session.name +session.name = PHPSESSID + +; Initialize session on request startup. +; https://php.net/session.auto-start +session.auto_start = 0 + +; Lifetime in seconds of cookie or, if 0, until browser is restarted. +; https://php.net/session.cookie-lifetime +session.cookie_lifetime = 0 + +; The path for which the cookie is valid. +; https://php.net/session.cookie-path +session.cookie_path = / + +; The domain for which the cookie is valid. +; https://php.net/session.cookie-domain +session.cookie_domain = + +; Whether or not to add the httpOnly flag to the cookie, which makes it +; inaccessible to browser scripting languages such as JavaScript. +; https://php.net/session.cookie-httponly +session.cookie_httponly = + +; Add SameSite attribute to cookie to help mitigate Cross-Site Request Forgery (CSRF/XSRF) +; Current valid values are "Strict", "Lax" or "None". When using "None", +; make sure to include the quotes, as `none` is interpreted like `false` in ini files. +; https://tools.ietf.org/html/draft-west-first-party-cookies-07 +session.cookie_samesite = + +; Handler used to serialize data. php is the standard serializer of PHP. +; https://php.net/session.serialize-handler +session.serialize_handler = php + +; Defines the probability that the 'garbage collection' process is started on every +; session initialization. The probability is calculated by using gc_probability/gc_divisor, +; e.g. 1/100 means there is a 1% chance that the GC process starts on each request. +; Default Value: 1 +; Development Value: 1 +; Production Value: 1 +; https://php.net/session.gc-probability +session.gc_probability = 1 + +; Defines the probability that the 'garbage collection' process is started on every +; session initialization. The probability is calculated by using gc_probability/gc_divisor, +; e.g. 1/100 means there is a 1% chance that the GC process starts on each request. +; For high volume production servers, using a value of 1000 is a more efficient approach. +; Default Value: 100 +; Development Value: 1000 +; Production Value: 1000 +; https://php.net/session.gc-divisor +session.gc_divisor = 1000 + +; After this number of seconds, stored data will be seen as 'garbage' and +; cleaned up by the garbage collection process. +; https://php.net/session.gc-maxlifetime +session.gc_maxlifetime = 1440 + +; NOTE: If you are using the subdirectory option for storing session files +; (see session.save_path above), then garbage collection does *not* +; happen automatically. You will need to do your own garbage +; collection through a shell script, cron entry, or some other method. +; For example, the following script is the equivalent of setting +; session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes): +; find /path/to/sessions -cmin +24 -type f | xargs rm + +; Check HTTP Referer to invalidate externally stored URLs containing ids. +; HTTP_REFERER has to contain this substring for the session to be +; considered as valid. +; https://php.net/session.referer-check +session.referer_check = + +; Set to {nocache,private,public,} to determine HTTP caching aspects +; or leave this empty to avoid sending anti-caching headers. +; https://php.net/session.cache-limiter +session.cache_limiter = nocache + +; Document expires after n minutes. +; https://php.net/session.cache-expire +session.cache_expire = 180 + +; trans sid support is disabled by default. +; Use of trans sid may risk your users' security. +; Use this option with caution. +; - User may send URL contains active session ID +; to other person via. email/irc/etc. +; - URL that contains active session ID may be stored +; in publicly accessible computer. +; - User may access your site with the same session ID +; always using URL stored in browser's history or bookmarks. +; https://php.net/session.use-trans-sid +session.use_trans_sid = 0 + +; Set session ID character length. This value could be between 22 to 256. +; Shorter length than default is supported only for compatibility reason. +; Users should use 32 or more chars. +; https://php.net/session.sid-length +; Default Value: 32 +; Development Value: 26 +; Production Value: 26 +session.sid_length = 26 + +; The URL rewriter will look for URLs in a defined set of HTML tags. +;
is special; if you include them here, the rewriter will +; add a hidden field with the info which is otherwise appended +; to URLs. tag's action attribute URL will not be modified +; unless it is specified. +; Note that all valid entries require a "=", even if no value follows. +; Default Value: "a=href,area=href,frame=src,form=" +; Development Value: "a=href,area=href,frame=src,form=" +; Production Value: "a=href,area=href,frame=src,form=" +; https://php.net/url-rewriter.tags +session.trans_sid_tags = "a=href,area=href,frame=src,form=" + +; URL rewriter does not rewrite absolute URLs by default. +; To enable rewrites for absolute paths, target hosts must be specified +; at RUNTIME. i.e. use ini_set() +; tags is special. PHP will check action attribute's URL regardless +; of session.trans_sid_tags setting. +; If no host is defined, HTTP_HOST will be used for allowed host. +; Example value: php.net,www.php.net,wiki.php.net +; Use "," for multiple hosts. No spaces are allowed. +; Default Value: "" +; Development Value: "" +; Production Value: "" +;session.trans_sid_hosts="" + +; Define how many bits are stored in each character when converting +; the binary hash data to something readable. +; Possible values: +; 4 (4 bits: 0-9, a-f) +; 5 (5 bits: 0-9, a-v) +; 6 (6 bits: 0-9, a-z, A-Z, "-", ",") +; Default Value: 4 +; Development Value: 5 +; Production Value: 5 +; https://php.net/session.hash-bits-per-character +session.sid_bits_per_character = 5 + +; Enable upload progress tracking in $_SESSION +; Default Value: On +; Development Value: On +; Production Value: On +; https://php.net/session.upload-progress.enabled +;session.upload_progress.enabled = On + +; Cleanup the progress information as soon as all POST data has been read +; (i.e. upload completed). +; Default Value: On +; Development Value: On +; Production Value: On +; https://php.net/session.upload-progress.cleanup +;session.upload_progress.cleanup = On + +; A prefix used for the upload progress key in $_SESSION +; Default Value: "upload_progress_" +; Development Value: "upload_progress_" +; Production Value: "upload_progress_" +; https://php.net/session.upload-progress.prefix +;session.upload_progress.prefix = "upload_progress_" + +; The index name (concatenated with the prefix) in $_SESSION +; containing the upload progress information +; Default Value: "PHP_SESSION_UPLOAD_PROGRESS" +; Development Value: "PHP_SESSION_UPLOAD_PROGRESS" +; Production Value: "PHP_SESSION_UPLOAD_PROGRESS" +; https://php.net/session.upload-progress.name +;session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS" + +; How frequently the upload progress should be updated. +; Given either in percentages (per-file), or in bytes +; Default Value: "1%" +; Development Value: "1%" +; Production Value: "1%" +; https://php.net/session.upload-progress.freq +;session.upload_progress.freq = "1%" + +; The minimum delay between updates, in seconds +; Default Value: 1 +; Development Value: 1 +; Production Value: 1 +; https://php.net/session.upload-progress.min-freq +;session.upload_progress.min_freq = "1" + +; Only write session data when session data is changed. Enabled by default. +; https://php.net/session.lazy-write +;session.lazy_write = On + +[Assertion] +; Switch whether to compile assertions at all (to have no overhead at run-time) +; -1: Do not compile at all +; 0: Jump over assertion at run-time +; 1: Execute assertions +; Changing from or to a negative value is only possible in php.ini! +; (For turning assertions on and off at run-time, toggle zend.assertions between the values 1 and 0) +; Default Value: 1 +; Development Value: 1 +; Production Value: -1 +; https://php.net/zend.assertions +zend.assertions = -1 + +[COM] +; path to a file containing GUIDs, IIDs or filenames of files with TypeLibs +; https://php.net/com.typelib-file +;com.typelib_file = + +; allow Distributed-COM calls +; https://php.net/com.allow-dcom +;com.allow_dcom = true + +; autoregister constants of a component's typelib on com_load() +; https://php.net/com.autoregister-typelib +;com.autoregister_typelib = true + +; register constants casesensitive +; https://php.net/com.autoregister-casesensitive +;com.autoregister_casesensitive = false + +; show warnings on duplicate constant registrations +; https://php.net/com.autoregister-verbose +;com.autoregister_verbose = true + +; The default character set code-page to use when passing strings to and from COM objects. +; Default: system ANSI code page +;com.code_page= + +; The version of the .NET framework to use. The value of the setting are the first three parts +; of the framework's version number, separated by dots, and prefixed with "v", e.g. "v4.0.30319". +;com.dotnet_version= + +[mbstring] +; language for internal character representation. +; This affects mb_send_mail() and mbstring.detect_order. +; https://php.net/mbstring.language +;mbstring.language = Japanese + +; Use of this INI entry is deprecated, use global internal_encoding instead. +; internal/script encoding. +; Some encoding cannot work as internal encoding. (e.g. SJIS, BIG5, ISO-2022-*) +; If empty, default_charset or internal_encoding or iconv.internal_encoding is used. +; The precedence is: default_charset < internal_encoding < iconv.internal_encoding +;mbstring.internal_encoding = + +; Use of this INI entry is deprecated, use global input_encoding instead. +; http input encoding. +; mbstring.encoding_translation = On is needed to use this setting. +; If empty, default_charset or input_encoding or mbstring.input is used. +; The precedence is: default_charset < input_encoding < mbstring.http_input +; https://php.net/mbstring.http-input +;mbstring.http_input = + +; Use of this INI entry is deprecated, use global output_encoding instead. +; http output encoding. +; mb_output_handler must be registered as output buffer to function. +; If empty, default_charset or output_encoding or mbstring.http_output is used. +; The precedence is: default_charset < output_encoding < mbstring.http_output +; To use an output encoding conversion, mbstring's output handler must be set +; otherwise output encoding conversion cannot be performed. +; https://php.net/mbstring.http-output +;mbstring.http_output = + +; enable automatic encoding translation according to +; mbstring.internal_encoding setting. Input chars are +; converted to internal encoding by setting this to On. +; Note: Do _not_ use automatic encoding translation for +; portable libs/applications. +; https://php.net/mbstring.encoding-translation +;mbstring.encoding_translation = Off + +; automatic encoding detection order. +; "auto" detect order is changed according to mbstring.language +; https://php.net/mbstring.detect-order +;mbstring.detect_order = auto + +; substitute_character used when character cannot be converted +; one from another +; https://php.net/mbstring.substitute-character +;mbstring.substitute_character = none + +; Enable strict encoding detection. +;mbstring.strict_detection = Off + +; This directive specifies the regex pattern of content types for which mb_output_handler() +; is activated. +; Default: mbstring.http_output_conv_mimetypes=^(text/|application/xhtml\+xml) +;mbstring.http_output_conv_mimetypes= + +; This directive specifies maximum stack depth for mbstring regular expressions. It is similar +; to the pcre.recursion_limit for PCRE. +;mbstring.regex_stack_limit=100000 + +; This directive specifies maximum retry count for mbstring regular expressions. It is similar +; to the pcre.backtrack_limit for PCRE. +;mbstring.regex_retry_limit=1000000 + +[gd] +; Tell the jpeg decode to ignore warnings and try to create +; a gd image. The warning will then be displayed as notices +; disabled by default +; https://php.net/gd.jpeg-ignore-warning +;gd.jpeg_ignore_warning = 1 + +[exif] +; Exif UNICODE user comments are handled as UCS-2BE/UCS-2LE and JIS as JIS. +; With mbstring support this will automatically be converted into the encoding +; given by corresponding encode setting. When empty mbstring.internal_encoding +; is used. For the decode settings you can distinguish between motorola and +; intel byte order. A decode setting cannot be empty. +; https://php.net/exif.encode-unicode +;exif.encode_unicode = ISO-8859-15 + +; https://php.net/exif.decode-unicode-motorola +;exif.decode_unicode_motorola = UCS-2BE + +; https://php.net/exif.decode-unicode-intel +;exif.decode_unicode_intel = UCS-2LE + +; https://php.net/exif.encode-jis +;exif.encode_jis = + +; https://php.net/exif.decode-jis-motorola +;exif.decode_jis_motorola = JIS + +; https://php.net/exif.decode-jis-intel +;exif.decode_jis_intel = JIS + +[Tidy] +; The path to a default tidy configuration file to use when using tidy +; https://php.net/tidy.default-config +;tidy.default_config = /usr/local/lib/php/default.tcfg + +; Should tidy clean and repair output automatically? +; WARNING: Do not use this option if you are generating non-html content +; such as dynamic images +; https://php.net/tidy.clean-output +tidy.clean_output = Off + +[soap] +; Enables or disables WSDL caching feature. +; https://php.net/soap.wsdl-cache-enabled +soap.wsdl_cache_enabled=1 + +; Sets the directory name where SOAP extension will put cache files. +; https://php.net/soap.wsdl-cache-dir +soap.wsdl_cache_dir="/tmp" + +; (time to live) Sets the number of second while cached file will be used +; instead of original one. +; https://php.net/soap.wsdl-cache-ttl +soap.wsdl_cache_ttl=86400 + +; Sets the size of the cache limit. (Max. number of WSDL files to cache) +soap.wsdl_cache_limit = 5 + +[sysvshm] +; A default size of the shared memory segment +;sysvshm.init_mem = 10000 + +[ldap] +; Sets the maximum number of open links or -1 for unlimited. +ldap.max_links = -1 + +[dba] +;dba.default_handler= + +[opcache] +; Determines if Zend OPCache is enabled +;opcache.enable=1 + +; Determines if Zend OPCache is enabled for the CLI version of PHP +;opcache.enable_cli=0 + +; The OPcache shared memory storage size. +;opcache.memory_consumption=128 + +; The amount of memory for interned strings in Mbytes. +;opcache.interned_strings_buffer=8 + +; The maximum number of keys (scripts) in the OPcache hash table. +; Only numbers between 200 and 1000000 are allowed. +;opcache.max_accelerated_files=10000 + +; The maximum percentage of "wasted" memory until a restart is scheduled. +;opcache.max_wasted_percentage=5 + +; When this directive is enabled, the OPcache appends the current working +; directory to the script key, thus eliminating possible collisions between +; files with the same name (basename). Disabling the directive improves +; performance, but may break existing applications. +;opcache.use_cwd=1 + +; When disabled, you must reset the OPcache manually or restart the +; webserver for changes to the filesystem to take effect. +;opcache.validate_timestamps=1 + +; How often (in seconds) to check file timestamps for changes to the shared +; memory storage allocation. ("1" means validate once per second, but only +; once per request. "0" means always validate) +;opcache.revalidate_freq=2 + +; Enables or disables file search in include_path optimization +;opcache.revalidate_path=0 + +; If disabled, all PHPDoc comments are dropped from the code to reduce the +; size of the optimized code. +;opcache.save_comments=1 + +; If enabled, compilation warnings (including notices and deprecations) will +; be recorded and replayed each time a file is included. Otherwise, compilation +; warnings will only be emitted when the file is first cached. +;opcache.record_warnings=0 + +; Allow file existence override (file_exists, etc.) performance feature. +;opcache.enable_file_override=0 + +; A bitmask, where each bit enables or disables the appropriate OPcache +; passes +;opcache.optimization_level=0x7FFFBFFF + +;opcache.dups_fix=0 + +; The location of the OPcache blacklist file (wildcards allowed). +; Each OPcache blacklist file is a text file that holds the names of files +; that should not be accelerated. The file format is to add each filename +; to a new line. The filename may be a full path or just a file prefix +; (i.e., /var/www/x blacklists all the files and directories in /var/www +; that start with 'x'). Line starting with a ; are ignored (comments). +;opcache.blacklist_filename= + +; Allows exclusion of large files from being cached. By default all files +; are cached. +;opcache.max_file_size=0 + +; How long to wait (in seconds) for a scheduled restart to begin if the cache +; is not being accessed. +;opcache.force_restart_timeout=180 + +; OPcache error_log file name. Empty string assumes "stderr". +;opcache.error_log= + +; All OPcache errors go to the Web server log. +; By default, only fatal errors (level 0) or errors (level 1) are logged. +; You can also enable warnings (level 2), info messages (level 3) or +; debug messages (level 4). +;opcache.log_verbosity_level=1 + +; Preferred Shared Memory back-end. Leave empty and let the system decide. +;opcache.preferred_memory_model= + +; Protect the shared memory from unexpected writing during script execution. +; Useful for internal debugging only. +;opcache.protect_memory=0 + +; Allows calling OPcache API functions only from PHP scripts which path is +; started from specified string. The default "" means no restriction +;opcache.restrict_api= + +; Mapping base of shared memory segments (for Windows only). All the PHP +; processes have to map shared memory into the same address space. This +; directive allows to manually fix the "Unable to reattach to base address" +; errors. +;opcache.mmap_base= + +; Facilitates multiple OPcache instances per user (for Windows only). All PHP +; processes with the same cache ID and user share an OPcache instance. +;opcache.cache_id= + +; Enables and sets the second level cache directory. +; It should improve performance when SHM memory is full, at server restart or +; SHM reset. The default "" disables file based caching. +;opcache.file_cache= + +; Enables or disables opcode caching in shared memory. +;opcache.file_cache_only=0 + +; Enables or disables checksum validation when script loaded from file cache. +;opcache.file_cache_consistency_checks=1 + +; Implies opcache.file_cache_only=1 for a certain process that failed to +; reattach to the shared memory (for Windows only). Explicitly enabled file +; cache is required. +;opcache.file_cache_fallback=1 + +; Enables or disables copying of PHP code (text segment) into HUGE PAGES. +; Under certain circumstances (if only a single global PHP process is +; started from which all others fork), this can increase performance +; by a tiny amount because TLB misses are reduced. On the other hand, this +; delays PHP startup, increases memory usage and degrades performance +; under memory pressure - use with care. +; Requires appropriate OS configuration. +;opcache.huge_code_pages=0 + +; Validate cached file permissions. +;opcache.validate_permission=0 + +; Prevent name collisions in chroot'ed environment. +;opcache.validate_root=0 + +; If specified, it produces opcode dumps for debugging different stages of +; optimizations. +;opcache.opt_debug_level=0 + +; Specifies a PHP script that is going to be compiled and executed at server +; start-up. +; https://php.net/opcache.preload +;opcache.preload= + +; Preloading code as root is not allowed for security reasons. This directive +; facilitates to let the preloading to be run as another user. +; https://php.net/opcache.preload_user +;opcache.preload_user= + +; Prevents caching files that are less than this number of seconds old. It +; protects from caching of incompletely updated files. In case all file updates +; on your site are atomic, you may increase performance by setting it to "0". +;opcache.file_update_protection=2 + +; Absolute path used to store shared lockfiles (for *nix only). +;opcache.lockfile_path=/tmp + +[curl] +; A default value for the CURLOPT_CAINFO option. This is required to be an +; absolute path. +;curl.cainfo = + +[openssl] +; The location of a Certificate Authority (CA) file on the local filesystem +; to use when verifying the identity of SSL/TLS peers. Most users should +; not specify a value for this directive as PHP will attempt to use the +; OS-managed cert stores in its absence. If specified, this value may still +; be overridden on a per-stream basis via the "cafile" SSL stream context +; option. +;openssl.cafile= + +; If openssl.cafile is not specified or if the CA file is not found, the +; directory pointed to by openssl.capath is searched for a suitable +; certificate. This value must be a correctly hashed certificate directory. +; Most users should not specify a value for this directive as PHP will +; attempt to use the OS-managed cert stores in its absence. If specified, +; this value may still be overridden on a per-stream basis via the "capath" +; SSL stream context option. +;openssl.capath= + +[ffi] +; FFI API restriction. Possible values: +; "preload" - enabled in CLI scripts and preloaded files (default) +; "false" - always disabled +; "true" - always enabled +;ffi.enable=preload + +; List of headers files to preload, wildcard patterns allowed. +;ffi.preload= diff --git a/plugin-settings.conf b/plugin-settings.conf new file mode 100644 index 0000000..0f981e3 --- /dev/null +++ b/plugin-settings.conf @@ -0,0 +1,42 @@ +# Ustawienia pluginu db-manager (MySQL/MariaDB). +# Always_enable - gdy 1, plugin jest dostepny dla wszystkich uzytkownikow bez checkboxa w edycji konta. +Always_enable=0 +# HITME_SQL_BACKUP_DIR - katalog z dumpami SQL przygotowanymi przez admina. +HITME_SQL_BACKUP_DIR=/home/admin/mysql_backup +# USER_SQL_BACKUP_DIR - katalog na backupy tworzone przez uzytkownika (DA_USER zostanie podmieniony). +USER_SQL_BACKUP_DIR=/home/da_user/mysql_backups/user_backups +# ALLOW_DOWNLOAD_HITME_SQL_BACKUP - czy uzytkownik moze pobierac backupy z HITME_SQL_BACKUP_DIR (true/false). +ALLOW_DOWNLOAD_HITME_SQL_BACKUP=true +# ENABLE_USER_BACKUPS - wlacza/ukrywa cala sekcje backupow uzytkownika (true/false). +ENABLE_USER_BACKUPS=true +# PRE_RESTORE_BACKUP_DIR - katalog na backupy wykonywane przed przywracaniem (DA_USER zostanie podmieniony). +PRE_RESTORE_BACKUP_DIR=/home/da_user/mysql_backups/pre_restore_backups +# PRE_RESTORE_USER_BACKUP_DIR - katalog na backupy wykonywane przed przywracaniem backupow uzytkownika (DA_USER zostanie podmieniony). +PRE_RESTORE_USER_BACKUP_DIR=/home/da_user/mysql_backups/pre_restore_user_backups +# ENABLE_PRE_RESTORE_BACKUP - czy tworzyc backup przed przywroceniem (true/false). +ENABLE_PRE_RESTORE_BACKUP=true +# ENABLE_USER_PRE_RESTORE_BACKUP - czy tworzyc backup przed przywroceniem backupu utworzonego przez uzytkownika (true/false). +ENABLE_USER_PRE_RESTORE_BACKUP=true +# SECURE_BACKUP - tworzy tymczasowego uzytkownika bazy (dbtmp__NAZWA_BAZY) tylko do backupu wskazanej bazy. +SECURE_BACKUP=true +# SECURE_RESTORE - tworzy tymczasowego uzytkownika bazy (dbtmp__NAZWA_BAZY) tylko do przywracania wskazanej bazy. +SECURE_RESTORE=true +# PREVENT_LOG_DELETION - blokuje usuwanie logow i wpisow zadan przez uzytkownika (true/false). +PREVENT_LOG_DELETION=true +# OWN_BACKUP_USER_RESTORE - przywracaj backupy uzytkownika tylko z kopii utworzonych przez plugin DB-Manager (true/false). +# Gdy true, nowe backupy uzytkownika sa zapisywane jako root:root i oznaczane metadanymi pluginu. +OWN_BACKUP_USER_RESTORE=true +# CHECK_DATABASE_COLLISIONS - blokuje rownolegle zadania na tej samej bazie (backup/restore) (true/false). +CHECK_DATABASE_COLLISIONS=true +# ENABLE_OPERATION_LIMITS - globalny przelacznik limitow operacji backup/restore (true/false). +ENABLE_OPERATION_LIMITS=true +# Limity przywracania (0 = brak limitu): 5 minut / 1 godzina / 24 godziny. +RESTORE_LIMIT_5MIN=10 +RESTORE_LIMIT_HOUR=30 +RESTORE_LIMIT_DAY=100 +# Limity backupu (0 = brak limitu): 5 minut / 1 godzina / 24 godziny. +BACKUP_LIMIT_5MIN=10 +BACKUP_LIMIT_HOUR=30 +BACKUP_LIMIT_DAY=100 +# ENABLE_LOGS_OVERLAY - czy otwierac logi w oknie overlay (true/false). +ENABLE_LOGS_OVERLAY=true diff --git a/plugin.conf b/plugin.conf new file mode 100644 index 0000000..3298a32 --- /dev/null +++ b/plugin.conf @@ -0,0 +1,6 @@ +name=DB-Manager +id=db-manager +type=user +author=HITME.PL +version=1.9.11 +user_run_as=root diff --git a/scripts/backup.sh b/scripts/backup.sh new file mode 100755 index 0000000..39198a8 --- /dev/null +++ b/scripts/backup.sh @@ -0,0 +1,437 @@ +#!/bin/bash +set -euo pipefail + +ERROR_MSG="" +BACKUP_OK=0 +DA_USER="" +LOG_FILE="" +TMP_DB_USER="tmprestore" +TMP_DB_PASS="" +TMP_DB_ACTIVE=0 +SECURE_BACKUP="true" +OWN_BACKUP_USER_RESTORE="true" + +JOB_FILE="${1:-}" +if [ -z "$JOB_FILE" ] || [ ! -f "$JOB_FILE" ]; then + echo "db-manager: missing job file" >&2 + exit 1 +fi + +urlencode() { + local s="$1" + local out="" + local i c + for ((i=0; i<${#s}; i++)); do + c="${s:i:1}" + case "$c" in + [a-zA-Z0-9.~_-]) out+="$c" ;; + ' ') out+='%20' ;; + *) printf -v c '%%%02X' "'$c"; out+="$c" ;; + esac + done + printf '%s' "$out" +} + +notify_user() { + local code="$1" + if [ -z "${DA_USER:-}" ]; then + return + fi + local subject message + if [ "$code" -eq 0 ] && [ "$BACKUP_OK" -eq 1 ]; then + subject="Backup bazy danych zakonczony pomyslnie" + message="Backup bazy ${DB_NAME:-} zostal wykonany." + else + subject="Blad wykonywania backupu bazy danych" + if [ -n "$ERROR_MSG" ]; then + message="Blad backupu: ${ERROR_MSG}" + else + message="Blad backupu bazy ${DB_NAME:-}. Log: ${LOG_FILE:-brak}." + fi + if [ -n "${LOG_FILE:-}" ] && [ -f "$LOG_FILE" ]; then + local log_content + log_content="$(cat "$LOG_FILE")" + message="${message}\n\nLog:\n${log_content}" + fi + fi + local task_queue="/usr/local/directadmin/data/task.queue" + local users="select1%3D$(urlencode "$DA_USER")" + local line="action=notify&value=users&subject=$(urlencode "$subject")&message=$(urlencode "$message")&users=${users}" + printf "%s\n" "$line" >> "$task_queue" +} + +db_lock_path_for_db() { + local db_name="$1" + local safe + safe="$(printf '%s' "$db_name" | tr -c 'A-Za-z0-9_.-' '_')" + printf '%s/data/lock/%s.lock' "$PLUGIN_DIR" "$safe" +} + +release_db_lock() { + local db_name lock_path + db_name="${DB_NAME:-}" + if [ -z "$db_name" ] || [ -z "${PLUGIN_DIR:-}" ]; then + return 0 + fi + lock_path="$(db_lock_path_for_db "$db_name")" + if [ -f "$lock_path" ]; then + rm -f "$lock_path" 2>/dev/null || true + fi +} + +on_exit() { + local code="$1" + cleanup_tmp_db_user + notify_user "$code" +} +trap 'on_exit $?' EXIT + +# shellcheck disable=SC1090 +source "$JOB_FILE" + +DA_USER="${DA_USER:-}" +DB_NAME="${DB_NAME:-}" +DATE_DIR="${DATE_DIR:-}" + +if [ -z "$DA_USER" ] || [ -z "$DB_NAME" ]; then + echo "Missing DA_USER or DB_NAME in job file" >&2 + exit 1 +fi + +JOB_BASE="$(basename "$JOB_FILE")" +JOB_ID="${JOB_BASE%%.*}" + +PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)" +CONFIG_FILE="${PLUGIN_DIR}/plugin-settings.conf" +LOG_DIR="${PLUGIN_DIR}/data/logs" +LOG_FILE="${LOG_DIR}/${JOB_ID}.log" +mkdir -p "$LOG_DIR" + +fail() { + local msg="$1" + ERROR_MSG="$msg" + echo "$msg" >> "$LOG_FILE" +} + +if [ ! -f "$CONFIG_FILE" ]; then + fail "Missing ${CONFIG_FILE}" + exit 1 +fi + +# shellcheck disable=SC1090 +source "$CONFIG_FILE" + +USER_SQL_BACKUP_DIR="${USER_SQL_BACKUP_DIR:-/home/da_user/mysql_backups/user_backups}" +SECURE_BACKUP="${SECURE_BACKUP:-true}" +OWN_BACKUP_USER_RESTORE="${OWN_BACKUP_USER_RESTORE:-true}" +SECURE_BACKUP="$(echo "$SECURE_BACKUP" | tr '[:upper:]' '[:lower:]')" +OWN_BACKUP_USER_RESTORE="$(echo "$OWN_BACKUP_USER_RESTORE" | tr '[:upper:]' '[:lower:]')" +if [ "$SECURE_BACKUP" = "1" ] || [ "$SECURE_BACKUP" = "yes" ]; then + SECURE_BACKUP="true" +fi +if [ "$OWN_BACKUP_USER_RESTORE" = "1" ] || [ "$OWN_BACKUP_USER_RESTORE" = "yes" ]; then + OWN_BACKUP_USER_RESTORE="true" +fi + +apply_da_user() { + local value="$1" + value="${value//DA_USER/$DA_USER}" + value="${value//da_user/$DA_USER}" + printf '%s' "$value" +} + +enforce_root_backup_tree_ownership() { + local root_dir="$1" + if [ "$(id -u)" -ne 0 ] || [ -z "$root_dir" ] || [ -L "$root_dir" ]; then + return 0 + fi + mkdir -p "$root_dir" 2>/dev/null || true + find "$root_dir" \( -type d -o -type f \) -exec chown root:root {} + 2>/dev/null || true +} + +USER_SQL_BACKUP_DIR="$(apply_da_user "$USER_SQL_BACKUP_DIR")" +TARGET_ROOT="$USER_SQL_BACKUP_DIR" +MYSQL_BACKUPS_ROOT="$(apply_da_user "/home/da_user/mysql_backups")" +enforce_root_backup_tree_ownership "$MYSQL_BACKUPS_ROOT" + +if [[ "$DB_NAME" != "${DA_USER}_"* && "$DB_NAME" != "$DA_USER" ]]; then + fail "DB_NAME does not belong to user" + exit 1 +fi + +build_tmp_db_user() { + local db_name="$1" + local cleaned name + cleaned="$(printf '%s' "$db_name" | tr -c 'A-Za-z0-9_' '_')" + name="dbtmp__${cleaned}" + if [ "${#name}" -gt 32 ]; then + name="${name:0:32}" + fi + if [ -z "$name" ]; then + name="tmprestore" + fi + printf '%s' "$name" +} + +TMP_DB_USER="$(build_tmp_db_user "$DB_NAME")" + +if [ -z "$DATE_DIR" ]; then + DATE_DIR="$(date +%d-%m-%Y_%H_%M)" + if [ -d "${TARGET_ROOT%/}/${DATE_DIR}" ]; then + DATE_DIR="$(date +%d-%m-%Y_%H_%M_%S)" + fi +fi +if [[ "$DATE_DIR" == *".."* || "$DATE_DIR" == */* ]]; then + fail "Invalid DATE_DIR" + exit 1 +fi + +TARGET_DIR="${TARGET_ROOT%/}/${DATE_DIR}" +mkdir -p "$TARGET_DIR" +chmod 700 "$TARGET_DIR" || true +MARKER_FILE="${TARGET_DIR}/.db-manager-owned" +{ + echo "OWNER=db-manager" + echo "DA_USER=${DA_USER}" + echo "DATE_DIR=${DATE_DIR}" + echo "CREATED_TS=$(date +%s)" +} > "$MARKER_FILE" +chmod 600 "$MARKER_FILE" 2>/dev/null || true +if [ "$(id -u)" -eq 0 ] && [ "$OWN_BACKUP_USER_RESTORE" = "true" ]; then + chown root:root "$TARGET_DIR" "$MARKER_FILE" 2>/dev/null || true +fi + +TARGET_FILE="${TARGET_DIR}/${DB_NAME}.sql.gz" +if [ -e "$TARGET_FILE" ]; then + IDX=1 + while [ -e "${TARGET_DIR}/${DB_NAME}_${IDX}.sql.gz" ]; do + IDX=$((IDX+1)) + done + TARGET_FILE="${TARGET_DIR}/${DB_NAME}_${IDX}.sql.gz" +fi + +MYSQL_CONF="/usr/local/directadmin/conf/mysql.conf" +if [ ! -f "$MYSQL_CONF" ]; then + fail "Missing ${MYSQL_CONF}" + exit 1 +fi + +MYSQL_USER="" +MYSQL_PASS="" +MYSQL_HOST="" +MYSQL_PORT="" +MYSQL_SOCKET="" +while IFS='=' read -r key val; do + key="${key%%[[:space:]]*}" + val="${val##[[:space:]]}" + case "$key" in + user) MYSQL_USER="$val" ;; + passwd) MYSQL_PASS="$val" ;; + host) MYSQL_HOST="$val" ;; + port) MYSQL_PORT="$val" ;; + socket) MYSQL_SOCKET="$val" ;; + esac +done < <(grep -v '^[[:space:]]*#' "$MYSQL_CONF" | grep -v '^[[:space:]]*$') + +MYSQL_OPTS=() +if [ -n "$MYSQL_USER" ]; then MYSQL_OPTS+=("-u${MYSQL_USER}"); fi +if [ -n "$MYSQL_PASS" ]; then MYSQL_OPTS+=("-p${MYSQL_PASS}"); fi +if [ -n "$MYSQL_HOST" ]; then MYSQL_OPTS+=("-h${MYSQL_HOST}"); fi +if [ -n "$MYSQL_PORT" ] && [ "$MYSQL_PORT" != "0" ]; then MYSQL_OPTS+=("-P${MYSQL_PORT}"); fi +if [ -n "$MYSQL_SOCKET" ]; then MYSQL_OPTS+=("--socket=${MYSQL_SOCKET}"); fi + +PATH="/usr/local/mysql/bin:/usr/local/mariadb/bin:/usr/local/bin:/usr/bin:/bin:${PATH:-}" +export PATH + +resolve_bin() { + local name path + for name in "$@"; do + if path="$(command -v "$name" 2>/dev/null)"; then + printf '%s' "$path" + return 0 + fi + done + return 1 +} + +sql_escape() { + local v="$1" + v="${v//\\/\\\\}" + v="${v//\'/\\\'}" + printf '%s' "$v" +} + +random_password() { + rand_chars() { + local charset="$1" + local need="$2" + local out="" chunk + while [ "${#out}" -lt "$need" ]; do + chunk="$(LC_ALL=C tr -dc "$charset" /dev/null | head -c $((need - ${#out})) || true)" + if [ -z "$chunk" ]; then + break + fi + out+="$chunk" + done + printf '%s' "$out" + } + + local lower='abcdefghijklmnopqrstuvwxyz' + local upper='ABCDEFGHIJKLMNOPQRSTUVWXYZ' + local digits='0123456789' + local special='-' + local all="${lower}${upper}${digits}${special}" + local seed extra mixed out tmp + local -a chars=() + local i j + + seed="$(rand_chars "$lower" 1)$(rand_chars "$upper" 1)$(rand_chars "$digits" 1)$(rand_chars "$special" 1)" + extra="$(rand_chars "$all" 20)" + mixed="${seed}${extra}" + + if [ "${#mixed}" -lt 24 ]; then + mixed="aA1-" + while [ "${#mixed}" -lt 24 ]; do + mixed="${mixed}aA1-" + done + mixed="${mixed:0:24}" + fi + + for ((i=0; i<${#mixed}; i++)); do + chars+=("${mixed:i:1}") + done + for ((i=${#chars[@]}-1; i>0; i--)); do + j=$((RANDOM % (i + 1))) + tmp="${chars[i]}" + chars[i]="${chars[j]}" + chars[j]="$tmp" + done + + out="" + for tmp in "${chars[@]}"; do + out+="$tmp" + done + printf '%s' "${out:0:24}" +} + +cleanup_tmp_db_user() { + if [ "${TMP_DB_ACTIVE:-0}" -ne 1 ]; then + return + fi + if [ -z "${MYSQL_BIN:-}" ]; then + return + fi + local user_esc + user_esc="$(sql_escape "$TMP_DB_USER")" + local sql=" +DROP USER IF EXISTS '${user_esc}'@'localhost'; +DROP USER IF EXISTS '${user_esc}'@'127.0.0.1'; +FLUSH PRIVILEGES;" + set +e + "${MYSQL_BIN}" "${MYSQL_OPTS[@]}" -e "$sql" >>"$LOG_FILE" 2>&1 + set -e + TMP_DB_ACTIVE=0 +} + +create_tmp_db_user_for_backup() { + TMP_DB_PASS="$(random_password)" + local user_esc pass_esc db_esc sql + user_esc="$(sql_escape "$TMP_DB_USER")" + pass_esc="$(sql_escape "$TMP_DB_PASS")" + db_esc="${DB_NAME//\`/\`\`}" + sql=" +DROP USER IF EXISTS '${user_esc}'@'localhost'; +DROP USER IF EXISTS '${user_esc}'@'127.0.0.1'; +CREATE USER '${user_esc}'@'localhost' IDENTIFIED BY '${pass_esc}'; +CREATE USER '${user_esc}'@'127.0.0.1' IDENTIFIED BY '${pass_esc}'; +GRANT SELECT, SHOW VIEW, EVENT, TRIGGER, LOCK TABLES ON \`${db_esc}\`.* TO '${user_esc}'@'localhost'; +GRANT SELECT, SHOW VIEW, EVENT, TRIGGER, LOCK TABLES ON \`${db_esc}\`.* TO '${user_esc}'@'127.0.0.1'; +FLUSH PRIVILEGES;" + set +e + "${MYSQL_BIN}" "${MYSQL_OPTS[@]}" -e "$sql" >>"$LOG_FILE" 2>&1 + local rc=$? + set -e + if [ "$rc" -ne 0 ]; then + return 1 + fi + TMP_DB_ACTIVE=1 + return 0 +} + +if ! MYSQLDUMP_BIN="$(resolve_bin mysqldump mariadb-dump)"; then + fail "mysqldump client not found (checked: mysqldump, mariadb-dump; PATH=${PATH})" + exit 1 +fi +if ! MYSQL_BIN="$(resolve_bin mysql mariadb)"; then + fail "mysql client not found (checked: mysql, mariadb; PATH=${PATH})" + exit 1 +fi + +MYSQL_ADMIN_CMD=("$MYSQL_BIN" "${MYSQL_OPTS[@]}" -N -B) +MYSQL_TABLES_CMD=("${MYSQL_ADMIN_CMD[@]}") +MYSQLDUMP_CONN_OPTS=("${MYSQL_OPTS[@]}") +if [ "$SECURE_BACKUP" = "true" ]; then + if ! create_tmp_db_user_for_backup; then + fail "Nie udalo sie utworzyc tymczasowego uzytkownika bazy dla backupu." + exit 1 + fi + MYSQLDUMP_CONN_OPTS=("-u${TMP_DB_USER}" "-p${TMP_DB_PASS}") + if [ -n "$MYSQL_SOCKET" ]; then + MYSQLDUMP_CONN_OPTS+=("--socket=${MYSQL_SOCKET}" "-hlocalhost") + else + MYSQLDUMP_CONN_OPTS+=("-h127.0.0.1") + if [ -n "$MYSQL_PORT" ] && [ "$MYSQL_PORT" != "0" ]; then + MYSQLDUMP_CONN_OPTS+=("-P${MYSQL_PORT}") + fi + fi + MYSQL_TABLES_CMD=("$MYSQL_BIN" "${MYSQLDUMP_CONN_OPTS[@]}" -N -B) +fi +MYSQLDUMP_CMD=("$MYSQLDUMP_BIN" "${MYSQLDUMP_CONN_OPTS[@]}" --single-transaction --routines --events --triggers --verbose --databases "$DB_NAME") + +echo "Rozpoczynam backup bazy: ${DB_NAME}" >> "$LOG_FILE" +echo "Tryb backupu: verbose" >> "$LOG_FILE" +if [ "$SECURE_BACKUP" = "true" ]; then + echo "Tryb bezpieczny backupu: ON (tymczasowy uzytkownik bazy)" >> "$LOG_FILE" +else + echo "Tryb bezpieczny backupu: OFF" >> "$LOG_FILE" +fi +DB_NAME_BT="${DB_NAME//\`/\`\`}" +set +e +TABLES_RAW="$("${MYSQL_TABLES_CMD[@]}" -e "SHOW TABLES FROM \`${DB_NAME_BT}\`" 2>>"$LOG_FILE")" +TABLES_CODE=$? +set -e +if [ "$TABLES_CODE" -eq 0 ]; then + if [ -n "$TABLES_RAW" ]; then + echo "Tabele do backupu:" >> "$LOG_FILE" + while IFS= read -r tbl; do + [ -z "$tbl" ] && continue + echo " - ${tbl}" >> "$LOG_FILE" + done <<< "$TABLES_RAW" + else + echo "Baza nie zawiera tabel." >> "$LOG_FILE" + fi +else + echo "Nie udalo sie pobrac listy tabel dla ${DB_NAME}." >> "$LOG_FILE" +fi + +set +e +${MYSQLDUMP_CMD[@]} 2>>"$LOG_FILE" | gzip -c > "$TARGET_FILE" +EXIT_CODE=$? +set -e +if [ "$EXIT_CODE" -ne 0 ]; then + rm -f "$TARGET_FILE" + fail "Backup bazy ${DB_NAME} zakonczony bledem." + exit 1 +fi + +if [ "$(id -u)" -eq 0 ] && [ "$OWN_BACKUP_USER_RESTORE" = "true" ]; then + chmod 700 "$TARGET_DIR" 2>/dev/null || true + chmod 600 "$TARGET_FILE" "$MARKER_FILE" 2>/dev/null || true + chown root:root "$TARGET_DIR" "$TARGET_FILE" "$MARKER_FILE" 2>/dev/null || true +else + chown "${DA_USER}:${DA_USER}" "$TARGET_FILE" 2>/dev/null || true +fi +enforce_root_backup_tree_ownership "$MYSQL_BACKUPS_ROOT" +echo "Backup zapisany: ${TARGET_FILE}" >> "$LOG_FILE" +BACKUP_OK=1 +exit 0 diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..d78e3df --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,95 @@ +#!/bin/bash +set -euo pipefail + +PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)" +CPIF="/usr/local/directadmin/data/admin/custom_package_items.conf" +CPIL='db_manager=type=checkbox&string=Włącz zarządzanie backupami baz danych&desc=Włącz zarządzanie backupami baz danych&default=no' + +plugin_always_enabled() { + local settings_file="$PLUGIN_DIR/plugin-settings.conf" + local line key value + + [ -r "$settings_file" ] || return 1 + while IFS= read -r line; do + line="${line%%#*}" + key="${line%%=*}" + value="${line#*=}" + key="$(printf '%s' "$key" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')" + if [ "$key" != "always_enable" ]; then + continue + fi + value="$(printf '%s' "$value" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]\"'"'"'')" + case "$value" in + 1|true|yes|y|on) return 0 ;; + *) return 1 ;; + esac + done < "$settings_file" + + return 1 +} + +bootstrap_custom_package_item() { + local install_cpif=0 + local count + + if [ "$(id -u)" -ne 0 ]; then + echo "db-manager: warning: not running as root, cannot update $CPIF" >&2 + return 0 + fi + + if [ -f "$CPIF" ]; then + sed -i '/^db-manager=/d' "$CPIF" || true + sed -i '/^db_manager=/d' "$CPIF" || true + count="$(grep -m1 -c '^db_manager=' "$CPIF" || true)" + if [ "${count:-0}" -eq 0 ] && ! plugin_always_enabled; then + install_cpif=1 + fi + else + if ! plugin_always_enabled; then + install_cpif=1 + fi + fi + + if [ "$install_cpif" -eq 1 ]; then + printf '%s\n' "$CPIL" >> "$CPIF" + fi + + if [ -f "$CPIF" ]; then + chown diradmin:diradmin "$CPIF" 2>/dev/null || true + chmod 640 "$CPIF" 2>/dev/null || true + fi +} + +if [ "${DB_MANAGER_INSTALL_LIB_ONLY:-0}" = "1" ]; then + return 0 2>/dev/null || exit 0 +fi + +# Ensure expected permissions and ownership so DirectAdmin can update plugin.conf. +if [ "$(id -u)" -eq 0 ]; then + chown -R diradmin:diradmin "$PLUGIN_DIR" || true +else + echo "db-manager: warning: not running as root, cannot chown to diradmin" >&2 +fi + +chmod 755 "$PLUGIN_DIR" "$PLUGIN_DIR/hooks" "$PLUGIN_DIR/user" "$PLUGIN_DIR/scripts" "$PLUGIN_DIR/images" || true +chmod 755 "$PLUGIN_DIR/user/index.html" "$PLUGIN_DIR/scripts/install.sh" "$PLUGIN_DIR/scripts/uninstall.sh" "$PLUGIN_DIR/scripts/restore.sh" "$PLUGIN_DIR/scripts/backup.sh" "$PLUGIN_DIR/scripts/worker.sh" || true +chmod 644 "$PLUGIN_DIR/plugin.conf" "$PLUGIN_DIR/hooks/user_txt.html" "$PLUGIN_DIR/plugin-settings.conf" "$PLUGIN_DIR/user/enhanced.css" "$PLUGIN_DIR/user/evolution.css" "$PLUGIN_DIR/images/user_icon.svg" || true + +DATA_DIR="$PLUGIN_DIR/data" +mkdir -p "$DATA_DIR/jobs/pending" "$DATA_DIR/jobs/processing" "$DATA_DIR/jobs/done" "$DATA_DIR/logs" "$DATA_DIR/pids" "$DATA_DIR/cancel" "$DATA_DIR/csrf" "$DATA_DIR/lock" +chmod 700 "$DATA_DIR" "$DATA_DIR/jobs" "$DATA_DIR/jobs/pending" "$DATA_DIR/jobs/processing" "$DATA_DIR/jobs/done" "$DATA_DIR/logs" "$DATA_DIR/pids" "$DATA_DIR/cancel" "$DATA_DIR/csrf" "$DATA_DIR/lock" || true +if [ "$(id -u)" -eq 0 ]; then + chown -R diradmin:diradmin "$DATA_DIR" || true +fi + +CRON_FILE="/etc/cron.d/db-manager" +if [ "$(id -u)" -eq 0 ]; then + echo "* * * * * root /usr/local/directadmin/plugins/db-manager/scripts/worker.sh >/dev/null 2>&1" > "$CRON_FILE" + chmod 644 "$CRON_FILE" || true +else + echo "db-manager: warning: not running as root, cannot install cron job" >&2 +fi + +bootstrap_custom_package_item + +echo "db-manager: install complete" diff --git a/scripts/restore.sh b/scripts/restore.sh new file mode 100755 index 0000000..e570273 --- /dev/null +++ b/scripts/restore.sh @@ -0,0 +1,723 @@ +#!/bin/bash +set -euo pipefail + +ERROR_MSG="" +SUCCESS_MSG="" +RESTORE_OK=0 +DA_USER="" +PRE_BACKUP_PATH="" +LOG_FILE="" +TMP_DB_USER="tmprestore" +TMP_DB_PASS="" +TMP_DB_ACTIVE=0 +SECURE_RESTORE="true" +SECURE_BACKUP="true" +OWN_BACKUP_USER_RESTORE="true" + +JOB_FILE="${1:-}" +if [ -z "$JOB_FILE" ] || [ ! -f "$JOB_FILE" ]; then + echo "db-manager: missing job file" >&2 + exit 1 +fi + +urlencode() { + local s="$1" + local out="" + local i c + for ((i=0; i<${#s}; i++)); do + c="${s:i:1}" + case "$c" in + [a-zA-Z0-9.~_-]) out+="$c" ;; + ' ') out+='%20' ;; + *) printf -v c '%%%02X' "'$c"; out+="$c" ;; + esac + done + printf '%s' "$out" +} + +notify_user() { + local code="$1" + if [ -z "${DA_USER:-}" ]; then + return + fi + local subject message + if [ "$code" -eq 0 ] && [ "$RESTORE_OK" -eq 1 ]; then + subject="Przywracanie bazy danych zakonczone pomyslnie" + if [ -n "$PRE_BACKUP_PATH" ]; then + message="Przywracanie bazy ${DB_NAME:-} z dumpa ${DATE_DIR:-} zakonczone. Backup przed przywroceniem: ${PRE_BACKUP_PATH}." + else + message="Przywracanie bazy ${DB_NAME:-} z dumpa ${DATE_DIR:-} zakonczone." + fi + else + subject="Blad przywracania bazy danych" + if [ -n "$ERROR_MSG" ]; then + message="Blad przywracania: ${ERROR_MSG}" + else + message="Blad przywracania bazy ${DB_NAME:-}. Log: ${LOG_FILE:-brak}." + fi + if [ -n "${LOG_FILE:-}" ] && [ -f "$LOG_FILE" ]; then + local log_content + log_content="$(cat "$LOG_FILE")" + message="${message}\n\nLog:\n${log_content}" + fi + fi + local task_queue="/usr/local/directadmin/data/task.queue" + local users="select1%3D$(urlencode "$DA_USER")" + local line="action=notify&value=users&subject=$(urlencode "$subject")&message=$(urlencode "$message")&users=${users}" + printf "%s\n" "$line" >> "$task_queue" +} + +db_lock_path_for_db() { + local db_name="$1" + local safe + safe="$(printf '%s' "$db_name" | tr -c 'A-Za-z0-9_.-' '_')" + printf '%s/data/lock/%s.lock' "$PLUGIN_DIR" "$safe" +} + +release_db_lock() { + local db_name lock_path + db_name="${DB_NAME:-}" + if [ -z "$db_name" ] || [ -z "${PLUGIN_DIR:-}" ]; then + return 0 + fi + lock_path="$(db_lock_path_for_db "$db_name")" + if [ -f "$lock_path" ]; then + rm -f "$lock_path" 2>/dev/null || true + fi +} + +on_exit() { + local code="$1" + cleanup_tmp_db_user + notify_user "$code" +} +trap 'on_exit $?' EXIT + +# shellcheck disable=SC1090 +source "$JOB_FILE" + +DA_USER="${DA_USER:-}" +DB_NAME="${DB_NAME:-}" +SOURCE_DB_NAME="${SOURCE_DB_NAME:-}" +DATE_DIR="${DATE_DIR:-}" +DUMP_FILE="${DUMP_FILE:-}" +BACKUP_SOURCE="${BACKUP_SOURCE:-hosting}" +if [ -z "$SOURCE_DB_NAME" ]; then + SOURCE_DB_NAME="$DB_NAME" +fi + +if [ -z "$DA_USER" ] || [ -z "$DB_NAME" ] || [ -z "$DATE_DIR" ]; then + echo "Missing DA_USER, DB_NAME or DATE_DIR in job file" >&2 + exit 1 +fi + +JOB_BASE="$(basename "$JOB_FILE")" +JOB_ID="${JOB_BASE%%.*}" + +PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)" +CONFIG_FILE="${PLUGIN_DIR}/plugin-settings.conf" +LOG_DIR="${PLUGIN_DIR}/data/logs" +LOG_FILE="${LOG_DIR}/${JOB_ID}.log" + +mkdir -p "$LOG_DIR" + +fail() { + local msg="$1" + ERROR_MSG="$msg" + echo "$msg" >> "$LOG_FILE" +} + +if [ ! -f "$CONFIG_FILE" ]; then + fail "Missing ${CONFIG_FILE}" + exit 1 +fi + +# shellcheck disable=SC1090 +source "$CONFIG_FILE" + +HITME_SQL_BACKUP_DIR="${HITME_SQL_BACKUP_DIR:-/home/admin/mysql_backups}" +USER_SQL_BACKUP_DIR="${USER_SQL_BACKUP_DIR:-/home/da_user/mysql_backups/user_backups}" +PRE_RESTORE_BACKUP_DIR="${PRE_RESTORE_BACKUP_DIR:-/home/da_user/mysql_backups/pre_restore_backups}" +PRE_RESTORE_USER_BACKUP_DIR="${PRE_RESTORE_USER_BACKUP_DIR:-/home/da_user/mysql_backups/pre_restore_user_backups}" +ENABLE_PRE_RESTORE_BACKUP="${ENABLE_PRE_RESTORE_BACKUP:-true}" +ENABLE_USER_PRE_RESTORE_BACKUP="${ENABLE_USER_PRE_RESTORE_BACKUP:-true}" +SECURE_RESTORE="${SECURE_RESTORE:-true}" +SECURE_BACKUP="${SECURE_BACKUP:-true}" +OWN_BACKUP_USER_RESTORE="${OWN_BACKUP_USER_RESTORE:-true}" +ENABLE_PRE_RESTORE_BACKUP="$(echo "$ENABLE_PRE_RESTORE_BACKUP" | tr '[:upper:]' '[:lower:]')" +ENABLE_USER_PRE_RESTORE_BACKUP="$(echo "$ENABLE_USER_PRE_RESTORE_BACKUP" | tr '[:upper:]' '[:lower:]')" +SECURE_RESTORE="$(echo "$SECURE_RESTORE" | tr '[:upper:]' '[:lower:]')" +SECURE_BACKUP="$(echo "$SECURE_BACKUP" | tr '[:upper:]' '[:lower:]')" +OWN_BACKUP_USER_RESTORE="$(echo "$OWN_BACKUP_USER_RESTORE" | tr '[:upper:]' '[:lower:]')" +if [ "$ENABLE_PRE_RESTORE_BACKUP" = "1" ] || [ "$ENABLE_PRE_RESTORE_BACKUP" = "yes" ]; then + ENABLE_PRE_RESTORE_BACKUP="true" +fi +if [ "$ENABLE_USER_PRE_RESTORE_BACKUP" = "1" ] || [ "$ENABLE_USER_PRE_RESTORE_BACKUP" = "yes" ]; then + ENABLE_USER_PRE_RESTORE_BACKUP="true" +fi +if [ "$SECURE_RESTORE" = "1" ] || [ "$SECURE_RESTORE" = "yes" ]; then + SECURE_RESTORE="true" +fi +if [ "$SECURE_BACKUP" = "1" ] || [ "$SECURE_BACKUP" = "yes" ]; then + SECURE_BACKUP="true" +fi +if [ "$OWN_BACKUP_USER_RESTORE" = "1" ] || [ "$OWN_BACKUP_USER_RESTORE" = "yes" ]; then + OWN_BACKUP_USER_RESTORE="true" +fi + +apply_da_user() { + local value="$1" + value="${value//DA_USER/$DA_USER}" + value="${value//da_user/$DA_USER}" + printf '%s' "$value" +} + +enforce_root_backup_tree_ownership() { + local root_dir="$1" + if [ "$(id -u)" -ne 0 ] || [ -z "$root_dir" ] || [ -L "$root_dir" ]; then + return 0 + fi + mkdir -p "$root_dir" 2>/dev/null || true + find "$root_dir" \( -type d -o -type f \) -exec chown root:root {} + 2>/dev/null || true +} + +quote_sh_value() { + local value="$1" + value="${value//\'/\'\\\'\'}" + printf "'%s'" "$value" +} + +set_job_var() { + local key="$1" + local value="$2" + local tmp + tmp="$(mktemp)" + grep -v "^${key}=" "$JOB_FILE" > "$tmp" 2>/dev/null || true + printf "%s=%s\n" "$key" "$(quote_sh_value "$value")" >> "$tmp" + mv "$tmp" "$JOB_FILE" + chmod 600 "$JOB_FILE" 2>/dev/null || true +} + +USER_SQL_BACKUP_DIR="$(apply_da_user "$USER_SQL_BACKUP_DIR")" +PRE_RESTORE_BACKUP_DIR="$(apply_da_user "$PRE_RESTORE_BACKUP_DIR")" +PRE_RESTORE_USER_BACKUP_DIR="$(apply_da_user "$PRE_RESTORE_USER_BACKUP_DIR")" +MYSQL_BACKUPS_ROOT="$(apply_da_user "/home/da_user/mysql_backups")" +enforce_root_backup_tree_ownership "$MYSQL_BACKUPS_ROOT" + +PRE_RESTORE_TARGET_DIR="$PRE_RESTORE_BACKUP_DIR" +if [ "$BACKUP_SOURCE" = "user" ]; then + PRE_RESTORE_TARGET_DIR="$PRE_RESTORE_USER_BACKUP_DIR" +fi + +PRE_RESTORE_ENABLED="$ENABLE_PRE_RESTORE_BACKUP" +if [ "$BACKUP_SOURCE" = "user" ]; then + PRE_RESTORE_ENABLED="$ENABLE_USER_PRE_RESTORE_BACKUP" +fi + +if [[ "$DATE_DIR" == *".."* || "$DATE_DIR" == */* ]]; then + fail "Invalid DATE_DIR" + exit 1 +fi + +if [[ "$DB_NAME" == *".."* || "$DB_NAME" == */* ]]; then + fail "Invalid DB_NAME" + exit 1 +fi + +if [[ "$DB_NAME" != "${DA_USER}_"* && "$DB_NAME" != "$DA_USER" ]]; then + fail "DB_NAME does not belong to user" + exit 1 +fi +if [[ "$SOURCE_DB_NAME" != "${DA_USER}_"* && "$SOURCE_DB_NAME" != "$DA_USER" ]]; then + fail "SOURCE_DB_NAME does not belong to user" + exit 1 +fi + +build_tmp_db_user() { + local db_name="$1" + local cleaned name + cleaned="$(printf '%s' "$db_name" | tr -c 'A-Za-z0-9_' '_')" + name="dbtmp__${cleaned}" + if [ "${#name}" -gt 32 ]; then + name="${name:0:32}" + fi + if [ -z "$name" ]; then + name="tmprestore" + fi + printf '%s' "$name" +} + +TMP_DB_USER="$(build_tmp_db_user "$DB_NAME")" + +resolve_dump_path() { + local source_name="${SOURCE_DB_NAME:-$DB_NAME}" + local base_path="${HITME_SQL_BACKUP_DIR%/}/${DATE_DIR}/${source_name}" + local candidate="" + + if [ -n "${DUMP_FILE:-}" ]; then + candidate="$DUMP_FILE" + if [ -f "$candidate" ]; then + printf '%s' "$candidate" + return 0 + fi + fi + + for candidate in "${base_path}.sql.gz" "${base_path}.gz" "${base_path}.sql"; do + if [ -f "$candidate" ]; then + printf '%s' "$candidate" + return 0 + fi + done + + return 1 +} + +path_in_dir() { + local path="$1" + local dir="$2" + local real_path real_dir + real_path="$(realpath "$path" 2>/dev/null || true)" + real_dir="$(realpath "$dir" 2>/dev/null || true)" + if [ -z "$real_path" ] || [ -z "$real_dir" ]; then + return 1 + fi + if [ "$real_path" = "$real_dir" ]; then + return 0 + fi + case "$real_path" in + "$real_dir"/*) return 0 ;; + esac + return 1 +} + +path_in_any_dir() { + local path="$1" + shift + local dir + for dir in "$@"; do + if [ -n "$dir" ] && path_in_dir "$path" "$dir"; then + return 0 + fi + done + return 1 +} + +is_trusted_user_backup_dir() { + local slot_dir="$1" + local marker owner user uid + marker="${slot_dir%/}/.db-manager-owned" + if [ -f "$marker" ]; then + owner="$(grep -E '^OWNER=' "$marker" 2>/dev/null | tail -n1 | cut -d= -f2- || true)" + user="$(grep -E '^DA_USER=' "$marker" 2>/dev/null | tail -n1 | cut -d= -f2- || true)" + if [ "$owner" = "db-manager" ] && [ "$user" = "$DA_USER" ]; then + return 0 + fi + fi + uid="$(stat -c '%u' "$slot_dir" 2>/dev/null || stat -f '%u' "$slot_dir" 2>/dev/null || echo '')" + if [ "$uid" = "0" ]; then + return 0 + fi + return 1 +} + +if ! DUMP_PATH="$(resolve_dump_path)"; then + fail "Dump file not found for database ${SOURCE_DB_NAME} in ${HITME_SQL_BACKUP_DIR%/}/${DATE_DIR}" + exit 1 +fi + +ALLOWED_DUMP_DIRS=() +if [ "$BACKUP_SOURCE" = "user" ]; then + ALLOWED_DUMP_DIRS+=("$USER_SQL_BACKUP_DIR" "$PRE_RESTORE_USER_BACKUP_DIR") +else + ALLOWED_DUMP_DIRS+=("$HITME_SQL_BACKUP_DIR" "$PRE_RESTORE_BACKUP_DIR") +fi +if ! path_in_any_dir "$DUMP_PATH" "${ALLOWED_DUMP_DIRS[@]}"; then + fail "Dump file path is outside allowed directories." + exit 1 +fi +if [ "$BACKUP_SOURCE" = "user" ] && [ "$OWN_BACKUP_USER_RESTORE" = "true" ]; then + if ! is_trusted_user_backup_dir "$(dirname "$DUMP_PATH")"; then + fail "Nie mozna przywrocic tej kopii zapasowej, poniewaz nie zostala utworzona przez plugin DB-Manager." + exit 1 + fi +fi + +MYSQL_CONF="/usr/local/directadmin/conf/mysql.conf" +if [ ! -f "$MYSQL_CONF" ]; then + fail "Missing ${MYSQL_CONF}" + exit 1 +fi + +MYSQL_USER="" +MYSQL_PASS="" +MYSQL_HOST="" +MYSQL_PORT="" +MYSQL_SOCKET="" +while IFS='=' read -r key val; do + key="${key%%[[:space:]]*}" + val="${val##[[:space:]]}" + case "$key" in + user) MYSQL_USER="$val" ;; + passwd) MYSQL_PASS="$val" ;; + host) MYSQL_HOST="$val" ;; + port) MYSQL_PORT="$val" ;; + socket) MYSQL_SOCKET="$val" ;; + esac +done < <(grep -v '^[[:space:]]*#' "$MYSQL_CONF" | grep -v '^[[:space:]]*$') + +MYSQL_OPTS=() +if [ -n "$MYSQL_USER" ]; then + MYSQL_OPTS+=("-u${MYSQL_USER}") +fi +if [ -n "$MYSQL_PASS" ]; then + MYSQL_OPTS+=("-p${MYSQL_PASS}") +fi +if [ -n "$MYSQL_HOST" ]; then + MYSQL_OPTS+=("-h${MYSQL_HOST}") +fi +if [ -n "$MYSQL_PORT" ] && [ "$MYSQL_PORT" != "0" ]; then + MYSQL_OPTS+=("-P${MYSQL_PORT}") +fi +if [ -n "$MYSQL_SOCKET" ]; then + MYSQL_OPTS+=("--socket=${MYSQL_SOCKET}") +fi + +# Cron from /etc/cron.d may run with a minimal PATH. +PATH="/usr/local/mysql/bin:/usr/local/mariadb/bin:/usr/local/bin:/usr/bin:/bin:${PATH:-}" +export PATH + +resolve_bin() { + local name path + for name in "$@"; do + if path="$(command -v "$name" 2>/dev/null)"; then + printf '%s' "$path" + return 0 + fi + done + return 1 +} + +sql_escape() { + local v="$1" + v="${v//\\/\\\\}" + v="${v//\'/\\\'}" + printf '%s' "$v" +} + +random_password() { + rand_chars() { + local charset="$1" + local need="$2" + local out="" chunk + while [ "${#out}" -lt "$need" ]; do + chunk="$(LC_ALL=C tr -dc "$charset" /dev/null | head -c $((need - ${#out})) || true)" + if [ -z "$chunk" ]; then + break + fi + out+="$chunk" + done + printf '%s' "$out" + } + + local lower='abcdefghijklmnopqrstuvwxyz' + local upper='ABCDEFGHIJKLMNOPQRSTUVWXYZ' + local digits='0123456789' + local special='-' + local all="${lower}${upper}${digits}${special}" + local seed extra mixed out tmp + local -a chars=() + local i j + + seed="$(rand_chars "$lower" 1)$(rand_chars "$upper" 1)$(rand_chars "$digits" 1)$(rand_chars "$special" 1)" + extra="$(rand_chars "$all" 20)" + mixed="${seed}${extra}" + + if [ "${#mixed}" -lt 24 ]; then + mixed="aA1-" + while [ "${#mixed}" -lt 24 ]; do + mixed="${mixed}aA1-" + done + mixed="${mixed:0:24}" + fi + + for ((i=0; i<${#mixed}; i++)); do + chars+=("${mixed:i:1}") + done + for ((i=${#chars[@]}-1; i>0; i--)); do + j=$((RANDOM % (i + 1))) + tmp="${chars[i]}" + chars[i]="${chars[j]}" + chars[j]="$tmp" + done + + out="" + for tmp in "${chars[@]}"; do + out+="$tmp" + done + printf '%s' "${out:0:24}" +} + +cleanup_tmp_db_user() { + if [ "${TMP_DB_ACTIVE:-0}" -ne 1 ]; then + return + fi + if [ -z "${MYSQL_BIN:-}" ]; then + return + fi + local user_esc sql + user_esc="$(sql_escape "$TMP_DB_USER")" + sql=" +DROP USER IF EXISTS '${user_esc}'@'localhost'; +DROP USER IF EXISTS '${user_esc}'@'127.0.0.1'; +FLUSH PRIVILEGES;" + set +e + "${MYSQL_BIN}" "${MYSQL_OPTS[@]}" -e "$sql" >>"$LOG_FILE" 2>&1 + set -e + TMP_DB_ACTIVE=0 +} + +create_tmp_db_user_for_restore() { + TMP_DB_PASS="$(random_password)" + local user_esc pass_esc db_esc sql + user_esc="$(sql_escape "$TMP_DB_USER")" + pass_esc="$(sql_escape "$TMP_DB_PASS")" + db_esc="${DB_NAME//\`/\`\`}" + sql=" +DROP USER IF EXISTS '${user_esc}'@'localhost'; +DROP USER IF EXISTS '${user_esc}'@'127.0.0.1'; +CREATE USER '${user_esc}'@'localhost' IDENTIFIED BY '${pass_esc}'; +CREATE USER '${user_esc}'@'127.0.0.1' IDENTIFIED BY '${pass_esc}'; +GRANT ALL PRIVILEGES ON \`${db_esc}\`.* TO '${user_esc}'@'localhost'; +GRANT ALL PRIVILEGES ON \`${db_esc}\`.* TO '${user_esc}'@'127.0.0.1'; +FLUSH PRIVILEGES;" + set +e + "${MYSQL_BIN}" "${MYSQL_OPTS[@]}" -e "$sql" >>"$LOG_FILE" 2>&1 + local rc=$? + set -e + if [ "$rc" -ne 0 ]; then + return 1 + fi + TMP_DB_ACTIVE=1 + return 0 +} + +sanitize_sql_stream_for_target_db() { + local src_db="$1" + local dst_db="$2" + local marker_file="${3:-}" + awk -v src_db="$src_db" -v dst_db="$dst_db" -v marker_file="$marker_file" ' + function escape_ere(str, out, i, ch) { + out = "" + for (i = 1; i <= length(str); i++) { + ch = substr(str, i, 1) + if (ch ~ /[][(){}.^$*+?|\\]/) { + out = out "\\" ch + } else { + out = out ch + } + } + return out + } + BEGIN { + src_bt = "`" src_db "`" + dst_bt = "`" dst_db "`" + src_bt_ere = escape_ere(src_bt) + fq_pattern = src_bt_ere "[[:space:]]*\\.[[:space:]]*`" + changed = 0 + needs_db_replace = (src_db != dst_db) + } + { + line = $0 + lower = tolower(line) + if (lower ~ /^[[:space:]]*create[[:space:]]+database[[:space:]]+/) { + changed = 1 + next + } + if (lower ~ /^[[:space:]]*\/\*![0-9]+[[:space:]]+create[[:space:]]+database[[:space:]]+/) { + changed = 1 + next + } + if (lower ~ /^[[:space:]]*drop[[:space:]]+database([[:space:]]+if[[:space:]]+exists)?[[:space:]]+/) { + changed = 1 + next + } + if (lower ~ /^[[:space:]]*\/\*![0-9]+[[:space:]]+drop[[:space:]]+database([[:space:]]+if[[:space:]]+exists)?[[:space:]]+/) { + changed = 1 + next + } + if (lower ~ /^[[:space:]]*use[[:space:]]+/ || lower ~ /^[[:space:]]*\/\*![0-9]+[[:space:]]+use[[:space:]]+/) { + changed = 1 + print "USE " dst_bt ";" + next + } + if (needs_db_replace) { + if (gsub(fq_pattern, dst_bt ".`", line) > 0) { + changed = 1 + } + } + print line + } + END { + if (marker_file != "") { + if (changed) { + print "1" > marker_file + } else { + print "0" > marker_file + } + } + } + ' +} + +if ! MYSQL_BIN="$(resolve_bin mysql mariadb)"; then + fail "mysql client not found (checked: mysql, mariadb; PATH=${PATH})" + exit 1 +fi +MYSQL_ADMIN_CMD=("$MYSQL_BIN" "${MYSQL_OPTS[@]}") +MYSQL_RESTORE_CMD=("${MYSQL_ADMIN_CMD[@]}") + +MYSQLDUMP_BIN="" +MYSQL_PREBACKUP_DUMP_CMD=() +if [ "$PRE_RESTORE_ENABLED" = "true" ]; then + if ! MYSQLDUMP_BIN="$(resolve_bin mysqldump mariadb-dump)"; then + fail "mysqldump client not found (checked: mysqldump, mariadb-dump; PATH=${PATH})" + exit 1 + fi + MYSQL_PREBACKUP_DUMP_CMD=("$MYSQLDUMP_BIN" "${MYSQL_OPTS[@]}" --single-transaction --routines --events --triggers) +fi + +NEED_TMP_USER=0 +if [ "$SECURE_RESTORE" = "true" ]; then + NEED_TMP_USER=1 +fi +if [ "$PRE_RESTORE_ENABLED" = "true" ] && [ "$SECURE_BACKUP" = "true" ]; then + NEED_TMP_USER=1 +fi +if [ "$NEED_TMP_USER" -eq 1 ]; then + if ! create_tmp_db_user_for_restore; then + fail "Nie udalo sie utworzyc tymczasowego uzytkownika bazy." + exit 1 + fi + TMP_CONN_OPTS=("-u${TMP_DB_USER}" "-p${TMP_DB_PASS}") + if [ -n "$MYSQL_SOCKET" ]; then + TMP_CONN_OPTS+=("--socket=${MYSQL_SOCKET}" "-hlocalhost") + else + TMP_CONN_OPTS+=("-h127.0.0.1") + if [ -n "$MYSQL_PORT" ] && [ "$MYSQL_PORT" != "0" ]; then + TMP_CONN_OPTS+=("-P${MYSQL_PORT}") + fi + fi + if [ "$SECURE_RESTORE" = "true" ]; then + MYSQL_RESTORE_CMD=("$MYSQL_BIN" "${TMP_CONN_OPTS[@]}") + fi + if [ "$PRE_RESTORE_ENABLED" = "true" ] && [ "$SECURE_BACKUP" = "true" ]; then + MYSQL_PREBACKUP_DUMP_CMD=("$MYSQLDUMP_BIN" "${TMP_CONN_OPTS[@]}" --single-transaction --routines --events --triggers) + fi +fi + +if [ "$SECURE_RESTORE" = "true" ]; then + echo "Tryb bezpieczny przywracania: ON (tymczasowy uzytkownik bazy)" >> "$LOG_FILE" +else + echo "Tryb bezpieczny przywracania: OFF" >> "$LOG_FILE" +fi +if [ "$PRE_RESTORE_ENABLED" = "true" ]; then + if [ "$SECURE_BACKUP" = "true" ]; then + echo "Tryb bezpieczny backupu przed przywroceniem: ON (tymczasowy uzytkownik bazy)" >> "$LOG_FILE" + else + echo "Tryb bezpieczny backupu przed przywroceniem: OFF" >> "$LOG_FILE" + fi +fi + +if [ "$PRE_RESTORE_ENABLED" = "true" ]; then + DB_NAME_SQL="${DB_NAME//\'/\\\'}" + set +e + DB_EXISTS="$(${MYSQL_ADMIN_CMD[@]} -N -B -e "SHOW DATABASES LIKE '${DB_NAME_SQL}'" 2>>"$LOG_FILE")" + EXISTS_CODE=$? + set -e + if [ "$EXISTS_CODE" -ne 0 ]; then + fail "Nie udalo sie sprawdzic istnienia bazy ${DB_NAME}." + exit 1 + fi + if [ -n "$DB_EXISTS" ]; then + mkdir -p "$PRE_RESTORE_TARGET_DIR" + chmod 700 "$PRE_RESTORE_TARGET_DIR" || true + BACKUP_DATE="$(date +%d-%m-%Y)" + BASE_NAME="${DB_NAME}_przed_przywroceniem_${BACKUP_DATE}" + TARGET_PATH="${PRE_RESTORE_TARGET_DIR%/}/${BASE_NAME}.sql.gz" + IDX=1 + while [ -e "$TARGET_PATH" ]; do + TARGET_PATH="${PRE_RESTORE_TARGET_DIR%/}/${BASE_NAME}_${IDX}.sql.gz" + IDX=$((IDX+1)) + done + + set +e + ${MYSQL_PREBACKUP_DUMP_CMD[@]} --databases "$DB_NAME" 2>>"$LOG_FILE" | gzip -c > "$TARGET_PATH" + DUMP_CODE=$? + set -e + + if [ "$DUMP_CODE" -ne 0 ]; then + rm -f "$TARGET_PATH" + fail "Nie udalo sie wykonac backupu bazy ${DB_NAME}." + exit 1 + fi + + if [ "$(id -u)" -eq 0 ]; then + chown root:root "$PRE_RESTORE_TARGET_DIR" "$TARGET_PATH" 2>/dev/null || true + chmod 700 "$PRE_RESTORE_TARGET_DIR" 2>/dev/null || true + chmod 600 "$TARGET_PATH" 2>/dev/null || true + else + chown "${DA_USER}:${DA_USER}" "$TARGET_PATH" 2>/dev/null || true + fi + enforce_root_backup_tree_ownership "$MYSQL_BACKUPS_ROOT" + PRE_BACKUP_PATH="$TARGET_PATH" + set_job_var "PRE_BACKUP_PATH" "$PRE_BACKUP_PATH" + echo "Wykonano backup przed przywroceniem: ${PRE_BACKUP_PATH}" >> "$LOG_FILE" + else + echo "Baza ${DB_NAME} nie istnieje - pomijam backup przed przywroceniem." >> "$LOG_FILE" + fi +else + echo "Backup przed przywroceniem jest wylaczony dla tego typu przywracania." >> "$LOG_FILE" +fi + +DB_NAME_BT="${DB_NAME//\`/\`\`}" +${MYSQL_ADMIN_CMD[@]} -e "CREATE DATABASE IF NOT EXISTS \`${DB_NAME_BT}\`" >> "$LOG_FILE" 2>&1 + +set +e +SANITIZE_MARKER_FILE="$(mktemp)" +printf "0\n" > "$SANITIZE_MARKER_FILE" +if [ "$SOURCE_DB_NAME" != "$DB_NAME" ]; then + echo "Sanityzacja dumpa przed importem: ${SOURCE_DB_NAME} -> ${DB_NAME}" >> "$LOG_FILE" +fi +if [[ "$DUMP_PATH" == *.gz ]]; then + gunzip -c "$DUMP_PATH" \ + | sanitize_sql_stream_for_target_db "$SOURCE_DB_NAME" "$DB_NAME" "$SANITIZE_MARKER_FILE" \ + | ${MYSQL_RESTORE_CMD[@]} --database="$DB_NAME" --verbose >> "$LOG_FILE" 2>&1 + RESTORE_CODE=$? +else + cat "$DUMP_PATH" \ + | sanitize_sql_stream_for_target_db "$SOURCE_DB_NAME" "$DB_NAME" "$SANITIZE_MARKER_FILE" \ + | ${MYSQL_RESTORE_CMD[@]} --database="$DB_NAME" --verbose >> "$LOG_FILE" 2>&1 + RESTORE_CODE=$? +fi +set -e + +SANITIZATION_REQUIRED="Nie" +if [ -r "$SANITIZE_MARKER_FILE" ]; then + if grep -q '^1$' "$SANITIZE_MARKER_FILE"; then + SANITIZATION_REQUIRED="Tak" + fi +fi +rm -f "$SANITIZE_MARKER_FILE" +set_job_var "SANITIZATION_REQUIRED" "$SANITIZATION_REQUIRED" +echo "Koniecznosc Sanityzacji: ${SANITIZATION_REQUIRED}" >> "$LOG_FILE" + +if [ "$RESTORE_CODE" -ne 0 ]; then + fail "Przywracanie bazy ${DB_NAME} zakonczone bledem." + exit 1 +fi + +RESTORE_OK=1 +SUCCESS_MSG="Przywracanie bazy ${DB_NAME} zakonczone pomyslnie." +echo "$SUCCESS_MSG" >> "$LOG_FILE" + +exit 0 diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh new file mode 100755 index 0000000..de53f89 --- /dev/null +++ b/scripts/uninstall.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -euo pipefail + +CRON_FILE="/etc/cron.d/db-manager" +CPIF="/usr/local/directadmin/data/admin/custom_package_items.conf" +if [ "$(id -u)" -eq 0 ]; then + rm -f "$CRON_FILE" + if [ -f "$CPIF" ]; then + sed -i '/^db-manager=/d' "$CPIF" || true + sed -i '/^db_manager=/d' "$CPIF" || true + chown diradmin:diradmin "$CPIF" 2>/dev/null || true + chmod 640 "$CPIF" 2>/dev/null || true + fi +else + echo "db-manager: warning: not running as root, cannot remove cron job or update custom package items" >&2 +fi + +echo "db-manager: uninstall complete" diff --git a/scripts/worker.sh b/scripts/worker.sh new file mode 100755 index 0000000..cd09979 --- /dev/null +++ b/scripts/worker.sh @@ -0,0 +1,104 @@ +#!/bin/bash +set -euo pipefail + +PLUGIN_DIR="$(cd "$(dirname "$0")/.." && pwd)" +DATA_DIR="$PLUGIN_DIR/data" +JOB_DIR="$DATA_DIR/jobs/pending" +PROCESSING_DIR="$DATA_DIR/jobs/processing" +DONE_DIR="$DATA_DIR/jobs/done" +WORKER_LOCK_DIR="$DATA_DIR/worker.lock" +DB_LOCK_DIR="$DATA_DIR/lock" +PID_DIR="$DATA_DIR/pids" +CANCEL_DIR="$DATA_DIR/cancel" + +mkdir -p "$JOB_DIR" "$PROCESSING_DIR" "$DONE_DIR" "$PID_DIR" "$CANCEL_DIR" "$DB_LOCK_DIR" + +if ! mkdir "$WORKER_LOCK_DIR" 2>/dev/null; then + exit 0 +fi + +cleanup() { + rmdir "$WORKER_LOCK_DIR" 2>/dev/null || true +} +trap cleanup EXIT + +db_lock_path_for_db() { + local db_name="$1" + local safe + safe="$(printf '%s' "$db_name" | tr -c 'A-Za-z0-9_.-' '_')" + printf '%s/%s.lock' "$DB_LOCK_DIR" "$safe" +} + +release_db_lock_later() { + local lock_path="$1" + if [ -z "$lock_path" ]; then + return 0 + fi + ( + sleep 10 + rm -f "$lock_path" 2>/dev/null || true + ) >/dev/null 2>&1 & +} + +while true; do + JOB_FILE=$(ls -1 "$JOB_DIR"/*.env 2>/dev/null | head -n 1) + if [ -z "${JOB_FILE:-}" ]; then + break + fi + + BASE_NAME="$(basename "$JOB_FILE")" + RUN_FILE="$PROCESSING_DIR/$BASE_NAME" + mv "$JOB_FILE" "$RUN_FILE" + JOB_ID="${BASE_NAME%.env}" + PID_FILE="$PID_DIR/${JOB_ID}.pid" + + JOB_TYPE="restore" + DB_NAME="" + DB_LOCK_FILE="" + # shellcheck disable=SC1090 + source "$RUN_FILE" || true + JOB_TYPE="${JOB_TYPE:-restore}" + DB_NAME="${DB_NAME:-}" + DB_LOCK_FILE="${DB_LOCK_FILE:-}" + + LOCK_PATH="" + if [ -n "$DB_LOCK_FILE" ]; then + case "$DB_LOCK_FILE" in + "$DB_LOCK_DIR"/*.lock) LOCK_PATH="$DB_LOCK_FILE" ;; + *) LOCK_PATH="" ;; + esac + fi + if [ -z "$LOCK_PATH" ] && [ -n "$DB_NAME" ]; then + LOCK_PATH="$(db_lock_path_for_db "$DB_NAME")" + fi + + RUNNER="$PLUGIN_DIR/scripts/restore.sh" + if [ "$JOB_TYPE" = "backup" ]; then + RUNNER="$PLUGIN_DIR/scripts/backup.sh" + fi + + set +e + "$RUNNER" "$RUN_FILE" & + RUN_PID=$! + echo "PID=${RUN_PID}" >> "$RUN_FILE" + echo "${RUN_PID}" > "$PID_FILE" + wait "$RUN_PID" + EXIT_CODE=$? + set -e + + rm -f "$PID_FILE" + + CANCEL_FLAG="${CANCEL_DIR}/${JOB_ID}.flag" + if [ -f "$CANCEL_FLAG" ]; then + rm -f "$CANCEL_FLAG" + mv "$RUN_FILE" "$DONE_DIR/${JOB_ID}.cancel" + elif [ "$EXIT_CODE" -eq 0 ]; then + mv "$RUN_FILE" "$DONE_DIR/${JOB_ID}.ok" + else + mv "$RUN_FILE" "$DONE_DIR/${JOB_ID}.fail" + fi + + release_db_lock_later "$LOCK_PATH" +done + +exit 0 diff --git a/tests/test_always_enable.php b/tests/test_always_enable.php new file mode 100644 index 0000000..c26325c --- /dev/null +++ b/tests/test_always_enable.php @@ -0,0 +1,51 @@ + "$PLUGIN_DIR/plugin-settings.conf" +bootstrap_custom_package_item +if [ -e "$CPIF" ]; then + echo "custom_package_items.conf should not be created when Always_enable=1" >&2 + exit 1 +fi + +printf 'Always_enable=0\n' > "$PLUGIN_DIR/plugin-settings.conf" +bootstrap_custom_package_item +if ! grep -q '^db_manager=' "$CPIF"; then + echo "db_manager checkbox should be installed when Always_enable=0" >&2 + exit 1 +fi + +echo "OK" diff --git a/uninstall.sh b/uninstall.sh new file mode 100755 index 0000000..4cc4c95 --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -eu + +PLUGIN_DIR="$(cd "$(dirname "$0")" && pwd)" +SCRIPT="$PLUGIN_DIR/scripts/uninstall.sh" + +if [ ! -x "$SCRIPT" ]; then + chmod 755 "$SCRIPT" 2>/dev/null || true +fi + +exec "$SCRIPT" diff --git a/user/enhanced.css b/user/enhanced.css new file mode 100644 index 0000000..8f47351 --- /dev/null +++ b/user/enhanced.css @@ -0,0 +1,569 @@ +:root { + --br-bg: #f4f6f8; + --br-card: #ffffff; + --br-text: #1f2a37; + --br-muted: #6b7280; + --br-border: #d9dee5; + --br-accent: #0ea5a4; + --br-accent-dark: #0f766e; + --br-danger: #dc2626; + --br-warn: #f59e0b; + --br-shadow: 0 8px 24px rgba(31, 41, 55, 0.12); +} +* { box-sizing: border-box; } +body { + margin: 0; + background: var(--br-bg); + color: var(--br-text); + font-family: "Helvetica Neue", Arial, sans-serif; +} +#content, +#content .container, +#content .container-fluid, +#content .main-content, +#content .page-content, +.main-content, +.page-content { + max-width: none !important; + width: 100% !important; +} +.br-container { + max-width: none; + width: 100%; + margin: 0; + padding: 16px 20px; +} +.br-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} +.br-title { + font-size: 20px; + font-weight: 700; + letter-spacing: 0.2px; +} +.br-card { + background: var(--br-card); + border: 1px solid var(--br-border); + border-radius: 10px; + padding: 16px; + box-shadow: var(--br-shadow); + margin-bottom: 16px; +} +.br-muted { color: var(--br-muted); } +.br-alert { + border-radius: 8px; + padding: 10px 12px; + margin: 8px 0; + border: 1px solid transparent; +} +.br-alert-error { background: #fff1f2; border-color: #fecdd3; color: #9f1239; } +.br-alert-warn { background: #fffbeb; border-color: #fde68a; color: #92400e; } +.br-alert-success { background: #ecfdf3; border-color: #bbf7d0; color: #166534; } +.br-overlay { + position: fixed; + inset: 0; + background: rgba(15, 23, 42, 0.6); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; +} +.br-modal { + width: min(560px, 92vw); + background: #ffffff; + border-radius: 12px; + padding: 16px; + border: 2px solid var(--br-border); + box-shadow: var(--br-shadow); + display: flex; + flex-direction: column; + max-height: 90vh; +} +.br-modal-log { + width: 90vw; + background: #ffffff; +} +.br-modal-danger { + border-color: var(--br-danger); +} +.br-modal-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 12px; +} +.br-modal-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-bottom: 10px; +} +.br-modal-title { + font-weight: 700; + font-size: 16px; +} +.br-modal-body { + max-height: 60vh; + overflow: auto; +} +.br-modal-scroll { + overflow: auto; + flex: 1; + min-height: 0; +} +.br-log-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} +.br-log-header-actions { + display: flex; + gap: 8px; +} +.br-log-meta { + display: grid; + gap: 6px; + margin: 10px 0 12px; + padding: 10px 12px; + border: 1px solid var(--br-border); + border-radius: 10px; + background: #f8fafc; + font-size: 13px; +} +.br-text-danger { + color: var(--br-danger); + font-weight: 700; +} +.br-log-actions { + margin: 8px 0 10px; +} +.br-picker-path { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 10px; + flex-wrap: wrap; +} +.br-picker-list { + list-style: none; + padding: 0; + margin: 0; + border: 1px solid var(--br-border); + border-radius: 10px; + max-height: 45vh; + overflow: auto; +} +.br-picker-create { + margin-top: 10px; + display: flex; + gap: 8px; +} +.br-picker-create input { + flex: 1; + padding: 6px 8px; + border: 1px solid var(--br-border); + border-radius: 8px; +} +.br-picker-list li { + padding: 8px 12px; + cursor: pointer; + border-bottom: 1px solid var(--br-border); +} +.br-picker-list li:last-child { + border-bottom: none; +} +.br-picker-list li:hover { + background: #f8fafc; +} +.br-picker-list li.active { + background: #e0f2fe; +} +.br-btn-small { + padding: 6px 10px; + font-size: 12px; +} +.br-actions-row { + display: flex; + gap: 8px; + margin-top: 10px; + flex-wrap: wrap; +} +.br-actions-row form { + margin: 0; +} +.br-icon-btn { + border: 1px solid var(--br-border); + background: #fff; + border-radius: 8px; + padding: 4px 8px; + cursor: pointer; + font-size: 16px; + line-height: 1; +} +.br-icon-btn:hover { + background: #f8fafc; +} +.br-table { + width: 100%; + border-collapse: collapse; + border: 1px solid var(--br-border); + border-radius: 8px; + overflow: hidden; + background: #fff; + table-layout: auto; +} +.br-table th { + text-align: left; + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--br-muted); + background: #f3f4f6; + padding: 10px; +} +.br-table td { + padding: 10px; + border-top: 1px solid var(--br-border); + vertical-align: top; +} +.br-table tr:nth-child(even) td { background: #f9fafb; } +.br-col-select { width: 50px; text-align: center; } +.br-col-status { width: 120px; } +.br-col-progress { width: 90px; } +.br-col-mode { width: 180px; } +.br-col-user { width: 140px; } +.br-col-backup { width: 220px; overflow-wrap: anywhere; } +.br-col-path { width: 260px; overflow-wrap: anywhere; } +.br-col-target { width: 220px; overflow-wrap: anywhere; } +.br-col-time { width: 150px; } +.br-col-actions { width: 120px; } +.br-table input[type="checkbox"] { + accent-color: var(--br-accent); + width: 16px; + height: 16px; +} +.br-actions { display: flex; gap: 8px; align-items: center; margin: 10px 0; } +.br-btn { + background: var(--br-accent); + color: #fff; + border: none; + border-radius: 6px; + padding: 8px 14px; + cursor: pointer; + font-weight: 600; +} +.br-btn:hover { background: var(--br-accent-dark); } +.br-btn:disabled, +.br-btn[disabled] { + opacity: 0.45; + cursor: not-allowed; +} +.br-btn:disabled:hover, +.br-btn[disabled]:hover { + background: var(--br-accent); +} +.br-btn-ghost { + background: transparent; + color: var(--br-text); + border: 1px solid var(--br-border); +} +.br-btn-ghost:hover { background: #eef2f7; } +.br-btn-ghost:disabled, +.br-btn-ghost[disabled] { + background: #f8fafc; + color: var(--br-muted); + border-color: var(--br-border); +} +.br-btn-ghost:disabled:hover, +.br-btn-ghost[disabled]:hover { + background: #f8fafc; +} +.br-btn-fogged { + background: var(--br-accent); + border: 1px solid var(--br-accent); + color: #ffffff; + opacity: 0.58; +} +.br-btn-fogged:hover { + background: var(--br-accent); +} +.br-btn-fogged:disabled, +.br-btn-fogged[disabled] { + background: var(--br-accent); + border-color: var(--br-accent); + color: #ffffff; + opacity: 0.58; +} +.br-btn-fogged:disabled:hover, +.br-btn-fogged[disabled]:hover { + background: var(--br-accent); +} +.br-btn-danger { + background: var(--br-danger); +} +.br-btn-danger:hover { background: #b91c1c; } +.br-link-danger { color: var(--br-danger); text-decoration: none; } +.br-link-danger:hover { text-decoration: underline; } +.br-inline-form { display: inline; } +.br-link-button { + background: none; + border: none; + padding: 0; + margin: 0; + color: inherit; + cursor: pointer; + font: inherit; +} +.br-link-button:hover { text-decoration: underline; } +.br-link-button.br-link-success { color: #15803d; } +.br-link-button.br-link-danger { color: var(--br-danger); } +input[type="text"] { + width: 100%; + max-width: 680px; + padding: 8px 10px; + border: 1px solid var(--br-border); + border-radius: 6px; + font-size: 14px; + background: #fff; +} +pre { + background: #0f172a; + color: #e2e8f0; + padding: 12px; + border-radius: 8px; + overflow: auto; +} +a { color: var(--br-accent-dark); text-decoration: none; } +a:hover { text-decoration: underline; } +.br-toast { + position: fixed; + bottom: 18px; + right: 18px; + background: #16a34a; + color: #fff; + padding: 12px 16px; + border-radius: 8px; + box-shadow: 0 12px 28px rgba(17, 24, 39, 0.25); + z-index: 9999; + font-weight: 600; +} +.br-toast-stack { + position: fixed; + top: 18px; + right: 18px; + display: flex; + flex-direction: column; + gap: 8px; + z-index: 9999; +} +.br-toast-stack .br-toast { + position: static; + top: auto; + bottom: auto; +} +.br-toast-error { + background: #dc2626; + top: 18px; + bottom: auto; +} +.br-toast-success { + background: #16a34a; +} +.br-status-line { + margin: 6px 0; + font-size: 14px; +} +.br-progress { + height: 8px; + background: #e5e7eb; + border-radius: 999px; + overflow: hidden; + margin: 6px 0 12px; +} +.br-progress-bar { + height: 100%; + background: var(--br-accent); + transition: width 0.3s ease; +} +.br-restore-layout { + display: grid; + gap: 16px; + grid-template-columns: minmax(320px, 420px) minmax(0, 1fr); + margin-top: 12px; +} +.br-restore-calendar, +.br-restore-picker { + border: 1px solid var(--br-border); + border-radius: 10px; + padding: 12px; + background: #fff; +} +.br-calendar-controls { + margin-bottom: 10px; +} +.br-month-nav { + display: grid; + grid-template-columns: 36px 1fr 36px; + gap: 8px; + align-items: center; + margin-bottom: 10px; +} +.br-month-nav form { + margin: 0; +} +.br-month-label { + text-align: center; + font-weight: 700; +} +.br-month-btn { + width: 36px; + height: 32px; + border: 1px solid var(--br-border); + border-radius: 8px; + background: #fff; + cursor: pointer; + font-weight: 700; +} +.br-month-btn:disabled { + opacity: 0.45; + cursor: not-allowed; +} +.br-calendar-grid { + width: 100%; + border-collapse: collapse; +} +.br-calendar-grid th, +.br-calendar-grid td { + border: 1px solid var(--br-border); + text-align: center; + padding: 4px; + width: 14.28%; + height: 38px; +} +.br-cal-day { + width: 100%; + height: 30px; + border: 1px solid var(--br-border); + background: #f8fafc; + border-radius: 6px; + cursor: pointer; +} +.br-cal-day.is-selected { + background: var(--br-accent); + color: #fff; + border-color: var(--br-accent); +} +.br-cal-day-disabled { + display: inline-block; + width: 100%; + color: var(--br-muted); + opacity: 0.45; +} +.br-cal-empty { + background: #f9fafb; +} +.br-time-list { + display: flex; + gap: 8px; + flex-wrap: wrap; + margin-bottom: 10px; +} +.br-time-item { + border: 1px solid var(--br-border); + border-radius: 999px; + padding: 6px 10px; + display: inline-flex; + align-items: center; + gap: 6px; + background: #fff; +} +.br-time-item input { + margin: 0; +} +@media (max-width: 980px) { + .br-restore-layout { + grid-template-columns: 1fr; + } +} +.br-tab-header { + display: flex; + gap: 8px; + flex-wrap: wrap; + margin-bottom: 12px; +} +.br-main-tabs { + display: flex; + gap: 8px; + flex-wrap: wrap; +} +.br-main-sections { + position: relative; +} +.br-main-sections.is-animating { + overflow: hidden; +} +.br-main-sections.is-animating .br-main-section { + position: absolute; + top: 0; + left: 0; + width: 100%; + margin-bottom: 0; + pointer-events: none; +} +.br-main-tab.is-active { + background: var(--br-accent); + color: #fff; + border-color: var(--br-accent); +} +.br-main-section.is-hidden { + display: none; +} +.br-user-tab.is-active { + background: var(--br-accent); + color: #fff; + border-color: var(--br-accent); +} +#user-tab-content { + will-change: transform, opacity; +} +.br-anim-enter-from-left { + animation: br-slide-in-from-left 0.24s ease both; +} +.br-anim-enter-from-right { + animation: br-slide-in-from-right 0.24s ease both; +} +.br-anim-leave-to-left { + animation: br-slide-out-to-left 0.24s ease both; +} +.br-anim-leave-to-right { + animation: br-slide-out-to-right 0.24s ease both; +} +@keyframes br-slide-in-from-left { + from { opacity: 0.2; transform: translateX(-24px); } + to { opacity: 1; transform: translateX(0); } +} +@keyframes br-slide-in-from-right { + from { opacity: 0.2; transform: translateX(24px); } + to { opacity: 1; transform: translateX(0); } +} +@keyframes br-slide-out-to-left { + from { opacity: 1; transform: translateX(0); } + to { opacity: 0; transform: translateX(-24px); } +} +@keyframes br-slide-out-to-right { + from { opacity: 1; transform: translateX(0); } + to { opacity: 0; transform: translateX(24px); } +} +.br-path-legend { + margin-top: 12px; + border-top: 1px dashed var(--br-border); + padding-top: 10px; +} +.br-path-legend p { + margin: 4px 0; +} diff --git a/user/evolution.css b/user/evolution.css new file mode 100644 index 0000000..527d110 --- /dev/null +++ b/user/evolution.css @@ -0,0 +1,569 @@ +:root { + --br-bg: #f5f8ff; + --br-card: #ffffff; + --br-text: #111827; + --br-muted: #64748b; + --br-border: #e2e8f0; + --br-accent: #2563eb; + --br-accent-dark: #1d4ed8; + --br-danger: #dc2626; + --br-warn: #f59e0b; + --br-shadow: 0 10px 28px rgba(15, 23, 42, 0.12); +} +* { box-sizing: border-box; } +body { + margin: 0; + background: var(--br-bg); + color: var(--br-text); + font-family: "IBM Plex Sans", "Segoe UI", Arial, sans-serif; +} +#content, +#content .container, +#content .container-fluid, +#content .main-content, +#content .page-content, +.main-content, +.page-content { + max-width: none !important; + width: 100% !important; +} +.br-container { + max-width: none; + width: 100%; + margin: 0; + padding: 16px 20px; +} +.br-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; +} +.br-title { + font-size: 20px; + font-weight: 700; + letter-spacing: 0.2px; +} +.br-card { + background: var(--br-card); + border: 1px solid var(--br-border); + border-radius: 12px; + padding: 16px; + box-shadow: var(--br-shadow); + margin-bottom: 16px; +} +.br-muted { color: var(--br-muted); } +.br-alert { + border-radius: 10px; + padding: 10px 12px; + margin: 8px 0; + border: 1px solid transparent; +} +.br-alert-error { background: #fff1f2; border-color: #fecdd3; color: #9f1239; } +.br-alert-warn { background: #fffbeb; border-color: #fde68a; color: #92400e; } +.br-alert-success { background: #ecfdf3; border-color: #bbf7d0; color: #166534; } +.br-overlay { + position: fixed; + inset: 0; + background: rgba(15, 23, 42, 0.6); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; +} +.br-modal { + width: min(560px, 92vw); + background: #ffffff; + border-radius: 12px; + padding: 16px; + border: 2px solid var(--br-border); + box-shadow: var(--br-shadow); + display: flex; + flex-direction: column; + max-height: 90vh; +} +.br-modal-log { + width: 90vw; + background: #ffffff; +} +.br-modal-danger { + border-color: var(--br-danger); +} +.br-modal-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 12px; +} +.br-modal-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-bottom: 10px; +} +.br-modal-title { + font-weight: 700; + font-size: 16px; +} +.br-modal-body { + max-height: 60vh; + overflow: auto; +} +.br-modal-scroll { + overflow: auto; + flex: 1; + min-height: 0; +} +.br-log-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} +.br-log-header-actions { + display: flex; + gap: 8px; +} +.br-log-meta { + display: grid; + gap: 6px; + margin: 10px 0 12px; + padding: 10px 12px; + border: 1px solid var(--br-border); + border-radius: 10px; + background: #f8fafc; + font-size: 13px; +} +.br-text-danger { + color: var(--br-danger); + font-weight: 700; +} +.br-log-actions { + margin: 8px 0 10px; +} +.br-picker-path { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 10px; + flex-wrap: wrap; +} +.br-picker-list { + list-style: none; + padding: 0; + margin: 0; + border: 1px solid var(--br-border); + border-radius: 10px; + max-height: 45vh; + overflow: auto; +} +.br-picker-create { + margin-top: 10px; + display: flex; + gap: 8px; +} +.br-picker-create input { + flex: 1; + padding: 6px 8px; + border: 1px solid var(--br-border); + border-radius: 8px; +} +.br-picker-list li { + padding: 8px 12px; + cursor: pointer; + border-bottom: 1px solid var(--br-border); +} +.br-picker-list li:last-child { + border-bottom: none; +} +.br-picker-list li:hover { + background: #f8fafc; +} +.br-picker-list li.active { + background: #d1fae5; +} +.br-btn-small { + padding: 6px 10px; + font-size: 12px; +} +.br-actions-row { + display: flex; + gap: 8px; + margin-top: 10px; + flex-wrap: wrap; +} +.br-actions-row form { + margin: 0; +} +.br-icon-btn { + border: 1px solid var(--br-border); + background: #fff; + border-radius: 8px; + padding: 4px 8px; + cursor: pointer; + font-size: 16px; + line-height: 1; +} +.br-icon-btn:hover { + background: #f8fafc; +} +.br-table { + width: 100%; + border-collapse: collapse; + border: 1px solid var(--br-border); + border-radius: 10px; + overflow: hidden; + background: #fff; + table-layout: auto; +} +.br-table th { + text-align: left; + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--br-muted); + background: #f1f5f9; + padding: 10px; +} +.br-table td { + padding: 10px; + border-top: 1px solid var(--br-border); + vertical-align: top; +} +.br-table tr:nth-child(even) td { background: #f8fafc; } +.br-col-select { width: 50px; text-align: center; } +.br-col-status { width: 120px; } +.br-col-progress { width: 90px; } +.br-col-mode { width: 180px; } +.br-col-user { width: 140px; } +.br-col-backup { width: 220px; overflow-wrap: anywhere; } +.br-col-path { width: 260px; overflow-wrap: anywhere; } +.br-col-target { width: 220px; overflow-wrap: anywhere; } +.br-col-time { width: 150px; } +.br-col-actions { width: 120px; } +.br-table input[type="checkbox"] { + accent-color: var(--br-accent); + width: 16px; + height: 16px; +} +.br-actions { display: flex; gap: 8px; align-items: center; margin: 10px 0; } +.br-btn { + background: var(--br-accent); + color: #fff; + border: none; + border-radius: 8px; + padding: 8px 14px; + cursor: pointer; + font-weight: 600; +} +.br-btn:hover { background: var(--br-accent-dark); } +.br-btn:disabled, +.br-btn[disabled] { + opacity: 0.45; + cursor: not-allowed; +} +.br-btn:disabled:hover, +.br-btn[disabled]:hover { + background: var(--br-accent); +} +.br-btn-ghost { + background: transparent; + color: var(--br-text); + border: 1px solid var(--br-border); +} +.br-btn-ghost:hover { background: #e2e8f0; } +.br-btn-ghost:disabled, +.br-btn-ghost[disabled] { + background: #f8fafc; + color: var(--br-muted); + border-color: var(--br-border); +} +.br-btn-ghost:disabled:hover, +.br-btn-ghost[disabled]:hover { + background: #f8fafc; +} +.br-btn-fogged { + background: var(--br-accent); + border: 1px solid var(--br-accent); + color: #ffffff; + opacity: 0.58; +} +.br-btn-fogged:hover { + background: var(--br-accent); +} +.br-btn-fogged:disabled, +.br-btn-fogged[disabled] { + background: var(--br-accent); + border-color: var(--br-accent); + color: #ffffff; + opacity: 0.58; +} +.br-btn-fogged:disabled:hover, +.br-btn-fogged[disabled]:hover { + background: var(--br-accent); +} +.br-btn-danger { + background: var(--br-danger); +} +.br-btn-danger:hover { background: #b91c1c; } +.br-link-danger { color: var(--br-danger); text-decoration: none; } +.br-link-danger:hover { text-decoration: underline; } +.br-inline-form { display: inline; } +.br-link-button { + background: none; + border: none; + padding: 0; + margin: 0; + color: inherit; + cursor: pointer; + font: inherit; +} +.br-link-button:hover { text-decoration: underline; } +.br-link-button.br-link-success { color: #15803d; } +.br-link-button.br-link-danger { color: var(--br-danger); } +input[type="text"] { + width: 100%; + max-width: 680px; + padding: 8px 10px; + border: 1px solid var(--br-border); + border-radius: 8px; + font-size: 14px; + background: #fff; +} +pre { + background: #0b1020; + color: #e2e8f0; + padding: 12px; + border-radius: 8px; + overflow: auto; +} +a { color: var(--br-accent-dark); text-decoration: none; } +a:hover { text-decoration: underline; } +.br-toast { + position: fixed; + bottom: 18px; + right: 18px; + background: #16a34a; + color: #fff; + padding: 12px 16px; + border-radius: 10px; + box-shadow: 0 12px 28px rgba(16, 24, 40, 0.25); + z-index: 9999; + font-weight: 600; +} +.br-toast-stack { + position: fixed; + top: 18px; + right: 18px; + display: flex; + flex-direction: column; + gap: 8px; + z-index: 9999; +} +.br-toast-stack .br-toast { + position: static; + top: auto; + bottom: auto; +} +.br-toast-error { + background: #dc2626; + top: 18px; + bottom: auto; +} +.br-toast-success { + background: #16a34a; +} +.br-status-line { + margin: 6px 0; + font-size: 14px; +} +.br-progress { + height: 8px; + background: #e5e7eb; + border-radius: 999px; + overflow: hidden; + margin: 6px 0 12px; +} +.br-progress-bar { + height: 100%; + background: var(--br-accent); + transition: width 0.3s ease; +} +.br-restore-layout { + display: grid; + gap: 16px; + grid-template-columns: minmax(320px, 420px) minmax(0, 1fr); + margin-top: 12px; +} +.br-restore-calendar, +.br-restore-picker { + border: 1px solid var(--br-border); + border-radius: 10px; + padding: 12px; + background: #fff; +} +.br-calendar-controls { + margin-bottom: 10px; +} +.br-month-nav { + display: grid; + grid-template-columns: 36px 1fr 36px; + gap: 8px; + align-items: center; + margin-bottom: 10px; +} +.br-month-nav form { + margin: 0; +} +.br-month-label { + text-align: center; + font-weight: 700; +} +.br-month-btn { + width: 36px; + height: 32px; + border: 1px solid var(--br-border); + border-radius: 8px; + background: #fff; + cursor: pointer; + font-weight: 700; +} +.br-month-btn:disabled { + opacity: 0.45; + cursor: not-allowed; +} +.br-calendar-grid { + width: 100%; + border-collapse: collapse; +} +.br-calendar-grid th, +.br-calendar-grid td { + border: 1px solid var(--br-border); + text-align: center; + padding: 4px; + width: 14.28%; + height: 38px; +} +.br-cal-day { + width: 100%; + height: 30px; + border: 1px solid var(--br-border); + background: #f8fafc; + border-radius: 6px; + cursor: pointer; +} +.br-cal-day.is-selected { + background: var(--br-accent); + color: #fff; + border-color: var(--br-accent); +} +.br-cal-day-disabled { + display: inline-block; + width: 100%; + color: var(--br-muted); + opacity: 0.45; +} +.br-cal-empty { + background: #f9fafb; +} +.br-time-list { + display: flex; + gap: 8px; + flex-wrap: wrap; + margin-bottom: 10px; +} +.br-time-item { + border: 1px solid var(--br-border); + border-radius: 999px; + padding: 6px 10px; + display: inline-flex; + align-items: center; + gap: 6px; + background: #fff; +} +.br-time-item input { + margin: 0; +} +@media (max-width: 980px) { + .br-restore-layout { + grid-template-columns: 1fr; + } +} +.br-tab-header { + display: flex; + gap: 8px; + flex-wrap: wrap; + margin-bottom: 12px; +} +.br-main-tabs { + display: flex; + gap: 8px; + flex-wrap: wrap; +} +.br-main-sections { + position: relative; +} +.br-main-sections.is-animating { + overflow: hidden; +} +.br-main-sections.is-animating .br-main-section { + position: absolute; + top: 0; + left: 0; + width: 100%; + margin-bottom: 0; + pointer-events: none; +} +.br-main-tab.is-active { + background: var(--br-accent); + color: #fff; + border-color: var(--br-accent); +} +.br-main-section.is-hidden { + display: none; +} +.br-user-tab.is-active { + background: var(--br-accent); + color: #fff; + border-color: var(--br-accent); +} +#user-tab-content { + will-change: transform, opacity; +} +.br-anim-enter-from-left { + animation: br-slide-in-from-left 0.24s ease both; +} +.br-anim-enter-from-right { + animation: br-slide-in-from-right 0.24s ease both; +} +.br-anim-leave-to-left { + animation: br-slide-out-to-left 0.24s ease both; +} +.br-anim-leave-to-right { + animation: br-slide-out-to-right 0.24s ease both; +} +@keyframes br-slide-in-from-left { + from { opacity: 0.2; transform: translateX(-24px); } + to { opacity: 1; transform: translateX(0); } +} +@keyframes br-slide-in-from-right { + from { opacity: 0.2; transform: translateX(24px); } + to { opacity: 1; transform: translateX(0); } +} +@keyframes br-slide-out-to-left { + from { opacity: 1; transform: translateX(0); } + to { opacity: 0; transform: translateX(-24px); } +} +@keyframes br-slide-out-to-right { + from { opacity: 1; transform: translateX(0); } + to { opacity: 0; transform: translateX(24px); } +} +.br-path-legend { + margin-top: 12px; + border-top: 1px dashed var(--br-border); + padding-top: 10px; +} +.br-path-legend p { + margin: 4px 0; +} diff --git a/user/index.html b/user/index.html new file mode 100755 index 0000000..c055948 --- /dev/null +++ b/user/index.html @@ -0,0 +1,4795 @@ +#!/usr/local/bin/php -nc/usr/local/directadmin/plugins/db-manager/php.ini + [], + 'present' => [], + ]; + if (!is_readable($path)) { + return $result; + } + $lines = file($path, FILE_IGNORE_NEW_LINES); + if ($lines === false) { + return $result; + } + foreach ($lines as $line) { + $line = trim($line); + if ($line === '' || $line[0] === '#') { + continue; + } + if (!preg_match('/^([A-Za-z0-9_]+)=(.*)$/', $line, $m)) { + continue; + } + $key = trim((string)$m[1]); + if ($key === '') { + continue; + } + $result['values'][$key] = parse_sh_value((string)$m[2]); + $result['present'][$key] = true; + } + return $result; +} + +function parse_plugins_list(string $value): array +{ + $value = trim($value); + if ($value === '') { + return []; + } + $out = []; + foreach (explode(':', $value) as $part) { + $part = strtolower(trim($part)); + if ($part === '') { + continue; + } + $out[] = $part; + } + return array_values(array_unique($out)); +} + +function conf_explicit_bool(array $conf, array $keys, ?bool &$out): bool +{ + $values = $conf['values'] ?? []; + $present = $conf['present'] ?? []; + $keys_lc = array_map('strtolower', $keys); + foreach ($present as $conf_key => $is_present) { + if (!$is_present) { + continue; + } + $conf_key_lc = strtolower((string)$conf_key); + if (!in_array($conf_key_lc, $keys_lc, true)) { + continue; + } + $out = parse_bool((string)($values[$conf_key] ?? ''), false); + return true; + } + return false; +} + +function is_custom_package_item_enabled(array $conf): bool +{ + $enabled = null; + if (conf_explicit_bool($conf, ['db_manager'], $enabled)) { + return (bool)$enabled; + } + return false; +} + +function is_plugin_allowed_by_conf(array $conf, string $plugin_id): bool +{ + $values = $conf['values'] ?? []; + $present = $conf['present'] ?? []; + $plugin_id = strtolower(trim($plugin_id)); + if ($plugin_id === '') { + return false; + } + + if (!empty($present['plugins_allow'])) { + $allow = parse_plugins_list((string)($values['plugins_allow'] ?? '')); + return in_array($plugin_id, $allow, true); + } + + if (!empty($present['plugins_deny'])) { + $deny = parse_plugins_list((string)($values['plugins_deny'] ?? '')); + return !in_array($plugin_id, $deny, true); + } + + return true; +} + +function resolve_package_conf_path(array $user_conf, string $da_user): string +{ + $values = $user_conf['values'] ?? []; + $package_name = trim((string)($values['package'] ?? $values['user_package'] ?? '')); + if ($package_name === '' || !preg_match('/^[A-Za-z0-9._-]+$/', $package_name)) { + return ''; + } + + $owners = []; + foreach (['creator', 'owner', 'reseller', 'username'] as $key) { + $owner = trim((string)($values[$key] ?? '')); + if ($owner !== '' && preg_match('/^[A-Za-z0-9._-]+$/', $owner)) { + $owners[] = $owner; + } + } + if (preg_match('/^[A-Za-z0-9._-]+$/', $da_user)) { + $owners[] = $da_user; + } + $owners[] = 'admin'; + $owners = array_values(array_unique($owners)); + + foreach ($owners as $owner) { + foreach (['', '.conf'] as $ext) { + $candidate = '/usr/local/directadmin/data/users/' . $owner . '/packages/' . $package_name . $ext; + if (is_readable($candidate)) { + return $candidate; + } + } + } + return ''; +} + +function is_plugin_enabled_for_user(string $da_user, string $plugin_id, array $settings = []): bool +{ + $base = '/usr/local/directadmin/data/users/' . $da_user; + if (parse_bool((string)($settings['Always_enable'] ?? '0'), false)) { + return true; + } + + $user_conf_path = $base . '/user.conf'; + $user_conf = load_conf_with_presence($user_conf_path); + if (!is_custom_package_item_enabled($user_conf)) { + return false; + } + if (!is_plugin_allowed_by_conf($user_conf, $plugin_id)) { + return false; + } + + $package_conf_path = resolve_package_conf_path($user_conf, $da_user); + if ($package_conf_path === '') { + return true; + } + $package_conf = load_conf_with_presence($package_conf_path); + if (!is_custom_package_item_enabled($package_conf)) { + return false; + } + return is_plugin_allowed_by_conf($package_conf, $plugin_id); +} + +function load_lang_map(string $lang, string $plugin_dir): array +{ + $lang = normalize_lang_code($lang); + $lang_dir = $plugin_dir . '/lang'; + $fallback_path = $lang_dir . '/en.php'; + $lang_path = $lang_dir . '/' . $lang . '.php'; + + $fallback = []; + if (is_readable($fallback_path)) { + $fallback_map = include $fallback_path; + if (is_array($fallback_map)) { + $fallback = $fallback_map; + } + } + if ($lang === 'en') { + return $fallback; + } + if (!is_readable($lang_path)) { + return $fallback; + } + $selected = include $lang_path; + if (!is_array($selected)) { + return $fallback; + } + return array_merge($fallback, $selected); +} + +function translate_text(string $text, array $map): string +{ + if (empty($map)) { + return $text; + } + uksort($map, function ($a, $b) { + return strlen((string)$b) <=> strlen((string)$a); + }); + return str_replace(array_keys($map), array_values($map), $text); +} + +function translate_html_safe(string $html, array $map): string +{ + if (empty($map)) { + return $html; + } + $parts = preg_split('~(]*>.*?)~is', $html, -1, PREG_SPLIT_DELIM_CAPTURE); + if ($parts === false) { + return translate_text($html, $map); + } + $out = ''; + foreach ($parts as $part) { + if (preg_match('~^ '/home/admin/mysql_backups', + 'USER_SQL_BACKUP_DIR' => '/home/da_user/mysql_backups/user_backups', + 'ALLOW_DOWNLOAD_HITME_SQL_BACKUP' => 'true', + 'ENABLE_USER_BACKUPS' => 'true', + 'PRE_RESTORE_BACKUP_DIR' => '/home/da_user/mysql_backups/pre_restore_backups', + 'PRE_RESTORE_USER_BACKUP_DIR' => '/home/da_user/mysql_backups/pre_restore_user_backups', + 'ENABLE_PRE_RESTORE_BACKUP' => 'true', + 'ENABLE_USER_PRE_RESTORE_BACKUP' => 'true', + 'SECURE_BACKUP' => 'true', + 'SECURE_RESTORE' => 'true', + 'PREVENT_LOG_DELETION' => 'true', + 'OWN_BACKUP_USER_RESTORE' => 'true', + 'CHECK_DATABASE_COLLISIONS' => 'true', + 'ENABLE_OPERATION_LIMITS' => 'true', + 'RESTORE_LIMIT_5MIN' => '10', + 'RESTORE_LIMIT_HOUR' => '30', + 'RESTORE_LIMIT_DAY' => '100', + 'BACKUP_LIMIT_5MIN' => '10', + 'BACKUP_LIMIT_HOUR' => '30', + 'BACKUP_LIMIT_DAY' => '100', + 'ENABLE_LOGS_OVERLAY' => 'true', + 'Always_enable' => '0', + ]; + if (!is_readable($path)) { + return $settings; + } + $lines = file($path, FILE_IGNORE_NEW_LINES); + if ($lines === false) { + return $settings; + } + foreach ($lines as $line) { + $line = trim($line); + if ($line === '' || $line[0] === '#') { + continue; + } + if (preg_match('/^(HITME_SQL_BACKUP_DIR|USER_SQL_BACKUP_DIR|ALLOW_DOWNLOAD_HITME_SQL_BACKUP|ENABLE_USER_BACKUPS|PRE_RESTORE_BACKUP_DIR|PRE_RESTORE_USER_BACKUP_DIR|ENABLE_PRE_RESTORE_BACKUP|ENABLE_USER_PRE_RESTORE_BACKUP|SECURE_BACKUP|SECURE_RESTORE|PREVENT_LOG_DELETION|OWN_BACKUP_USER_RESTORE|CHECK_DATABASE_COLLISIONS|ENABLE_OPERATION_LIMITS|RESTORE_LIMIT_5MIN|RESTORE_LIMIT_HOUR|RESTORE_LIMIT_DAY|BACKUP_LIMIT_5MIN|BACKUP_LIMIT_HOUR|BACKUP_LIMIT_DAY|ENABLE_LOGS_OVERLAY|Always_enable)=(.*)$/', $line, $m)) { + $key = (string)$m[1]; + $value = parse_sh_value($m[2]); + $settings[$key] = $value; + } + } + return $settings; +} + +function apply_da_user(string $value, string $da_user): string +{ + $value = str_replace('DA_USER', $da_user, $value); + $value = str_replace('da_user', $da_user, $value); + return $value; +} + +function read_params(): array +{ + $params = []; + if (!empty($_GET)) { + $params = array_merge($params, $_GET); + } + if (!empty($_POST)) { + $params = array_merge($params, $_POST); + } + + $get_raw = getenv('GET') ?: getenv('get') ?: ''; + if ($get_raw !== '') { + parse_str($get_raw, $parsed); + $params = array_merge($parsed, $params); + } + + $qs = $_SERVER['QUERY_STRING'] ?? ''; + if ($qs !== '') { + parse_str($qs, $parsed); + $params = array_merge($parsed, $params); + } + + $post_raw = getenv('POST') ?: getenv('post') ?: ''; + if ($post_raw !== '') { + parse_str($post_raw, $parsed); + $params = array_merge($parsed, $params); + } + + return $params; +} + +function load_job_vars(string $path): array +{ + $vars = [ + 'DA_USER' => '', + 'DB_NAME' => '', + 'SOURCE_DB_NAME' => '', + 'JOB_TYPE' => '', + 'BACKUP_SOURCE' => '', + 'RESTORE_MODE' => '', + 'SANITIZATION_REQUIRED' => '', + 'DATE_DIR' => '', + 'DUMP_FILE' => '', + 'PRE_BACKUP_PATH' => '', + 'START_TS' => '', + 'PID' => '', + ]; + if (!is_readable($path)) { + return $vars; + } + $lines = file($path, FILE_IGNORE_NEW_LINES); + if ($lines === false) { + return $vars; + } + foreach ($lines as $line) { + $line = trim($line); + if ($line === '' || $line[0] === '#') { + continue; + } + if (preg_match('/^(DA_USER|DB_NAME|SOURCE_DB_NAME|JOB_TYPE|BACKUP_SOURCE|RESTORE_MODE|SANITIZATION_REQUIRED|DATE_DIR|DUMP_FILE|PRE_BACKUP_PATH|START_TS|PID)=(.*)$/', $line, $m)) { + $vars[$m[1]] = parse_sh_value($m[2]); + } + } + return $vars; +} + +function normalize_backup_source(array $vars): string +{ + $source = strtolower(trim((string)($vars['BACKUP_SOURCE'] ?? ''))); + return $source === 'user' ? 'user' : 'hosting'; +} + +function normalize_job_type(array $vars): string +{ + $job_type = strtolower(trim((string)($vars['JOB_TYPE'] ?? ''))); + return $job_type === 'backup' ? 'backup' : 'restore'; +} + +function normalize_restore_mode(array $vars): string +{ + $mode = strtolower(trim((string)($vars['RESTORE_MODE'] ?? ''))); + if ($mode === 'new_db' || $mode === 'overwrite') { + return $mode; + } + $db = trim((string)($vars['DB_NAME'] ?? '')); + $source_db = trim((string)($vars['SOURCE_DB_NAME'] ?? '')); + if ($db !== '' && $source_db !== '' && $db !== $source_db) { + return 'new_db'; + } + return 'overwrite'; +} + +function sanitize_required_label(array $vars): string +{ + $raw = strtolower(trim((string)($vars['SANITIZATION_REQUIRED'] ?? ''))); + if (in_array($raw, ['tak', 'true', '1', 'yes', 'y', 'on'], true)) { + return 'Tak'; + } + if (in_array($raw, ['nie', 'false', '0', 'no', 'n', 'off'], true)) { + return 'Nie'; + } + $source = trim((string)($vars['SOURCE_DB_NAME'] ?? '')); + $target = trim((string)($vars['DB_NAME'] ?? '')); + if ($source !== '' && $target !== '' && $source !== $target) { + return 'Tak'; + } + return 'Nie'; +} + +function backup_source_label(string $source): string +{ + if ($source === 'user') { + return t('Kopia bazy uzytkownika'); + } + return t('Kopia bazy udostepnianej przez HITME.PL'); +} + +function restore_mode_label(string $mode, string $job_type): string +{ + if ($job_type === 'backup') { + return t('Tworzenie Kopii bazy danych'); + } + if ($mode === 'new_db') { + return t('Przywracanie bazy danych - Do nowej bazy danych'); + } + return t('Przywracanie bazy danych - Nadpisanie oryginalnej bazy danych'); +} + +function job_id_from_file(string $file): string +{ + $base = basename($file); + if (substr($base, -3) === '.ok') { + $base = substr($base, 0, -3); + } elseif (substr($base, -5) === '.fail') { + $base = substr($base, 0, -5); + } elseif (substr($base, -7) === '.cancel') { + $base = substr($base, 0, -7); + } + if (substr($base, -4) === '.env') { + $base = substr($base, 0, -4); + } + return $base; +} + +function find_job_file(string $job_base, string $file, string $job_id): string +{ + $candidates = [ + $job_base . '/processing/' . $file, + $job_base . '/pending/' . $file, + $job_base . '/done/' . $file, + ]; + if ($job_id !== '') { + $candidates[] = $job_base . '/processing/' . $job_id . '.env'; + $candidates[] = $job_base . '/pending/' . $job_id . '.env'; + $candidates[] = $job_base . '/done/' . $job_id . '.ok'; + $candidates[] = $job_base . '/done/' . $job_id . '.fail'; + $candidates[] = $job_base . '/done/' . $job_id . '.cancel'; + } + foreach ($candidates as $path) { + if (is_file($path)) { + return $path; + } + } + return ''; +} + +function job_belongs_to_user(string $job_id, string $job_base, string $da_user): bool +{ + if ($job_id === '') { + return false; + } + $file = find_job_file($job_base, $job_id . '.env', $job_id); + if ($file === '') { + return false; + } + $vars = load_job_vars($file); + return ($vars['DA_USER'] ?? '') === $da_user; +} + +function h(string $value): string +{ + return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); +} + +function format_datetime(string $value, int $fallback = 0): string +{ + $ts = is_numeric($value) ? (int)$value : 0; + if ($ts <= 0) { + $ts = $fallback > 0 ? $fallback : time(); + } + return date('d-m-Y H:i:s', $ts); +} + +function normalize_log(string $text): string +{ + $text = str_replace("\r", "", $text); + return rtrim($text); +} + +function log_text_output(string $text): void +{ + header('Content-Type: text/plain; charset=utf-8'); + echo '__DB_RESTORE_LOG_TEXT__'; + echo $text; + echo '__DB_RESTORE_LOG_TEXT__'; + exit; +} + +function read_log_tail(string $path, int $max_lines = 200): string +{ + if (!is_readable($path)) { + return ''; + } + $lines = file($path, FILE_IGNORE_NEW_LINES); + if ($lines === false) { + return ''; + } + if (count($lines) > $max_lines) { + $lines = array_slice($lines, -$max_lines); + } + return implode("\n", $lines); +} + +function last_non_empty_line(string $text): string +{ + $lines = preg_split('/\r?\n/', $text); + if ($lines === false) { + return ''; + } + for ($i = count($lines) - 1; $i >= 0; $i--) { + $line = trim((string)$lines[$i]); + if ($line !== '') { + return $line; + } + } + return ''; +} + +function extract_progress(string $line): ?int +{ + if (preg_match('/\b(\d{1,3})%\b/', $line, $m)) { + $progress = (int)$m[1]; + if ($progress < 0) { + $progress = 0; + } + if ($progress > 100) { + $progress = 100; + } + return $progress; + } + return null; +} + +function display_status(string $base_status, string $last_line): string +{ + if ($base_status !== 'processing') { + return $base_status; + } + $line = strtolower(trim($last_line)); + if ($line === '') { + return $base_status; + } + if (strpos($line, 'preparing') !== false || strpos($line, 'przygotow') !== false) { + return 'processing'; + } + return $base_status; +} + +function status_label(string $status): string +{ + $map = [ + 'pending' => 'Oczekuje', + 'processing' => 'W trakcie', + 'done' => 'Zakonczone', + 'failed' => 'Blad', + 'canceled' => 'Anulowane', + 'unknown' => 'Nieznany', + ]; + return t($map[$status] ?? 'Nieznany'); +} + +function get_job_status(string $job_id, string $job_base): string +{ + if (is_file($job_base . '/processing/' . $job_id . '.env')) { + return 'processing'; + } + if (is_file($job_base . '/pending/' . $job_id . '.env')) { + return 'pending'; + } + if (is_file($job_base . '/done/' . $job_id . '.ok')) { + return 'done'; + } + if (is_file($job_base . '/done/' . $job_id . '.fail')) { + return 'failed'; + } + if (is_file($job_base . '/done/' . $job_id . '.cancel')) { + return 'canceled'; + } + return 'unknown'; +} + +function build_log_download_content(string $job_id, string $job_file_path, string $log_path, string $job_base): string +{ + $vars = load_job_vars($job_file_path); + $log_meta_start = format_datetime($vars['START_TS'] ?? '', time()); + $log_meta_db = $vars['DB_NAME'] ?? '-'; + $log_meta_date = format_backup_label((string)($vars['DATE_DIR'] ?? '-')); + $job_type = normalize_job_type($vars); + $backup_source = normalize_backup_source($vars); + $restore_mode = normalize_restore_mode($vars); + $log_meta_backup_kind = backup_source_label($backup_source); + $log_meta_restore_mode = restore_mode_label($restore_mode, $job_type); + $log_meta_sanitization = sanitize_required_label($vars); + + $log_text = is_file($log_path) && is_readable($log_path) ? (string)file_get_contents($log_path) : ''; + $log_text = normalize_log($log_text); + $last_line = last_non_empty_line($log_text); + $progress = extract_progress($last_line); + $display = display_status(get_job_status($job_id, $job_base), $last_line); + $status = status_label($display); + if (in_array($display, ['done', 'failed', 'canceled'], true)) { + $progress_text = $display === 'done' ? '100%' : status_label($display); + } else { + $progress_text = $progress !== null ? ($progress . '%') : ($last_line !== '' ? $last_line : '-'); + } + + $header = "Data rozpoczecia: {$log_meta_start}\n"; + $header .= "Baza danych: {$log_meta_db}\n"; + $header .= "Backup (data katalogu): {$log_meta_date}\n"; + $header .= "Rodzaj przywracanej bazy: {$log_meta_backup_kind}\n"; + $header .= "Tryb przywracania: {$log_meta_restore_mode}\n"; + $header .= "Koniecznosc Sanityzacji: {$log_meta_sanitization}\n"; + $header .= "Status: {$status}\n"; + $header .= "Postep: {$progress_text}\n"; + $header .= "----- log -----\n"; + + $body = $log_text !== '' ? ($log_text . "\n") : "Log nie istnieje.\n"; + return $header . $body; +} + +function load_user_skin(string $user): string +{ + $path = '/usr/local/directadmin/data/users/' . $user . '/user.conf'; + if (!is_readable($path)) { + return 'enhanced'; + } + $lines = file($path, FILE_IGNORE_NEW_LINES); + if ($lines === false) { + return 'enhanced'; + } + foreach ($lines as $line) { + $line = trim($line); + if ($line === '' || $line[0] === '#') { + continue; + } + if (preg_match('/^skin=(.+)$/', $line, $m)) { + $skin = strtolower(trim($m[1])); + if (strpos($skin, 'evolution') !== false) { + return 'evolution'; + } + if (strpos($skin, 'enhanced') !== false) { + return 'enhanced'; + } + return $skin; + } + } + return 'enhanced'; +} + +function list_backup_dates(string $base_dir): array +{ + if (!is_dir($base_dir) || !is_readable($base_dir)) { + return []; + } + $base_real = realpath($base_dir); + if ($base_real === false) { + return []; + } + $items = []; + foreach (scandir($base_dir) ?: [] as $entry) { + if ($entry === '.' || $entry === '..') { + continue; + } + if (!preg_match('/^[A-Za-z0-9._-]+$/', $entry)) { + continue; + } + $path = $base_dir . '/' . $entry; + if (!is_dir($path) || is_link($path)) { + continue; + } + $path_real = realpath($path); + if ($path_real === false || strpos($path_real, $base_real . '/') !== 0) { + continue; + } + $items[] = [ + 'name' => $entry, + 'mtime' => @filemtime($path) ?: 0, + ]; + } + usort($items, function ($a, $b) { + return $b['mtime'] <=> $a['mtime']; + }); + return array_map(function ($item) { + return $item['name']; + }, $items); +} + +function parse_backup_dir_timestamp(string $dir_name, int $fallback = 0): int +{ + if (preg_match('/^(\d{2})[_-](\d{2})[_-](\d{2,4})(?:[-_](\d{2})[_-](\d{2})(?:[_-](\d{2}))?)?$/', $dir_name, $m)) { + $day = (int)$m[1]; + $month = (int)$m[2]; + $year = (int)$m[3]; + if ($year < 100) { + $year += 2000; + } + $hour = isset($m[4]) ? (int)$m[4] : 0; + $minute = isset($m[5]) ? (int)$m[5] : 0; + $second = isset($m[6]) ? (int)$m[6] : 0; + if (checkdate($month, $day, $year)) { + return mktime($hour, $minute, $second, $month, $day, $year); + } + } + return $fallback > 0 ? $fallback : 0; +} + +function format_backup_label(string $date_dir): string +{ + $ts = parse_backup_dir_timestamp($date_dir, 0); + if ($ts > 0) { + return date('d-m-Y H:i:s', $ts); + } + return $date_dir; +} + +function build_restore_confirmation_message( + string $source_type, + string $source_db, + string $target_db, + string $restore_mode, + string $backup_label = '' +): string { + $source_db = trim($source_db); + $target_db = trim($target_db); + if ($source_db === '') { + $source_db = '(nieznana baza)'; + } + if ($target_db === '') { + $target_db = $source_db; + } + + if ($source_type === 'user') { + $message = 'Czy na pewno chcesz przywrócić backup bazy danych z kopii zapasowej użytkownika'; + if ($backup_label !== '') { + $message .= ' z dn. ' . $backup_label; + } + } else { + $message = 'Czy na pewno chcesz przywrócić backup bazy danych z kopii zapasowych HITME.PL'; + } + + if ($restore_mode === 'new_db') { + $message .= ' Nazwa bazy: ' . $source_db . ' i przywrócić kopię do nowej bazy danych - ' . $target_db . '.'; + $message .= "\n" . 'Wskazana nowa baza danych ' . $target_db . ' musi istnieć. Utwórz ją ręcznie w DirectAdmin przed kontynuowaniem.'; + $message .= "\n" . 'Docelowa baza danych ' . $target_db . ' nie może zawierać żadnych danych przed kontynuowaniem operacji.'; + return $message; + } + + $message .= ' Nazwa bazy: ' . $source_db . ' i nadpisać istniejącą bazę danych - ' . $target_db . '.'; + return $message; +} + +function format_month_label(string $month_value, string $lang_code): string +{ + if (!preg_match('/^(\d{4})-(\d{2})$/', $month_value, $m)) { + return $month_value; + } + $year = $m[1]; + $month = (int)$m[2]; + $months_pl = [ + 1 => 'styczen', 2 => 'luty', 3 => 'marzec', 4 => 'kwiecien', + 5 => 'maj', 6 => 'czerwiec', 7 => 'lipiec', 8 => 'sierpien', + 9 => 'wrzesien', 10 => 'pazdziernik', 11 => 'listopad', 12 => 'grudzien', + ]; + $months_en = [ + 1 => 'January', 2 => 'February', 3 => 'March', 4 => 'April', + 5 => 'May', 6 => 'June', 7 => 'July', 8 => 'August', + 9 => 'September', 10 => 'October', 11 => 'November', 12 => 'December', + ]; + $list = $lang_code === 'pl' ? $months_pl : $months_en; + $name = $list[$month] ?? $month_value; + return $name . ' ' . $year; +} + +function month_nav_targets(array $available_months, string $selected_month): array +{ + $prev = ''; + $next = ''; + $idx = array_search($selected_month, $available_months, true); + if ($idx !== false) { + if ($idx < count($available_months) - 1) { + $prev = $available_months[$idx + 1]; + } + if ($idx > 0) { + $next = $available_months[$idx - 1]; + } + } + return ['prev' => $prev, 'next' => $next]; +} + +function calendar_weekdays(string $lang_code): array +{ + if ($lang_code === 'pl') { + return ['Pn', 'Wt', 'Sr', 'Cz', 'Pt', 'So', 'Nd']; + } + return ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; +} + +function render_hidden_inputs_from_params(array $params, array $exclude = []): string +{ + $exclude_map = array_fill_keys($exclude, true); + $out = ''; + foreach ($params as $key => $value) { + if (!is_string($key) || isset($exclude_map[$key])) { + continue; + } + $safe_key = htmlspecialchars($key, ENT_QUOTES, 'UTF-8'); + if (is_array($value)) { + foreach ($value as $item) { + if (is_array($item) || is_object($item)) { + continue; + } + $safe_value = htmlspecialchars((string)$item, ENT_QUOTES, 'UTF-8'); + $out .= '' . "\n"; + } + continue; + } + if (is_object($value)) { + continue; + } + $safe_value = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); + $out .= '' . "\n"; + } + return $out; +} + +function render_warning_overlay(string $title, string $message, array $params, string $base_url): string +{ + $hidden = render_hidden_inputs_from_params($params, ['confirm_action']); + $lines = preg_split('/\r?\n/', $message) ?: [$message]; + $printed = 0; + ob_start(); + ?> +
+
+

+ + +

Uwaga:

+ + + + + +
+ Nie + +
+ +
+
+ + + + + + + DB-Manager + + + + + + +
+

Manager kopii zapasowych baz danych

+
+
+
+
+ + + 0 ? date('Y-m-d', $ts) : $dir_name; + $slot = [ + 'dir' => $dir_name, + 'ts' => $ts, + 'day_key' => $day_key, + 'day_label' => $ts > 0 ? date('d-m-Y', $ts) : $day_key, + 'time_label' => $ts > 0 ? date('H:i:s', $ts) : '-', + ]; + $slots[] = $slot; + if (!isset($days[$day_key])) { + $days[$day_key] = [ + 'day_key' => $day_key, + 'day_label' => $slot['day_label'], + 'ts' => $ts, + 'slots' => [], + ]; + } + $days[$day_key]['slots'][] = $slot; + if ($ts > $days[$day_key]['ts']) { + $days[$day_key]['ts'] = $ts; + } + } + + usort($slots, function ($a, $b) { + return $b['ts'] <=> $a['ts']; + }); + foreach ($days as &$day_info) { + usort($day_info['slots'], function ($a, $b) { + return $b['ts'] <=> $a['ts']; + }); + } + unset($day_info); + + uasort($days, function ($a, $b) { + return $b['ts'] <=> $a['ts']; + }); + + return [ + 'slots' => $slots, + 'days' => $days, + ]; +} + +function is_trusted_user_backup_dir(string $path_real, string $da_user): bool +{ + $marker = rtrim($path_real, '/') . '/.db-manager-owned'; + if (is_readable($marker) && is_file($marker) && !is_link($marker)) { + $owner = ''; + $user = ''; + foreach (file($marker, FILE_IGNORE_NEW_LINES) ?: [] as $line) { + $line = trim((string)$line); + if ($line === '' || strpos($line, '=') === false) { + continue; + } + [$k, $v] = explode('=', $line, 2); + $k = trim((string)$k); + $v = trim((string)$v); + if ($k === 'OWNER') { + $owner = $v; + } elseif ($k === 'DA_USER') { + $user = $v; + } + } + if ($owner === 'db-manager' && $user === $da_user) { + return true; + } + } + + $uid = @fileowner($path_real); + if ($uid === 0) { + return true; + } + + return false; +} + +function list_user_databases(string $base_dir, string $date_dir, string $da_user, bool $trusted_only = false): array +{ + if ($date_dir === '' || !preg_match('/^[A-Za-z0-9._-]+$/', $date_dir)) { + return []; + } + $base_real = realpath($base_dir); + $path = $base_dir . '/' . $date_dir; + $path_real = realpath($path); + if ($base_real === false || $path_real === false || strpos($path_real, $base_real . '/') !== 0) { + return []; + } + if (!is_dir($path_real) || !is_readable($path_real) || is_link($path_real)) { + return []; + } + if ($trusted_only && !is_trusted_user_backup_dir($path_real, $da_user)) { + return []; + } + $items = []; + foreach (scandir($path_real) ?: [] as $base) { + if ($base === '.' || $base === '..') { + continue; + } + $file = $path_real . '/' . $base; + if (!is_file($file) || is_link($file)) { + continue; + } + $file_real = realpath($file); + if ($file_real === false || strpos($file_real, $path_real . '/') !== 0) { + continue; + } + $db_name = db_name_from_dump_filename($base); + if ($db_name === '') { + continue; + } + if ($db_name !== $da_user && strpos($db_name, $da_user . '_') !== 0) { + continue; + } + $items[] = [ + 'name' => $db_name, + 'file' => $file_real, + 'size' => @filesize($file_real) ?: 0, + 'mtime' => @filemtime($file_real) ?: 0, + ]; + } + usort($items, function ($a, $b) { + return strcmp($a['name'], $b['name']); + }); + return $items; +} + +function collect_backup_slots_filtered(string $base_dir, string $da_user, bool $trusted_only = false): array +{ + $slots = []; + $days = []; + foreach (list_backup_dates($base_dir) as $dir_name) { + $dbs = list_user_databases($base_dir, $dir_name, $da_user, $trusted_only); + if (empty($dbs)) { + continue; + } + $path = $base_dir . '/' . $dir_name; + $mtime = @filemtime($path) ?: 0; + $ts = parse_backup_dir_timestamp($dir_name, $mtime); + $day_key = $ts > 0 ? date('Y-m-d', $ts) : $dir_name; + $slot = [ + 'dir' => $dir_name, + 'ts' => $ts, + 'day_key' => $day_key, + 'day_label' => $ts > 0 ? date('d-m-Y', $ts) : $day_key, + 'time_label' => $ts > 0 ? date('H:i:s', $ts) : '-', + ]; + $slots[] = $slot; + if (!isset($days[$day_key])) { + $days[$day_key] = [ + 'day_key' => $day_key, + 'day_label' => $slot['day_label'], + 'ts' => $ts, + 'slots' => [], + ]; + } + $days[$day_key]['slots'][] = $slot; + if ($ts > $days[$day_key]['ts']) { + $days[$day_key]['ts'] = $ts; + } + } + + usort($slots, function ($a, $b) { + return $b['ts'] <=> $a['ts']; + }); + foreach ($days as &$day_info) { + usort($day_info['slots'], function ($a, $b) { + return $b['ts'] <=> $a['ts']; + }); + } + unset($day_info); + + uasort($days, function ($a, $b) { + return $b['ts'] <=> $a['ts']; + }); + + return [ + 'slots' => $slots, + 'days' => $days, + ]; +} + +function find_user_backup_entry(array $items, string $backup_file): array +{ + if ($backup_file === '' || !preg_match('/^[A-Za-z0-9._-]+$/', $backup_file)) { + return []; + } + foreach ($items as $item) { + $item_file = basename((string)($item['file'] ?? '')); + if ($item_file === $backup_file) { + return $item; + } + } + return []; +} + +function csrf_token_path(string $data_dir, string $user): string +{ + $safe = preg_replace('/[^A-Za-z0-9_.-]/', '_', $user); + return $data_dir . '/csrf/' . $safe . '.token'; +} + +function get_csrf_token(string $data_dir, string $user): string +{ + $path = csrf_token_path($data_dir, $user); + $dir = dirname($path); + if (!is_dir($dir)) { + @mkdir($dir, 0700, true); + } + if (is_readable($path)) { + $mtime = @filemtime($path) ?: 0; + if ($mtime > 0 && $mtime > (time() - 12 * 3600)) { + $token = trim((string)file_get_contents($path)); + if ($token !== '') { + return $token; + } + } + } + $token = bin2hex(random_bytes(32)); + @file_put_contents($path, $token, LOCK_EX); + @chmod($path, 0600); + return $token; +} + +function check_csrf(string $token, string $data_dir, string $user): bool +{ + $path = csrf_token_path($data_dir, $user); + if (!is_readable($path)) { + return false; + } + $stored = trim((string)file_get_contents($path)); + if ($stored === '' || $token === '') { + return false; + } + return hash_equals($stored, $token); +} + +function is_path_within_dir(string $path, string $dir): bool +{ + $real_path = realpath($path); + $real_dir = realpath($dir); + if ($real_path === false || $real_dir === false) { + return false; + } + if ($real_path === $real_dir) { + return true; + } + return strpos($real_path, $real_dir . '/') === 0; +} + +function is_path_within_any_dir(string $path, array $dirs): bool +{ + foreach ($dirs as $dir) { + if ($dir !== '' && is_path_within_dir($path, $dir)) { + return true; + } + } + return false; +} + +function read_mysql_conf_values(string $path): array +{ + $values = [ + 'user' => '', + 'passwd' => '', + 'host' => '', + 'port' => '', + 'socket' => '', + ]; + if (!is_readable($path)) { + return $values; + } + $lines = file($path, FILE_IGNORE_NEW_LINES); + if ($lines === false) { + return $values; + } + foreach ($lines as $line) { + $line = trim($line); + if ($line === '' || $line[0] === '#') { + continue; + } + if (strpos($line, '=') === false) { + continue; + } + [$key, $val] = explode('=', $line, 2); + $key = trim($key); + $val = trim($val); + if (array_key_exists($key, $values)) { + $values[$key] = $val; + } + } + return $values; +} + +function resolve_bin_path(array $names): string +{ + $dirs = [ + '/usr/local/mysql/bin', + '/usr/local/mariadb/bin', + '/usr/local/bin', + '/usr/bin', + '/bin', + ]; + $env_path = getenv('PATH'); + if (is_string($env_path) && $env_path !== '') { + foreach (explode(':', $env_path) as $part) { + if ($part !== '') { + $dirs[] = $part; + } + } + } + $dirs = array_values(array_unique($dirs)); + foreach ($names as $name) { + foreach ($dirs as $dir) { + $candidate = rtrim($dir, '/') . '/' . $name; + if (is_file($candidate) && is_executable($candidate)) { + return $candidate; + } + } + } + return ''; +} + +function list_owned_databases_via_mysql(string $da_user): array +{ + $mysql_conf = read_mysql_conf_values('/usr/local/directadmin/conf/mysql.conf'); + $mysql_bin = resolve_bin_path(['mysql', 'mariadb']); + if ($mysql_bin === '') { + return []; + } + $args = [$mysql_bin, '-N', '-B', '-e', 'SHOW DATABASES']; + if ($mysql_conf['user'] !== '') { + $args[] = '-u'; + $args[] = $mysql_conf['user']; + } + if ($mysql_conf['passwd'] !== '') { + $args[] = '--password=' . $mysql_conf['passwd']; + } + if ($mysql_conf['host'] !== '') { + $args[] = '-h'; + $args[] = $mysql_conf['host']; + } + if ($mysql_conf['port'] !== '' && $mysql_conf['port'] !== '0') { + $args[] = '-P'; + $args[] = $mysql_conf['port']; + } + if ($mysql_conf['socket'] !== '') { + $args[] = '--socket=' . $mysql_conf['socket']; + } + $cmd = implode(' ', array_map('escapeshellarg', $args)); + $stdout = []; + $exit_code = 1; + @exec($cmd, $stdout, $exit_code); + if ($exit_code !== 0) { + return []; + } + $dbs = []; + foreach ($stdout as $line) { + $db = trim((string)$line); + if ($db === '') { + continue; + } + if ($db === $da_user || strpos($db, $da_user . '_') === 0) { + $dbs[] = $db; + } + } + sort($dbs, SORT_STRING); + return array_values(array_unique($dbs)); +} + +function is_valid_target_db_name(string $db_name, string $da_user): bool +{ + if (!preg_match('/^[A-Za-z0-9_]+$/', $db_name)) { + return false; + } + return strpos($db_name, $da_user . '_') === 0; +} + +function database_has_tables(string $db_name): ?bool +{ + $mysql_conf = read_mysql_conf_values('/usr/local/directadmin/conf/mysql.conf'); + $mysql_bin = resolve_bin_path(['mysql', 'mariadb']); + if ($mysql_bin === '') { + @error_log('db-manager: database_has_tables: mysql client not found'); + return null; + } + + $db_sql = str_replace(['\\', "'"], ['\\\\', "\\'"], $db_name); + $query = "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='{$db_sql}'"; + $args = [$mysql_bin, '-N', '-B', '-e', $query]; + if ($mysql_conf['user'] !== '') { + $args[] = '-u'; + $args[] = $mysql_conf['user']; + } + if ($mysql_conf['passwd'] !== '') { + $args[] = '--password=' . $mysql_conf['passwd']; + } + if ($mysql_conf['host'] !== '') { + $args[] = '-h'; + $args[] = $mysql_conf['host']; + } + if ($mysql_conf['port'] !== '' && $mysql_conf['port'] !== '0') { + $args[] = '-P'; + $args[] = $mysql_conf['port']; + } + if ($mysql_conf['socket'] !== '') { + $args[] = '--socket=' . $mysql_conf['socket']; + } + + $cmd = implode(' ', array_map('escapeshellarg', $args)); + $stdout = []; + $exit_code = 1; + @exec($cmd, $stdout, $exit_code); + if ($exit_code !== 0) { + @error_log('db-manager: database_has_tables: mysql query failed for db=' . $db_name . ' exit=' . $exit_code); + return null; + } + $count = isset($stdout[0]) ? (int)trim((string)$stdout[0]) : 0; + if ($count < 0) { + $count = 0; + } + return $count > 0; +} + +function render_restore_mode_hidden_fields(string $restore_mode, string $target_db, string $main_tab = ''): string +{ + $html = '' + . ''; + if ($main_tab === 'hitme' || $main_tab === 'user') { + $html .= ''; + } + return $html; +} + +function trigger_worker(string $plugin_dir): void +{ + $worker = $plugin_dir . '/scripts/worker.sh'; + if (!is_file($worker) || !is_executable($worker)) { + return; + } + $cmd = escapeshellarg($worker) . ' >/dev/null 2>&1 &'; + + if (function_exists('exec')) { + @exec($cmd); + return; + } + if (function_exists('shell_exec')) { + @shell_exec($cmd); + } +} + +function parse_non_negative_int($value, int $default): int +{ + $value = trim((string)$value); + if ($value === '' || !preg_match('/^\d+$/', $value)) { + return $default; + } + $num = (int)$value; + return $num >= 0 ? $num : $default; +} + +function format_wait_time_pl(int $seconds): string +{ + if ($seconds < 1) { + return '1 s'; + } + $seconds = (int)ceil($seconds); + $days = intdiv($seconds, 86400); + $seconds %= 86400; + $hours = intdiv($seconds, 3600); + $seconds %= 3600; + $minutes = intdiv($seconds, 60); + $seconds %= 60; + $parts = []; + if ($days > 0) { + $parts[] = $days . ' d'; + } + if ($hours > 0) { + $parts[] = $hours . ' h'; + } + if ($minutes > 0) { + $parts[] = $minutes . ' min'; + } + if ($seconds > 0 || empty($parts)) { + $parts[] = $seconds . ' s'; + } + return implode(' ', $parts); +} + +function get_operation_limit_windows(array $settings, string $op_type): array +{ + if ($op_type === 'backup') { + return [ + ['seconds' => 300, 'limit' => parse_non_negative_int($settings['BACKUP_LIMIT_5MIN'] ?? '10', 10), 'label' => '5 min'], + ['seconds' => 3600, 'limit' => parse_non_negative_int($settings['BACKUP_LIMIT_HOUR'] ?? '30', 30), 'label' => '1 godzina'], + ['seconds' => 86400, 'limit' => parse_non_negative_int($settings['BACKUP_LIMIT_DAY'] ?? '100', 100), 'label' => '24 godziny'], + ]; + } + return [ + ['seconds' => 300, 'limit' => parse_non_negative_int($settings['RESTORE_LIMIT_5MIN'] ?? '10', 10), 'label' => '5 min'], + ['seconds' => 3600, 'limit' => parse_non_negative_int($settings['RESTORE_LIMIT_HOUR'] ?? '30', 30), 'label' => '1 godzina'], + ['seconds' => 86400, 'limit' => parse_non_negative_int($settings['RESTORE_LIMIT_DAY'] ?? '100', 100), 'label' => '24 godziny'], + ]; +} + +function reserve_operation_limit( + string $plugin_data_dir, + string $da_user, + string $op_type, + int $count, + array $settings, + string &$error_message +): bool { + if ($count < 1) { + return true; + } + $limits_enabled = parse_bool((string)($settings['ENABLE_OPERATION_LIMITS'] ?? 'true'), true); + if (!$limits_enabled) { + return true; + } + $windows = get_operation_limit_windows($settings, $op_type); + $has_any_limit = false; + foreach ($windows as $window) { + if (($window['limit'] ?? 0) > 0) { + $has_any_limit = true; + break; + } + } + if (!$has_any_limit) { + return true; + } + + $rate_dir = $plugin_data_dir . '/rate_limits'; + if (!is_dir($rate_dir) && !@mkdir($rate_dir, 0700, true) && !is_dir($rate_dir)) { + return true; + } + + $rate_file = $rate_dir . '/' . preg_replace('/[^A-Za-z0-9_.-]/', '_', $da_user) . '.' . $op_type . '.log'; + $fh = @fopen($rate_file, 'c+'); + if ($fh === false) { + return true; + } + if (!@flock($fh, LOCK_EX)) { + @fclose($fh); + return true; + } + + $now = time(); + $max_window = 86400; + $timestamps = []; + rewind($fh); + while (($line = fgets($fh)) !== false) { + $line = trim($line); + if ($line === '' || !preg_match('/^\d+$/', $line)) { + continue; + } + $ts = (int)$line; + if ($ts >= ($now - $max_window)) { + $timestamps[] = $ts; + } + } + sort($timestamps, SORT_NUMERIC); + + $max_wait = 0; + $limit_label = ''; + foreach ($windows as $window) { + $limit = (int)$window['limit']; + $seconds = (int)$window['seconds']; + if ($limit <= 0) { + continue; + } + $recent = []; + foreach ($timestamps as $ts) { + if ($ts > ($now - $seconds)) { + $recent[] = $ts; + } + } + $current_count = count($recent); + if (($current_count + $count) > $limit) { + $needed = ($current_count + $count) - $limit; + $idx = $needed - 1; + $candidate_wait = $seconds; + if (isset($recent[$idx])) { + $candidate_wait = max(1, ($recent[$idx] + $seconds) - $now); + } + if ($candidate_wait > $max_wait) { + $max_wait = $candidate_wait; + $limit_label = (string)$window['label']; + } + } + } + + if ($max_wait > 0) { + $op_label = $op_type === 'backup' ? 'tworzenia backupów' : 'przywracania'; + $error_message = 'Przekroczono limit operacji ' . $op_label . ' (' . $limit_label . '). Możesz ponowić próbę za ' . format_wait_time_pl($max_wait) . '.'; + @flock($fh, LOCK_UN); + @fclose($fh); + return false; + } + + for ($i = 0; $i < $count; $i++) { + $timestamps[] = $now; + } + sort($timestamps, SORT_NUMERIC); + ftruncate($fh, 0); + rewind($fh); + foreach ($timestamps as $ts) { + fwrite($fh, (string)$ts . "\n"); + } + @fflush($fh); + @flock($fh, LOCK_UN); + @fclose($fh); + return true; +} + +function list_active_jobs_for_user(string $job_base, string $da_user): array +{ + $items = []; + $maps = [ + 'pending' => 'pending', + 'processing' => 'processing', + ]; + foreach ($maps as $subdir => $status) { + foreach (glob($job_base . '/' . $subdir . '/*.env') ?: [] as $path) { + if (!is_file($path)) { + continue; + } + $vars = load_job_vars($path); + if (($vars['DA_USER'] ?? '') !== $da_user) { + continue; + } + $items[] = [ + 'status' => $status, + 'job_type' => normalize_job_type($vars), + 'db_name' => trim((string)($vars['DB_NAME'] ?? '')), + 'start_ts' => (int)trim((string)($vars['START_TS'] ?? '0')), + ]; + } + } + usort($items, function ($a, $b) { + return ($a['start_ts'] ?? 0) <=> ($b['start_ts'] ?? 0); + }); + return $items; +} + +function db_lock_dir(string $plugin_dir): string +{ + return rtrim($plugin_dir, '/') . '/data/lock'; +} + +function db_lock_path(string $plugin_dir, string $db_name): string +{ + $safe = preg_replace('/[^A-Za-z0-9_.-]/', '_', trim($db_name)); + if ($safe === null || $safe === '') { + $safe = 'unknown_db'; + } + return db_lock_dir($plugin_dir) . '/' . $safe . '.lock'; +} + +function acquire_db_lock(string $plugin_dir, string $db_name, string $job_id, string $job_type, string &$error_message): string +{ + $error_message = ''; + $db_name = trim($db_name); + if ($db_name === '') { + $error_message = t('Nie wybrano poprawnej bazy danych.'); + return ''; + } + + $lock_dir = db_lock_dir($plugin_dir); + if (!is_dir($lock_dir) && !@mkdir($lock_dir, 0700, true) && !is_dir($lock_dir)) { + $error_message = t('Nie mozna zablokowac kolejki zadan. Sprobuj ponownie.'); + return ''; + } + + $lock_path = db_lock_path($plugin_dir, $db_name); + $fh = @fopen($lock_path, 'x'); + if ($fh === false) { + $existing_job_type = 'restore'; + if (is_readable($lock_path)) { + foreach (file($lock_path, FILE_IGNORE_NEW_LINES) ?: [] as $line) { + $line = trim((string)$line); + if (strpos($line, 'JOB_TYPE=') === 0) { + $existing_job_type = strtolower(trim(substr($line, 9))); + break; + } + } + } + $existing_job_type = $existing_job_type === 'backup' ? 'backup' : 'restore'; + $error_message = sprintf( + t('Dla bazy %s istnieje juz aktywne zadanie (%s, %s). Zaczekaj na jego zakonczenie i ponow probe.'), + $db_name, + active_job_type_label($existing_job_type), + active_job_status_label('pending') + ); + return ''; + } + + $job_type = strtolower(trim($job_type)) === 'backup' ? 'backup' : 'restore'; + $content = "DB_NAME={$db_name}\nJOB_ID={$job_id}\nJOB_TYPE={$job_type}\nCREATED_TS=" . (string)time() . "\n"; + @fwrite($fh, $content); + @fflush($fh); + @fclose($fh); + @chmod($lock_path, 0600); + return $lock_path; +} + +function release_db_lock_file(string $path): void +{ + if ($path !== '' && is_file($path)) { + @unlink($path); + } +} + +function release_db_lock_for_job_path(string $job_path, string $plugin_dir): void +{ + if (!is_file($job_path)) { + return; + } + $vars = load_job_vars($job_path); + $db_name = trim((string)($vars['DB_NAME'] ?? '')); + if ($db_name === '') { + return; + } + release_db_lock_file(db_lock_path($plugin_dir, $db_name)); +} + +function check_db_lock_present(string $plugin_dir, string $db_name, string &$error_message): bool +{ + $error_message = ''; + $db_name = trim($db_name); + if ($db_name === '') { + return true; + } + $lock_path = db_lock_path($plugin_dir, $db_name); + clearstatcache(true, $lock_path); + if (!is_file($lock_path)) { + return true; + } + + $existing_job_type = 'restore'; + if (is_readable($lock_path)) { + foreach (file($lock_path, FILE_IGNORE_NEW_LINES) ?: [] as $line) { + $line = trim((string)$line); + if (strpos($line, 'JOB_TYPE=') === 0) { + $existing_job_type = strtolower(trim(substr($line, 9))); + break; + } + } + } + $existing_job_type = $existing_job_type === 'backup' ? 'backup' : 'restore'; + $error_message = sprintf( + t('Dla bazy %s istnieje juz aktywne zadanie (%s, %s). Zaczekaj na jego zakonczenie i ponow probe.'), + $db_name, + active_job_type_label($existing_job_type), + active_job_status_label('pending') + ); + return false; +} + +function acquire_jobs_queue_lock(string $job_base, string &$error_message) +{ + $error_message = ''; + if (!is_dir($job_base) && !@mkdir($job_base, 0700, true) && !is_dir($job_base)) { + $error_message = t('Nie mozna zablokowac kolejki zadan. Sprobuj ponownie.'); + return null; + } + $lock_path = rtrim($job_base, '/') . '/.queue.lock'; + $fh = @fopen($lock_path, 'c+'); + if ($fh === false) { + $error_message = t('Nie mozna zablokowac kolejki zadan. Sprobuj ponownie.'); + return null; + } + @chmod($lock_path, 0600); + if (!@flock($fh, LOCK_EX)) { + @fclose($fh); + $error_message = t('Nie mozna zablokowac kolejki zadan. Sprobuj ponownie.'); + return null; + } + return $fh; +} + +function release_jobs_queue_lock($fh): void +{ + if (is_resource($fh)) { + @flock($fh, LOCK_UN); + @fclose($fh); + } +} + +function active_job_type_label(string $job_type): string +{ + return $job_type === 'backup' ? t('Tworzenie kopii zapasowej') : t('Przywracanie kopii zapasowej'); +} + +function active_job_status_label(string $status): string +{ + return $status === 'processing' ? t('jest w trakcie') : t('oczekuje w kolejce'); +} + +function check_database_collisions( + array $settings, + string $plugin_dir, + string $job_base, + string $da_user, + string &$error_message, + string $db_name = '' +): bool +{ + $collisions_enabled = parse_bool((string)($settings['CHECK_DATABASE_COLLISIONS'] ?? 'true'), true); + if (!$collisions_enabled) { + return true; + } + $db_name = trim($db_name); + if ($db_name === '') { + return true; + } + + if (!check_db_lock_present($plugin_dir, $db_name, $error_message)) { + return false; + } + + $active_jobs = list_active_jobs_for_user($job_base, $da_user); + foreach ($active_jobs as $job) { + $active_db_name = trim((string)($job['db_name'] ?? '')); + if ($active_db_name === '' || $active_db_name !== $db_name) { + continue; + } + $active_job_type = strtolower(trim((string)($job['job_type'] ?? ''))); + $active_job_type = $active_job_type === 'backup' ? 'backup' : 'restore'; + $job_type = active_job_type_label($active_job_type); + $status = active_job_status_label((string)($job['status'] ?? 'pending')); + $error_message = sprintf( + t('Dla bazy %s istnieje juz aktywne zadanie (%s, %s). Zaczekaj na jego zakonczenie i ponow probe.'), + $db_name, + $job_type, + $status + ); + return false; + } + + return true; +} + +function list_jobs(string $dir, string $status, string $pattern, string $log_dir, string $pid_dir, string $cancel_dir, string $da_user, array $pre_restore_dirs): array +{ + $items = []; + foreach (glob($dir . '/' . $pattern) ?: [] as $path) { + $vars = load_job_vars($path); + if (($vars['DA_USER'] ?? '') !== $da_user) { + continue; + } + $job_id = job_id_from_file($path); + $log_path = $log_dir . '/' . $job_id . '.log'; + $cancel_requested = is_file($cancel_dir . '/' . $job_id . '.flag'); + $pid = ''; + if ($status === 'processing') { + $pid_path = $pid_dir . '/' . $job_id . '.pid'; + if (is_readable($pid_path)) { + $pid = trim((string)file_get_contents($pid_path)); + } elseif (!empty($vars['PID'])) { + $pid = (string)$vars['PID']; + } + if ($pid !== '' && !preg_match('/^[0-9]+$/', $pid)) { + $pid = ''; + } + if ($pid !== '' && function_exists('posix_kill')) { + if (!@posix_kill((int)$pid, 0)) { + $pid = ''; + } + } + } + $progress_text = '-'; + $display_status = $status; + if ($status === 'processing' && is_readable($log_path)) { + $last = last_non_empty_line(read_log_tail($log_path, 2000)); + $display_status = display_status($status, $last); + $progress = extract_progress($last); + if ($cancel_requested) { + $display_status = 'canceled'; + $progress_text = 'Anulowane'; + } elseif ($progress !== null) { + $progress_text = $progress . '%'; + } elseif ($last !== '') { + $progress_text = $last; + } else { + $progress_text = 'W trakcie'; + } + } elseif ($status === 'done') { + $progress_text = '100%'; + } elseif ($status === 'failed') { + $progress_text = 'Blad'; + } elseif ($status === 'canceled') { + $progress_text = 'Anulowane'; + } + $pre_backup_path = trim((string)($vars['PRE_BACKUP_PATH'] ?? '')); + $pre_backup_exists = $pre_backup_path !== '' && is_file($pre_backup_path) && is_path_within_any_dir($pre_backup_path, $pre_restore_dirs); + $job_type = normalize_job_type($vars); + $backup_source = normalize_backup_source($vars); + $restore_mode = normalize_restore_mode($vars); + $items[] = [ + 'status' => $status, + 'display_status' => $display_status, + 'file' => basename($path), + 'time' => @filemtime($path) ?: 0, + 'job_id' => $job_id, + 'log_exists' => is_readable($log_path), + 'progress_text' => $progress_text, + 'cancel_requested' => $cancel_requested, + 'db_name' => $vars['DB_NAME'] ?? '', + 'date_dir' => $vars['DATE_DIR'] ?? '', + 'start_ts' => $vars['START_TS'] ?? '', + 'pid' => $pid, + 'pre_backup_path' => $pre_backup_path, + 'pre_backup_exists' => $pre_backup_exists, + 'backup_source' => $backup_source, + 'job_type' => $job_type, + 'restore_mode' => $restore_mode, + 'backup_kind_label' => backup_source_label($backup_source), + 'restore_mode_label' => restore_mode_label($restore_mode, $job_type), + ]; + } + return $items; +} + +function render_jobs_section(array $jobs, string $csrf_token, string $base_url, bool $enable_logs_overlay, bool $prevent_log_deletion): string +{ + if (empty($jobs)) { + return '

Brak zadan do wyswietlenia.

'; + } + ob_start(); + ?> + + + + + + + + + + + + + + + + + + + + + + + + + +
Data RozpoczeciaNazwa BazyData BackupuRodzaj przywracanej bazyTryb PracyStatusAkcje
+ Pokaż logi'; + } else { + $actions[] = 'Pokaż logi'; + } + } + ?> + + ' + . '' + . '' + . '' + . '' + . ''; + ?> + + + Zapisz log'; + ?> + + + ' + . '' + . '' + . '' + . '' + . ''; + $actions[] = '
' + . '' + . '' + . '' + . '' + . '
'; + ?> + + + ' + . '' + . '' + . '' + . '' + . ''; + ?> + + +
+ +
+
+ +
+

Log zadania

+
+ + + + + X + +
+
+
+

Podglad na zywo (odswiezanie co 3s)

+
+
Data rozpoczecia:
+
Baza danych:
+
Backup (data katalogu):
+
Rodzaj przywracanej bazy:
+
Tryb przywracania:
+
Koniecznosc sanityzacji:
+
+
Status:
+
Postep:
+
+
+
+
+ + + +
+
+
+ +
+
+ Brak baz danych uzytkownika do backupu.

'; + } + ob_start(); + ?> +
+ + + + + + + + + + + + + + + + + + + + + +
Nazwa BazyAkcja
+ +
+
+ +
+
+ ' . h($empty_state_message) . '

'; + } + + ob_start(); + ?> +
+
+

Kalendarz backupow uzytkownika

+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ +
+ + + + + + + + + + + + + + + '; + for ($col = 1; $col <= 7; $col++) { + if (($row === 0 && $col < $calendar_offset) || $day_num > $calendar_days) { + echo ''; + continue; + } + $day_key = date('Y-m-', $month_ts) . str_pad((string)$day_num, 2, '0', STR_PAD_LEFT); + $is_active = isset($days[$day_key]); + $is_selected = $day_key === $selected_day; + echo ''; + $day_num++; + } + echo ''; + if ($day_num > $calendar_days) { + break; + } + } + ?> + +
'; + if ($is_active) { + echo ''; + } else { + echo '' . h((string)$day_num) . ''; + } + echo '
+
+
+ +
+

Wybierz backup i baze

+ +

Wybierz aktywna date w kalendarzu.

+ + 1) { ?> +

Data rozpoczecia generowania backupow

+
+ + + + + +
+ + + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Baza danychRozmiarData i godzina backupuAkcje
required /> +
+ + + +
+
+
+ +

Brak baz dla wybranego backupu.

+ +

Wybierz godzine backupu, aby zobaczyc liste baz.

+ + +
+
+ +
+ + + + +
+
+ $day_info) { + if (substr($day_key, 0, 7) === $selected_month) { + $selected_day = $day_key; + break; + } + } + if ($selected_day === '' && !empty($days)) { + $selected_day = (string)array_key_first($days); + } +} + +$day_slots = ($selected_day !== '' && isset($days[$selected_day])) ? $days[$selected_day]['slots'] : []; +$slot_names = array_map(function ($slot) { + return $slot['dir']; +}, $day_slots); +if ($selected_slot === '' && count($slot_names) === 1) { + $selected_slot = $slot_names[0]; +} +if ($selected_slot !== '' && !in_array($selected_slot, $slot_names, true)) { + $selected_slot = ''; +} + +$databases = $selected_slot !== '' ? list_user_databases($hitme_dir, $selected_slot, $da_user, false) : []; + +$active_user_tab = (string)($params['user_tab'] ?? 'backup_create'); +if ($active_user_tab !== 'backup_create' && $active_user_tab !== 'restore_user') { + $active_user_tab = 'backup_create'; +} +$main_tab_raw = trim((string)($params['main_tab'] ?? '')); +$main_tab_from_request = $main_tab_raw === 'hitme' || ($enable_user_backups && $main_tab_raw === 'user'); +$main_tab = $main_tab_raw; +if (!$main_tab_from_request) { + $user_main_actions = ['user_tab', 'queue_user_backup', 'restore_user', 'download_user_backup', 'delete_user_backup']; + $main_tab = ($enable_user_backups && in_array((string)$action, $user_main_actions, true)) ? 'user' : 'hitme'; +} +if (!$enable_user_backups) { + $main_tab = 'hitme'; + $active_user_tab = 'restore_user'; +} +$owned_user_dbs = list_owned_databases_via_mysql($da_user); + +$u_selected_day = trim((string)($params['u_day'] ?? '')); +$u_selected_slot = trim((string)($params['u_slot'] ?? '')); +$u_selected_month = trim((string)($params['u_month'] ?? '')); +$u_selected_db = trim((string)($params['u_db'] ?? '')); + +$u_slot_index = collect_backup_slots_filtered($user_backup_runtime_dir, $da_user, $own_backup_user_restore); +$u_days = $u_slot_index['days']; +$user_restore_empty_message = 'Brak kopii zapasowych użytkownika dostępnych do przywrócenia - najpierw utwórz kopię zapasową na zakładce Wykonaj Backup Baz danych'; +$u_available_months = []; +foreach ($u_days as $day_info) { + $u_available_months[substr($day_info['day_key'], 0, 7)] = true; +} +$u_available_months = array_keys($u_available_months); +rsort($u_available_months, SORT_STRING); +if ($u_selected_month === '') { + $u_selected_month = !empty($u_available_months) ? $u_available_months[0] : date('Y-m'); +} +if (!in_array($u_selected_month, $u_available_months, true) && !empty($u_available_months)) { + $u_selected_month = $u_available_months[0]; +} +if ($u_selected_day === '' || !isset($u_days[$u_selected_day]) || substr($u_selected_day, 0, 7) !== $u_selected_month) { + $u_selected_day = ''; + foreach ($u_days as $day_key => $day_info) { + if (substr($day_key, 0, 7) === $u_selected_month) { + $u_selected_day = $day_key; + break; + } + } + if ($u_selected_day === '' && !empty($u_days)) { + $u_selected_day = (string)array_key_first($u_days); + } +} +$u_day_slots = ($u_selected_day !== '' && isset($u_days[$u_selected_day])) ? $u_days[$u_selected_day]['slots'] : []; +$u_slot_names = array_map(function ($slot) { + return $slot['dir']; +}, $u_day_slots); +if ($u_selected_slot === '' && count($u_slot_names) === 1) { + $u_selected_slot = $u_slot_names[0]; +} +if ($u_selected_slot !== '' && !in_array($u_selected_slot, $u_slot_names, true)) { + $u_selected_slot = ''; +} +$u_databases = $u_selected_slot !== '' ? list_user_databases($user_backup_runtime_dir, $u_selected_slot, $da_user, $own_backup_user_restore) : []; + +function require_csrf(string $token, string $data_dir, string $user, array &$errors): bool +{ + if (!check_csrf($token, $data_dir, $user)) { + $errors[] = 'Nieprawidlowy token CSRF.'; + return false; + } + return true; +} + +$confirm_overlay_html = ''; +if (!$enable_user_backups && in_array((string)$action, ['user_tab', 'queue_user_backup', 'restore_user', 'download_user_backup', 'delete_user_backup'], true)) { + $errors[] = 'Sekcja backupow uzytkownika jest wylaczona.'; + $action = ''; +} +if ($method === 'POST') { + $confirm_required_actions = ['restore', 'restore_user', 'restore_pre_backup', 'delete_pre_backup', 'delete_user_backup']; + if (!$prevent_log_deletion) { + $confirm_required_actions[] = 'delete_job'; + } + $confirm_flag = (string)($params['confirm_action'] ?? ''); + + if ($confirm_flag !== '1' && in_array($action, ['restore', 'restore_user', 'restore_pre_backup'], true)) { + $target_for_lock_check = $target_restore_db !== '' ? $target_restore_db : ($da_user . '_'); + $db_for_lock_check = ''; + if ($action === 'restore') { + $restore_source_db = trim((string)($params['db'] ?? $selected_db)); + $db_for_lock_check = $restore_mode === 'new_db' + ? $target_for_lock_check + : ($restore_source_db !== '' ? $restore_source_db : $target_for_lock_check); + } elseif ($action === 'restore_user') { + $restore_user_source_db = trim((string)($params['u_db'] ?? $u_selected_db)); + $db_for_lock_check = $restore_mode === 'new_db' + ? $target_for_lock_check + : ($restore_user_source_db !== '' ? $restore_user_source_db : $target_for_lock_check); + } elseif ($action === 'restore_pre_backup') { + $job_file = basename((string)($params['job'] ?? '')); + $job_id = job_id_from_file($job_file); + if ($job_id !== '' && job_belongs_to_user($job_id, $job_base, $da_user)) { + $job_file_path = find_job_file($job_base, $job_file, $job_id); + $vars = $job_file_path !== '' ? load_job_vars($job_file_path) : []; + $db_for_lock_check = trim((string)($vars['DB_NAME'] ?? '')); + } + } + if ($db_for_lock_check !== '') { + $lock_error = ''; + if (!check_database_collisions($settings, $plugin_dir, $job_base, $da_user, $lock_error, $db_for_lock_check)) { + $errors[] = $lock_error; + $action = ''; + } + } + } + + if (in_array($action, $confirm_required_actions, true) && $confirm_flag !== '1') { + $target_for_warning = $target_restore_db !== '' ? $target_restore_db : ($da_user . '_'); + $restore_source_db = trim((string)($params['db'] ?? $selected_db)); + $restore_user_source_db = trim((string)($params['u_db'] ?? $u_selected_db)); + $restore_slot_user = trim((string)($params['u_slot'] ?? $u_selected_slot)); + $restore_user_label = $restore_slot_user !== '' ? format_backup_label($restore_slot_user) : ''; + $restore_target_db = $restore_mode === 'new_db' + ? $target_for_warning + : ($restore_source_db !== '' ? $restore_source_db : $target_for_warning); + $restore_user_target_db = $restore_mode === 'new_db' + ? $target_for_warning + : ($restore_user_source_db !== '' ? $restore_user_source_db : $target_for_warning); + $confirm_messages = [ + 'restore' => build_restore_confirmation_message('hitme', $restore_source_db, $restore_target_db, $restore_mode), + 'restore_user' => build_restore_confirmation_message('user', $restore_user_source_db, $restore_user_target_db, $restore_mode, $restore_user_label), + 'restore_pre_backup' => 'Przywrócenie backupu sprzed przywrócenia może nadpisać obecne dane. Czy kontynuować?', + 'delete_pre_backup' => 'Usunięcie backupu sprzed przywrócenia jest nieodwracalne. Czy kontynuować?', + 'delete_job' => 'Usuniesz log i wpis zadania. Tej operacji nie można cofnąć. Czy kontynuować?', + 'delete_user_backup' => 'Usuniesz backup użytkownika. Tej operacji nie można cofnąć. Czy kontynuować?', + ]; + $confirm_overlay_html = render_warning_overlay( + 'Potwierdzenie operacji', + $confirm_messages[$action] ?? 'Czy kontynuować operację?', + $params, + $base_url + ); + $action = ''; + } +} + +if ($action === 'user_tab') { + header('Content-Type: text/html; charset=utf-8'); + $html = render_user_tabs( + $active_user_tab, + $owned_user_dbs, + $base_url, + $csrf_token, + $u_selected_month, + $u_selected_day, + $u_selected_slot, + $u_selected_db, + $u_available_months, + $u_days, + $u_day_slots, + $u_databases, + $lang_code, + $restore_mode, + $target_restore_db, + $main_tab, + $user_restore_empty_message + ); + echo translate_html_safe($html, $lang_map ?? []); + exit; +} + +if ($action === 'restore' && $method === 'POST') { + $token = (string)($params['csrf_token'] ?? ''); + require_csrf($token, $plugin_data_dir, $da_user, $errors); + + if ($selected_day === '' || !isset($days[$selected_day])) { + $errors[] = 'Nie wybrano poprawnej daty backupu.'; + } + if ($selected_slot === '' || !in_array($selected_slot, $slot_names, true)) { + $errors[] = 'Nie wybrano poprawnej godziny backupu.'; + } + $db_map = []; + foreach ($databases as $db) { + $db_map[$db['name']] = $db; + } + if ($selected_db === '' || !isset($db_map[$selected_db])) { + $errors[] = 'Nie wybrano poprawnej bazy danych.'; + } + $target_db_for_restore = $selected_db; + if ($restore_mode === 'new_db') { + if ($target_restore_db === '' || !is_valid_target_db_name($target_restore_db, $da_user)) { + $errors[] = 'Nazwa docelowej bazy danych musi zaczynać się od prefiksu ' . $da_user . '_ (DA_USER_).'; + } elseif ($target_restore_db === $selected_db) { + $errors[] = 'Docelowa baza danych musi byc inna niz zrodlowa baza z backupu.'; + } elseif (!in_array($target_restore_db, $owned_user_dbs, true)) { + $errors[] = 'Wskazana docelowa baza danych nie istnieje. Utworz ja recznie w DirectAdmin.'; + } else { + $has_tables = database_has_tables($target_restore_db); + if ($has_tables === null) { + $errors[] = 'Nie udalo sie sprawdzic, czy docelowa baza danych jest pusta.'; + } elseif ($has_tables === true) { + $errors[] = 'Docelowa baza danych nie jest pusta. Usun dane przed kontynuowaniem.'; + } + } + if (empty($errors)) { + $target_db_for_restore = $target_restore_db; + } + } + + $queue_lock = null; + if (empty($errors)) { + $lock_error = ''; + $queue_lock = acquire_jobs_queue_lock($job_base, $lock_error); + if ($queue_lock === null) { + $errors[] = $lock_error; + } + } + if (empty($errors)) { + $sim_error = ''; + if (!check_database_collisions($settings, $plugin_dir, $job_base, $da_user, $sim_error, $target_db_for_restore)) { + $errors[] = $sim_error; + } + } + + if (empty($errors)) { + $limit_error = ''; + if (!reserve_operation_limit($plugin_data_dir, $da_user, 'restore', 1, $settings, $limit_error)) { + $errors[] = $limit_error; + } + } + + if (empty($errors)) { + $job_dir = $job_base . '/pending'; + if (!is_dir($job_dir) && !@mkdir($job_dir, 0700, true) && !is_dir($job_dir)) { + $errors[] = 'Nie mozna utworzyc katalogu zadan.'; + } else { + $job_id = date('YmdHis') . '-' . bin2hex(random_bytes(4)); + $job_file = $job_dir . '/restore-' . $job_id . '.env'; + $dump_path = (string)($db_map[$selected_db]['file'] ?? ''); + if ($dump_path === '' || !is_file($dump_path)) { + $errors[] = 'Nie znaleziono pliku dump dla wybranej bazy.'; + } + } + $db_lock_path = ''; + if (empty($errors)) { + $db_lock_error = ''; + $db_lock_path = acquire_db_lock($plugin_dir, $target_db_for_restore, $job_id, 'restore', $db_lock_error); + if ($db_lock_path === '') { + $errors[] = $db_lock_error; + } + } + if (empty($errors)) { + $job_content = "DA_USER=" . escapeshellarg($da_user) . "\n"; + $job_content .= "START_TS=" . escapeshellarg((string)time()) . "\n"; + $job_content .= "DATE_DIR=" . escapeshellarg($selected_slot) . "\n"; + $job_content .= "DB_NAME=" . escapeshellarg($target_db_for_restore) . "\n"; + $job_content .= "SOURCE_DB_NAME=" . escapeshellarg($selected_db) . "\n"; + $job_content .= "DUMP_FILE=" . escapeshellarg($dump_path) . "\n"; + $job_content .= "DB_LOCK_FILE=" . escapeshellarg($db_lock_path) . "\n"; + $job_content .= "BACKUP_SOURCE=" . escapeshellarg('hosting') . "\n"; + $job_content .= "RESTORE_MODE=" . escapeshellarg($restore_mode) . "\n"; + + if (@file_put_contents($job_file, $job_content, LOCK_EX) === false) { + release_db_lock_file($db_lock_path); + $errors[] = 'Nie udalo sie zapisac pliku zadania.'; + } else { + @chmod($job_file, 0600); + trigger_worker($plugin_dir); + $message_status = 'Zadanie przywracania bazy dodane do kolejki. Otrzymasz powiadomienie po zakonczeniu.'; + } + } + } + release_jobs_queue_lock($queue_lock); +} + +if ($action === 'queue_user_backup' && $method === 'POST') { + $token = (string)($params['csrf_token'] ?? ''); + require_csrf($token, $plugin_data_dir, $da_user, $errors); + if ($restore_mode === 'new_db') { + $errors[] = 'W trybie przywracania do nowej bazy tworzenie backupow uzytkownika jest ukryte.'; + } + $selected_dbs = []; + $single_db = trim((string)($params['single_db'] ?? '')); + if ($single_db !== '') { + $selected_dbs[] = $single_db; + } else { + $raw = $params['dbs'] ?? []; + if (!is_array($raw)) { + $raw = [$raw]; + } + foreach ($raw as $db) { + $db = trim((string)$db); + if ($db !== '') { + $selected_dbs[] = $db; + } + } + $selected_dbs = array_values(array_unique($selected_dbs)); + if (!empty($params['bulk_backup']) && count($selected_dbs) < 2) { + $errors[] = 'Aby wykonac backup zbiorczy zaznacz co najmniej dwie bazy.'; + } + } + if (empty($selected_dbs)) { + $errors[] = 'Nie wybrano baz do backupu.'; + } + $allowed = array_fill_keys($owned_user_dbs, true); + foreach ($selected_dbs as $db) { + if (!isset($allowed[$db])) { + $errors[] = 'Nieprawidlowa baza danych: ' . $db; + break; + } + } + $queue_lock = null; + if (empty($errors)) { + $lock_error = ''; + $queue_lock = acquire_jobs_queue_lock($job_base, $lock_error); + if ($queue_lock === null) { + $errors[] = $lock_error; + } + } + if (empty($errors)) { + $sim_error = ''; + foreach ($selected_dbs as $db_name_to_backup) { + if (!check_database_collisions($settings, $plugin_dir, $job_base, $da_user, $sim_error, $db_name_to_backup)) { + $errors[] = $sim_error; + break; + } + } + } + if (empty($errors)) { + $limit_error = ''; + if (!reserve_operation_limit($plugin_data_dir, $da_user, 'backup', count($selected_dbs), $settings, $limit_error)) { + $errors[] = $limit_error; + } + } + if (empty($errors)) { + $job_dir = $job_base . '/pending'; + if (!is_dir($job_dir) && !@mkdir($job_dir, 0700, true) && !is_dir($job_dir)) { + $errors[] = 'Nie mozna utworzyc katalogu zadan.'; + } else { + $date_dir = date('d-m-Y_H_i'); + $target_dir = rtrim($user_backup_runtime_dir, '/') . '/' . $date_dir; + if (is_dir($target_dir)) { + do { + $date_dir = date('d-m-Y_H_i_s'); + $target_dir = rtrim($user_backup_runtime_dir, '/') . '/' . $date_dir; + if (!is_dir($target_dir)) { + break; + } + usleep(250000); + } while (true); + } + while (is_dir($target_dir)) { + usleep(250000); + $date_dir = date('d-m-Y_H_i_s'); + $target_dir = rtrim($user_backup_runtime_dir, '/') . '/' . $date_dir; + } + if (!is_dir($target_dir) && !@mkdir($target_dir, 0700, true) && !is_dir($target_dir)) { + $errors[] = 'Nie mozna utworzyc katalogu backupu uzytkownika.'; + } else { + $queued = 0; + foreach ($selected_dbs as $db) { + $job_id = date('YmdHis') . '-' . bin2hex(random_bytes(4)); + $job_file = $job_dir . '/backup-' . $job_id . '.env'; + $db_lock_error = ''; + $db_lock_path = acquire_db_lock($plugin_dir, $db, $job_id, 'backup', $db_lock_error); + if ($db_lock_path === '') { + $errors[] = $db_lock_error; + continue; + } + $job_content = "JOB_TYPE=" . escapeshellarg('backup') . "\n"; + $job_content .= "DA_USER=" . escapeshellarg($da_user) . "\n"; + $job_content .= "START_TS=" . escapeshellarg((string)time()) . "\n"; + $job_content .= "DATE_DIR=" . escapeshellarg($date_dir) . "\n"; + $job_content .= "DB_NAME=" . escapeshellarg($db) . "\n"; + $job_content .= "DB_LOCK_FILE=" . escapeshellarg($db_lock_path) . "\n"; + $job_content .= "BACKUP_SOURCE=" . escapeshellarg('user') . "\n"; + $job_content .= "RESTORE_MODE=" . escapeshellarg('backup') . "\n"; + if (@file_put_contents($job_file, $job_content, LOCK_EX) === false) { + release_db_lock_file($db_lock_path); + $errors[] = 'Nie udalo sie zapisac pliku zadania backupu dla bazy ' . $db . '.'; + continue; + } + @chmod($job_file, 0600); + $queued++; + } + if ($queued > 0) { + trigger_worker($plugin_dir); + $message_status = $queued === 1 + ? 'Dodano zadanie backupu bazy danych.' + : sprintf('Dodano zadania backupu %d baz danych.', $queued); + } + } + } + } + release_jobs_queue_lock($queue_lock); +} + +if ($action === 'download_hitme_backup' && $method === 'POST') { + $token = (string)($params['csrf_token'] ?? ''); + require_csrf($token, $plugin_data_dir, $da_user, $errors); + if (!$allow_download_hitme_sql_backup) { + $errors[] = 'Pobieranie backupow HITME.SQL jest wylaczone.'; + } + + $backup_file = basename((string)($params['backup_file'] ?? '')); + $download_slot = trim((string)($params['slot'] ?? '')); + if (empty($errors) && !preg_match('/^[A-Za-z0-9._-]+$/', $download_slot)) { + $errors[] = 'Nie wybrano poprawnej godziny backupu.'; + } + if (empty($errors) && !preg_match('/^[A-Za-z0-9._-]+$/', $backup_file)) { + $errors[] = 'Nieprawidlowa nazwa pliku backupu.'; + } + $slot_dir = ''; + $slot_real = false; + if (empty($errors)) { + $slot_dir = rtrim($hitme_dir, '/') . '/' . $download_slot; + $slot_real = realpath($slot_dir); + if ($slot_real === false || !is_dir($slot_real) || !is_path_within_dir($slot_real, $hitme_dir)) { + $errors[] = 'Nieprawidlowa sciezka backupu.'; + } + } + $backup_path = ''; + $source_db_name = ''; + if (empty($errors)) { + $source_db_name = db_name_from_dump_filename($backup_file); + if ($source_db_name === '') { + $errors[] = 'Nieprawidlowa nazwa pliku backupu.'; + } elseif ($source_db_name !== $da_user && strpos($source_db_name, $da_user . '_') !== 0) { + $errors[] = 'Nieprawidlowa sciezka backupu.'; + } + } + if (empty($errors)) { + $backup_path = (string)$slot_real . '/' . $backup_file; + if (!is_file($backup_path) || !is_path_within_dir($backup_path, $hitme_dir)) { + $errors[] = 'Nie znaleziono pliku dump dla wybranej bazy.'; + } + } + + if (empty($errors)) { + while (ob_get_level() > 0) { + @ob_end_clean(); + } + if (headers_sent($hs_file, $hs_line)) { + $errors[] = 'Nie mozna pobrac pliku backupu (naglowki zostaly juz wyslane).'; + @file_put_contents($plugin_log, date('Y-m-d H:i:s') . " download_hitme_backup headers_sent {$hs_file}:{$hs_line}\n", FILE_APPEND); + } else { + $download_name = basename($backup_path); + $content_type = 'application/octet-stream'; + if (preg_match('/\.sql$/i', $download_name)) { + $content_type = 'application/sql'; + } elseif (preg_match('/\.gz$/i', $download_name)) { + $content_type = 'application/gzip'; + } + header('Content-Type: ' . $content_type); + header('Content-Disposition: attachment; filename="' . $download_name . '"'); + header('X-Content-Type-Options: nosniff'); + header('Cache-Control: no-store, no-cache, must-revalidate'); + $size = @filesize($backup_path); + if (is_int($size) && $size > 0) { + header('Content-Length: ' . $size); + } + readfile($backup_path); + exit; + } + } + if (!empty($errors)) { + @file_put_contents( + $plugin_log, + date('Y-m-d H:i:s') . ' download_hitme_backup error: ' . implode(' | ', $errors) . "\n", + FILE_APPEND + ); + } +} + +if ($action === 'download_user_backup' && $method === 'POST') { + $token = (string)($params['csrf_token'] ?? ''); + require_csrf($token, $plugin_data_dir, $da_user, $errors); + + $backup_file = basename((string)($params['backup_file'] ?? '')); + $download_slot = trim((string)($params['u_slot'] ?? '')); + if (empty($errors) && !preg_match('/^[A-Za-z0-9._-]+$/', $download_slot)) { + $errors[] = 'Nie wybrano poprawnej godziny backupu uzytkownika.'; + } + if (empty($errors) && !preg_match('/^[A-Za-z0-9._-]+$/', $backup_file)) { + $errors[] = 'Nieprawidlowa nazwa pliku backupu uzytkownika.'; + } + $slot_dir = ''; + $slot_real = false; + if (empty($errors)) { + $slot_dir = rtrim($user_backup_runtime_dir, '/') . '/' . $download_slot; + $slot_real = realpath($slot_dir); + if ($slot_real === false || !is_dir($slot_real) || !is_path_within_dir($slot_real, $user_backup_runtime_dir)) { + $errors[] = 'Nieprawidlowa sciezka backupu uzytkownika.'; + } + } + $backup_path = ''; + $source_db_name = ''; + if (empty($errors)) { + $source_db_name = db_name_from_dump_filename($backup_file); + if ($source_db_name === '') { + $errors[] = 'Nieprawidlowa nazwa pliku backupu uzytkownika.'; + } elseif ($source_db_name !== $da_user && strpos($source_db_name, $da_user . '_') !== 0) { + $errors[] = 'Nieprawidlowa sciezka backupu uzytkownika.'; + } + } + if (empty($errors)) { + $backup_path = (string)$slot_real . '/' . $backup_file; + if (!is_file($backup_path) || !is_path_within_dir($backup_path, $user_backup_runtime_dir)) { + $errors[] = 'Nie znaleziono pliku dump dla wybranej bazy.'; + } + } + + if (empty($errors)) { + while (ob_get_level() > 0) { + @ob_end_clean(); + } + if (headers_sent($hs_file, $hs_line)) { + $errors[] = 'Nie mozna pobrac pliku backupu (naglowki zostaly juz wyslane).'; + @file_put_contents($plugin_log, date('Y-m-d H:i:s') . " download_user_backup headers_sent {$hs_file}:{$hs_line}\n", FILE_APPEND); + } else { + $download_name = basename($backup_path); + $content_type = 'application/octet-stream'; + if (preg_match('/\.sql$/i', $download_name)) { + $content_type = 'application/sql'; + } elseif (preg_match('/\.gz$/i', $download_name)) { + $content_type = 'application/gzip'; + } + header('Content-Type: ' . $content_type); + header('Content-Disposition: attachment; filename="' . $download_name . '"'); + header('X-Content-Type-Options: nosniff'); + header('Cache-Control: no-store, no-cache, must-revalidate'); + $size = @filesize($backup_path); + if (is_int($size) && $size > 0) { + header('Content-Length: ' . $size); + } + readfile($backup_path); + exit; + } + } + if (!empty($errors)) { + @file_put_contents( + $plugin_log, + date('Y-m-d H:i:s') . ' download_user_backup error: ' . implode(' | ', $errors) . "\n", + FILE_APPEND + ); + } +} + +if ($action === 'delete_user_backup' && $method === 'POST') { + $token = (string)($params['csrf_token'] ?? ''); + require_csrf($token, $plugin_data_dir, $da_user, $errors); + + $backup_file = basename((string)($params['backup_file'] ?? '')); + if ($u_selected_slot === '' || !in_array($u_selected_slot, $u_slot_names, true)) { + $errors[] = 'Nie wybrano poprawnej godziny backupu uzytkownika.'; + } + $entry = empty($errors) ? find_user_backup_entry($u_databases, $backup_file) : []; + $backup_path = (string)($entry['file'] ?? ''); + if (empty($errors) && ($backup_path === '' || !is_file($backup_path))) { + $errors[] = 'Nie znaleziono pliku dump dla wybranej bazy.'; + } + if (empty($errors) && !is_path_within_dir($backup_path, $user_backup_runtime_dir)) { + $errors[] = 'Nieprawidlowa sciezka backupu uzytkownika.'; + } + if (empty($errors) && !@unlink($backup_path)) { + $errors[] = 'Nie udalo sie usunac backupu uzytkownika.'; + } + if (empty($errors)) { + $message_status = 'Backup uzytkownika zostal usuniety.'; + + $u_slot_index = collect_backup_slots_filtered($user_backup_runtime_dir, $da_user, $own_backup_user_restore); + $u_days = $u_slot_index['days']; + $u_available_months = []; + foreach ($u_days as $day_info) { + $u_available_months[substr($day_info['day_key'], 0, 7)] = true; + } + $u_available_months = array_keys($u_available_months); + rsort($u_available_months, SORT_STRING); + if (empty($u_days)) { + $u_selected_month = date('Y-m'); + $u_selected_day = ''; + $u_selected_slot = ''; + $u_selected_db = ''; + $u_day_slots = []; + $u_slot_names = []; + $u_databases = []; + $user_restore_empty_message = 'Brak kopii zapasowych użytkownika dostępnych do przywrócenia - najpierw utwórz kopię zapasową na zakładce Wykonaj Backup Baz danych'; + } else { + // Po usunieciu backupu zawsze przechodzimy na najnowszy dostepny backup. + $u_selected_day = (string)array_key_first($u_days); + $u_selected_month = substr($u_selected_day, 0, 7); + $u_day_slots = $u_days[$u_selected_day]['slots'] ?? []; + $u_slot_names = array_map(function ($slot) { + return $slot['dir']; + }, $u_day_slots); + $u_selected_slot = !empty($u_slot_names) ? (string)$u_slot_names[0] : ''; + $u_databases = $u_selected_slot !== '' ? list_user_databases($user_backup_runtime_dir, $u_selected_slot, $da_user, $own_backup_user_restore) : []; + $u_selected_db = !empty($u_databases) ? (string)($u_databases[0]['name'] ?? '') : ''; + } + } +} + +if ($action === 'restore_user' && $method === 'POST') { + $token = (string)($params['csrf_token'] ?? ''); + require_csrf($token, $plugin_data_dir, $da_user, $errors); + if ($u_selected_day === '' || !isset($u_days[$u_selected_day])) { + $errors[] = 'Nie wybrano poprawnej daty backupu uzytkownika.'; + } + if ($u_selected_slot === '' || !in_array($u_selected_slot, $u_slot_names, true)) { + $errors[] = 'Nie wybrano poprawnej godziny backupu uzytkownika.'; + } + $u_db_map = []; + foreach ($u_databases as $db) { + $u_db_map[$db['name']] = $db; + } + if ($u_selected_db === '' || !isset($u_db_map[$u_selected_db])) { + $errors[] = 'Nie wybrano poprawnej bazy danych.'; + } + $target_db_for_restore = $u_selected_db; + if ($restore_mode === 'new_db') { + if ($target_restore_db === '' || !is_valid_target_db_name($target_restore_db, $da_user)) { + $errors[] = 'Nazwa docelowej bazy danych musi zaczynać się od prefiksu ' . $da_user . '_ (DA_USER_).'; + } elseif ($target_restore_db === $u_selected_db) { + $errors[] = 'Docelowa baza danych musi byc inna niz zrodlowa baza z backupu.'; + } elseif (!in_array($target_restore_db, $owned_user_dbs, true)) { + $errors[] = 'Wskazana docelowa baza danych nie istnieje. Utworz ja recznie w DirectAdmin.'; + } else { + $has_tables = database_has_tables($target_restore_db); + if ($has_tables === null) { + $errors[] = 'Nie udalo sie sprawdzic, czy docelowa baza danych jest pusta.'; + } elseif ($has_tables === true) { + $errors[] = 'Docelowa baza danych nie jest pusta. Usun dane przed kontynuowaniem.'; + } + } + if (empty($errors)) { + $target_db_for_restore = $target_restore_db; + } + } + $queue_lock = null; + if (empty($errors)) { + $lock_error = ''; + $queue_lock = acquire_jobs_queue_lock($job_base, $lock_error); + if ($queue_lock === null) { + $errors[] = $lock_error; + } + } + if (empty($errors)) { + $sim_error = ''; + if (!check_database_collisions($settings, $plugin_dir, $job_base, $da_user, $sim_error, $target_db_for_restore)) { + $errors[] = $sim_error; + } + } + if (empty($errors)) { + $limit_error = ''; + if (!reserve_operation_limit($plugin_data_dir, $da_user, 'restore', 1, $settings, $limit_error)) { + $errors[] = $limit_error; + } + } + if (empty($errors)) { + $job_dir = $job_base . '/pending'; + if (!is_dir($job_dir) && !@mkdir($job_dir, 0700, true) && !is_dir($job_dir)) { + $errors[] = 'Nie mozna utworzyc katalogu zadan.'; + } else { + $job_id = date('YmdHis') . '-' . bin2hex(random_bytes(4)); + $job_file = $job_dir . '/restore-' . $job_id . '.env'; + $dump_path = (string)($u_db_map[$u_selected_db]['file'] ?? ''); + if ($dump_path === '' || !is_file($dump_path)) { + $errors[] = 'Nie znaleziono pliku dump dla wybranej bazy.'; + } else { + $db_lock_error = ''; + $db_lock_path = acquire_db_lock($plugin_dir, $target_db_for_restore, $job_id, 'restore', $db_lock_error); + if ($db_lock_path === '') { + $errors[] = $db_lock_error; + } + } + if (empty($errors)) { + $job_content = "DA_USER=" . escapeshellarg($da_user) . "\n"; + $job_content .= "START_TS=" . escapeshellarg((string)time()) . "\n"; + $job_content .= "DATE_DIR=" . escapeshellarg($u_selected_slot) . "\n"; + $job_content .= "DB_NAME=" . escapeshellarg($target_db_for_restore) . "\n"; + $job_content .= "SOURCE_DB_NAME=" . escapeshellarg($u_selected_db) . "\n"; + $job_content .= "DUMP_FILE=" . escapeshellarg($dump_path) . "\n"; + $job_content .= "DB_LOCK_FILE=" . escapeshellarg($db_lock_path) . "\n"; + $job_content .= "BACKUP_SOURCE=" . escapeshellarg('user') . "\n"; + $job_content .= "RESTORE_MODE=" . escapeshellarg($restore_mode) . "\n"; + if (@file_put_contents($job_file, $job_content, LOCK_EX) === false) { + release_db_lock_file($db_lock_path); + $errors[] = 'Nie udalo sie zapisac pliku zadania.'; + } else { + @chmod($job_file, 0600); + trigger_worker($plugin_dir); + $message_status = 'Zadanie przywracania backupu uzytkownika dodane do kolejki.'; + } + } + } + } + release_jobs_queue_lock($queue_lock); +} + +if ($action === 'restore_pre_backup' && $method === 'POST') { + $token = (string)($params['csrf_token'] ?? ''); + require_csrf($token, $plugin_data_dir, $da_user, $errors); + $file = basename((string)($params['job'] ?? '')); + $job_id = job_id_from_file($file); + if ($job_id === '' || !job_belongs_to_user($job_id, $job_base, $da_user)) { + $errors[] = 'Nie znaleziono zadania.'; + } else { + $job_file_path = find_job_file($job_base, $file, $job_id); + $vars = $job_file_path !== '' ? load_job_vars($job_file_path) : []; + $db_name = trim((string)($vars['DB_NAME'] ?? '')); + $pre_backup_path = trim((string)($vars['PRE_BACKUP_PATH'] ?? '')); + if ($db_name === '') { + $errors[] = 'Brak nazwy bazy w zadaniu.'; + } elseif ($pre_backup_path === '' || !is_file($pre_backup_path)) { + $errors[] = 'Backup przed przywroceniem nie istnieje.'; + } elseif (!is_path_within_any_dir($pre_backup_path, [$pre_restore_dir, $pre_restore_user_dir])) { + $errors[] = 'Nieprawidlowa sciezka backupu przed przywroceniem.'; + } else { + $queue_lock = null; + $lock_error = ''; + $queue_lock = acquire_jobs_queue_lock($job_base, $lock_error); + if ($queue_lock === null) { + $errors[] = $lock_error; + } + if (empty($errors)) { + $sim_error = ''; + if (!check_database_collisions($settings, $plugin_dir, $job_base, $da_user, $sim_error, $db_name)) { + $errors[] = $sim_error; + } + } + if (empty($errors)) { + $limit_error = ''; + if (!reserve_operation_limit($plugin_data_dir, $da_user, 'restore', 1, $settings, $limit_error)) { + $errors[] = $limit_error; + } + } + if (empty($errors)) { + $job_dir = $job_base . '/pending'; + if (!is_dir($job_dir) && !@mkdir($job_dir, 0700, true) && !is_dir($job_dir)) { + $errors[] = 'Nie mozna utworzyc katalogu zadan.'; + } else { + $new_job_id = date('YmdHis') . '-' . bin2hex(random_bytes(4)); + $new_job_file = $job_dir . '/restore-' . $new_job_id . '.env'; + $db_lock_error = ''; + $db_lock_path = acquire_db_lock($plugin_dir, $db_name, $new_job_id, 'restore', $db_lock_error); + if ($db_lock_path === '') { + $errors[] = $db_lock_error; + } + } + if (empty($errors)) { + $job_content = "DA_USER=" . escapeshellarg($da_user) . "\n"; + $job_content .= "START_TS=" . escapeshellarg((string)time()) . "\n"; + $job_content .= "DATE_DIR=" . escapeshellarg(basename($pre_backup_path)) . "\n"; + $job_content .= "DB_NAME=" . escapeshellarg($db_name) . "\n"; + $job_content .= "DUMP_FILE=" . escapeshellarg($pre_backup_path) . "\n"; + $job_content .= "DB_LOCK_FILE=" . escapeshellarg($db_lock_path) . "\n"; + $source = is_path_within_dir($pre_backup_path, $pre_restore_user_dir) ? 'user' : 'hosting'; + $job_content .= "BACKUP_SOURCE=" . escapeshellarg($source) . "\n"; + $job_content .= "RESTORE_MODE=" . escapeshellarg('overwrite') . "\n"; + if (@file_put_contents($new_job_file, $job_content, LOCK_EX) === false) { + release_db_lock_file($db_lock_path); + $errors[] = 'Nie udalo sie zapisac pliku zadania.'; + } else { + @chmod($new_job_file, 0600); + trigger_worker($plugin_dir); + $message_status = 'Dodano zadanie przywracania backupu przed przywroceniem.'; + } + } + } + release_jobs_queue_lock($queue_lock); + } + } +} + +if ($action === 'delete_pre_backup' && $method === 'POST') { + $token = (string)($params['csrf_token'] ?? ''); + require_csrf($token, $plugin_data_dir, $da_user, $errors); + $file = basename((string)($params['job'] ?? '')); + $job_id = job_id_from_file($file); + if ($job_id === '' || !job_belongs_to_user($job_id, $job_base, $da_user)) { + $errors[] = 'Nie znaleziono zadania.'; + } else { + $job_file_path = find_job_file($job_base, $file, $job_id); + $vars = $job_file_path !== '' ? load_job_vars($job_file_path) : []; + $pre_backup_path = trim((string)($vars['PRE_BACKUP_PATH'] ?? '')); + if ($pre_backup_path === '' || !is_file($pre_backup_path)) { + $errors[] = 'Backup przed przywroceniem nie istnieje.'; + } elseif (!is_path_within_any_dir($pre_backup_path, [$pre_restore_dir, $pre_restore_user_dir])) { + $errors[] = 'Nieprawidlowa sciezka backupu przed przywroceniem.'; + } elseif (!@unlink($pre_backup_path)) { + $errors[] = 'Nie udalo sie usunac backupu przed przywroceniem.'; + } else { + $message_status = 'Backup przed przywroceniem zostal usuniety.'; + } + } +} + +if ($action === 'cancel_job' && $method === 'POST') { + $token = (string)($params['csrf_token'] ?? ''); + require_csrf($token, $plugin_data_dir, $da_user, $errors); + $file = basename((string)($params['job'] ?? '')); + $job_id = job_id_from_file($file); + if ($job_id === '' || !job_belongs_to_user($job_id, $job_base, $da_user)) { + $errors[] = 'Nieprawidlowe zadanie.'; + } elseif (!empty($errors)) { + } else { + $pending_file = $job_base . '/pending/' . $file; + $processing_file = $job_base . '/processing/' . $file; + $cancel_file = $cancel_dir . '/' . $job_id . '.flag'; + if (is_file($pending_file)) { + release_db_lock_for_job_path($pending_file, $plugin_dir); + $target = $job_base . '/done/' . $job_id . '.cancel'; + if (@rename($pending_file, $target)) { + $message_status = 'Zadanie zostalo anulowane.'; + } else { + @unlink($pending_file); + $message_status = 'Zadanie zostalo anulowane.'; + } + } elseif (!is_file($processing_file)) { + $errors[] = 'Zadanie nie jest w trakcie.'; + } else { + $pid = ''; + $pid_path = $pid_dir . '/' . $job_id . '.pid'; + if (is_readable($pid_path)) { + $pid = trim((string)file_get_contents($pid_path)); + } else { + $vars = load_job_vars($processing_file); + if (!empty($vars['PID'])) { + $pid = (string)$vars['PID']; + } + } + if ($pid === '' || !preg_match('/^[0-9]+$/', $pid)) { + $errors[] = 'Nie znaleziono procesu do anulowania.'; + } elseif (!function_exists('posix_kill')) { + $errors[] = 'Brak obsugi posix_kill na serwerze.'; + } else { + $ok = @posix_kill((int)$pid, SIGTERM); + usleep(300000); + if (@posix_kill((int)$pid, 0)) { + @posix_kill((int)$pid, SIGKILL); + usleep(300000); + } + if ($ok && !@posix_kill((int)$pid, 0)) { + @file_put_contents($cancel_file, "1"); + $message_status = 'Zadanie zostalo anulowane.'; + } else { + $errors[] = 'Nie udalo sie anulowac zadania.'; + } + } + } + } +} + +if ($action === 'delete_selected' && $method === 'POST') { + $token = (string)($params['csrf_token'] ?? ''); + require_csrf($token, $plugin_data_dir, $da_user, $errors); + if ($prevent_log_deletion) { + $errors[] = 'Usuwanie logow i wpisow zadan jest zablokowane przez konfiguracje.'; + } else { + $to_delete = $params['jobs'] ?? []; + if (!is_array($to_delete)) { + $to_delete = [$to_delete]; + } + $deleted = 0; + foreach ($to_delete as $file) { + $file = basename((string)$file); + $job_id = job_id_from_file($file); + if ($job_id === '' || !job_belongs_to_user($job_id, $job_base, $da_user)) { + continue; + } + foreach (['pending', 'processing', 'done'] as $dir) { + foreach (glob($job_base . '/' . $dir . '/' . $job_id . '*') ?: [] as $path) { + if (is_file($path)) { + if ($dir !== 'processing') { + release_db_lock_for_job_path($path, $plugin_dir); + } + @unlink($path); + } + } + } + @unlink($log_dir . '/' . $job_id . '.log'); + @unlink($pid_dir . '/' . $job_id . '.pid'); + @unlink($cancel_dir . '/' . $job_id . '.flag'); + $deleted++; + } + $message_status = $deleted > 0 ? 'Wybrane zadania zostaly usuniete.' : 'Nie wybrano zadnych zadan.'; + } +} + +if ($action === 'delete_job' && $method === 'POST') { + $token = (string)($params['csrf_token'] ?? ''); + require_csrf($token, $plugin_data_dir, $da_user, $errors); + if ($prevent_log_deletion) { + $errors[] = 'Usuwanie logow i wpisow zadan jest zablokowane przez konfiguracje.'; + } else { + $file = basename((string)($params['job'] ?? '')); + $job_id = job_id_from_file($file); + if ($job_id === '' || !job_belongs_to_user($job_id, $job_base, $da_user)) { + $errors[] = 'Nie znaleziono zadania.'; + } else { + foreach (['pending', 'processing', 'done'] as $dir) { + foreach (glob($job_base . '/' . $dir . '/' . $job_id . '*') ?: [] as $path) { + if (is_file($path)) { + if ($dir !== 'processing') { + release_db_lock_for_job_path($path, $plugin_dir); + } + @unlink($path); + } + } + } + @unlink($log_dir . '/' . $job_id . '.log'); + @unlink($pid_dir . '/' . $job_id . '.pid'); + @unlink($cancel_dir . '/' . $job_id . '.flag'); + $message_status = 'Zadanie zostalo usuniete.'; + } + } +} + +if ($action === 'download_log' && $method === 'POST') { + $token = (string)($params['csrf_token'] ?? ''); + if (!check_csrf($token, $plugin_data_dir, $da_user)) { + header('HTTP/1.1 403 Forbidden'); + exit; + } + $file = basename((string)($params['job'] ?? '')); + $job_id = job_id_from_file($file); + if ($job_id === '' || !job_belongs_to_user($job_id, $job_base, $da_user)) { + header('HTTP/1.1 404 Not Found'); + exit; + } + $job_file_path = find_job_file($job_base, $job_id . '.env', $job_id); + $log_path = $log_dir . '/' . $job_id . '.log'; + $content = build_log_download_content($job_id, $job_file_path, $log_path, $job_base); + if (!empty($lang_map)) { + $content = translate_text($content, $lang_map); + } + $raw = trim((string)($params['raw'] ?? '')) === '1'; + if ($raw) { + log_text_output($content); + } + header('Content-Type: text/plain; charset=utf-8'); + header('Content-Disposition: attachment; filename="' . $job_id . '.log"'); + echo $content; + exit; +} + +$view_log_job = ''; +$log_meta_start = ''; +$log_meta_db = ''; +$log_meta_date = ''; +$log_meta_backup_kind = ''; +$log_meta_restore_mode = ''; +$log_meta_sanitization = 'Nie'; +$log_status_value = ''; +$log_message_view = ''; +$log_progress = null; +$log_last_line = '-'; +$view_log_status = ''; + +if ($action === 'view_log') { + $file = basename((string)($params['job'] ?? '')); + $job_id = job_id_from_file($file); + if ($job_id === '' || !job_belongs_to_user($job_id, $job_base, $da_user)) { + $errors[] = 'Nie znaleziono logu.'; + } else { + $job_file_path = find_job_file($job_base, $job_id . '.env', $job_id); + $vars = load_job_vars($job_file_path); + $log_meta_start = format_datetime($vars['START_TS'] ?? '', time()); + $log_meta_db = $vars['DB_NAME'] ?? '-'; + $log_meta_date = format_backup_label((string)($vars['DATE_DIR'] ?? '-')); + $log_meta_backup_kind = backup_source_label(normalize_backup_source($vars)); + $log_meta_restore_mode = restore_mode_label(normalize_restore_mode($vars), normalize_job_type($vars)); + $log_meta_sanitization = sanitize_required_label($vars); + $status = get_job_status($job_id, $job_base); + $log_path = $log_dir . '/' . $job_id . '.log'; + $log_message_view = normalize_log(read_log_tail($log_path, 2000)); + $log_last_line = last_non_empty_line($log_message_view); + $display = display_status($status, $log_last_line); + $log_status_value = status_label($display); + $log_progress = extract_progress($log_last_line); + if (in_array($display, ['done', 'failed', 'canceled'], true)) { + $log_progress = $display === 'done' ? 100 : 0; + $log_last_line = status_label($display); + } elseif ($log_last_line === '') { + $log_last_line = '-'; + } + $view_log_status = $status; + $view_log_job = $file; + } + + $view_log_overlay = $enable_logs_overlay && (($params['overlay'] ?? '') === '1'); + if ($view_log_overlay) { + header('Content-Type: text/html; charset=utf-8'); + if ($view_log_job === '') { + echo translate_html_safe('
Nie znaleziono logu.
', $lang_map ?? []); + } else { + $overlay_html = render_log_view_html( + $view_log_job, + $log_meta_start, + $log_meta_db, + $log_meta_date, + $log_meta_backup_kind, + $log_meta_restore_mode, + $log_meta_sanitization, + $log_status_value, + $log_progress, + $log_last_line, + $log_message_view, + $view_log_status, + true, + $base_url + ); + echo translate_html_safe($overlay_html, $lang_map ?? []); + } + exit; + } +} + +$jobs = array_merge( + list_jobs($job_base . '/pending', 'pending', '*.env', $log_dir, $pid_dir, $cancel_dir, $da_user, [$pre_restore_dir, $pre_restore_user_dir]), + list_jobs($job_base . '/processing', 'processing', '*.env', $log_dir, $pid_dir, $cancel_dir, $da_user, [$pre_restore_dir, $pre_restore_user_dir]), + list_jobs($job_base . '/done', 'done', '*.ok', $log_dir, $pid_dir, $cancel_dir, $da_user, [$pre_restore_dir, $pre_restore_user_dir]), + list_jobs($job_base . '/done', 'failed', '*.fail', $log_dir, $pid_dir, $cancel_dir, $da_user, [$pre_restore_dir, $pre_restore_user_dir]), + list_jobs($job_base . '/done', 'canceled', '*.cancel', $log_dir, $pid_dir, $cancel_dir, $da_user, [$pre_restore_dir, $pre_restore_user_dir]) +); +usort($jobs, function ($a, $b) { + return $b['time'] <=> $a['time']; +}); +if (count($jobs) > 50) { + $jobs = array_slice($jobs, 0, 50); +} + +if ($action === 'status') { + header('Content-Type: text/html; charset=utf-8'); + $status_html = render_jobs_section($jobs, $csrf_token, $base_url, $enable_logs_overlay, $prevent_log_deletion); + echo translate_html_safe($status_html, $lang_map ?? []); + exit; +} + +if ($action === 'log_stream') { + $file = basename((string)($params['job'] ?? '')); + if (!preg_match('/^[-A-Za-z0-9_.]+$/', $file)) { + header('HTTP/1.1 400 Bad Request'); + exit; + } + $job_id = job_id_from_file($file); + if ($job_id === '' || !job_belongs_to_user($job_id, $job_base, $da_user)) { + header('HTTP/1.1 403 Forbidden'); + exit; + } + $log_path = $log_dir . '/' . $job_id . '.log'; + $log_text = normalize_log(read_log_tail($log_path, 2000)); + $last_line = last_non_empty_line($log_text); + $progress = extract_progress($last_line); + $status = get_job_status($job_id, $job_base); + $display = display_status($status, $last_line); + if (in_array($display, ['done', 'failed', 'canceled'], true)) { + $progress = ($display === 'done') ? 100 : 0; + $progress_text = status_label($display); + } else { + $progress_text = $progress !== null ? ($progress . '%') : ($last_line !== '' ? $last_line : '-'); + } + header('Content-Type: application/json; charset=utf-8'); + echo json_encode([ + 'status' => $status, + 'display_status' => $display, + 'status_label' => status_label($status), + 'display_label' => status_label($display), + 'log' => $log_text, + 'last_line' => $last_line, + 'progress' => $progress, + 'progress_text' => $progress_text, + ], JSON_UNESCAPED_UNICODE); + exit; +} + +$active_day_keys = array_keys($days); +$month_ts = strtotime($selected_month . '-01'); +if ($month_ts === false) { + $month_ts = strtotime(date('Y-m-01')); +} +$calendar_days = (int)date('t', $month_ts); +$calendar_offset = (int)date('N', $month_ts); +$hosting_month_nav = month_nav_targets($available_months, $selected_month); +$hosting_weekdays = calendar_weekdays($lang_code); + +ob_start(); +?> + + + + Manager kopii zapasowych baz danych + + + + + +
+
+ + + +
+
Manager kopii zapasowych baz danych
+
+
+
+ + + + +
+
+
+

Tryb przywracania baz danych

+
+ + +
+
+ + + +
+
+ +
+ +
+ +

+ +
+ + +
+ +

+ +
+ + +
+ + + + +
+ +
+
+

Backupy udostepniane przez HITME.PL

+ +
+
+

Kalendarz backupow

+ +

Brak aktywnych dat backupu dla tego uzytkownika.

+ +
+
+ + + + + +
+
+
+ + + + + +
+
+ +
+ + + + + + + + + + + + + '; + for ($col = 1; $col <= 7; $col++) { + if (($row === 0 && $col < $calendar_offset) || $day_num > $calendar_days) { + echo ''; + continue; + } + $day_key = date('Y-m-', $month_ts) . str_pad((string)$day_num, 2, '0', STR_PAD_LEFT); + $is_active = isset($days[$day_key]); + $is_selected = $day_key === $selected_day; + echo ''; + $day_num++; + } + echo ''; + if ($day_num > $calendar_days) { + break; + } + } + ?> + +
'; + if ($is_active) { + echo ''; + } else { + echo '' . h((string)$day_num) . ''; + } + echo '
+
+ +
+ +
+

Wybierz backup i baze

+ +

Wybierz aktywna date w kalendarzu.

+ + 1) { ?> +

Data rozpoczecia generowania backupow

+
+ + + +
+ + + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Baza danychRozmiarData i godzina backupuAkcje
+ required /> + +
+ + + + +
+
+
+ +

Brak baz dla wybranego backupu.

+ +

Wybierz godzine backupu, aby zobaczyc liste baz.

+ + +
+
+ +
+ + +
+

Backupy Uzytkownika

+
+ +
+
+ +
+ +
+

Status zadan

+
+ +
+
+ +
+

Katalogi kopii zapasowych

+
+

Backup przed przywroceniem (HITME.PL):

+ +

Backupy uzytkownika:

+

Backup przed przywroceniem (backupy uzytkownika):

+ +
+
+
+ + + + +