User::add()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
c 0
b 0
f 0
dl 0
loc 23
rs 9.7998
cc 4
nc 3
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace App\Models;
4
5
use App\Jobs\SendAccountExpiredEmail;
6
use App\Jobs\SendAccountWillExpireEmail;
7
use App\Jobs\SendInviteEmail;
8
use Carbon\CarbonImmutable;
9
use Illuminate\Database\Eloquent\Builder;
10
use Illuminate\Database\Eloquent\Collection;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, App\Models\Collection. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
11
use Illuminate\Database\Eloquent\Model;
12
use Illuminate\Database\Eloquent\ModelNotFoundException;
13
use Illuminate\Database\Eloquent\Relations\BelongsTo;
14
use Illuminate\Database\Eloquent\Relations\HasMany;
15
use Illuminate\Database\Eloquent\Relations\HasOne;
16
use Illuminate\Database\Eloquent\SoftDeletes;
17
use Illuminate\Foundation\Auth\User as Authenticatable;
18
use Illuminate\Http\Request;
19
use Illuminate\Notifications\Notifiable;
20
use Illuminate\Support\Arr;
21
use Illuminate\Support\Carbon;
22
use Illuminate\Support\Facades\Hash;
23
use Illuminate\Support\Facades\Password;
24
use Illuminate\Support\Facades\Validator;
25
use Illuminate\Support\Str;
26
use Jrean\UserVerification\Traits\UserVerification;
27
use Junaidnasir\Larainvite\Facades\Invite;
28
use Junaidnasir\Larainvite\InviteTrait;
29
use Spatie\Permission\Models\Role;
30
use Spatie\Permission\Traits\HasRoles;
0 ignored issues
show
Bug introduced by
The type Spatie\Permission\Traits\HasRoles was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
31
32
/**
33
 * App\Models\User.
34
 *
35
 * App\Models\User.
36
 *
37
 * @property int $id
38
 * @property string $username
39
 * @property string|null $firstname
40
 * @property string|null $lastname
41
 * @property string $email
42
 * @property string $password
43
 * @property int $user_roles_id FK to roles.id
44
 * @property string|null $host
45
 * @property int $grabs
46
 * @property string $rsstoken
47
 * @property \Carbon\Carbon|null $created_at
48
 * @property \Carbon\Carbon|null $updated_at
49
 * @property string|null $resetguid
50
 * @property string|null $lastlogin
51
 * @property string|null $apiaccess
52
 * @property int $invites
53
 * @property int|null $invitedby
54
 * @property int $movieview
55
 * @property int $xxxview
56
 * @property int $musicview
57
 * @property int $consoleview
58
 * @property int $bookview
59
 * @property int $gameview
60
 * @property string|null $saburl
61
 * @property string|null $sabapikey
62
 * @property bool|null $sabapikeytype
63
 * @property bool|null $sabpriority
64
 * @property string|null $nzbgeturl
65
 * @property string|null $nzbgetusername
66
 * @property string|null $nzbgetpassword
67
 * @property string|null $nzbvortex_api_key
68
 * @property string|null $nzbvortex_server_url
69
 * @property string $notes
70
 * @property string|null $cp_url
71
 * @property string|null $cp_api
72
 * @property string|null $style
73
 * @property string|null $rolechangedate When does the role expire
74
 * @property string|null $remember_token
75
 * @property-read Collection|\App\Models\ReleaseComment[] $comment
76
 * @property-read Collection|\App\Models\UserDownload[] $download
77
 * @property-read Collection|\App\Models\DnzbFailure[] $failedRelease
78
 * @property-read Collection|\App\Models\Invitation[] $invitation
79
 * @property-read \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications
80
 * @property-read Collection|\App\Models\UsersRelease[] $release
81
 * @property-read Collection|\App\Models\UserRequest[] $request
82
 * @property-read Collection|\App\Models\UserSerie[] $series
83
 *
84
 * @method static Builder|\App\Models\User whereApiaccess($value)
85
 * @method static Builder|\App\Models\User whereBookview($value)
86
 * @method static Builder|\App\Models\User whereConsoleview($value)
87
 * @method static Builder|\App\Models\User whereCpApi($value)
88
 * @method static Builder|\App\Models\User whereCpUrl($value)
89
 * @method static Builder|\App\Models\User whereCreatedAt($value)
90
 * @method static Builder|\App\Models\User whereEmail($value)
91
 * @method static Builder|\App\Models\User whereFirstname($value)
92
 * @method static Builder|\App\Models\User whereGameview($value)
93
 * @method static Builder|\App\Models\User whereGrabs($value)
94
 * @method static Builder|\App\Models\User whereHost($value)
95
 * @method static Builder|\App\Models\User whereId($value)
96
 * @method static Builder|\App\Models\User whereInvitedby($value)
97
 * @method static Builder|\App\Models\User whereInvites($value)
98
 * @method static Builder|\App\Models\User whereLastlogin($value)
99
 * @method static Builder|\App\Models\User whereLastname($value)
100
 * @method static Builder|\App\Models\User whereMovieview($value)
101
 * @method static Builder|\App\Models\User whereMusicview($value)
102
 * @method static Builder|\App\Models\User whereNotes($value)
103
 * @method static Builder|\App\Models\User whereNzbgetpassword($value)
104
 * @method static Builder|\App\Models\User whereNzbgeturl($value)
105
 * @method static Builder|\App\Models\User whereNzbgetusername($value)
106
 * @method static Builder|\App\Models\User whereNzbvortexApiKey($value)
107
 * @method static Builder|\App\Models\User whereNzbvortexServerUrl($value)
108
 * @method static Builder|\App\Models\User wherePassword($value)
109
 * @method static Builder|\App\Models\User whereRememberToken($value)
110
 * @method static Builder|\App\Models\User whereResetguid($value)
111
 * @method static Builder|\App\Models\User whereRolechangedate($value)
112
 * @method static Builder|\App\Models\User whereRsstoken($value)
113
 * @method static Builder|\App\Models\User whereSabapikey($value)
114
 * @method static Builder|\App\Models\User whereSabapikeytype($value)
115
 * @method static Builder|\App\Models\User whereSabpriority($value)
116
 * @method static Builder|\App\Models\User whereSaburl($value)
117
 * @method static Builder|\App\Models\User whereStyle($value)
118
 * @method static Builder|\App\Models\User whereUpdatedAt($value)
119
 * @method static Builder|\App\Models\User whereUserRolesId($value)
120
 * @method static Builder|\App\Models\User whereUsername($value)
121
 * @method static Builder|\App\Models\User whereXxxview($value)
122
 * @method static Builder|\App\Models\User whereVerified($value)
123
 * @method static Builder|\App\Models\User whereApiToken($value)
124
 *
125
 * @mixin \Eloquent
126
 *
127
 * @property int $roles_id FK to roles.id
128
 * @property string $api_token
129
 * @property int $rate_limit
130
 * @property string|null $email_verified_at
131
 * @property int $verified
132
 * @property string|null $verification_token
133
 * @property-read Collection|\Junaidnasir\Larainvite\Models\LaraInviteModel[] $invitationPending
134
 * @property-read Collection|\Junaidnasir\Larainvite\Models\LaraInviteModel[] $invitationSuccess
135
 * @property-read Collection|\Junaidnasir\Larainvite\Models\LaraInviteModel[] $invitations
136
 * @property-read Collection|\Spatie\Permission\Models\Permission[] $permissions
137
 * @property-read Role $role
138
 * @property-read Collection|\Spatie\Permission\Models\Role[] $roles
139
 *
140
 * @method static Builder|\App\Models\User newModelQuery()
141
 * @method static Builder|\App\Models\User newQuery()
142
 * @method static Builder|\App\Models\User permission($permissions)
143
 * @method static Builder|\App\Models\User query()
144
 * @method static Builder|\App\Models\User whereEmailVerifiedAt($value)
145
 * @method static Builder|\App\Models\User whereRateLimit($value)
146
 * @method static Builder|\App\Models\User whereRolesId($value)
147
 * @method static Builder|\App\Models\User whereVerificationToken($value)
148
 */
