Completed
Push — dev ( 0c8160...357043 )
by Darko
08:50
created

User   F

Complexity

Total Complexity 105

Size/Duplication

Total Lines 825
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 3
Bugs 2 Features 0
Metric Value
wmc 105
eloc 283
c 3
b 2
f 0
dl 0
loc 825
ccs 0
cts 286
cp 0
rs 2

38 Methods

Rating   Name   Duplication   Size   Complexity  
A comment() 0 3 1
A getByUsername() 0 3 1
A getByEmail() 0 3 1
A deleteUser() 0 3 1
A failedRelease() 0 3 1
A release() 0 3 1
A getCount() 0 21 5
A role() 0 3 1
A request() 0 3 1
A download() 0 3 1
A invitation() 0 3 1
A series() 0 3 1
A updateUserRole() 0 3 1
A updateUserRoleChangeDate() 0 9 4
A updateUser() 0 44 3
A updatePassResetGuid() 0 5 1
A getCategoryExclusionForApi() 0 6 2
A add() 0 23 4
B updateExpiredRoles() 0 41 8
A getByRssToken() 0 3 1
A updatePassword() 0 5 1
A deleteUnVerified() 0 3 1
A generatePassword() 0 3 1
A checkAndUseInvite() 0 11 2
A getUsersByMonth() 0 3 1
B getRange() 0 33 7
A pruneRequestHistory() 0 9 2
A isValidUrl() 0 3 2
A getByIdAndRssToken() 0 8 2
A generateUsername() 0 3 1
A updateRssKey() 0 5 1
C getBrowseOrder() 0 42 14
A getByPassResetGuid() 0 3 1
C getCategoryExclusionById() 0 49 12
A incrementGrabs() 0 3 1
A getTopGrabbers() 0 3 1
A sendInvite() 0 9 1
B signUp() 0 36 7

How to fix   Complexity   

Complex Class

Complex classes like User often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use User, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace App\Models;
4
5
use Illuminate\Support\Arr;
6
use Illuminate\Support\Str;
7
use Illuminate\Http\Request;
8
use App\Jobs\SendInviteEmail;
9
use Illuminate\Support\Carbon;
10
use Spatie\Permission\Models\Role;
11
use Illuminate\Support\Facades\Hash;
12
use App\Jobs\SendAccountExpiredEmail;
13
use Spatie\Permission\Traits\HasRoles;
14
use App\Jobs\SendAccountWillExpireEmail;
15
use Illuminate\Notifications\Notifiable;
16
use Illuminate\Support\Facades\Password;
17
use Illuminate\Support\Facades\Validator;
18
use Jrean\UserVerification\Traits\UserVerification;
19
use Illuminate\Foundation\Auth\User as Authenticatable;
20
21
/**
22
 * App\Models\User.
23
 *
24
 * @property int $id
25
 * @property string $username
26
 * @property string|null $firstname
27
 * @property string|null $lastname
28
 * @property string $email
29
 * @property string $password
30
 * @property int $user_roles_id FK to roles.id
31
 * @property string|null $host
32
 * @property int $grabs
33
 * @property string $rsstoken
34
 * @property \Carbon\Carbon|null $created_at
35
 * @property \Carbon\Carbon|null $updated_at
36
 * @property string|null $resetguid
37
 * @property string|null $lastlogin
38
 * @property string|null $apiaccess
39
 * @property int $invites
40
 * @property int|null $invitedby
41
 * @property int $movieview
42
 * @property int $xxxview
43
 * @property int $musicview
44
 * @property int $consoleview
45
 * @property int $bookview
46
 * @property int $gameview
47
 * @property string|null $saburl
48
 * @property string|null $sabapikey
49
 * @property bool|null $sabapikeytype
50
 * @property bool|null $sabpriority
51
 * @property bool $queuetype Type of queue, Sab or NZBGet
52
 * @property string|null $nzbgeturl
53
 * @property string|null $nzbgetusername
54
 * @property string|null $nzbgetpassword
55
 * @property string|null $nzbvortex_api_key
56
 * @property string|null $nzbvortex_server_url
57
 * @property string $userseed
58
 * @property string $notes
59
 * @property string|null $cp_url
60
 * @property string|null $cp_api
61
 * @property string|null $style
62
 * @property string|null $rolechangedate When does the role expire
63
 * @property string|null $remember_token
64
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\ReleaseComment[] $comment
65
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\UserDownload[] $download
66
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\DnzbFailure[] $failedRelease
67
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\Invitation[] $invitation
68
 * @property-read \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications
69
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\UsersRelease[] $release
70
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\UserRequest[] $request
71
 * @property-read \Illuminate\Database\Eloquent\Collection|\App\Models\UserSerie[] $series
72
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereApiaccess($value)
73
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereBookview($value)
74
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereConsoleview($value)
75
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereCpApi($value)
76
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereCpUrl($value)
77
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereCreatedAt($value)
78
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereEmail($value)
79
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereFirstname($value)
80
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereGameview($value)
81
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereGrabs($value)
82
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereHost($value)
83
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereId($value)
84
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereInvitedby($value)
85
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereInvites($value)
86
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereLastlogin($value)
87
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereLastname($value)
88
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereMovieview($value)
89
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereMusicview($value)
90
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereNotes($value)
91
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereNzbgetpassword($value)
92
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereNzbgeturl($value)
93
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereNzbgetusername($value)
94
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereNzbvortexApiKey($value)
95
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereNzbvortexServerUrl($value)
96
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User wherePassword($value)
97
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereQueuetype($value)
98
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereRememberToken($value)
99
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereResetguid($value)
100
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereRolechangedate($value)
101
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereRsstoken($value)
102
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereSabapikey($value)
103
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereSabapikeytype($value)
104
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereSabpriority($value)
105
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereSaburl($value)
106
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereStyle($value)
107
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereUpdatedAt($value)
108
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereUserRolesId($value)
109
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereUsername($value)
110
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereUserseed($value)
111
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereXxxview($value)
112
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereVerified($value)
113
 * @method static \Illuminate\Database\Eloquent\Builder|\App\Models\User whereApiToken($value)
114
 * @mixin \Eloquent
115
 */
