Passed
Push — master ( cc786a...5052e6 )
by Darko
07:18 queued 12s
created

User::getRange()   B

Complexity

Conditions 7
Paths 2

Size

Total Lines 33
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 16
dl 0
loc 33
rs 8.8333
c 1
b 1
f 0
cc 7
nc 2
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 DariusIII\Token\Facades\Token;
10
use Illuminate\Database\Eloquent\Builder;
11
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...
12
use Illuminate\Database\Eloquent\Model;
13
use Illuminate\Database\Eloquent\ModelNotFoundException;
14
use Illuminate\Database\Eloquent\Relations\BelongsTo;
15
use Illuminate\Database\Eloquent\Relations\HasMany;
16
use Illuminate\Database\Eloquent\Relations\HasOne;
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;
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 $userseed
70
 * @property string $notes
71
 * @property string|null $cp_url
72
 * @property string|null $cp_api
73
 * @property string|null $style
74
 * @property string|null $rolechangedate When does the role expire
75
 * @property string|null $remember_token
76
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ReleaseComment[] $comment
77
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\UserDownload[] $download
78
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\DnzbFailure[] $failedRelease
79
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\Invitation[] $invitation
80
 * @property-read \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications
81
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\UsersRelease[] $release
82
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\UserRequest[] $request
83
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\UserSerie[] $series
84
 *
85
 * @method static Builder|\App\Models\User whereApiaccess($value)
86
 * @method static Builder|\App\Models\User whereBookview($value)
87
 * @method static Builder|\App\Models\User whereConsoleview($value)
88
 * @method static Builder|\App\Models\User whereCpApi($value)
89
 * @method static Builder|\App\Models\User whereCpUrl($value)
90
 * @method static Builder|\App\Models\User whereCreatedAt($value)
91
 * @method static Builder|\App\Models\User whereEmail($value)
92
 * @method static Builder|\App\Models\User whereFirstname($value)
93
 * @method static Builder|\App\Models\User whereGameview($value)
94
 * @method static Builder|\App\Models\User whereGrabs($value)
95
 * @method static Builder|\App\Models\User whereHost($value)
96
 * @method static Builder|\App\Models\User whereId($value)
97
 * @method static Builder|\App\Models\User whereInvitedby($value)
98
 * @method static Builder|\App\Models\User whereInvites($value)
99
 * @method static Builder|\App\Models\User whereLastlogin($value)
100
 * @method static Builder|\App\Models\User whereLastname($value)
101
 * @method static Builder|\App\Models\User whereMovieview($value)
102
 * @method static Builder|\App\Models\User whereMusicview($value)
103
 * @method static Builder|\App\Models\User whereNotes($value)
104
 * @method static Builder|\App\Models\User whereNzbgetpassword($value)
105
 * @method static Builder|\App\Models\User whereNzbgeturl($value)
106
 * @method static Builder|\App\Models\User whereNzbgetusername($value)
107
 * @method static Builder|\App\Models\User whereNzbvortexApiKey($value)
108
 * @method static Builder|\App\Models\User whereNzbvortexServerUrl($value)
109
 * @method static Builder|\App\Models\User wherePassword($value)
110
 * @method static Builder|\App\Models\User whereRememberToken($value)
111
 * @method static Builder|\App\Models\User whereResetguid($value)
112
 * @method static Builder|\App\Models\User whereRolechangedate($value)
113
 * @method static Builder|\App\Models\User whereRsstoken($value)
114
 * @method static Builder|\App\Models\User whereSabapikey($value)
115
 * @method static Builder|\App\Models\User whereSabapikeytype($value)
116
 * @method static Builder|\App\Models\User whereSabpriority($value)
117
 * @method static Builder|\App\Models\User whereSaburl($value)
118
 * @method static Builder|\App\Models\User whereStyle($value)
119
 * @method static Builder|\App\Models\User whereUpdatedAt($value)
120
 * @method static Builder|\App\Models\User whereUserRolesId($value)
121
 * @method static Builder|\App\Models\User whereUsername($value)
122
 * @method static Builder|\App\Models\User whereUserseed($value)
123
 * @method static Builder|\App\Models\User whereXxxview($value)
124
 * @method static Builder|\App\Models\User whereVerified($value)
125
 * @method static Builder|\App\Models\User whereApiToken($value)
126
 *
