$ctx->t($key, $vars);
$et = static fn(string $key, array $vars = []): string => AppContext::e($ctx->t($key, $vars));
$daUser = $ctx->daUser->username();
$prefix = $ctx->daUser->prefix();
$queue = new BackupQueueService($ctx->settings, $ctx->daUser);
try {
$queue->ensureInfrastructure();
} catch (Throwable $e) {
$errors[] = $ctx->formatException($e);
}
if (!$ctx->settings->enableUploadBackup()) {
$ctx->renderPage(
'Przywróć Backup',
' ' . $et('Obsługa przywracania backupów jest wyłączona (`ENABLE_UPLOAD_BACKUP=false`).') . '
' . $et('Brak baz danych do wyboru.') . '
'; } if ($selected === '' || !in_array($selected, $filtered, true)) { $selected = $filtered[0] ?? ''; } $safePrefix = preg_replace('/[^A-Za-z0-9_-]+/', '_', $idPrefix) ?? ''; $items = '' . $line1 . '
' . $line2 . '
'; } else { $confirmBody = '' . $ctx->t( 'Czy na pewno chcesz przywrócić kopię zapasową bazy danych {source} z dn. {date} {overwrite} znajdujące się w bazie danych {target}?', ['source' => $sourceHtml, 'date' => $dateHtml, 'overwrite' => $overwriteHtml, 'target' => $targetHtml] ) . '
'; } $confirmOverlay = $renderConfirmOverlay($confirmBody, [ 'active_tab' => 'local', 'form_action' => 'restore_local', 'local_backup' => $backupId, 'restore_mode' => $restoreMode, 'target_db' => $targetDb, ]); $activeTab = 'local'; } else { if ($restoreMode === 'new_db') { $jobId = $queue->queueRestoreFromLocal($backupId, $restoreMode, $targetDb); } else { $jobId = $queue->queueRestoreFromLocal($backupId, $restoreMode, ''); } $ok[] = $tr('Dodano zadanie przywracania backupu lokalnego (ID: {id}).', ['id' => $jobId]); $activeTab = 'local'; } } elseif ($action === 'restore_file') { $targetDb = NamePolicy::normalizeDatabaseName($ctx->postString('target_db'), $prefix); $ensureDbExists($ctx, $targetDb); $confirmAction = $ctx->postString('confirm_action') === '1'; $sourceMode = strtolower($ctx->postString('source_mode', 'local')); if (!in_array($sourceMode, ['local', 'server'], true)) { $sourceMode = 'local'; } $stagedPath = trim($ctx->postString('uploaded_tmp_path')); $uploadedOriginalName = trim($ctx->postString('uploaded_original_name')); $localUploadField = trim($ctx->postString('source_file_upload')); if ($localUploadField === '') { $rawUploadField = $_POST['source_file_upload'] ?? null; if (is_array($rawUploadField) && isset($rawUploadField[0])) { $localUploadField = trim((string)$rawUploadField[0]); } elseif (!is_array($rawUploadField) && $rawUploadField !== null) { $localUploadField = trim((string)$rawUploadField); } } $serverPath = trim($sourceFilePathValue); if ($sourceMode === 'server' && $serverPath === '') { throw new RuntimeException('Wskaż ścieżkę do pliku SQL lub SQL.GZ.'); } if ($sourceMode === 'local' && $stagedPath === '') { $uploadFile = $_FILES['source_file_upload'] ?? null; if (is_array($uploadFile) && !empty($uploadFile) && (int)($uploadFile['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_NO_FILE) { $stagedPath = $queue->stageRestoreUploadFromPhpUpload($uploadFile, $uploadedOriginalName); if ($uploadedOriginalName === '') { $uploadedOriginalName = basename((string)($uploadFile['name'] ?? '')); } } elseif ($localUploadField !== '') { $stagedPath = $queue->stageRestoreUploadFromDirectAdminTemp($localUploadField, $uploadedOriginalName); } else { throw new RuntimeException('Nie udało się przygotować pliku do przywrócenia. Wybierz plik ponownie.'); } } if ($sourceMode === 'local' && $stagedPath === '') { throw new RuntimeException('Nie udało się przygotować pliku do przywrócenia. Wybierz plik ponownie.'); } $fileLabel = ''; if ($sourceMode === 'local') { if ($uploadedOriginalName !== '') { $fileLabel = $displayFileName($uploadedOriginalName); } if ($fileLabel === '' && $stagedPath !== '') { $fileLabel = $displayFileName($stagedPath); } } else { $fileLabel = $displayFileName($serverPath); } if ($fileLabel === '') { $fileLabel = 'backup.sql.gz'; } // Bezpieczeństwo: dla restore z pliku nie wykrywamy i nie ufamy nazwie bazy z nazwy pliku. // Import zawsze ma trafić do bazy docelowej wskazanej przez użytkownika. $sourceDbName = ''; $postedRestoreMode = strtolower(trim($ctx->postString('restore_mode'))); $restoreModeFile = in_array($postedRestoreMode, ['overwrite', 'new_db'], true) ? $postedRestoreMode : 'overwrite'; if (!$allowRestoreOther) { $restoreModeFile = 'overwrite'; } @error_log(sprintf( "[%s] RESTORE_FILE action=%s user=%s source_mode=%s has_files=%s local_field=%s source_path=%s uploaded_tmp=%s uploaded_name=%s source_db=%s target_db=%s restore_mode=%s confirm=%s\n", date('c'), $ctx->action(), $daUser, $sourceMode, empty($_FILES) ? '0' : '1', $localUploadField, $serverPath, $stagedPath, $uploadedOriginalName, $sourceDbName, $targetDb, $restoreModeFile, $confirmAction ? '1' : '0' ), 3, PLUGIN_ROOT . '/error.log'); if (!$confirmAction) { $fileHtml = '' . AppContext::e($fileLabel) . ''; $targetHtmlDanger = '' . AppContext::e($targetDb) . ''; if ($restoreModeFile === 'new_db' && $sourceDbName !== '') { $sourceHtml = '' . AppContext::e($sourceDbName) . ''; $line1 = $ctx->t( 'Czy na pewno chcesz przywrócić dane z kopii zapasowej bazy danych {source} z pliku {file} do innej bazy danych o nazwie {target}?', ['source' => $sourceHtml, 'file' => $fileHtml, 'target' => $targetHtmlDanger] ); $line2 = $ctx->t( 'Uwaga: dane znajdujące się obecnie w bazie danych {target} zostaną usunięte i nadpisane danymi z kopii zapasowej dla bazy {source}.', ['target' => $targetHtmlDanger, 'source' => $sourceHtml] ); } else { $line1 = $ctx->t( 'Czy na pewno chcesz przywrócić plik kopii zapasowej {file} do bazy danych {target}?', ['file' => $fileHtml, 'target' => $targetHtmlDanger] ); $line2 = $ctx->t( 'Uwaga: jeśli baza danych {target} zawiera dane, zostaną one nadpisane.', ['target' => $targetHtmlDanger] ); } $confirmOverlay = $renderConfirmOverlay('' . $line1 . '
' . $line2 . '
', [ 'active_tab' => 'file', 'form_action' => 'restore_file', 'source_mode' => $sourceMode, 'source_file_path' => $serverPath, 'uploaded_tmp_path' => $stagedPath, 'uploaded_original_name' => ($uploadedOriginalName !== '' ? $uploadedOriginalName : $fileLabel), 'source_db_name' => $sourceDbName, 'restore_mode' => $restoreModeFile, 'target_db' => $targetDb, ]); $activeTab = 'file'; } else { if ($sourceMode === 'local') { $jobId = $queue->queueRestoreFromFilePath($targetDb, $stagedPath, $restoreModeFile, $sourceDbName); } else { $filePath = $serverPath; $jobId = $queue->queueRestoreFromFilePath($targetDb, $filePath, $restoreModeFile, $sourceDbName); } $ok[] = $tr('Zadanie przywrócenia kopii zapasowej {file} do bazy danych {db} zostało dodane do kolejki', [ 'file' => $fileLabel, 'db' => $targetDb, ]); $activeTab = 'file'; } } elseif ($action === 'download_user_backup') { $jobId = trim($ctx->postString('backup_job_id')); $backupPath = trim($ctx->postString('backup_path')); $path = ''; if ($jobId !== '') { $path = $queue->resolveBackupTargetFileForCurrentUser($jobId); } if ($path === '' && $backupPath !== '') { $path = $queue->resolveUserBackupFileForCurrentUser($backupPath); } if ($path === '') { throw new RuntimeException('Nie można odnaleźć pliku backupu.'); } $streamUserBackup($path); } elseif ($action === 'delete_user_backup') { $jobId = trim($ctx->postString('backup_job_id')); $backupPath = trim($ctx->postString('backup_path')); if ($jobId === '' && $backupPath === '') { throw new RuntimeException('Brak identyfikatora zadania backupu.'); } $confirmAction = $ctx->postString('confirm_action') === '1'; if (!$confirmAction) { $dbLabel = 'nieznana baza'; $dateLabel = '-'; if ($jobId !== '') { $job = $queue->getJobForCurrentUser($jobId); if (($job['type'] ?? '') !== 'backup') { throw new RuntimeException('Wskazane zadanie nie jest zadaniem backupu.'); } $dbLabel = (string)($job['db_name'] ?? '') !== '' ? (string)$job['db_name'] : $dbLabel; $startTs = (int)($job['start_ts'] ?? 0); $dateLabel = $startTs > 0 ? date('Y-m-d H:i:s', $startTs) : '-'; } elseif ($backupPath !== '') { $entry = $findUserBackupEntry($queue, $backupPath); if ($entry !== null) { $dbLabel = (string)($entry['db_name'] ?? '') !== '' ? (string)$entry['db_name'] : $dbLabel; $ts = (int)($entry['mtime'] ?? 0); $dateLabel = $ts > 0 ? date('Y-m-d H:i:s', $ts) : $dateLabel; } } $confirmBody = '' . $ctx->t( 'Czy na pewno chcesz usunąć backup bazy danych {db} z dn. {date}?', ['db' => '' . AppContext::e($dbLabel) . '', 'date' => '' . AppContext::e($dateLabel) . ''] ) . '
'; $confirmOverlay = $renderConfirmOverlay($confirmBody, [ 'active_tab' => 'user', 'form_action' => 'delete_user_backup', 'backup_job_id' => $jobId, 'backup_path' => $backupPath, 'user_month' => $ctx->postString('user_month', $ctx->queryString('user_month')), 'user_date' => $ctx->postString('user_date', $ctx->queryString('user_date')), 'user_slot' => $ctx->postString('user_slot', $ctx->queryString('user_slot')), ]); $activeTab = 'user'; } else { if ($jobId !== '') { $deletedPath = $queue->deleteBackupTargetFileForCurrentUser($jobId); try { $queue->deleteBackupLogForCurrentUser($jobId); } catch (Throwable $logErr) { // ignore log cleanup failure } } else { $deletedPath = $queue->deleteUserBackupFileForCurrentUser($backupPath); } $ok[] = $tr('Usunięto plik backupu: {path}', ['path' => $deletedPath]); $activeTab = 'user'; } } } catch (Throwable $e) { $errors[] = $ctx->formatException($e); } } $backupsByDate = $queue->listLocalBackupsByDate(); $availableDates = array_keys($backupsByDate); $availableMonths = []; foreach ($availableDates as $dateKey) { if (strlen($dateKey) >= 7) { $availableMonths[substr($dateKey, 0, 7)] = true; } } $availableMonths = array_keys($availableMonths); rsort($availableMonths, SORT_STRING); $selectedMonth = trim($ctx->postString('backup_month', $ctx->queryString('month', ''))); if ($selectedMonth === '' || (!empty($availableMonths) && !in_array($selectedMonth, $availableMonths, true))) { $selectedMonth = $availableMonths[0] ?? date('Y-m'); } $selectedDate = trim($ctx->postString('backup_date', $ctx->queryString('backup_date'))); $today = date('Y-m-d'); $monthRequested = ($ctx->postString('backup_month', '') !== '' || $ctx->queryString('month', '') !== ''); $dateRequested = ($ctx->postString('backup_date', '') !== '' || $ctx->queryString('backup_date', '') !== ''); if (!$monthRequested && !$dateRequested && isset($backupsByDate[$today])) { $selectedMonth = substr($today, 0, 7); $selectedDate = $today; } if ($selectedDate === '' || !isset($backupsByDate[$selectedDate]) || substr($selectedDate, 0, 7) !== $selectedMonth) { $selectedDate = ''; foreach ($availableDates as $dayKey) { if (substr($dayKey, 0, 7) === $selectedMonth) { $selectedDate = $dayKey; break; } } } $selectedEntries = ($selectedDate !== '' && isset($backupsByDate[$selectedDate])) ? $backupsByDate[$selectedDate] : []; $selectedSlot = trim($ctx->postString('backup_slot', $ctx->queryString('backup_slot', ''))); $timeSlots = []; foreach ($selectedEntries as $entry) { $timeLabel = trim((string)($entry['time'] ?? '')); $mtime = (int)($entry['mtime'] ?? 0); if ($timeLabel === '' && $mtime > 0) { $timeLabel = date('H:i:s', $mtime); } if ($timeLabel === '') { $timeLabel = '00:00:00'; } if (!isset($timeSlots[$timeLabel])) { $timeSlots[$timeLabel] = []; } $timeSlots[$timeLabel][] = $entry; } $hasMultipleSlots = count($timeSlots) > 1; if ($hasMultipleSlots) { if ($selectedSlot === '' || !isset($timeSlots[$selectedSlot])) { $slotKeys = array_keys($timeSlots); $selectedSlot = $slotKeys[0] ?? ''; } } else { $selectedSlot = ''; } $entriesToShow = $hasMultipleSlots && $selectedSlot !== '' ? ($timeSlots[$selectedSlot] ?? []) : $selectedEntries; $formatMonthLabel = static function (string $monthValue, string $langCode): string { if (!preg_match('/^(\\d{4})-(\\d{2})$/', $monthValue, $m)) { return $monthValue; } $year = $m[1]; $month = (int)$m[2]; $monthsPl = [ 1 => 'styczeń', 2 => 'luty', 3 => 'marzec', 4 => 'kwiecień', 5 => 'maj', 6 => 'czerwiec', 7 => 'lipiec', 8 => 'sierpień', 9 => 'wrzesień', 10 => 'październik', 11 => 'listopad', 12 => 'grudzień', ]; $monthsEn = [ 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 = ($langCode === 'pl') ? $monthsPl : $monthsEn; $name = $list[$month] ?? $monthValue; return $name . ' ' . $year; }; $formatFullDate = static function (string $dateValue, string $langCode): string { if (!preg_match('/^(\\d{4})-(\\d{2})-(\\d{2})$/', $dateValue, $m)) { return $dateValue; } $year = (int)$m[1]; $month = (int)$m[2]; $day = (int)$m[3]; $monthsPl = [ 1 => 'stycznia', 2 => 'lutego', 3 => 'marca', 4 => 'kwietnia', 5 => 'maja', 6 => 'czerwca', 7 => 'lipca', 8 => 'sierpnia', 9 => 'września', 10 => 'października', 11 => 'listopada', 12 => 'grudnia', ]; if ($langCode === 'pl') { $monthName = $monthsPl[$month] ?? $m[2]; return $day . ' ' . $monthName . ' ' . $year; } $dt = DateTime::createFromFormat('Y-m-d', $dateValue); return $dt instanceof DateTime ? $dt->format('F j, Y') : $dateValue; }; $formatTimeLabel = static function (string $timeValue): string { if (preg_match('/^(\\d{2}:\\d{2})/', $timeValue, $m)) { return $m[1]; } return $timeValue; }; $monthNavTargets = static function (array $availableMonths, string $selectedMonth): array { $prev = ''; $next = ''; $idx = array_search($selectedMonth, $availableMonths, true); if ($idx !== false) { if ($idx < count($availableMonths) - 1) { $prev = $availableMonths[$idx + 1]; } if ($idx > 0) { $next = $availableMonths[$idx - 1]; } } return ['prev' => $prev, 'next' => $next]; }; $calendarWeekdays = static function (string $langCode): array { if ($langCode === 'pl') { return ['Pn', 'Wt', 'Śr', 'Cz', 'Pt', 'So', 'Nd']; } return ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; }; $langCode = $ctx->daUser->language(); $selectedDateLabel = $selectedDate !== '' ? $formatFullDate($selectedDate, $langCode) : ''; $userBackupsByDate = $queue->listUserBackupsByDate(500); $userAvailableDates = array_keys($userBackupsByDate); $userAvailableMonths = []; foreach ($userAvailableDates as $dateKey) { if (strlen($dateKey) >= 7) { $userAvailableMonths[substr($dateKey, 0, 7)] = true; } } $userAvailableMonths = array_keys($userAvailableMonths); rsort($userAvailableMonths, SORT_STRING); $userSelectedMonth = trim($ctx->postString('user_month', $ctx->queryString('user_month', ''))); if ($userSelectedMonth === '' || (!empty($userAvailableMonths) && !in_array($userSelectedMonth, $userAvailableMonths, true))) { $userSelectedMonth = $userAvailableMonths[0] ?? date('Y-m'); } $userSelectedDate = trim($ctx->postString('user_date', $ctx->queryString('user_date'))); $userMonthRequested = ($ctx->postString('user_month', '') !== '' || $ctx->queryString('user_month', '') !== ''); $userDateRequested = ($ctx->postString('user_date', '') !== '' || $ctx->queryString('user_date', '') !== ''); if (!$userMonthRequested && !$userDateRequested && isset($userBackupsByDate[$today])) { $userSelectedMonth = substr($today, 0, 7); $userSelectedDate = $today; } if ($userSelectedDate === '' || !isset($userBackupsByDate[$userSelectedDate]) || substr($userSelectedDate, 0, 7) !== $userSelectedMonth) { $userSelectedDate = ''; foreach ($userAvailableDates as $dayKey) { if (substr($dayKey, 0, 7) === $userSelectedMonth) { $userSelectedDate = $dayKey; break; } } } $userSelectedEntries = ($userSelectedDate !== '' && isset($userBackupsByDate[$userSelectedDate])) ? $userBackupsByDate[$userSelectedDate] : []; $userSelectedSlot = trim($ctx->postString('user_slot', $ctx->queryString('user_slot', ''))); $userTimeSlots = []; foreach ($userSelectedEntries as $entry) { $timeLabel = trim((string)($entry['time'] ?? '')); $mtime = (int)($entry['mtime'] ?? 0); if ($timeLabel === '' && $mtime > 0) { $timeLabel = date('H:i:s', $mtime); } if ($timeLabel === '') { $timeLabel = '00:00:00'; } if (!isset($userTimeSlots[$timeLabel])) { $userTimeSlots[$timeLabel] = []; } $userTimeSlots[$timeLabel][] = $entry; } $userHasMultipleSlots = count($userTimeSlots) > 1; if ($userHasMultipleSlots) { if ($userSelectedSlot === '' || !isset($userTimeSlots[$userSelectedSlot])) { $slotKeys = array_keys($userTimeSlots); $userSelectedSlot = $slotKeys[0] ?? ''; } } else { $userSelectedSlot = ''; } $userEntriesToShow = $userHasMultipleSlots && $userSelectedSlot !== '' ? ($userTimeSlots[$userSelectedSlot] ?? []) : $userSelectedEntries; $userSelectedDateLabel = $userSelectedDate !== '' ? $formatFullDate($userSelectedDate, $langCode) : ''; $jobs = $queue->listJobsForCurrentUser(500); $tabs = ''; $tabs .= '' . $et('Brak backupów HITME.PL dla wybranej daty.') . '
'; return $html; } if ($selectedDateLabel !== '') { $html .= '| ' . $et('Data i godzina') . ' | ' . $et('Baza źródłowa') . ' | '; if ($showTargetColumn) { $header .= ''; } $header .= '' . $et('Plik') . ' | ' . $et('Rozmiar') . ' | ' . $et('Akcja') . ' | '; $html .= '
|---|
' . $et('Brak backupów użytkownika dla wybranej daty.') . '
'; return $html; } if ($userSelectedDateLabel !== '') { $html .= '| ' . $et('Nazwa bazy danych') . ' | ' . $et('Akcje') . ' |
|---|
' . AppContext::e($tr('Wskaż plik `.sql`, `.gz` lub `.sql.gz` z komputera albo podaj ścieżkę pliku na serwerze (w katalogu `/home/{user}`).', ['user' => $daUser])) . '
'; $fileSection .= ''; $fileSection .= '| ' . $et('Data rozpoczęcia') . ' | ' . $et('Nazwa bazy') . ' | ' . $et('Rodzaj operacji') . ' | ' . $et('Typ przywracania') . ' | ' . $et('Status') . ' | ' . $et('Akcja') . ' |
|---|