ProfileController   F
last analyzed

Complexity

Total Complexity 77

Size/Duplication

Total Lines 336
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 77
eloc 196
dl 0
loc 336
rs 2.24
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
F show() 0 69 18
A updateTheme() 0 20 3
A destroy() 0 16 5
F edit() 0 209 51

How to fix   Complexity   

Complex Class

Complex classes like ProfileController 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 ProfileController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace App\Http\Controllers;
4
5
use App\Jobs\SendAccountDeletedEmail;
6
use App\Models\ReleaseComment;
7
use App\Models\User;
8
use App\Models\UserDownload;
9
use App\Models\UserRequest;
10
use App\Rules\ValidEmailDomain;
11
use Illuminate\Contracts\View\Factory;
12
use Illuminate\Contracts\View\View;
13
use Illuminate\Foundation\Application;
14
use Illuminate\Http\RedirectResponse;
15
use Illuminate\Http\Request;
16
use Illuminate\Support\Arr;
17
use Illuminate\Support\Facades\Auth;
18
use Illuminate\Support\Facades\Validator;
19
use Jrean\UserVerification\Facades\UserVerification;
20
21
class ProfileController extends BasePageController
22
{
23
    /**
24
     * @throws \Throwable
25
     */
26
    public function show(Request $request)
27
    {
28
29
        $userID = $this->userdata->id;
30
        $privileged = $this->userdata->hasRole('Admin') || $this->userdata->hasRole('Moderator');
31
        $privateProfiles = config('nntmux_settings.private_profiles');
32
        $publicView = false;
33
34
        if ($privileged || ! $privateProfiles) {
35
            $altID = ($request->has('id') && (int) $request->input('id') >= 0) ? (int) $request->input('id') : false;
36
            $altUsername = ($request->has('name') && $request->input('name') !== '') ? $request->input('name') : false;
37
38
            // If both 'id' and 'name' are specified, 'id' should take precedence.
39
            if ($altID === false && $altUsername !== false) {
40
                $user = User::getByUsername($altUsername);
41
                if ($user) {
42
                    $this->userdata = $user;
43
                    $altID = $user['id'];
44
                    $userID = $altID;
45
                }
46
            } elseif ($altID !== false) {
47
                $user = User::find($altID);
48
                if ($user) {
49
                    $this->userdata = $user;
50
                    $userID = $altID;
51
                    $publicView = true;
52
                }
53
            }
54
        }
55
56
        if ($this->userdata === null) {
57
            return $this->show404('No such user!');
58
        }
59
60
        // Check if the user selected a theme.
61
        if (! isset($this->userdata->style) || $this->userdata->style === 'None') {
62
            $this->userdata->style = 'Using the admin selected theme.';
63
        }
64
65
        // Get 24-hour hourly data for downloads and API requests
66
        $downloadsHourly = UserDownload::getHourlyDownloads($userID);
67
        $apiRequestsHourly = UserRequest::getHourlyApiRequests($userID);
68
69
        // Get role limits
70
        $userRole = $this->userdata->roles->first();
71
        $downloadLimit = $userRole->downloadrequests ?? 0;
72
        $apiLimit = $userRole->apirequests ?? 0;
73
74
        $this->viewData = array_merge($this->viewData, [
75
            'downloadlist' => UserDownload::getDownloadRequestsForUser($userID),
76
            'apirequests' => UserRequest::getApiRequests($userID),
77
            'grabstoday' => UserDownload::getDownloadRequests($userID),
78
            'userinvitedby' => ($this->userdata->invitedby && $this->userdata->invitedby !== '') ? User::find($this->userdata->invitedby) : null,
79
            'user' => $this->userdata,
80
            'privateprofiles' => $privateProfiles,
81
            'publicview' => $publicView,
82
            'privileged' => $privileged,
83
            'isadmin' => $this->userdata->hasRole('Admin'),
84
            'commentslist' => ReleaseComment::getCommentsForUserRange($userID),
85
            'downloadsHourly' => $downloadsHourly,
86
            'apiRequestsHourly' => $apiRequestsHourly,
87
            'downloadLimit' => $downloadLimit,
88
            'apiLimit' => $apiLimit,
89
            'meta_title' => 'View User Profile',
90
            'meta_keywords' => 'view,profile,user,details',
91
            'meta_description' => 'View User Profile for '.$this->userdata->username,
92
        ]);
93
94
        return view('profile.index', $this->viewData);
95
    }
96
97
    /**
98
     * @return Factory|\Illuminate\View\View|View
99
     *
100
     * @throws \Exception
101
     */
102
    public function edit(Request $request)
103
    {
104
105
        $action = $request->input('action') ?? 'view';
106
107
        $userid = $this->userdata->id;
108
        if (! $this->userdata) {
109
            $this->show404('No such user!');
110
        }
111
112
        $errorStr = '';
113
        $success_2fa = $request->session()->get('success');
114
        $error_2fa = $request->session()->get('error');
115
116
        // Generate 2FA QR code URL if 2FA is set up but not enabled
117
        $google2fa_url = '';
118
        if ($this->userdata->passwordSecurity()->exists() && ! $this->userdata->passwordSecurity->google2fa_enable) {
119
            $google2fa_url = \Google2FA::getQRCodeInline(
0 ignored issues
show
Bug introduced by
The type Google2FA 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...
120
                config('app.name'),
121
                $this->userdata->email,
122
                $this->userdata->passwordSecurity->google2fa_secret
123
            );
124
        }
125
126
        switch ($action) {
127
            case 'newapikey':
128
                User::updateRssKey($userid);
129
130
                return redirect()->to('profile');
0 ignored issues
show
Bug Best Practice introduced by
The expression return redirect()->to('profile') returns the type Illuminate\Http\RedirectResponse which is incompatible with the documented return type Illuminate\Contracts\Vie...ew|Illuminate\View\View.
Loading history...
131
            case 'clearcookies':
132
                return redirect()->to('profileedit');
0 ignored issues
show
Bug Best Practice introduced by
The expression return redirect()->to('profileedit') returns the type Illuminate\Http\RedirectResponse which is incompatible with the documented return type Illuminate\Contracts\Vie...ew|Illuminate\View\View.
Loading history...
133
            case 'submit':
134
                $validator = Validator::make($request->all(), [
135
                    'email' => ['nullable', 'string', 'email', 'max:255', 'unique:users,email,'.$userid, new ValidEmailDomain()],
136
                    'password' => ['nullable', 'string', 'min:8', 'confirmed', 'regex:/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/'],
137
                ]);
138
139
                if ($validator->fails()) {
140
                    $errorStr = implode('', Arr::collapse($validator->errors()->toArray()));
141
                } else {
142
                    User::updateUser(
143
                        $userid,
144
                        $this->userdata->username,
145
                        $request->input('email'),
146
                        $this->userdata->grabs,
147
                        $this->userdata->roles_id,
148
                        $this->userdata->notes,
149
                        $this->userdata->invites,
150
                        $request->has('movieview') ? 1 : 0,
151
                        $request->has('musicview') ? 1 : 0,
152
                        $request->has('gameview') ? 1 : 0,
153
                        $request->has('xxxview') ? 1 : 0,
154
                        $request->has('consoleview') ? 1 : 0,
155
                        $request->has('bookview') ? 1 : 0,
156
                        'None',
157
                    );
158
159
                    // Update theme preference
160
                    if ($request->has('theme_preference')) {
161
                        $themeValue = $request->input('theme_preference');
162
                        if (in_array($themeValue, ['light', 'dark', 'system'])) {
163
                            User::where('id', $userid)->update(['theme_preference' => $themeValue]);
164
                        }
165
                    }
166
167
                    // Update timezone preference
168
                    if ($request->has('timezone')) {
169
                        $timezoneValue = $request->input('timezone');
170
                        $validTimezones = array_merge(['UTC'], ...array_values(getAvailableTimezones()));
171
                        if (in_array($timezoneValue, $validTimezones)) {
172
                            User::where('id', $userid)->update(['timezone' => $timezoneValue]);
173
                        }
174
                    }
175
176
                    // Handle Console permission
177
                    if ($request->has('viewconsole')) {
178
                        if (! $this->userdata->hasDirectPermission('view console')) {
179
                            $this->userdata->givePermissionTo('view console');
180
                        }
181
                    } else {
182
                        if ($this->userdata->hasPermissionTo('view console')) {
183
                            $this->userdata->revokePermissionTo('view console');
184
                        }
185
                    }
186
187
                    // Handle Movies permission
188
                    if ($request->has('viewmovies')) {
189
                        if (! $this->userdata->hasDirectPermission('view movies')) {
190
                            $this->userdata->givePermissionTo('view movies');
191
                        }
192
                    } else {
193
                        if ($this->userdata->hasPermissionTo('view movies')) {
194
                            $this->userdata->revokePermissionTo('view movies');
195
                        }
196
                    }
197
198
                    // Handle Audio permission
199
                    if ($request->has('viewaudio')) {
200
                        if (! $this->userdata->hasDirectPermission('view audio')) {
201
                            $this->userdata->givePermissionTo('view audio');
202
                        }
203
                    } else {
204
                        if ($this->userdata->hasPermissionTo('view audio')) {
205
                            $this->userdata->revokePermissionTo('view audio');
206
                        }
207
                    }
208
209
                    // Handle PC/Games permission
210
                    if ($request->has('viewpc')) {
211
                        if (! $this->userdata->hasDirectPermission('view pc')) {
212
                            $this->userdata->givePermissionTo('view pc');
213
                        }
214
                    } else {
215
                        if ($this->userdata->hasPermissionTo('view pc')) {
216
                            $this->userdata->revokePermissionTo('view pc');
217
                        }
218
                    }
219
220
                    // Handle TV permission
221
                    if ($request->has('viewtv')) {
222
                        if (! $this->userdata->hasDirectPermission('view tv')) {
223
                            $this->userdata->givePermissionTo('view tv');
224
                        }
225
                    } else {
226
                        if ($this->userdata->hasPermissionTo('view tv')) {
227
                            $this->userdata->revokePermissionTo('view tv');
228
                        }
229
                    }
230
231
                    // Handle Adult permission
232
                    if ($request->has('viewadult')) {
233
                        if (! $this->userdata->hasDirectPermission('view adult')) {
234
                            $this->userdata->givePermissionTo('view adult');
235
                        }
236
                    } else {
237
                        if ($this->userdata->hasPermissionTo('view adult')) {
238
                            $this->userdata->revokePermissionTo('view adult');
239
                        }
240
                    }
241
242
                    // Handle Books permission
243
                    if ($request->has('viewbooks')) {
244
                        if (! $this->userdata->hasDirectPermission('view books')) {
245
                            $this->userdata->givePermissionTo('view books');
246
                        }
247
                    } else {
248
                        if ($this->userdata->hasPermissionTo('view books')) {
249
                            $this->userdata->revokePermissionTo('view books');
250
                        }
251
                    }
252
253
                    // Handle Other permission
254
                    if ($request->has('viewother')) {
255
                        if (! $this->userdata->hasDirectPermission('view other')) {
256
                            $this->userdata->givePermissionTo('view other');
257
                        }
258
                    } else {
259
                        if ($this->userdata->hasPermissionTo('view other')) {
260
                            $this->userdata->revokePermissionTo('view other');
261
                        }
262
                    }
263
264
                    if ($request->has('password') && ! empty($request->input('password'))) {
265
                        User::updatePassword($userid, $request->input('password'));
266
                    }
267
268
                    if (! $this->userdata->hasRole('Admin')) {
269
                        if (! empty($request->input('email')) && $this->userdata->email !== $request->input('email')) {
270
                            $this->userdata->email = $request->input('email');
271
272
                            $verify_user = $this->userdata;
273
274
                            UserVerification::generate($verify_user);
275
276
                            UserVerification::send($verify_user, 'User email verification required');
277
278
                            Auth::logout();
279
280
                            return redirect()->to('login')->with('info', 'You will be able to login after you verify your new email address');
0 ignored issues
show
Bug Best Practice introduced by
The expression return redirect()->to('l...our new email address') returns the type Illuminate\Http\RedirectResponse which is incompatible with the documented return type Illuminate\Contracts\Vie...ew|Illuminate\View\View.
Loading history...
281
                        }
282
                    }
283
284
                    return redirect()->to('profile')->with('success', 'Profile changes saved');
0 ignored issues
show
Bug Best Practice introduced by
The expression return redirect()->to('p...Profile changes saved') returns the type Illuminate\Http\RedirectResponse which is incompatible with the documented return type Illuminate\Contracts\Vie...ew|Illuminate\View\View.
Loading history...
285
                }
286
                break;
287
288
            case 'view':
289
            default:
290
                break;
291
        }
292
293
        $this->viewData = array_merge($this->viewData, [
294
            'error' => $errorStr,
295
            'user' => $this->userdata,
296
            'userexccat' => User::getCategoryExclusionById($userid),
297
            'success_2fa' => $success_2fa,
298
            'error_2fa' => $error_2fa,
299
            'google2fa_url' => $google2fa_url,
300
            'yesno_ids' => [1, 0],
301
            'yesno_names' => ['Yes', 'No'],
302
            'publicview' => false,
303
            'privileged' => $this->userdata->hasRole('Admin') || $this->userdata->hasRole('Moderator'),
304
            'userinvitedby' => ($this->userdata->invitedby && $this->userdata->invitedby !== '') ? User::find($this->userdata->invitedby) : null,
305
            'meta_title' => 'Edit User Profile',
306
            'meta_keywords' => 'edit,profile,user,details',
307
            'meta_description' => 'Edit User Profile for '.$this->userdata->username,
308
        ]);
309
310
        return view('profile.edit', $this->viewData);
311
    }
312
313
    /**
314
     * @throws \Exception
315
     */
316
    public function destroy(Request $request): Application|View|Factory|RedirectResponse|\Illuminate\Contracts\Foundation\Application
317
    {
318
        $userId = $request->input('id');
319
320
        if ($userId !== null && (int) $userId === $this->userdata->id && ! $this->userdata->hasRole('Admin')) {
321
            $user = User::find($userId);
322
            SendAccountDeletedEmail::dispatch($user);
323
            Auth::logout();
324
            $user->delete();
325
        }
326
327
        if ($this->userdata->hasRole('Admin')) {
328
            return redirect()->to('profile');
329
        }
330
331
        return view('errors.503')->with('warning', 'Dont try to delete another user account!');
332
    }
333
334
    /**
335
     * Update user's dark mode preference
336
     */
337
    public function updateTheme(Request $request)
338
    {
339
        $user = Auth::user();
340
341
        if (! $user) {
342
            return response()->json(['success' => false, 'message' => 'User not authenticated'], 401);
343
        }
344
345
        $validator = Validator::make($request->all(), [
346
            'theme_preference' => ['required', 'in:light,dark,system'],
347
        ]);
348
349
        if ($validator->fails()) {
350
            return response()->json(['success' => false, 'message' => 'Invalid input'], 400);
351
        }
352
353
        $user->theme_preference = $request->input('theme_preference');
0 ignored issues
show
Bug introduced by
Accessing theme_preference on the interface Illuminate\Contracts\Auth\Authenticatable suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
354
        $user->save();
355
356
        return response()->json(['success' => true, 'theme_preference' => $user->theme_preference]);
357
    }
358
}
359