diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..f757485 --- /dev/null +++ b/.htaccess @@ -0,0 +1,4 @@ +php_value upload_max_filesize 20M +php_value post_max_size 20M +php_value max_execution_time 300 +php_value max_input_time 300 \ No newline at end of file diff --git a/admin_documents.php b/admin_documents.php index d8adae1..a508795 100644 --- a/admin_documents.php +++ b/admin_documents.php @@ -1,6 +1,12 @@ 0) { + $errors[] = 'The uploaded file exceeds the server limit (post_max_size). Please try a smaller file or contact admin.'; + } else { + $action = $_POST['action'] ?? ''; + $id = isset($_POST['id']) ? (int)$_POST['id'] : 0; - try { - if ($action === 'create_document') { - library_create_document($_POST, $_FILES['document_file'] ?? [], $_FILES['cover_file'] ?? []); - library_set_flash('success', 'Document created successfully.'); - header('Location: /admin_documents.php'); - exit; - } elseif ($action === 'update_document') { - if (!$id) { - throw new RuntimeException('Invalid Document ID.'); + try { + if ($action === 'create_document') { + library_create_document($_POST, $_FILES['document_file'] ?? [], $_FILES['cover_file'] ?? []); + library_set_flash('success', 'Document created successfully.'); + header('Location: /admin_documents.php'); + exit; + } elseif ($action === 'update_document') { + if (!$id) { + throw new RuntimeException('Invalid Document ID.'); + } + library_update_document($id, $_POST, $_FILES['document_file'] ?? [], $_FILES['cover_file'] ?? []); + library_set_flash('success', 'Document updated successfully.'); + header('Location: /admin_documents.php'); + exit; + } elseif ($action === 'delete_document') { + if (!$id) { + throw new RuntimeException('Invalid Document ID.'); + } + library_delete_document($id); + library_set_flash('success', 'Document deleted successfully.'); + header('Location: /admin_documents.php'); + exit; } - library_update_document($id, $_POST, $_FILES['document_file'] ?? [], $_FILES['cover_file'] ?? []); - library_set_flash('success', 'Document updated successfully.'); - header('Location: /admin_documents.php'); - exit; - } elseif ($action === 'delete_document') { - if (!$id) { - throw new RuntimeException('Invalid Document ID.'); - } - library_delete_document($id); - library_set_flash('success', 'Document deleted successfully.'); - header('Location: /admin_documents.php'); - exit; + } catch (Throwable $exception) { + $errors[] = $exception->getMessage(); + error_log('AdminDocuments Error: ' . $exception->getMessage()); } - } catch (Throwable $exception) { - $errors[] = $exception->getMessage(); } } // Search Logic $search = isset($_GET['search']) ? trim($_GET['search']) : ''; -// We can implement search in fetch_documents if needed, currently it supports filters -// For now, fetch all and filter in PHP or improve SQL later if specific search needed. -// library_fetch_documents doesn't have search param yet, let's just fetch all. $documents = library_fetch_documents(false); -// Basic search filter in PHP for now if ($search !== '') { $documents = array_filter($documents, function($doc) use ($search) { return stripos($doc['title_en'] ?? '', $search) !== false @@ -63,7 +71,14 @@ admin_render_header('Material Entry', 'documents'); ?> -
+
+
Submission Error
+ +
@@ -162,25 +177,27 @@ admin_render_header('Material Entry', 'documents');
- @@ -327,6 +343,13 @@ admin_render_header('Material Entry', 'documents'); document.addEventListener('DOMContentLoaded', function() { documentModal = new bootstrap.Modal(document.getElementById('documentModal')); + + // Form submission loading state + document.getElementById('documentForm').addEventListener('submit', function() { + const btn = document.getElementById('saveButton'); + btn.disabled = true; + btn.querySelector('.spinner-border').classList.remove('d-none'); + }); }); function updateSubcategories(selectedSubId = null) { @@ -354,6 +377,11 @@ admin_render_header('Material Entry', 'documents'); document.getElementById('doc_id').value = ''; document.getElementById('documentForm').reset(); + // UI Helpers for Create + document.getElementById('file_required_indicator').classList.remove('d-none'); + document.getElementById('document_file_input').required = true; + document.getElementById('file_help_text').innerText = 'Required for new documents.'; + // Clear previews document.getElementById('current_cover_preview').classList.add('d-none'); document.getElementById('current_file_info').classList.add('d-none'); @@ -369,6 +397,11 @@ admin_render_header('Material Entry', 'documents'); document.getElementById('doc_action').value = 'update_document'; document.getElementById('doc_id').value = doc.id; + // UI Helpers for Edit + document.getElementById('file_required_indicator').classList.add('d-none'); + document.getElementById('document_file_input').required = false; + document.getElementById('file_help_text').innerText = 'Leave empty to keep existing file.'; + // Fill fields document.getElementById('doc_title_en').value = doc.title_en || ''; document.getElementById('doc_title_ar').value = doc.title_ar || ''; @@ -420,4 +453,4 @@ admin_render_header('Material Entry', 'documents'); } - + \ No newline at end of file diff --git a/assets/css/custom.css b/assets/css/custom.css index 2e1905e..8d65ea0 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -347,3 +347,28 @@ footer a { min-height: 60vh; } } + +/* Flipbook Custom Styles */ +#flipbook-wrapper { + background: #333; + box-shadow: inset 0 0 30px rgba(0,0,0,0.6); +} + +#flipbook .page { + background-color: #fff; + /* Soft border for realism */ + border-right: 1px solid #ddd; +} + +#flipbook-toolbar button { + transition: all 0.2s ease; +} + +#flipbook-toolbar button:hover { + transform: scale(1.1); + color: var(--accent); +} + +#flipbook-toolbar button:active { + transform: scale(0.95); +} \ No newline at end of file diff --git a/db/migrations/006_add_author_column.sql b/db/migrations/006_add_author_column.sql new file mode 100644 index 0000000..64c15b1 --- /dev/null +++ b/db/migrations/006_add_author_column.sql @@ -0,0 +1,2 @@ +ALTER TABLE library_documents +ADD COLUMN author VARCHAR(255) DEFAULT NULL; diff --git a/document.php b/document.php index 331479e..eb1017e 100644 --- a/document.php +++ b/document.php @@ -68,7 +68,7 @@ library_render_header(
-
Author
+
Author
Views
File
@@ -81,7 +81,11 @@ library_render_header(

Read in the browser

- Open file + + Open fullscreen + + Open file + @@ -91,9 +95,36 @@ library_render_header(

This title is marked as login-required by the admin, so it stays hidden from the public reading experience.

-
- + +
+
+
+
Loading Book...
+
+ +
+ + +
+ + +
+ Page +
+ 1 + / + -- +
+
+ + +
+

Document stored

@@ -158,5 +189,142 @@ library_render_header(
+ + + + + + + + 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); @@ -162,6 +173,94 @@ function library_allowed_extensions(): array ]; } +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 @@ -521,6 +620,8 @@ function library_create_document(array $payload, array $file, array $coverFile = $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); @@ -531,14 +632,16 @@ function library_create_document(array $payload, array $file, array $coverFile = 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 + 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 + :cover_image_path, :publisher, :publish_year, :author, :country, :type_id, :page_count, :summary_en, :summary_ar, + :description_en, :description_ar )'); $stmt->execute([ @@ -567,6 +670,8 @@ function library_create_document(array $payload, array $file, array $coverFile = ':page_count' => $pageCount, ':summary_en' => $summaryEn ?: null, ':summary_ar' => $summaryAr ?: null, + ':description_en' => $descriptionEn ?: null, + ':description_ar' => $descriptionAr ?: null, ]); return (int) db()->lastInsertId(); @@ -622,6 +727,8 @@ function library_update_document(int $id, array $payload, array $file = [], arra $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; @@ -642,7 +749,8 @@ function library_update_document(int $id, array $payload, array $file = [], arra 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'; + 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, @@ -665,6 +773,8 @@ function library_update_document(int $id, array $payload, array $file = [], arra ':page_count' => $pageCount, ':summary_en' => $summaryEn ?: null, ':summary_ar' => $summaryAr ?: null, + ':description_en' => $descriptionEn ?: null, + ':description_ar' => $descriptionAr ?: null, ':id' => $id, ]; @@ -694,4 +804,4 @@ function library_delete_document(int $id): void // For now, just delete DB record. $stmt = db()->prepare('DELETE FROM library_documents WHERE id = ?'); $stmt->execute([$id]); -} \ No newline at end of file +} diff --git a/index.php b/index.php index 0103368..c508262 100644 --- a/index.php +++ b/index.php @@ -137,7 +137,7 @@ library_render_header(
Author
-
+
Views
Tags
@@ -182,7 +182,7 @@ library_render_header( - + diff --git a/uploads/library/20260325102925---8a635cbb.pdf b/uploads/library/20260325102925---8a635cbb.pdf new file mode 100644 index 0000000..9b53d72 Binary files /dev/null and b/uploads/library/20260325102925---8a635cbb.pdf differ diff --git a/uploads/library/20260325103107---099c7452.pdf b/uploads/library/20260325103107---099c7452.pdf new file mode 100644 index 0000000..9b53d72 Binary files /dev/null and b/uploads/library/20260325103107---099c7452.pdf differ diff --git a/uploads/library/20260325103121---3affd94e.pdf b/uploads/library/20260325103121---3affd94e.pdf new file mode 100644 index 0000000..9b53d72 Binary files /dev/null and b/uploads/library/20260325103121---3affd94e.pdf differ diff --git a/uploads/library/20260325103208---ed905fee.pdf b/uploads/library/20260325103208---ed905fee.pdf new file mode 100644 index 0000000..9b53d72 Binary files /dev/null and b/uploads/library/20260325103208---ed905fee.pdf differ diff --git a/viewer.php b/viewer.php new file mode 100644 index 0000000..94175d6 --- /dev/null +++ b/viewer.php @@ -0,0 +1,539 @@ + 0 ? library_fetch_document($documentId, $publicOnly) : null; + +if (!$document || empty($document['file_path'])) { + http_response_code(404); + die('Document not found or no file attached.'); +} + +$fileUrl = library_file_url((string) $document['file_path']); +?> + + + + + + <?= h($document['title_en'] ?: 'Document Viewer') ?> - Reader + + + + + + + + +
+
+ + + +
+
+ + +
+
+ +
+ + +
+
+ +
+ +
+ +
+ 1 / -- +
+ +
+ +
+ + + +
+
+ +
+
+
+
Loading Document...
+
+
+
+
+ + + + + + + \ No newline at end of file