'boolean', ]; public function navItem(): BelongsTo { return $this->belongsTo(NavItem::class); } public function grantedBy(): BelongsTo { return $this->belongsTo(User::class, 'granted_by_user_id'); } /** * Grant check with a per-page default fallback. Passes if the user * matches an explicit user/group/role grant; otherwise falls back * to the page's `default` row (deny by default — no row or * can_view=false denies). Permission-based access is checked * separately by the caller — grants extend, never revoke. */ public static function userCanAccess(User $user, NavItem $navItem): bool { if ($user->is_super_admin) return true; $groupIds = $user->groups()->pluck('groups.id'); $roleIds = $user->roles()->pluck('roles.id'); $hasExplicit = static::where('nav_item_id', $navItem->id) ->where('can_view', true) ->where(function ($q) use ($user, $groupIds, $roleIds) { $q->where(fn ($u) => $u->where('grantee_type', 'user')->where('grantee_id', $user->id)) ->orWhere(fn ($g) => $g->where('grantee_type', 'group')->whereIn('grantee_id', $groupIds)) ->orWhere(fn ($r) => $r->where('grantee_type', 'role')->whereIn('grantee_id', $roleIds)); }) ->exists(); if ($hasExplicit) return true; return (bool) static::where('nav_item_id', $navItem->id) ->where('grantee_type', 'default') ->value('can_view'); } }