Files
Atomcms-edit/app/Models/User.php
T
root f29ba72591 Fix security, performance, and code quality issues across CMS
Security:
- Replace unescaped {!! !!} with Purify::clean() in 15+ Blade templates (XSS)
- Add rate limiting to register (3/hr), upload (10/min), SSE (6/min)
- Add max:5000 validation on article comments
- Remove duplicate exception handler callback

Hardcoded paths:
- Replace ~44 /var/www/ hardcoded paths with env() configs
- CatalogService (13), AutoDetectService (18), Commandocentrum (11), AppServiceProvider (2)

Performance:
- Add 10 missing database indexes (radio_song_requests, help_center_tickets, etc.)
- Replace Cache::flush() with targeted Cache::forget() in RadioSettings
- Cache getCachedCategories() in TicketController (N+1 fix)
- Remove redundant top-3 leaderboard query

Bug fixes:
- Fix undefined $enabled variable → $isOnline in radio index view
- Add getAvatarAttribute() accessor for non-existent avatar column
- Fix User::guilds() from wrong HasMany to HasManyThrough

Code quality:
- Replace file_get_contents with Http::timeout(10) in TraxService
- Remove commented Echo/Pusher boilerplate in bootstrap.js
- Remove TODO/FIXME comments from logo-generator templates
- Replace hardcoded Turnstile CDN URL with config()
- Restore QUEUE_CONNECTION=redis in .env.example files
2026-06-29 18:28:19 +02:00

425 lines
13 KiB
PHP
Executable File

