Files
BarangaySystem/app/Http/Controllers/Helpers/Permissions/UserPermissions.php
Jonathan Sykes fbb7e3ff37
Some checks failed
tests / PHP 8.2 (swoole-5.1.6) (push) Has been cancelled
tests / PHP 8.3 (swoole-5.1.6) (push) Has been cancelled
tests / PHP 8.4 (swoole-6.0) (push) Has been cancelled
feat: implement barangay system phases 2-14
Complete adaptation from BukidBountyApp to Philippine barangay governance:

- Barangay models: Resident, Household, HouseholdMember, Blotter, BlotterHearing,
  DocumentRequest, RequestPayment, RequestType, BarangayProject, BarangayBudget
- Controllers: ResidentController, HouseholdController, BlotterController,
  BlotterHearingController, DocumentRequestController, RequestTypeController,
  ProjectController, BudgetController, QRPHController, AdminConsoleController,
  UserController, FileController, ChapterController, LoginController
- Vue pages: Home, ManageResidents, ResidentProfile, ManageHouseholds, ManageBlotters,
  BlotterDetail, RequestDocument, ManageDocumentRequests, DocumentRequestDetail,
  ManageRequestTypes, ManageProjects, BudgetLedger, AdminConsole
- Barangay roles: PunongBarangay, Kagawad, Secretary, Treasurer, SK, Tanod, BHW, Staff, Resident
- UserPermissions matrix rewritten with barangay-specific permission mappings
- VueRouteMap replaced with barangay SPA routes
- UserActions enum references corrected across all controllers
- Removed all market/cooperative/POS/subscription code and models
2026-06-07 03:09:09 +08:00

620 lines
23 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Http\Controllers\Helpers\Permissions;
use App\Enums\UserTypes;
use App\Models\User;
use Hypervel\Support\Facades\Auth;
use App\Enums\UserActions;
class UserPermissions
{
use Roles;
use PermissionsCheck;
public static function IsParentofTargetUser(string|int $hashkeyORId)
{
$currentUser = Auth::user();
if (!$currentUser) return false;
$targetUser = is_numeric($hashkeyORId)
? User::find($hashkeyORId)
: User::where('hashkey', $hashkeyORId)->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('/(?<!^)[A-Z]/', '_$0', $role));
if ($e = UserActions::tryFrom($snake)) return $e;
throw new \InvalidArgumentException("Unknown role: {$role}");
}
public static function isAncestorOf(User|int|string $ancestor, User|int|string $descendant): bool
{
if (!$ancestor || !$descendant) return false;
$ancestorUser = $ancestor instanceof User ? $ancestor : (is_numeric($ancestor) ? User::find((int)$ancestor) : User::where('hashkey', $ancestor)->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 => [],
};
}
}