127
 * @mixin \Eloquent
128
 *
129
 * @property int $roles_id FK to roles.id
130
 * @property string $api_token
131
 * @property int $rate_limit
132
 * @property string|null $email_verified_at
133
 * @property int $verified
134
 * @property string|null $verification_token
135
 * @property-read \Illuminate\Database\Eloquent\Collection|\Junaidnasir\Larainvite\Models\LaraInviteModel[] $invitationPending
136
 * @property-read \Illuminate\Database\Eloquent\Collection|\Junaidnasir\Larainvite\Models\LaraInviteModel[] $invitationSuccess
137
 * @property-read \Illuminate\Database\Eloquent\Collection|\Junaidnasir\Larainvite\Models\LaraInviteModel[] $invitations
138
 * @property-read \Illuminate\Database\Eloquent\Collection|\Spatie\Permission\Models\Permission[] $permissions
139
 * @property-read \Spatie\Permission\Models\Role $role
140
 * @property-read \Illuminate\Database\Eloquent\Collection|\Spatie\Permission\Models\Role[] $roles
141
 *
142
 * @method static Builder|\App\Models\User newModelQuery()
143
 * @method static Builder|\App\Models\User newQuery()
144
 * @method static Builder|\App\Models\User permission($permissions)
145
 * @method static Builder|\App\Models\User query()
146
 * @method static Builder|\App\Models\User whereEmailVerifiedAt($value)
147
 * @method static Builder|\App\Models\User whereRateLimit($value)
148
 * @method static Builder|\App\Models\User whereRolesId($value)
149
 * @method static Builder|\App\Models\User whereVerificationToken($value)
150
 */
