Files
My-KOPKB/be/Modules/Auth/Entities/User.php
T

340 lines
9.4 KiB
PHP

<?php
namespace Modules\Auth\Entities;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Str;
use Laravel\Sanctum\HasApiTokens;
use Spatie\Activitylog\LogOptions;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Permission\Contracts\Permission;
use Spatie\Permission\PermissionRegistrar;
use Spatie\Permission\Traits\HasPermissions;
use Spatie\Permission\Traits\HasRoles;
use App\Services\ActiveRoleService;
use Lab404\Impersonate\Models\Impersonate;
use Modules\Role\Entities\Role;
use Modules\User\Entities\Address;
use Modules\User\Entities\BankDetail;
use Modules\User\Entities\Employment;
use Modules\User\Entities\Heir;
class User extends Authenticatable
{
/** @use HasFactory<\Modules\Auth\Database\Factories\UserFactory> */
use HasApiTokens, HasFactory, HasPermissions, HasRoles, HasUuids, Impersonate, LogsActivity, Notifiable, SoftDeletes;
protected $table = 'users';
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'email',
'password',
'ic_number',
'position',
'phone_number',
'image_url',
'status',
'two_factor_secret',
'two_factor_recovery_codes',
'two_factor_confirmed_at',
'gender',
'marriage_status',
'member_number',
'member_type',
'public_profile_token',
'join_date',
'birth_date',
'birth_place',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'password',
'remember_token',
'two_factor_secret',
'two_factor_recovery_codes',
'two_factor_confirmed_at',
'public_profile_token',
];
protected static function booted(): void
{
static::creating(function (User $user) {
$expiryDays = self::publicProfileTokenTtlDays();
if (empty($user->public_profile_token)) {
$user->public_profile_token = Str::random(48);
}
if (empty($user->public_profile_token_expires_at)) {
$user->public_profile_token_expires_at = now()->addDays($expiryDays);
}
});
}
public static function publicProfileTokenTtlDays(): int
{
return (int) config('user.public_profile_token_ttl_days', 90);
}
public function isPublicProfileTokenExpired(): bool
{
return $this->public_profile_token_expires_at !== null
&& $this->public_profile_token_expires_at->isPast();
}
public function ensurePublicProfileToken(): string
{
$expiryDays = self::publicProfileTokenTtlDays();
if (blank($this->public_profile_token) || $this->isPublicProfileTokenExpired()) {
$this->forceFill([
'public_profile_token' => Str::random(48),
'public_profile_token_expires_at' => now()->addDays($expiryDays),
])->save();
return $this->public_profile_token;
}
$this->forceFill([
'public_profile_token_expires_at' => now()->addDays($expiryDays),
])->save();
return $this->public_profile_token;
}
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'status' => 'string',
'gender' => 'string',
'marriage_status' => 'string',
'member_number' => 'integer',
'member_type' => 'string',
'public_profile_token_expires_at' => 'datetime',
'join_date' => 'date',
'birth_date' => 'date',
'birth_place' => 'string',
];
}
/**
* Role used for permission checks in the current session (Sanctum token).
*/
public function activeRole(): ?Role
{
return ActiveRoleService::getActiveRole($this);
}
public function addresses()
{
return $this->hasMany(Address::class);
}
public function employments()
{
return $this->hasMany(Employment::class);
}
public function bankDetails()
{
return $this->hasMany(BankDetail::class);
}
public function heirs()
{
return $this->hasMany(Heir::class);
}
public function hasPermissionAction($action): bool
{
$role = $this->activeRole();
return $role && $role->permission_actions &&
in_array($action, $role->permission_actions, true);
}
public function getActivityLogOptions(): LogOptions
{
return LogOptions::defaults()
->logAll()
->logOnlyDirty()
->dontSubmitEmptyLogs()
->setDescriptionForEvent(fn (string $eventName) => "User {$this->name} was {$eventName}");
}
/**
* Override hasPermissionTo to bypass all permission checks for DEVELOPER role
*/
public function hasPermissionTo($permission, $guardName = null): bool
{
$activeRole = $this->activeRole();
if ($activeRole?->name === 'DEVELOPER') {
return true;
}
static $isCheckingPermission = false;
if ($isCheckingPermission) {
return false;
}
$isCheckingPermission = true;
try {
$permissionClass = app(PermissionRegistrar::class)->getPermissionClass();
if (is_string($permission)) {
$permission = $permissionClass::findByName($permission, $guardName ?? 'api');
}
if (is_int($permission)) {
$permission = $permissionClass::findById($permission, $guardName ?? 'api');
}
if (! $permission instanceof Permission) {
return false;
}
if ($this->permissions->contains('id', $permission->id)) {
return true;
}
if (! $activeRole) {
return false;
}
$activeRole->loadMissing('permissions');
return $activeRole->permissions->contains('id', $permission->id);
} catch (\Exception $e) {
return false;
} finally {
$isCheckingPermission = false;
}
}
/**
* Scope to exclude current user unless they have developer role
*/
public function scopeExcludeCurrentUserUnlessDeveloper($query, $currentUser = null)
{
$currentUser = $currentUser ?? auth()->user();
if ($currentUser && ! $currentUser->hasRole('DEVELOPER')) {
return $query->where('id', '!=', $currentUser->id);
}
return $query;
}
/**
* Scope to exclude users with DEVELOPER role unless current user is DEVELOPER
*/
public function scopeExcludeDevelopersUnlessDeveloper($query, $currentUser = null)
{
$currentUser = $currentUser ?? auth()->user();
if ($currentUser && ! $currentUser->hasRole('DEVELOPER')) {
return $query->whereDoesntHave('roles', function ($q) {
$q->where('name', 'DEVELOPER');
});
}
return $query;
}
/**
* Check if the user can impersonate another user
*/
public function canImpersonate(): bool
{
// Check permission instead of hardcoded roles
return $this->hasPermissionTo('menyamar pengguna');
}
/**
* Check if the user can be impersonated
*/
public function canBeImpersonated(): bool
{
// DEVELOPER cannot be impersonated by anyone
if ($this->hasRole('DEVELOPER')) {
return false;
}
// Check if the current user has permission to impersonate
$currentUser = auth()->user();
if (!$currentUser) {
return false;
}
// Users with permission can impersonate other users (except DEVELOPER)
return $currentUser->hasPermissionTo('menyamar pengguna');
}
public function hasVerifiedEmail(): bool
{
return $this->email_verified_at !== null;
}
/**
* Check if user can login based on their status
*/
public function canLogin(): bool
{
return $this->status === 'active';
}
/**
* Whether credentials are valid for issuing a session (includes verified pending users).
*/
public function canAuthenticate(): bool
{
if ($this->canLogin()) {
return true;
}
return $this->hasVerifiedEmail() && $this->status === 'pending';
}
/**
* Get the login restriction message based on user status
*/
public function getLoginRestrictionMessage(): ?string
{
switch ($this->status) {
case 'pending':
return 'Akaun anda sedang menunggu pengaktifan dari pentadbir sistem. Sila hubungi pentadbir sistem.';
case 'inactive':
return 'Akaun anda tidak aktif. Sila hubungi pentadbir sistem.';
default:
return 'Akaun anda tidak dapat mengakses sistem. Sila hubungi pentadbir sistem.';
}
}
}