149
class User extends Authenticatable
150
{
151
    use HasRoles, InviteTrait, Notifiable, SoftDeletes, UserVerification;
152
153
    public const ERR_SIGNUP_BADUNAME = -1;
154
155
    public const ERR_SIGNUP_BADPASS = -2;
156
157
    public const ERR_SIGNUP_BADEMAIL = -3;
158
159
    public const ERR_SIGNUP_UNAMEINUSE = -4;
160
161
    public const ERR_SIGNUP_EMAILINUSE = -5;
162
163
    public const ERR_SIGNUP_BADINVITECODE = -6;
164
165
    public const SUCCESS = 1;
166
167
    public const ROLE_USER = 1;
168
169
    public const ROLE_ADMIN = 2;
170
171
    /**
172
     * Users SELECT queue type.
173
     */
174
    public const QUEUE_NONE = 0;
175
176
    public const QUEUE_SABNZBD = 1;
177
178
    public const QUEUE_NZBGET = 2;
179
180
    /**
181
     * @var string
182
     */
183
184
    /**
185
     * @var bool
186
     */
187
    protected $dateFormat = false;
188
189
    /**
190
     * @var array
191
     */
192
    protected $hidden = ['remember_token', 'password'];
193
194
    /**
195
     * @var array
196
     */
197
    protected $guarded = [];
198
199
    protected function getDefaultGuardName(): string
200
    {
201
        return 'web';
202
    }
203
204
    public function role(): BelongsTo
205
    {
206
        return $this->belongsTo(Role::class, 'roles_id');
207
    }
208
209
    public function request(): HasMany
210
    {
211
        return $this->hasMany(UserRequest::class, 'users_id');
212
    }
213
214
    public function download(): HasMany
215
    {
216
        return $this->hasMany(UserDownload::class, 'users_id');
217
    }
218
219
    public function release(): HasMany
220
    {
221
        return $this->hasMany(UsersRelease::class, 'users_id');
222
    }
223
224
    public function series(): HasMany
225
    {
226
        return $this->hasMany(UserSerie::class, 'users_id');
227
    }
228
229
    public function invitation(): HasMany
230
    {
231
        return $this->hasMany(Invitation::class, 'users_id');
232
    }
233
234
    public function failedRelease(): HasMany
235
    {
236
        return $this->hasMany(DnzbFailure::class, 'users_id');
237
    }
238
239
    public function comment(): HasMany
240
    {
241
        return $this->hasMany(ReleaseComment::class, 'users_id');
242
    }
243
244
    /**
245
     * @throws \Exception
246
     */
247
    public static function deleteUser($id): void
248
    {
249
        self::find($id)->delete();
250
    }
251
252
    public static function getCount(?string $role = null, ?string $username = '', ?string $host = '', ?string $email = ''): int
253
    {
254
        $res = self::query()->where('email', '<>', '[email protected]');
255
256
        if (! empty($role)) {
257
            $res->where('roles_id', $role);
258
        }
259
260
        if ($username !== '') {
261
            $res->where('username', 'like', '%'.$username.'%');
262
        }
263
264
        if ($host !== '') {
265
            $res->where('host', 'like', '%'.$host.'%');
266
        }
267
268
        if ($email !== '') {
269
            $res->where('email', 'like', '%'.$email.'%');
270
        }
271
272
        return $res->count(['id']);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $res->count(array('id')) could return the type Illuminate\Database\Eloquent\Builder which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
273
    }
274
275
    public static function updateUser(int $id, string $userName, ?string $email, int $grabs, int $role, ?string $notes, int $invites, int $movieview, int $musicview, int $gameview, int $xxxview, int $consoleview, int $bookview, string $style = 'None'): int
276
    {
277
        $userName = trim($userName);
278
279
        $rateLimit = Role::query()->where('id', $role)->first();
280
281
        $sql = [
282
            'username' => $userName,
283
            'grabs' => $grabs,
284
            'roles_id' => $role,
285
            'notes' => substr($notes, 0, 255),
0 ignored issues
show
Bug introduced by
It seems like $notes can also be of type null; however, parameter $string of substr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

285
            'notes' => substr(/** @scrutinizer ignore-type */ $notes, 0, 255),
Loading history...
286
            'invites' => $invites,
287
            'movieview' => $movieview,
288
            'musicview' => $musicview,
289
            'gameview' => $gameview,
290
            'xxxview' => $xxxview,
291
            'consoleview' => $consoleview,
292
            'bookview' => $bookview,
293
            'style' => $style,
294
            'rate_limit' => $rateLimit ? $rateLimit['rate_limit'] : 60,
295
        ];
296
297
        if (! empty($email)) {
298
            $email = trim($email);
299
            $sql += ['email' => $email];
300
        }
301
302
        $user = self::find($id);
303
        $user->update($sql);
304
        $user->syncRoles([$rateLimit['name']]);
305
306
        return self::SUCCESS;
307
    }
308
309
    /**
310
     * @return User|Builder|Model|object|null
311
     */
312
    public static function getByUsername(string $userName)
313
    {
314
        return self::whereUsername($userName)->first();
315
    }
316
317
    /**
318
     * @return Model|static
319
     *
320
     * @throws ModelNotFoundException
321
     */
322
    public static function getByEmail(string $email)
323
    {
324
        return self::whereEmail($email)->first();
325
    }
326
327
    public static function updateUserRole(int $uid, int|string $role): bool
328
    {
329
        if (is_int($role)) {
330
            $roleQuery = Role::query()->where('id', $role)->first();
331
        } else {
332
            $roleQuery = Role::query()->where('name', $role)->first();
333
        }
334
        $roleName = $roleQuery->name;
335
336
        $user = self::find($uid);
337
        $user->syncRoles([$roleName]);
338
339
        return self::find($uid)->update(['roles_id' => $roleQuery->id]);
340
    }
341
342
    public static function updateUserRoleChangeDate(int $uid, $date = '', int $addYear = 0): void
343
    {
344
        $user = self::find($uid);
345
        $currRoleExp = $user->rolechangedate ?? now()->toDateTimeString();
346
        if (! empty($date)) {
347
            $user->update(['rolechangedate' => $date]);
348
        }
349
        if (empty($date) && ! empty($addYear)) {
350
            $user->update(['rolechangedate' => Carbon::createFromDate($currRoleExp)->addYears($addYear)]);
351
        }
352
    }
353
354
    public static function updateExpiredRoles(): void
355
    {
356
        $now = CarbonImmutable::now();
357
        $period = [
358
            'day' => $now->addDay(),
359
            'week' => $now->addWeek(),
360
            'month' => $now->addMonth(),
361
        ];
362
363
        foreach ($period as $value) {
364
            $users = self::query()->whereDate('rolechangedate', '=', $value)->get();
365
            $days = $now->diffInDays($value, true);
366
            foreach ($users as $user) {
367
                SendAccountWillExpireEmail::dispatch($user, $days)->onQueue('emails');
368
            }
369
        }
370
        foreach (self::query()->whereDate('rolechangedate', '<', $now)->get() as $expired) {
371
            $expired->update(['roles_id' => self::ROLE_USER, 'rolechangedate' => null]);
372
            $expired->syncRoles(['User']);
373
            SendAccountExpiredEmail::dispatch($expired)->onQueue('emails');
374
        }
375
    }
376
377
    /**
378
     * @throws \Throwable
379
     */
380
    public static function getRange($start, $offset, $orderBy, ?string $userName = '', ?string $email = '', ?string $host = '', ?string $role = '', bool $apiRequests = false): Collection
381
    {
382
        if ($apiRequests) {
383
            UserRequest::clearApiRequests(false);
384
            $query = "
385
				SELECT users.*, roles.name AS rolename, COUNT(user_requests.id) AS apirequests
386
				FROM users
387
				INNER JOIN roles ON roles.id = users.roles_id
388
				LEFT JOIN user_requests ON user_requests.users_id = users.id
389
				WHERE users.id != 0 %s %s %s %s
390
				AND email != '[email protected]'
391
				GROUP BY users.id
392
				ORDER BY %s %s %s ";
393
        } else {
394
            $query = '
395
				SELECT users.*, roles.name AS rolename
396
				FROM users
397
				INNER JOIN roles ON roles.id = users.roles_id
398
				WHERE 1=1 %s %s %s %s
399
				ORDER BY %s %s %s';
400
        }
401
        $order = self::getBrowseOrder($orderBy);
402
403
        return self::fromQuery(
404
            sprintf(
405
                $query,
406
                ! empty($userName) ? 'AND users.username '.'LIKE '.escapeString('%'.$userName.'%') : '',
407
                ! empty($email) ? 'AND users.email '.'LIKE '.escapeString('%'.$email.'%') : '',
408
                ! empty($host) ? 'AND users.host '.'LIKE '.escapeString('%'.$host.'%') : '',
409
                (! empty($role) ? ('AND users.roles_id = '.$role) : ''),
410
                $order[0],
411
                $order[1],
412
                ($start === false ? '' : ('LIMIT '.$offset.' OFFSET '.$start))
413
            )
414
        );
415
    }
416
417
    /**
418
     * Get sort types for sorting users on the web page user list.
419
     *
420
     * @return string[]
421
     */
422
    public static function getBrowseOrder($orderBy): array
423
    {
424
        $order = (empty($orderBy) ? 'username_desc' : $orderBy);
425
        $orderArr = explode('_', $order);
426
        $orderField = match ($orderArr[0]) {
427
            'email' => 'email',
428
            'host' => 'host',
429
            'createdat' => 'created_at',
430
            'lastlogin' => 'lastlogin',
431
            'apiaccess' => 'apiaccess',
432
            'grabs' => 'grabs',
433
            'role' => 'rolename',
434
            'rolechangedate' => 'rolechangedate',
435
            'verification' => 'verified',
436
            default => 'username',
437
        };
438
        $orderSort = (isset($orderArr[1]) && preg_match('/^asc|desc$/i', $orderArr[1])) ? $orderArr[1] : 'desc';
439
440
        return [$orderField, $orderSort];
441
    }
442
443
    /**
444
     * Verify a password against a hash.
445
     *
446
     * Automatically update the hash if it needs to be.
447
     *
448
     * @param  string  $password  Password to check against hash.
449
     * @param  bool|string  $hash  Hash to check against password.
450
     * @param  int  $userID  ID of the user.
451
     */
452
    public static function checkPassword(string $password, bool|string $hash, int $userID = -1): bool
453
    {
454
        if (Hash::check($password, $hash) === false) {
455
            return false;
456
        }
457
458
        // Update the hash if it needs to be.
459
        if (is_numeric($userID) && $userID > 0 && Hash::needsRehash($hash)) {
460
            $hash = self::hashPassword($password);
461
462
            if ($hash !== false) {
0 ignored issues
show
introduced by
The condition $hash !== false is always true.
Loading history...
463
                self::find($userID)->update(['password' => $hash]);
464
            }
465
        }
466
467
        return true;
468
    }
469
470
    public static function updateRssKey($uid): int
471
    {
472
        self::find($uid)->update(['api_token' => md5(Password::getRepository()->createNewToken())]);
0 ignored issues
show
Bug introduced by
The method createNewToken() does not exist on Illuminate\Auth\Passwords\TokenRepositoryInterface. Did you maybe mean create()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

472
        self::find($uid)->update(['api_token' => md5(Password::getRepository()->/** @scrutinizer ignore-call */ createNewToken())]);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
473
474
        return self::SUCCESS;
475
    }
476
477
    public static function updatePassResetGuid($id, $guid): int
478
    {
479
        self::find($id)->update(['resetguid' => $guid]);
480
481
        return self::SUCCESS;
482
    }
483
484
    public static function updatePassword(int $id, string $password): int
485
    {
486
        self::find($id)->update(['password' => self::hashPassword($password)]);
487
488
        return self::SUCCESS;
489
    }
490
491
    public static function hashPassword($password): string
492
    {
493
        return Hash::make($password);
494
    }
495
496
    /**
497
     * @return Model|static
498
     *
499
     * @throws ModelNotFoundException
500
     */
501
    public static function getByPassResetGuid(string $guid)
502
    {
503
        return self::whereResetguid($guid)->first();
504
    }
505
506
    public static function incrementGrabs(int $id, int $num = 1): void
507
    {
508
        self::find($id)->increment('grabs', $num);
509
    }
510
511
    /**
512
     * @return Model|null|static
513
     */
514
    public static function getByRssToken(string $rssToken)
515
    {
516
        return self::whereApiToken($rssToken)->first();
517
    }
518
519
    public static function isValidUrl($url): bool
520
    {
521
        return (! preg_match('/^(http|https|ftp):\/\/([A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)+):?(\d+)?\/?/i', $url)) ? false : true;
522
    }
523
524
    /**
525
     * @throws \Exception
526
     */
527
    public static function generatePassword(int $length = 15): string
528
    {
529
        return Str::password($length);
530
    }
531
532
    /**
533
     * @throws \Exception
534
     */
535
    public static function signUp($userName, $password, $email, $host, $notes, int $invites = Invitation::DEFAULT_INVITES, string $inviteCode = '', bool $forceInviteMode = false, int $role = self::ROLE_USER, bool $validate = true): bool|int|string
536
    {
537
        $user = [
538
            'username' => trim($userName),
539
            'password' => trim($password),
540
            'email' => trim($email),
541
        ];
542
543
        if ($validate) {
544
            $validator = Validator::make($user, [
545
                'username' => ['required', 'string', 'min:5', 'max:255', 'unique:users'],
546
                'email' => ['required', 'string', 'email', 'max:255', 'unique:users', 'indisposable'],
547
                'password' => ['required', 'string', 'min:8', 'confirmed', 'regex:/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/'],
548
            ]);
549
550
            if ($validator->fails()) {
551
                return implode('', Arr::collapse($validator->errors()->toArray()));
552
            }
553
        }
554
555
        // Make sure this is the last check, as if a further validation check failed, the invite would still have been used up.
556
        $invitedBy = 0;
557
        if (! $forceInviteMode && (int) Settings::settingValue('registerstatus') === Settings::REGISTER_STATUS_INVITE) {
558
            if ($inviteCode === '') {
559
                return self::ERR_SIGNUP_BADINVITECODE;
560
            }
561
562
            $invitedBy = self::checkAndUseInvite($inviteCode);
563
            if ($invitedBy < 0) {
564
                return self::ERR_SIGNUP_BADINVITECODE;
565
            }
566
        }
567
568
        return self::add($user['username'], $user['password'], $user['email'], $role, $notes, $host, $invites, $invitedBy);
569
    }
570
571
    /**
572
     * If a invite is used, decrement the person who invited's invite count.
573
     */
574
    public static function checkAndUseInvite(string $inviteCode): int
575
    {
576
        $invite = Invitation::getInvite($inviteCode);
577
        if (! $invite) {
578
            return -1;
579
        }
580
581
        self::query()->where('id', $invite['users_id'])->decrement('invites');
582
        Invitation::deleteInvite($inviteCode);
583
584
        return $invite['users_id'];
585
    }
586
587
    /**
588
     * @return false|int|mixed
589
     */
590
    public static function add(string $userName, string $password, string $email, int $role, ?string $notes = '', string $host = '', int $invites = Invitation::DEFAULT_INVITES, int $invitedBy = 0)
591
    {
592
        $password = self::hashPassword($password);
593
        if (! $password) {
594
            return false;
595
        }
596
597
        $storeips = config('nntmux:settings.store_user_ip') === true ? $host : '';
598
599
        $user = self::create(
600
            [
601
                'username' => $userName,
602
                'password' => $password,
603
                'email' => $email,
604
                'host' => $storeips,
605
                'roles_id' => $role,
606
                'invites' => $invites,
607
                'invitedby' => (int) $invitedBy === 0 ? null : $invitedBy,
608
                'notes' => $notes,
609
            ]
610
        );
611
612
        return $user->id;
613
    }
614
615
    /**
616
     * Get the list of categories the user has excluded.
617
     *
618
     * @param  int  $userID  ID of the user.
619
     *
620
     * @throws \Exception
621
     */
622
    public static function getCategoryExclusionById(int $userID): array
623
    {
624
        $ret = [];
625
626
        $user = self::find($userID);
627
628
        $userAllowed = $user->getDirectPermissions()->pluck('name')->toArray();
629
        $roleAllowed = $user->getAllPermissions()->pluck('name')->toArray();
630
631
        $allowed = array_intersect($roleAllowed, $userAllowed);
632
633
        $cats = ['view console', 'view movies', 'view audio', 'view tv', 'view pc', 'view adult', 'view books', 'view other'];
634
635
        if (! empty($allowed)) {
636
            foreach ($cats as $cat) {
637
                if (! \in_array($cat, $allowed, false)) {
638
                    $ret[] = match ($cat) {
639
                        'view console' => 1000,
640
                        'view movies' => 2000,
641
                        'view audio' => 3000,
642
                        'view pc' => 4000,
643
                        'view tv' => 5000,
644
                        'view adult' => 6000,
645
                        'view books' => 7000,
646
                        'view other' => 1,
647
                    };
648
                }
649
            }
650
        }
651
652
        return Category::query()->whereIn('root_categories_id', $ret)->pluck('id')->toArray();
653
    }
654
655
    /**
656
     * @throws \Exception
657
     */
658
    public static function getCategoryExclusionForApi(Request $request): array
659
    {
660
        $apiToken = $request->has('api_token') ? $request->input('api_token') : $request->input('apikey');
661
        $user = self::getByRssToken($apiToken);
662
663
        return self::getCategoryExclusionById($user->id);
664
    }
665
666
    /**
667
     * @throws \Exception
668
     */
669
    public static function sendInvite($serverUrl, $uid, $emailTo): string
670
    {
671
        $user = self::find($uid);
672
        $token = Invite::invite($emailTo, $user->id);
673
        $url = $serverUrl.'/register?invitecode='.$token;
674
675
        Invitation::addInvite($uid, $token);
676
        SendInviteEmail::dispatch($emailTo, $user, $url)->onQueue('emails');
677
678
        return $url;
679
    }
680
681
    /**
682
     * Deletes users that have not verified their accounts for 3 or more days.
683
     */
684
    public static function deleteUnVerified(): void
685
    {
686
        static::whereVerified(0)->where('created_at', '<', now()->subDays(3))->delete();
687
    }
688
689
    public function passwordSecurity(): HasOne
690
    {
691
        return $this->hasOne(PasswordSecurity::class);
692
    }
693
}
694