151
class User extends Authenticatable
152
{
153
    use HasRoles, InviteTrait, Notifiable, UserVerification;
0 ignored issues
show
introduced by
The trait Spatie\Permission\Traits\HasRoles requires some properties which are not provided by App\Models\User: $name, $guard_name
Loading history...
154
155
    public const ERR_SIGNUP_BADUNAME = -1;
156
157
    public const ERR_SIGNUP_BADPASS = -2;
158
159
    public const ERR_SIGNUP_BADEMAIL = -3;
160
161
    public const ERR_SIGNUP_UNAMEINUSE = -4;
162
163
    public const ERR_SIGNUP_EMAILINUSE = -5;
164
165
    public const ERR_SIGNUP_BADINVITECODE = -6;
166
167
    public const SUCCESS = 1;
168
169
    public const ROLE_USER = 1;
170
171
    public const ROLE_ADMIN = 2;
172
173
    /**
174
     * Users SELECT queue type.
175
     */
176
    public const QUEUE_NONE = 0;
177
178
    public const QUEUE_SABNZBD = 1;
179
180
    public const QUEUE_NZBGET = 2;
181
182
    /**
183
     * @var string
184
     */
185
186
    /**
187
     * @var bool
188
     */
189
    protected $dateFormat = false;
190
191
    /**
192
     * @var array
193
     */
194
    protected $hidden = ['remember_token', 'password'];
195
196
    /**
197
     * @var array
198
     */
199
    protected $guarded = [];
200
201
    public function role(): BelongsTo
202
    {
203
        return $this->belongsTo(Role::class, 'roles_id');
204
    }
205
206
    public function request(): HasMany
207
    {
208
        return $this->hasMany(UserRequest::class, 'users_id');
209
    }
210
211
    public function download(): HasMany
212
    {
213
        return $this->hasMany(UserDownload::class, 'users_id');
214
    }
215
216
    public function release(): HasMany
217
    {
218
        return $this->hasMany(UsersRelease::class, 'users_id');
219
    }
220
221
    public function series(): HasMany
222
    {
223
        return $this->hasMany(UserSerie::class, 'users_id');
224
    }
225
226
    public function invitation(): HasMany
227
    {
228
        return $this->hasMany(Invitation::class, 'users_id');
229
    }
230
231
    public function failedRelease(): HasMany
232
    {
233
        return $this->hasMany(DnzbFailure::class, 'users_id');
234
    }
235
236
    public function comment(): HasMany
237
    {
238
        return $this->hasMany(ReleaseComment::class, 'users_id');
239
    }
240
241
    /**
242
     * @throws \Exception
243
     */
244
    public static function deleteUser($id): void
245
    {
246
        self::find($id)->delete();
247
    }
248
249
    public static function getCount(string $role = null, ?string $username = '', ?string $host = '', ?string $email = ''): int
250
    {
251
        $res = self::query()->where('email', '<>', '[email protected]');
252
253
        if (! empty($role)) {
254
            $res->where('roles_id', $role);
255
        }
256
257
        if ($username !== '') {
258
            $res->where('username', 'like', '%'.$username.'%');
259
        }
260
261
        if ($host !== '') {
262
            $res->where('host', 'like', '%'.$host.'%');
263
        }
264
265
        if ($email !== '') {
266
            $res->where('email', 'like', '%'.$email.'%');
267
        }
268
269
        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...
270
    }
271
272
    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
273
    {
274
        $userName = trim($userName);
275
276
        $rateLimit = Role::query()->where('id', $role)->first();
277
278
        $sql = [
279
            'username' => $userName,
280
            'grabs' => $grabs,
281
            'roles_id' => $role,
282
            '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

282
            'notes' => substr(/** @scrutinizer ignore-type */ $notes, 0, 255),
Loading history...
283
            'invites' => $invites,
284
            'movieview' => $movieview,
285
            'musicview' => $musicview,
286
            'gameview' => $gameview,
287
            'xxxview' => $xxxview,
288
            'consoleview' => $consoleview,
289
            'bookview' => $bookview,
290
            'style' => $style,
291
            'rate_limit' => $rateLimit ? $rateLimit['rate_limit'] : 60,
292
        ];
293
294
        if (! empty($email)) {
295
            $email = trim($email);
296
            $sql += ['email' => $email];
297
        }
298
299
        $user = self::find($id);
300
        $user->update($sql);
301
        $user->syncRoles([$rateLimit['name']]);
302
303
        return self::SUCCESS;
304
    }
305
306
    /**
307
     * @return User|Builder|Model|object|null
308
     */
309
    public static function getByUsername(string $userName)
310
    {
311
        return self::whereUsername($userName)->first();
312
    }
313
314
    /**
315
     * @return Model|static
316
     *
317
     * @throws ModelNotFoundException
318
     */
319
    public static function getByEmail(string $email)
320
    {
321
        return self::whereEmail($email)->first();
322
    }
323
324
    public static function updateUserRole(int $uid, int $role): bool
325
    {
326
        $roleQuery = Role::query()->where('id', $role)->first();
327
        $roleName = $roleQuery->name;
328
329
        $user = self::find($uid);
330
        $user->syncRoles([$roleName]);
331
332
        return self::find($uid)->update(['roles_id' => $role]);
333
    }
334
335
    public static function updateUserRoleChangeDate(int $uid, $date = '', int $addYear = 0): void
336
    {
337
        $user = self::find($uid);
338
        $currRoleExp = $user->rolechangedate ?? now()->toDateTimeString();
339
        if (! empty($date)) {
340
            $user->update(['rolechangedate' => $date]);
341
        }
342
        if (empty($date) && ! empty($addYear)) {
343
            $user->update(['rolechangedate' => Carbon::createFromDate($currRoleExp)->addYears($addYear)]);
0 ignored issues
show
Bug introduced by
It seems like $currRoleExp can also be of type string; however, parameter $year of Carbon\Carbon::createFromDate() does only seem to accept integer|null, 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

343
            $user->update(['rolechangedate' => Carbon::createFromDate(/** @scrutinizer ignore-type */ $currRoleExp)->addYears($addYear)]);
Loading history...
344
        }
345
    }
346
347
    public static function updateExpiredRoles(): void
348
    {
349
        $now = CarbonImmutable::now();
350
        $period = [
351
            'day' => $now->addDay(),
352
            'week' => $now->addWeek(),
353
            'month' => $now->addMonth(),
354
        ];
355
356
        foreach ($period as $value) {
357
            $users = self::query()->whereDate('rolechangedate', '=', $value)->get();
358
            $days = $now->diffInDays($value);
359
            foreach ($users as $user) {
360
                SendAccountWillExpireEmail::dispatch($user, $days)->onQueue('emails');
361
            }
362
        }
363
        foreach (self::query()->whereDate('rolechangedate', '<', $now)->get() as $expired) {
364
            $expired->update(['roles_id' => self::ROLE_USER, 'rolechangedate' => null]);
365
            $expired->syncRoles(['User']);
366
            SendAccountExpiredEmail::dispatch($expired)->onQueue('emails');
367
        }
368
    }
369
370
    /**
371
     * @throws \Throwable
372
     */
373
    public static function getRange($start, $offset, $orderBy, ?string $userName = '', ?string $email = '', ?string $host = '', ?string $role = '', bool $apiRequests = false): Collection
374
    {
375
        if ($apiRequests) {
376
            UserRequest::clearApiRequests(false);
377
            $query = "
378
				SELECT users.*, roles.name AS rolename, COUNT(user_requests.id) AS apirequests
379
				FROM users
380
				INNER JOIN roles ON roles.id = users.roles_id
381
				LEFT JOIN user_requests ON user_requests.users_id = users.id
382
				WHERE users.id != 0 %s %s %s %s
383
				AND email != '[email protected]'
384
				GROUP BY users.id
385
				ORDER BY %s %s %s ";
386
        } else {
387
            $query = '
388
				SELECT users.*, roles.name AS rolename
389
				FROM users
390
				INNER JOIN roles ON roles.id = users.roles_id
391
				WHERE 1=1 %s %s %s %s
392
				ORDER BY %s %s %s';
393
        }
394
        $order = self::getBrowseOrder($orderBy);
395
396
        return self::fromQuery(
0 ignored issues
show
Bug Best Practice introduced by
The expression return self::fromQuery(s.... ' OFFSET ' . $start)) could return the type Illuminate\Database\Eloq...gHasThroughRelationship which is incompatible with the type-hinted return Illuminate\Database\Eloquent\Collection. Consider adding an additional type-check to rule them out.
Loading history...
397
            sprintf(
398
                $query,
399
                ! empty($userName) ? 'AND users.username '.'LIKE '.escapeString('%'.$userName.'%') : '',
400
                ! empty($email) ? 'AND users.email '.'LIKE '.escapeString('%'.$email.'%') : '',
401
                ! empty($host) ? 'AND users.host '.'LIKE '.escapeString('%'.$host.'%') : '',
402
                (! empty($role) ? ('AND users.roles_id = '.$role) : ''),
403
                $order[0],
404
                $order[1],
405
                ($start === false ? '' : ('LIMIT '.$offset.' OFFSET '.$start))
406
            )
407
        );
408
    }
