first(); if (!$targetUser) return false; return $targetUser->parentuid === $currentUser->id; } public static function isAncestorOrFamilyOfTargetUser(string|int $hashkeyORId): bool { $currentUser = Auth::user(); if (!$currentUser) return false; $targetUser = is_numeric($hashkeyORId) ? User::find($hashkeyORId) : User::where('hashkey', $hashkeyORId)->first(); if (!$targetUser) return false; if ($currentUser->id === $targetUser->id) return true; if ($targetUser->getAllDescendants()->contains($currentUser)) return true; $parent = $targetUser->parent; while ($parent) { if ($parent->id === $currentUser->id) return true; $parent = $parent->parent; } return false; } public static function isIndirectParentOfTargetUser(string|int $hashkeyORId): bool { $currentUser = Auth::user(); if (!$currentUser) return false; $targetUser = is_numeric($hashkeyORId) ? User::find($hashkeyORId) : User::where('hashkey', $hashkeyORId)->first(); if (!$targetUser) return false; if ($currentUser->id === $targetUser->id) return false; $descendants = $targetUser->getAllDescendants(); if ($descendants->contains($currentUser)) { if ($targetUser->parentuid === $currentUser->id) return false; return true; } return false; } private static function safeUserActionFromString(string $value): ?UserActions { foreach (UserActions::cases() as $case) { if ($case->value === $value) return $case; } return null; } public static function isDescendantOfCurrentUser(string|int|User|null $hashkeyOrId): bool { if (!$hashkeyOrId) return false; $currentUser = Auth::user(); if (!$currentUser) return false; $targetUser = $hashkeyOrId instanceof User ? $hashkeyOrId : (is_numeric($hashkeyOrId) ? User::find($hashkeyOrId) : User::where('hashkey', $hashkeyOrId)->first()); if (!$targetUser || $currentUser->id === $targetUser->id) return false; $descendants = $currentUser->getAllDescendants(); return $descendants->contains('id', $targetUser->id); } public static function isActionPermitted(string|int|UserTypes $hashkeyORId, UserActions $userActions) { $currentUser = Auth::user(); if (!Auth::check()) return false; $currentUserType = $currentUser->acct_type; if (!($currentUserType instanceof UserTypes)) { $currentUserType = UserTypes::tryFrom($currentUserType) ?? UserTypes::PUBLIC; } $isDeniedRoles = self::isUserDeniedRoles($userActions); if ($isDeniedRoles) return false; if (self::CheckifRoleDoesNotRequireaTargetUser($userActions)) { $preliminary_permission = true; } elseif (($hashkeyORId || $hashkeyORId === 0 || $hashkeyORId === '0') && !($hashkeyORId instanceof UserTypes)) { $preliminary_permission = self::isUserPreliminaryPermissionAllowed($hashkeyORId); } elseif ($hashkeyORId instanceof UserTypes) { $preliminary_permission = self::isUserPreliminaryPermissionAllowed($hashkeyORId); } else { $preliminary_permission = false; } if (!$preliminary_permission) return false; $permissionString = $userActions->value; $permissionEnum = self::safeUserActionFromString($permissionString); $allowedThroughAdditionalRoles = self::isUserAllowedbyAdditionalRoles($userActions); $isPermissionAllowed = (isset(self::roles()[$currentUserType->value]) && in_array($permissionEnum, self::roles()[$currentUserType->value])); return $permissionEnum && ($isPermissionAllowed || $allowedThroughAdditionalRoles); } private static function isUserPreliminaryPermissionAllowed(string|int|UserTypes $hashkeyORId) { $currentUser = Auth::user(); if (!$currentUser) return false; if ($currentUser->acct_type === UserTypes::SUPER_ADMIN) return true; $currentUserType = $currentUser->acct_type; if (!($currentUserType instanceof UserTypes)) { $currentUserType = UserTypes::tryFrom($currentUserType) ?? UserTypes::PUBLIC; } $allowedUserTypes = UserTypeService::getAllowedUserTypes($currentUserType); if ($hashkeyORId instanceof UserTypes) { $isTypeAllowedtobeModified = in_array($hashkeyORId, $allowedUserTypes); } else { try { $TargetUser = is_string($hashkeyORId) ? User::where('hashkey', $hashkeyORId)->first() : User::where('id', $hashkeyORId)->first(); $target_acct_type = $TargetUser->acct_type; $isTypeAllowedtobeModified = in_array($target_acct_type, $allowedUserTypes); } catch (\Throwable $th) { throw new \Exception('' . $th->getMessage()); } } $IndirectParent = self::isDescendantOfCurrentUser($hashkeyORId); $isSelf = $currentUser->hashkey === $hashkeyORId; return ($IndirectParent || $isSelf) && $isTypeAllowedtobeModified; } public static function isUserAllowedbyAdditionalRoles(UserActions $userActions): bool { $currentUser = Auth::user(); if (!$currentUser) return false; $additionalRoles = $currentUser->additional_roles ?? []; if (empty($additionalRoles)) return false; foreach ($additionalRoles as $role) { if ($role instanceof UserActions) { if ($role === $userActions) return true; } elseif (is_string($role)) { if ($role === $userActions->value) return true; } } return false; } public static function isUserDeniedRoles(UserActions $userActions) { $currentUser = User::findOrFail(Auth::id()); if (!$currentUser) return false; if ($currentUser->acct_type === UserTypes::SUPER_ADMIN) return false; $currentUserAdditionalRoles = $currentUser->denied_roles ?? []; if (empty($currentUserAdditionalRoles)) return false; return in_array($userActions->value, $currentUserAdditionalRoles); } public static function getUserRoles(int $id) { try { $currentUser = User::findOrFail($id); } catch (\Throwable $th) { return false; } $acct_type = $currentUser->acct_type; $defaultuserRoles = self::roles()[$acct_type->value] ?? []; $additionalRoles = $currentUser->additional_roles ?? []; $deniedRoles = $currentUser->denied_roles ?? []; $mergedRoles = array_merge($defaultuserRoles, $additionalRoles); $uniqueRoles = []; foreach ($mergedRoles as $role) { $uniqueRoles[$role->value] = $role; } foreach ($deniedRoles as $denied) { unset($uniqueRoles[$denied->value]); } return array_values($uniqueRoles); } public static function normalizeRole(UserActions|string $role): UserActions { if ($role instanceof UserActions) return $role; if ($e = UserActions::tryFrom($role)) return $e; foreach (UserActions::cases() as $case) { if ($case->name === $role) return $case; } $snake = strtolower(preg_replace('/(?first()); $descendantUser = $descendant instanceof User ? $descendant : (is_numeric($descendant) ? User::find((int)$descendant) : User::where('hashkey', $descendant)->first()); if (!$ancestorUser || !$descendantUser) return false; if ($ancestorUser->id === $descendantUser->id) return true; $parent = $descendantUser->parent; while ($parent) { if ($parent->id === $ancestorUser->id) return true; $parent = $parent->parent; } return false; } } trait PermissionsCheck { public static function isUserModificationAllowed(string|int $hashkeyORId): bool { return self::isActionPermitted($hashkeyORId, UserActions::ModifyUser); } public static function isUserSetActiveAllowed(string|int $hashkeyORId): bool { return self::isActionPermitted($hashkeyORId, UserActions::SetActiveUser); } public static function isUserSetInactiveAllowed(string|int $hashkeyORId): bool { return self::isActionPermitted($hashkeyORId, UserActions::SetInActiveUser); } public static function isUserDeletionAllowed(string|int $hashkeyORId): bool { return self::isActionPermitted($hashkeyORId, UserActions::DeleteUser); } public static function isUserExecChangeAllowed(string|int $hashkeyORId) { return self::isActionPermitted($hashkeyORId, UserActions::UpdateUserExec); } public static function isUserExecViewingAllowed(string|int $hashkeyORId) { return self::isActionPermitted($hashkeyORId, UserActions::ViewUserExec); } public static function isUserExecDeletionAllowed(string|int $hashkeyORId) { return self::isActionPermitted($hashkeyORId, UserActions::DeleteUserExec); } public static function isUserNotesViewingAllowed(string|int $hashkeyORId) { return self::isActionPermitted($hashkeyORId, UserActions::ViewUserNotes); } public static function isUserNotesUpdateAllowed(string|int $hashkeyORId) { return self::isActionPermitted($hashkeyORId, UserActions::SetUserNotes); } public static function isUserNotesDeletionAllowed(string|int $hashkeyORId) { return self::isActionPermitted($hashkeyORId, UserActions::DeleteUserNotes); } public static function isUserPasswordChangeAllowed(string|int $hashkeyORId) { return self::isActionPermitted($hashkeyORId, UserActions::ChangeUserPassword); } public static function isForceLogoutUserAllowed(string|int $hashkeyORId) { return self::isActionPermitted($hashkeyORId, UserActions::ForceLogoutUser); } public static function isUserAllowedtoViewAnotherUserRoles(string|int $hashkeyORId) { return self::isActionPermitted($hashkeyORId, UserActions::UserAllowedtoChangeAnotherUserRoles); } public static function isUserAllowedtoViewSelfRoles(string|int $hashkeyORId) { return self::isActionPermitted($hashkeyORId, UserActions::UserAllowedtoViewSelfRoles); } public static function isUserAllowedtoChangeAnotherUserRoles(string|int $hashkeyORId) { return self::isActionPermitted($hashkeyORId, UserActions::UserAllowedtoChangeAnotherUserRoles); } public function isUserAllowedtoChangeParent(string|int $hashkeyORId) { return self::isActionPermitted($hashkeyORId, UserActions::ChangeAnotherUsersParent); } } trait Roles { public static $RoleswithNoTargetUser = [ UserActions::ViewAllUserTypes, UserActions::ListAllUsersAsParentforUserCreation, UserActions::CheckifMobileNumberExists, UserActions::CheckifUsernameExists, UserActions::ViewAllFiles, UserActions::UploadAllFiles, UserActions::DeleteAllFiles, UserActions::ModifyAllFiles, UserActions::ViewGlobalReports, UserActions::CreateAnnouncement, UserActions::ModifyAnnouncement, UserActions::DeleteAnnouncement, UserActions::ViewAllAnnouncements, UserActions::ViewAccountingReports, UserActions::ManageAccounting, UserActions::UltimateConsole, UserActions::UltimateLogs, UserActions::UltimateReports, UserActions::UltimateMaintenance, UserActions::UltimateQuery, UserActions::UltimateGlobalMessage, UserActions::UltimateFlush, UserActions::ManageLandingPages, UserActions::ManageQrphPaymentCode, UserActions::ViewChapterOrgChart, UserActions::ManageChapterMembers, UserActions::ViewScopedMemberReports, UserActions::AssignChapterOfficer, UserActions::ViewResidents, UserActions::ManageResidents, UserActions::ImportResidents, UserActions::ExportResidents, UserActions::ViewHouseholds, UserActions::ManageHouseholds, UserActions::ViewDocumentRequests, UserActions::CreateDocumentRequest, UserActions::ProcessDocumentRequest, UserActions::ManageRequestTypes, UserActions::ManageFeeSchedules, UserActions::ViewBlotters, UserActions::CreateBlotter, UserActions::ProcessBlotter, UserActions::ManageBlotterHearings, UserActions::ViewBarangayProjects, UserActions::ManageBarangayProjects, UserActions::ViewBarangayBudget, UserActions::ManageBarangayBudget, UserActions::ViewFeePayments, UserActions::ManageFeePayments, ]; public static function CheckifRoleDoesNotRequireaTargetUser(UserActions $userAction): bool { return in_array($userAction, self::$RoleswithNoTargetUser, true); } public static function roles() { // Barangay staff actions shared by most staff roles $staffReadActions = [ UserActions::ViewResidents, UserActions::ViewHouseholds, UserActions::ViewBlotters, UserActions::ViewDocumentRequests, UserActions::ViewBarangayProjects, UserActions::ViewBarangayBudget, UserActions::ViewAllAnnouncements, UserActions::ViewAllFiles, UserActions::ViewUserInfo, UserActions::ViewChapterOrgChart, UserActions::ViewFeePayments, UserActions::ViewAllUserTypes, UserActions::ListAllUsersAsParentforUserCreation, UserActions::CheckifMobileNumberExists, UserActions::CheckifUsernameExists, ]; $staffManageActions = array_merge($staffReadActions, [ UserActions::ManageResidents, UserActions::ImportResidents, UserActions::ExportResidents, UserActions::ManageHouseholds, UserActions::CreateBlotter, UserActions::ProcessBlotter, UserActions::ManageBlotterHearings, UserActions::ProcessDocumentRequest, UserActions::ManageRequestTypes, UserActions::ManageFeeSchedules, UserActions::ManageBarangayProjects, UserActions::ManageBarangayBudget, UserActions::ManageFeePayments, UserActions::CreateAnnouncement, UserActions::ModifyAnnouncement, UserActions::DeleteAnnouncement, UserActions::UploadAllFiles, UserActions::ModifyAllFiles, UserActions::DeleteAllFiles, UserActions::ManageUserInfo, UserActions::ManageChapterMembers, UserActions::AssignChapterOfficer, ]); return [ // Super admin gets everything UserTypes::SUPER_ADMIN->value => UserActions::cases(), // Punong Barangay - full management UserTypes::PUNONG_BARANGAY->value => array_merge($staffManageActions, [ UserActions::UltimateConsole, UserActions::UltimateLogs, UserActions::UltimateMaintenance, UserActions::UltimateGlobalMessage, UserActions::UltimateFlush, UserActions::UltimateReports, UserActions::ManageQrphPaymentCode, UserActions::ManageLandingPages, UserActions::ModifyGlobalReports, UserActions::ViewGlobalReports, UserActions::ViewAccountingReports, UserActions::ManageAccounting, UserActions::ViewScopedMemberReports, UserActions::CreateUser, UserActions::ModifyUser, UserActions::SetActiveUser, UserActions::SetInActiveUser, UserActions::ChangeUserPassword, UserActions::ForceLogoutUser, UserActions::UserAllowedtoChangeAnotherUserRoles, UserActions::UserAllowedtoViewOtherUserRoles, UserActions::UserAllowedtoViewAllRoles, UserActions::ChangeAnotherUsersParent, UserActions::ViewAllUserTypes, UserActions::CreateUserSecretary, UserActions::CreateUserTreasurer, UserActions::CreateUserKagawad, UserActions::CreateUserSkChairperson, UserActions::CreateUserSkCouncilor, UserActions::CreateUserTanod, UserActions::CreateUserBhw, UserActions::CreateUserDaycareWorker, UserActions::CreateUserStaff, UserActions::CreateUserResident, UserActions::CreateUserAudit, ]), // Kagawad - manage operations, limited admin UserTypes::KAGAWAD->value => array_merge($staffManageActions, [ UserActions::ViewGlobalReports, UserActions::ViewAccountingReports, UserActions::ViewScopedMemberReports, UserActions::CreateUserResident, ]), // Secretary - records & documents focus UserTypes::SECRETARY->value => array_merge($staffManageActions, [ UserActions::ViewGlobalReports, UserActions::CreateUserResident, UserActions::ModifyUser, UserActions::SetActiveUser, UserActions::SetInActiveUser, ]), // Treasurer - budget & payments focus UserTypes::TREASURER->value => array_merge($staffReadActions, [ UserActions::ViewBarangayBudget, UserActions::ManageBarangayBudget, UserActions::ViewFeePayments, UserActions::ManageFeePayments, UserActions::ViewDocumentRequests, UserActions::ProcessDocumentRequest, UserActions::ViewAccountingReports, UserActions::ManageAccounting, UserActions::ManageQrphPaymentCode, UserActions::UploadAllFiles, UserActions::ModifyAllFiles, ]), // SK Chairperson - youth & projects UserTypes::SK_CHAIRPERSON->value => array_merge($staffReadActions, [ UserActions::ManageBarangayProjects, UserActions::CreateAnnouncement, UserActions::ModifyAnnouncement, UserActions::UploadAllFiles, UserActions::CreateUserSkCouncilor, UserActions::CreateUserResident, ]), // SK Councilor - limited support UserTypes::SK_COUNCILOR->value => array_merge($staffReadActions, [ UserActions::CreateAnnouncement, UserActions::UploadAllFiles, ]), // Tanod - blotter & security UserTypes::TANOD->value => array_merge($staffReadActions, [ UserActions::CreateBlotter, UserActions::ProcessBlotter, UserActions::ManageBlotterHearings, UserActions::UploadAllFiles, ]), // BHW - residents & health UserTypes::BHW->value => array_merge($staffReadActions, [ UserActions::ManageResidents, UserActions::ManageHouseholds, UserActions::UploadAllFiles, ]), // Daycare Worker - basic read + residents UserTypes::DAYCARE_WORKER->value => array_merge($staffReadActions, [ UserActions::ManageResidents, UserActions::UploadAllFiles, ]), // Staff - general operations UserTypes::STAFF->value => array_merge($staffReadActions, [ UserActions::ProcessDocumentRequest, UserActions::CreateBlotter, UserActions::UploadAllFiles, UserActions::CreateUserResident, ]), // Resident - self-service only UserTypes::RESIDENT->value => [ UserActions::CreateDocumentRequest, UserActions::ViewDocumentRequests, UserActions::ViewAllAnnouncements, UserActions::ViewUserInfo, UserActions::ManageUserInfo, UserActions::UserAllowedtoViewSelfRoles, UserActions::CheckifMobileNumberExists, UserActions::CheckifUsernameExists, UserActions::UploadAllFiles, ], // Audit - read-only across the system UserTypes::AUDIT->value => array_merge($staffReadActions, [ UserActions::ViewGlobalReports, UserActions::ViewAccountingReports, UserActions::ViewScopedMemberReports, ]), ]; } } class UserTypeService { public static function getAllowedUserTypes(UserTypes $currentUserType): array { return match ($currentUserType) { UserTypes::SUPER_ADMIN => UserTypes::cases(), UserTypes::PUNONG_BARANGAY => [ UserTypes::KAGAWAD, UserTypes::SECRETARY, UserTypes::TREASURER, UserTypes::SK_CHAIRPERSON, UserTypes::SK_COUNCILOR, UserTypes::TANOD, UserTypes::BHW, UserTypes::DAYCARE_WORKER, UserTypes::STAFF, UserTypes::RESIDENT, UserTypes::AUDIT, ], UserTypes::KAGAWAD, UserTypes::SECRETARY => [ UserTypes::RESIDENT, ], UserTypes::SK_CHAIRPERSON => [ UserTypes::SK_COUNCILOR, UserTypes::RESIDENT, ], default => [], }; } }