prepare('SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :t'); $stmt->execute([':t' => 'admin_task_files']); $cached = ((int)$stmt->fetchColumn() > 0); if (!$cached) { $pdo->exec( 'CREATE TABLE IF NOT EXISTS admin_task_files (' . 'id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,' . 'task_id BIGINT UNSIGNED NOT NULL,' . 'file_name VARCHAR(255) NOT NULL,' . 'file_mime VARCHAR(255) NULL,' . 'file_size BIGINT UNSIGNED NULL,' . 'file_data LONGBLOB NOT NULL,' . 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,' . 'PRIMARY KEY (id),' . 'KEY idx_task_id (task_id),' . 'KEY idx_created_at (created_at),' . 'CONSTRAINT fk_admin_task_files_task FOREIGN KEY (task_id) REFERENCES admin_tasks(id) ON DELETE CASCADE' . ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' ); $stmt->execute([':t' => 'admin_task_files']); $cached = ((int)$stmt->fetchColumn() > 0); } } catch (Throwable $e) { $cached = false; } return $cached; } function admin_task_comments_table_exists(PDO $pdo): bool { static $cached = null; if ($cached !== null) { return $cached; } try { $stmt = $pdo->prepare('SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :t'); $stmt->execute([':t' => 'admin_task_comments']); $cached = ((int)$stmt->fetchColumn() > 0); if (!$cached) { $pdo->exec( 'CREATE TABLE IF NOT EXISTS admin_task_comments (' . 'id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,' . 'task_id BIGINT UNSIGNED NOT NULL,' . 'user_id INT NOT NULL,' . 'username VARCHAR(100) NOT NULL,' . 'comment TEXT NOT NULL,' . 'created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,' . 'updated_at TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,' . 'PRIMARY KEY (id),' . 'KEY idx_task_id (task_id),' . 'KEY idx_created_at (created_at),' . 'KEY idx_user_id (user_id),' . 'CONSTRAINT fk_admin_task_comments_task FOREIGN KEY (task_id) REFERENCES admin_tasks(id) ON DELETE CASCADE' . ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' ); $stmt->execute([':t' => 'admin_task_comments']); $cached = ((int)$stmt->fetchColumn() > 0); } } catch (Throwable $e) { $cached = false; } return $cached; } function admin_task_get_comments_count_map(PDO $pdo, array $taskIds): array { $map = []; if (empty($taskIds) || !admin_task_comments_table_exists($pdo)) { return $map; } $taskIds = array_values(array_unique(array_map('intval', $taskIds))); $taskIds = array_values(array_filter($taskIds, static fn($id) => $id > 0)); if (empty($taskIds)) { return $map; } $ph = []; $bind = []; foreach ($taskIds as $i => $id) { $k = ':id' . $i; $ph[] = $k; $bind[$k] = $id; } $sql = 'SELECT task_id, COUNT(*) AS cnt FROM admin_task_comments WHERE task_id IN (' . implode(', ', $ph) . ') GROUP BY task_id'; $stmt = $pdo->prepare($sql); foreach ($bind as $k => $v) { $stmt->bindValue($k, $v, PDO::PARAM_INT); } $stmt->execute(); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $r) { $taskId = (int)($r['task_id'] ?? 0); if ($taskId > 0) { $map[$taskId] = (int)($r['cnt'] ?? 0); } } return $map; } function admin_task_assert_exists(PDO $pdo, int $taskId): void { $stmt = $pdo->prepare('SELECT id FROM admin_tasks WHERE id = :id LIMIT 1'); $stmt->execute([':id' => $taskId]); if (!(bool)$stmt->fetchColumn()) { admin_json_error('Nie znaleziono notatki', 404); } } function admin_task_list_comments(PDO $pdo, int $taskId): array { if (!admin_task_comments_table_exists($pdo)) { return []; } $stmt = $pdo->prepare( 'SELECT id, task_id, user_id, username, comment, created_at, updated_at ' . 'FROM admin_task_comments WHERE task_id = :task_id ORDER BY id ASC' ); $stmt->execute([':task_id' => $taskId]); return $stmt->fetchAll(PDO::FETCH_ASSOC) ?: []; } function admin_task_collect_uploads(int $maxFileBytes): array { $uploads = []; $uploadErrorToMessage = static function (int $err): string { if ($err === UPLOAD_ERR_INI_SIZE || $err === UPLOAD_ERR_FORM_SIZE) { return 'Plik przekracza limit uploadu serwera PHP (sprawdź upload_max_filesize i post_max_size).'; } if ($err === UPLOAD_ERR_PARTIAL) { return 'Plik został wysłany tylko częściowo.'; } if ($err === UPLOAD_ERR_NO_TMP_DIR) { return 'Brak katalogu tymczasowego na serwerze.'; } if ($err === UPLOAD_ERR_CANT_WRITE) { return 'Serwer nie może zapisać pliku na dysk.'; } if ($err === UPLOAD_ERR_EXTENSION) { return 'Upload został zatrzymany przez rozszerzenie PHP.'; } return 'Błąd uploadu pliku.'; }; $append = static function ($name, $type, $size, $tmpName, $error) use (&$uploads, $maxFileBytes, $uploadErrorToMessage): void { $err = isset($error) ? (int)$error : UPLOAD_ERR_NO_FILE; if ($err === UPLOAD_ERR_NO_FILE) { return; } if ($err !== UPLOAD_ERR_OK) { admin_json_error($uploadErrorToMessage($err) . ' (kod: ' . $err . ')', 422); } $actualSize = isset($size) ? (int)$size : 0; if ($actualSize > $maxFileBytes) { $mb = (int)round($maxFileBytes / 1024 / 1024); admin_json_error('Każdy załącznik może mieć maksymalnie ' . $mb . ' MB', 422); } $tmp = (string)($tmpName ?? ''); if ($tmp === '' || !is_uploaded_file($tmp)) { admin_json_error('Nieprawidłowy upload pliku', 422); } $blob = file_get_contents($tmp); if ($blob === false) { admin_json_error('Nie udało się odczytać pliku', 500); } $uploads[] = [ 'file_name' => (string)($name ?? 'plik'), 'file_mime' => (string)($type ?? 'application/octet-stream'), 'file_size' => isset($size) ? (int)$size : null, 'file_data' => $blob, ]; }; $fields = ['files', 'file']; foreach ($fields as $field) { if (empty($_FILES[$field]) || !is_array($_FILES[$field])) { continue; } $upload = $_FILES[$field]; $isMulti = isset($upload['name']) && is_array($upload['name']); if ($isMulti) { $count = count($upload['name']); for ($i = 0; $i < $count; $i++) { $append( $upload['name'][$i] ?? null, $upload['type'][$i] ?? null, $upload['size'][$i] ?? null, $upload['tmp_name'][$i] ?? null, $upload['error'][$i] ?? UPLOAD_ERR_NO_FILE ); } } else { $append( $upload['name'] ?? null, $upload['type'] ?? null, $upload['size'] ?? null, $upload['tmp_name'] ?? null, $upload['error'] ?? UPLOAD_ERR_NO_FILE ); } } return $uploads; } function admin_task_count_current_attachments(PDO $pdo, int $taskId): array { $legacyCount = 0; $modernCount = 0; $stmt = $pdo->prepare('SELECT (file_name IS NOT NULL AND file_name <> \'\') AS has_file FROM admin_tasks WHERE id = :id LIMIT 1'); $stmt->execute([':id' => $taskId]); $row = $stmt->fetch(PDO::FETCH_ASSOC); if ($row && (int)($row['has_file'] ?? 0) === 1) { $legacyCount = 1; } if (admin_task_files_table_exists($pdo)) { $stmt = $pdo->prepare('SELECT COUNT(*) FROM admin_task_files WHERE task_id = :id'); $stmt->execute([':id' => $taskId]); $modernCount = (int)$stmt->fetchColumn(); } return [ 'legacy' => $legacyCount, 'modern' => $modernCount, 'total' => $legacyCount + $modernCount, ]; } function admin_task_parse_delete_file_ids($value): array { if ($value === null) { return []; } if (is_string($value)) { $value = trim($value); if ($value === '') { return []; } $value = explode(',', $value); } if (!is_array($value)) { return []; } $ids = []; foreach ($value as $item) { $id = (int)$item; if ($id > 0) { $ids[] = $id; } } return array_values(array_unique($ids)); } function admin_task_insert_files(PDO $pdo, int $taskId, array $uploads): void { if (empty($uploads)) { return; } $stmt = $pdo->prepare( 'INSERT INTO admin_task_files (task_id, file_name, file_mime, file_size, file_data) ' . 'VALUES (:task_id, :file_name, :file_mime, :file_size, :file_data)' ); foreach ($uploads as $file) { $stmt->bindValue(':task_id', $taskId, PDO::PARAM_INT); $stmt->bindValue(':file_name', (string)$file['file_name'], PDO::PARAM_STR); $stmt->bindValue(':file_mime', (string)$file['file_mime'], PDO::PARAM_STR); $stmt->bindValue(':file_size', $file['file_size'] !== null ? (int)$file['file_size'] : null, $file['file_size'] !== null ? PDO::PARAM_INT : PDO::PARAM_NULL); $stmt->bindValue(':file_data', $file['file_data'], PDO::PARAM_LOB); $stmt->execute(); } } function admin_task_get_attachments_by_task(PDO $pdo, array $taskIds): array { $map = []; if (empty($taskIds) || !admin_task_files_table_exists($pdo)) { return $map; } $taskIds = array_values(array_unique(array_map('intval', $taskIds))); $taskIds = array_values(array_filter($taskIds, static fn($id) => $id > 0)); if (empty($taskIds)) { return $map; } $placeholders = []; $params = []; foreach ($taskIds as $i => $id) { $k = ':id' . $i; $placeholders[] = $k; $params[$k] = $id; } $sql = 'SELECT id, task_id, file_name, file_mime, file_size FROM admin_task_files ' . 'WHERE task_id IN (' . implode(', ', $placeholders) . ') ORDER BY id ASC'; $stmt = $pdo->prepare($sql); foreach ($params as $k => $v) { $stmt->bindValue($k, $v, PDO::PARAM_INT); } $stmt->execute(); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as $r) { $taskId = (int)($r['task_id'] ?? 0); if ($taskId <= 0) { continue; } if (!isset($map[$taskId])) { $map[$taskId] = []; } $fileId = (int)($r['id'] ?? 0); $map[$taskId][] = [ 'id' => $fileId, 'name' => (string)($r['file_name'] ?? ''), 'mime' => (string)($r['file_mime'] ?? ''), 'size' => isset($r['file_size']) ? (int)$r['file_size'] : null, 'download_url' => '/api/admin_task_file.php?file_id=' . $fileId, ]; } return $map; } if ($method === 'GET') { $limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 50; $limit = max(1, min(200, $limit)); try { $stmt = $pdo->prepare( 'SELECT id, title, description, created_by, created_by_username, created_at, updated_at, ' . 'is_done, done_at, done_by, done_by_username, ' . '(file_name IS NOT NULL AND file_name <> \'\') AS has_file, file_name, file_mime, file_size ' . 'FROM admin_tasks ' . 'ORDER BY id DESC ' . 'LIMIT :limit' ); $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); $stmt->execute(); $rows = $stmt->fetchAll(PDO::FETCH_ASSOC); $taskIds = []; foreach ($rows as $r) { $taskIds[] = (int)($r['id'] ?? 0); } $attachmentsMap = admin_task_get_attachments_by_task($pdo, $taskIds); $commentsCountMap = admin_task_get_comments_count_map($pdo, $taskIds); foreach ($rows as &$r) { $taskId = (int)($r['id'] ?? 0); $attachments = $attachmentsMap[$taskId] ?? []; if (!empty($r['file_name'])) { $attachments[] = [ 'id' => null, 'name' => (string)$r['file_name'], 'mime' => (string)($r['file_mime'] ?? ''), 'size' => isset($r['file_size']) ? (int)$r['file_size'] : null, 'download_url' => '/api/admin_task_file.php?id=' . $taskId, 'legacy' => true, ]; } $r['attachments'] = $attachments; $r['attachments_count'] = count($attachments); $r['has_file'] = $r['attachments_count'] > 0; $r['comments_count'] = (int)($commentsCountMap[$taskId] ?? 0); if ($r['has_file'] && !empty($attachments[0])) { $r['file_name'] = (string)($attachments[0]['name'] ?? $r['file_name']); $r['file_mime'] = (string)($attachments[0]['mime'] ?? $r['file_mime']); $r['file_size'] = isset($attachments[0]['size']) ? (int)$attachments[0]['size'] : $r['file_size']; } $r['is_done'] = (bool)((int)($r['is_done'] ?? 0)); $r['done_by'] = isset($r['done_by']) ? (int)$r['done_by'] : null; } unset($r); admin_json_response([ 'success' => true, 'data' => $rows, 'count' => count($rows), ]); } catch (Throwable $e) { $msg = (string)$e->getMessage(); $sqlState = ($e instanceof PDOException) ? (string)($e->getCode() ?? '') : ''; $isSchemaProblem = false; // Common MySQL/PDO signals: // - SQLSTATE[42S22]: Column not found (new columns not installed) // - SQLSTATE[42S02]: Base table or view not found if (stripos($msg, 'Unknown column') !== false) $isSchemaProblem = true; if (stripos($msg, 'Base table or view not found') !== false) $isSchemaProblem = true; if ($sqlState === '42S22' || $sqlState === '42S02') $isSchemaProblem = true; if ($isSchemaProblem) { admin_json_error('Baza danych nie ma jeszcze aktualnych kolumn/tabel dla notatek. Uruchom /administration/install_notes_chat.php i odśwież Dashboard.', 500); } admin_json_error('Błąd pobierania notatek', 500); } } if ($method === 'POST') { $contentType = (string)($_SERVER['CONTENT_TYPE'] ?? ''); // 1) JSON actions: update/delete/toggle_done if (stripos($contentType, 'application/json') !== false) { $body = admin_read_json_body(); $action = isset($body['action']) ? (string)$body['action'] : ''; $id = isset($body['id']) ? (int)$body['id'] : 0; $taskId = isset($body['task_id']) ? (int)$body['task_id'] : 0; $commentId = isset($body['comment_id']) ? (int)$body['comment_id'] : 0; if ($action === '') { admin_json_error('Nieprawidłowe żądanie (action)', 422); } if (in_array($action, ['delete', 'toggle_done', 'update'], true) && $id <= 0) { admin_json_error('Nieprawidłowe żądanie (id)', 422); } if (in_array($action, ['list_comments', 'add_comment'], true) && $taskId <= 0) { admin_json_error('Nieprawidłowe żądanie (task_id)', 422); } if ($action === 'delete_comment' && $commentId <= 0) { admin_json_error('Nieprawidłowe żądanie (comment_id)', 422); } try { if ($action === 'delete') { $stmt = $pdo->prepare('DELETE FROM admin_tasks WHERE id = :id'); $stmt->execute([':id' => $id]); admin_json_response(['success' => true]); } if ($action === 'toggle_done') { $stmt = $pdo->prepare('SELECT is_done FROM admin_tasks WHERE id = :id'); $stmt->execute([':id' => $id]); $cur = $stmt->fetch(PDO::FETCH_ASSOC); if (!$cur) { admin_json_error('Nie znaleziono notatki', 404); } $currentDone = (bool)((int)($cur['is_done'] ?? 0)); $desired = null; if (array_key_exists('is_done', $body)) { $desired = (bool)$body['is_done']; } $newDone = $desired !== null ? $desired : !$currentDone; if ($newDone) { $up = $pdo->prepare( 'UPDATE admin_tasks ' . 'SET is_done = 1, done_at = CURRENT_TIMESTAMP, done_by = :uid, done_by_username = :u ' . 'WHERE id = :id' ); $up->execute([ ':uid' => (int)$auth['user_id'], ':u' => (string)$auth['username'], ':id' => $id, ]); } else { $up = $pdo->prepare( 'UPDATE admin_tasks ' . 'SET is_done = 0, done_at = NULL, done_by = NULL, done_by_username = NULL ' . 'WHERE id = :id' ); $up->execute([':id' => $id]); } admin_json_response(['success' => true, 'is_done' => (bool)$newDone]); } if ($action === 'update') { $stmt = $pdo->prepare('SELECT title, description FROM admin_tasks WHERE id = :id'); $stmt->execute([':id' => $id]); $cur = $stmt->fetch(PDO::FETCH_ASSOC); if (!$cur) { admin_json_error('Nie znaleziono notatki', 404); } $title = array_key_exists('title', $body) ? trim((string)$body['title']) : (string)($cur['title'] ?? ''); $description = array_key_exists('description', $body) ? trim((string)$body['description']) : (string)($cur['description'] ?? ''); if ($title === '') { admin_json_error('Tytuł jest wymagany', 422); } if (mb_strlen($title) > $ADMIN_TASK_TITLE_MAX) { admin_json_error('Tytuł jest zbyt długi (max ' . $ADMIN_TASK_TITLE_MAX . ' znaków)', 422); } if ($description !== '' && mb_strlen($description) > $ADMIN_TASK_DESC_MAX) { admin_json_error('Opis jest zbyt długi (max ' . $ADMIN_TASK_DESC_MAX . ' znaków)', 422); } $up = $pdo->prepare('UPDATE admin_tasks SET title = :title, description = :description WHERE id = :id'); $up->bindValue(':title', $title, PDO::PARAM_STR); $up->bindValue(':description', $description !== '' ? $description : null, $description !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL); $up->bindValue(':id', $id, PDO::PARAM_INT); $up->execute(); admin_json_response(['success' => true]); } if ($action === 'list_comments') { admin_task_assert_exists($pdo, $taskId); $comments = admin_task_list_comments($pdo, $taskId); admin_json_response(['success' => true, 'data' => $comments]); } if ($action === 'add_comment') { admin_task_assert_exists($pdo, $taskId); $comment = trim((string)($body['comment'] ?? '')); if ($comment === '') { admin_json_error('Treść komentarza jest wymagana', 422); } if (mb_strlen($comment) > $ADMIN_TASK_COMMENT_MAX) { admin_json_error('Komentarz jest zbyt długi (max ' . $ADMIN_TASK_COMMENT_MAX . ' znaków)', 422); } if (!admin_task_comments_table_exists($pdo)) { admin_json_error('Brak tabeli komentarzy tasków', 500); } $ins = $pdo->prepare( 'INSERT INTO admin_task_comments (task_id, user_id, username, comment) ' . 'VALUES (:task_id, :user_id, :username, :comment)' ); $ins->execute([ ':task_id' => $taskId, ':user_id' => (int)$auth['user_id'], ':username' => (string)$auth['username'], ':comment' => $comment, ]); admin_json_response(['success' => true, 'id' => (int)$pdo->lastInsertId()], 201); } if ($action === 'delete_comment') { if (!admin_task_comments_table_exists($pdo)) { admin_json_error('Brak tabeli komentarzy tasków', 500); } $del = $pdo->prepare('DELETE FROM admin_task_comments WHERE id = :id'); $del->execute([':id' => $commentId]); admin_json_response(['success' => true]); } admin_json_error('Nieznana akcja', 422); } catch (Throwable $e) { $msg = (string)$e->getMessage(); $sqlState = ($e instanceof PDOException) ? (string)($e->getCode() ?? '') : ''; $isSchemaProblem = false; if (stripos($msg, 'Unknown column') !== false) $isSchemaProblem = true; if (stripos($msg, 'Base table or view not found') !== false) $isSchemaProblem = true; if ($sqlState === '42S22' || $sqlState === '42S02') $isSchemaProblem = true; if ($isSchemaProblem) { admin_json_error('Baza danych nie ma jeszcze aktualnych kolumn/tabel dla notatek. Uruchom /administration/install_notes_chat.php i spróbuj ponownie.', 500); } admin_json_error('Błąd operacji notatek', 500); } } // 2) multipart/form-data: create OR update (with optional file) $action = isset($_POST['action']) ? (string)$_POST['action'] : ''; $id = isset($_POST['id']) ? (int)$_POST['id'] : 0; $title = isset($_POST['title']) ? trim((string)$_POST['title']) : ''; $description = isset($_POST['description']) ? trim((string)$_POST['description']) : ''; $clearFile = isset($_POST['clear_file']) ? (bool)((int)$_POST['clear_file']) : false; $deleteFileIds = admin_task_parse_delete_file_ids($_POST['delete_file_ids'] ?? null); $newUploads = admin_task_collect_uploads($ADMIN_TASK_ATTACHMENT_MAX_BYTES); $hasNewUpload = !empty($newUploads); $hasFilesTable = admin_task_files_table_exists($pdo); if (count($newUploads) > $ADMIN_TASK_ATTACHMENTS_MAX) { admin_json_error('Maksymalnie ' . $ADMIN_TASK_ATTACHMENTS_MAX . ' załączników na raz', 422); } if (!$hasFilesTable && ($hasNewUpload || !empty($deleteFileIds))) { admin_json_error('Obsługa załączników tasków wymaga aktualizacji bazy. Uruchom /administration/install_notes_chat.php i spróbuj ponownie.', 500); } try { if ($action === 'update') { if ($id <= 0) { admin_json_error('Brak id do edycji', 422); } $stmt = $pdo->prepare('SELECT title, description, file_name FROM admin_tasks WHERE id = :id'); $stmt->execute([':id' => $id]); $cur = $stmt->fetch(PDO::FETCH_ASSOC); if (!$cur) { admin_json_error('Nie znaleziono notatki', 404); } $newTitle = $title !== '' ? $title : (string)($cur['title'] ?? ''); $newDesc = array_key_exists('description', $_POST) ? $description : (string)($cur['description'] ?? ''); if ($newTitle === '') { admin_json_error('Tytuł jest wymagany', 422); } if (mb_strlen($newTitle) > $ADMIN_TASK_TITLE_MAX) { admin_json_error('Tytuł jest zbyt długi (max ' . $ADMIN_TASK_TITLE_MAX . ' znaków)', 422); } if ($newDesc !== '' && mb_strlen($newDesc) > $ADMIN_TASK_DESC_MAX) { admin_json_error('Opis jest zbyt długi (max ' . $ADMIN_TASK_DESC_MAX . ' znaków)', 422); } if ($hasFilesTable) { $counts = admin_task_count_current_attachments($pdo, $id); $deleteModernCount = 0; if (!empty($deleteFileIds)) { $ph = []; $bind = [':task_id' => $id]; foreach ($deleteFileIds as $idx => $fid) { $k = ':fid' . $idx; $ph[] = $k; $bind[$k] = (int)$fid; } $cntSql = 'SELECT COUNT(*) FROM admin_task_files WHERE task_id = :task_id AND id IN (' . implode(', ', $ph) . ')'; $cntStmt = $pdo->prepare($cntSql); foreach ($bind as $k => $v) { $cntStmt->bindValue($k, $v, PDO::PARAM_INT); } $cntStmt->execute(); $deleteModernCount = (int)$cntStmt->fetchColumn(); } $legacyAfter = $clearFile ? 0 : ($counts['legacy'] > 0 ? 1 : 0); $modernAfter = max(0, $counts['modern'] - $deleteModernCount) + count($newUploads); $totalAfter = $legacyAfter + $modernAfter; if ($totalAfter > $ADMIN_TASK_ATTACHMENTS_MAX) { admin_json_error('Task może mieć maksymalnie ' . $ADMIN_TASK_ATTACHMENTS_MAX . ' załączników', 422); } } $pdo->beginTransaction(); try { $fields = ['title = :title', 'description = :description']; $params = [ ':title' => $newTitle, ':description' => $newDesc !== '' ? $newDesc : null, ':id' => $id, ]; if ($clearFile) { $fields[] = 'file_name = NULL'; $fields[] = 'file_mime = NULL'; $fields[] = 'file_size = NULL'; $fields[] = 'file_data = NULL'; } elseif (!$hasFilesTable && $hasNewUpload) { $legacyFile = $newUploads[0]; $fields[] = 'file_name = :file_name'; $fields[] = 'file_mime = :file_mime'; $fields[] = 'file_size = :file_size'; $fields[] = 'file_data = :file_data'; $params[':file_name'] = $legacyFile['file_name']; $params[':file_mime'] = $legacyFile['file_mime']; $params[':file_size'] = $legacyFile['file_size']; $params[':file_data'] = $legacyFile['file_data']; } $sql = 'UPDATE admin_tasks SET ' . implode(', ', $fields) . ' WHERE id = :id'; $up = $pdo->prepare($sql); $up->bindValue(':title', (string)$params[':title'], PDO::PARAM_STR); $up->bindValue(':description', $params[':description'], $params[':description'] !== null ? PDO::PARAM_STR : PDO::PARAM_NULL); $up->bindValue(':id', (int)$params[':id'], PDO::PARAM_INT); if (array_key_exists(':file_name', $params)) { $up->bindValue(':file_name', (string)$params[':file_name'], PDO::PARAM_STR); $up->bindValue(':file_mime', (string)$params[':file_mime'], PDO::PARAM_STR); $up->bindValue(':file_size', $params[':file_size'] !== null ? (int)$params[':file_size'] : null, $params[':file_size'] !== null ? PDO::PARAM_INT : PDO::PARAM_NULL); $up->bindValue(':file_data', $params[':file_data'], PDO::PARAM_LOB); } $up->execute(); if ($hasFilesTable) { if (!empty($deleteFileIds)) { $ph = []; $bind = [':task_id' => $id]; foreach ($deleteFileIds as $idx => $fid) { $k = ':fid' . $idx; $ph[] = $k; $bind[$k] = (int)$fid; } $delSome = $pdo->prepare('DELETE FROM admin_task_files WHERE task_id = :task_id AND id IN (' . implode(', ', $ph) . ')'); foreach ($bind as $k => $v) { $delSome->bindValue($k, $v, PDO::PARAM_INT); } $delSome->execute(); } if ($hasNewUpload) { admin_task_insert_files($pdo, $id, $newUploads); } } $pdo->commit(); } catch (Throwable $e) { $pdo->rollBack(); throw $e; } admin_json_response(['success' => true]); } // create if ($title === '') { admin_json_error('Tytuł jest wymagany', 422); } if (mb_strlen($title) > $ADMIN_TASK_TITLE_MAX) { admin_json_error('Tytuł jest zbyt długi (max ' . $ADMIN_TASK_TITLE_MAX . ' znaków)', 422); } if ($description !== '' && mb_strlen($description) > $ADMIN_TASK_DESC_MAX) { admin_json_error('Opis jest zbyt długi (max ' . $ADMIN_TASK_DESC_MAX . ' znaków)', 422); } if ($hasFilesTable && count($newUploads) > $ADMIN_TASK_ATTACHMENTS_MAX) { admin_json_error('Task może mieć maksymalnie ' . $ADMIN_TASK_ATTACHMENTS_MAX . ' załączników', 422); } $pdo->beginTransaction(); try { if ($hasFilesTable) { $stmt = $pdo->prepare( 'INSERT INTO admin_tasks (title, description, created_by, created_by_username) ' . 'VALUES (:title, :description, :created_by, :created_by_username)' ); $stmt->bindValue(':title', $title, PDO::PARAM_STR); $stmt->bindValue(':description', $description !== '' ? $description : null, $description !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL); $stmt->bindValue(':created_by', (int)$auth['user_id'], PDO::PARAM_INT); $stmt->bindValue(':created_by_username', (string)$auth['username'], PDO::PARAM_STR); $stmt->execute(); $newId = (int)$pdo->lastInsertId(); if ($hasNewUpload) { admin_task_insert_files($pdo, $newId, $newUploads); } } else { $legacyFile = $hasNewUpload ? $newUploads[0] : null; $stmt = $pdo->prepare( 'INSERT INTO admin_tasks (title, description, file_name, file_mime, file_size, file_data, created_by, created_by_username) ' . 'VALUES (:title, :description, :file_name, :file_mime, :file_size, :file_data, :created_by, :created_by_username)' ); $stmt->bindValue(':title', $title, PDO::PARAM_STR); $stmt->bindValue(':description', $description !== '' ? $description : null, $description !== '' ? PDO::PARAM_STR : PDO::PARAM_NULL); $stmt->bindValue(':file_name', $legacyFile['file_name'] ?? null, isset($legacyFile['file_name']) ? PDO::PARAM_STR : PDO::PARAM_NULL); $stmt->bindValue(':file_mime', $legacyFile['file_mime'] ?? null, isset($legacyFile['file_mime']) ? PDO::PARAM_STR : PDO::PARAM_NULL); $stmt->bindValue(':file_size', $legacyFile['file_size'] ?? null, isset($legacyFile['file_size']) ? PDO::PARAM_INT : PDO::PARAM_NULL); if ($legacyFile !== null) { $stmt->bindValue(':file_data', $legacyFile['file_data'], PDO::PARAM_LOB); } else { $stmt->bindValue(':file_data', null, PDO::PARAM_NULL); } $stmt->bindValue(':created_by', (int)$auth['user_id'], PDO::PARAM_INT); $stmt->bindValue(':created_by_username', (string)$auth['username'], PDO::PARAM_STR); $stmt->execute(); $newId = (int)$pdo->lastInsertId(); } $pdo->commit(); } catch (Throwable $e) { $pdo->rollBack(); throw $e; } admin_json_response(['success' => true, 'id' => $newId], 201); } catch (Throwable $e) { $msg = (string)$e->getMessage(); $sqlState = ($e instanceof PDOException) ? (string)($e->getCode() ?? '') : ''; $isSchemaProblem = false; if (stripos($msg, 'Unknown column') !== false) $isSchemaProblem = true; if (stripos($msg, 'Base table or view not found') !== false) $isSchemaProblem = true; if ($sqlState === '42S22' || $sqlState === '42S02') $isSchemaProblem = true; if ($isSchemaProblem) { admin_json_error('Baza danych nie ma jeszcze aktualnych kolumn/tabel dla notatek. Uruchom /administration/install_notes_chat.php i spróbuj ponownie.', 500); } admin_json_error('Błąd zapisu notatki', 500); } } admin_json_error('Metoda niedozwolona', 405);