t('Wybierz plik'); $close = $ctx->t('Zamknij'); $up = $ctx->t('W górę'); $newDir = $ctx->t('Nowy katalog'); $create = $ctx->t('Utwórz'); $cancel = $ctx->t('Anuluj'); $choose = $ctx->t('Wybierz'); $html = ''; $html .= ''; return $html; } public static function handleDirList(AppContext $ctx, BackupQueueService $queue, string $rootPath): bool { $action = strtolower($ctx->queryString('action')); if ($action !== 'dir_list') { return false; } while (ob_get_level() > 0) { @ob_end_clean(); } $base = self::normalizeBase($rootPath); $mode = strtolower($ctx->queryString('mode', 'dir')) === 'file' ? 'file' : 'dir'; $requested = trim($ctx->queryString('path', $base)); if ($requested === '') { $requested = $base; } if ($requested !== '' && $requested[0] !== '/') { $requested = '/' . $requested; } $selected = ''; if ($mode === 'file' && $requested !== '') { $realReq = @realpath($requested); if ($realReq !== false && is_file($realReq)) { $selected = $realReq; $requested = dirname($realReq); } } [$resolved, $base, $ok] = self::resolvePickerPath($requested, $base); if (!$ok) { self::pickerOutput(['ok' => false, 'error' => 'Nieprawidłowa ścieżka.']); exit; } if ($base !== '/' && $selected !== '') { if (strpos($selected, $base . '/') !== 0 && $selected !== $base) { $selected = ''; } } $dirItems = []; $fileItems = []; $entries = @scandir($resolved); if (is_array($entries)) { foreach ($entries as $entry) { if ($entry === '.' || $entry === '..') { continue; } $full = $resolved . '/' . $entry; if (is_dir($full)) { $dirItems[] = $entry; } elseif ($mode === 'file' && is_file($full) && $queue->isAllowedSqlFile($full)) { $fileItems[] = $entry; } } } natcasesort($dirItems); natcasesort($fileItems); $items = []; foreach ($dirItems as $name) { $items[] = ['name' => $name, 'type' => 'dir']; } foreach ($fileItems as $name) { $items[] = ['name' => $name, 'type' => 'file']; } self::pickerOutput([ 'ok' => true, 'path' => $resolved, 'base' => $base, 'items' => $items, 'selected' => $selected, ]); exit; } public static function handleDirCreate(AppContext $ctx, string $rootPath, string $daUser): bool { $action = strtolower($ctx->queryString('action')); if ($action !== 'dir_create') { return false; } while (ob_get_level() > 0) { @ob_end_clean(); } $base = self::normalizeBase($rootPath); $parentReq = trim($ctx->queryString('path', $base)); if ($parentReq !== '' && $parentReq[0] !== '/') { $parentReq = '/' . $parentReq; } [$parent, $base, $ok] = self::resolvePickerPath($parentReq, $base); $name = trim($ctx->queryString('name')); if ($name === '' || $name === '.' || $name === '..' || strpos($name, '/') !== false || strpos($name, '\\') !== false) { self::pickerOutput(['ok' => false, 'error' => 'invalid_name']); exit; } if (!$ok || !is_dir($parent)) { self::pickerOutput(['ok' => false, 'error' => 'invalid_parent']); exit; } $newPath = rtrim($parent, '/') . '/' . $name; if ($base !== '/' && strpos($newPath, $base . '/') !== 0 && $newPath !== $base) { self::pickerOutput(['ok' => false, 'error' => 'outside_base']); exit; } if (file_exists($newPath)) { self::pickerOutput(['ok' => false, 'error' => 'exists']); exit; } if (!@mkdir($newPath, 0700, true)) { self::pickerOutput(['ok' => false, 'error' => 'mkdir_failed']); exit; } @chown($newPath, $daUser); @chgrp($newPath, $daUser); self::pickerOutput(['ok' => true, 'path' => $parent, 'base' => $base, 'created' => $newPath]); exit; } /** * @param array $payload */ private static function pickerOutput(array $payload): void { $json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE); if ($json === false) { $json = json_encode(['ok' => false, 'error' => 'json_encode_failed'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); } $marker = '__DA_MYSQL_PICKER_JSON__'; header('Content-Type: text/plain; charset=UTF-8'); echo $marker . "\n" . $json . "\n" . $marker; } private static function normalizeBase(string $base): string { $base = trim($base); if ($base === '') { return '/'; } if ($base[0] !== '/') { $base = '/' . $base; } if ($base !== '/' && substr($base, -1) === '/') { $base = rtrim($base, '/'); } return $base === '' ? '/' : $base; } /** * @return array{0:string,1:string,2:bool} */ private static function resolvePickerPath(string $requested, string $base): array { $base = self::normalizeBase($base); $candidate = trim($requested); if ($candidate === '') { $candidate = $base; } if ($candidate[0] !== '/') { $candidate = '/' . $candidate; } $resolved = @realpath($candidate); if ($resolved === false || !is_dir($resolved)) { $resolved = @realpath($base); } if ($resolved === false || !is_dir($resolved)) { return [$base, $base, false]; } if ($base !== '/' && strpos($resolved, $base . '/') !== 0 && $resolved !== $base) { $resolved = @realpath($base); } if ($resolved === false || !is_dir($resolved)) { return [$base, $base, false]; } return [$resolved, $base, true]; } }