- NasFileManager.php - filesystem helper (browse, upload, download, delete, rename, mkdir) - project-files.php API - CRUD operace nad soubory projektu - ProjectFileManager.jsx - React komponenta v detailu projektu - Automaticke vytvoreni slozky pri vytvoreni projektu (rucne i z objednavky) - Prejmenovani slozky pri zmene nazvu projektu - Checkbox "Smazat i soubory na disku" pri mazani projektu/objednavky - Path traversal ochrana, MIME validace, blocklist nebezpecnych typu - Bily spinner v primary tlacitkach, ConfirmModal message jako div - Case-insensitive rename fix pro Windows filesystem Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
237 lines
6.1 KiB
PHP
237 lines
6.1 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Project Files API handlery
|
|
*
|
|
* Vsechny operace se soubory projektu na NAS.
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* @param array<string, mixed> $authData
|
|
*/
|
|
function handleFilesList(PDO $pdo, array $authData): void
|
|
{
|
|
$projectId = (int) ($_GET['project_id'] ?? 0);
|
|
if (!$projectId) {
|
|
errorResponse('ID projektu je povinné');
|
|
}
|
|
|
|
$project = getProjectForFiles($pdo, $projectId);
|
|
$path = $_GET['path'] ?? '';
|
|
|
|
$fm = new NasFileManager();
|
|
if (!$fm->isConfigured()) {
|
|
errorResponse('Souborový systém není nakonfigurován', 500);
|
|
}
|
|
|
|
$result = $fm->listFiles($project['project_number'], $path);
|
|
if ($result === null) {
|
|
errorResponse('Složka nebyla nalezena', 404);
|
|
}
|
|
|
|
$result['project_number'] = $project['project_number'];
|
|
$result['folder_exists'] = true;
|
|
successResponse($result);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $authData
|
|
*/
|
|
function handleFilesDownload(PDO $pdo, array $authData): void
|
|
{
|
|
$projectId = (int) ($_GET['project_id'] ?? 0);
|
|
if (!$projectId) {
|
|
errorResponse('ID projektu je povinné');
|
|
}
|
|
|
|
$project = getProjectForFiles($pdo, $projectId);
|
|
$path = $_GET['path'] ?? '';
|
|
if ($path === '') {
|
|
errorResponse('Cesta k souboru je povinná');
|
|
}
|
|
|
|
$fm = new NasFileManager();
|
|
$error = $fm->downloadFile($project['project_number'], $path);
|
|
if ($error !== null) {
|
|
errorResponse($error, 404);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $authData
|
|
*/
|
|
function handleFilesUpload(PDO $pdo, array $authData): void
|
|
{
|
|
$projectId = (int) ($_GET['project_id'] ?? 0);
|
|
if (!$projectId) {
|
|
errorResponse('ID projektu je povinné');
|
|
}
|
|
|
|
$project = getProjectForFiles($pdo, $projectId);
|
|
$path = $_GET['path'] ?? '';
|
|
|
|
$fm = new NasFileManager();
|
|
if (!$fm->isConfigured()) {
|
|
errorResponse('Souborový systém není nakonfigurován', 500);
|
|
}
|
|
|
|
// Vytvorit slozku pokud neexistuje
|
|
if (!$fm->projectFolderExists($project['project_number'])) {
|
|
$fm->createProjectFolder($project['project_number'], $project['name']);
|
|
}
|
|
|
|
if (empty($_FILES['file'])) {
|
|
errorResponse('Nebyl nahrán žádný soubor');
|
|
}
|
|
|
|
$error = $fm->uploadFile($project['project_number'], $path, $_FILES['file']);
|
|
if ($error !== null) {
|
|
errorResponse($error);
|
|
}
|
|
|
|
AuditLog::logCreate(
|
|
'project_file',
|
|
$projectId,
|
|
['file' => $_FILES['file']['name'] ?? '', 'path' => $path],
|
|
"Nahrán soubor do projektu '{$project['project_number']}'"
|
|
);
|
|
|
|
successResponse(null, 'Soubor byl nahrán');
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $authData
|
|
*/
|
|
function handleFilesCreateFolder(PDO $pdo, array $authData): void
|
|
{
|
|
$projectId = (int) ($_GET['project_id'] ?? 0);
|
|
if (!$projectId) {
|
|
errorResponse('ID projektu je povinné');
|
|
}
|
|
|
|
$project = getProjectForFiles($pdo, $projectId);
|
|
$input = getJsonInput();
|
|
$path = $input['path'] ?? '';
|
|
$folderName = trim($input['folder_name'] ?? '');
|
|
|
|
if ($folderName === '') {
|
|
errorResponse('Název složky je povinný');
|
|
}
|
|
if (mb_strlen($folderName) > 100) {
|
|
errorResponse('Název složky je příliš dlouhý (max 100 znaků)');
|
|
}
|
|
|
|
$fm = new NasFileManager();
|
|
if (!$fm->isConfigured()) {
|
|
errorResponse('Souborový systém není nakonfigurován', 500);
|
|
}
|
|
|
|
// Vytvorit projektovou slozku pokud neexistuje
|
|
if (!$fm->projectFolderExists($project['project_number'])) {
|
|
$fm->createProjectFolder($project['project_number'], $project['name']);
|
|
}
|
|
|
|
$error = $fm->createFolder($project['project_number'], $path, $folderName);
|
|
if ($error !== null) {
|
|
errorResponse($error);
|
|
}
|
|
|
|
AuditLog::logCreate(
|
|
'project_file',
|
|
$projectId,
|
|
['folder' => $folderName, 'path' => $path],
|
|
"Vytvořena složka '$folderName' v projektu '{$project['project_number']}'"
|
|
);
|
|
|
|
successResponse(null, 'Složka byla vytvořena');
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $authData
|
|
*/
|
|
function handleFilesMove(PDO $pdo, array $authData): void
|
|
{
|
|
$projectId = (int) ($_GET['project_id'] ?? 0);
|
|
if (!$projectId) {
|
|
errorResponse('ID projektu je povinné');
|
|
}
|
|
|
|
$project = getProjectForFiles($pdo, $projectId);
|
|
$input = getJsonInput();
|
|
$fromPath = $input['from_path'] ?? '';
|
|
$toPath = $input['to_path'] ?? '';
|
|
|
|
if ($fromPath === '' || $toPath === '') {
|
|
errorResponse('Zdrojová i cílová cesta jsou povinné');
|
|
}
|
|
|
|
$fm = new NasFileManager();
|
|
$error = $fm->moveItem($project['project_number'], $fromPath, $toPath);
|
|
if ($error !== null) {
|
|
errorResponse($error);
|
|
}
|
|
|
|
AuditLog::logUpdate(
|
|
'project_file',
|
|
$projectId,
|
|
['path' => $fromPath],
|
|
['path' => $toPath],
|
|
"Přesun/přejmenování v projektu '{$project['project_number']}'"
|
|
);
|
|
|
|
successResponse(null, 'Soubor byl přesunut');
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $authData
|
|
*/
|
|
function handleFilesDelete(PDO $pdo, array $authData): void
|
|
{
|
|
$projectId = (int) ($_GET['project_id'] ?? 0);
|
|
if (!$projectId) {
|
|
errorResponse('ID projektu je povinné');
|
|
}
|
|
|
|
$project = getProjectForFiles($pdo, $projectId);
|
|
$path = $_GET['path'] ?? '';
|
|
|
|
if ($path === '') {
|
|
errorResponse('Cesta k souboru je povinná');
|
|
}
|
|
|
|
$fm = new NasFileManager();
|
|
$error = $fm->deleteItem($project['project_number'], $path);
|
|
if ($error !== null) {
|
|
errorResponse($error);
|
|
}
|
|
|
|
AuditLog::logDelete(
|
|
'project_file',
|
|
$projectId,
|
|
['path' => $path],
|
|
"Smazán soubor/složka v projektu '{$project['project_number']}'"
|
|
);
|
|
|
|
successResponse(null, 'Soubor byl smazán');
|
|
}
|
|
|
|
/**
|
|
* Nacte projekt z DB pro file operace
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
function getProjectForFiles(PDO $pdo, int $projectId): array
|
|
{
|
|
$stmt = $pdo->prepare('SELECT id, project_number, name FROM projects WHERE id = ?');
|
|
$stmt->execute([$projectId]);
|
|
$project = $stmt->fetch();
|
|
|
|
if (!$project) {
|
|
errorResponse('Projekt nebyl nalezen', 404);
|
|
}
|
|
|
|
return $project;
|
|
}
|