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>
This commit is contained in:
202
app/Models/User.php
Executable file
202
app/Models/User.php
Executable file
@@ -0,0 +1,202 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user