Files
2025-07-17 11:47:34 +02:00

225 lines
6.2 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Models;
use App\Enums\Weekday;
use App\Models\Concerns\CustomAuditable;
use App\Models\Concerns\HasUuids;
use App\Models\Passport\Token;
use Database\Factories\UserFactory;
use Filament\Models\Contracts\FilamentUser;
use Filament\Panel;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\Pivot;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Storage;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Jetstream\HasProfilePhoto;
use Laravel\Jetstream\HasTeams;
use Laravel\Passport\AuthCode;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
use OwenIt\Auditing\Contracts\Auditable as AuditableContract;
/**
* @property string $id
* @property string $name
* @property string $email
* @property Carbon|null $email_verified_at
* @property string|null $password
* @property string|null $two_factor_secret
* @property string $timezone
* @property bool $is_placeholder
* @property Weekday $week_start
* @property string|null $profile_photo_path
* @property-read Organization|null $currentOrganization
* @property-read Organization|null $currentTeam
* @property-read string $profile_photo_url
* @property-read Collection<int, Token> $tokens
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property string|null $current_team_id
* @property Collection<int, Organization> $organizations
* @property Collection<int, TimeEntry> $timeEntries
* @property Member $membership
*
* @method HasMany<Organization, $this> ownedTeams()
* @method static UserFactory factory()
* @method static Builder<User> query()
* @method Builder<User> belongsToOrganization(Organization $organization)
* @method Builder<User> active()
*/
class User extends Authenticatable implements AuditableContract, FilamentUser, MustVerifyEmail, OAuthenticatable
{
use CustomAuditable;
use HasApiTokens;
/** @use HasFactory<UserFactory> */
use HasFactory;
use HasProfilePhoto;
use HasTeams;
use HasUuids;
use Notifiable;
use TwoFactorAuthenticatable;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'password',
'remember_token',
'two_factor_recovery_codes',
'two_factor_secret',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'name' => 'string',
'email' => 'string',
'email_verified_at' => 'datetime',
'is_admin' => 'boolean',
'is_placeholder' => 'boolean',
'week_start' => Weekday::class,
];
/**
* The model's default values for attributes.
*
* @var array<string, mixed>
*/
protected $attributes = [
'week_start' => Weekday::Monday,
];
/**
* Get the URL to the user's profile photo.
*
* @return Attribute<string, never>
*/
protected function profilePhotoUrl(): Attribute
{
return Attribute::get(function (): string {
return $this->profile_photo_path
? Storage::disk($this->profilePhotoDisk())->url($this->profile_photo_path)
: $this->defaultProfilePhotoUrl();
});
}
public function canAccessPanel(Panel $panel): bool
{
return in_array($this->email, config('auth.super_admins', []), true) && $this->hasVerifiedEmail();
}
public function canBeImpersonated(): bool
{
return $this->is_placeholder === false;
}
/**
* @return BelongsToMany<Organization, $this, Pivot, 'membership'>
*/
public function organizations(): BelongsToMany
{
return $this->belongsToMany(Organization::class, Member::class)
->withPivot([
'id',
'role',
'billable_rate',
])
->withTimestamps()
->as('membership');
}
/**
* @return HasMany<TimeEntry, $this>
*/
public function timeEntries(): HasMany
{
return $this->hasMany(TimeEntry::class);
}
/**
* @return BelongsTo<Organization, $this>
*/
public function currentOrganization(): BelongsTo
{
return $this->belongsTo(Organization::class, 'current_team_id');
}
/**
* @return HasMany<ProjectMember, $this>
*/
public function projectMembers(): HasMany
{
return $this->hasMany(ProjectMember::class, 'user_id');
}
/**
* @return HasMany<Token, $this>
*/
public function accessTokens(): HasMany
{
return $this->hasMany(Token::class);
}
/**
* @return HasMany<AuthCode, $this>
*/
public function authCodes(): HasMany
{
return $this->hasMany(AuthCode::class);
}
/**
* @param Builder<User> $builder
*/
public function scopeActive(Builder $builder): void
{
$builder->where('is_placeholder', '=', false);
}
/**
* @param Builder<User> $builder
* @return Builder<User>
*/
public function scopeBelongsToOrganization(Builder $builder, Organization $organization): Builder
{
return $builder->where(function (Builder $builder) use ($organization): Builder {
return $builder->whereHas('organizations', function (Builder $query) use ($organization): void {
$query->whereKey($organization->getKey());
})->orWhereHas('ownedTeams', function (Builder $query) use ($organization): void {
$query->whereKey($organization->getKey());
});
});
}
}