<?php
declare(strict_types=1);
namespace App\Models;
use App\Models\Articles\WebsiteArticle;
use App\Models\Articles\WebsiteArticleComment;
use App\Models\Community\Staff\WebsiteStaffApplications;
use App\Models\Community\Staff\WebsiteTeam;
use App\Models\Game\Furniture\Item;
use App\Models\Game\Guild\Guild;
use App\Models\Game\Guild\GuildMember;
use App\Models\Game\Permission;
use App\Models\Game\Player\MessengerFriendship;
use App\Models\Game\Player\UserBadge;
use App\Models\Game\Player\UserCurrency;
use App\Models\Game\Player\UserSetting;
use App\Models\Game\Player\UserSubscription;
use App\Models\Game\Room;
use App\Models\Help\WebsiteHelpCenterTicket;
use App\Models\Miscellaneous\CameraWeb;
use App\Models\Miscellaneous\WebsiteBetaCode;
use App\Models\Shop\WebsitePaypalTransaction;
use App\Models\Shop\WebsiteUsedShopVoucher;
use App\Models\User\Ban;
use App\Models\User\ClaimedReferralLog;
use App\Models\User\Referral;
use App\Models\User\UserReferral;
use App\Models\User\WebsiteUserGuestbook;
use Filament\Models\Contracts\FilamentUser;
use Filament\Models\Contracts\HasName;
use Filament\Panel;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Fortify\TwoFactorAuthenticationProvider;
use Laravel\Sanctum\HasApiTokens;
use Spatie\Activitylog\Models\Concerns\LogsActivity;
use Spatie\Activitylog\Support\LogOptions;
/**
* @method static \Illuminate\Database\Eloquent\Builder|User query()
* @method static \Illuminate\Database\Eloquent\Builder|User where($column, $operator = null, $value = null)
* @method static \Illuminate\Database\Eloquent\Builder|User orWhere($column, $operator = null, $value = null)
* @method static \Illuminate\Database\Eloquent\Builder|User find($id, $columns = ['*'])
* @method static \Illuminate\Database\Eloquent\Builder|User first($columns = ['*'])
* @method static \Illuminate\Database\Eloquent\Builder|User create(array $attributes)
* @method static \Illuminate\Database\Eloquent\Builder|User updateOrCreate(array $attributes, array $values = [])
* @method static \Illuminate\Database\Eloquent\Builder|User select($columns = ['*'])
* @method static \Illuminate\Database\Eloquent\Builder|User count($columns = '*')
* @method static \Illuminate\Database\Eloquent\Builder|User get($columns = ['*'])
*
* @property int $id
* @property string $username
* @property string $mail
* @property string $password
* @property string $account_created
* @property string|null $last_login
* @property string|null $last_online
* @property string|null $motto
* @property string $look
* @property int $credits
* @property int $rank
* @property bool $online
* @property string $auth_ticket
* @property string $ip_register
* @property string $ip_current
* @property int $home_room
* @property string|null $referral_code
* @property string|null $two_factor_secret
* @property string|null $two_factor_recovery_codes
* @property string|null $two_factor_confirmed_at
* @property bool|null $hidden_staff
* @property int|null $team_id
* @property int|null $website_balance
* @property int $radio_points
* @property array|null $preferences
* @property-read UserSetting|null $settings
* @property-read Collection<int, UserCurrency> $currencies
* @property-read Ban|null $ban
* @property-read Permission $permission
* @property-read UserReferral|null $referrals
* @property-read Collection<int, Referral> $userReferrals
* @property-read Collection<int, ClaimedReferralLog> $claimedReferralLog
* @property-read Collection<int, UserBadge> $badges
* @property-read Collection<int, Room> $rooms
* @property-read Collection<int, MessengerFriendship> $friends
* @property-read Ban|null $banRelation
* @property-read WebsiteBetaCode|null $betaCode
* @property-read WebsiteTeam|null $team
* @property-read Collection<int, WebsiteStaffApplications> $applications
* @property-read UserSubscription|null $hcSubscription
* @property-read Collection<int, WebsiteArticleComment> $articleComments
* @property-read Collection<int, WebsitePaypalTransaction> $transactions
* @property-read Collection<int, WebsiteUsedShopVoucher> $usedShopVouchers
* @property-read Collection<int, Item> $items
* @property-read Collection<int, WebsiteHelpCenterTicket> $tickets
* @property-read Collection<int, CameraWeb> $photos
* @property-read Collection<int, WebsiteUserGuestbook> $profileGuestbook
* @property-read Collection<int, WebsiteUserGuestbook> $guestbook
* @property-read Collection<int, ChatlogRoom> $chatLogs
* @property-read Collection<int, ChatlogPrivate> $chatLogsPrivate
* @property-read Collection<int, WebsiteArticle> $articles
* @property-read Collection<int, Session> $sessions
*
* @extends Factory<User>
*/
class User extends Authenticatable implements FilamentUser, HasName
{
use HasApiTokens;
use HasFactory;
use LogsActivity;
use Notifiable;
use TwoFactorAuthenticatable;
#[\Override]
public $timestamps = false;
#[\Override]
protected $fillable = ['username', 'mail', 'password', 'account_created', 'last_login', 'motto', 'look', 'credits', 'last_username_change', 'auth_ticket', 'home_room', 'ip_register', 'ip_current', 'referral_code', 'preferences', 'avatar_background', 'home_background', 'background_id', 'background_stand_id', 'background_overlay_id', 'gender'];
#[\Override]
protected $hidden = ['password', 'remember_token'];
/**
* @return array<string, string>
*/
#[\Override]
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'hidden_staff' => 'boolean',
'online' => 'boolean',
'preferences' => 'array',
];
}
public function currencies(): HasMany
{
return $this->hasMany(UserCurrency::class, 'user_id');
}
public function sessions(): HasMany
{
return $this->hasMany(Session::class);
}
public function currency(string $currency): int
{
if (! $this->relationLoaded('currencies')) {
$this->load('currencies');
}
$type = match ($currency) {
'duckets' => 0,
'diamonds' => 5,
'points' => 101,
default => 0,
};
return $this->currencies->where('type', $type)->first()->amount ?? 0;
}
public function permission(): HasOne
{
return $this->hasOne(Permission::class, 'id', 'rank');
}
public function articles(): HasMany
{
return $this->hasMany(WebsiteArticle::class);
}
public function referrals(): HasOne
{
return $this->hasOne(UserReferral::class);
}
public function userReferrals(): HasMany
{
return $this->hasMany(Referral::class);
}
public function claimedReferralLog(): HasMany
{
return $this->hasMany(ClaimedReferralLog::class);
}
public function badges(): HasMany
{
return $this->hasMany(UserBadge::class);
}
public function rooms(): HasMany
{
return $this->hasMany(Room::class, 'owner_id');
}
public function friends(): HasMany
{
return $this->hasMany(MessengerFriendship::class, 'user_one_id');
}
public function referralsNeeded(): int
{
$referrals = $this->referrals?->referrals_total ?? 0;
return (int) setting('referrals_needed') - (int) $referrals;
}
public function ban(): HasOne
{
return $this->hasOne(Ban::class, 'user_id')
->where('ban_expire', '>', time())
->whereIn('type', ['account', 'super'])
->withoutGlobalScopes();
}
public function settings(): HasOne
{
return $this->hasOne(UserSetting::class);
}
public function ssoTicket(): string
{
$hotelName = Str::replace(' ', '', (string) setting('hotel_name'));
$sso = sprintf('%s-%s', $hotelName, Str::uuid()->toString());
$this->update([
'auth_ticket' => $sso,
'ip_current' => request()->ip(),
]);
return $sso;
}
public function betaCode(): HasOne
{
return $this->hasOne(WebsiteBetaCode::class);
}
public function team(): BelongsTo
{
return $this->belongsTo(WebsiteTeam::class, 'team_id');
}
public function applications(): HasMany
{
return $this->hasMany(WebsiteStaffApplications::class, 'user_id');
}
public function hcSubscription(): HasOne
{
return $this->hasOne(UserSubscription::class);
}
public function articleComments(): HasMany
{
return $this->hasMany(WebsiteArticleComment::class);
}
public function transactions(): HasMany
{
return $this->hasMany(WebsitePaypalTransaction::class);
}
public function usedShopVouchers(): HasMany
{
return $this->hasMany(WebsiteUsedShopVoucher::class);
}
public function items(): HasMany
{
return $this->hasMany(Item::class, 'user_id');
}
public function tickets(): HasMany
{
return $this->hasMany(WebsiteHelpCenterTicket::class);
}
public function photos(): HasMany
{
return $this->hasMany(CameraWeb::class);
}
public function guilds(): HasManyThrough
{
return $this->hasManyThrough(Guild::class, GuildMember::class, 'user_id', 'id', 'id', 'guild_id');
}
public function guildMemberships(): HasMany
{
return $this->hasMany(GuildMember::class, 'user_id');
}
public function profileGuestbook(): HasMany
{
return $this->hasMany(WebsiteUserGuestbook::class, 'profile_id');
}
public function guestbook(): HasMany
{
return $this->hasMany(WebsiteUserGuestbook::class, 'user_id');
}
public function chatLogs(): HasMany
{
return $this->hasMany(ChatlogRoom::class, 'user_from_id');
}
public function chatLogsPrivate(): HasMany
{
return $this->hasMany(ChatlogPrivate::class, 'user_from_id');
}
public function socialAccounts(): HasMany
{
return $this->hasMany(SocialAccount::class);
}
public function hasSocialAccount(string $provider): bool
{
return $this->socialAccounts()->where('provider', $provider)->exists();
}
public function getOnlineFriends(int $total = 10): Collection
{
return $this->friends()
->select(['user_two_id', 'users.id', 'users.username', 'users.look', 'users.motto', 'users.last_online'])
->join('users', 'users.id', '=', 'user_two_id')
->where('users.online', '1')
->inRandomOrder()
->limit($total)
->get()
->map(fn ($item) => (new User)->forceFill([
'id' => $item->id,
'username' => $item->username,
'look' => $item->look,
'motto' => $item->motto,
'last_online' => $item->last_online,
]));
}
public function confirmTwoFactorAuthentication(string $code): bool
{
$secret = $this->two_factor_secret;
if ($secret === null) {
return false;
}
$codeIsValid = app(TwoFactorAuthenticationProvider::class)
->verify(decrypt((string) $secret), $code);
if (! $codeIsValid) {
return false;
}
$this->forceFill(['two_factor_confirmed_at' => now()])->save();
return true;
}
public function hasAppliedForPosition(int $rankId): bool
{
return $this->applications()->where('rank_id', $rankId)->exists();
}
public function changePassword(string $newPassword): void
{
$this->password = Hash::make($newPassword);
$this->save();
}
public function getAvatarAttribute(): ?string
{
$imager = setting('avatar_imager');
if ($imager && $this->look) {
return $imager . $this->look . '&direction=2&head_direction=3&gesture=sml&action=wav';
}
return null;
}
public function getFilamentName(): string
{
return $this->username ?? 'Guest';
}
public function canAccessPanel(Panel $panel): bool
{
return hasHousekeepingPermission('can_access_housekeeping');
}
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnly(['id', 'username', 'motto', 'rank', 'credits'])
->logOnlyDirty();
}
public function hasAppliedForTeam(int $teamId): bool
{
if ($teamId === 0) {
return false;
}
return $this->applications()
->where('team_id', $teamId)
->exists();
}
}