4796 lines
183 KiB
HTML
Executable File
4796 lines
183 KiB
HTML
Executable File
#!/usr/local/bin/php -nc/usr/local/directadmin/plugins/db-manager/php.ini
|
|
<?php
|
|
function parse_sh_value(string $value): string
|
|
{
|
|
$value = trim($value);
|
|
if ($value === '') {
|
|
return '';
|
|
}
|
|
if ($value[0] === "'" && substr($value, -1) === "'") {
|
|
return substr($value, 1, -1);
|
|
}
|
|
if ($value[0] === '"' && substr($value, -1) === '"') {
|
|
return stripcslashes(substr($value, 1, -1));
|
|
}
|
|
$value = preg_replace('/\s+#.*$/', '', $value);
|
|
return trim($value);
|
|
}
|
|
|
|
function parse_bool(string $value, bool $default = false): bool
|
|
{
|
|
$normalized = strtolower(trim($value));
|
|
if ($normalized === '') {
|
|
return $default;
|
|
}
|
|
if (in_array($normalized, ['1', 'true', 'yes', 'y', 'on'], true)) {
|
|
return true;
|
|
}
|
|
if (in_array($normalized, ['0', 'false', 'no', 'n', 'off'], true)) {
|
|
return false;
|
|
}
|
|
return $default;
|
|
}
|
|
|
|
function normalize_lang_code(string $value): string
|
|
{
|
|
$value = strtolower(trim($value));
|
|
if ($value === '') {
|
|
return 'en';
|
|
}
|
|
if (strpos($value, 'pl') === 0 || strpos($value, 'polish') !== false || strpos($value, 'polski') !== false) {
|
|
return 'pl';
|
|
}
|
|
if (strpos($value, 'en') === 0 || strpos($value, 'english') !== false) {
|
|
return 'en';
|
|
}
|
|
return 'en';
|
|
}
|
|
|
|
function load_user_language(string $user): string
|
|
{
|
|
$path = '/usr/local/directadmin/data/users/' . $user . '/user.conf';
|
|
if (!is_readable($path)) {
|
|
return 'en';
|
|
}
|
|
$lines = file($path, FILE_IGNORE_NEW_LINES);
|
|
if ($lines === false) {
|
|
return 'en';
|
|
}
|
|
foreach ($lines as $line) {
|
|
$line = trim($line);
|
|
if ($line === '' || $line[0] === '#') {
|
|
continue;
|
|
}
|
|
if (preg_match('/^(?:lang|language|locale)=(.+)$/i', $line, $m)) {
|
|
return normalize_lang_code((string)$m[1]);
|
|
}
|
|
}
|
|
return 'en';
|
|
}
|
|
|
|
function load_conf_with_presence(string $path): array
|
|
{
|
|
$result = [
|
|
'values' => [],
|
|
'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('~(<script\\b[^>]*>.*?</script>)~is', $html, -1, PREG_SPLIT_DELIM_CAPTURE);
|
|
if ($parts === false) {
|
|
return translate_text($html, $map);
|
|
}
|
|
$out = '';
|
|
foreach ($parts as $part) {
|
|
if (preg_match('~^<script\\b~i', $part)) {
|
|
$out .= $part;
|
|
} else {
|
|
$out .= translate_text($part, $map);
|
|
}
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
function t(string $text): string
|
|
{
|
|
global $lang_map;
|
|
if (empty($lang_map)) {
|
|
return $text;
|
|
}
|
|
return $lang_map[$text] ?? $text;
|
|
}
|
|
|
|
function load_settings(string $path): array
|
|
{
|
|
$settings = [
|
|
'HITME_SQL_BACKUP_DIR' => '/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 .= '<input type="hidden" name="' . $safe_key . '[]" value="' . $safe_value . '" />' . "\n";
|
|
}
|
|
continue;
|
|
}
|
|
if (is_object($value)) {
|
|
continue;
|
|
}
|
|
$safe_value = htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
|
|
$out .= '<input type="hidden" name="' . $safe_key . '" value="' . $safe_value . '" />' . "\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();
|
|
?>
|
|
<div class="br-overlay">
|
|
<div class="br-modal br-modal-danger">
|
|
<h3><?php echo h($title); ?></h3>
|
|
<?php foreach ($lines as $line) { ?>
|
|
<?php $line = trim((string)$line); if ($line === '') { continue; } ?>
|
|
<p><?php if ($printed === 0) { ?><strong>Uwaga:</strong> <?php } ?><?php echo h($line); ?></p>
|
|
<?php $printed++; ?>
|
|
<?php } ?>
|
|
<form method="post" action="<?php echo h($base_url); ?>">
|
|
<?php echo $hidden; ?>
|
|
<input type="hidden" name="confirm_action" value="1" />
|
|
<div class="br-modal-actions">
|
|
<a class="br-btn br-btn-ghost" href="<?php echo h($base_url); ?>">Nie</a>
|
|
<button class="br-btn br-btn-danger" type="submit">Tak, kontynuuj</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
return trim((string)ob_get_clean());
|
|
}
|
|
|
|
function render_plugin_unavailable_page(string $inline_css, string $message): string
|
|
{
|
|
ob_start();
|
|
?>
|
|
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
<title>DB-Manager</title>
|
|
<?php if ($inline_css !== '') { ?>
|
|
<style><?php echo $inline_css; ?></style>
|
|
<?php } ?>
|
|
<style>
|
|
.br-disabled-shell {
|
|
max-width: 980px;
|
|
margin: 24px auto;
|
|
padding: 0 16px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="br-shell br-shell-full br-disabled-shell">
|
|
<h1>Manager kopii zapasowych baz danych</h1>
|
|
<div class="br-card">
|
|
<div class="br-alert br-alert-error"><?php echo h($message); ?></div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
<?php
|
|
return (string)ob_get_clean();
|
|
}
|
|
|
|
function db_name_from_dump_filename(string $filename): string
|
|
{
|
|
if (substr($filename, -7) === '.sql.gz') {
|
|
return substr($filename, 0, -7);
|
|
}
|
|
if (substr($filename, -3) === '.gz') {
|
|
return substr($filename, 0, -3);
|
|
}
|
|
if (substr($filename, -4) === '.sql') {
|
|
return substr($filename, 0, -4);
|
|
}
|
|
return '';
|
|
}
|
|
|
|
function collect_backup_slots(string $base_dir, string $da_user): array
|
|
{
|
|
$slots = [];
|
|
$days = [];
|
|
foreach (list_backup_dates($base_dir) as $dir_name) {
|
|
$dbs = list_user_databases($base_dir, $dir_name, $da_user);
|
|
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 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 = '<input type="hidden" name="restore_mode" value="' . h($restore_mode) . '" class="js-restore-mode-field" />'
|
|
. '<input type="hidden" name="target_db" value="' . h($target_db) . '" class="js-target-db-field" />';
|
|
if ($main_tab === 'hitme' || $main_tab === 'user') {
|
|
$html .= '<input type="hidden" name="main_tab" value="' . h($main_tab) . '" class="js-main-tab-field" />';
|
|
}
|
|
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 '<p class="br-muted">Brak zadan do wyswietlenia.</p>';
|
|
}
|
|
ob_start();
|
|
?>
|
|
<table class="br-table" border="1" cellpadding="6" cellspacing="0">
|
|
<thead>
|
|
<tr>
|
|
<th>Data Rozpoczecia</th>
|
|
<th>Nazwa Bazy</th>
|
|
<th>Data Backupu</th>
|
|
<th>Rodzaj przywracanej bazy</th>
|
|
<th>Tryb Pracy</th>
|
|
<th>Status</th>
|
|
<th>Akcje</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($jobs as $job) { ?>
|
|
<tr data-job-id="<?php echo h($job['job_id']); ?>" data-status="<?php echo h($job['display_status'] ?? $job['status']); ?>">
|
|
<td><?php echo h(format_datetime($job['start_ts'], (int)$job['time'])); ?></td>
|
|
<td><?php echo h($job['db_name']); ?></td>
|
|
<td><?php echo h(format_backup_label($job['date_dir'])); ?></td>
|
|
<td><?php echo h($job['backup_kind_label'] ?? '-'); ?></td>
|
|
<td><?php echo h($job['restore_mode_label'] ?? '-'); ?></td>
|
|
<td><?php echo h(status_label($job['display_status'] ?? $job['status'])); ?></td>
|
|
<td>
|
|
<?php
|
|
$actions = [];
|
|
if ($job['log_exists'] || $job['status'] === 'processing') {
|
|
if ($enable_logs_overlay) {
|
|
$safe_job = str_replace("'", "\\'", (string)$job['file']);
|
|
$actions[] = '<a href="' . h($base_url) . '?action=view_log&job=' . h((string)$job['file']) . '" onclick="if (typeof openLogOverlay === \'function\') { openLogOverlay(\'' . h($safe_job) . '\'); return false; }">Pokaż logi</a>';
|
|
} else {
|
|
$actions[] = '<a href="' . h($base_url) . '?action=view_log&job=' . h((string)$job['file']) . '">Pokaż logi</a>';
|
|
}
|
|
}
|
|
?>
|
|
<?php if (in_array($job['status'], ['processing', 'pending'], true)) { ?>
|
|
<?php
|
|
$actions[] = '<form method="post" action="' . h($base_url) . '" class="br-inline-form">'
|
|
. '<input type="hidden" name="action" value="cancel_job" />'
|
|
. '<input type="hidden" name="job" value="' . h((string)$job['file']) . '" />'
|
|
. '<input type="hidden" name="csrf_token" value="' . h($csrf_token) . '" />'
|
|
. '<button class="br-link-button" type="submit">Anuluj</button>'
|
|
. '</form>';
|
|
?>
|
|
<?php } ?>
|
|
<?php if (in_array($job['status'], ['done', 'failed', 'canceled'], true) && $job['log_exists']) { ?>
|
|
<?php
|
|
$actions[] = '<a href="#" onclick="downloadLog(\'' . h((string)$job['file']) . '\'); return false;">Zapisz log</a>';
|
|
?>
|
|
<?php } ?>
|
|
<?php if ($job['status'] === 'done' && $job['pre_backup_exists']) { ?>
|
|
<?php
|
|
$actions[] = '<form method="post" action="' . h($base_url) . '" class="br-inline-form">'
|
|
. '<input type="hidden" name="action" value="restore_pre_backup" />'
|
|
. '<input type="hidden" name="job" value="' . h((string)$job['file']) . '" />'
|
|
. '<input type="hidden" name="csrf_token" value="' . h($csrf_token) . '" />'
|
|
. '<button class="br-link-button br-link-success" type="submit">Przywróć poprzednią wersję bazy danych</button>'
|
|
. '</form>';
|
|
$actions[] = '<form method="post" action="' . h($base_url) . '" class="br-inline-form">'
|
|
. '<input type="hidden" name="action" value="delete_pre_backup" />'
|
|
. '<input type="hidden" name="job" value="' . h((string)$job['file']) . '" />'
|
|
. '<input type="hidden" name="csrf_token" value="' . h($csrf_token) . '" />'
|
|
. '<button class="br-link-button br-link-danger" type="submit">Usuń backup poprzedniej wersji bazy danych</button>'
|
|
. '</form>';
|
|
?>
|
|
<?php } ?>
|
|
<?php if (!$prevent_log_deletion && ($job['log_exists'] || $job['status'] === 'processing' || $job['status'] === 'pending' || $job['status'] === 'failed' || $job['status'] === 'canceled' || $job['status'] === 'done')) { ?>
|
|
<?php
|
|
$actions[] = '<form method="post" action="' . h($base_url) . '" class="br-inline-form">'
|
|
. '<input type="hidden" name="action" value="delete_job" />'
|
|
. '<input type="hidden" name="job" value="' . h((string)$job['file']) . '" />'
|
|
. '<input type="hidden" name="csrf_token" value="' . h($csrf_token) . '" />'
|
|
. '<button class="br-link-button br-link-danger" type="submit">Usun log</button>'
|
|
. '</form>';
|
|
?>
|
|
<?php } ?>
|
|
<?php
|
|
echo !empty($actions) ? implode(' | ', $actions) : '-';
|
|
?>
|
|
</td>
|
|
</tr>
|
|
<?php } ?>
|
|
</tbody>
|
|
</table>
|
|
<?php
|
|
return ob_get_clean();
|
|
}
|
|
|
|
function render_log_view_html(
|
|
string $view_log_job,
|
|
string $log_meta_start,
|
|
string $log_meta_db,
|
|
string $log_meta_date,
|
|
string $log_meta_backup_kind,
|
|
string $log_meta_restore_mode,
|
|
string $log_meta_sanitization,
|
|
string $log_status_value,
|
|
?int $log_progress,
|
|
string $log_last_line,
|
|
string $log_message,
|
|
string $status,
|
|
bool $overlay,
|
|
string $base_url
|
|
): string {
|
|
ob_start();
|
|
if ($overlay) {
|
|
?>
|
|
<div class="br-overlay">
|
|
<div class="br-modal br-modal-log">
|
|
<?php
|
|
}
|
|
?>
|
|
<div class="br-log-header">
|
|
<h3>Log zadania</h3>
|
|
<div class="br-log-header-actions">
|
|
<button class="br-icon-btn" type="button" onclick="copyLogToClipboard();" title="Kopiuj do schowka" aria-label="Kopiuj do schowka">Kopiuj</button>
|
|
<?php if ($overlay) { ?>
|
|
<button class="br-icon-btn" type="button" data-close-overlay="1" title="Zamknij" aria-label="Zamknij">X</button>
|
|
<?php } else { ?>
|
|
<a class="br-icon-btn" href="<?php echo h($base_url); ?>" title="Zamknij" aria-label="Zamknij">X</a>
|
|
<?php } ?>
|
|
</div>
|
|
</div>
|
|
<div class="br-modal-scroll">
|
|
<p class="br-muted">Podglad na zywo (odswiezanie co 3s)</p>
|
|
<div class="br-log-meta">
|
|
<div><strong>Data rozpoczecia:</strong> <?php echo h($log_meta_start); ?></div>
|
|
<div><strong>Baza danych:</strong> <?php echo h($log_meta_db); ?></div>
|
|
<div><strong>Backup (data katalogu):</strong> <?php echo h($log_meta_date); ?></div>
|
|
<div><strong>Rodzaj przywracanej bazy:</strong> <?php echo h($log_meta_backup_kind); ?></div>
|
|
<div><strong>Tryb przywracania:</strong> <?php echo h($log_meta_restore_mode); ?></div>
|
|
<div><strong>Koniecznosc sanityzacji:</strong> <?php if ($log_meta_sanitization === 'Tak') { ?><span class="br-text-danger"><?php echo h($log_meta_sanitization); ?></span><?php } else { ?><?php echo h($log_meta_sanitization); ?><?php } ?></div>
|
|
</div>
|
|
<div class="br-status-line">Status: <strong id="log-status"><?php echo h($log_status_value); ?></strong></div>
|
|
<div class="br-status-line">Postep: <span id="log-progress-text"><?php echo h($log_progress !== null ? ($log_progress . '%') : $log_last_line); ?></span></div>
|
|
<div class="br-progress">
|
|
<div id="log-progress-bar" class="br-progress-bar" style="width: <?php echo h($log_progress !== null ? (string)$log_progress : '0'); ?>%;"></div>
|
|
</div>
|
|
<div class="br-log-actions">
|
|
<?php if ($view_log_job !== '' && in_array($status, ['done', 'failed', 'canceled'], true)) { ?>
|
|
<button class="br-btn br-btn-ghost" type="button" onclick="downloadLog('<?php echo h($view_log_job); ?>');">Zapisz log</button>
|
|
<?php } ?>
|
|
</div>
|
|
<pre id="log-content"><?php echo h($log_message); ?></pre>
|
|
</div>
|
|
<?php
|
|
if ($overlay) {
|
|
?>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
}
|
|
return trim((string)ob_get_clean());
|
|
}
|
|
|
|
function render_user_backup_create_tab(array $owned_dbs, string $csrf_token, string $base_url, string $restore_mode, string $target_db, string $main_tab): string
|
|
{
|
|
if (empty($owned_dbs)) {
|
|
return '<p class="br-muted">Brak baz danych uzytkownika do backupu.</p>';
|
|
}
|
|
ob_start();
|
|
?>
|
|
<form method="post" action="<?php echo h($base_url); ?>" id="user-backup-form">
|
|
<input type="hidden" name="action" value="queue_user_backup" />
|
|
<input type="hidden" name="user_tab" value="backup_create" />
|
|
<input type="hidden" name="csrf_token" value="<?php echo h($csrf_token); ?>" />
|
|
<?php echo render_restore_mode_hidden_fields($restore_mode, $target_db, $main_tab); ?>
|
|
<table class="br-table" border="1" cellpadding="6" cellspacing="0">
|
|
<thead>
|
|
<tr>
|
|
<th></th>
|
|
<th>Nazwa Bazy</th>
|
|
<th>Akcja</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($owned_dbs as $db_name) { ?>
|
|
<tr>
|
|
<td><input type="checkbox" name="dbs[]" value="<?php echo h($db_name); ?>" class="js-user-db-check" /></td>
|
|
<td><?php echo h($db_name); ?></td>
|
|
<td>
|
|
<button class="br-btn br-btn-ghost" type="submit" name="single_db" value="<?php echo h($db_name); ?>">Wykonaj Backup</button>
|
|
</td>
|
|
</tr>
|
|
<?php } ?>
|
|
</tbody>
|
|
</table>
|
|
<div class="br-actions-row">
|
|
<button class="br-btn" type="submit" name="bulk_backup" value="1" id="bulk-user-backup-btn" style="display:none;">Wykonaj backupy wszystkich zaznaczonych baz</button>
|
|
</div>
|
|
</form>
|
|
<?php
|
|
return (string)ob_get_clean();
|
|
}
|
|
|
|
function render_user_restore_tab(
|
|
string $base_url,
|
|
string $csrf_token,
|
|
string $selected_month,
|
|
string $selected_day,
|
|
string $selected_slot,
|
|
string $selected_db,
|
|
array $available_months,
|
|
array $days,
|
|
array $day_slots,
|
|
array $databases,
|
|
string $lang_code,
|
|
string $restore_mode,
|
|
string $target_db,
|
|
string $main_tab,
|
|
string $empty_state_message
|
|
): string {
|
|
$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);
|
|
$nav = month_nav_targets($available_months, $selected_month);
|
|
$weekdays = calendar_weekdays($lang_code);
|
|
if (empty($active_day_keys)) {
|
|
return '<p class="br-muted">' . h($empty_state_message) . '</p>';
|
|
}
|
|
|
|
ob_start();
|
|
?>
|
|
<div class="br-restore-layout">
|
|
<div class="br-restore-calendar">
|
|
<h4>Kalendarz backupow uzytkownika</h4>
|
|
<div class="br-month-nav">
|
|
<form method="get" action="<?php echo h($base_url); ?>">
|
|
<input type="hidden" name="action" value="user_tab" />
|
|
<input type="hidden" name="user_tab" value="restore_user" />
|
|
<input type="hidden" name="u_day" value="" />
|
|
<input type="hidden" name="u_slot" value="" />
|
|
<input type="hidden" name="u_month" value="<?php echo h($nav['prev']); ?>" />
|
|
<?php echo render_restore_mode_hidden_fields($restore_mode, $target_db, $main_tab); ?>
|
|
<button class="br-month-btn" type="submit" <?php echo $nav['prev'] === '' ? 'disabled' : ''; ?> title="Poprzedni miesiac" aria-label="Poprzedni miesiac">◀</button>
|
|
</form>
|
|
<div class="br-month-label"><?php echo h(format_month_label($selected_month, $lang_code)); ?></div>
|
|
<form method="get" action="<?php echo h($base_url); ?>">
|
|
<input type="hidden" name="action" value="user_tab" />
|
|
<input type="hidden" name="user_tab" value="restore_user" />
|
|
<input type="hidden" name="u_day" value="" />
|
|
<input type="hidden" name="u_slot" value="" />
|
|
<input type="hidden" name="u_month" value="<?php echo h($nav['next']); ?>" />
|
|
<?php echo render_restore_mode_hidden_fields($restore_mode, $target_db, $main_tab); ?>
|
|
<button class="br-month-btn" type="submit" <?php echo $nav['next'] === '' ? 'disabled' : ''; ?> title="Nastepny miesiac" aria-label="Nastepny miesiac">▶</button>
|
|
</form>
|
|
</div>
|
|
|
|
<form method="get" action="<?php echo h($base_url); ?>" class="br-calendar-grid-wrap">
|
|
<input type="hidden" name="action" value="user_tab" />
|
|
<input type="hidden" name="user_tab" value="restore_user" />
|
|
<input type="hidden" name="u_month" value="<?php echo h($selected_month); ?>" />
|
|
<input type="hidden" name="u_slot" value="" />
|
|
<?php echo render_restore_mode_hidden_fields($restore_mode, $target_db, $main_tab); ?>
|
|
<table class="br-calendar-grid">
|
|
<thead>
|
|
<tr>
|
|
<?php foreach ($weekdays as $weekday) { ?>
|
|
<th><?php echo h($weekday); ?></th>
|
|
<?php } ?>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php
|
|
$day_num = 1;
|
|
for ($row = 0; $row < 6; $row++) {
|
|
echo '<tr>';
|
|
for ($col = 1; $col <= 7; $col++) {
|
|
if (($row === 0 && $col < $calendar_offset) || $day_num > $calendar_days) {
|
|
echo '<td class="br-cal-empty"></td>';
|
|
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 '<td>';
|
|
if ($is_active) {
|
|
echo '<button class="br-cal-day' . ($is_selected ? ' is-selected' : '') . '" type="submit" name="u_day" value="' . h($day_key) . '">' . h((string)$day_num) . '</button>';
|
|
} else {
|
|
echo '<span class="br-cal-day-disabled">' . h((string)$day_num) . '</span>';
|
|
}
|
|
echo '</td>';
|
|
$day_num++;
|
|
}
|
|
echo '</tr>';
|
|
if ($day_num > $calendar_days) {
|
|
break;
|
|
}
|
|
}
|
|
?>
|
|
</tbody>
|
|
</table>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="br-restore-picker">
|
|
<h4>Wybierz backup i baze</h4>
|
|
<?php if ($selected_day === '' || empty($day_slots)) { ?>
|
|
<p class="br-muted">Wybierz aktywna date w kalendarzu.</p>
|
|
<?php } else { ?>
|
|
<?php if (count($day_slots) > 1) { ?>
|
|
<p class="br-muted">Data rozpoczecia generowania backupow</p>
|
|
<form method="get" action="<?php echo h($base_url); ?>" class="br-time-picker">
|
|
<input type="hidden" name="action" value="user_tab" />
|
|
<input type="hidden" name="user_tab" value="restore_user" />
|
|
<input type="hidden" name="u_month" value="<?php echo h($selected_month); ?>" />
|
|
<input type="hidden" name="u_day" value="<?php echo h($selected_day); ?>" />
|
|
<?php echo render_restore_mode_hidden_fields($restore_mode, $target_db, $main_tab); ?>
|
|
<div class="br-time-list">
|
|
<?php foreach ($day_slots as $slot) { ?>
|
|
<label class="br-time-item">
|
|
<input type="radio" name="u_slot" value="<?php echo h($slot['dir']); ?>" <?php echo $slot['dir'] === $selected_slot ? 'checked' : ''; ?> />
|
|
<span><?php echo h($slot['time_label']); ?></span>
|
|
</label>
|
|
<?php } ?>
|
|
</div>
|
|
</form>
|
|
<?php } ?>
|
|
|
|
<?php if ($selected_slot !== '' && !empty($databases)) { ?>
|
|
<form method="post" action="<?php echo h($base_url); ?>" id="user-restore-form">
|
|
<input type="hidden" name="action" value="restore_user" />
|
|
<input type="hidden" name="user_tab" value="restore_user" />
|
|
<input type="hidden" name="u_month" value="<?php echo h($selected_month); ?>" />
|
|
<input type="hidden" name="u_day" value="<?php echo h($selected_day); ?>" />
|
|
<input type="hidden" name="u_slot" value="<?php echo h($selected_slot); ?>" />
|
|
<input type="hidden" name="csrf_token" value="<?php echo h($csrf_token); ?>" />
|
|
<?php echo render_restore_mode_hidden_fields($restore_mode, $target_db, $main_tab); ?>
|
|
<table class="br-table" border="1" cellpadding="6" cellspacing="0">
|
|
<thead>
|
|
<tr>
|
|
<th></th>
|
|
<th>Baza danych</th>
|
|
<th>Rozmiar</th>
|
|
<th>Data i godzina backupu</th>
|
|
<th>Akcje</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($databases as $db) { ?>
|
|
<?php $backup_file = basename((string)$db['file']); ?>
|
|
<?php $is_selected_row = $db['name'] === $selected_db; ?>
|
|
<tr>
|
|
<td><input type="radio" name="u_db" value="<?php echo h($db['name']); ?>" <?php echo $db['name'] === $selected_db ? 'checked' : ''; ?> required /></td>
|
|
<td><?php echo h($db['name']); ?></td>
|
|
<td><?php echo h(number_format($db['size'] / 1024 / 1024, 2) . ' MB'); ?></td>
|
|
<td><?php echo h(format_datetime((string)$db['mtime'], (int)$db['mtime'])); ?></td>
|
|
<td>
|
|
<div class="br-actions-row">
|
|
<button
|
|
class="br-btn br-btn-small js-user-row-restore<?php echo $is_selected_row ? '' : ' br-btn-fogged'; ?>"
|
|
type="submit"
|
|
<?php echo $is_selected_row ? '' : 'disabled'; ?>
|
|
>
|
|
Przywróć kopię zapasową
|
|
</button>
|
|
<button class="br-btn br-btn-ghost br-btn-small js-download-user-backup" type="submit" name="download_user_backup" value="<?php echo h($backup_file); ?>" formnovalidate>Pobierz kopię zapasową</button>
|
|
<button class="br-btn br-btn-danger br-btn-small" type="submit" name="delete_user_backup" value="<?php echo h($backup_file); ?>" formnovalidate>Usun</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<?php } ?>
|
|
</tbody>
|
|
</table>
|
|
</form>
|
|
<?php } elseif ($selected_slot !== '') { ?>
|
|
<p class="br-muted">Brak baz dla wybranego backupu.</p>
|
|
<?php } else { ?>
|
|
<p class="br-muted">Wybierz godzine backupu, aby zobaczyc liste baz.</p>
|
|
<?php } ?>
|
|
<?php } ?>
|
|
</div>
|
|
</div>
|
|
<?php
|
|
return (string)ob_get_clean();
|
|
}
|
|
|
|
function render_user_tabs(
|
|
string $active_tab,
|
|
array $owned_dbs,
|
|
string $base_url,
|
|
string $csrf_token,
|
|
string $u_selected_month,
|
|
string $u_selected_day,
|
|
string $u_selected_slot,
|
|
string $u_selected_db,
|
|
array $u_available_months,
|
|
array $u_days,
|
|
array $u_day_slots,
|
|
array $u_databases,
|
|
string $lang_code,
|
|
string $restore_mode,
|
|
string $target_db,
|
|
string $main_tab,
|
|
string $user_restore_empty_message
|
|
): string {
|
|
if ($restore_mode === 'new_db' && $active_tab === 'backup_create') {
|
|
$active_tab = 'restore_user';
|
|
}
|
|
$content = $active_tab === 'restore_user'
|
|
? render_user_restore_tab($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_db, $main_tab, $user_restore_empty_message)
|
|
: render_user_backup_create_tab($owned_dbs, $csrf_token, $base_url, $restore_mode, $target_db, $main_tab);
|
|
|
|
ob_start();
|
|
?>
|
|
<div class="br-tab-header">
|
|
<?php if ($restore_mode !== 'new_db') { ?>
|
|
<button class="br-btn br-btn-ghost br-user-tab <?php echo $active_tab === 'backup_create' ? 'is-active' : ''; ?>" type="button" data-user-tab="backup_create">Wykonaj Backup Baz danych</button>
|
|
<?php } ?>
|
|
<button class="br-btn br-btn-ghost br-user-tab <?php echo $active_tab === 'restore_user' ? 'is-active' : ''; ?>" type="button" data-user-tab="restore_user">Przywróć kopię zapasową</button>
|
|
</div>
|
|
<div id="user-tab-content"><?php echo $content; ?></div>
|
|
<?php
|
|
return (string)ob_get_clean();
|
|
}
|
|
|
|
$params = read_params();
|
|
$action = $params['action'] ?? '';
|
|
$method = $_SERVER['REQUEST_METHOD'] ?? '';
|
|
|
|
if ($method === 'POST') {
|
|
$download_hitme_clicked = trim((string)($params['download_hitme_backup'] ?? ''));
|
|
$download_clicked = trim((string)($params['download_user_backup'] ?? ''));
|
|
$delete_clicked = trim((string)($params['delete_user_backup'] ?? ''));
|
|
if ($download_hitme_clicked !== '') {
|
|
$params['backup_file'] = $download_hitme_clicked;
|
|
$action = 'download_hitme_backup';
|
|
} elseif ($download_clicked !== '') {
|
|
$params['backup_file'] = $download_clicked;
|
|
$action = 'download_user_backup';
|
|
} elseif ($delete_clicked !== '') {
|
|
$params['backup_file'] = $delete_clicked;
|
|
$action = 'delete_user_backup';
|
|
}
|
|
}
|
|
|
|
$plugin_dir = realpath(__DIR__ . '/..') ?: '/usr/local/directadmin/plugins/db-manager';
|
|
$plugin_data_dir = $plugin_dir . '/data';
|
|
if (!is_dir($plugin_data_dir)) {
|
|
@mkdir($plugin_data_dir, 0700, true);
|
|
}
|
|
$plugin_log = $plugin_data_dir . '/plugin.log';
|
|
ini_set('log_errors', '1');
|
|
ini_set('error_log', $plugin_log);
|
|
|
|
$request_line = date('Y-m-d H:i:s') . ' ' . ($_SERVER['REQUEST_METHOD'] ?? '') . ' ' . ($_SERVER['REQUEST_URI'] ?? '');
|
|
$request_line .= ' action=' . ($action !== '' ? $action : '-');
|
|
$request_line .= ' qs=' . ($_SERVER['QUERY_STRING'] ?? '');
|
|
@file_put_contents($plugin_log, $request_line . "\n", FILE_APPEND);
|
|
|
|
$da_user = getenv('USERNAME');
|
|
if ($da_user === false || $da_user === '') {
|
|
$da_user = getenv('LOGNAME') ?: getenv('USER') ?: 'unknown';
|
|
}
|
|
$lang_code = load_user_language($da_user);
|
|
$lang_map = load_lang_map($lang_code, $plugin_dir);
|
|
|
|
$skin = load_user_skin($da_user);
|
|
$css_file = $skin === 'evolution' ? 'evolution.css' : 'enhanced.css';
|
|
|
|
$settings_path = $plugin_dir . '/plugin-settings.conf';
|
|
$settings = load_settings($settings_path);
|
|
|
|
if ($action === 'css') {
|
|
$skin_req = strtolower((string)($params['skin'] ?? ''));
|
|
if ($skin_req !== 'evolution' && $skin_req !== 'enhanced') {
|
|
$skin_req = 'enhanced';
|
|
}
|
|
$css_path = $plugin_dir . '/user/' . $skin_req . '.css';
|
|
if (!is_readable($css_path)) {
|
|
header('HTTP/1.1 404 Not Found');
|
|
exit;
|
|
}
|
|
header('Content-Type: text/css; charset=utf-8');
|
|
readfile($css_path);
|
|
exit;
|
|
}
|
|
|
|
$inline_css = '';
|
|
$css_path = $plugin_dir . '/user/' . $css_file;
|
|
if (is_readable($css_path)) {
|
|
$inline_css = (string)file_get_contents($css_path);
|
|
}
|
|
|
|
$plugin_enabled = is_plugin_enabled_for_user($da_user, 'db-manager', $settings);
|
|
if (!$plugin_enabled) {
|
|
header('Content-Type: text/html; charset=utf-8');
|
|
$disabled_message = t('Opcja zarzadzania bazami danych jest niedostepna - skontaktuj sie z supportem aby uzyskac wiecej informacji');
|
|
echo translate_html_safe(render_plugin_unavailable_page($inline_css, $disabled_message), $lang_map ?? []);
|
|
exit;
|
|
}
|
|
|
|
$errors = [];
|
|
$warnings = [];
|
|
$message_status = '';
|
|
$log_message = '';
|
|
$enable_logs_overlay = parse_bool((string)($settings['ENABLE_LOGS_OVERLAY'] ?? 'true'), true);
|
|
|
|
$hitme_dir = $settings['HITME_SQL_BACKUP_DIR'] ?? '/home/admin/mysql_backups';
|
|
$user_backup_dir = $settings['USER_SQL_BACKUP_DIR'] ?? '/home/da_user/mysql_backups/user_backups';
|
|
$pre_restore_dir = $settings['PRE_RESTORE_BACKUP_DIR'] ?? '/home/da_user/mysql_backups/pre_restore_backups';
|
|
$pre_restore_user_dir = $settings['PRE_RESTORE_USER_BACKUP_DIR'] ?? '/home/da_user/mysql_backups/pre_restore_user_backups';
|
|
$enable_pre_restore = strtolower(trim((string)($settings['ENABLE_PRE_RESTORE_BACKUP'] ?? 'true')));
|
|
$enable_user_pre_restore = strtolower(trim((string)($settings['ENABLE_USER_PRE_RESTORE_BACKUP'] ?? 'true')));
|
|
$secure_backup_mode = strtolower(trim((string)($settings['SECURE_BACKUP'] ?? 'true')));
|
|
$secure_restore_mode = strtolower(trim((string)($settings['SECURE_RESTORE'] ?? 'true')));
|
|
$allow_download_hitme_sql_backup = parse_bool((string)($settings['ALLOW_DOWNLOAD_HITME_SQL_BACKUP'] ?? 'true'), true);
|
|
$enable_user_backups = parse_bool((string)($settings['ENABLE_USER_BACKUPS'] ?? 'true'), true);
|
|
$prevent_log_deletion = parse_bool((string)($settings['PREVENT_LOG_DELETION'] ?? 'true'), true);
|
|
$own_backup_user_restore = parse_bool((string)($settings['OWN_BACKUP_USER_RESTORE'] ?? 'true'), true);
|
|
|
|
$user_backup_dir = apply_da_user($user_backup_dir, $da_user);
|
|
$pre_restore_dir = apply_da_user($pre_restore_dir, $da_user);
|
|
$pre_restore_user_dir = apply_da_user($pre_restore_user_dir, $da_user);
|
|
$user_backup_runtime_dir = $user_backup_dir;
|
|
|
|
if ($hitme_dir === '' || !is_dir($hitme_dir)) {
|
|
$errors[] = 'Brak katalogu HITME_SQL_BACKUP_DIR.';
|
|
}
|
|
if ($enable_pre_restore !== 'true' && $enable_pre_restore !== 'false') {
|
|
$warnings[] = 'ENABLE_PRE_RESTORE_BACKUP ma niepoprawna wartosc (uzywam true/false).';
|
|
}
|
|
if ($enable_user_pre_restore !== 'true' && $enable_user_pre_restore !== 'false') {
|
|
$warnings[] = 'ENABLE_USER_PRE_RESTORE_BACKUP ma niepoprawna wartosc (uzywam true/false).';
|
|
}
|
|
if ($secure_backup_mode !== 'true' && $secure_backup_mode !== 'false') {
|
|
$warnings[] = 'SECURE_BACKUP ma niepoprawna wartosc (uzywam true/false).';
|
|
}
|
|
if ($secure_restore_mode !== 'true' && $secure_restore_mode !== 'false') {
|
|
$warnings[] = 'SECURE_RESTORE ma niepoprawna wartosc (uzywam true/false).';
|
|
}
|
|
|
|
$csrf_token = get_csrf_token($plugin_data_dir, $da_user);
|
|
|
|
$base_url = '/CMD_PLUGINS/db-manager';
|
|
$job_base = $plugin_dir . '/data/jobs';
|
|
$log_dir = $plugin_dir . '/data/logs';
|
|
$pid_dir = $plugin_dir . '/data/pids';
|
|
$cancel_dir = $plugin_dir . '/data/cancel';
|
|
|
|
$selected_day = trim((string)($params['day'] ?? ''));
|
|
$selected_slot = trim((string)($params['slot'] ?? ''));
|
|
$selected_month = trim((string)($params['month'] ?? ''));
|
|
$selected_db = trim((string)($params['db'] ?? ''));
|
|
$restore_mode = trim((string)($params['restore_mode'] ?? 'overwrite'));
|
|
$target_restore_db = trim((string)($params['target_db'] ?? ''));
|
|
if ($restore_mode !== 'overwrite' && $restore_mode !== 'new_db') {
|
|
$restore_mode = 'overwrite';
|
|
}
|
|
if ($target_restore_db === '') {
|
|
$target_restore_db = $da_user . '_';
|
|
}
|
|
|
|
$slot_index = collect_backup_slots_filtered($hitme_dir, $da_user, false);
|
|
$days = $slot_index['days'];
|
|
|
|
$available_months = [];
|
|
foreach ($days as $day_info) {
|
|
$available_months[substr($day_info['day_key'], 0, 7)] = true;
|
|
}
|
|
$available_months = array_keys($available_months);
|
|
rsort($available_months, SORT_STRING);
|
|
|
|
if ($selected_month === '') {
|
|
$selected_month = !empty($available_months) ? $available_months[0] : date('Y-m');
|
|
}
|
|
if (!in_array($selected_month, $available_months, true) && !empty($available_months)) {
|
|
$selected_month = $available_months[0];
|
|
}
|
|
|
|
if ($selected_day === '' || !isset($days[$selected_day]) || substr($selected_day, 0, 7) !== $selected_month) {
|
|
$selected_day = '';
|
|
foreach ($days as $day_key => $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('<div class="br-overlay"><div class="br-modal"><div class="br-alert br-alert-error">Nie znaleziono logu.</div><div class="br-modal-actions"><button class="br-btn br-btn-ghost" type="button" data-close-overlay="1">Zamknij</button></div></div></div>', $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();
|
|
?>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title>Manager kopii zapasowych baz danych</title>
|
|
<?php if ($inline_css !== '') { ?>
|
|
<style><?php echo $inline_css; ?></style>
|
|
<?php } ?>
|
|
</head>
|
|
<body>
|
|
<div class="br-container">
|
|
<div id="br-overlay-container"></div>
|
|
<?php if ($confirm_overlay_html !== '') { ?>
|
|
<?php echo $confirm_overlay_html; ?>
|
|
<?php } ?>
|
|
<div class="br-header">
|
|
<div class="br-title">Manager kopii zapasowych baz danych</div>
|
|
</div>
|
|
<div class="br-card">
|
|
<div class="br-main-tabs" id="main-backup-tabs">
|
|
<button
|
|
class="br-btn br-btn-ghost br-main-tab <?php echo $main_tab === 'hitme' ? 'is-active' : ''; ?>"
|
|
type="button"
|
|
data-main-tab="hitme"
|
|
>
|
|
Kopie zapasowe udostepniane przez HITME.PL
|
|
</button>
|
|
<?php if ($enable_user_backups) { ?>
|
|
<button
|
|
class="br-btn br-btn-ghost br-main-tab <?php echo $main_tab === 'user' ? 'is-active' : ''; ?>"
|
|
type="button"
|
|
data-main-tab="user"
|
|
>
|
|
Kopie zapasowe uzytkownika
|
|
</button>
|
|
<?php } ?>
|
|
</div>
|
|
</div>
|
|
<div class="br-card">
|
|
<h3>Tryb przywracania baz danych</h3>
|
|
<div class="br-actions-row" id="restore-mode-group">
|
|
<label><input type="radio" name="restore_mode_global" value="overwrite" <?php echo $restore_mode === 'overwrite' ? 'checked' : ''; ?> /> Nadpisanie istniejacej bazy danych (obecne dzialanie)</label>
|
|
<label><input type="radio" name="restore_mode_global" value="new_db" <?php echo $restore_mode === 'new_db' ? 'checked' : ''; ?> /> Przywrocenie wskazanej bazy do nowej bazy danych</label>
|
|
</div>
|
|
<div id="restore-mode-target-wrap" style="<?php echo $restore_mode === 'new_db' ? '' : 'display:none;'; ?>">
|
|
<label for="restore-mode-target-input"><strong>Nazwa docelowej bazy danych</strong></label>
|
|
<input type="text" id="restore-mode-target-input" value="<?php echo h($target_restore_db); ?>" placeholder="<?php echo h($da_user . '_mojabaza'); ?>" autocomplete="off" />
|
|
<div id="restore-mode-error" class="br-alert br-alert-error" style="display:none;"></div>
|
|
</div>
|
|
</div>
|
|
<?php if (!empty($errors) || !empty($warnings) || $message_status !== '' || ($action === 'view_log' && $view_log_job !== '')) { ?>
|
|
<div class="br-card">
|
|
<?php if (!empty($errors)) { ?>
|
|
<div class="br-alert br-alert-error">
|
|
<?php foreach ($errors as $err) { ?>
|
|
<p><?php echo h($err); ?></p>
|
|
<?php } ?>
|
|
</div>
|
|
<?php } ?>
|
|
<?php if (!empty($warnings)) { ?>
|
|
<div class="br-alert br-alert-warn">
|
|
<?php foreach ($warnings as $warn) { ?>
|
|
<p><?php echo h($warn); ?></p>
|
|
<?php } ?>
|
|
</div>
|
|
<?php } ?>
|
|
<?php if ($message_status !== '') { ?>
|
|
<div class="br-alert br-alert-success"><?php echo h($message_status); ?></div>
|
|
<?php } ?>
|
|
<?php if ($action === 'view_log' && $view_log_job !== '') { ?>
|
|
<?php echo 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,
|
|
false,
|
|
$base_url
|
|
); ?>
|
|
<?php } ?>
|
|
</div>
|
|
<?php } ?>
|
|
<div class="br-main-sections" id="br-main-sections">
|
|
<div class="br-card br-main-section <?php echo $main_tab !== 'hitme' ? 'is-hidden' : ''; ?>" id="br-main-section-hitme" data-main-section="hitme">
|
|
<h3>Backupy udostepniane przez HITME.PL</h3>
|
|
|
|
<div class="br-restore-layout">
|
|
<div class="br-restore-calendar">
|
|
<h4>Kalendarz backupow</h4>
|
|
<?php if (empty($active_day_keys)) { ?>
|
|
<p class="br-muted">Brak aktywnych dat backupu dla tego uzytkownika.</p>
|
|
<?php } else { ?>
|
|
<div class="br-month-nav">
|
|
<form method="get" action="<?php echo h($base_url); ?>">
|
|
<input type="hidden" name="month" value="<?php echo h($hosting_month_nav['prev']); ?>" />
|
|
<input type="hidden" name="day" value="" />
|
|
<input type="hidden" name="slot" value="" />
|
|
<?php echo render_restore_mode_hidden_fields($restore_mode, $target_restore_db, $main_tab); ?>
|
|
<button class="br-month-btn" type="submit" <?php echo $hosting_month_nav['prev'] === '' ? 'disabled' : ''; ?> title="Poprzedni miesiac" aria-label="Poprzedni miesiac">◀</button>
|
|
</form>
|
|
<div class="br-month-label"><?php echo h(format_month_label($selected_month, $lang_code)); ?></div>
|
|
<form method="get" action="<?php echo h($base_url); ?>">
|
|
<input type="hidden" name="month" value="<?php echo h($hosting_month_nav['next']); ?>" />
|
|
<input type="hidden" name="day" value="" />
|
|
<input type="hidden" name="slot" value="" />
|
|
<?php echo render_restore_mode_hidden_fields($restore_mode, $target_restore_db, $main_tab); ?>
|
|
<button class="br-month-btn" type="submit" <?php echo $hosting_month_nav['next'] === '' ? 'disabled' : ''; ?> title="Nastepny miesiac" aria-label="Nastepny miesiac">▶</button>
|
|
</form>
|
|
</div>
|
|
|
|
<form method="get" action="<?php echo h($base_url); ?>" class="br-calendar-grid-wrap">
|
|
<input type="hidden" name="month" value="<?php echo h($selected_month); ?>" />
|
|
<input type="hidden" name="slot" value="" />
|
|
<?php echo render_restore_mode_hidden_fields($restore_mode, $target_restore_db, $main_tab); ?>
|
|
<table class="br-calendar-grid">
|
|
<thead>
|
|
<tr>
|
|
<?php foreach ($hosting_weekdays as $weekday) { ?>
|
|
<th><?php echo h($weekday); ?></th>
|
|
<?php } ?>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php
|
|
$day_num = 1;
|
|
for ($row = 0; $row < 6; $row++) {
|
|
echo '<tr>';
|
|
for ($col = 1; $col <= 7; $col++) {
|
|
if (($row === 0 && $col < $calendar_offset) || $day_num > $calendar_days) {
|
|
echo '<td class="br-cal-empty"></td>';
|
|
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 '<td>';
|
|
if ($is_active) {
|
|
echo '<button class="br-cal-day' . ($is_selected ? ' is-selected' : '') . '" type="submit" name="day" value="' . h($day_key) . '">' . h((string)$day_num) . '</button>';
|
|
} else {
|
|
echo '<span class="br-cal-day-disabled">' . h((string)$day_num) . '</span>';
|
|
}
|
|
echo '</td>';
|
|
$day_num++;
|
|
}
|
|
echo '</tr>';
|
|
if ($day_num > $calendar_days) {
|
|
break;
|
|
}
|
|
}
|
|
?>
|
|
</tbody>
|
|
</table>
|
|
</form>
|
|
<?php } ?>
|
|
</div>
|
|
|
|
<div class="br-restore-picker">
|
|
<h4>Wybierz backup i baze</h4>
|
|
<?php if ($selected_day === '' || empty($day_slots)) { ?>
|
|
<p class="br-muted">Wybierz aktywna date w kalendarzu.</p>
|
|
<?php } else { ?>
|
|
<?php if (count($day_slots) > 1) { ?>
|
|
<p class="br-muted">Data rozpoczecia generowania backupow</p>
|
|
<form method="get" action="<?php echo h($base_url); ?>" class="br-time-picker">
|
|
<input type="hidden" name="month" value="<?php echo h($selected_month); ?>" />
|
|
<input type="hidden" name="day" value="<?php echo h($selected_day); ?>" />
|
|
<?php echo render_restore_mode_hidden_fields($restore_mode, $target_restore_db, $main_tab); ?>
|
|
<div class="br-time-list">
|
|
<?php foreach ($day_slots as $slot) { ?>
|
|
<label class="br-time-item">
|
|
<input type="radio" name="slot" value="<?php echo h($slot['dir']); ?>" <?php echo $slot['dir'] === $selected_slot ? 'checked' : ''; ?> onchange="this.form.submit();" />
|
|
<span><?php echo h($slot['time_label']); ?></span>
|
|
</label>
|
|
<?php } ?>
|
|
</div>
|
|
</form>
|
|
<?php } ?>
|
|
|
|
<?php if ($selected_slot !== '' && !empty($databases)) { ?>
|
|
<form method="post" action="<?php echo h($base_url); ?>" id="restore-form">
|
|
<input type="hidden" name="action" value="restore" />
|
|
<input type="hidden" name="month" value="<?php echo h($selected_month); ?>" />
|
|
<input type="hidden" name="day" value="<?php echo h($selected_day); ?>" />
|
|
<input type="hidden" name="slot" value="<?php echo h($selected_slot); ?>" />
|
|
<input type="hidden" name="csrf_token" value="<?php echo h($csrf_token); ?>" />
|
|
<?php echo render_restore_mode_hidden_fields($restore_mode, $target_restore_db, $main_tab); ?>
|
|
<table class="br-table" border="1" cellpadding="6" cellspacing="0">
|
|
<thead>
|
|
<tr>
|
|
<th></th>
|
|
<th>Baza danych</th>
|
|
<th>Rozmiar</th>
|
|
<th>Data i godzina backupu</th>
|
|
<th>Akcje</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($databases as $db) { ?>
|
|
<?php $is_selected_row = $db['name'] === $selected_db; ?>
|
|
<?php $backup_file = basename((string)$db['file']); ?>
|
|
<tr>
|
|
<td>
|
|
<input type="radio" name="db" value="<?php echo h($db['name']); ?>" <?php echo $db['name'] === $selected_db ? 'checked' : ''; ?> required />
|
|
</td>
|
|
<td><?php echo h($db['name']); ?></td>
|
|
<td><?php echo h(number_format($db['size'] / 1024 / 1024, 2) . ' MB'); ?></td>
|
|
<td><?php echo h(format_datetime((string)$db['mtime'], (int)$db['mtime'])); ?></td>
|
|
<td>
|
|
<div class="br-actions-row">
|
|
<button
|
|
class="br-btn br-btn-small js-hitme-row-restore<?php echo $is_selected_row ? '' : ' br-btn-fogged'; ?>"
|
|
type="submit"
|
|
<?php echo $is_selected_row ? '' : 'disabled'; ?>
|
|
>
|
|
Przywróć kopię zapasową
|
|
</button>
|
|
<?php if ($allow_download_hitme_sql_backup) { ?>
|
|
<button class="br-btn br-btn-ghost br-btn-small js-download-hitme-backup" type="submit" name="download_hitme_backup" value="<?php echo h($backup_file); ?>" formnovalidate>Pobierz kopię zapasową</button>
|
|
<?php } ?>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<?php } ?>
|
|
</tbody>
|
|
</table>
|
|
</form>
|
|
<?php } elseif ($selected_slot !== '') { ?>
|
|
<p class="br-muted">Brak baz dla wybranego backupu.</p>
|
|
<?php } else { ?>
|
|
<p class="br-muted">Wybierz godzine backupu, aby zobaczyc liste baz.</p>
|
|
<?php } ?>
|
|
<?php } ?>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<?php if ($enable_user_backups) { ?>
|
|
<div class="br-card br-main-section <?php echo $main_tab !== 'user' ? 'is-hidden' : ''; ?>" id="br-main-section-user" data-main-section="user">
|
|
<h3>Backupy Uzytkownika</h3>
|
|
<div id="user-tabs">
|
|
<?php echo 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
|
|
); ?>
|
|
</div>
|
|
</div>
|
|
<?php } ?>
|
|
</div>
|
|
|
|
<div class="br-card">
|
|
<h3>Status zadan</h3>
|
|
<div id="job-status">
|
|
<?php echo render_jobs_section($jobs, $csrf_token, $base_url, $enable_logs_overlay, $prevent_log_deletion); ?>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="br-card">
|
|
<h3>Katalogi kopii zapasowych</h3>
|
|
<div class="br-path-legend">
|
|
<p class="br-muted"><strong>Backup przed przywroceniem (HITME.PL):</strong> <?php echo h($pre_restore_dir); ?></p>
|
|
<?php if ($enable_user_backups) { ?>
|
|
<p class="br-muted"><strong>Backupy uzytkownika:</strong> <?php echo h($user_backup_runtime_dir); ?></p>
|
|
<p class="br-muted"><strong>Backup przed przywroceniem (backupy uzytkownika):</strong> <?php echo h($pre_restore_user_dir); ?></p>
|
|
<?php } ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
var baseUrl = <?php echo json_encode($base_url, JSON_UNESCAPED_SLASHES); ?>;
|
|
var csrfToken = <?php echo json_encode($csrf_token); ?>;
|
|
var logsOverlayEnabled = <?php echo $enable_logs_overlay ? 'true' : 'false'; ?>;
|
|
var logOverlayJob = '';
|
|
var logOverlayTimer = null;
|
|
var pageLogJob = <?php echo json_encode($view_log_job); ?>;
|
|
var pageLogTimer = null;
|
|
var activeMainTab = <?php echo json_encode($main_tab); ?>;
|
|
var mainTabAnimTimer = null;
|
|
var userBackupsEnabled = <?php echo $enable_user_backups ? 'true' : 'false'; ?>;
|
|
|
|
function widenLayoutForEvolution() {
|
|
var root = document.querySelector('.br-container');
|
|
if (!root) { return; }
|
|
root.style.maxWidth = 'none';
|
|
root.style.width = '100%';
|
|
root.style.margin = '0';
|
|
root.style.padding = '16px 20px';
|
|
|
|
var parent = root.parentElement;
|
|
var hops = 0;
|
|
while (parent && parent !== document.body && hops < 12) {
|
|
parent.style.maxWidth = 'none';
|
|
parent.style.width = '100%';
|
|
var cs = window.getComputedStyle(parent);
|
|
if (cs.marginLeft === 'auto') { parent.style.marginLeft = '0'; }
|
|
if (cs.marginRight === 'auto') { parent.style.marginRight = '0'; }
|
|
hops++;
|
|
parent = parent.parentElement;
|
|
}
|
|
}
|
|
|
|
function normalizeJobId(job) {
|
|
if (!job) { return ''; }
|
|
var id = String(job).replace(/\.(ok|fail|cancel)$/i, '');
|
|
id = id.replace(/\.env$/i, '');
|
|
id = id.replace(/\.env\.(ok|fail|cancel)$/i, '');
|
|
return id;
|
|
}
|
|
|
|
function extractLogText(text) {
|
|
var marker = '__DB_RESTORE_LOG_TEXT__';
|
|
var start = text.indexOf(marker);
|
|
if (start === -1) { return null; }
|
|
start += marker.length;
|
|
var end = text.indexOf(marker, start);
|
|
if (end === -1) { return null; }
|
|
return text.substring(start, end);
|
|
}
|
|
|
|
function doCopyText(text) {
|
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
navigator.clipboard.writeText(text).catch(function () {});
|
|
return;
|
|
}
|
|
var ta = document.createElement('textarea');
|
|
ta.value = text;
|
|
ta.style.position = 'fixed';
|
|
ta.style.top = '-1000px';
|
|
document.body.appendChild(ta);
|
|
ta.select();
|
|
try { document.execCommand('copy'); } catch (e) {}
|
|
document.body.removeChild(ta);
|
|
}
|
|
|
|
function normalizeMainTab(tab) {
|
|
if (tab === 'user' && userBackupsEnabled) {
|
|
return 'user';
|
|
}
|
|
return 'hitme';
|
|
}
|
|
|
|
function mainTabOrder(tab) {
|
|
return tab === 'user' ? 1 : 0;
|
|
}
|
|
|
|
function syncMainTabFields(tab) {
|
|
var forms = document.querySelectorAll('form');
|
|
for (var i = 0; i < forms.length; i++) {
|
|
var form = forms[i];
|
|
var field = form.querySelector('input[name="main_tab"]');
|
|
if (!field) {
|
|
field = document.createElement('input');
|
|
field.type = 'hidden';
|
|
field.name = 'main_tab';
|
|
field.className = 'js-main-tab-field';
|
|
form.appendChild(field);
|
|
}
|
|
field.value = tab;
|
|
}
|
|
}
|
|
|
|
function clearMainTabAnimation() {
|
|
if (mainTabAnimTimer) {
|
|
clearTimeout(mainTabAnimTimer);
|
|
mainTabAnimTimer = null;
|
|
}
|
|
var wrap = document.getElementById('br-main-sections');
|
|
if (wrap) {
|
|
wrap.classList.remove('is-animating');
|
|
wrap.style.minHeight = '';
|
|
}
|
|
var sections = document.querySelectorAll('[data-main-section]');
|
|
for (var i = 0; i < sections.length; i++) {
|
|
sections[i].classList.remove('br-anim-enter-from-left', 'br-anim-enter-from-right', 'br-anim-leave-to-left', 'br-anim-leave-to-right');
|
|
}
|
|
}
|
|
|
|
function setMainTabButtons(tab) {
|
|
var buttons = document.querySelectorAll('.br-main-tab');
|
|
for (var j = 0; j < buttons.length; j++) {
|
|
var btn = buttons[j];
|
|
btn.classList.toggle('is-active', btn.getAttribute('data-main-tab') === tab);
|
|
}
|
|
}
|
|
|
|
function showMainTabSection(tab) {
|
|
var sections = document.querySelectorAll('[data-main-section]');
|
|
for (var i = 0; i < sections.length; i++) {
|
|
var section = sections[i];
|
|
var sectionTab = section.getAttribute('data-main-section');
|
|
var hidden = sectionTab !== tab;
|
|
section.classList.toggle('is-hidden', hidden);
|
|
}
|
|
}
|
|
|
|
function applyMainTab(tab, persist, animate) {
|
|
tab = normalizeMainTab(tab);
|
|
var previousTab = normalizeMainTab(activeMainTab);
|
|
activeMainTab = tab;
|
|
clearMainTabAnimation();
|
|
setMainTabButtons(tab);
|
|
|
|
syncMainTabFields(tab);
|
|
|
|
if (!animate || previousTab === tab) {
|
|
showMainTabSection(tab);
|
|
return;
|
|
}
|
|
|
|
var fromSection = document.querySelector('[data-main-section="' + previousTab + '"]');
|
|
var toSection = document.querySelector('[data-main-section="' + tab + '"]');
|
|
if (!fromSection || !toSection) {
|
|
showMainTabSection(tab);
|
|
return;
|
|
}
|
|
if (fromSection === toSection) {
|
|
showMainTabSection(tab);
|
|
return;
|
|
}
|
|
|
|
var wrap = document.getElementById('br-main-sections');
|
|
if (wrap) {
|
|
wrap.classList.add('is-animating');
|
|
var nextHeight = Math.max(fromSection.offsetHeight, toSection.offsetHeight);
|
|
if (nextHeight > 0) {
|
|
wrap.style.minHeight = String(nextHeight) + 'px';
|
|
}
|
|
}
|
|
|
|
fromSection.classList.remove('is-hidden');
|
|
toSection.classList.remove('is-hidden');
|
|
|
|
var isForward = mainTabOrder(tab) > mainTabOrder(previousTab);
|
|
toSection.classList.add(isForward ? 'br-anim-enter-from-right' : 'br-anim-enter-from-left');
|
|
fromSection.classList.add(isForward ? 'br-anim-leave-to-left' : 'br-anim-leave-to-right');
|
|
|
|
mainTabAnimTimer = window.setTimeout(function () {
|
|
fromSection.classList.add('is-hidden');
|
|
clearMainTabAnimation();
|
|
}, 240);
|
|
}
|
|
|
|
function initMainTabs() {
|
|
var buttons = document.querySelectorAll('.br-main-tab');
|
|
if (!buttons.length) {
|
|
return;
|
|
}
|
|
var initial = normalizeMainTab(activeMainTab);
|
|
applyMainTab(initial, false, false);
|
|
for (var i = 0; i < buttons.length; i++) {
|
|
if (buttons[i].dataset.bound === '1') { continue; }
|
|
buttons[i].dataset.bound = '1';
|
|
buttons[i].addEventListener('click', function () {
|
|
applyMainTab(this.getAttribute('data-main-tab') || 'hitme', true, true);
|
|
});
|
|
}
|
|
}
|
|
|
|
function parseFilenameFromDisposition(disposition, fallback) {
|
|
var safeFallback = fallback || 'backup.sql.gz';
|
|
if (!disposition) { return safeFallback; }
|
|
var utf8 = disposition.match(/filename\\*=UTF-8''([^;]+)/i);
|
|
if (utf8 && utf8[1]) {
|
|
try {
|
|
return decodeURIComponent(utf8[1]);
|
|
} catch (e) {}
|
|
}
|
|
var plain = disposition.match(/filename=\"?([^\";]+)\"?/i);
|
|
if (plain && plain[1]) {
|
|
return plain[1];
|
|
}
|
|
return safeFallback;
|
|
}
|
|
|
|
function saveBlob(blob, filename) {
|
|
var link = document.createElement('a');
|
|
link.href = URL.createObjectURL(blob);
|
|
link.download = filename || 'backup.sql.gz';
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
setTimeout(function () {
|
|
URL.revokeObjectURL(link.href);
|
|
document.body.removeChild(link);
|
|
}, 0);
|
|
}
|
|
|
|
function bindHitmeBackupDownloadButtons() {
|
|
var form = document.getElementById('restore-form');
|
|
if (!form) { return; }
|
|
var buttons = form.querySelectorAll('.js-download-hitme-backup');
|
|
for (var i = 0; i < buttons.length; i++) {
|
|
if (buttons[i].dataset.boundDownload === '1') { continue; }
|
|
buttons[i].dataset.boundDownload = '1';
|
|
buttons[i].addEventListener('click', function (e) {
|
|
if (!window.fetch || !window.FormData || !window.URL || !window.URL.createObjectURL) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
var backupFile = String(this.value || '');
|
|
if (!backupFile) { return; }
|
|
var btn = this;
|
|
btn.disabled = true;
|
|
var data = new FormData(form);
|
|
data.set('action', 'download_hitme_backup');
|
|
data.set('backup_file', backupFile);
|
|
fetch(baseUrl, { method: 'POST', body: data, credentials: 'same-origin' })
|
|
.then(function (res) {
|
|
if (!res.ok) {
|
|
throw new Error('HTTP ' + res.status);
|
|
}
|
|
var disposition = String(res.headers.get('Content-Disposition') || '');
|
|
return res.blob().then(function (blob) {
|
|
return { blob: blob, disposition: disposition };
|
|
});
|
|
})
|
|
.then(function (payload) {
|
|
if (!payload.blob || payload.blob.size === 0) {
|
|
throw new Error('Pusty plik odpowiedzi');
|
|
}
|
|
var filename = parseFilenameFromDisposition(payload.disposition, backupFile);
|
|
saveBlob(payload.blob, filename);
|
|
})
|
|
.catch(function (err) {
|
|
alert('Nie udało się pobrać backupu: ' + (err && err.message ? err.message : 'nieznany błąd'));
|
|
})
|
|
.finally(function () {
|
|
btn.disabled = false;
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
function bindOverlay(container) {
|
|
if (!container) { return; }
|
|
var overlays = container.querySelectorAll('.br-overlay');
|
|
for (var i = 0; i < overlays.length; i++) {
|
|
overlays[i].addEventListener('click', function (e) {
|
|
if (e.target === this) {
|
|
closeOverlay();
|
|
}
|
|
});
|
|
}
|
|
var closeButtons = container.querySelectorAll('[data-close-overlay]');
|
|
for (var j = 0; j < closeButtons.length; j++) {
|
|
closeButtons[j].addEventListener('click', function () {
|
|
closeOverlay();
|
|
});
|
|
}
|
|
}
|
|
|
|
function closeOverlay() {
|
|
var container = document.getElementById('br-overlay-container');
|
|
if (!container) { return; }
|
|
container.innerHTML = '';
|
|
if (logOverlayTimer) {
|
|
clearInterval(logOverlayTimer);
|
|
logOverlayTimer = null;
|
|
}
|
|
logOverlayJob = '';
|
|
}
|
|
|
|
function showOverlay(html) {
|
|
var container = document.getElementById('br-overlay-container');
|
|
if (!container) { return; }
|
|
container.innerHTML = html;
|
|
bindOverlay(container);
|
|
}
|
|
|
|
function refreshLogCommon(job, onUpdate, onComplete) {
|
|
if (!job) { return; }
|
|
fetch(baseUrl + '?action=log_stream&job=' + encodeURIComponent(job), { credentials: 'same-origin', cache: 'no-store' })
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (data) {
|
|
if (typeof onUpdate === 'function') {
|
|
onUpdate(data || {});
|
|
}
|
|
if (data && data.status && data.status !== 'processing' && typeof onComplete === 'function') {
|
|
onComplete();
|
|
}
|
|
})
|
|
.catch(function () {});
|
|
}
|
|
|
|
function refreshLogOverlay() {
|
|
if (!logOverlayJob) { return; }
|
|
refreshLogCommon(logOverlayJob, function (data) {
|
|
var el = document.getElementById('log-content');
|
|
if (el && typeof data.log === 'string') {
|
|
el.textContent = data.log;
|
|
el.scrollTop = el.scrollHeight;
|
|
}
|
|
var statusEl = document.getElementById('log-status');
|
|
if (statusEl && data.display_label) {
|
|
statusEl.textContent = data.display_label;
|
|
}
|
|
var progressTextEl = document.getElementById('log-progress-text');
|
|
if (progressTextEl) {
|
|
if (typeof data.progress_text === 'string') {
|
|
progressTextEl.textContent = data.progress_text;
|
|
} else if (typeof data.last_line === 'string') {
|
|
progressTextEl.textContent = data.last_line;
|
|
}
|
|
}
|
|
var bar = document.getElementById('log-progress-bar');
|
|
if (bar) {
|
|
var p = (typeof data.progress === 'number') ? data.progress : 0;
|
|
bar.style.width = p + '%';
|
|
}
|
|
}, function () {
|
|
if (logOverlayTimer) {
|
|
clearInterval(logOverlayTimer);
|
|
logOverlayTimer = null;
|
|
}
|
|
});
|
|
}
|
|
|
|
function refreshPageLog() {
|
|
if (!pageLogJob) { return; }
|
|
refreshLogCommon(pageLogJob, function (data) {
|
|
var el = document.getElementById('log-content');
|
|
if (el && typeof data.log === 'string') {
|
|
el.textContent = data.log;
|
|
el.scrollTop = el.scrollHeight;
|
|
}
|
|
var statusEl = document.getElementById('log-status');
|
|
if (statusEl && data.display_label) {
|
|
statusEl.textContent = data.display_label;
|
|
}
|
|
var progressTextEl = document.getElementById('log-progress-text');
|
|
if (progressTextEl) {
|
|
if (typeof data.progress_text === 'string') {
|
|
progressTextEl.textContent = data.progress_text;
|
|
} else if (typeof data.last_line === 'string') {
|
|
progressTextEl.textContent = data.last_line;
|
|
}
|
|
}
|
|
var bar = document.getElementById('log-progress-bar');
|
|
if (bar) {
|
|
var p = (typeof data.progress === 'number') ? data.progress : 0;
|
|
bar.style.width = p + '%';
|
|
}
|
|
}, function () {
|
|
if (pageLogTimer) {
|
|
clearInterval(pageLogTimer);
|
|
pageLogTimer = null;
|
|
}
|
|
});
|
|
}
|
|
|
|
window.downloadLog = function (job) {
|
|
if (!job) { return; }
|
|
var id = normalizeJobId(job);
|
|
var formData = new FormData();
|
|
formData.append('action', 'download_log');
|
|
formData.append('raw', '1');
|
|
formData.append('job', job);
|
|
formData.append('csrf_token', csrfToken || '');
|
|
fetch(baseUrl, { method: 'POST', body: formData, credentials: 'same-origin' })
|
|
.then(function (r) { return r.text(); })
|
|
.then(function (text) {
|
|
var content = extractLogText(text || '');
|
|
if (content === null) {
|
|
content = text || '';
|
|
}
|
|
var blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
|
|
var a = document.createElement('a');
|
|
a.href = URL.createObjectURL(blob);
|
|
a.download = (id || 'log') + '.log';
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
setTimeout(function () {
|
|
URL.revokeObjectURL(a.href);
|
|
document.body.removeChild(a);
|
|
}, 0);
|
|
})
|
|
.catch(function () {});
|
|
};
|
|
|
|
window.copyLogToClipboard = function () {
|
|
var job = logOverlayJob || pageLogJob || '';
|
|
var el = document.getElementById('log-content');
|
|
if (!job) {
|
|
if (!el) { return; }
|
|
doCopyText(el.textContent || '');
|
|
return;
|
|
}
|
|
var formData = new FormData();
|
|
formData.append('action', 'download_log');
|
|
formData.append('raw', '1');
|
|
formData.append('job', job);
|
|
formData.append('csrf_token', csrfToken || '');
|
|
fetch(baseUrl, { method: 'POST', body: formData, credentials: 'same-origin' })
|
|
.then(function (r) { return r.text(); })
|
|
.then(function (text) {
|
|
var content = extractLogText(text || '');
|
|
if (content === null) {
|
|
content = text || '';
|
|
}
|
|
doCopyText(content);
|
|
})
|
|
.catch(function () {
|
|
if (el) { doCopyText(el.textContent || ''); }
|
|
});
|
|
};
|
|
|
|
window.openLogOverlay = function (job) {
|
|
if (!job) { return; }
|
|
if (!logsOverlayEnabled) {
|
|
window.location.href = baseUrl + '?action=view_log&job=' + encodeURIComponent(job);
|
|
return;
|
|
}
|
|
logOverlayJob = job;
|
|
fetch(baseUrl + '?action=view_log&overlay=1&job=' + encodeURIComponent(job), { credentials: 'same-origin', cache: 'no-store' })
|
|
.then(function (r) { return r.text(); })
|
|
.then(function (html) {
|
|
showOverlay(html);
|
|
refreshLogOverlay();
|
|
if (logOverlayTimer) {
|
|
clearInterval(logOverlayTimer);
|
|
}
|
|
logOverlayTimer = setInterval(refreshLogOverlay, 3000);
|
|
})
|
|
.catch(function () {});
|
|
};
|
|
|
|
var statusEl = document.getElementById('job-status');
|
|
var busy = false;
|
|
function refreshStatus() {
|
|
if (!statusEl) { return; }
|
|
if (busy) { return; }
|
|
busy = true;
|
|
fetch(baseUrl + '?action=status', { credentials: 'same-origin', cache: 'no-store' })
|
|
.then(function (res) {
|
|
if (!res.ok) { return ''; }
|
|
return res.text();
|
|
})
|
|
.then(function (html) {
|
|
if (html) {
|
|
statusEl.innerHTML = html;
|
|
syncMainTabFields(activeMainTab);
|
|
}
|
|
})
|
|
.catch(function () {})
|
|
.finally(function () { busy = false; });
|
|
}
|
|
|
|
bindOverlay(document.getElementById('br-overlay-container'));
|
|
initMainTabs();
|
|
bindHitmeBackupDownloadButtons();
|
|
widenLayoutForEvolution();
|
|
window.addEventListener('dbrestore:tab-updated', function () {
|
|
syncMainTabFields(activeMainTab);
|
|
});
|
|
if (pageLogJob) {
|
|
pageLogTimer = setInterval(refreshPageLog, 3000);
|
|
}
|
|
setInterval(refreshStatus, 3000);
|
|
window.addEventListener('resize', widenLayoutForEvolution);
|
|
})();
|
|
|
|
(function () {
|
|
var daUserPrefix = <?php echo json_encode($da_user . '_'); ?>;
|
|
var modeRadios = document.querySelectorAll('input[name="restore_mode_global"]');
|
|
var targetWrap = document.getElementById('restore-mode-target-wrap');
|
|
var targetInput = document.getElementById('restore-mode-target-input');
|
|
var errorBox = document.getElementById('restore-mode-error');
|
|
|
|
function currentMode() {
|
|
var checked = document.querySelector('input[name="restore_mode_global"]:checked');
|
|
return checked ? checked.value : 'overwrite';
|
|
}
|
|
|
|
function currentTargetDb() {
|
|
return targetInput ? String(targetInput.value || '').trim() : '';
|
|
}
|
|
|
|
function ensureTargetPrefix() {
|
|
if (!targetInput) { return; }
|
|
var value = String(targetInput.value || '').trim();
|
|
if (!value) {
|
|
targetInput.value = daUserPrefix;
|
|
return;
|
|
}
|
|
if (value.indexOf(daUserPrefix) !== 0) {
|
|
targetInput.value = daUserPrefix + value.replace(/^[^_]*_?/, '');
|
|
}
|
|
}
|
|
|
|
function targetNameValid(name) {
|
|
if (!name) { return false; }
|
|
var escapedPrefix = daUserPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
var re = new RegExp('^' + escapedPrefix + '[A-Za-z0-9_]+$');
|
|
return re.test(name);
|
|
}
|
|
|
|
function syncHiddenFields() {
|
|
var mode = currentMode();
|
|
var target = currentTargetDb();
|
|
var modeFields = document.querySelectorAll('.js-restore-mode-field');
|
|
for (var i = 0; i < modeFields.length; i++) {
|
|
modeFields[i].value = mode;
|
|
}
|
|
var targetFields = document.querySelectorAll('.js-target-db-field');
|
|
for (var j = 0; j < targetFields.length; j++) {
|
|
targetFields[j].value = target;
|
|
}
|
|
}
|
|
|
|
function validateModeSection(showMessage, sourceDb) {
|
|
if (errorBox) {
|
|
errorBox.style.display = 'none';
|
|
errorBox.textContent = '';
|
|
}
|
|
if (currentMode() !== 'new_db') {
|
|
return true;
|
|
}
|
|
var val = currentTargetDb();
|
|
if (!targetNameValid(val)) {
|
|
if (showMessage && errorBox) {
|
|
errorBox.textContent = 'Nazwa docelowej bazy danych musi zaczynać się od prefiksu ' + daUserPrefix + ' (DA_USER_).';
|
|
errorBox.style.display = 'block';
|
|
}
|
|
return false;
|
|
}
|
|
if (sourceDb && val === sourceDb) {
|
|
if (showMessage && errorBox) {
|
|
errorBox.textContent = 'Docelowa baza danych musi być inna niż źródłowa baza z backupu.';
|
|
errorBox.style.display = 'block';
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function enforceModeUi() {
|
|
var mode = currentMode();
|
|
if (targetWrap) {
|
|
targetWrap.style.display = mode === 'new_db' ? '' : 'none';
|
|
}
|
|
if (mode === 'new_db') {
|
|
ensureTargetPrefix();
|
|
}
|
|
var backupTabBtn = document.querySelector('.br-user-tab[data-user-tab="backup_create"]');
|
|
if (backupTabBtn) {
|
|
backupTabBtn.style.display = mode === 'new_db' ? 'none' : '';
|
|
}
|
|
if (mode === 'new_db') {
|
|
var activeBackup = document.querySelector('.br-user-tab.is-active[data-user-tab="backup_create"]');
|
|
var restoreTabBtn = document.querySelector('.br-user-tab[data-user-tab="restore_user"]');
|
|
if (activeBackup && restoreTabBtn) {
|
|
restoreTabBtn.click();
|
|
}
|
|
}
|
|
syncHiddenFields();
|
|
}
|
|
|
|
for (var i = 0; i < modeRadios.length; i++) {
|
|
modeRadios[i].addEventListener('change', function () {
|
|
enforceModeUi();
|
|
validateModeSection(false);
|
|
});
|
|
}
|
|
if (targetInput) {
|
|
targetInput.addEventListener('input', function () {
|
|
syncHiddenFields();
|
|
validateModeSection(false);
|
|
});
|
|
targetInput.addEventListener('blur', function () {
|
|
ensureTargetPrefix();
|
|
syncHiddenFields();
|
|
});
|
|
}
|
|
|
|
document.addEventListener('submit', function (e) {
|
|
var form = e.target;
|
|
if (!form || form.tagName !== 'FORM') { return; }
|
|
syncHiddenFields();
|
|
var submitterName = '';
|
|
if (e.submitter && e.submitter.name) {
|
|
submitterName = String(e.submitter.name);
|
|
}
|
|
if (submitterName === 'download_user_backup' || submitterName === 'download_hitme_backup' || submitterName === 'delete_user_backup') {
|
|
return;
|
|
}
|
|
var actionField = form.querySelector('input[name="action"]');
|
|
var actionValue = actionField ? String(actionField.value || '') : '';
|
|
if (actionValue === 'restore' || actionValue === 'restore_user') {
|
|
var sourceDb = '';
|
|
if (actionValue === 'restore') {
|
|
var src = form.querySelector('input[name="db"]:checked');
|
|
sourceDb = src ? String(src.value || '') : '';
|
|
} else if (actionValue === 'restore_user') {
|
|
var usrc = form.querySelector('input[name="u_db"]:checked');
|
|
sourceDb = usrc ? String(usrc.value || '') : '';
|
|
}
|
|
if (!validateModeSection(true, sourceDb)) {
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
}, true);
|
|
|
|
window.__dbRestoreMode = {
|
|
getMode: currentMode,
|
|
getTargetDb: currentTargetDb,
|
|
sync: syncHiddenFields,
|
|
validate: validateModeSection
|
|
};
|
|
enforceModeUi();
|
|
})();
|
|
|
|
(function () {
|
|
function bindRowRestoreActions(formId, radioName, buttonSelector) {
|
|
var form = document.getElementById(formId);
|
|
if (!form) { return; }
|
|
var buttons = form.querySelectorAll(buttonSelector);
|
|
if (!buttons.length) { return; }
|
|
function setButtonState(btn, isActive) {
|
|
btn.disabled = !isActive;
|
|
if (isActive) {
|
|
btn.classList.remove('br-btn-fogged');
|
|
btn.setAttribute('aria-disabled', 'false');
|
|
} else {
|
|
if (!btn.classList.contains('br-btn-fogged')) {
|
|
btn.classList.add('br-btn-fogged');
|
|
}
|
|
btn.setAttribute('aria-disabled', 'true');
|
|
}
|
|
}
|
|
function updateButtons() {
|
|
var checked = form.querySelector('input[name="' + radioName + '"]:checked');
|
|
for (var i = 0; i < buttons.length; i++) {
|
|
var btn = buttons[i];
|
|
var row = btn.closest('tr');
|
|
var rowRadio = row ? row.querySelector('input[name="' + radioName + '"]') : null;
|
|
var isActive = !!(checked && rowRadio && checked === rowRadio);
|
|
setButtonState(btn, isActive);
|
|
}
|
|
}
|
|
if (form.dataset.rowRestoreBound === '1') {
|
|
if (typeof form.__rowRestoreUpdate === 'function') {
|
|
form.__rowRestoreUpdate();
|
|
}
|
|
return;
|
|
}
|
|
form.dataset.rowRestoreBound = '1';
|
|
form.__rowRestoreUpdate = updateButtons;
|
|
form.addEventListener('change', updateButtons);
|
|
for (var i = 0; i < buttons.length; i++) {
|
|
buttons[i].addEventListener('click', function (e) {
|
|
if (this.disabled) {
|
|
e.preventDefault();
|
|
return;
|
|
}
|
|
var row = this.closest('tr');
|
|
var rowRadio = row ? row.querySelector('input[name="' + radioName + '"]') : null;
|
|
if (rowRadio) {
|
|
rowRadio.checked = true;
|
|
}
|
|
updateButtons();
|
|
});
|
|
}
|
|
updateButtons();
|
|
}
|
|
bindRowRestoreActions('restore-form', 'db', '.js-hitme-row-restore');
|
|
bindRowRestoreActions('user-restore-form', 'u_db', '.js-user-row-restore');
|
|
window.addEventListener('dbrestore:tab-updated', function () {
|
|
bindRowRestoreActions('restore-form', 'db', '.js-hitme-row-restore');
|
|
bindRowRestoreActions('user-restore-form', 'u_db', '.js-user-row-restore');
|
|
});
|
|
})();
|
|
|
|
(function () {
|
|
var baseUrl = <?php echo json_encode($base_url, JSON_UNESCAPED_SLASHES); ?>;
|
|
var userTabsEl = document.getElementById('user-tabs');
|
|
if (!userTabsEl) { return; }
|
|
|
|
function withRestoreMode(query) {
|
|
var params = new URLSearchParams(query);
|
|
if (window.__dbRestoreMode) {
|
|
params.set('restore_mode', window.__dbRestoreMode.getMode());
|
|
params.set('target_db', window.__dbRestoreMode.getTargetDb());
|
|
}
|
|
return params.toString();
|
|
}
|
|
|
|
function parseFilenameFromDisposition(disposition, fallback) {
|
|
var safeFallback = fallback || 'backup.sql.gz';
|
|
if (!disposition) { return safeFallback; }
|
|
var utf8 = disposition.match(/filename\\*=UTF-8''([^;]+)/i);
|
|
if (utf8 && utf8[1]) {
|
|
try {
|
|
return decodeURIComponent(utf8[1]);
|
|
} catch (e) {}
|
|
}
|
|
var plain = disposition.match(/filename=\"?([^\";]+)\"?/i);
|
|
if (plain && plain[1]) {
|
|
return plain[1];
|
|
}
|
|
return safeFallback;
|
|
}
|
|
|
|
function saveBlob(blob, filename) {
|
|
var link = document.createElement('a');
|
|
link.href = URL.createObjectURL(blob);
|
|
link.download = filename || 'backup.sql.gz';
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
setTimeout(function () {
|
|
URL.revokeObjectURL(link.href);
|
|
document.body.removeChild(link);
|
|
}, 0);
|
|
}
|
|
|
|
function bindBackupSelection() {
|
|
var form = userTabsEl.querySelector('#user-backup-form');
|
|
if (!form) { return; }
|
|
var checks = form.querySelectorAll('.js-user-db-check');
|
|
var bulkBtn = form.querySelector('#bulk-user-backup-btn');
|
|
if (!bulkBtn) { return; }
|
|
function updateBulkButton() {
|
|
var checked = 0;
|
|
for (var i = 0; i < checks.length; i++) {
|
|
if (checks[i].checked) { checked++; }
|
|
}
|
|
bulkBtn.style.display = checked >= 2 ? 'inline-block' : 'none';
|
|
}
|
|
for (var i = 0; i < checks.length; i++) {
|
|
checks[i].addEventListener('change', updateBulkButton);
|
|
}
|
|
updateBulkButton();
|
|
}
|
|
|
|
function bindUserBackupDownloadButtons() {
|
|
var form = userTabsEl.querySelector('#user-restore-form');
|
|
if (!form) { return; }
|
|
var buttons = form.querySelectorAll('.js-download-user-backup');
|
|
for (var i = 0; i < buttons.length; i++) {
|
|
if (buttons[i].dataset.boundDownload === '1') { continue; }
|
|
buttons[i].dataset.boundDownload = '1';
|
|
buttons[i].addEventListener('click', function (e) {
|
|
if (!window.fetch || !window.FormData || !window.URL || !window.URL.createObjectURL) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
var backupFile = String(this.value || '');
|
|
if (!backupFile) { return; }
|
|
var btn = this;
|
|
btn.disabled = true;
|
|
var data = new FormData(form);
|
|
data.set('action', 'download_user_backup');
|
|
data.set('backup_file', backupFile);
|
|
fetch(baseUrl, { method: 'POST', body: data, credentials: 'same-origin' })
|
|
.then(function (res) {
|
|
if (!res.ok) {
|
|
throw new Error('HTTP ' + res.status);
|
|
}
|
|
var disposition = String(res.headers.get('Content-Disposition') || '');
|
|
return res.blob().then(function (blob) {
|
|
return { blob: blob, disposition: disposition };
|
|
});
|
|
})
|
|
.then(function (payload) {
|
|
if (!payload.blob || payload.blob.size === 0) {
|
|
throw new Error('Pusty plik odpowiedzi');
|
|
}
|
|
var filename = parseFilenameFromDisposition(payload.disposition, backupFile);
|
|
saveBlob(payload.blob, filename);
|
|
})
|
|
.catch(function (err) {
|
|
alert('Nie udało się pobrać backupu: ' + (err && err.message ? err.message : 'nieznany błąd'));
|
|
})
|
|
.finally(function () {
|
|
btn.disabled = false;
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
function bindUserRestoreSlotPicker() {
|
|
var radios = userTabsEl.querySelectorAll('form.br-time-picker input[type="radio"][name="u_slot"]');
|
|
for (var i = 0; i < radios.length; i++) {
|
|
if (radios[i].dataset.bound === '1') { continue; }
|
|
radios[i].dataset.bound = '1';
|
|
radios[i].addEventListener('change', function () {
|
|
if (!this.checked) { return; }
|
|
var form = this.form;
|
|
if (!form) { return; }
|
|
var params = new URLSearchParams(new FormData(form));
|
|
loadUserTab(withRestoreMode(params.toString()), 0);
|
|
});
|
|
}
|
|
}
|
|
|
|
function userTabOrder(tab) {
|
|
return tab === 'restore_user' ? 1 : 0;
|
|
}
|
|
|
|
function getActiveUserTab() {
|
|
var active = userTabsEl.querySelector('.br-user-tab.is-active');
|
|
if (!active) {
|
|
return 'backup_create';
|
|
}
|
|
var tab = active.getAttribute('data-user-tab') || 'backup_create';
|
|
return tab === 'restore_user' ? 'restore_user' : 'backup_create';
|
|
}
|
|
|
|
function animateUserTabPanel(direction) {
|
|
if (!direction) { return; }
|
|
var panel = userTabsEl.querySelector('#user-tab-content');
|
|
if (!panel) { return; }
|
|
panel.classList.remove('br-anim-enter-from-left', 'br-anim-enter-from-right');
|
|
void panel.offsetWidth;
|
|
panel.classList.add(direction > 0 ? 'br-anim-enter-from-right' : 'br-anim-enter-from-left');
|
|
window.setTimeout(function () {
|
|
panel.classList.remove('br-anim-enter-from-left', 'br-anim-enter-from-right');
|
|
}, 240);
|
|
}
|
|
|
|
function bindUserTabButtons() {
|
|
var buttons = userTabsEl.querySelectorAll('.br-user-tab');
|
|
for (var i = 0; i < buttons.length; i++) {
|
|
if (buttons[i].dataset.bound === '1') { continue; }
|
|
buttons[i].dataset.bound = '1';
|
|
buttons[i].addEventListener('click', function () {
|
|
var tab = this.getAttribute('data-user-tab') || 'backup_create';
|
|
var currentTab = getActiveUserTab();
|
|
var direction = userTabOrder(tab) - userTabOrder(currentTab);
|
|
loadUserTab(withRestoreMode('action=user_tab&user_tab=' + encodeURIComponent(tab)), direction);
|
|
});
|
|
}
|
|
}
|
|
|
|
function loadUserTab(query, direction) {
|
|
var animDirection = (typeof direction === 'number') ? direction : 0;
|
|
fetch(baseUrl + '?' + query, { credentials: 'same-origin', cache: 'no-store' })
|
|
.then(function (res) {
|
|
if (!res.ok) { return ''; }
|
|
return res.text();
|
|
})
|
|
.then(function (html) {
|
|
if (!html) { return; }
|
|
userTabsEl.innerHTML = html;
|
|
if (window.__dbRestoreMode) {
|
|
window.__dbRestoreMode.sync();
|
|
}
|
|
bindUserTabButtons();
|
|
bindBackupSelection();
|
|
bindUserBackupDownloadButtons();
|
|
bindUserRestoreSlotPicker();
|
|
animateUserTabPanel(animDirection);
|
|
var evt = new Event('dbrestore:tab-updated');
|
|
window.dispatchEvent(evt);
|
|
})
|
|
.catch(function () {});
|
|
}
|
|
|
|
userTabsEl.addEventListener('submit', function (e) {
|
|
var form = e.target;
|
|
if (!form || form.tagName !== 'FORM') { return; }
|
|
var actionField = form.querySelector('input[name="action"]');
|
|
if (!actionField || actionField.value !== 'user_tab') { return; }
|
|
e.preventDefault();
|
|
var params = new URLSearchParams(new FormData(form));
|
|
loadUserTab(withRestoreMode(params.toString()), 0);
|
|
});
|
|
|
|
bindUserTabButtons();
|
|
bindBackupSelection();
|
|
bindUserBackupDownloadButtons();
|
|
bindUserRestoreSlotPicker();
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|
|
<?php
|
|
$page = (string)ob_get_clean();
|
|
echo translate_html_safe($page, $lang_map ?? []);
|