- Popup/banner quảng cáo cấu hình từ admin (loại, link, lịch hiển thị) - Đặt lại mật khẩu: token 80 ký tự kèm check digit Luhn thay cho token+email lộ trong URL, chỉ lưu bản băm, một lần dùng, hết hạn 60 phút - Xác thực 2 lớp qua email khi đăng nhập (mã 6 số, ghi nhớ thiết bị 90 ngày) - ApiAuthCheck chặn ngay tài khoản bị khoá dù JWT còn hạn (trước đây dùng được tới hết TTL) - Handler trả 404 thay vì 500 khi ModelNotFoundException không bọc HttpException - Test ClientTamperingTest: xác nhận IDOR/giả mạo dữ liệu từ client đều bị chặn Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
71 lines
1.8 KiB
PHP
71 lines
1.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Traits\LogsActivity;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
class PopupBanner extends Model
|
|
{
|
|
use LogsActivity;
|
|
|
|
public const DISK = 'public';
|
|
|
|
public const TYPES = [
|
|
'advertisement' => 'Quảng cáo',
|
|
'promotion' => 'Chương trình khuyến mãi',
|
|
];
|
|
|
|
public const PUBLIC_FIELDS = ['id', 'title', 'type', 'image_url', 'link_url', 'open_in_new_tab'];
|
|
|
|
protected $fillable = [
|
|
'title',
|
|
'type',
|
|
'image_path',
|
|
'link_url',
|
|
'open_in_new_tab',
|
|
'is_active',
|
|
'starts_at',
|
|
'ends_at',
|
|
'sort_order',
|
|
];
|
|
|
|
protected $casts = [
|
|
'open_in_new_tab' => 'boolean',
|
|
'is_active' => 'boolean',
|
|
'starts_at' => 'datetime',
|
|
'ends_at' => 'datetime',
|
|
'sort_order' => 'integer',
|
|
];
|
|
|
|
protected $appends = ['image_url'];
|
|
|
|
public function getImageUrlAttribute(): string
|
|
{
|
|
return Storage::disk(self::DISK)->url($this->image_path);
|
|
}
|
|
|
|
public function scopeVisible(Builder $query): Builder
|
|
{
|
|
return $query->where('is_active', true)
|
|
->where(fn (Builder $q) => $q->whereNull('starts_at')->orWhere('starts_at', '<=', now()))
|
|
->where(fn (Builder $q) => $q->whereNull('ends_at')->orWhere('ends_at', '>=', now()))
|
|
->orderBy('sort_order')
|
|
->orderBy('id');
|
|
}
|
|
|
|
/**
|
|
* @return list<array<string, mixed>>
|
|
*/
|
|
public static function publicList(): array
|
|
{
|
|
return self::visible()->get()
|
|
->map(fn (self $banner) => $banner->only(self::PUBLIC_FIELDS))
|
|
->all();
|
|
}
|
|
}
|