219 lines
5.8 KiB
PHP
219 lines
5.8 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
|
use App\Models\UserSetting;
|
|
use Illuminate\Database\Eloquent\Concerns\HasUlids;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
|
use Illuminate\Notifications\Notifiable;
|
|
use Illuminate\Support\Str;
|
|
use Laravel\Sanctum\HasApiTokens;
|
|
|
|
class User extends Authenticatable
|
|
{
|
|
/** @use HasFactory<\Database\Factories\UserFactory> */
|
|
use HasFactory, Notifiable;
|
|
use HasUlids; // generates ULIDs automatically
|
|
|
|
protected $keyType = 'string';
|
|
public $incrementing = false;
|
|
|
|
/**
|
|
* The attributes that are mass assignable.
|
|
*
|
|
* @var list<string>
|
|
*/
|
|
protected $fillable = [
|
|
'firstname',
|
|
'lastname',
|
|
'displayname',
|
|
'name',
|
|
'email',
|
|
'timezone',
|
|
'phone',
|
|
'locale',
|
|
];
|
|
|
|
/**
|
|
* The attributes that should be hidden for serialization.
|
|
*
|
|
* @var list<string>
|
|
*/
|
|
protected $hidden = [
|
|
'password',
|
|
'remember_token',
|
|
];
|
|
|
|
/**
|
|
* Get the attributes that should be cast.
|
|
*
|
|
* @return array<string, string>
|
|
*/
|
|
protected function casts(): array
|
|
{
|
|
return [
|
|
'email_verified_at' => 'datetime',
|
|
'password' => 'hashed',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Expose a Breeze-compatible "name" attribute without a physical column.
|
|
* Preference: displayname (explicit override), then first + last, then email.
|
|
*/
|
|
public function getNameAttribute(): string
|
|
{
|
|
$displayname = is_string($this->displayname) ? trim($this->displayname) : '';
|
|
if ($displayname !== '') {
|
|
return $displayname;
|
|
}
|
|
|
|
$first = is_string($this->firstname) ? trim($this->firstname) : '';
|
|
$last = is_string($this->lastname) ? trim($this->lastname) : '';
|
|
$full = trim($first . ' ' . $last);
|
|
|
|
if ($full !== '') {
|
|
return $full;
|
|
}
|
|
|
|
return (string) ($this->email ?? '');
|
|
}
|
|
|
|
/**
|
|
* Map "name" writes to first/last names, keeping displayname optional.
|
|
*/
|
|
public function setNameAttribute(?string $value): void
|
|
{
|
|
$incoming = trim((string) $value);
|
|
|
|
$currentFirst = is_string($this->attributes['firstname'] ?? null)
|
|
? trim((string) $this->attributes['firstname'])
|
|
: '';
|
|
$currentLast = is_string($this->attributes['lastname'] ?? null)
|
|
? trim((string) $this->attributes['lastname'])
|
|
: '';
|
|
$currentGenerated = trim($currentFirst . ' ' . $currentLast);
|
|
|
|
if ($incoming === '') {
|
|
$this->attributes['firstname'] = null;
|
|
$this->attributes['lastname'] = null;
|
|
return;
|
|
}
|
|
|
|
$parts = preg_split('/\s+/', $incoming, 2);
|
|
$this->attributes['firstname'] = $parts[0] ?? null;
|
|
$this->attributes['lastname'] = $parts[1] ?? null;
|
|
|
|
$displayname = is_string($this->attributes['displayname'] ?? null)
|
|
? trim((string) $this->attributes['displayname'])
|
|
: '';
|
|
|
|
if ($displayname !== '' && $displayname === $currentGenerated) {
|
|
$this->attributes['displayname'] = $incoming;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* user can own many calendars
|
|
*/
|
|
public function calendars(): HasMany
|
|
{
|
|
return $this->hasMany(Calendar::class);
|
|
}
|
|
|
|
/**
|
|
* get the current user's principal uri
|
|
*/
|
|
public function getPrincipalUriAttribute(): string
|
|
{
|
|
return 'principals/' . $this->email;
|
|
}
|
|
|
|
/**
|
|
* get all user addresses
|
|
*/
|
|
public function addresses()
|
|
{
|
|
return $this->hasMany(\App\Models\UserAddress::class, 'user_id', 'id');
|
|
}
|
|
|
|
/**
|
|
* get the user's billing address
|
|
*/
|
|
public function billingAddress()
|
|
{
|
|
return $this->addresses()
|
|
->where('kind', 'billing')
|
|
->where('is_primary', 1)
|
|
->first();
|
|
}
|
|
|
|
/**
|
|
* user can have many settings
|
|
*/
|
|
public function userSettings(): HasMany
|
|
{
|
|
return $this->hasMany(UserSetting::class, 'user_id');
|
|
}
|
|
|
|
/**
|
|
* get a user setting by key
|
|
*/
|
|
public function getSetting(string $key, mixed $default = null): mixed
|
|
{
|
|
// avoid repeated queries if the relationship is already eager loaded
|
|
if ($this->relationLoaded('userSettings')) {
|
|
$match = $this->userSettings->firstWhere('key', $key);
|
|
return $match?->value ?? $default;
|
|
}
|
|
|
|
$value = $this->userSettings()
|
|
->where('key', $key)
|
|
->value('value');
|
|
|
|
return $value ?? $default;
|
|
}
|
|
|
|
/**
|
|
* set a user setting by key
|
|
*/
|
|
public function setSetting(string $key, mixed $value): void
|
|
{
|
|
// store everything as a string in the value column
|
|
$stringValue = is_null($value) ? null : (string) $value;
|
|
|
|
$this->userSettings()->updateOrCreate(
|
|
['key' => $key],
|
|
['value' => $stringValue],
|
|
);
|
|
|
|
// keep in-memory relation in sync if it was loaded
|
|
if ($this->relationLoaded('userSettings')) {
|
|
$existing = $this->userSettings->firstWhere('key', $key);
|
|
|
|
if ($existing) {
|
|
$existing->value = $stringValue;
|
|
} else {
|
|
$this->userSettings->push(new UserSetting([
|
|
'user_id' => $this->getKey(),
|
|
'key' => $key,
|
|
'value' => $stringValue,
|
|
]));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* convenience: set many settings at once
|
|
*/
|
|
public function setSettings(array $pairs): void
|
|
{
|
|
foreach ($pairs as $key => $value) {
|
|
$this->setSetting($key, $value);
|
|
}
|
|
}
|
|
}
|