exec($sql); } } // Run new migrations if needed $migration2Path = __DIR__ . '/../db/migrations/002_add_library_metadata.sql'; if (is_file($migration2Path)) { // Simple check if columns exist $exists = db()->query("SHOW COLUMNS FROM library_documents LIKE 'category_ar'")->fetch(); if (!$exists) { $sql = file_get_contents($migration2Path); db()->exec($sql); } } $migration3Path = __DIR__ . '/../db/migrations/003_normalize_categories.sql'; if (is_file($migration3Path)) { // Simple check if table exists $exists = db()->query("SHOW TABLES LIKE 'library_categories'")->fetch(); if (!$exists) { $sql = file_get_contents($migration3Path); db()->exec($sql); } } $migration4Path = __DIR__ . '/../db/migrations/004_create_types_table.sql'; if (is_file($migration4Path)) { // Simple check if table exists $exists = db()->query("SHOW TABLES LIKE 'library_types'")->fetch(); if (!$exists) { $sql = file_get_contents($migration4Path); db()->exec($sql); } } $migration5Path = __DIR__ . '/../db/migrations/005_update_document_fields.sql'; if (is_file($migration5Path)) { // Check if column exists $exists = db()->query("SHOW COLUMNS FROM library_documents LIKE 'cover_image_path'")->fetch(); if (!$exists) { $sql = file_get_contents($migration5Path); db()->exec($sql); } } $migration6Path = __DIR__ . '/../db/migrations/006_add_author_column.sql'; if (is_file($migration6Path)) { // Check if column exists $exists = db()->query("SHOW COLUMNS FROM library_documents LIKE 'author'")->fetch(); if (!$exists) { $sql = file_get_contents($migration6Path); db()->exec($sql); } } $uploadDir = __DIR__ . '/../uploads/library'; if (!is_dir($uploadDir)) { mkdir($uploadDir, 0775, true); } $coverDir = __DIR__ . '/../uploads/covers'; if (!is_dir($coverDir)) { mkdir($coverDir, 0775, true); } library_seed_demo_documents(); $booted = true; } function h(?string $value): string { return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); } function library_project_meta(): array { return [ 'name' => $_SERVER['PROJECT_NAME'] ?? 'Nabd Library', 'description' => $_SERVER['PROJECT_DESCRIPTION'] ?? 'Bilingual electronic library for Arabic and English documents, online reading, and AI-assisted summaries.', 'image' => $_SERVER['PROJECT_IMAGE_URL'] ?? '', ]; } function library_set_flash(string $type, string $message): void { $_SESSION['library_flash'][] = ['type' => $type, 'message' => $message]; } function library_get_flashes(): array { $flashes = $_SESSION['library_flash'] ?? []; unset($_SESSION['library_flash']); return is_array($flashes) ? $flashes : []; } function library_seed_demo_documents(): void { $count = (int) (db()->query('SELECT COUNT(*) FROM library_documents')->fetchColumn() ?: 0); if ($count > 0) { return; } $pdfRelative = 'uploads/library/demo-library-guide.pdf'; $txtRelative = 'uploads/library/demo-bilingual-notes.txt'; $pdfAbsolute = __DIR__ . '/../' . $pdfRelative; $txtAbsolute = __DIR__ . '/../' . $txtRelative; } function library_old(string $key, string $default = ''): string { return isset($_POST[$key]) ? trim((string) $_POST[$key]) : $default; } function library_document_type_label(string $type): string { $map = [ 'pdf' => 'PDF reader', 'txt' => 'Text note', 'doc' => 'Word document', 'docx' => 'Word document', 'ppt' => 'PowerPoint', 'pptx' => 'PowerPoint', ]; return $map[strtolower($type)] ?? strtoupper($type); } function library_language_label(string $lang): string { $map = [ 'en' => 'English', 'ar' => 'Arabic', 'bilingual' => 'Bilingual', ]; return $map[$lang] ?? 'Unknown'; } function library_visibility_label(string $visibility): string { return $visibility === 'private' ? 'Private / login' : 'Public'; } function library_allowed_extensions(): array { return [ 'pdf' => 'PDF reader', 'txt' => 'Text note', 'doc' => 'Word document', 'docx' => 'Word document', 'ppt' => 'PowerPoint', 'pptx' => 'PowerPoint', ]; } function library_file_url(string $path): string { if ($path === '') return ''; if ($path[0] !== '/') { return '/' . $path; } return $path; } function library_can_preview(array $doc): bool { // Only PDFs are supported for inline preview in this version return strtolower((string) ($doc['document_type'] ?? '')) === 'pdf'; } function library_increment_views(int $id): void { library_bootstrap(); $stmt = db()->prepare('UPDATE library_documents SET view_count = view_count + 1 WHERE id = ?'); $stmt->execute([$id]); } function library_generate_summary(int $id): array { library_bootstrap(); $doc = library_fetch_document($id); if (!$doc) { return ['success' => false, 'message' => 'Document not found.']; } $descEn = trim((string) ($doc['description_en'] ?? '')); $descAr = trim((string) ($doc['description_ar'] ?? '')); if ($descEn === '' && $descAr === '') { // Fallback to title if description is missing $descEn = trim((string) ($doc['title_en'] ?? '')); $descAr = trim((string) ($doc['title_ar'] ?? '')); } $prompt = "Please summarize the following document content into a concise paragraph in English and a concise paragraph in Arabic.\n\n"; if ($descEn) $prompt .= "English content: $descEn\n"; if ($descAr) $prompt .= "Arabic content: $descAr\n"; $prompt .= "\nReturn the result as valid JSON with keys 'summary_en' and 'summary_ar'."; // Call AI $resp = LocalAIApi::createResponse([ 'model' => 'gpt-5-mini', 'messages' => [ ['role' => 'system', 'content' => 'You are a helpful bilingual assistant.'], ['role' => 'user', 'content' => $prompt], ] ]); if (empty($resp['success'])) { return ['success' => false, 'message' => 'AI request failed: ' . ($resp['error'] ?? 'Unknown error')]; } $text = LocalAIApi::extractText($resp); $sumEn = ''; $sumAr = ''; // Try to parse JSON $jsonStart = strpos($text, '{'); $jsonEnd = strrpos($text, '}'); if ($jsonStart !== false && $jsonEnd !== false) { $jsonStr = substr($text, $jsonStart, $jsonEnd - $jsonStart + 1); $data = json_decode($jsonStr, true); $sumEn = $data['summary_en'] ?? ''; $sumAr = $data['summary_ar'] ?? ''; } // If JSON parsing failed or returned empty strings, try to infer or use raw text if (!$sumEn && !$sumAr) { // If the AI just returned text, use it for English (or both) $sumEn = $text; } // Update DB $stmt = db()->prepare('UPDATE library_documents SET summary_text = ?, summary_en = ?, summary_ar = ? WHERE id = ?'); // Note: summary_text is legacy/combined, we can store JSON or just English $combined = "English: $sumEn\n\nArabic: $sumAr"; $stmt->execute([$combined, $sumEn, $sumAr, $id]); return ['success' => true, 'message' => 'Summary generated successfully.']; } // --- Category Functions --- function library_get_categories(string $search = ''): array { library_bootstrap(); $sql = 'SELECT * FROM library_categories'; $params = []; if ($search !== '') { $sql .= ' WHERE name_en LIKE ? OR name_ar LIKE ?'; $params[] = "%$search%"; $params[] = "%$search%"; } $sql .= ' ORDER BY name_en ASC'; $stmt = db()->prepare($sql); $stmt->execute($params); return $stmt->fetchAll() ?: []; } function library_get_subcategories(?int $categoryId = null, string $search = ''): array { library_bootstrap(); $sql = 'SELECT * FROM library_subcategories WHERE 1=1'; $params = []; if ($categoryId !== null) { $sql .= ' AND category_id = ?'; $params[] = $categoryId; } if ($search !== '') { $sql .= ' AND (name_en LIKE ? OR name_ar LIKE ?)'; $params[] = "%$search%"; $params[] = "%$search%"; } $sql .= ' ORDER BY name_en ASC'; $stmt = db()->prepare($sql); $stmt->execute($params); return $stmt->fetchAll() ?: []; } function library_create_category(string $nameEn, string $nameAr): int { library_bootstrap(); $stmt = db()->prepare('INSERT INTO library_categories (name_en, name_ar) VALUES (?, ?)'); $stmt->execute([$nameEn, $nameAr]); return (int) db()->lastInsertId(); } function library_create_subcategory(int $categoryId, string $nameEn, string $nameAr): int { library_bootstrap(); $stmt = db()->prepare('INSERT INTO library_subcategories (category_id, name_en, name_ar) VALUES (?, ?, ?)'); $stmt->execute([$categoryId, $nameEn, $nameAr]); return (int) db()->lastInsertId(); } function library_get_category_by_id(int $id): ?array { library_bootstrap(); $stmt = db()->prepare('SELECT * FROM library_categories WHERE id = ?'); $stmt->execute([$id]); return $stmt->fetch() ?: null; } function library_get_subcategory_by_id(int $id): ?array { library_bootstrap(); $stmt = db()->prepare('SELECT * FROM library_subcategories WHERE id = ?'); $stmt->execute([$id]); return $stmt->fetch() ?: null; } function library_update_category(int $id, string $nameEn, string $nameAr): void { library_bootstrap(); $stmt = db()->prepare('UPDATE library_categories SET name_en = ?, name_ar = ? WHERE id = ?'); $stmt->execute([$nameEn, $nameAr, $id]); } function library_delete_category(int $id): void { library_bootstrap(); $stmt = db()->prepare('DELETE FROM library_categories WHERE id = ?'); $stmt->execute([$id]); } function library_update_subcategory(int $id, int $categoryId, string $nameEn, string $nameAr): void { library_bootstrap(); $stmt = db()->prepare('UPDATE library_subcategories SET category_id = ?, name_en = ?, name_ar = ? WHERE id = ?'); $stmt->execute([$categoryId, $nameEn, $nameAr, $id]); } function library_delete_subcategory(int $id): void { library_bootstrap(); $stmt = db()->prepare('DELETE FROM library_subcategories WHERE id = ?'); $stmt->execute([$id]); } // --- End Category Functions --- // --- Type Functions --- function library_get_types(string $search = ''): array { library_bootstrap(); $sql = 'SELECT * FROM library_types'; $params = []; if ($search !== '') { $sql .= ' WHERE name_en LIKE ? OR name_ar LIKE ?'; $params[] = "%$search%"; $params[] = "%$search%"; } $sql .= ' ORDER BY name_en ASC'; $stmt = db()->prepare($sql); $stmt->execute($params); return $stmt->fetchAll() ?: []; } function library_create_type(string $nameEn, string $nameAr): int { library_bootstrap(); $stmt = db()->prepare('INSERT INTO library_types (name_en, name_ar) VALUES (?, ?)'); $stmt->execute([$nameEn, $nameAr]); return (int) db()->lastInsertId(); } function library_get_type_by_id(int $id): ?array { library_bootstrap(); $stmt = db()->prepare('SELECT * FROM library_types WHERE id = ?'); $stmt->execute([$id]); return $stmt->fetch() ?: null; } function library_update_type(int $id, string $nameEn, string $nameAr): void { library_bootstrap(); $stmt = db()->prepare('UPDATE library_types SET name_en = ?, name_ar = ? WHERE id = ?'); $stmt->execute([$nameEn, $nameAr, $id]); } function library_delete_type(int $id): void { library_bootstrap(); $stmt = db()->prepare('DELETE FROM library_types WHERE id = ?'); $stmt->execute([$id]); } // --- End Type Functions --- function library_fetch_documents(bool $publicOnly = false, array $filters = []): array { library_bootstrap(); $sql = 'SELECT d.*, c.name_en as cat_en, c.name_ar as cat_ar, sc.name_en as sub_en, sc.name_ar as sub_ar, t.name_en as type_en, t.name_ar as type_ar FROM library_documents d LEFT JOIN library_categories c ON d.category_id = c.id LEFT JOIN library_subcategories sc ON d.subcategory_id = sc.id LEFT JOIN library_types t ON d.type_id = t.id WHERE 1=1'; $params = []; if ($publicOnly) { $sql .= ' AND d.visibility = :visibility'; $params[':visibility'] = 'public'; } $sql .= ' ORDER BY d.is_featured DESC, d.created_at DESC'; $stmt = db()->prepare($sql); foreach ($params as $key => $value) { $stmt->bindValue($key, $value); } $stmt->execute(); return $stmt->fetchAll() ?: []; } function library_fetch_document(int $id, bool $publicOnly = false): ?array { library_bootstrap(); $sql = 'SELECT d.*, c.name_en as cat_en, c.name_ar as cat_ar, sc.name_en as sub_en, sc.name_ar as sub_ar, t.name_en as type_en, t.name_ar as type_ar FROM library_documents d LEFT JOIN library_categories c ON d.category_id = c.id LEFT JOIN library_subcategories sc ON d.subcategory_id = sc.id LEFT JOIN library_types t ON d.type_id = t.id WHERE d.id = :id'; $params = [':id' => $id]; if ($publicOnly) { $sql .= ' AND d.visibility = :visibility'; $params[':visibility'] = 'public'; } $stmt = db()->prepare($sql); foreach ($params as $key => $value) { $stmt->bindValue($key, $value); } $stmt->execute(); return $stmt->fetch() ?: null; } function library_recent_documents(int $limit = 3, bool $publicOnly = false): array { library_bootstrap(); $sql = 'SELECT * FROM library_documents WHERE 1=1'; if ($publicOnly) { $sql .= ' AND visibility = "public"'; } $sql .= ' ORDER BY created_at DESC LIMIT ' . (int)$limit; $stmt = db()->query($sql); return $stmt ? $stmt->fetchAll() : []; } function library_catalog_metrics(): array { library_bootstrap(); $sql = 'SELECT COUNT(*) AS total_count, SUM(CASE WHEN visibility = "public" THEN 1 ELSE 0 END) AS public_count, SUM(CASE WHEN visibility = "private" THEN 1 ELSE 0 END) AS private_count, SUM(CASE WHEN summary_text IS NOT NULL THEN 1 ELSE 0 END) AS summarized_count FROM library_documents'; $row = db()->query($sql)->fetch() ?: []; return [ 'total_count' => (int) ($row['total_count'] ?? 0), 'public_count' => (int) ($row['public_count'] ?? 0), 'private_count' => (int) ($row['private_count'] ?? 0), 'summarized_count' => (int) ($row['summarized_count'] ?? 0), ]; } function library_handle_uploaded_file(array $file): array { if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) { throw new RuntimeException('Please upload a document file.'); } $originalName = (string) ($file['name'] ?? ''); $extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION)); $allowed = library_allowed_extensions(); if (!isset($allowed[$extension])) { throw new RuntimeException('Unsupported file type.'); } $size = (int) ($file['size'] ?? 0); if ($size <= 0 || $size > 12 * 1024 * 1024) { throw new RuntimeException('File must be smaller than 12 MB.'); } $safeBase = preg_replace('/[^a-zA-Z0-9_-]+/', '-', pathinfo($originalName, PATHINFO_FILENAME)) ?: 'document'; $storedName = strtolower(date('YmdHis') . '-' . $safeBase . '-' . bin2hex(random_bytes(4)) . '.' . $extension); $relativePath = 'uploads/library/' . $storedName; $absolutePath = __DIR__ . '/../' . $relativePath; if (!move_uploaded_file((string) $file['tmp_name'], $absolutePath)) { throw new RuntimeException('Unable to save the uploaded file.'); } return [ 'file_name' => $originalName, 'file_path' => $relativePath, 'document_type' => $extension, 'file_size_kb' => (int) ceil($size / 1024), ]; } function library_handle_cover_image(array $file): ?string { if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) { return null; } $originalName = (string) ($file['name'] ?? ''); $extension = strtolower(pathinfo($originalName, PATHINFO_EXTENSION)); $allowed = ['jpg', 'jpeg', 'png', 'webp', 'gif']; if (!in_array($extension, $allowed)) { throw new RuntimeException('Unsupported cover image type. Allowed: jpg, png, webp, gif.'); } $size = (int) ($file['size'] ?? 0); if ($size <= 0 || $size > 5 * 1024 * 1024) { throw new RuntimeException('Cover image must be smaller than 5 MB.'); } $safeBase = preg_replace('/[^a-zA-Z0-9_-]+/', '-', pathinfo($originalName, PATHINFO_FILENAME)) ?: 'cover'; $storedName = strtolower(date('YmdHis') . '-' . $safeBase . '-' . bin2hex(random_bytes(4)) . '.' . $extension); $relativePath = 'uploads/covers/' . $storedName; $absolutePath = __DIR__ . '/../' . $relativePath; if (!move_uploaded_file((string) $file['tmp_name'], $absolutePath)) { throw new RuntimeException('Unable to save the cover image.'); } return $relativePath; } function library_create_document(array $payload, array $file, array $coverFile = []): int { library_bootstrap(); $titleEn = trim((string) ($payload['title_en'] ?? '')); $titleAr = trim((string) ($payload['title_ar'] ?? '')); // Process IDs $categoryId = !empty($payload['category_id']) ? (int)$payload['category_id'] : null; $subcategoryId = !empty($payload['subcategory_id']) ? (int)$payload['subcategory_id'] : null; $typeId = !empty($payload['type_id']) ? (int)$payload['type_id'] : null; // Fetch names for backward compatibility if needed, or just store IDs $categoryName = ''; $categoryNameAr = ''; $subName = ''; $subNameAr = ''; if ($categoryId) { $cat = library_get_category_by_id($categoryId); if ($cat) { $categoryName = $cat['name_en']; $categoryNameAr = $cat['name_ar']; } } if ($subcategoryId) { $sub = library_get_subcategory_by_id($subcategoryId); if ($sub) { $subName = $sub['name_en']; $subNameAr = $sub['name_ar']; } } $visibility = (string) ($payload['visibility'] ?? 'public'); $allow_download = !empty($payload['allow_download']) ? 1 : 0; $allow_print = !empty($payload['allow_print']) ? 1 : 0; $allow_copy = !empty($payload['allow_copy']) ? 1 : 0; // New Fields $publisher = trim((string) ($payload['publisher'] ?? '')); $publishYear = !empty($payload['publish_year']) ? (int)$payload['publish_year'] : null; $author = trim((string) ($payload['author'] ?? '')); $country = trim((string) ($payload['country'] ?? '')); $pageCount = !empty($payload['page_count']) ? (int)$payload['page_count'] : null; $summaryEn = trim((string) ($payload['summary_en'] ?? '')); $summaryAr = trim((string) ($payload['summary_ar'] ?? '')); $descriptionEn = trim((string) ($payload['description_en'] ?? '')); $descriptionAr = trim((string) ($payload['description_ar'] ?? '')); $fileData = library_handle_uploaded_file($file); $coverPath = library_handle_cover_image($coverFile); $stmt = db()->prepare('INSERT INTO library_documents ( title_en, title_ar, category, category_ar, sub_category, sub_category_ar, category_id, subcategory_id, visibility, document_type, file_name, file_path, file_size_kb, allow_download, allow_print, allow_copy, cover_image_path, publisher, publish_year, author, country, type_id, page_count, summary_en, summary_ar, description_en, description_ar ) VALUES ( :title_en, :title_ar, :category, :category_ar, :sub_category, :sub_category_ar, :category_id, :subcategory_id, :visibility, :document_type, :file_name, :file_path, :file_size_kb, :allow_download, :allow_print, :allow_copy, :cover_image_path, :publisher, :publish_year, :author, :country, :type_id, :page_count, :summary_en, :summary_ar, :description_en, :description_ar )'); $stmt->execute([ ':title_en' => $titleEn ?: null, ':title_ar' => $titleAr ?: null, ':category' => $categoryName ?: null, ':category_ar' => $categoryNameAr ?: null, ':sub_category' => $subName ?: null, ':sub_category_ar' => $subNameAr ?: null, ':category_id' => $categoryId, ':subcategory_id' => $subcategoryId, ':visibility' => $visibility, ':document_type' => $fileData['document_type'], ':file_name' => $fileData['file_name'], ':file_path' => $fileData['file_path'], ':file_size_kb' => $fileData['file_size_kb'], ':allow_download' => $allow_download, ':allow_print' => $allow_print, ':allow_copy' => $allow_copy, ':cover_image_path' => $coverPath, ':publisher' => $publisher ?: null, ':publish_year' => $publishYear, ':author' => $author ?: null, ':country' => $country ?: null, ':type_id' => $typeId, ':page_count' => $pageCount, ':summary_en' => $summaryEn ?: null, ':summary_ar' => $summaryAr ?: null, ':description_en' => $descriptionEn ?: null, ':description_ar' => $descriptionAr ?: null, ]); return (int) db()->lastInsertId(); } function library_update_document(int $id, array $payload, array $file = [], array $coverFile = []): void { library_bootstrap(); // Fetch existing document to keep file if not replaced $existing = library_fetch_document($id); if (!$existing) { throw new RuntimeException('Document not found.'); } $titleEn = trim((string) ($payload['title_en'] ?? '')); $titleAr = trim((string) ($payload['title_ar'] ?? '')); // Process IDs $categoryId = !empty($payload['category_id']) ? (int)$payload['category_id'] : null; $subcategoryId = !empty($payload['subcategory_id']) ? (int)$payload['subcategory_id'] : null; $typeId = !empty($payload['type_id']) ? (int)$payload['type_id'] : null; $categoryName = ''; $categoryNameAr = ''; $subName = ''; $subNameAr = ''; if ($categoryId) { $cat = library_get_category_by_id($categoryId); if ($cat) { $categoryName = $cat['name_en']; $categoryNameAr = $cat['name_ar']; } } if ($subcategoryId) { $sub = library_get_subcategory_by_id($subcategoryId); if ($sub) { $subName = $sub['name_en']; $subNameAr = $sub['name_ar']; } } $visibility = (string) ($payload['visibility'] ?? 'public'); $allow_download = !empty($payload['allow_download']) ? 1 : 0; $allow_print = !empty($payload['allow_print']) ? 1 : 0; $allow_copy = !empty($payload['allow_copy']) ? 1 : 0; $publisher = trim((string) ($payload['publisher'] ?? '')); $publishYear = !empty($payload['publish_year']) ? (int)$payload['publish_year'] : null; $author = trim((string) ($payload['author'] ?? '')); $country = trim((string) ($payload['country'] ?? '')); $pageCount = !empty($payload['page_count']) ? (int)$payload['page_count'] : null; $summaryEn = trim((string) ($payload['summary_en'] ?? '')); $summaryAr = trim((string) ($payload['summary_ar'] ?? '')); $descriptionEn = trim((string) ($payload['description_en'] ?? '')); $descriptionAr = trim((string) ($payload['description_ar'] ?? '')); // Handle File Update $fileData = null; if (!empty($file['name'])) { $fileData = library_handle_uploaded_file($file); } // Handle Cover Update $coverPath = null; if (!empty($coverFile['name'])) { $coverPath = library_handle_cover_image($coverFile); } $sql = 'UPDATE library_documents SET title_en = :title_en, title_ar = :title_ar, category = :category, category_ar = :category_ar, sub_category = :sub_category, sub_category_ar = :sub_category_ar, category_id = :category_id, subcategory_id = :subcategory_id, visibility = :visibility, allow_download = :allow_download, allow_print = :allow_print, allow_copy = :allow_copy, publisher = :publisher, publish_year = :publish_year, author = :author, country = :country, type_id = :type_id, page_count = :page_count, summary_en = :summary_en, summary_ar = :summary_ar, description_en = :description_en, description_ar = :description_ar'; $params = [ ':title_en' => $titleEn ?: null, ':title_ar' => $titleAr ?: null, ':category' => $categoryName ?: null, ':category_ar' => $categoryNameAr ?: null, ':sub_category' => $subName ?: null, ':sub_category_ar' => $subNameAr ?: null, ':category_id' => $categoryId, ':subcategory_id' => $subcategoryId, ':visibility' => $visibility, ':allow_download' => $allow_download, ':allow_print' => $allow_print, ':allow_copy' => $allow_copy, ':publisher' => $publisher ?: null, ':publish_year' => $publishYear, ':author' => $author ?: null, ':country' => $country ?: null, ':type_id' => $typeId, ':page_count' => $pageCount, ':summary_en' => $summaryEn ?: null, ':summary_ar' => $summaryAr ?: null, ':description_en' => $descriptionEn ?: null, ':description_ar' => $descriptionAr ?: null, ':id' => $id, ]; if ($fileData) { $sql .= ', document_type = :document_type, file_name = :file_name, file_path = :file_path, file_size_kb = :file_size_kb'; $params[':document_type'] = $fileData['document_type']; $params[':file_name'] = $fileData['file_name']; $params[':file_path'] = $fileData['file_path']; $params[':file_size_kb'] = $fileData['file_size_kb']; } if ($coverPath) { $sql .= ', cover_image_path = :cover_image_path'; $params[':cover_image_path'] = $coverPath; } $sql .= ' WHERE id = :id'; $stmt = db()->prepare($sql); $stmt->execute($params); } function library_delete_document(int $id): void { library_bootstrap(); // Optionally delete files, but for safety we might keep them or delete them. // For now, just delete DB record. $stmt = db()->prepare('DELETE FROM library_documents WHERE id = ?'); $stmt->execute([$id]); }