Passed
Push — master ( 992504...dab7e9 )
by Darko
12:33
created

AdminUserController   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 332
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 49
eloc 209
dl 0
loc 332
rs 8.48
c 1
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A verify() 0 10 2
F index() 0 66 13
F edit() 0 209 29
A destroy() 0 17 3
A resendVerification() 0 12 2

How to fix   Complexity   

Complex Class

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

1
<?php
2
3
namespace App\Http\Controllers\Admin;
4
5
use App\Http\Controllers\BasePageController;
6
use App\Models\Invitation;
7
use App\Models\User;
8
use App\Models\UserDownload;
9
use App\Models\UserRequest;
10
use Illuminate\Http\RedirectResponse;
11
use Illuminate\Http\Request;
12
use Jrean\UserVerification\Facades\UserVerification;
13
use Spatie\Permission\Models\Role;
14
use Stevebauman\Location\Facades\Location;
15
16
class AdminUserController extends BasePageController
17
{
18
    /**
19
     * @throws \Throwable
20
     */
21
    public function index(Request $request)
22
    {
23
        $this->setAdminPrefs();
24
25
        $meta_title = $title = 'User List';
26
27
        $roles = [];
28
        $userRoles = Role::cursor()->remember();
29
        foreach ($userRoles as $userRole) {
30
            $roles[$userRole->id] = $userRole->name;
31
        }
32
33
        $ordering = getUserBrowseOrdering();
34
        $orderBy = $request->has('ob') && \in_array($request->input('ob'), $ordering, false) ? $request->input('ob') : '';
35
        $page = $request->has('page') && is_numeric($request->input('page')) ? $request->input('page') : 1;
36
        $offset = ($page - 1) * config('nntmux.items_per_page');
37
38
        $variables = [
39
            'username' => $request->has('username') ? $request->input('username') : '',
40
            'email' => $request->has('email') ? $request->input('email') : '',
41
            'host' => $request->has('host') ? $request->input('host') : '',
42
            'role' => $request->has('role') ? $request->input('role') : '',
43
            'created_from' => $request->has('created_from') ? $request->input('created_from') : '',
44
            'created_to' => $request->has('created_to') ? $request->input('created_to') : '',
45
        ];
46
47
        $result = User::getRange(
48
            $offset,
49
            config('nntmux.items_per_page'),
50
            $orderBy,
51
            $variables['username'],
52
            $variables['email'],
53
            $variables['host'],
54
            $variables['role'],
55
            true,
56
            $variables['created_from'],
57
            $variables['created_to']
58
        );
59
60
        $results = $this->paginate($result ?? [], User::getCount($variables['role'], $variables['username'], $variables['host'], $variables['email'], $variables['created_from'], $variables['created_to']) ?? 0, config('nntmux.items_per_page'), $page, $request->url(), $request->query());
61
62
        // Note: API request counts are already included via the getRange query when $apiRequests = true
63
        // Country lookups and additional counts removed to improve performance on large datasets
64
        // These can be added back via individual user profile pages or AJAX calls if needed
65
66
        // Build order by URLs
67
        $orderByUrls = [];
68
        foreach ($ordering as $orderType) {
69
            $orderByUrls['orderby'.$orderType] = url('admin/user-list?ob='.$orderType);
70
        }
71
72
        $this->viewData = array_merge($this->viewData, [
73
            'username' => $variables['username'],
74
            'email' => $variables['email'],
75
            'host' => $variables['host'],
76
            'role' => $variables['role'],
77
            'created_from' => $variables['created_from'],
78
            'created_to' => $variables['created_to'],
79
            'role_ids' => array_keys($roles),
80
            'role_names' => $roles,
81
            'userlist' => $results,
82
            'title' => $title,
83
            'meta_title' => $meta_title,
84
        ], $orderByUrls);
85
86
        return view('admin.users.index', $this->viewData);
87
    }
88
89
    /**
90
     * @return RedirectResponse|\Illuminate\View\View
91
     *
92
     * @throws \Exception|\Throwable
93
     */
94
    public function edit(Request $request)
95
    {
96
        $this->setAdminPrefs();
97
98
        $user = [
99
            'id' => '',
100
            'username' => '',
101
            'email' => '',
102
            'password' => '',
103
            'role' => User::ROLE_USER,
104
            'notes' => '',
105
            'rate_limit' => 60,
106
        ];
107
108
        $meta_title = $title = 'View User';
109
110
        // set the current action
111
        $action = $request->input('action') ?? 'view';
112
113
        // get the user roles
114
        $userRoles = Role::cursor()->remember();
115
        $roles = [];
116
        $defaultRole = 'User';
117
        $defaultInvites = Invitation::DEFAULT_INVITES;
118
        foreach ($userRoles as $r) {
119
            $roles[$r->id] = $r->name;
120
            if ($r->isdefault === 1) {
121
                $defaultRole = $r->id;
122
                $defaultInvites = $r->defaultinvites;
123
            }
124
        }
125
126
        $error = null;
127
128
        switch ($action) {
129
            case 'add':
130
                $user += [
131
                    'role' => $defaultRole,
132
                    'notes' => '',
133
                    'invites' => $defaultInvites,
134
                    'movieview' => 0,
135
                    'xxxview' => 0,
136
                    'musicview' => 0,
137
                    'consoleview' => 0,
138
                    'gameview' => 0,
139
                    'bookview' => 0,
140
                ];
141
                break;
142
            case 'submit':
143
                if (empty($request->input('id'))) {
144
                    $invites = $defaultInvites;
145
                    foreach ($userRoles as $role) {
146
                        if ($role['id'] === $request->input('role')) {
147
                            $invites = $role['defaultinvites'];
148
                        }
149
                    }
150
                    $ret = User::signUp($request->input('username'), $request->input('password'), $request->input('email'), '', $request->input('notes'), $invites, '', true, $request->input('role'), false);
151
                } else {
152
                    $editedUser = User::find($request->input('id'));
153
154
                    // Check if role is changing and get stack preference
155
                    $roleChanged = $editedUser->roles_id != $request->input('role');
156
                    $stackRole = $request->input('stack_role') ? true : false; // Check if checkbox is checked
157
                    $changedBy = auth()->check() ? auth()->id() : null;
158
159
                    // CRITICAL: Capture the ORIGINAL rolechangedate BEFORE any updates
160
                    // This is needed for accurate role history tracking
161
                    // Convert to string to avoid any Carbon object reference issues
162
                    $originalRoleChangeDate = $editedUser->rolechangedate
163
                        ? $editedUser->rolechangedate->toDateTimeString()
164
                        : null;
165
166
                    \Log::info('AdminUserController - Before updates', [
167
                        'user_id' => $editedUser->id,
168
                        'originalRoleChangeDate' => $originalRoleChangeDate,
169
                        'current_roles_id' => $editedUser->roles_id,
170
                        'requested_role' => $request->input('role'),
171
                        'roleChanged' => $roleChanged,
172
                        'stackRole' => $stackRole,
173
                        'form_rolechangedate' => $request->input('rolechangedate'),
174
                    ]);
175
176
                    // Handle pending role cancellation
177
                    if ($request->has('cancel_pending_role') && $request->input('cancel_pending_role')) {
178
                        $editedUser->cancelPendingRole();
179
                    }
180
181
                    // Handle rolechangedate - Update the expiry for the CURRENT role FIRST
182
                    // This must happen BEFORE role change so the new expiry applies to the old role
183
                    $adminManuallySetExpiry = false;
184
                    if ($request->has('rolechangedate')) {
185
                        $roleChangeDate = $request->input('rolechangedate');
186
                        if (! empty($roleChangeDate)) {
187
                            User::updateUserRoleChangeDate($editedUser->id, $roleChangeDate);
188
                            $adminManuallySetExpiry = true; // Flag that admin set custom expiry
189
                        } else {
190
                            // Clear the rolechangedate if empty string is provided
191
                            $editedUser->update(['rolechangedate' => null]);
192
                        }
193
                        $editedUser->refresh();
194
195
                        \Log::info('AdminUserController - After expiry update', [
196
                            'user_id' => $editedUser->id,
197
                            'new_rolechangedate' => $editedUser->rolechangedate,
198
                            'adminManuallySetExpiry' => $adminManuallySetExpiry,
199
                        ]);
200
                    }
201
202
                    // If role is changing, handle it with stacking logic
203
                    // Pass the original expiry so history records the correct old_expiry_date
204
                    if ($roleChanged && $request->input('role') !== null) {
205
                        \Log::info('AdminUserController - About to call updateUserRole', [
206
                            'user_id' => $editedUser->id,
207
                            'new_role' => (int) $request->input('role'),
208
                            'originalRoleChangeDate_passed' => $originalRoleChangeDate,
209
                            'current_user_rolechangedate' => $editedUser->rolechangedate,
210
                        ]);
211
212
                        User::updateUserRole(
213
                            $editedUser->id,
214
                            (int) $request->input('role'), // Cast to integer
215
                            !$adminManuallySetExpiry, // Only apply promotions if admin didn't set custom expiry
216
                            $stackRole, // Stack role if requested
217
                            $changedBy,
218
                            $originalRoleChangeDate, // Pass original expiry for history
219
                            $adminManuallySetExpiry // Preserve admin's manually set expiry date
220
                        );
221
                        $editedUser->refresh();
222
                    }
223
                    // Note: We don't call updateUserRole when role hasn't changed
224
                    // If admin manually set a rolechangedate, that's already applied above
225
226
                    // Update user basic information (but NOT the role - it's handled above)
227
                    // Use current role to avoid overwriting
228
                    $ret = User::updateUser(
229
                        $editedUser->id,
230
                        $request->input('username'),
231
                        $request->input('email'),
232
                        $editedUser->grabs,
233
                        $editedUser->roles_id, // Use current role, not the request role
234
                        $request->input('notes'),
235
                        $request->input('invites'),
236
                        ($request->has('movieview') ? 1 : 0),
237
                        ($request->has('musicview') ? 1 : 0),
238
                        ($request->has('gameview') ? 1 : 0),
239
                        ($request->has('xxxview') ? 1 : 0),
240
                        ($request->has('consoleview') ? 1 : 0),
241
                        ($request->has('bookview') ? 1 : 0)
242
                    );
243
244
                    if ($request->input('password') !== null) {
245
                        User::updatePassword($editedUser->id, $request->input('password'));
246
                    }
247
                }
248
249
                if ($ret >= 0) {
250
                    return redirect()->to('admin/user-list');
251
                }
252
253
                $error = match ($ret) {
254
                    User::ERR_SIGNUP_BADUNAME => 'Bad username. Try a better one.',
255
                    User::ERR_SIGNUP_BADPASS => 'Bad password. Try a longer one.',
256
                    User::ERR_SIGNUP_BADEMAIL => 'Bad email.',
257
                    User::ERR_SIGNUP_UNAMEINUSE => 'Username in use.',
258
                    User::ERR_SIGNUP_EMAILINUSE => 'Email in use.',
259
                    default => 'Unknown save error.',
260
                };
261
                $user += [
262
                    'id' => $request->input('id'),
263
                    'username' => $request->input('username'),
264
                    'email' => $request->input('email'),
265
                    'role' => $request->input('role'),
266
                    'notes' => $request->input('notes'),
267
                ];
268
                break;
269
            case 'view':
270
            default:
271
                if ($request->has('id')) {
272
                    $title = 'User Edit';
273
                    $id = $request->input('id');
274
                    $user = User::find($id);
275
276
                    // Add daily API and download counts
277
                    if ($user) {
278
                        try {
279
                            $user->daily_api_count = UserRequest::getApiRequests($user->id);
280
                            $user->daily_download_count = UserDownload::getDownloadRequests($user->id);
281
                        } catch (\Exception $e) {
282
                            $user->daily_api_count = 0;
283
                            $user->daily_download_count = 0;
284
                        }
285
                    }
286
                }
287
288
                break;
289
        }
290
291
        $this->viewData = array_merge($this->viewData, [
292
            'yesno_ids' => [1, 0],
293
            'yesno_names' => ['Yes', 'No'],
294
            'role_ids' => array_keys($roles),
295
            'role_names' => $roles,
296
            'user' => $user,
297
            'error' => $error,
298
            'title' => $title,
299
            'meta_title' => $meta_title,
300
        ]);
301
302
        return view('admin.users.edit', $this->viewData);
303
    }
304
305
    public function destroy(Request $request): RedirectResponse
306
    {
307
        if ($request->has('id')) {
308
            $user = User::find($request->input('id'));
309
            $username = $user->username; // Store username before deletion
310
311
            $user->delete();
312
313
            // Redirect with username to display in notification
314
            return redirect()->to('admin/user-list?deleted=1&username='.urlencode($username));
315
        }
316
317
        if ($request->has('redir')) {
318
            return redirect()->to($request->input('redir'));
319
        }
320
321
        return redirect()->to($request->server('HTTP_REFERER'));
0 ignored issues
show
Bug introduced by
It seems like $request->server('HTTP_REFERER') can also be of type array; however, parameter $path of Illuminate\Routing\Redirector::to() 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

321
        return redirect()->to(/** @scrutinizer ignore-type */ $request->server('HTTP_REFERER'));
Loading history...
322
    }
323
324
    public function resendVerification(Request $request): RedirectResponse
325
    {
326
        if ($request->has('id')) {
327
            $user = User::find($request->input('id'));
328
            UserVerification::generate($user);
329
330
            UserVerification::send($user, 'User email verification required');
331
332
            return redirect()->back()->with('success', 'Email verification for '.$user->username.' sent');
333
        }
334
335
        return redirect()->back()->with('error', 'User is invalid');
336
    }
337
338
    public function verify(Request $request): RedirectResponse
339
    {
340
        if ($request->has('id')) {
341
            $user = User::find($request->input('id'));
342
            User::query()->where('id', $request->input('id'))->update(['verified' => 1, 'email_verified_at' => now()]);
343
344
            return redirect()->back()->with('success', 'Email verification for '.$user->username.' sent');
345
        }
346
347
        return redirect()->back()->with('error', 'User is invalid');
348
    }
349
}
350