Files
WebAPP/app/Models/User.php
Rhino 2e24a40d68 Stand: SMTP-Test, Admin-Mail-Tab, Notifiable-Fix, Lazy-Quill
- 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>
2026-03-02 07:30:37 +01:00

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);
}
}