409
410
    /**
411
     * Get sort types for sorting users on the web page user list.
412
     *
413
     * @return string[]
414
     */
415
    public static function getBrowseOrder($orderBy): array
416
    {
417
        $order = (empty($orderBy) ? 'username_desc' : $orderBy);
418
        $orderArr = explode('_', $order);
419
        $orderField = match ($orderArr[0]) {
420
            'email' => 'email',
421
            'host' => 'host',
422
            'createdat' => 'created_at',
423
            'lastlogin' => 'lastlogin',
424
            'apiaccess' => 'apiaccess',
425
            'grabs' => 'grabs',
426
            'role' => 'rolename',
427
            'rolechangedate' => 'rolechangedate',
428
            'verification' => 'verified',
429
            default => 'username',
430
        };
431
        $orderSort = (isset($orderArr[1]) && preg_match('/^asc|desc$/i', $orderArr[1])) ? $orderArr[1] : 'desc';
432
433
        return [$orderField, $orderSort];
434
    }
435
436
    /**
437
     * Verify a password against a hash.
438
     *
439
     * Automatically update the hash if it needs to be.
440
     *
441
     * @param  string  $password  Password to check against hash.
442
     * @param  bool|string  $hash  Hash to check against password.
443
     * @param  int  $userID  ID of the user.
444
     */
445
    public static function checkPassword(string $password, bool|string $hash, int $userID = -1): bool
446
    {
447
        if (Hash::check($password, $hash) === false) {
448
            return false;
449
        }
450
451
        // Update the hash if it needs to be.
452
        if (is_numeric($userID) && $userID > 0 && Hash::needsRehash($hash)) {
453
            $hash = self::hashPassword($password);
454
455
            if ($hash !== false) {
0 ignored issues
show
introduced by
The condition $hash !== false is always true.
Loading history...
456
                self::find($userID)->update(['password' => $hash]);
457
            }
458
        }
459
460
        return true;
461
    }
462
463
    public static function updateRssKey($uid): int
464
    {
465
        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

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