From 4d150146210b61f77b639df9c38f4d1fbd9541c0 Mon Sep 17 00:00:00 2001 From: "m.razaghi" Date: Sun, 12 Jul 2026 16:24:16 +0330 Subject: [PATCH] Fixed api issue --- .../api/GlobalSearchApiController.php | 177 ++++++++---------- 1 file changed, 83 insertions(+), 94 deletions(-) diff --git a/app/Http/Controllers/api/GlobalSearchApiController.php b/app/Http/Controllers/api/GlobalSearchApiController.php index 4276f82..bf15e5e 100644 --- a/app/Http/Controllers/api/GlobalSearchApiController.php +++ b/app/Http/Controllers/api/GlobalSearchApiController.php @@ -42,15 +42,16 @@ class GlobalSearchApiController extends Controller $perPage = min(max((int) $request->input('per_page', 15), 10), 100); $page = (int) $request->input('page', 1); - $results = collect(); + $staticResults = collect(); // For Art & Structure (IGNORES SORTING) + $sortableResults = collect(); // For Bylaws, Precedents, Opinions (SORTS BASED ON REQUEST) if (!empty($searchTerm)) { $limitPerTable = 100; // ========================================================= - // 1. Search Arts FIRST + // 1. Search Arts FIRST (Exact match to ArtController) // ========================================================= - $arts = Art::with(['law', 'law.category', 'chapter', 'part', 'volum', 'book', 'section', 'gate']) + $arts = Art::with(['law.category', 'chapter', 'part', 'volum', 'book', 'section', 'gate']) ->where(function ($q) use ($searchTerm) { $q->where('title', 'LIKE', "%{$searchTerm}%") ->orWhere('text', 'LIKE', "%{$searchTerm}%") @@ -61,63 +62,10 @@ class GlobalSearchApiController extends Controller $formatted['type'] = 'art'; return $formatted; }); - $results = $results->concat($arts); // Arts are prioritized! + $staticResults = $staticResults->concat($arts); // ========================================================= - // 2. Search Bylaw Documents - // ========================================================= - $bylaws = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority']) - ->where('is_published', true) - ->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}%"); - }); - })->take($limitPerTable)->get()->map(function($item) { - $formatted = $this->formatBylaw($item); - $formatted['type'] = 'bylaw'; - return $formatted; - }); - $results = $results->concat($bylaws); - - // ========================================================= - // 3. Search Judicial Precedents - // ========================================================= - $judicials = JudicialPrecedent::with('category') - ->where(function ($q) use ($searchTerm) { - $q->where('ruling_number', 'LIKE', "%{$searchTerm}%") - ->orWhere('subject', 'LIKE', "%{$searchTerm}%") - ->orWhere('full_text', 'LIKE', "%{$searchTerm}%"); - })->take($limitPerTable)->get()->map(function($item) { - $formatted = $this->formatJudicial($item); - $formatted['type'] = 'judicial_precedent'; - return $formatted; - }); - $results = $results->concat($judicials); - - // ========================================================= - // 4. Search Legal Opinions - // ========================================================= - $opinions = LegalOpinion::with('category') - ->where(function ($q) use ($searchTerm) { - $q->where('opinion_number', 'LIKE', "%{$searchTerm}%") - ->orWhere('subject', 'LIKE', "%{$searchTerm}%") - ->orWhere('full_text', 'LIKE', "%{$searchTerm}%") - ->orWhere('issuing_authority', 'LIKE', "%{$searchTerm}%"); - })->take($limitPerTable)->get()->map(function($item) { - $formatted = $this->formatOpinion($item); - $formatted['type'] = 'legal_opinion'; - return $formatted; - }); - $results = $results->concat($opinions); - - // ========================================================= - // 5. Search Laws and Structural Models + // 2. Search Laws and Structural Models (Exact match to ArtController) // ========================================================= $structuralModels = [ Law::class, Volum::class, Book::class, Division::class, @@ -138,40 +86,95 @@ class GlobalSearchApiController extends Controller return $formatted; }); - $results = $results->concat($items); + $staticResults = $staticResults->concat($items); } + + // ========================================================= + // 3. Search Bylaw Documents + // ========================================================= + $bylaws = BylawDocument::with(['category', 'approvalAuthority', 'promulgationAuthority']) + ->where('is_published', true) + ->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}%"); + }); + })->take($limitPerTable)->get()->map(function($item) { + $formatted = $this->formatBylaw($item); + $formatted['type'] = 'bylaw'; + return $formatted; + }); + $sortableResults = $sortableResults->concat($bylaws); + + // ========================================================= + // 4. Search Judicial Precedents + // ========================================================= + $judicials = JudicialPrecedent::with('category') + ->where(function ($q) use ($searchTerm) { + $q->where('ruling_number', 'LIKE', "%{$searchTerm}%") + ->orWhere('subject', 'LIKE', "%{$searchTerm}%") + ->orWhere('full_text', 'LIKE', "%{$searchTerm}%"); + })->take($limitPerTable)->get()->map(function($item) { + $formatted = $this->formatJudicial($item); + $formatted['type'] = 'judicial_precedent'; + return $formatted; + }); + $sortableResults = $sortableResults->concat($judicials); + + // ========================================================= + // 5. Search Legal Opinions + // ========================================================= + $opinions = LegalOpinion::with('category') + ->where(function ($q) use ($searchTerm) { + $q->where('opinion_number', 'LIKE', "%{$searchTerm}%") + ->orWhere('subject', 'LIKE', "%{$searchTerm}%") + ->orWhere('full_text', 'LIKE', "%{$searchTerm}%") + ->orWhere('issuing_authority', 'LIKE', "%{$searchTerm}%"); + })->take($limitPerTable)->get()->map(function($item) { + $formatted = $this->formatOpinion($item); + $formatted['type'] = 'legal_opinion'; + return $formatted; + }); + $sortableResults = $sortableResults->concat($opinions); } - // --- Apply Dynamic Memory Sorting --- + // --- Apply Sorting ONLY to $sortableResults (Arts and Structures are ignored!) --- if ($request->has('sort_by') && $request->input('sort_by') != null) { $sortBy = $request->input('sort_by'); $sortOrder = $request->input('sort', 'desc'); if ($sortBy === 'number') { $sortCallback = function ($item) { - return (string) ($item['promulgation_number'] ?? $item['ruling_number'] ?? $item['opinion_number'] ?? $item['number'] ?? ''); + return (string) ($item['promulgation_number'] ?? $item['ruling_number'] ?? $item['opinion_number'] ?? '0'); }; if ($sortOrder === 'asc') { - $results = $results->sortBy($sortCallback, SORT_NATURAL)->values(); + $sortableResults = $sortableResults->sortBy($sortCallback, SORT_NATURAL)->values(); } else { - $results = $results->sortByDesc($sortCallback, SORT_NATURAL)->values(); + $sortableResults = $sortableResults->sortByDesc($sortCallback, SORT_NATURAL)->values(); } } elseif ($sortBy === 'date') { $sortCallback = function ($item) { - // FIX: Fallback to created_at so Articles are not pushed to the end of the list! - return (string) ($item['approval_date'] ?? $item['ruling_date'] ?? $item['opinion_date'] ?? $item['created_at'] ?? '1970-01-01'); + return (string) ($item['approval_date'] ?? $item['ruling_date'] ?? $item['opinion_date'] ?? '1970-01-01'); }; if ($sortOrder === 'asc') { - $results = $results->sortBy($sortCallback)->values(); + $sortableResults = $sortableResults->sortBy($sortCallback)->values(); } else { - $results = $results->sortByDesc($sortCallback)->values(); + $sortableResults = $sortableResults->sortByDesc($sortCallback)->values(); } } } - // --- Execute Manual Pagination on the Sorted Collection --- + // Combine Results: Static (Arts/Structures) ALWAYS at the top, followed by Sorted Docs + $results = $staticResults->concat($sortableResults); + + // --- Execute Manual Pagination on the final Collection --- $total = $results->count(); $paginatedItems = $results->forPage($page, $perPage)->values(); @@ -207,7 +210,7 @@ class GlobalSearchApiController extends Controller if (!empty($searchTerm)) { - // 1. Search Arts FIRST + // 1. Search Arts FIRST for Fast Search $artQuery = Art::with(['law', 'law.category', 'chapter', 'part', 'volum', 'book', 'section', 'gate']) ->where(function ($q) use ($searchTerm) { $q->where('title', 'LIKE', "%{$searchTerm}%") @@ -343,7 +346,6 @@ class GlobalSearchApiController extends Controller 'promulgation_number' => $item->promulgation_number, 'description' => $isLocked ? null : $description, 'is_locked' => $isLocked, - 'created_at' => $item->created_at ? (string)$item->created_at : null, // Added for date sorting fallback ]; } @@ -360,7 +362,6 @@ class GlobalSearchApiController extends Controller 'issuing_authority' => $item->issuing_authority, 'category_name' => optional($item->category)->name, 'is_locked' => $isLocked, - 'created_at' => $item->created_at ? (string)$item->created_at : null, ]; } @@ -377,7 +378,6 @@ class GlobalSearchApiController extends Controller 'issuing_authority' => $item->issuing_authority, 'category_name' => optional($item->category)->name, 'is_locked' => $isLocked, - 'created_at' => $item->created_at ? (string)$item->created_at : null, ]; } @@ -390,19 +390,11 @@ class GlobalSearchApiController extends Controller $isLocked = ! auth()->user()->isSubscriber() && (bool) ($law?->is_locked ?? false); $text = $q->text ?? ''; + $searchQuoted = preg_quote($searchTerm, '/'); - // Multi-byte safe Context Snippet Extraction for Persian/Arabic - if (!empty($searchTerm) && !empty($text)) { - $pos = mb_stripos($text, $searchTerm); - if ($pos !== false) { - $start = max(0, $pos - 50); - $length = mb_strlen($searchTerm) + 100; - $context = mb_substr($text, $start, $length); - if ($start > 0) $context = '...' . $context; - if ($start + $length < mb_strlen($text)) $context = $context . '...'; - } else { - $context = $text; - } + // Exact regex match logic from ArtController + if (preg_match('/\b(.{0,50})\b('.$searchQuoted.')\b(.{0,50})\b/i', $text, $matches)) { + $context = trim($matches[1] ?? '').' '.($matches[2] ?? '').' '.trim($matches[3] ?? ''); } else { $context = $text; } @@ -415,19 +407,12 @@ class GlobalSearchApiController extends Controller 'is_locked' => $isLocked, 'route' => array_values($this->route($modelClass, $q)), 'category' => optional($law?->category)->name, - + 'law' => $law?->title, 'count_art' => $law?->arts?->count() ?? 0, - 'count_volums'=> $law?->volums?->count() ?? 0, + 'count_volum' => $law?->volums?->count() ?? 0, 'price' => $law?->price, 'image' => $law?->image, - 'created_at' => $q->created_at ? (string)$q->created_at : null, - // Relational Objects - 'law' => $q->law != null ? [ - 'id' => $q->law->id, - 'title' => $q->law->title, - 'number' => $q->law->number, - ] : null, 'chapter' => $q->chapter != null ? [ 'id' => $q->chapter->id, 'title' => $q->chapter->title, @@ -443,6 +428,11 @@ class GlobalSearchApiController extends Controller 'title' => $q->volum->title, 'number' => $q->volum->number, ] : null, + 'law_object' => $q->law != null ? [ + 'id' => $q->law->id, + 'title' => $q->law->title, + 'number' => $q->law->number, + ] : null, 'book' => $q->book != null ? [ 'id' => $q->book->id, 'title' => $q->book->title, @@ -472,7 +462,6 @@ class GlobalSearchApiController extends Controller 'number' => $item->number, 'is_locked' => $isLocked, 'route' => array_values($this->route($modelClass, $item)), - 'created_at'=> $item->created_at ? (string)$item->created_at : null, 'law' => $law ? [ 'id' => $law->id, 'title' => $law->title,