$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`).') . '

', $ok, $errors ); return; } $jsonResponse = static function (array $payload, int $statusCode = 200): void { http_response_code($statusCode); header('Content-Type: text/plain; charset=UTF-8'); $json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if ($json === false) { $json = '{"ok":false,"error":"JSON encode failed"}'; } $marker = '__DA_POSTGRESQL_UPLOAD_JSON__'; echo $marker . "\n" . $json . "\n" . $marker; }; $requestAction = strtolower($ctx->queryString('action')); if ($ctx->isPost() && $requestAction === 'upload_blob') { $jsonResponse([ 'ok' => false, 'error' => $ctx->t('Tryb przesyłania został zaktualizowany. Odśwież stronę i spróbuj ponownie.'), ], 409); return; } $activeTab = strtolower($ctx->postString('active_tab', $ctx->queryString('tab', 'local'))); if (!in_array($activeTab, ['local', 'user', 'file'], true)) { $activeTab = 'local'; } $sourceFilePathValue = $ctx->postString('source_file_path'); if ($sourceFilePathValue === '') { $rawPath = $_POST['source_file_path'] ?? null; if (is_array($rawPath) && isset($rawPath[0])) { $sourceFilePathValue = trim((string)$rawPath[0]); } } if ($sourceFilePathValue === '') { $sourceFilePathValue = $ctx->queryString('source_file_path'); } $uploadedTmpPathValue = $ctx->postString('uploaded_tmp_path', $ctx->queryString('uploaded_tmp_path')); $uploadedOriginalNameValue = $ctx->postString('uploaded_original_name', $ctx->queryString('uploaded_original_name')); $sourceLocationMode = strtolower($ctx->postString('source_mode', $ctx->queryString('source_mode', 'local'))); if (!in_array($sourceLocationMode, ['local', 'server'], true)) { $sourceLocationMode = 'local'; } $allowRestoreOther = $ctx->settings->allowRestoreToOtherDatabase(); $restoreMode = strtolower($ctx->postString('restore_mode', $ctx->queryString('restore_mode', 'overwrite'))); if (!in_array($restoreMode, ['overwrite', 'new_db'], true)) { $restoreMode = 'overwrite'; } if (!$allowRestoreOther) { $restoreMode = 'overwrite'; } $restoreTargetDb = trim($ctx->postString('target_db', $ctx->queryString('target_db', ''))); if ($restoreTargetDb !== '' && strpos($restoreTargetDb, $prefix) !== 0) { $restoreTargetDb = $prefix . $restoreTargetDb; } $availableDbs = []; try { $availableDbs = array_map(static fn(array $db): string => (string)$db['name'], $ctx->pg->listDatabasesForUser($daUser, false)); } catch (Throwable $e) { $errors[] = $ctx->formatException($e); } if ($restoreTargetDb !== '' && !in_array($restoreTargetDb, $availableDbs, true)) { $restoreTargetDb = ''; } if ($restoreTargetDb === '' && !empty($availableDbs)) { $restoreTargetDb = $availableDbs[0]; } $renderTargetRadios = function (array $dbs, string $selected, string $name, array $exclude = [], bool $required = false, bool $disabled = false, string $idPrefix = '', string $formId = '') use ($et): string { $exclude = array_fill_keys($exclude, true); $filtered = []; foreach ($dbs as $dbName) { if (isset($exclude[$dbName])) { continue; } $filtered[] = $dbName; } if (empty($filtered)) { return '

' . $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 = '
'; foreach ($filtered as $idx => $dbName) { $id = 'restore-target-' . $safePrefix . '-' . $idx; $checked = ($dbName === $selected) ? ' checked' : ''; $req = ($required && $idx === 0) ? ' required' : ''; $dis = $disabled ? ' disabled' : ''; $formAttr = $formId !== '' ? ' form="' . AppContext::e($formId) . '"' : ''; $items .= ''; } $items .= '
'; return $items; }; $ensureDbExists = static function (AppContext $ctx, string $dbName): void { try { $ctx->pg->getDatabaseInfo($dbName); } catch (Throwable $e) { throw new RuntimeException('Wskazana docelowa baza danych nie istnieje. Utwórz ją ręcznie w DirectAdmin.'); } }; $formatBackupLabel = static function (array $entry): string { $date = (string)($entry['date'] ?? ''); $time = (string)($entry['time'] ?? ''); $raw = trim($date . ' ' . $time); if ($raw === '') { return '-'; } $ts = strtotime($raw); if ($ts === false) { return $raw; } return date('d-m-Y H:i:s', $ts); }; $displayFileName = static function (string $value): string { $name = basename(trim($value)); if ($name === '') { return ''; } if (preg_match('/^stream-[^_]+_(.+)$/', $name, $m) === 1) { $name = $m[1]; } if (preg_match('/^[A-Za-z]+-[0-9]{8,}-[0-9a-f]{6,}_(.+)$/', $name, $m) === 1) { $name = $m[1]; } return $name; }; $renderHiddenInputs = static function (array $fields): string { $out = ''; foreach ($fields as $name => $value) { if (!is_string($name)) { continue; } if (is_array($value)) { foreach ($value as $item) { if (is_array($item) || is_object($item)) { continue; } $out .= ''; } continue; } if (is_object($value)) { continue; } $out .= ''; } return $out; }; $renderConfirmOverlay = static function (string $bodyHtml, array $fields) use ($ctx, $et, $renderHiddenInputs): string { $hidden = $renderHiddenInputs($fields); $html = ''; $html .= '
'; $html .= '
'; $html .= '

' . $et('Potwierdzenie operacji') . '

'; $html .= $bodyHtml; $html .= '
'; $html .= $ctx->csrfField('upload_backup_submit'); $html .= $hidden; $html .= ''; $html .= '
'; $html .= '' . $et('Nie') . ''; $html .= ''; $html .= '
'; $html .= '
'; $html .= '
'; return $html; }; $confirmOverlay = ''; $findUserBackupEntry = static function (BackupQueueService $queue, string $relPath): ?array { $relPath = ltrim(trim($relPath), '/'); if ($relPath === '') { return null; } $all = $queue->listUserBackupsByDate(800); foreach ($all as $entries) { foreach ($entries as $entry) { if (!is_array($entry)) { continue; } if (($entry['rel_path'] ?? '') === $relPath) { return $entry; } } } return null; }; $streamUserBackup = static function (string $path): void { $downloadName = basename($path); $downloadName = preg_replace('/[^A-Za-z0-9._-]+/', '_', $downloadName) ?? 'backup.sql'; $contentType = 'application/octet-stream'; if (preg_match('/\.sql$/i', $downloadName)) { $contentType = 'application/sql'; } elseif (preg_match('/\.gz$/i', $downloadName)) { $contentType = 'application/gzip'; } clearstatcache(true, $path); $contentLength = @filesize($path); while (ob_get_level() > 0) { @ob_end_clean(); } if (function_exists('apache_setenv')) { @apache_setenv('no-gzip', '1'); } @ini_set('zlib.output_compression', '0'); @ini_set('output_buffering', '0'); header_remove('Content-Encoding'); header('Content-Type: ' . $contentType); header('Content-Disposition: attachment; filename="' . str_replace('"', '', $downloadName) . '"'); header('X-Content-Type-Options: nosniff'); header('Cache-Control: no-store, no-cache, must-revalidate'); header('Pragma: no-cache'); if (is_int($contentLength) && $contentLength > 0) { header('Content-Length: ' . (string)$contentLength); } @readfile($path); exit; }; if ($ctx->isPost()) { try { $ctx->requireCsrf('upload_backup_submit'); $action = $ctx->postString('form_action'); $restoreMode = strtolower($ctx->postString('restore_mode', $restoreMode)); if (!in_array($restoreMode, ['overwrite', 'new_db'], true)) { $restoreMode = 'overwrite'; } if (!$allowRestoreOther) { $restoreMode = 'overwrite'; } if ($action === 'restore_local') { $backupId = $ctx->postString('local_backup'); $entry = $queue->getLocalBackupEntry($backupId); $sourceDb = (string)($entry['db_name'] ?? ''); $backupLabel = $formatBackupLabel($entry); $confirmAction = $ctx->postString('confirm_action') === '1'; $targetDb = $sourceDb; if ($restoreMode === 'new_db') { $targetDb = NamePolicy::normalizeDatabaseName($ctx->postString('target_db'), $prefix); if ($sourceDb !== '' && $targetDb === $sourceDb) { throw new RuntimeException('Docelowa baza danych musi być inna niż źródłowa baza z backupu.'); } $ensureDbExists($ctx, $targetDb); } if (!$confirmAction) { $sourceHtml = '' . AppContext::e($sourceDb !== '' ? $sourceDb : $targetDb) . ''; $targetHtml = '' . AppContext::e($targetDb !== '' ? $targetDb : $sourceDb) . ''; $targetHtmlDanger = '' . AppContext::e($targetDb !== '' ? $targetDb : $sourceDb) . ''; $dateHtml = '' . AppContext::e($backupLabel) . ''; $overwriteHtml = '' . $et('nadpisując obecne dane') . ''; if ($restoreMode === 'new_db') { $line1 = $ctx->t( 'Czy na pewno chcesz przywrócić dane z kopii zapasowej bazy danych {source} z dn. {date} do innej bazy danych o nazwie {target}?', ['source' => $sourceHtml, 'date' => $dateHtml, '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] ); $confirmBody = '

' . $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 .= '
'; $tabs .= '' . $et('Backupy HITME.PL') . ''; $tabs .= '' . $et('Backupy użytkownika') . ''; $tabs .= '' . $et('Backupy z pliku') . ''; $tabs .= '
'; $restoreModeSection = ''; if ($activeTab === 'local' && $allowRestoreOther) { $restoreModeSection .= '
'; $restoreModeSection .= '

' . $et('Tryb przywracania') . '

'; $restoreModeSection .= '
'; $restoreModeSection .= ''; $restoreModeSection .= ''; $restoreModeSection .= '
'; $restoreModeSection .= '
'; } $renderRestorePicker = function () use ( $ctx, $selectedEntries, $entriesToShow, $hasMultipleSlots, $timeSlots, $selectedSlot, $selectedDateLabel, $selectedDate, $selectedMonth, $restoreMode, $allowRestoreOther, $availableDbs, $restoreTargetDb, $renderTargetRadios, $formatTimeLabel, $et ): string { $html = ''; if (empty($selectedEntries)) { $html .= '

' . $et('Brak backupów HITME.PL dla wybranej daty.') . '

'; return $html; } if ($selectedDateLabel !== '') { $html .= '
' . $et('Data:') . ' ' . AppContext::e($selectedDateLabel) . '
'; } if ($hasMultipleSlots) { $html .= '
'; $html .= ''; $html .= ''; $html .= ''; $html .= ''; $html .= '
' . $et('Wybierz godzinę backupu') . '
'; $html .= '
'; foreach ($timeSlots as $slotKey => $_entries) { $timeLabel = $formatTimeLabel($slotKey); $checked = ($slotKey === $selectedSlot) ? ' checked' : ''; $html .= ''; } $html .= '
'; } $showTargetColumn = $allowRestoreOther; $rows = ''; foreach ($entriesToShow as $entry) { $sourceDb = (string)($entry['source_db'] ?? ''); $formId = 'restore-local-' . preg_replace('/[^A-Za-z0-9_-]+/', '_', (string)$entry['id']); $timeLabel = $formatTimeLabel(trim((string)($entry['time'] ?? ''))); $dateLabel = $selectedDateLabel !== '' ? $selectedDateLabel : (string)$entry['date']; if ($hasMultipleSlots && $timeLabel !== '') { $dateLabel .= ' ' . $timeLabel; } $rows .= ''; $rows .= '' . AppContext::e(trim($dateLabel)) . ''; $rows .= '' . AppContext::e($sourceDb !== '' ? $sourceDb : '-') . ''; if ($showTargetColumn) { $rows .= ''; if ($sourceDb !== '') { $rows .= '
'; $rows .= $renderTargetRadios($availableDbs, $restoreTargetDb, 'target_db', [$sourceDb], $restoreMode === 'new_db', $restoreMode !== 'new_db', $entry['id'], $formId); $rows .= '
'; } else { $rows .= '' . $et('Nie można ustalić bazy z nazwy pliku.') . ''; } $rows .= ''; } $rows .= '' . AppContext::e($entry['file']) . ''; $rows .= '' . AppContext::e($entry['size']) . ''; $rows .= ''; $rows .= '
'; $rows .= $ctx->csrfField('upload_backup_submit'); $rows .= ''; $rows .= ''; $rows .= ''; $rows .= ''; if ($sourceDb !== '') { $rows .= ''; } else { $rows .= ''; } $rows .= '
'; $rows .= ''; $rows .= ''; } $html .= ''; $header = ''; if ($showTargetColumn) { $header .= ''; } $header .= ''; $html .= '' . $header . ''; $html .= '' . $rows . '
' . $et('Data i godzina') . '' . $et('Baza źródłowa') . '' . $et('Baza docelowa') . '' . $et('Plik') . '' . $et('Rozmiar') . '' . $et('Akcja') . '
'; return $html; }; $renderUserBackupPicker = function () use ( $ctx, $userSelectedEntries, $userEntriesToShow, $userHasMultipleSlots, $userTimeSlots, $userSelectedSlot, $userSelectedDateLabel, $userSelectedDate, $userSelectedMonth, $formatTimeLabel, $et ): string { $html = ''; if (empty($userSelectedEntries)) { $html .= '

' . $et('Brak backupów użytkownika dla wybranej daty.') . '

'; return $html; } if ($userSelectedDateLabel !== '') { $html .= '
' . $et('Data:') . ' ' . AppContext::e($userSelectedDateLabel) . '
'; } if ($userHasMultipleSlots) { $html .= '
'; $html .= ''; $html .= ''; $html .= ''; $html .= '
' . $et('Wybierz godzinę backupu') . '
'; $html .= '
'; foreach ($userTimeSlots as $slotKey => $_entries) { $timeLabel = $formatTimeLabel($slotKey); $checked = ($slotKey === $userSelectedSlot) ? ' checked' : ''; $html .= ''; } $html .= '
'; } $rows = ''; foreach ($userEntriesToShow as $entry) { $jobId = (string)($entry['job_id'] ?? ''); $dbName = (string)($entry['db_name'] ?? ''); $hasFile = (bool)($entry['has_file'] ?? false); $relPath = (string)($entry['rel_path'] ?? ''); $overlayId = 'log-overlay-' . preg_replace('/[^A-Za-z0-9_-]+/', '-', $jobId); $rows .= ''; $rows .= '' . AppContext::e($dbName !== '' ? $dbName : '-') . ''; $rows .= ''; if ($hasFile) { $rows .= '
'; if ($jobId !== '') { $rows .= ''; } elseif ($relPath !== '') { $rows .= ''; } $rows .= ''; $rows .= '
'; } if ($jobId !== '') { $rows .= ''; } if ($hasFile) { $rows .= '
'; $rows .= $ctx->csrfField('upload_backup_submit'); $rows .= ''; $rows .= ''; if ($jobId !== '') { $rows .= ''; } if ($relPath !== '') { $rows .= ''; } $rows .= ''; $rows .= ''; $rows .= ''; $rows .= ''; $rows .= '
'; } $rows .= ''; $rows .= ''; } $html .= ''; $html .= ''; $html .= '' . $rows . '
' . $et('Nazwa bazy danych') . '' . $et('Akcje') . '
'; return $html; }; if ($ctx->queryString('action') === 'local_picker') { header('Content-Type: text/html; charset=UTF-8'); echo $renderRestorePicker(); return; } if ($ctx->queryString('action') === 'user_picker') { header('Content-Type: text/html; charset=UTF-8'); echo $renderUserBackupPicker(); return; } $localSection = ''; if ($activeTab === 'local') { $localSection .= '
'; $localSection .= '

' . $et('Backupy HITME.PL') . '

'; $localSection .= '
'; $localSection .= '
'; $localSection .= '

' . $et('Kalendarz backupów') . '

'; $nav = $monthNavTargets($availableMonths, $selectedMonth); $langCode = $ctx->daUser->language(); $localSection .= '
'; $localSection .= '
'; $localSection .= ''; $localSection .= ''; $localSection .= ''; $localSection .= ''; $localSection .= ''; $localSection .= '
'; $localSection .= '
' . AppContext::e($formatMonthLabel($selectedMonth, $langCode)) . '
'; $localSection .= '
'; $localSection .= ''; $localSection .= ''; $localSection .= ''; $localSection .= ''; $localSection .= ''; $localSection .= '
'; $localSection .= '
'; $monthTs = strtotime($selectedMonth . '-01'); if ($monthTs === false) { $monthTs = strtotime(date('Y-m-01')); } $calendarDays = (int)date('t', $monthTs); $calendarOffset = (int)date('N', $monthTs); $weekdays = $calendarWeekdays($langCode); $localSection .= '
'; $localSection .= ''; $localSection .= ''; $localSection .= ''; $localSection .= ''; $localSection .= ''; foreach ($weekdays as $dayLabel) { $localSection .= ''; } $localSection .= ''; $dayNum = 1; for ($row = 0; $row < 6; $row++) { $localSection .= ''; for ($col = 1; $col <= 7; $col++) { if (($row === 0 && $col < $calendarOffset) || $dayNum > $calendarDays) { $localSection .= ''; continue; } $dayKey = date('Y-m-', $monthTs) . str_pad((string)$dayNum, 2, '0', STR_PAD_LEFT); $hasBackup = isset($backupsByDate[$dayKey]); $isSelected = ($dayKey === $selectedDate); $localSection .= ''; $dayNum++; } $localSection .= ''; if ($dayNum > $calendarDays) { break; } } $localSection .= '
' . AppContext::e($dayLabel) . '
'; if ($hasBackup) { $localSection .= ''; } else { $localSection .= '' . AppContext::e((string)$dayNum) . ''; } $localSection .= '
'; $localSection .= '
'; $localSection .= '
'; $localSection .= '
'; $localSection .= $renderRestorePicker(); $localSection .= '
'; $localSection .= '
'; $localSection .= '
'; } $userSection = ''; if ($activeTab === 'user') { $userSection .= '
'; $userSection .= '

' . $et('Backupy użytkownika') . '

'; $userSection .= '
'; $userSection .= '
'; $userSection .= '

' . $et('Kalendarz backupów') . '

'; $userNav = $monthNavTargets($userAvailableMonths, $userSelectedMonth); $userSection .= '
'; $userSection .= '
'; $userSection .= ''; $userSection .= ''; $userSection .= ''; $userSection .= ''; $userSection .= '
'; $userSection .= '
' . AppContext::e($formatMonthLabel($userSelectedMonth, $langCode)) . '
'; $userSection .= '
'; $userSection .= ''; $userSection .= ''; $userSection .= ''; $userSection .= ''; $userSection .= '
'; $userSection .= '
'; $userMonthTs = strtotime($userSelectedMonth . '-01'); if ($userMonthTs === false) { $userMonthTs = strtotime(date('Y-m-01')); } $userCalendarDays = (int)date('t', $userMonthTs); $userCalendarOffset = (int)date('N', $userMonthTs); $weekdays = $calendarWeekdays($langCode); $userSection .= '
'; $userSection .= ''; $userSection .= ''; $userSection .= ''; $userSection .= ''; foreach ($weekdays as $dayLabel) { $userSection .= ''; } $userSection .= ''; $dayNum = 1; for ($row = 0; $row < 6; $row++) { $userSection .= ''; for ($col = 1; $col <= 7; $col++) { if (($row === 0 && $col < $userCalendarOffset) || $dayNum > $userCalendarDays) { $userSection .= ''; continue; } $dayKey = date('Y-m-', $userMonthTs) . str_pad((string)$dayNum, 2, '0', STR_PAD_LEFT); $hasBackup = isset($userBackupsByDate[$dayKey]); $isSelected = ($dayKey === $userSelectedDate); $userSection .= ''; $dayNum++; } $userSection .= ''; if ($dayNum > $userCalendarDays) { break; } } $userSection .= '
' . AppContext::e($dayLabel) . '
'; if ($hasBackup) { $userSection .= ''; } else { $userSection .= '' . AppContext::e((string)$dayNum) . ''; } $userSection .= '
'; $userSection .= '
'; $userSection .= '
'; $userSection .= '
'; $userSection .= $renderUserBackupPicker(); $userSection .= '
'; $userSection .= '
'; $userSection .= '
'; } $fileSection = ''; if ($activeTab === 'file') { $fileSection .= '
'; $fileSection .= '

' . $et('Backupy z pliku') . '

'; $fileSection .= '

' . 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 .= $ctx->csrfField('upload_backup_submit'); $fileSection .= ''; $fileSection .= ''; $fileSection .= '
'; $fileSection .= ''; $fileSection .= '
'; $fileSection .= ''; $fileSection .= ''; $fileSection .= '
'; $fileSection .= ''; $fileSection .= ''; $fileSection .= ''; $fileSection .= '
' . $et('Po dodaniu pliku zostanie on przesłany na serwer i dodany do kolejki przywracania.') . '
'; $fileSection .= ''; $fileSection .= '
'; $fileSection .= '
' . $renderTargetRadios($availableDbs, $restoreTargetDb, 'target_db', [], true, false, 'file') . '
'; $fileSection .= '
'; $fileSection .= '
'; $fileSection .= '
'; $fileSection .= '
'; $fileSection .= FilePicker::renderOverlay( $ctx, 'file-picker-overlay', '/home/' . $daUser, $ctx->url('file_picker.html'), 'source-file-path' ); } $statusRows = ''; $logOverlays = []; foreach ($jobs as $job) { $statusLabel = match ($job['status']) { 'pending' => $tr('Oczekuje'), 'processing' => $tr('W toku'), 'done_ok' => $tr('Zakończone OK'), 'done_fail' => $tr('Błąd'), 'done_cancel' => $tr('Anulowane'), default => $job['status'], }; $jobType = (string)($job['type'] ?? ''); $operationLabel = ($jobType === 'backup') ? $tr('Tworzenie kopii zapasowej') : $tr('Przywracanie kopii zapasowej'); $restoreModeRaw = strtolower(trim((string)($job['restore_mode'] ?? ''))); $restoreModeLabel = $jobType === 'restore' ? ($restoreModeRaw === 'new_db' ? $tr('Przywracanie do nowej bazy') : $tr('Przywracanie z nadpisaniem')) : $tr('Nie dotyczy'); $jobId = (string)($job['job_id'] ?? ''); $overlayId = 'log-overlay-' . preg_replace('/[^A-Za-z0-9_-]+/', '-', $jobId); $contentId = $overlayId . '-content'; $sourceDbName = trim((string)($job['source_db_name'] ?? '')); $targetDbName = (string)($job['db_name'] ?? '-'); $hasDistinctSourceTarget = ($jobType === 'restore' && $sourceDbName !== '' && $sourceDbName !== $targetDbName); $dbDisplay = $hasDistinctSourceTarget ? ($sourceDbName . ' → ' . $targetDbName) : $targetDbName; $logText = ''; try { $logText = $queue->readLogForCurrentUser($jobId); } catch (Throwable $e) { $logText = $ctx->formatException($e); } $statusRows .= ''; $statusRows .= '' . AppContext::e($job['start_ts'] > 0 ? date('Y-m-d H:i:s', $job['start_ts']) : '-') . ''; $statusRows .= '' . AppContext::e($dbDisplay) . ''; $statusRows .= '' . AppContext::e($operationLabel) . ''; $statusRows .= '' . AppContext::e($restoreModeLabel) . ''; $statusRows .= '' . AppContext::e($statusLabel) . ''; $statusRows .= ''; $statusRows .= ''; $statusRows .= ''; $statusRows .= ''; $logMeta = '
'; $logMeta .= '
' . $et('Data rozpoczęcia') . ': ' . AppContext::e($job['start_ts'] > 0 ? date('Y-m-d H:i:s', $job['start_ts']) : '-') . '
'; $logMeta .= '
' . $et('Rodzaj operacji') . ': ' . AppContext::e($operationLabel) . '
'; $logMeta .= '
' . $et('Typ przywracania') . ': ' . AppContext::e($restoreModeLabel) . '
'; if ($hasDistinctSourceTarget) { $logMeta .= '
' . $et('Baza źródłowa') . ': ' . AppContext::e($sourceDbName !== '' ? $sourceDbName : '-') . '
'; $logMeta .= '
' . $et('Baza docelowa') . ': ' . AppContext::e($targetDbName) . '
'; } else { $logMeta .= '
' . $et('Nazwa bazy') . ': ' . AppContext::e($targetDbName) . '
'; } $logMeta .= '
' . $et('Status') . ': ' . AppContext::e($statusLabel) . '
'; $logMeta .= '
'; $logOverlays[] = ''; } if ($statusRows === '') { $statusRows = '' . $et('Brak zadań backup/restore do wyświetlenia.') . ''; } $statusTable = '' . $statusRows . '
' . $et('Data rozpoczęcia') . '' . $et('Nazwa bazy') . '' . $et('Rodzaj operacji') . '' . $et('Typ przywracania') . '' . $et('Status') . '' . $et('Akcja') . '
'; $statusInner = $statusTable . (!empty($logOverlays) ? implode('', $logOverlays) : ''); if ($ctx->queryString('action') === 'status') { header('Content-Type: text/html; charset=UTF-8'); echo $statusInner; return; } $logsSection = ''; $logsSection .= '
'; $logsSection .= '

' . $et('Kolejka zadań i logi') . '

'; $logsSection .= '
' . $statusInner . '
'; $logsSection .= '
'; $body = $tabs . $restoreModeSection . $localSection . $userSection . $fileSection . $confirmOverlay; $shouldRefresh = false; foreach ($jobs as $job) { if (in_array($job['status'], ['pending', 'processing'], true)) { $shouldRefresh = true; break; } } if ($shouldRefresh) { $body .= '
'; } $ctx->renderPage('Przywróć Backup', $body, $ok, $errors, $logsSection); };