453 lines
18 KiB
PHP
453 lines
18 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers\api;
|
||
|
||
use App\Http\Controllers\Controller;
|
||
use App\Models\BylawDocument;
|
||
use App\Models\BylawLegalAuthority;
|
||
use App\Models\BylawSubjectCategory;
|
||
use App\Models\BylawNode;
|
||
use App\Models\BylawCodificationLink;
|
||
use App\Traits\BaseApiResponse;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\DB;
|
||
|
||
class BylawApiController extends Controller
|
||
{
|
||
use BaseApiResponse;
|
||
|
||
public function categories()
|
||
{
|
||
$categories = BylawSubjectCategory::all()->map(function ($category) {
|
||
return [
|
||
'id' => $category->id,
|
||
'name' => $category->name,
|
||
];
|
||
});
|
||
|
||
return $this->success($categories, '', '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, '', '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);
|
||
|
||
// واکشی ۱۰۰٪ قطعی گرهها با آرایههای بومی (Native PHP Arrays)
|
||
$documentIds = collect($paginator->items())->pluck('id')->toArray();
|
||
$nodesDictionary = [];
|
||
|
||
if (!empty($documentIds)) {
|
||
$nodes = DB::table('bylaw_nodes')
|
||
->whereIn('document_id', $documentIds)
|
||
->orderBy('sort_order', 'asc')
|
||
->select('document_id', 'node_content')
|
||
->get();
|
||
|
||
foreach ($nodes as $node) {
|
||
$docId = $node->document_id;
|
||
if (!isset($nodesDictionary[$docId])) {
|
||
$nodesDictionary[$docId] = [];
|
||
}
|
||
if (!empty(trim((string)$node->node_content))) {
|
||
$nodesDictionary[$docId][] = trim((string)$node->node_content);
|
||
}
|
||
}
|
||
}
|
||
|
||
$items = collect($paginator->items())->map(function ($item) use ($nodesDictionary) {
|
||
return $this->formatBylaw($item, $nodesDictionary[$item->id] ?? null);
|
||
});
|
||
|
||
$responseData = [
|
||
'items' => $items,
|
||
'current_page' => $paginator->currentPage(),
|
||
'last_page' => $paginator->lastPage(),
|
||
'per_page' => $paginator->perPage(),
|
||
'total' => $paginator->total(),
|
||
];
|
||
|
||
return $this->success($responseData, '', '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, '', '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);
|
||
|
||
$documentIds = collect($paginator->items())->pluck('id')->toArray();
|
||
$nodesDictionary = [];
|
||
|
||
if (!empty($documentIds)) {
|
||
$nodes = DB::table('bylaw_nodes')
|
||
->whereIn('document_id', $documentIds)
|
||
->orderBy('sort_order', 'asc')
|
||
->select('document_id', 'node_content')
|
||
->get();
|
||
|
||
foreach ($nodes as $node) {
|
||
$docId = $node->document_id;
|
||
if (!isset($nodesDictionary[$docId])) {
|
||
$nodesDictionary[$docId] = [];
|
||
}
|
||
if (!empty(trim((string)$node->node_content))) {
|
||
$nodesDictionary[$docId][] = trim((string)$node->node_content);
|
||
}
|
||
}
|
||
}
|
||
|
||
$items = collect($paginator->items())->map(function ($item) use ($nodesDictionary) {
|
||
return $this->formatBylaw($item, $nodesDictionary[$item->id] ?? null);
|
||
});
|
||
|
||
$responseData = [
|
||
'items' => $items,
|
||
'current_page' => $paginator->currentPage(),
|
||
'last_page' => $paginator->lastPage(),
|
||
'per_page' => $paginator->perPage(),
|
||
'total' => $paginator->total(),
|
||
];
|
||
|
||
return $this->success($responseData, '', '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();
|
||
|
||
$documentIds = $items->pluck('id')->toArray();
|
||
$nodesDictionary = [];
|
||
|
||
if (!empty($documentIds)) {
|
||
$nodes = DB::table('bylaw_nodes')
|
||
->whereIn('document_id', $documentIds)
|
||
->orderBy('sort_order', 'asc')
|
||
->select('document_id', 'node_content')
|
||
->get();
|
||
|
||
foreach ($nodes as $node) {
|
||
$docId = $node->document_id;
|
||
if (!isset($nodesDictionary[$docId])) {
|
||
$nodesDictionary[$docId] = [];
|
||
}
|
||
if (!empty(trim((string)$node->node_content))) {
|
||
$nodesDictionary[$docId][] = trim((string)$node->node_content);
|
||
}
|
||
}
|
||
}
|
||
|
||
$formattedItems = $items->map(function ($item) use ($nodesDictionary) {
|
||
return $this->formatBylaw($item, $nodesDictionary[$item->id] ?? null);
|
||
});
|
||
|
||
return $this->success([
|
||
'items' => $formattedItems,
|
||
'count' => $count
|
||
], '', '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]);
|
||
|
||
$documentIds = collect($itemsToFormat)->pluck('id')->toArray();
|
||
$nodesDictionary = [];
|
||
|
||
if (!empty($documentIds)) {
|
||
$nodes = DB::table('bylaw_nodes')
|
||
->whereIn('document_id', $documentIds)
|
||
->orderBy('sort_order', 'asc')
|
||
->select('document_id', 'node_content')
|
||
->get();
|
||
|
||
foreach ($nodes as $node) {
|
||
$docId = $node->document_id;
|
||
if (!isset($nodesDictionary[$docId])) {
|
||
$nodesDictionary[$docId] = [];
|
||
}
|
||
if (!empty(trim((string)$node->node_content))) {
|
||
$nodesDictionary[$docId][] = trim((string)$node->node_content);
|
||
}
|
||
}
|
||
}
|
||
|
||
$results = array_values(array_map(function($item) use ($nodesDictionary) {
|
||
return $this->formatBylaw($item, $nodesDictionary[$item->id] ?? null);
|
||
}, $itemsToFormat));
|
||
|
||
return $this->success($results, '', 'Pager Window Details');
|
||
}
|
||
|
||
private function isLockedForCurrentUser($item): bool
|
||
{
|
||
return ! auth()->user()->isSubscriber() && (bool) ($item->is_locked ?? false);
|
||
}
|
||
|
||
/**
|
||
* سیستم کاملاً ضد گلوله برای استخراج Description
|
||
*/
|
||
private function formatBylaw($item, $preloadedNodes = null)
|
||
{
|
||
$isLocked = $this->isLockedForCurrentUser($item);
|
||
|
||
// ۱. اولویت اول: متنی که ادمین در پنل وارد کرده است
|
||
$description = $item->description;
|
||
|
||
// ۲. اولویت دوم: اگر توضیحات ادمین خالی بود، متن قانون را از گرهها میگیریم
|
||
if (empty(trim((string)$description))) {
|
||
|
||
// اگر آرایه گرهها از قبل (در توابع index یا search) آماده شده بود:
|
||
if ($preloadedNodes !== null && is_array($preloadedNodes)) {
|
||
$description = implode("\n\n", $preloadedNodes);
|
||
|
||
// در غیر این صورت (مثلا در تابع single) مستقیماً از دیتابیس میگیریم
|
||
} else {
|
||
$nodes = DB::table('bylaw_nodes')
|
||
->where('document_id', $item->id)
|
||
->orderBy('sort_order', 'asc')
|
||
->pluck('node_content')
|
||
->toArray();
|
||
|
||
$validContents = [];
|
||
foreach ($nodes as $content) {
|
||
if (!empty(trim((string)$content))) {
|
||
$validContents[] = trim((string)$content);
|
||
}
|
||
}
|
||
$description = implode("\n\n", $validContents);
|
||
}
|
||
}
|
||
|
||
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 : $description,
|
||
'is_locked' => $isLocked,
|
||
];
|
||
}
|
||
}
|