- Fix: Notifiable-Trait zum User-Model hinzugefuegt (behebt notify()-500er) - Installer: SMTP-Verbindungstest mit EsmtpTransport + Ueberspringen-Link - Admin: Neuer E-Mail-Tab mit SMTP-Konfiguration + Verbindungstest - Admin: Lazy Quill-Initialisierung (nur sichtbare Locale wird geladen) - Uebersetzungen: 17 neue Mail-Keys in allen 6 Sprachen Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
203 lines
4.9 KiB
PHP
Executable File
203 lines
4.9 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Enums\UserRole;
|
|
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\SoftDeletes;
|
|
use Illuminate\Notifications\Notifiable;
|
|
use App\Notifications\ResetPasswordNotification;
|
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
|
use Illuminate\Support\Collection;
|
|
|
|
class User extends Authenticatable
|
|
{
|
|
use HasFactory, Notifiable, SoftDeletes;
|
|
|
|
protected $fillable = [
|
|
'name',
|
|
'email',
|
|
'phone',
|
|
'password',
|
|
'locale',
|
|
'profile_picture',
|
|
];
|
|
|
|
protected $hidden = [
|
|
'password',
|
|
'remember_token',
|
|
];
|
|
|
|
protected function casts(): array
|
|
{
|
|
return [
|
|
'email_verified_at' => 'datetime',
|
|
'password' => 'hashed',
|
|
'role' => UserRole::class,
|
|
'is_active' => 'boolean',
|
|
'last_login_at' => 'datetime',
|
|
'dsgvo_accepted_at' => 'datetime',
|
|
'dsgvo_notice_accepted_at' => 'datetime',
|
|
];
|
|
}
|
|
|
|
public function sendPasswordResetNotification($token): void
|
|
{
|
|
$this->notify(new ResetPasswordNotification($token));
|
|
}
|
|
|
|
public function isAdmin(): bool
|
|
{
|
|
return $this->role === UserRole::Admin;
|
|
}
|
|
|
|
public function isCoach(): bool
|
|
{
|
|
return $this->role === UserRole::Coach;
|
|
}
|
|
|
|
public function isParentRep(): bool
|
|
{
|
|
return $this->role === UserRole::ParentRep;
|
|
}
|
|
|
|
public function isStaff(): bool
|
|
{
|
|
return in_array($this->role, [UserRole::Admin, UserRole::Coach]);
|
|
}
|
|
|
|
public function canAccessAdminPanel(): bool
|
|
{
|
|
return $this->isStaff() || $this->isParentRep();
|
|
}
|
|
|
|
public function canViewActivityLog(): bool
|
|
{
|
|
return $this->id === 1 && $this->isAdmin();
|
|
}
|
|
|
|
public function children(): BelongsToMany
|
|
{
|
|
return $this->belongsToMany(Player::class, 'parent_player', 'parent_id', 'player_id')
|
|
->withPivot('relationship_label', 'created_at');
|
|
}
|
|
|
|
/**
|
|
* Team-IDs, auf die der User Zugriff hat (über seine Kinder).
|
|
* Direkte DB-Query ohne Model-Hydration.
|
|
*/
|
|
public function accessibleTeamIds(): Collection
|
|
{
|
|
return $this->children()->distinct()->pluck('players.team_id');
|
|
}
|
|
|
|
public function comments(): HasMany
|
|
{
|
|
return $this->hasMany(Comment::class);
|
|
}
|
|
|
|
public function caterings(): HasMany
|
|
{
|
|
return $this->hasMany(EventCatering::class);
|
|
}
|
|
|
|
public function createdInvitations(): HasMany
|
|
{
|
|
return $this->hasMany(Invitation::class, 'created_by');
|
|
}
|
|
|
|
public function coachTeams(): BelongsToMany
|
|
{
|
|
return $this->belongsToMany(Team::class, 'team_user')
|
|
->withPivot('created_at');
|
|
}
|
|
|
|
public function scopeActive($query)
|
|
{
|
|
return $query->where('is_active', true);
|
|
}
|
|
|
|
public function getAvatarUrl(): ?string
|
|
{
|
|
if ($this->profile_picture) {
|
|
return asset('storage/' . $this->profile_picture);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public function getInitials(): string
|
|
{
|
|
$parts = explode(' ', trim($this->name));
|
|
if (count($parts) >= 2) {
|
|
return mb_strtoupper(mb_substr($parts[0], 0, 1) . mb_substr(end($parts), 0, 1));
|
|
}
|
|
return mb_strtoupper(mb_substr($this->name, 0, 2));
|
|
}
|
|
|
|
public function isRestorable(): bool
|
|
{
|
|
return $this->trashed() && $this->deleted_at->diffInDays(now()) < 7;
|
|
}
|
|
|
|
public function isDsgvoRestricted(): bool
|
|
{
|
|
if ($this->role !== UserRole::User) {
|
|
return false;
|
|
}
|
|
|
|
return !$this->isDsgvoConfirmed();
|
|
}
|
|
|
|
public function needsDsgvoBanner(): bool
|
|
{
|
|
if ($this->role !== UserRole::User) {
|
|
return false;
|
|
}
|
|
|
|
return !$this->isDsgvoConfirmed();
|
|
}
|
|
|
|
public function dsgvoBannerState(): ?string
|
|
{
|
|
if ($this->role !== UserRole::User) {
|
|
return null;
|
|
}
|
|
|
|
if ($this->dsgvo_consent_file === null) {
|
|
return 'upload_required';
|
|
}
|
|
|
|
if ($this->dsgvo_accepted_at === null) {
|
|
return 'pending_confirmation';
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public function hasDsgvoConsent(): bool
|
|
{
|
|
return $this->dsgvo_consent_file !== null;
|
|
}
|
|
|
|
public function isDsgvoConfirmed(): bool
|
|
{
|
|
return $this->dsgvo_accepted_at !== null;
|
|
}
|
|
|
|
public function dsgvoAcceptedBy(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'dsgvo_accepted_by')->withTrashed();
|
|
}
|
|
|
|
public function getOrphanedChildren(): Collection
|
|
{
|
|
return $this->children()
|
|
->withCount('parents')
|
|
->get()
|
|
->filter(fn (Player $child) => $child->parents_count <= 1);
|
|
}
|
|
}
|