217 lines
7.0 KiB
PHP
217 lines
7.0 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Navigation;
|
|
|
|
use App\Models\NavItem;
|
|
use App\Models\Role;
|
|
use App\Models\RoleNavItem;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class NavBuilderService
|
|
{
|
|
public function __construct(private NavbarService $navbarService)
|
|
{
|
|
}
|
|
|
|
public function getAdminPayload(): array
|
|
{
|
|
$all = NavItem::query()
|
|
->orderBy('menu_parent_id', 'asc')
|
|
->orderBy('label', 'asc')
|
|
->orderBy('id', 'asc')
|
|
->get()
|
|
->toArray();
|
|
|
|
$byId = [];
|
|
foreach ($all as $row) {
|
|
$row['children'] = [];
|
|
$byId[$row['id']] = $row;
|
|
}
|
|
|
|
$tree = [];
|
|
foreach ($byId as $id => &$node) {
|
|
$pid = $node['menu_parent_id'] ?: null;
|
|
if ($pid && isset($byId[$pid])) {
|
|
$byId[$pid]['children'][] = &$node;
|
|
} else {
|
|
$tree[] = &$node;
|
|
}
|
|
}
|
|
unset($node);
|
|
|
|
$this->sortTreeAlpha($tree);
|
|
|
|
$flatAlpha = $all;
|
|
usort($flatAlpha, fn ($a, $b) => strnatcasecmp($this->labelKey($a['label'] ?? ''), $this->labelKey($b['label'] ?? '')));
|
|
|
|
$roleAssignments = $this->buildRoleAssignments();
|
|
$flattened = $this->flattenTreeForResponse($tree, $roleAssignments);
|
|
|
|
$parentOptions = array_map(static function ($row) {
|
|
return [
|
|
'id' => (int) ($row['id'] ?? 0),
|
|
'label' => (string) ($row['label'] ?? ''),
|
|
];
|
|
}, $flatAlpha);
|
|
|
|
$roles = Role::query()->orderBy('name')->get(['id', 'name'])->toArray();
|
|
|
|
return [
|
|
'items' => $flattened,
|
|
'roles' => $roles,
|
|
'parentOptions' => $parentOptions,
|
|
];
|
|
}
|
|
|
|
public function save(array $payload): array
|
|
{
|
|
$id = (int) ($payload['id'] ?? 0);
|
|
$menuParentId = $payload['menu_parent_id'] ?? $payload['parent_id'] ?? null;
|
|
$menuParentId = ($menuParentId === '' || $menuParentId === null) ? null : (int) $menuParentId;
|
|
if ($id && $menuParentId === $id) {
|
|
$menuParentId = null;
|
|
}
|
|
|
|
if ($menuParentId !== null && !NavItem::query()->whereKey($menuParentId)->exists()) {
|
|
return ['ok' => false, 'message' => 'Selected parent does not exist.'];
|
|
}
|
|
|
|
$data = [
|
|
'menu_parent_id' => $menuParentId,
|
|
'label' => trim((string) ($payload['label'] ?? '')),
|
|
'url' => trim((string) ($payload['url'] ?? '')) ?: null,
|
|
'icon_class' => trim((string) ($payload['icon_class'] ?? '')) ?: null,
|
|
'target' => trim((string) ($payload['target'] ?? '')) ?: null,
|
|
'sort_order' => (int) ($payload['sort_order'] ?? 0),
|
|
'is_enabled' => !empty($payload['is_enabled']) ? 1 : 0,
|
|
];
|
|
|
|
$roleIds = array_values(array_unique(array_filter(
|
|
array_map('intval', (array) ($payload['roles'] ?? [])),
|
|
fn ($v) => $v > 0
|
|
)));
|
|
|
|
$navItemId = null;
|
|
|
|
DB::transaction(function () use ($id, $data, $roleIds, &$navItemId): void {
|
|
if ($id > 0) {
|
|
NavItem::query()->whereKey($id)->update($data);
|
|
$navItemId = $id;
|
|
} else {
|
|
$navItemId = (int) NavItem::query()->create($data)->id;
|
|
}
|
|
|
|
RoleNavItem::query()->where('nav_item_id', $navItemId)->delete();
|
|
foreach ($roleIds as $rid) {
|
|
RoleNavItem::query()->create([
|
|
'role_id' => $rid,
|
|
'nav_item_id' => $navItemId,
|
|
]);
|
|
}
|
|
});
|
|
|
|
$this->navbarService->clearCache();
|
|
|
|
return ['ok' => true, 'id' => $navItemId];
|
|
}
|
|
|
|
public function delete(int $id): bool
|
|
{
|
|
$deleted = (bool) NavItem::query()->whereKey($id)->delete();
|
|
$this->navbarService->clearCache();
|
|
|
|
return $deleted;
|
|
}
|
|
|
|
public function reorder(array $orders): void
|
|
{
|
|
DB::transaction(function () use ($orders): void {
|
|
foreach ($orders as $id => $order) {
|
|
NavItem::query()->whereKey((int) $id)->update(['sort_order' => (int) $order]);
|
|
}
|
|
});
|
|
|
|
$this->navbarService->clearCache();
|
|
}
|
|
|
|
private function buildRoleAssignments(): array
|
|
{
|
|
$rows = RoleNavItem::query()
|
|
->select('role_nav_items.nav_item_id', 'roles.id as role_id', 'roles.name as role_name')
|
|
->join('roles', 'roles.id', '=', 'role_nav_items.role_id')
|
|
->get()
|
|
->toArray();
|
|
|
|
$roleAssignments = [];
|
|
foreach ($rows as $row) {
|
|
$navId = (int) ($row['nav_item_id'] ?? 0);
|
|
if ($navId <= 0) {
|
|
continue;
|
|
}
|
|
|
|
$roleId = (int) ($row['role_id'] ?? 0);
|
|
$roleName = $row['role_name'] ?? ($roleId ? ('#' . $roleId) : null);
|
|
|
|
if ($roleId > 0) {
|
|
$roleAssignments[$navId]['ids'][] = $roleId;
|
|
}
|
|
if ($roleName !== null) {
|
|
$roleAssignments[$navId]['names'][] = $roleName;
|
|
}
|
|
}
|
|
|
|
return $roleAssignments;
|
|
}
|
|
|
|
private function flattenTreeForResponse(array $tree, array $roleAssignments, int $depth = 0): array
|
|
{
|
|
$output = [];
|
|
foreach ($tree as $node) {
|
|
$id = (int) ($node['id'] ?? 0);
|
|
$roles = $roleAssignments[$id] ?? ['ids' => [], 'names' => []];
|
|
|
|
$output[] = [
|
|
'id' => $id,
|
|
'label' => (string) ($node['label'] ?? ''),
|
|
'url' => $node['url'] ?? null,
|
|
'icon_class' => $node['icon_class'] ?? null,
|
|
'target' => $node['target'] ?? null,
|
|
'menu_parent_id' => $node['menu_parent_id'] ?? null,
|
|
'sort_order' => (int) ($node['sort_order'] ?? 0),
|
|
'is_enabled' => (int) ($node['is_enabled'] ?? 0),
|
|
'depth' => $depth,
|
|
'roles' => [
|
|
'ids' => array_values(array_unique($roles['ids'] ?? [])),
|
|
'names' => array_values(array_unique($roles['names'] ?? [])),
|
|
],
|
|
];
|
|
|
|
if (!empty($node['children'])) {
|
|
$output = array_merge($output, $this->flattenTreeForResponse($node['children'], $roleAssignments, $depth + 1));
|
|
}
|
|
}
|
|
|
|
return $output;
|
|
}
|
|
|
|
private function labelKey(string $s): string
|
|
{
|
|
$s = trim(preg_replace('/\s+/', ' ', $s));
|
|
$s = preg_replace('/^[^[:alnum:]]+/u', '', $s);
|
|
$s = preg_replace('/^(?i)(the|an|a)\s+/', '', $s);
|
|
return mb_strtolower($s, 'UTF-8');
|
|
}
|
|
|
|
private function sortTreeAlpha(array &$nodes): void
|
|
{
|
|
usort($nodes, fn ($a, $b) => strnatcasecmp($this->labelKey($a['label'] ?? ''), $this->labelKey($b['label'] ?? '')));
|
|
foreach ($nodes as &$node) {
|
|
if (!empty($node['children'])) {
|
|
$this->sortTreeAlpha($node['children']);
|
|
}
|
|
}
|
|
unset($node);
|
|
}
|
|
}
|