diff --git a/app/Console/Commands/GenerateSitemap.php b/app/Console/Commands/GenerateSitemap.php index a9d6549d..068be6ae 100644 --- a/app/Console/Commands/GenerateSitemap.php +++ b/app/Console/Commands/GenerateSitemap.php @@ -48,7 +48,7 @@ public function handle(): int } // 2. Subjects - Subject::orderBy('name')->get()->each(function ($subject) use ($sitemap) { + Subject::where('is_disabled', false)->orderBy('name')->get()->each(function ($subject) use ($sitemap) { $sitemap->add( Url::create(url($subject->slug)) ->setLastModificationDate($subject->updated_at) diff --git a/app/Http/Controllers/NodeController.php b/app/Http/Controllers/NodeController.php index e7199ea9..d3ce1ee1 100644 --- a/app/Http/Controllers/NodeController.php +++ b/app/Http/Controllers/NodeController.php @@ -13,6 +13,10 @@ class NodeController extends Controller { public function show(Subject $subject, $path) { + if ($subject->is_disabled) { + abort(404); + } + $slugs = explode('/', trim($path, '/')); $node = null; diff --git a/app/Http/Controllers/ResourceController.php b/app/Http/Controllers/ResourceController.php index 0eea9a30..98f0f460 100644 --- a/app/Http/Controllers/ResourceController.php +++ b/app/Http/Controllers/ResourceController.php @@ -169,6 +169,8 @@ public function storeBulkImages(BulkImageStoreRequest $request) $nodeId = (int) $validated['node_id']; DB::transaction(function () use ($request, $validated, $userId, $nodeId, $isModerator) { + $content = $validated['content'] ?? null; + foreach ($request->file('files') as $index => $file) { $filePath = $file->store('resources/images'); $title = $validated['custom_titles'][$index]; @@ -178,12 +180,14 @@ public function storeBulkImages(BulkImageStoreRequest $request) 'user_id' => $userId, 'node_id' => $nodeId, 'title' => $title, + 'content' => $content, 'resource_type' => 'image', 'file_path' => $filePath, ]); } else { ResourceChangeRequest::recordCreate($userId, $nodeId, [ 'title' => $title, + 'content' => $content, 'resource_type' => 'image', 'file_path' => $filePath, ]); diff --git a/app/Http/Controllers/SubjectController.php b/app/Http/Controllers/SubjectController.php index a3164b75..a4a1cd84 100644 --- a/app/Http/Controllers/SubjectController.php +++ b/app/Http/Controllers/SubjectController.php @@ -27,7 +27,8 @@ public function index(Request $request, $course) } $subjects = Cache::rememberForever("home_page_subjects_{$course}_{$group}", function () use ($course, $group) { - return Subject::orderBy('sort_order', 'asc') + return Subject::where('is_disabled', false) + ->orderBy('sort_order', 'asc') ->where('course', $course) ->whereIn('group', [$group, 'common']) ->withCount([ @@ -96,6 +97,10 @@ public function index(Request $request, $course) public function show(Subject $subject) { + if ($subject->is_disabled) { + abort(404); + } + $nodes = Cache::rememberForever("subject_page_{$subject->id}", function () use ($subject) { return Node::where('subject_id', $subject->id) ->whereNull('parent_id') diff --git a/app/Http/Requests/Resource/BulkImageStoreRequest.php b/app/Http/Requests/Resource/BulkImageStoreRequest.php index 08c2357a..f539f7ae 100644 --- a/app/Http/Requests/Resource/BulkImageStoreRequest.php +++ b/app/Http/Requests/Resource/BulkImageStoreRequest.php @@ -31,6 +31,7 @@ public function rules(): array 'custom_titles.*' => 'required|string|max:100', 'files' => 'required|array|min:1|max:20', 'files.*' => 'required|image|mimes:jpg,jpeg,png,webp|max:5120', + 'content' => 'nullable|string', ]; } } diff --git a/app/Http/Requests/Subject/StoreSubjectRequest.php b/app/Http/Requests/Subject/StoreSubjectRequest.php index 320d15e4..29dc6388 100644 --- a/app/Http/Requests/Subject/StoreSubjectRequest.php +++ b/app/Http/Requests/Subject/StoreSubjectRequest.php @@ -44,6 +44,8 @@ public function rules(): array 'course' => ['required', 'string', 'in:ssc,hsc'], 'group' => ['sometimes', 'string', 'in:science,humanities,commerce,common'], 'is_trackable' => ['sometimes', 'boolean'], + 'is_optional' => ['sometimes', 'boolean'], + 'is_disabled' => ['sometimes', 'boolean'], ]; } } diff --git a/app/Http/Requests/Subject/UpdateSubjectRequest.php b/app/Http/Requests/Subject/UpdateSubjectRequest.php index bd7b6afc..fc27775d 100644 --- a/app/Http/Requests/Subject/UpdateSubjectRequest.php +++ b/app/Http/Requests/Subject/UpdateSubjectRequest.php @@ -44,6 +44,8 @@ public function rules(): array 'course' => ['sometimes', 'string', 'in:ssc,hsc'], 'group' => ['sometimes', 'string', 'in:science,humanities,commerce,common'], 'is_trackable' => ['sometimes', 'boolean'], + 'is_optional' => ['sometimes', 'boolean'], + 'is_disabled' => ['sometimes', 'boolean'], ]; } } diff --git a/app/Models/Subject.php b/app/Models/Subject.php index 41691ec6..3858704a 100644 --- a/app/Models/Subject.php +++ b/app/Models/Subject.php @@ -13,6 +13,7 @@ protected function casts(): array return [ 'is_trackable' => 'boolean', 'is_optional' => 'boolean', + 'is_disabled' => 'boolean', ]; } diff --git a/database/migrations/2026_10_17_132746_add_is_disabled_to_subjects_table.php b/database/migrations/2026_10_17_132746_add_is_disabled_to_subjects_table.php new file mode 100644 index 00000000..e31bf6e7 --- /dev/null +++ b/database/migrations/2026_10_17_132746_add_is_disabled_to_subjects_table.php @@ -0,0 +1,32 @@ +boolean('is_disabled')->default(false)->after('is_trackable'); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('subjects', function (Blueprint $table) { + if (Schema::hasColumn('subjects', 'is_disabled')) { + $table->dropColumn('is_disabled'); + } + }); + } +}; diff --git a/resources/js/components/admin/BulkImageModal.vue b/resources/js/components/admin/BulkImageModal.vue index 715f768a..07903f34 100644 --- a/resources/js/components/admin/BulkImageModal.vue +++ b/resources/js/components/admin/BulkImageModal.vue @@ -8,6 +8,7 @@ import { Loader2, AlertCircle, X, + ChevronDown, } from 'lucide-vue-next'; import { ref, computed, watch, onUnmounted } from 'vue'; import BaseModal from '@/components/BaseModal.vue'; @@ -38,6 +39,8 @@ const isProcessingServer = ref(false); const namingStrategy = ref<'serial' | 'original'>('serial'); const namingPrefix = ref('image'); const startNumber = ref(1); +const showMoreOptions = ref(false); +const content = ref(''); const MAX_IMAGES = 20; @@ -138,6 +141,8 @@ const clearAll = () => { compressionStatusText.value = ''; uploadProgress.value = null; isProcessingServer.value = false; + content.value = ''; + showMoreOptions.value = false; }; const handleClose = () => { @@ -218,6 +223,7 @@ const submitForm = async () => { start_number: startNumber.value, files: filesToUpload, custom_titles: processedTitles.value, + content: content.value.trim() ? content.value.trim() : null, }; router.post('/resources/bulk/images', payload, { @@ -465,6 +471,44 @@ const submitForm = async () => { + +
+ + +
+ +