116
class User extends Authenticatable
117
{
118
    use Notifiable, UserVerification, HasRoles;
0 ignored issues
show
introduced by
The trait Spatie\Permission\Traits\HasRoles requires some properties which are not provided by App\Models\User: $name, $map, $permissions, $roles, $guard_name
Loading history...
introduced by
The trait Jrean\UserVerification\Traits\UserVerification requires some properties which are not provided by App\Models\User: $verified, $verification_token
Loading history...
Bug introduced by
The trait Illuminate\Notifications\Notifiable requires the property $phone_number which is not provided by App\Models\User.
Loading history...
119
120
    public const ERR_SIGNUP_BADUNAME = -1;
121
    public const ERR_SIGNUP_BADPASS = -2;
122
    public const ERR_SIGNUP_BADEMAIL = -3;
123
    public const ERR_SIGNUP_UNAMEINUSE = -4;
124
    public const ERR_SIGNUP_EMAILINUSE = -5;
125
    public const ERR_SIGNUP_BADINVITECODE = -6;
126
    public const ERR_SIGNUP_BADCAPTCHA = -7;
127
    public const SUCCESS = 1;
128
129
    public const ROLE_USER = 1;
130
    public const ROLE_ADMIN = 2;
131
    public const ROLE_DISABLED = 3;
132
    public const ROLE_MODERATOR = 4;
133
134
    /**
135
     * Users SELECT queue type.
136
     */
137
    public const QUEUE_NONE = 0;
138
    public const QUEUE_SABNZBD = 1;
139
    public const QUEUE_NZBGET = 2;
140
141
    /**
142
     * @var string
143
     */
144
    protected $table = 'users';
145
146
    /**
147
     * @var bool
148
     */
149
    protected $dateFormat = false;
150
151
    /**
152
     * @var array
153
     */
154
    protected $hidden = ['remember_token', 'password'];
155
156
    /**
157
     * @var array
158
     */
159
    protected $guarded = [];
160
161
    /**
162
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
163
     */
164
    public function role()
165
    {
166
        return $this->belongsTo(Role::class, 'roles_id');
167
    }
168
169
    /**
170
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
171
     */
172
    public function request()
173
    {
174
        return $this->hasMany(UserRequest::class, 'users_id');
175
    }
176
177
    /**
178
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
179
     */
180
    public function download()
181
    {
182
        return $this->hasMany(UserDownload::class, 'users_id');
183
    }
184
185
    /**
186
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
187
     */
188
    public function release()
189
    {
190
        return $this->hasMany(UsersRelease::class, 'users_id');
191
    }
192
193
    /**
194
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
195
     */
196
    public function series()
197
    {
198
        return $this->hasMany(UserSerie::class, 'users_id');
199
    }
200
201
    /**
202
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
203
     */
204
    public function invitation()
205
    {
206
        return $this->hasMany(Invitation::class, 'users_id');
207
    }
208
209
    /**
210
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
211
     */
212
    public function failedRelease()
213
    {
214
        return $this->hasMany(DnzbFailure::class, 'users_id');
215
    }
216
217
    /**
218
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
219
     */
220
    public function comment()
221
    {
222
        return $this->hasMany(ReleaseComment::class, 'users_id');
223
    }
224
225
    /**
226
     * @param $id
227
     * @throws \Exception
228
     */
229
    public static function deleteUser($id): void
230
    {
231
        self::find($id)->delete();
232
    }
233
234
    /**
235
     * @param string $role
236
     * @param string $username
237
     * @param string $host
238
     * @param string $email
239
     * @return int
240
     */
241
    public static function getCount($role = '', $username = '', $host = '', $email = ''): int
242
    {
243
        $res = self::query()->where('email', '<>', '[email protected]');
244
245
        if ($role !== '') {
246
            $res->where('roles_id', $role);
247
        }
248
249
        if ($username !== '') {
250
            $res->where('username', 'like', '%'.$username.'%');
251
        }
252
253
        if ($host !== '') {
254
            $res->where('host', 'like', '%'.$host.'%');
255
        }
256
257
        if ($email !== '') {
258
            $res->where('email', 'like', '%'.$email.'%');
259
        }
260
261
        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...
262
    }
263
264
    /**
265
     * @param  int      $id
266
     * @param  string      $userName
267
     * @param  string      $email
268
     * @param  int     $grabs
269
     * @param  int     $role
270
     * @param  string    $notes
271
     * @param  int     $invites
272
     * @param  int     $movieview
273
     * @param  int    $musicview
274
     * @param  int   $gameview
275
     * @param  int    $xxxview
276
     * @param  int    $consoleview
277
     * @param  int    $bookview
278
     * @param string $queueType
279
     * @param string $nzbgetURL
280
     * @param string $nzbgetUsername
281
     * @param string $nzbgetPassword
282
     * @param string $saburl
283
     * @param string $sabapikey
284
     * @param string $sabpriority
285
     * @param string $sabapikeytype
286
     * @param bool   $nzbvortexServerUrl
287
     * @param bool   $nzbvortexApiKey
288
     * @param bool   $cp_url
289
     * @param bool   $cp_api
290
     * @param string $style
291
     *
292
     * @return int
293
     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
294
     */
295
    public static function updateUser($id, $userName, $email, $grabs, $role, $notes, $invites, $movieview, $musicview, $gameview, $xxxview, $consoleview, $bookview, $queueType = '', $nzbgetURL = '', $nzbgetUsername = '', $nzbgetPassword = '', $saburl = '', $sabapikey = '', $sabpriority = '', $sabapikeytype = '', $nzbvortexServerUrl = false, $nzbvortexApiKey = false, $cp_url = false, $cp_api = false, $style = 'None'): int
296
    {
297
        $userName = trim($userName);
298
299
        $rateLimit = Role::query()->where('id', $role)->first();
300
301
        $sql = [
302
            'username' => $userName,
303
            'grabs' => $grabs,
304
            'roles_id' => $role,
305
            'notes' => substr($notes, 0, 255),
306
            'invites' => $invites,
307
            'movieview' => $movieview,
308
            'musicview' => $musicview,
309
            'gameview' => $gameview,
310
            'xxxview' => $xxxview,
311
            'consoleview' => $consoleview,
312
            'bookview' => $bookview,
313
            'style' => $style,
314
            'queuetype'  => $queueType,
315
            'nzbgeturl' => $nzbgetURL,
316
            'nzbgetusername' => $nzbgetUsername,
317
            'nzbgetpassword' => $nzbgetPassword,
318
            'saburl' => $saburl,
319
            'sabapikey' => $sabapikey,
320
            'sabapikeytype' => $sabapikeytype,
321
            'sabpriority' => $sabpriority,
322
            'nzbvortex_server_url' => $nzbvortexServerUrl,
323
            'nzbvortex_api_key' => $nzbvortexApiKey,
324
            'cp_url' => $cp_url,
325
            'cp_api' => $cp_api,
326
            'rate_limit' => $rateLimit ? $rateLimit['rate_limit'] : 60,
327
        ];
328
329
        if (! empty($email)) {
330
            $email = trim($email);
331
            $sql += ['email' => $email];
332
        }
333
334
        $user = self::find($id);
335
        $user->update($sql);
336
        $user->syncRoles([$rateLimit['name']]);
337
338
        return self::SUCCESS;
339
    }
340
341
    /**
342
     * @param string $userName
343
     * @return \Illuminate\Database\Eloquent\Model|null|static
344
     */
345
    public static function getByUsername(string $userName)
346
    {
347
        return self::whereUsername($userName)->first();
348
    }
349
350
    /**
351
     * @param string $email
352
     *
353
     * @return \Illuminate\Database\Eloquent\Model|static
354
     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
355
     */
356
    public static function getByEmail(string $email)
357
    {
358
        return self::whereEmail($email)->first();
359
    }
360
361
    /**
362
     * @param int $uid
363
     * @param int $role
364
     *
365
     * @return bool
366
     */
367
    public static function updateUserRole(int $uid, int $role)
368
    {
369
        return self::find($uid)->update(['roles_id' => $role]);
370
    }
371
372
    /**
373
     * @param int $uid
374
     * @param $date
375
     * @param int $addYear
376
     */
377
    public static function updateUserRoleChangeDate($uid, $date = '', $addYear = 0): void
378
    {
379
        $user = self::find($uid);
380
        $currRoleExp = $user::select(['rolechangedate'])->first();
381
        if (! empty($date)) {
382
            $user->update(['rolechangedate' => $date]);
383
        }
384
        if (empty($date) && ! empty($addYear)) {
385
            $user->update(['rolechangedate' => Carbon::createFromDate($currRoleExp['rolechangedate'])->addYears($addYear)]);
386
        }
387
    }
388
389
    /**
390
     * @param int|null $period
391
     */
392
    public static function updateExpiredRoles(int $period = null): void
393
    {
394
        $now = now();
395
        $endDate = $now->addDays($period);
396
        $query = self::query()->whereDate('rolechangedate', '>', now())->whereDate('rolechangedate', '<=', $endDate);
397
        if ($period === null) {
398
            $endDate = $now;
399
            $query = self::query()->whereDate('rolechangedate', '>', $endDate);
400
        }
401
402
        $usersCheck = $query->get();
403
404
        foreach ($usersCheck as $userCheck) {
405
            $emailCheckQuery = RoleExpirationEmail::query()->where('users_id', $userCheck->id)->get();
406
            if ($period === null) {
407
                $userCheck->update(['roles_id' => self::ROLE_USER, 'rolechangedate' => null]);
408
                $userCheck->syncRoles('User');
409
                SendAccountExpiredEmail::dispatch($userCheck);
410
            } else {
411
                if ($emailCheckQuery->isNotEmpty()) {
412
                    $booleans = [
413
                        'day' => $period === 1 && $emailCheckQuery->day === false,
414
                        'week' => $period === 7 && $emailCheckQuery->week === false,
415
                        'month' => $period === 30 && $emailCheckQuery->month === false,
416
                    ];
417
                    RoleExpirationEmail::query()->where('users_id', '=', $userCheck->id)->update($booleans);
418
                } else {
419
                    $booleans = [
420
                        'day' => $period === 1,
421
                        'week' => $period === 7,
422
                        'month' => $period === 30,
423
                    ];
424
                    $userId = [
425
                        'users_id' => $userCheck->id,
426
                    ];
427
                    $booleans += $userId;
428
429
                    RoleExpirationEmail::create($booleans);
430
                }
431
432
                SendAccountWillExpireEmail::dispatch($userCheck, $period);
433
            }
434
        }
435
    }
436
437
    /**
438
     * @param        $start
439
     * @param        $offset
440
     * @param        $orderBy
441
     * @param string $userName
442
     * @param string $email
443
     * @param string $host
444
     * @param string $role
445
     * @param bool   $apiRequests
446
     *
447
     * @return \Illuminate\Database\Eloquent\Collection
448
     * @throws \Throwable
449
     */
450
    public static function getRange($start, $offset, $orderBy, $userName = '', $email = '', $host = '', $role = '', $apiRequests = false)
451
    {
452
        if ($apiRequests) {
453
            UserRequest::clearApiRequests(false);
454
            $query = "
455
				SELECT users.*, roles.name AS rolename, COUNT(user_requests.id) AS apirequests
456
				FROM users
457
				INNER JOIN roles ON roles.id = users.roles_id
458
				LEFT JOIN user_requests ON user_requests.users_id = users.id
459
				WHERE users.id != 0 %s %s %s %s
460
				AND email != '[email protected]'
461
				GROUP BY users.id
462
				ORDER BY %s %s %s ";
463
        } else {
464
            $query = '
465
				SELECT users.*, roles.name AS rolename
466
				FROM users
467
				INNER JOIN roles ON roles.id = users.roles_id
468
				WHERE 1=1 %s %s %s %s
469
				ORDER BY %s %s %s';
470
        }
471
        $order = self::getBrowseOrder($orderBy);
472
473
        return self::fromQuery(
474
            sprintf(
475
                $query,
476
                ! empty($userName) ? 'AND users.username '.'LIKE '.escapeString('%'.$userName.'%') : '',
477
                ! empty($email) ? 'AND users.email '.'LIKE '.escapeString('%'.$email.'%') : '',
478
                ! empty($host) ? 'AND users.host '.'LIKE '.escapeString('%'.$host.'%') : '',
479
                (! empty($role) ? ('AND users.roles_id = '.$role) : ''),
480
                $order[0],
481
                $order[1],
482
                ($start === false ? '' : ('LIMIT '.$offset.' OFFSET '.$start))
483
            )
484
        );
485
    }
486
487
    /**
488
     * Get sort types for sorting users on the web page user list.
489
     *
490
     * @param $orderBy
491
     *
492
     * @return string[]
493
     */
494
    public static function getBrowseOrder($orderBy): array
495
    {
496
        $order = (empty($orderBy) ? 'username_desc' : $orderBy);
497
        $orderArr = explode('_', $order);
498
        switch ($orderArr[0]) {
499
            case 'username':
500
                $orderField = 'username';
501
                break;
502
            case 'email':
503
                $orderField = 'email';
504
                break;
505
            case 'host':
506
                $orderField = 'host';
507
                break;
508
            case 'createdat':
509
                $orderField = 'created_at';
510
                break;
511
            case 'lastlogin':
512
                $orderField = 'lastlogin';
513
                break;
514
            case 'apiaccess':
515
                $orderField = 'apiaccess';
516
                break;
517
            case 'grabs':
518
                $orderField = 'grabs';
519
                break;
520
            case 'role':
521
                $orderField = 'rolename';
522
                break;
523
            case 'rolechangedate':
524
                $orderField = 'rolechangedate';
525
                break;
526
            case 'verification':
527
                $orderField = 'verified';
528
                break;
529
            default:
530
                $orderField = 'username';
531
                break;
532
        }
533
        $orderSort = (isset($orderArr[1]) && preg_match('/^asc|desc$/i', $orderArr[1])) ? $orderArr[1] : 'desc';
534
535
        return [$orderField, $orderSort];
536
    }
537
538
    /**
539
     * Verify a password against a hash.
540
     *
541
     * Automatically update the hash if it needs to be.
542
     *
543
     * @param string $password Password to check against hash.
544
     * @param string|bool $hash     Hash to check against password.
545
     * @param int    $userID   ID of the user.
546
     *
547
     * @return bool
548
     */
549
    public static function checkPassword($password, $hash, $userID = -1): bool
550
    {
551
        if (Hash::check($password, $hash) === false) {
552
            return false;
553
        }
554
555
        // Update the hash if it needs to be.
556
        if (is_numeric($userID) && $userID > 0 && Hash::needsRehash($hash)) {
557
            $hash = self::hashPassword($password);
558
559
            if ($hash !== false) {
0 ignored issues
show
introduced by
The condition $hash !== false is always true.
Loading history...
560
                self::find($userID)->update(['password' => $hash]);
561
            }
562
        }
563
564
        return true;
565
    }
566
567
    /**
568
     * @param $uid
569
     *
570
     * @return int
571
     */
572
    public static function updateRssKey($uid): int
573
    {
574
        self::find($uid)->update(['api_token' => md5(Password::getRepository()->createNewToken())]);
575
576
        return self::SUCCESS;
577
    }
578
579
    /**
580
     * @param $id
581
     * @param $guid
582
     *
583
     * @return int
584
     */
585
    public static function updatePassResetGuid($id, $guid): int
586
    {
587
        self::find($id)->update(['resetguid' => $guid]);
588
589
        return self::SUCCESS;
590
    }
591
592
    /**
593
     * @param int    $id
594
     * @param string $password
595
     *
596
     * @return int
597
     */
598
    public static function updatePassword(int $id, string $password): int
599
    {
600
        self::find($id)->update(['password' => self::hashPassword($password), 'userseed' => md5(Str::uuid()->toString())]);
601
602
        return self::SUCCESS;
603
    }
604
605
    /**
606
     * @param $password
607
     * @return mixed
608
     */
609
    public static function hashPassword($password)
610
    {
611
        return Hash::make($password);
612
    }
613
614
    /**
615
     * @param $guid
616
     *
617
     * @return \Illuminate\Database\Eloquent\Model|static
618
     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
619
     */
620
    public static function getByPassResetGuid(string $guid)
621
    {
622
        return self::whereResetguid($guid)->first();
623
    }
624
625
    /**
626
     * @param     $id
627
     * @param int $num
628
     */
629
    public static function incrementGrabs(int $id, $num = 1): void
630
    {
631
        self::find($id)->increment('grabs', $num);
632
    }
633
634
    /**
635
     * Check if the user is in the database, and if their API key is good, return user data if so.
636
     *
637
     *
638
     * @param $userID
639
     * @param $rssToken
640
     *
641
     * @return bool|\Illuminate\Database\Eloquent\Model|null|static
642
     */
643
    public static function getByIdAndRssToken($userID, $rssToken)
644
    {
645
        $user = self::query()->where(['id' => $userID, 'api_token' => $rssToken])->first();
646
        if ($user === null) {
647
            return false;
648
        }
649
650
        return $user;
651
    }
652
653
    /**
654
     * @param string $rssToken
655
     * @return \Illuminate\Database\Eloquent\Model|null|static
656
     */
657
    public static function getByRssToken(string $rssToken)
658
    {
659
        return self::whereApiToken($rssToken)->first();
660
    }
661
662
    /**
663
     * @param $url
664
     *
665
     * @return bool
666
     */
667
    public static function isValidUrl($url): bool
668
    {
669
        return (! preg_match('/^(http|https|ftp):\/\/([A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)+):?(\d+)?\/?/i', $url)) ? false : true;
670
    }
671
672
    /**
673
     * Generate a random username.
674
     *
675
     *
676
     * @return string
677
     */
678
    public static function generateUsername(): string
679
    {
680
        return Str::random();
681
    }
682
683
    /**
684
     * @param int $length
685
     *
686
     * @return string
687
     * @throws \Exception
688
     */
689
    public static function generatePassword($length = 15): string
690
    {
691
        return \Token::random($length, true);
0 ignored issues
show
Bug introduced by
The type Token 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...
692
    }
693
694
    /**
695
     * Register a new user.
696
     *
697
     * @param        $userName
698
     * @param        $password
699
     * @param        $email
700
     * @param        $host
701
     * @param        $notes
702
     * @param int $invites
703
     * @param string $inviteCode
704
     * @param bool $forceInviteMode
705
     *
706
     * @param int $role
707
     * @param bool $validate
708
     * @return bool|int|string
709
     * @throws \Exception
710
     */
711
    public static function signUp($userName, $password, $email, $host, $notes, $invites = Invitation::DEFAULT_INVITES, $inviteCode = '', $forceInviteMode = false, $role = self::ROLE_USER, $validate = true)
712
    {
713
        $user = [
714
            'username' => trim($userName),
715
            'password' => trim($password),
716
            'email' => trim($email),
717
        ];
718
719
        if ($validate) {
720
            $validator = Validator::make($user, [
721
                'username' => ['required', 'string', 'min:5', 'max:255', 'unique:users'],
722
                'email' => ['required', 'string', 'email', 'max:255', 'unique:users', 'indisposable'],
723
                'password' => ['required', 'string', 'min:8', 'confirmed', 'regex:/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/'],
724
            ]);
725
726
            if ($validator->fails()) {
727
                $error = implode('', Arr::collapse($validator->errors()->toArray()));
728
729
                return $error;
730
            }
731
        }
732
733
        // Make sure this is the last check, as if a further validation check failed, the invite would still have been used up.
734
        $invitedBy = 0;
735
        if (! $forceInviteMode && (int) Settings::settingValue('..registerstatus') === Settings::REGISTER_STATUS_INVITE) {
736
            if ($inviteCode === '') {
737
                return self::ERR_SIGNUP_BADINVITECODE;
738
            }
739
740
            $invitedBy = self::checkAndUseInvite($inviteCode);
741
            if ($invitedBy < 0) {
742
                return self::ERR_SIGNUP_BADINVITECODE;
743
            }
744
        }
745
746
        return self::add($user['username'], $user['password'], $user['email'], $role, $notes, $host, $invites, $invitedBy);
747
    }
748
749
    /**
750
     * If a invite is used, decrement the person who invited's invite count.
751
     *
752
     * @param string $inviteCode
753
     *
754
     * @return int
755
     */
756
    public static function checkAndUseInvite(string $inviteCode): int
757
    {
758
        $invite = Invitation::getInvite($inviteCode);
759
        if (! $invite) {
760
            return -1;
761
        }
762
763
        self::query()->where('id', $invite['users_id'])->decrement('invites');
764
        Invitation::deleteInvite($inviteCode);
765
766
        return $invite['users_id'];
767
    }
768
769
    /**
770
     * Add a new user.
771
     *
772
     * @param string    $userName
773
     * @param  string   $password
774
     * @param  string   $email
775
     * @param int    $role
776
     * @param string    $notes
777
     * @param string    $host
778
     * @param int $invites
779
     * @param int $invitedBy
780
     *
781
     * @return bool|int
782
     * @throws \Exception
783
     */
784
    public static function add($userName, $password, $email, $role, $notes = '', $host = '', $invites = Invitation::DEFAULT_INVITES, $invitedBy = 0)
785
    {
786
        $password = self::hashPassword($password);
787
        if (! $password) {
788
            return false;
789
        }
790
791
        $storeips = (int) Settings::settingValue('..storeuserips') === 1 ? $host : '';
792
793
        $user = self::create(
794
            [
795
                'username' => $userName,
796
                'password' => $password,
797
                'email' => $email,
798
                'host' => $storeips,
799
                'roles_id' => $role,
800
                'invites' => $invites,
801
                'invitedby' => (int) $invitedBy === 0 ? null : $invitedBy,
802
                'notes' => $notes,
803
            ]
804
        );
805
806
        return $user->id;
807
    }
808
809
    /**
810
     * Get the list of categories the user has excluded.
811
     *
812
     * @param int $userID ID of the user.
813
     *
814
     * @return array
815
     * @throws \Exception
816
     */
817
    public static function getCategoryExclusionById($userID): array
818
    {
819
        $ret = [];
820
821
        $user = self::find($userID);
822
823
        $userAllowed = $user->getDirectPermissions()->pluck('name')->toArray();
824
        $roleAllowed = $user->getAllPermissions()->pluck('name')->toArray();
825
826
        $allowed = array_intersect($roleAllowed, $userAllowed);
827
828
        $cats = ['view console', 'view movies', 'view audio', 'view tv', 'view pc', 'view adult', 'view books', 'view other'];
829
830
        if (! empty($allowed)) {
831
            foreach ($cats as $cat) {
832
                if (! \in_array($cat, $allowed, false)) {
833
                    switch ($cat) {
834
                        case 'view console':
835
                            $ret[] = 1000;
836
                            continue 2;
837
                        case 'view movies':
838
                            $ret[] = 2000;
839
                            continue 2;
840
                        case 'view audio':
841
                            $ret[] = 3000;
842
                            continue 2;
843
                        case 'view pc':
844
                            $ret[] = 4000;
845
                            continue 2;
846
                        case 'view tv':
847
                            $ret[] = 5000;
848
                            continue 2;
849
                        case 'view adult':
850
                            $ret[] = 6000;
851
                            continue 2;
852
                        case 'view books':
853
                            $ret[] = 7000;
854
                            continue 2;
855
                        case 'view other':
856
                            $ret[] = 1;
857
858
                    }
859
                }
860
            }
861
        }
862
863
        $exclusion = Category::query()->whereIn('root_categories_id', $ret)->pluck('id')->toArray();
864
865
        return $exclusion;
866
    }
867
868
    /**
869
     * @param \Illuminate\Http\Request $request
870
     * @return array
871
     * @throws \Exception
872
     */
873
    public static function getCategoryExclusionForApi(Request $request): array
874
    {
875
        $apiToken = $request->has('api_token') ? $request->input('api_token') : $request->input('apikey');
876
        $user = self::getByRssToken($apiToken);
877
878
        return self::getCategoryExclusionById($user->id);
879
    }
880
881
    /**
882
     * @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection|static[]
883
     */
884
    public static function getTopGrabbers()
885
    {
886
        return self::query()->selectRaw('id, username, SUM(grabs) as grabs')->groupBy('id', 'username')->having('grabs', '>', 0)->orderBy('grabs', 'desc')->limit(10)->get();
887
    }
888
889
    /**
890
     * @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection|static[]
891
     */
892
    public static function getUsersByMonth()
893
    {
894
        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'])->orderBy('created_at', 'desc')->get();
895
    }
896
897
    /**
898
     * @param $serverUrl
899
     * @param $uid
900
     * @param $emailTo
901
     *
902
     * @return string
903
     * @throws \Exception
904
     */
905
    public static function sendInvite($serverUrl, $uid, $emailTo): string
906
    {
907
        $token = \Token::randomString(40);
908
        $url = $serverUrl.'register?invitecode='.$token;
909
910
        Invitation::addInvite($uid, $token);
911
        SendInviteEmail::dispatch($emailTo, $uid, $url);
912
913
        return $url;
914
    }
915
916
    /**
917
     * Deletes old rows FROM the user_requests and user_downloads tables.
918
     * if site->userdownloadpurgedays SET to 0 then all release history is removed but
919
     * the download/request rows must remain for at least one day to allow the role based
920
     * limits to apply.
921
     *
922
     * @param int $days
923
     */
924
    public static function pruneRequestHistory($days = 0): void
925
    {
926
        if ($days === 0) {
927
            $days = 1;
928
            UserDownload::query()->update(['releases_id' => null]);
929
        }
930
931
        UserRequest::query()->where('timestamp', '<', now()->subDays($days))->delete();
932
        UserDownload::query()->where('timestamp', '<', now()->subDays($days))->delete();
933
    }
934
935
    /**
936
     * Deletes users that have not verified their accounts for 3 or more days.
937
     */
938
    public static function deleteUnVerified(): void
939
    {
940
        static::whereVerified(0)->where('created_at', '<', now()->subDays(3))->delete();
941
    }
942
}
943