map(function ($category) { return [ 'id' => $category->id, 'name' => $category->name, ]; }); return $this->success($categories, 'Success', 'Bylaw Categories List'); } public function authorities() { $authorities = BylawLegalAuthority::all()->map(function ($authority) { return [ 'id' => $authority->id, 'name' => $authority->name, 'authority_type' => $authority->authority_type, ]; }); return $this->success($authorities, 'Success', 'Bylaw Authorities List'); } public function index(Request $request) { $request->validate([ 'category_id' => 'nullable|exists:bylaw_subject_categories,id', 'approval_authority_id' => 'nullable|exists:bylaw_legal_authorities,id', 'bylaw_type' => 'nullable|in:executive,delegated,independent', 'sort_by' => 'nullable|in:date,number', 'sort' => 'nullable|in:asc,desc', 'per_page' => 'nullable|integer|min:1|max:100', ]); $query = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority']) ->where('is_published', true); if ($request->filled('category_id')) { $query->where('category_id', $request->category_id); } if ($request->filled('approval_authority_id')) { $query->where('approval_authority_id', $request->approval_authority_id); } if ($request->filled('bylaw_type')) { $query->where('bylaw_type', $request->bylaw_type); } $sortBy = $request->input('sort_by', 'date'); $sortOrder = $request->input('sort', 'desc'); $sortColumn = $sortBy === 'number' ? 'promulgation_number' : 'approval_date'; if ($sortBy === 'number') { $query->orderByRaw("LENGTH({$sortColumn}) {$sortOrder}")->orderBy($sortColumn, $sortOrder); } else { $query->orderBy($sortColumn, $sortOrder); } $perPage = min(max((int) $request->input('per_page', 15), 10), 100); $paginator = $query->paginate($perPage); // واکشی دسته‌جمعی و ایمنِ گره‌ها با استفاده از Eloquent $docIds = collect($paginator->items())->pluck('id')->map(fn($id) => (int)$id)->toArray(); $nodesByDoc = collect(); if (!empty($docIds)) { $nodesByDoc = BylawNode::whereIn('document_id', $docIds) ->orderBy('sort_order', 'asc') ->get() ->groupBy(fn($node) => (int)$node->document_id); } $items = collect($paginator->items())->map(function ($item) use ($nodesByDoc) { $itemNodes = $nodesByDoc->get((int)$item->id, collect()); return $this->formatBylaw($item, $itemNodes); }); $responseData = [ 'items' => $items, 'current_page' => $paginator->currentPage(), 'last_page' => $paginator->lastPage(), 'per_page' => $paginator->perPage(), 'total' => $paginator->total(), ]; return $this->success($responseData, 'Success', 'Bylaws Fetched'); } public function single($id) { $bylaw = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority']) ->where('is_published', true) ->find($id); if (!$bylaw) { return $this->failed([], [ 'title' => 'خطا', 'message' => 'آیین‌نامه مورد نظر یافت نشد.' ], 404); } $formatted = $this->formatBylaw($bylaw); if (!$formatted['is_locked']) { $eloquentNodes = BylawNode::with('type') ->where('document_id', $bylaw->id) ->orderBy('sort_order', 'asc') ->get(); $formatted['nodes'] = $eloquentNodes->map(function ($node) { return [ 'id' => $node->id, 'parent_id' => $node->parent_id, 'node_type' => optional($node->type)->name, 'node_identifier' => $node->node_identifier, 'title' => $node->title, 'node_content' => $node->node_content, 'sort_order' => $node->sort_order, 'valid_from' => $node->valid_from, 'valid_to' => $node->valid_to, 'node_status' => $node->node_status, ]; })->toArray(); $links = BylawCodificationLink::where('source_entity_type', 'bylaw_doc') ->where('source_entity_id', $bylaw->id) ->get() ->map(function ($link) { return [ 'id' => $link->id, 'target_entity_type' => $link->target_entity_type, 'target_entity_id' => $link->target_entity_id, 'link_type' => $link->link_type, 'effective_date' => $link->effective_date, 'justification' => $link->justification, ]; }); $formatted['links'] = $links->toArray(); } return $this->success($formatted, 'Success', 'Bylaw Document Details Fetched'); } public function search(Request $request) { $request->validate([ 'search' => 'nullable|string', 'sort_by' => 'nullable|in:date,number', 'sort' => 'nullable|in:asc,desc', 'per_page' => 'nullable|integer|min:1|max:100', ]); $query = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority']) ->where('is_published', true); if ($request->filled('search')) { $searchTerm = $request->input('search'); $query->where(function ($q) use ($searchTerm) { $q->where('title', 'LIKE', "%{$searchTerm}%") ->orWhere('description', 'LIKE', "%{$searchTerm}%") ->orWhere('promulgation_number', 'LIKE', "%{$searchTerm}%") ->orWhere('official_gazette_number', 'LIKE', "%{$searchTerm}%") ->orWhereIn('id', function($subQuery) use ($searchTerm) { $subQuery->select('document_id') ->from('bylaw_nodes') ->where('node_content', 'LIKE', "%{$searchTerm}%") ->orWhere('title', 'LIKE', "%{$searchTerm}%"); }); }); } $sortBy = $request->input('sort_by', 'date'); $sortOrder = $request->input('sort', 'desc'); $sortColumn = $sortBy === 'number' ? 'promulgation_number' : 'approval_date'; if ($sortBy === 'number') { $query->orderByRaw("LENGTH({$sortColumn}) {$sortOrder}")->orderBy($sortColumn, $sortOrder); } else { $query->orderBy($sortColumn, $sortOrder); } $perPage = min(max((int) $request->input('per_page', 15), 10), 100); $paginator = $query->paginate($perPage); $docIds = collect($paginator->items())->pluck('id')->map(fn($id) => (int)$id)->toArray(); $nodesByDoc = collect(); if (!empty($docIds)) { $nodesByDoc = BylawNode::whereIn('document_id', $docIds) ->orderBy('sort_order', 'asc') ->get() ->groupBy(fn($node) => (int)$node->document_id); } $items = collect($paginator->items())->map(function ($item) use ($nodesByDoc) { $itemNodes = $nodesByDoc->get((int)$item->id, collect()); return $this->formatBylaw($item, $itemNodes); }); $responseData = [ 'items' => $items, 'current_page' => $paginator->currentPage(), 'last_page' => $paginator->lastPage(), 'per_page' => $paginator->perPage(), 'total' => $paginator->total(), ]; return $this->success($responseData, 'Success', 'Search Results Fetched'); } public function fast_search(Request $request) { $searchTerm = $request->input('search'); $query = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority']) ->where('is_published', true); if (!empty($searchTerm)) { $query->where(function ($q) use ($searchTerm) { $q->where('title', 'LIKE', "%{$searchTerm}%") ->orWhere('description', 'LIKE', "%{$searchTerm}%") ->orWhere('promulgation_number', 'LIKE', "%{$searchTerm}%") ->orWhere('official_gazette_number', 'LIKE', "%{$searchTerm}%") ->orWhereIn('id', function($subQuery) use ($searchTerm) { $subQuery->select('document_id') ->from('bylaw_nodes') ->where('node_content', 'LIKE', "%{$searchTerm}%") ->orWhere('title', 'LIKE', "%{$searchTerm}%"); }); }); } $count = $query->count(); $items = $query->orderBy('approval_date', 'desc')->take(5)->get(); $docIds = $items->pluck('id')->map(fn($id) => (int)$id)->toArray(); $nodesByDoc = collect(); if (!empty($docIds)) { $nodesByDoc = BylawNode::whereIn('document_id', $docIds) ->orderBy('sort_order', 'asc') ->get() ->groupBy(fn($node) => (int)$node->document_id); } $formattedItems = $items->map(function ($item) use ($nodesByDoc) { $itemNodes = $nodesByDoc->get((int)$item->id, collect()); return $this->formatBylaw($item, $itemNodes); }); return $this->success([ 'items' => $formattedItems, 'count' => $count ], 'Success', 'Fast Search Results'); } public function pager($id) { $current = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority']) ->where('is_published', true) ->find($id); if (!$current) { return $this->failed([], [ 'title' => 'خطا', 'message' => 'آیین‌نامه مورد نظر یافت نشد.' ], 404); } $previousItem = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority']) ->where('is_published', true) ->where(function ($query) use ($current) { if ($current->approval_date) { $query->where('approval_date', '<', $current->approval_date) ->orWhere(function ($subQuery) use ($current) { $subQuery->where('approval_date', '=', $current->approval_date) ->where('id', '<', $current->id); }); } else { $query->whereNull('approval_date')->where('id', '<', $current->id); } }) ->orderBy('approval_date', 'desc') ->orderBy('id', 'desc') ->first(); $nextItem = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority']) ->where('is_published', true) ->where(function ($query) use ($current) { if ($current->approval_date) { $query->where('approval_date', '>', $current->approval_date) ->orWhere(function ($subQuery) use ($current) { $subQuery->where('approval_date', '=', $current->approval_date) ->where('id', '>', $current->id); }); } else { $query->whereNotNull('approval_date') ->orWhere(function ($subQuery) use ($current) { $subQuery->whereNull('approval_date')->where('id', '>', $current->id); }); } }) ->orderBy('approval_date', 'asc') ->orderBy('id', 'asc') ->first(); $itemsToFormat = array_filter([$previousItem, $current, $nextItem]); $docIds = collect($itemsToFormat)->pluck('id')->map(fn($id) => (int)$id)->toArray(); $nodesByDoc = collect(); if (!empty($docIds)) { $nodesByDoc = BylawNode::whereIn('document_id', $docIds) ->orderBy('sort_order', 'asc') ->get() ->groupBy(fn($node) => (int)$node->document_id); } $results = collect($itemsToFormat)->map(function($item) use ($nodesByDoc) { $itemNodes = $nodesByDoc->get((int)$item->id, collect()); return $this->formatBylaw($item, $itemNodes); })->values()->toArray(); return $this->success($results, 'Success', 'Pager Window Details'); } private function isLockedForCurrentUser($item): bool { if (auth()->check() && auth()->user() && method_exists(auth()->user(), 'isSubscriber')) { return !auth()->user()->isSubscriber() && (bool) ($item->is_locked ?? false); } return (bool) ($item->is_locked ?? false); } /** * متد قدرتمند برای استخراج ایمن متون */ private function formatBylaw($item, $itemNodes = null) { $isLocked = $this->isLockedForCurrentUser($item); // واکشی در صورت خالی بودن کالکشن if ($itemNodes === null) { $itemNodes = BylawNode::where('document_id', $item->id)->orderBy('sort_order', 'asc')->get(); } // استخراج محتوا و تبدیل امنِ Encoding (ضد باگِ JSON Encode لاراول) $nodeContentAsDescription = $itemNodes->pluck('node_content') ->map(function ($text) { // اطمینان از حذف فاصله‌ها و اعمال انکودینگ استاندارد برای جلوگیری از باگ‌های نامرئی $safeText = trim((string)$text); return mb_convert_encoding($safeText, 'UTF-8', 'UTF-8'); }) ->filter(function ($text) { return $text !== ''; }) ->implode("\n\n"); // اگر همچنان خالی ماند (فقط در صورتی که دیتابیس واقعاً رکورد نداشته باشد) if (empty($nodeContentAsDescription)) { $nodeContentAsDescription = mb_convert_encoding(trim((string)$item->description), 'UTF-8', 'UTF-8'); } return [ 'id' => $item->id, 'title' => $item->title, 'category_id' => $item->category_id, 'category_name' => optional($item->category)->name, 'approval_authority_id' => $item->approval_authority_id, 'approval_authority_name' => optional($item->approvalAuthority)->name, 'approval_date' => $item->approval_date, 'promulgation_authority_id' => $item->promulgation_authority_id, 'promulgation_authority_name'=> optional($item->promulgationAuthority)->name, 'promulgation_date' => $item->promulgation_date, 'promulgation_number' => $item->promulgation_number, 'official_gazette_number' => $item->official_gazette_number, 'official_gazette_date' => $item->official_gazette_date, 'execution_date' => $item->execution_date, 'bylaw_type' => $item->bylaw_type, 'global_status' => $item->global_status, 'pdf_file_url' => $isLocked ? null : ($item->pdf_file_path ? asset('storage/' . $item->pdf_file_path) : null), // نتیجه استخراج ۱۰۰٪ در اینجا می‌نشیند 'description' => $isLocked ? null : $nodeContentAsDescription, // 🚨 پارامتر عیب‌یابی: این عدد نشان می‌دهد چند گره متصل به این فایل در دیتابیس پیدا شد 'debug_node_count' => $itemNodes->count(), 'is_locked' => $isLocked, ]; } }