Files
nexusphp/app/Models/User.php

617 lines
19 KiB
PHP
Raw Normal View History

2021-04-02 19:48:41 +08:00
<?php
namespace App\Models;
2021-04-29 19:18:13 +08:00
use App\Http\Middleware\Locale;
use App\Repositories\ExamRepository;
2022-01-19 23:54:55 +08:00
use Carbon\Carbon;
2021-12-14 16:22:03 +08:00
use Illuminate\Database\Eloquent\Builder;
2022-06-27 01:39:01 +08:00
use Illuminate\Database\Eloquent\Casts\Attribute;
2021-04-02 19:48:41 +08:00
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
2022-06-12 15:15:09 +08:00
use Illuminate\Support\Facades\Auth;
2022-06-28 13:33:18 +08:00
use Illuminate\Support\Facades\Cookie;
2021-05-14 02:11:57 +08:00
use Illuminate\Support\Facades\DB;
2021-04-22 03:24:59 +08:00
use Laravel\Sanctum\HasApiTokens;
2022-06-08 14:15:59 +08:00
use Nexus\Database\NexusDB;
2022-06-27 01:39:01 +08:00
use Filament\Models\Contracts\FilamentUser;
use Filament\Models\Contracts\HasName;
2022-08-19 15:30:16 +08:00
use NexusPlugin\Permission\Models\Role;
2022-08-20 19:11:28 +08:00
use NexusPlugin\Permission\Models\UserPermission;
2021-04-02 19:48:41 +08:00
2022-06-27 01:39:01 +08:00
class User extends Authenticatable implements FilamentUser, HasName
2021-04-02 19:48:41 +08:00
{
use HasFactory, Notifiable, HasApiTokens;
2021-04-02 19:48:41 +08:00
2021-04-17 19:01:33 +08:00
public $timestamps = false;
2022-05-13 03:12:38 +08:00
protected $perPage = 50;
protected $connection = NexusDB::ELOQUENT_CONNECTION_NAME;
2021-04-17 19:01:33 +08:00
const STATUS_CONFIRMED = 'confirmed';
const STATUS_PENDING = 'pending';
2021-04-25 21:28:58 +08:00
const ENABLED_YES = 'yes';
const ENABLED_NO = 'no';
2021-04-19 20:13:21 +08:00
const CLASS_PEASANT = "0";
const CLASS_USER = "1";
const CLASS_POWER_USER = "2";
const CLASS_ELITE_USER = "3";
const CLASS_CRAZY_USER = "4";
const CLASS_INSANE_USER = "5";
const CLASS_VETERAN_USER = "6";
const CLASS_EXTREME_USER = "7";
const CLASS_ULTIMATE_USER = "8";
const CLASS_NEXUS_MASTER = "9";
const CLASS_VIP = "10";
const CLASS_RETIREE = "11";
const CLASS_UPLOADER = "12";
const CLASS_MODERATOR = "13";
const CLASS_ADMINISTRATOR = "14";
const CLASS_SYSOP = "15";
2021-04-22 03:24:59 +08:00
const CLASS_STAFF_LEADER = "16";
2021-04-19 20:13:21 +08:00
2022-04-03 16:03:47 +08:00
public static array $classes = [
2021-04-19 20:13:21 +08:00
self::CLASS_PEASANT => ['text' => 'Peasant'],
2021-12-14 16:22:03 +08:00
self::CLASS_USER => ['text' => 'User', 'min_seed_points' => 0],
self::CLASS_POWER_USER => ['text' => 'Power User', 'min_seed_points' => 40000],
self::CLASS_ELITE_USER => ['text' => 'Elite User', 'min_seed_points' => 80000],
self::CLASS_CRAZY_USER => ['text' => 'Crazy User', 'min_seed_points' => 150000],
self::CLASS_INSANE_USER => ['text' => 'Insane User', 'min_seed_points' => 250000],
self::CLASS_VETERAN_USER => ['text' => 'Veteran User', 'min_seed_points' => 400000],
self::CLASS_EXTREME_USER => ['text' => 'Extreme User', 'min_seed_points' => 600000],
self::CLASS_ULTIMATE_USER => ['text' => 'Ultimate User', 'min_seed_points' => 800000],
self::CLASS_NEXUS_MASTER => ['text' => 'Nexus Master', 'min_seed_points' => 1000000],
2021-04-19 20:13:21 +08:00
self::CLASS_VIP => ['text' => 'Vip'],
self::CLASS_RETIREE => ['text' => 'Retiree'],
self::CLASS_UPLOADER => ['text' => 'Uploader'],
self::CLASS_MODERATOR => ['text' => 'Moderator'],
self::CLASS_ADMINISTRATOR => ['text' => 'Administrator'],
self::CLASS_SYSOP => ['text' => 'Sysop'],
2021-04-22 03:24:59 +08:00
self::CLASS_STAFF_LEADER => ['text' => 'Staff Leader'],
2021-04-19 20:13:21 +08:00
];
const DONATE_YES = 'yes';
const DONATE_NO = 'no';
public static $donateStatus = [
self::DONATE_YES => ['text' => 'Yes'],
self::DONATE_NO => ['text' => 'No'],
];
2022-11-08 19:06:37 +08:00
const GENDER_FEMALE = 'Female';
const GENDER_MALE = 'Male';
const GENDER_UNKNOWN = 'N/A';
public static array $genders = [
self::GENDER_MALE => 'Male',
self::GENDER_FEMALE => 'Female',
self::GENDER_UNKNOWN => 'N/A',
];
2022-05-04 15:20:15 +08:00
public static array $cardTitles = [
2022-02-21 17:20:04 +08:00
'uploaded_human' => '上传量',
'downloaded_human' => '下载量',
'share_ratio' => '分享率',
2022-02-21 00:14:52 +08:00
// 'seed_time' => '做种时间',
2022-03-04 16:16:56 +08:00
'bonus' => '魔力值',
2022-02-21 00:14:52 +08:00
'seed_points' => '做种积分',
'invites' => '邀请',
2021-05-15 19:29:44 +08:00
];
public static array $notificationOptions = ['topic_reply', 'hr_reached'];
2021-04-22 03:24:59 +08:00
public function getClassTextAttribute(): string
2021-04-19 20:13:21 +08:00
{
2022-08-17 16:01:55 +08:00
return self::getClassText($this->class);
}
public static function getClassText($class)
{
2022-08-19 15:30:16 +08:00
if (!is_numeric($class)|| !isset(self::$classes[$class])) {
2022-04-25 01:22:07 +08:00
return '';
}
2022-08-17 16:01:55 +08:00
if ($class >= self::CLASS_VIP) {
$classText = nexus_trans('user.class_names.' . $class);
} else {
$classText = self::$classes[$class]['text'];
}
$alias = Setting::get("account.{$class}_alias");
2022-04-25 01:22:07 +08:00
if (!empty($alias)) {
$classText .= "({$alias})";
}
return $classText;
2021-04-19 20:13:21 +08:00
}
2022-08-21 15:22:08 +08:00
public static function listClass($min = self::CLASS_PEASANT, $max = self::CLASS_STAFF_LEADER): array
2022-08-19 15:30:16 +08:00
{
$result = [];
foreach (self::$classes as $class => $info) {
2022-08-21 15:22:08 +08:00
if ($class >= $min && $class <= $max) {
$result[$class] = self::getClassText($class);
}
2022-08-19 15:30:16 +08:00
}
return $result;
}
2024-11-18 22:20:31 +08:00
public static function exists($id): bool
{
return self::query()->where("id", $id)->exists();
}
2024-12-25 23:09:07 +08:00
public function canAccessPanel(\Filament\Panel $panel): bool
2022-06-27 01:39:01 +08:00
{
2022-07-02 15:08:23 +08:00
return $this->canAccessAdmin();
2022-06-27 01:39:01 +08:00
}
public function getFilamentName(): string
{
return $this->username;
}
/**
* @see ExamRepository::isExamMatchUser()
*
* @return string
*/
public function getDonateStatusAttribute(): string
{
if ($this->isDonating()) {
return self::DONATE_YES;
}
return self::DONATE_NO;
}
2021-04-17 19:01:33 +08:00
/**
* 为数组 / JSON 序列化准备日期。
*
* @param \DateTimeInterface $date
* @return string
*/
2021-04-22 03:24:59 +08:00
protected function serializeDate(\DateTimeInterface $date): string
2021-04-17 19:01:33 +08:00
{
2022-08-10 17:38:05 +08:00
return $date->format($this->dateFormat ?: 'Y-m-d H:i:s');
2021-04-17 19:01:33 +08:00
}
2021-04-02 19:48:41 +08:00
/**
* The attributes that are mass assignable.
*
* @var array
*/
2021-05-14 20:41:43 +08:00
protected $fillable = [
'username', 'email', 'passhash', 'secret', 'stylesheet', 'editsecret', 'added', 'enabled', 'status',
2022-05-03 00:38:50 +08:00
'leechwarn', 'leechwarnuntil', 'page', 'class', 'uploaded', 'downloaded', 'clientselect', 'showclienterror', 'last_home',
'seedbonus', 'downloadpos', 'vip_added', 'vip_until', 'title', 'invites', 'attendance_card',
2025-04-05 21:43:37 +07:00
'seed_points_per_hour', 'passkey', 'auth_key'
2021-05-14 20:41:43 +08:00
];
2021-04-02 19:48:41 +08:00
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
2025-04-05 15:38:40 +07:00
'secret', 'passhash', 'passkey', 'auth_key'
2021-04-02 19:48:41 +08:00
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
2021-04-25 21:28:58 +08:00
'added' => 'datetime',
2022-04-08 15:22:30 +08:00
'last_login' => 'datetime',
2022-03-06 15:43:29 +08:00
'last_access' => 'datetime',
2022-04-08 15:22:30 +08:00
'last_home' => 'datetime',
'last_offer' => 'datetime',
'forum_access' => 'datetime',
'last_staffmsg' => 'datetime',
'last_pm' => 'datetime',
'last_comment' => 'datetime',
'last_post' => 'datetime',
'lastwarned' => 'datetime',
'last_browse' => 'datetime:U',
'last_music' => 'datetime:U',
'last_catchup' => 'datetime:U',
'donoruntil' => 'datetime',
'warneduntil' => 'datetime',
'noaduntil' => 'datetime',
'vip_until' => 'datetime',
'leechwarnuntil' => 'datetime',
2021-04-02 19:48:41 +08:00
];
2021-04-25 02:12:14 +08:00
2024-03-23 04:51:59 +08:00
public static array $commonFields = [
'id', 'username', 'email', 'class', 'status', 'added', 'avatar', 'passkey',
2021-05-12 13:45:00 +08:00
'uploaded', 'downloaded', 'seedbonus', 'seedtime', 'leechtime',
'invited_by', 'enabled', 'seed_points', 'last_access', 'invites',
2023-02-01 10:58:01 +08:00
'lang', 'attendance_card', 'privacy', 'noad', 'downloadpos', 'donoruntil', 'donor',
'downloadpos', 'vip_added', 'vip_until', 'title', 'invites', 'attendance_card',
'seed_points_per_hour'
2021-04-27 19:13:32 +08:00
];
public static function getDefaultUserAttributes(): array
{
return [
'id' => 0,
'username' => nexus_trans('user.deleted_username'),
'class' => self::CLASS_PEASANT,
'email' => '',
'status' => self::STATUS_CONFIRMED,
'added' => '1970-01-01 08:00:00',
'avatar' => '',
'uploaded' => 0,
'downloaded' => 0,
'seedbonus' => 0,
'seedtime' => 0,
'leechtime' => 0,
'enabled' => self::ENABLED_NO,
'seed_points' => 0
];
}
2022-06-12 15:15:09 +08:00
public static function defaultUser(): static
{
return new static(self::getDefaultUserAttributes());
}
2022-06-12 15:15:09 +08:00
public static function getClassName($class, $compact = false, $b_colored = false, $I18N = false)
{
2022-08-20 19:11:28 +08:00
$class_name = self::$classes[$class]['text'] ?? '';
2022-06-12 15:15:09 +08:00
if ($class >= self::CLASS_VIP && $I18N) {
$class_name = nexus_trans("user.class_names.$class");
}
2022-08-20 19:11:28 +08:00
$class_name_color = self::$classes[$class]['text'] ?? '';
2022-06-12 15:15:09 +08:00
if ($compact) {
$class_name = str_replace(" ", "",$class_name);
}
2022-08-20 19:11:28 +08:00
if ($class_name && $b_colored) {
2022-06-12 15:15:09 +08:00
return "<b class='" . str_replace(" ", "",$class_name_color) . "_Name'>" . $class_name . "</b>";
}
return $class_name;
}
public function checkIsNormal(array $fields = ['status', 'enabled']): bool
2021-04-25 21:28:58 +08:00
{
if (in_array('status', $fields) && $this->getAttribute('status') != self::STATUS_CONFIRMED) {
2021-04-25 21:28:58 +08:00
throw new \InvalidArgumentException(sprintf('User: %s is not confirmed.', $this->id));
}
if (in_array('enabled', $fields) && $this->getAttribute('enabled') != self::ENABLED_YES) {
throw new \InvalidArgumentException(sprintf('User: %s is not enabled.', $this->id));
}
return true;
}
2021-04-25 02:12:14 +08:00
2021-04-29 19:18:13 +08:00
public function getLocaleAttribute()
{
2022-11-08 19:06:37 +08:00
$locale = null;
$log = "user: " . $this->id;
if (get_user_id() == $this->id) {
$locale = Locale::getLocaleFromCookie();
$log .= ", locale from cookie: $locale";
}
if (!$locale) {
2022-06-28 13:33:18 +08:00
$lang = $this->language->site_lang_folder;
$locale = Locale::$languageMaps[$lang] ?? 'en';
2022-11-08 19:06:37 +08:00
$log .= ", [NO_DATA_FROM_COOKIE], lang from database: $lang, locale: $locale";
2022-06-28 13:33:18 +08:00
}
do_log($log);
return $locale;
2021-04-29 19:18:13 +08:00
}
2021-05-13 21:31:09 +08:00
public function getSiteLangFolderAttribute()
{
$result = optional($this->language)->site_lang_folder;
if ($result && in_array($result, ['en', 'chs', 'cht'])) {
return $result;
}
return 'en';
}
2022-06-27 01:39:01 +08:00
protected function uploadedText(): Attribute
{
return new Attribute(
get: fn($value, $attributes) => mksize($attributes['uploaded'])
);
}
protected function downloadedText(): Attribute
{
return new Attribute(
get: fn($value, $attributes) => mksize($attributes['downloaded'])
);
}
2022-11-08 19:06:37 +08:00
protected function genderText(): Attribute
{
return new Attribute(
get: fn($value, $attributes) => nexus_trans('user.genders.' . $attributes['gender'])
);
}
protected function getTwoFactorAuthenticationStatusAttribute(): string
{
return $this->two_step_secret != "" ? "yes" : "no";
}
2021-12-14 16:22:03 +08:00
public static function getMinSeedPoints($class)
{
$setting = Setting::get("account.{$class}_min_seed_points");
if (is_numeric($setting)) {
return $setting;
}
return self::$classes[$class]['min_seed_points'] ?? false;
}
public function scopeNormal(Builder $query): Builder
{
return $query->where('status', self::STATUS_CONFIRMED)->where('enabled', self::ENABLED_YES);
}
2021-04-25 02:12:14 +08:00
2021-04-26 20:37:17 +08:00
public function exams()
2021-04-25 02:12:14 +08:00
{
2021-04-25 21:28:58 +08:00
return $this->hasMany(ExamUser::class, 'uid');
2021-04-25 02:12:14 +08:00
}
public function language()
{
return $this->belongsTo(Language::class, 'lang');
}
2021-05-10 20:05:52 +08:00
public function invitee_code()
{
return $this->hasOne(Invite::class, 'invitee_register_uid');
}
2021-05-12 13:45:00 +08:00
public function inviter()
{
return $this->belongsTo(User::class, 'invited_by');
}
2022-12-13 13:51:39 +08:00
public function temporary_invites()
{
return $this->hasMany(Invite::class, 'inviter')
->where('invitee', '')
->whereNotNull('expired_at')
->where('expired_at', '>=', Carbon::now())
;
}
2021-05-15 19:29:44 +08:00
public function send_messages()
{
return $this->hasMany(Message::class, 'sender');
}
public function receive_messages()
{
return $this->hasMany(Message::class, 'receiver');
}
2021-05-16 00:35:48 +08:00
/**
* torrent comments
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
2021-05-15 19:29:44 +08:00
public function comments()
{
return $this->hasMany(Comment::class, 'user');
}
2021-05-16 00:35:48 +08:00
/**
* forum posts
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
2021-05-15 19:29:44 +08:00
public function posts()
{
return $this->hasMany(Post::class, 'userid');
}
public function torrents()
{
return $this->hasMany(Torrent::class, 'owner');
}
2021-12-14 16:22:03 +08:00
public function bookmarks(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Bookmark::class, 'userid');
}
2021-05-15 19:29:44 +08:00
public function peers_torrents()
{
return $this->hasManyThrough(
Torrent::class,
Peer::class,
'userid',
'id',
'id',
'torrent');
}
public function snatched_torrents()
{
return $this->hasManyThrough(
Torrent::class,
Snatch::class,
'userid',
'id',
'id',
'torrentid');
}
2022-02-21 17:20:04 +08:00
public function seeding_torrents()
{
return $this->peers_torrents()->where('peers.seeder', Peer::SEEDER_YES);
}
public function leeching_torrents()
{
return $this->peers_torrents()->where('peers.seeder', Peer::SEEDER_NO);
}
public function completed_torrents()
{
return $this->snatched_torrents()->where('snatched.finished', Snatch::FINISHED_YES);
}
public function incomplete_torrents()
{
return $this->snatched_torrents()->where('snatched.finished', Snatch::FINISHED_NO);
}
2021-06-21 02:01:26 +08:00
public function hitAndRuns(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(HitAndRun::class, 'uid');
}
2022-01-19 23:54:55 +08:00
public function medals(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Medal::class, 'user_medals', 'uid', 'medal_id')
->withPivot(['id', 'expire_at', 'status', 'priority'])
2022-03-19 14:55:43 +08:00
->withTimestamps()
->orderByPivot('priority', 'desc')
2022-03-19 14:55:43 +08:00
;
2022-01-19 23:54:55 +08:00
}
public function valid_medals(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->medals()->where(function ($query) {
$query->whereNull('user_medals.expire_at')->orWhere('user_medals.expire_at', '>=', Carbon::now());
});
}
2022-03-19 14:55:43 +08:00
public function wearing_medals(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->valid_medals()->where('user_medals.status', UserMedal::STATUS_WEARING);
}
2022-03-30 15:37:11 +08:00
public function reward_torrent_logs(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Reward::class, 'userid');
}
public function thank_torrent_logs(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Thank::class, 'userid');
}
public function poll_answers(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(PollAnswer::class, 'userid');
}
2022-08-10 17:38:05 +08:00
public function metas()
{
return $this->hasMany(UserMeta::class, 'uid');
}
public function usernameChangeLogs()
{
return $this->hasMany(UsernameChangeLog::class, 'uid');
}
2022-08-17 16:01:55 +08:00
public function roles()
{
return $this->belongsToMany(Role::class, 'user_roles', 'uid', 'role_id')->withTimestamps();
}
2022-08-19 15:30:16 +08:00
public function directPermissions()
2022-08-17 16:01:55 +08:00
{
2022-08-20 19:11:28 +08:00
return $this->hasMany(UserPermission::class, 'uid');
2022-08-17 16:01:55 +08:00
}
2024-05-24 02:27:44 +08:00
public function examAndTasks(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Exam::class, "exam_users", "uid", "exam_id");
}
2024-06-07 02:51:05 +08:00
public function onGoingExamAndTasks(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->examAndTasks()->wherePivot("status", ExamUser::STATUS_NORMAL);
}
public function modifyLogs(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(UserModifyLog::class, "user_id");
}
2021-05-15 19:29:44 +08:00
public function getAvatarAttribute($value)
{
if ($value) {
if (substr($value, 0, 4) == 'http') {
return $value;
} else {
2021-05-17 21:07:50 +08:00
do_log("user: {$this->id} avatar: $value is not valid url.");
2021-05-15 19:29:44 +08:00
}
}
return getSchemeAndHttpHost() . '/pic/default_avatar.png';
}
public function updateWithModComment(array $update, $modComment): bool
2022-06-08 14:15:59 +08:00
{
return $this->updateWithComment($update, $modComment, 'modcomment');
}
public function updateWithComment(array $update, $comment, $commentField): bool
2021-05-14 02:11:57 +08:00
{
if (!$this->exists) {
2022-06-08 14:15:59 +08:00
throw new \RuntimeException('This method only works when user exists !');
2021-05-14 02:11:57 +08:00
}
2021-05-14 20:41:43 +08:00
//@todo how to do prepare bindings here ?
// $comment = addslashes($comment);
// do_log("update: " . json_encode($update) . ", $commentField: $comment", 'notice');
// $update[$commentField] = NexusDB::raw("if($commentField = '', '$comment', concat_ws('\n', '$comment', $commentField))");
if ($commentField != "modcomment") {
throw new \RuntimeException("unsupported commentField: $commentField !");
}
return NexusDB::transaction(function () use ($update, $comment) {
$this->modifyLogs()->create(['content' => $comment]);
return $this->update($update);
});
2021-05-14 02:11:57 +08:00
}
2022-06-12 15:15:09 +08:00
public function canAccessAdmin(): bool
2021-06-04 21:04:12 +08:00
{
$targetClass = self::getAccessAdminClassMin();
2021-06-04 21:04:12 +08:00
if (!$this->class || $this->class < $targetClass) {
do_log(sprintf('user: %s, no class or class < %s, can not access admin.', $this->id, $targetClass));
return false;
}
return true;
}
public static function getAccessAdminClassMin()
{
return Setting::get("system.access_admin_class_min") ?: User::CLASS_ADMINISTRATOR;
}
2022-06-12 15:15:09 +08:00
public function isDonating(): bool
2022-03-28 19:52:10 +08:00
{
2022-04-19 21:03:45 +08:00
$rawDonorUntil = $this->getRawOriginal('donoruntil');
if (
$this->donor == 'yes'
&& ($rawDonorUntil === null || $rawDonorUntil == '0000-00-00 00:00:00' || $this->donoruntil->gte(Carbon::now()))
) {
2022-03-28 19:52:10 +08:00
return true;
}
return false;
}
public function acceptNotification($name): bool
{
return is_null($this->original['notifs']) || str_contains($this->notifs, "[{$name}]");
}
2022-06-12 15:15:09 +08:00
2022-09-25 16:33:52 +08:00
2021-04-02 19:48:41 +08:00
}