2026-05-26 08:14:29 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Models;
|
|
|
|
|
|
|
|
|
|
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
|
|
|
|
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
|
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
|
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
|
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
2026-05-27 10:45:05 +02:00
|
|
|
use Illuminate\Auth\Passwords\CanResetPassword;
|
|
|
|
|
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
|
2026-05-26 08:14:29 +02:00
|
|
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
|
|
|
|
use Illuminate\Notifications\Notifiable;
|
2026-05-27 10:45:05 +02:00
|
|
|
use Illuminate\Support\Facades\Crypt;
|
|
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
|
use Symfony\Component\Mailer\Mailer;
|
|
|
|
|
use Symfony\Component\Mailer\Transport;
|
|
|
|
|
use Symfony\Component\Mime\Address;
|
|
|
|
|
use Symfony\Component\Mime\Email;
|
2026-05-26 08:14:29 +02:00
|
|
|
|
|
|
|
|
#[Fillable(['name', 'email', 'password', 'tenant_id', 'is_admin', 'permissions', 'role_preset_id', 'status'])]
|
|
|
|
|
#[Hidden(['password', 'remember_token'])]
|
2026-05-27 10:45:05 +02:00
|
|
|
class User extends Authenticatable implements CanResetPasswordContract
|
2026-05-26 08:14:29 +02:00
|
|
|
{
|
2026-05-27 10:45:05 +02:00
|
|
|
use HasFactory, Notifiable, CanResetPassword;
|
2026-05-26 08:14:29 +02:00
|
|
|
|
|
|
|
|
protected $fillable = ['name', 'email', 'password', 'tenant_id', 'is_admin', 'permissions', 'role_preset_id', 'status'];
|
|
|
|
|
|
|
|
|
|
protected function casts(): array
|
|
|
|
|
{
|
|
|
|
|
return [
|
|
|
|
|
'email_verified_at' => 'datetime',
|
|
|
|
|
'password' => 'hashed',
|
|
|
|
|
'is_admin' => 'boolean',
|
|
|
|
|
'permissions' => 'array',
|
|
|
|
|
'status' => 'string',
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public const STATUS_ACTIVE = 'active';
|
|
|
|
|
public const STATUS_SUSPENDED = 'suspended';
|
|
|
|
|
public const STATUS_PENDING = 'pending';
|
|
|
|
|
|
2026-05-26 14:10:04 +02:00
|
|
|
public const MODULES = ['individui', 'gruppi', 'eventi', 'documenti', 'mailing', 'viste', 'report', 'settings'];
|
2026-05-26 08:14:29 +02:00
|
|
|
public const LEVEL_NONE = 0;
|
|
|
|
|
public const LEVEL_READ = 1;
|
|
|
|
|
public const LEVEL_FULL = 2;
|
|
|
|
|
|
|
|
|
|
public function tenant(): BelongsTo
|
|
|
|
|
{
|
|
|
|
|
return $this->belongsTo(Tenant::class);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function rolePreset(): BelongsTo
|
|
|
|
|
{
|
|
|
|
|
return $this->belongsTo(RolePreset::class);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function notifiche(): HasMany
|
|
|
|
|
{
|
|
|
|
|
return $this->hasMany(Notifica::class)->orderBy('created_at', 'desc');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function unreadNotifiche(): HasMany
|
|
|
|
|
{
|
|
|
|
|
return $this->hasMany(Notifica::class)->where('is_read', false)->orderBy('created_at', 'desc');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function unreadNotificheCount(): int
|
|
|
|
|
{
|
|
|
|
|
return $this->notifiche()->unread()->count();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function activityLogs(): HasMany
|
|
|
|
|
{
|
|
|
|
|
return $this->hasMany(ActivityLog::class)->orderBy('created_at', 'desc');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function isSuperAdmin(): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->is_admin === true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static function firstUserIsAdmin(): bool
|
|
|
|
|
{
|
|
|
|
|
return self::count() === 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function isActive(): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->status !== self::STATUS_SUSPENDED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getPermission(string $module): int
|
|
|
|
|
{
|
|
|
|
|
if ($this->isSuperAdmin()) {
|
|
|
|
|
return self::LEVEL_FULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $this->permissions[$module] ?? self::LEVEL_NONE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function hasPermission(string $module, int $level = self::LEVEL_READ): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->getPermission($module) >= $level;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function canAccess(string $module): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->getPermission($module) >= self::LEVEL_READ;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function canManage(string $module): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->getPermission($module) >= self::LEVEL_FULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function canRead(string $module): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->getPermission($module) >= self::LEVEL_READ;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function canWrite(string $module): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->getPermission($module) >= self::LEVEL_FULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function canDelete(string $module): bool
|
|
|
|
|
{
|
|
|
|
|
return $this->getPermission($module) >= self::LEVEL_FULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setPermission(string $module, int $level): self
|
|
|
|
|
{
|
|
|
|
|
$permissions = $this->permissions ?? [];
|
|
|
|
|
$permissions[$module] = $level;
|
|
|
|
|
$this->permissions = $permissions;
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function setPermissions(array $permissions): self
|
|
|
|
|
{
|
|
|
|
|
$this->permissions = $permissions;
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getPermissionsSummary(): array
|
|
|
|
|
{
|
|
|
|
|
$result = [];
|
|
|
|
|
foreach (self::MODULES as $module) {
|
|
|
|
|
$level = $this->getPermission($module);
|
|
|
|
|
$result[$module] = match ($level) {
|
|
|
|
|
self::LEVEL_NONE => 'Nessuno',
|
|
|
|
|
self::LEVEL_READ => 'Lettura',
|
|
|
|
|
self::LEVEL_FULL => 'Completo',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return $result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function applyRolePreset(RolePreset $preset): self
|
|
|
|
|
{
|
|
|
|
|
$this->role_preset_id = $preset->id;
|
|
|
|
|
$this->permissions = $preset->permissions;
|
|
|
|
|
return $this;
|
|
|
|
|
}
|
2026-05-27 10:45:05 +02:00
|
|
|
|
|
|
|
|
public function sendPasswordResetNotification($token): void
|
|
|
|
|
{
|
|
|
|
|
$settings = EmailSetting::getActive();
|
|
|
|
|
if (!$settings || empty($settings->smtp_host)) {
|
|
|
|
|
Log::warning('Impossibile inviare reset password: SMTP non configurato');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$smtpUsername = $settings->smtp_username ?: $settings->email_address;
|
|
|
|
|
$smtpPassword = $settings->smtp_password
|
|
|
|
|
? Crypt::decryptString($settings->smtp_password)
|
|
|
|
|
: $settings->getDecryptedPassword();
|
|
|
|
|
|
|
|
|
|
$encryption = $settings->smtp_encryption ?? 'tls';
|
|
|
|
|
$scheme = ($encryption === 'ssl') ? 'smtps' : 'smtp';
|
|
|
|
|
|
|
|
|
|
$dsn = sprintf(
|
|
|
|
|
'%s://%s:%s@%s:%d?encryption=%s',
|
|
|
|
|
$scheme,
|
|
|
|
|
rawurlencode($smtpUsername),
|
|
|
|
|
rawurlencode($smtpPassword),
|
|
|
|
|
$settings->smtp_host,
|
|
|
|
|
$settings->smtp_port ?? 587,
|
|
|
|
|
$encryption
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$transport = Transport::fromDsn($dsn);
|
|
|
|
|
$mailer = new Mailer($transport);
|
|
|
|
|
|
|
|
|
|
$resetUrl = url('/password/reset/' . $token);
|
|
|
|
|
|
|
|
|
|
$fromName = $settings->email_name ?? config('app.name');
|
|
|
|
|
$fromName = preg_replace('/[^\p{L}\p{N}\s]/u', '', $fromName);
|
|
|
|
|
$fromName = trim($fromName) ?: config('app.name');
|
|
|
|
|
|
|
|
|
|
$email = (new Email())
|
|
|
|
|
->from(new Address($settings->email_address, $fromName))
|
|
|
|
|
->to($this->email)
|
|
|
|
|
->subject('Reset della password - ' . config('app.name'))
|
|
|
|
|
->html(view('auth.emails.reset-password', [
|
|
|
|
|
'user' => $this,
|
|
|
|
|
'token' => $token,
|
|
|
|
|
'resetUrl' => $resetUrl,
|
|
|
|
|
'appName' => config('app.name'),
|
|
|
|
|
])->render());
|
|
|
|
|
|
|
|
|
|
$mailer->send($email);
|
|
|
|
|
|
|
|
|
|
Log::info('Password reset email sent', ['email' => $this->email]);
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
Log::error('Failed to send password reset email', [
|
|
|
|
|
'email' => $this->email,
|
|
|
|
|
'error' => $e->getMessage(),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-26 08:14:29 +02:00
|
|
|
}
|