Completed
Push — master ( 391996...8a6498 )
by Craig
09:47 queued 04:23
created

UserAdministrationController::modifyAction()   B

Complexity

Conditions 10
Paths 8

Size

Total Lines 52
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 30
c 0
b 0
f 0
nc 8
nop 6
dl 0
loc 52
rs 7.6666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Zikula package.
7
 *
8
 * Copyright Zikula Foundation - https://ziku.la/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Zikula\UsersModule\Controller;
15
16
use InvalidArgumentException;
17
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
18
use Symfony\Component\Form\FormInterface;
19
use Symfony\Component\HttpFoundation\RedirectResponse;
20
use Symfony\Component\HttpFoundation\Request;
21
use Symfony\Component\HttpFoundation\Response;
22
use Symfony\Component\Routing\Annotation\Route;
23
use Symfony\Component\Routing\RouterInterface;
24
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
25
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
26
use Translation\Extractor\Annotation\Desc;
27
use Zikula\Bundle\CoreBundle\Controller\AbstractController;
28
use Zikula\Bundle\CoreBundle\Filter\AlphaFilter;
29
use Zikula\Bundle\CoreBundle\Response\PlainResponse;
30
use Zikula\Bundle\HookBundle\Dispatcher\HookDispatcherInterface;
31
use Zikula\Bundle\HookBundle\Hook\ProcessHook;
32
use Zikula\Bundle\HookBundle\Hook\ValidationHook;
33
use Zikula\Bundle\HookBundle\Hook\ValidationProviders;
34
use Zikula\Component\SortableColumns\Column;
35
use Zikula\Component\SortableColumns\SortableColumns;
36
use Zikula\ExtensionsModule\Api\ApiInterface\VariableApiInterface;
37
use Zikula\GroupsModule\Constant;
38
use Zikula\PermissionsModule\Annotation\PermissionCheck;
39
use Zikula\ThemeModule\Engine\Annotation\Theme;
40
use Zikula\UsersModule\Api\ApiInterface\CurrentUserApiInterface;
41
use Zikula\UsersModule\Collector\AuthenticationMethodCollector;
42
use Zikula\UsersModule\Constant as UsersConstant;
43
use Zikula\UsersModule\Entity\RepositoryInterface\UserRepositoryInterface;
44
use Zikula\UsersModule\Entity\UserEntity;
45
use Zikula\UsersModule\Event\ActiveUserPostDeletedEvent;
46
use Zikula\UsersModule\Event\ActiveUserPostUpdatedEvent;
47
use Zikula\UsersModule\Event\DeleteUserFormPostCreatedEvent;
48
use Zikula\UsersModule\Event\DeleteUserFormPostValidatedEvent;
49
use Zikula\UsersModule\Event\EditUserFormPostCreatedEvent;
50
use Zikula\UsersModule\Event\EditUserFormPostValidatedEvent;
51
use Zikula\UsersModule\Event\RegistrationPostDeletedEvent;
52
use Zikula\UsersModule\Event\RegistrationPostUpdatedEvent;
53
use Zikula\UsersModule\Form\Type\AdminModifyUserType;
54
use Zikula\UsersModule\Form\Type\DeleteConfirmationType;
55
use Zikula\UsersModule\Form\Type\DeleteType;
56
use Zikula\UsersModule\Form\Type\MailType;
57
use Zikula\UsersModule\Form\Type\RegistrationType\ApproveRegistrationConfirmationType;
58
use Zikula\UsersModule\Form\Type\SearchUserType;
59
use Zikula\UsersModule\Helper\AdministrationActionsHelper;
60
use Zikula\UsersModule\Helper\MailHelper;
61
use Zikula\UsersModule\Helper\RegistrationHelper;
62
use Zikula\UsersModule\HookSubscriber\UserManagementUiHooksSubscriber;
63
64
/**
65
 * Class UserAdministrationController
66
 *
67
 * @Route("/admin")
68
 */
69
class UserAdministrationController extends AbstractController
70
{
71
    /**
72
     * @Route("/list/{sort}/{sortdir}/{letter}/{page}", methods = {"GET"}, requirements={"page" = "\d+"})
73
     * @PermissionCheck("moderate")
74
     * @Theme("admin")
75
     * @Template("@ZikulaUsersModule/UserAdministration/list.html.twig")
76
     */
77
    public function listAction(
78
        Request $request,
79
        UserRepositoryInterface $userRepository,
80
        RouterInterface $router,
81
        AdministrationActionsHelper $actionsHelper,
82
        AuthenticationMethodCollector $authenticationMethodCollector,
83
        string $sort = 'uid',
84
        string $sortdir = 'DESC',
85
        string $letter = 'all',
86
        int $page = 1
87
    ): array {
88
        $sortableColumns = new SortableColumns($router, 'zikulausersmodule_useradministration_list', 'sort', 'sortdir');
89
        $sortableColumns->addColumns([new Column('uname'), new Column('uid'), new Column('registrationDate'), new Column('lastLogin'), new Column('activated')]);
90
        $sortableColumns->setOrderByFromRequest($request);
91
        $sortableColumns->setAdditionalUrlParameters([
92
            'letter' => $letter,
93
            'page' => $page
94
        ]);
95
96
        $filter = [];
97
        if (!empty($letter) && 'all' !== $letter) {
98
            $filter['uname'] = ['operator' => 'like', 'operand' => "${letter}%"];
99
        }
100
        $pageSize = $this->getVar(UsersConstant::MODVAR_ITEMS_PER_PAGE, UsersConstant::DEFAULT_ITEMS_PER_PAGE);
101
        $paginator = $userRepository->query($filter, [$sort => $sortdir], 'and', $page, $pageSize);
102
        $paginator->setRoute('zikulausersmodule_useradministration_list');
103
        $routeParameters = [
104
            'sort' => $sort,
105
            'sortdir' => $sortdir,
106
            'letter' => $letter,
107
        ];
108
        $paginator->setRouteParameters($routeParameters);
109
110
        return [
111
            'sort' => $sortableColumns->generateSortableColumns(),
112
            'actionsHelper' => $actionsHelper,
113
            'authMethodCollector' => $authenticationMethodCollector,
114
            'alpha' => new AlphaFilter('zikulausersmodule_useradministration_list', $routeParameters, $letter),
115
            'paginator' => $paginator
116
        ];
117
    }
118
119
    /**
120
     * Called from UsersModule/Resources/public/js/Zikula.Users.Admin.View.js
121
     * to populate a username search
122
     *
123
     * @Route("/getusersbyfragmentastable", methods = {"POST"}, options={"expose"=true, "i18n"=false})
124
     */
125
    public function getUsersByFragmentAsTableAction(
126
        Request $request,
127
        UserRepositoryInterface $userRepository,
128
        AdministrationActionsHelper $actionsHelper
129
    ): Response {
130
        if (!$this->hasPermission('ZikulaUsersModule', '::', ACCESS_MODERATE)) {
131
            return new PlainResponse('');
132
        }
133
        $fragment = $request->request->get('fragment');
134
        $filter = [
135
            'activated' => ['operator' => 'notIn', 'operand' => [
136
                UsersConstant::ACTIVATED_PENDING_REG,
137
                UsersConstant::ACTIVATED_PENDING_DELETE
138
            ]],
139
            'uname' => ['operator' => 'like', 'operand' => "${fragment}%"]
140
        ];
141
        $users = $userRepository->query($filter);
142
143
        return $this->render('@ZikulaUsersModule/UserAdministration/userlist.html.twig', [
144
            'users' => $users,
145
            'actionsHelper' => $actionsHelper
146
        ], new PlainResponse());
147
    }
148
149
    /**
150
     * @Route("/user/modify/{user}", requirements={"user" = "^[1-9]\d*$"})
151
     * @Theme("admin")
152
     * @Template("@ZikulaUsersModule/UserAdministration/modify.html.twig")
153
     *
154
     * @return array|RedirectResponse
155
     * @throws AccessDeniedException Thrown if the user hasn't edit permissions for the user record
156
     */
157
    public function modifyAction(
158
        Request $request,
159
        UserEntity $user,
160
        CurrentUserApiInterface $currentUserApi,
161
        VariableApiInterface $variableApi,
162
        EventDispatcherInterface $eventDispatcher,
163
        HookDispatcherInterface $hookDispatcher
164
    ) {
165
        if (!$this->hasPermission('ZikulaUsersModule::', $user->getUname() . '::' . $user->getUid(), ACCESS_EDIT)) {
166
            throw new AccessDeniedException();
167
        }
168
        if (UsersConstant::USER_ID_ANONYMOUS === $user->getUid()) {
169
            throw new AccessDeniedException($this->trans("Error! You can't edit the guest account."));
170
        }
171
172
        $form = $this->createForm(AdminModifyUserType::class, $user);
173
        $originalUser = clone $user;
174
        $editUserFormPostCreatedEvent = new EditUserFormPostCreatedEvent($form);
175
        $eventDispatcher->dispatch($editUserFormPostCreatedEvent);
176
        $form->handleRequest($request);
177
178
        $hook = new ValidationHook(new ValidationProviders());
179
        $hookDispatcher->dispatch(UserManagementUiHooksSubscriber::EDIT_VALIDATE, $hook);
180
        $validators = $hook->getValidators();
181
182
        if ($form->isSubmitted() && $form->isValid() && !$validators->hasErrors()) {
183
            if ($form->get('submit')->isClicked()) {
0 ignored issues
show
Bug introduced by
The method isClicked() does not exist on Symfony\Component\Form\FormInterface. It seems like you code against a sub-type of Symfony\Component\Form\FormInterface such as Symfony\Component\Form\SubmitButton. ( Ignorable by Annotation )

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

183
            if ($form->get('submit')->/** @scrutinizer ignore-call */ isClicked()) {
Loading history...
184
                $user = $form->getData();
185
                $this->checkSelf($currentUserApi, $variableApi, $user, $originalUser->getGroups()->toArray());
186
187
                $eventDispatcher->dispatch(new EditUserFormPostValidatedEvent($form, $user));
188
189
                $this->getDoctrine()->getManager()->flush();
190
191
                $updateEvent = UsersConstant::ACTIVATED_PENDING_REG === $user->getActivated()
192
                    ? new RegistrationPostUpdatedEvent($user, $originalUser)
193
                    : new ActiveUserPostUpdatedEvent($user, $originalUser);
194
                $eventDispatcher->dispatch($updateEvent);
195
196
                $hookDispatcher->dispatch(UserManagementUiHooksSubscriber::EDIT_PROCESS, new ProcessHook($user->getUid()));
197
198
                $this->addFlash('status', "Done! Saved user's account information.");
199
            } elseif ($form->get('cancel')->isClicked()) {
200
                $this->addFlash('status', 'Operation cancelled.');
201
            }
202
203
            return $this->redirectToRoute('zikulausersmodule_useradministration_list');
204
        }
205
206
        return [
207
            'form' => $form->createView(),
208
            'additionalTemplates' => isset($editUserFormPostCreatedEvent) ? $editUserFormPostCreatedEvent->getTemplates() : []
209
        ];
210
    }
211
212
    /**
213
     * @Route("/approve/{user}/{force}", requirements={"user" = "^[1-9]\d*$"})
214
     * @PermissionCheck("moderate")
215
     * @Theme("admin")
216
     * @Template("@ZikulaUsersModule/UserAdministration/approve.html.twig")
217
     *
218
     * @return array|RedirectResponse
219
     */
220
    public function approveAction(
221
        Request $request,
222
        UserEntity $user,
223
        RegistrationHelper $registrationHelper,
224
        MailHelper $mailHelper,
225
        bool $force = false
226
    ) {
227
        $forceVerification = $this->hasPermission('ZikulaUsersModule', '::', ACCESS_ADMIN) && $force;
228
        $form = $this->createForm(ApproveRegistrationConfirmationType::class, [
229
            'user' => $user->getUid(),
230
            'force' => $forceVerification
231
        ], [
232
            'buttonLabel' => $this->trans('Approve')
233
        ]);
234
        $redirectToRoute = 'zikulausersmodule_useradministration_list';
235
236
        if (!$forceVerification) {
237
            if ($user->isApproved()) {
238
                $this->addFlash('error', $this->trans('Warning! Nothing to do! %sub% is already approved.', ['%sub%' => $user->getUname()]));
239
240
                return $this->redirectToRoute($redirectToRoute);
241
            }
242
            if (!$user->isApproved() && !$this->hasPermission('ZikulaUsersModule::', '::', ACCESS_ADMIN)) {
243
                $this->addFlash('error', $this->trans('Error! %sub% cannot be approved.', ['%sub%' => $user->getUname()]));
244
245
                return $this->redirectToRoute($redirectToRoute);
246
            }
247
        }
248
249
        $form->handleRequest($request);
250
        if ($form->isSubmitted() && $form->isValid()) {
251
            if ($form->get('confirm')->isClicked()) {
252
                $registrationHelper->approve($user);
253
                if (UsersConstant::ACTIVATED_PENDING_REG === $user->getActivated()) {
254
                    $notificationErrors = $mailHelper->createAndSendRegistrationMail($user, true, false);
255
                } else {
256
                    $notificationErrors = $mailHelper->createAndSendUserMail($user, true, false);
257
                }
258
259
                if ($notificationErrors) {
260
                    $this->addFlash('error', implode('<br />', $notificationErrors));
261
                }
262
                $this->addFlash('status', $this->trans('Done! %sub% has been approved.', ['%sub%' => $user->getUname()]));
263
            } elseif ($form->get('cancel')->isClicked()) {
264
                $this->addFlash('status', 'Operation cancelled.');
265
            }
266
267
            return $this->redirectToRoute($redirectToRoute);
268
        }
269
270
        return [
271
            'form' => $form->createView(),
272
            'user' => $user
273
        ];
274
    }
275
276
    /**
277
     * @Route("/delete/{user}", requirements={"user" = "^[1-9]\d*$"})
278
     * @PermissionCheck("delete")
279
     * @Theme("admin")
280
     * @Template("@ZikulaUsersModule/UserAdministration/delete.html.twig")
281
     *
282
     * @return array|RedirectResponse
283
     */
284
    public function deleteAction(
285
        Request $request,
286
        CurrentUserApiInterface $currentUserApi,
287
        UserRepositoryInterface $userRepository,
288
        HookDispatcherInterface $hookDispatcher,
289
        EventDispatcherInterface $eventDispatcher,
290
        UserEntity $user = null
291
    ) {
292
        $uids = [];
293
        if (!isset($user) && 'POST' === $request->getMethod() && $request->request->has('zikulausersmodule_delete')) {
294
            $uids = $request->request->get('zikulausersmodule_delete')['users'];
295
        } elseif (isset($user)) {
296
            $uids = [$user->getUid()];
297
        }
298
        $usersImploded = implode(',', $uids);
299
300
        $deleteConfirmationForm = $this->createForm(DeleteConfirmationType::class, [
301
            'users' => $usersImploded
302
        ]);
303
        $deleteUserFormPostCreatedEvent = new DeleteUserFormPostCreatedEvent($deleteConfirmationForm);
304
        $eventDispatcher->dispatch($deleteUserFormPostCreatedEvent);
305
        $deleteConfirmationForm->handleRequest($request);
306
        if (empty($uids) && !$deleteConfirmationForm->isSubmitted()) {
307
            throw new InvalidArgumentException($this->trans('No users selected.'));
308
        }
309
        if ($deleteConfirmationForm->isSubmitted()) {
310
            if ($deleteConfirmationForm->get('cancel')->isClicked()) {
311
                $this->addFlash('success', 'Operation cancelled.');
312
313
                return $this->redirectToRoute('zikulausersmodule_useradministration_list');
314
            }
315
            $userIdsImploded = $deleteConfirmationForm->get('users')->getData();
316
            $userIds = explode(',', $userIdsImploded);
317
            $valid = true;
318
            foreach ($userIds as $k => $uid) {
319
                if (in_array($uid, [UsersConstant::USER_ID_ANONYMOUS, UsersConstant::USER_ID_ADMIN, $currentUserApi->get('uid')], true)) {
320
                    unset($userIds[$k]);
321
                    $this->addFlash('danger', $this->trans('You are not allowed to delete user id %uid%', ['%uid%' => $uid]));
322
                    continue;
323
                }
324
                $hookDispatcher->dispatch(UserManagementUiHooksSubscriber::DELETE_VALIDATE, $hook = new ValidationHook());
325
                if ($hook->getValidators()->hasErrors()) {
326
                    $valid = false;
327
                }
328
            }
329
            if ($valid && $deleteConfirmationForm->isValid()) {
330
                // send email to 'denied' registrations. see MailHelper::sendNotification (regdeny) #2915
331
                $deletedUsers = $userRepository->query(['uid' => ['operator' => 'in', 'operand' => $userIds]]);
332
                foreach ($deletedUsers as $deletedUser) {
333
                    if (UsersConstant::ACTIVATED_ACTIVE === $deletedUser->getActivated()) {
334
                        $eventDispatcher->dispatch(new ActiveUserPostDeletedEvent($deletedUser));
335
                    } else {
336
                        $eventDispatcher->dispatch(new RegistrationPostDeletedEvent($deletedUser));
337
                    }
338
                    $eventDispatcher->dispatch(new DeleteUserFormPostValidatedEvent($deleteConfirmationForm, $deletedUser));
339
                    $hookDispatcher->dispatch(UserManagementUiHooksSubscriber::DELETE_PROCESS, new ProcessHook($deletedUser->getUid()));
340
                    $userRepository->removeAndFlush($deletedUser);
341
                }
342
                $this->addFlash(
343
                    'success',
344
                    /** @Desc("{count, plural,\n  one   {User deleted!}\n  other {# users deleted!}\n}") */
345
                    $this->getTranslator()->trans(
346
                        'plural_n.users.deleted',
347
                        ['%count%' => count($deletedUsers)]
348
                    )
349
                );
350
351
                return $this->redirectToRoute('zikulausersmodule_useradministration_list');
352
            }
353
        }
354
        $users = $userRepository->findByUids($uids);
355
356
        return [
357
            'users' => $users,
358
            'form' => $deleteConfirmationForm->createView(),
359
            'additionalTemplates' => isset($deleteUserFormPostCreatedEvent) ? $deleteUserFormPostCreatedEvent->getTemplates() : []
360
        ];
361
    }
362
363
    /**
364
     * @Route("/search")
365
     * @PermissionCheck("moderate")
366
     * @Theme("admin")
367
     * @Template("@ZikulaUsersModule/UserAdministration/search.html.twig")
368
     *
369
     * @return array|Response
370
     */
371
    public function searchAction(
372
        Request $request,
373
        UserRepositoryInterface $userRepository,
374
        VariableApiInterface $variableApi
375
    ) {
376
        $form = $this->createForm(SearchUserType::class, []);
377
        $form->handleRequest($request);
378
        if ($form->isSubmitted()) {
379
            $resultsForm = $this->createForm(DeleteType::class, [], [
380
                'choices' => $userRepository->queryBySearchForm($form->getData(), 250),
0 ignored issues
show
Unused Code introduced by
The call to Zikula\UsersModule\Entit...ce::queryBySearchForm() has too many arguments starting with 250. ( Ignorable by Annotation )

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

380
                'choices' => $userRepository->/** @scrutinizer ignore-call */ queryBySearchForm($form->getData(), 250),

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
381
                'action' => $this->generateUrl('zikulausersmodule_useradministration_delete')
382
            ]);
383
384
            return $this->render('@ZikulaUsersModule/UserAdministration/searchResults.html.twig', [
385
                'resultsForm' => $resultsForm->createView(),
386
                'mailForm' => $this->buildMailForm($variableApi)->createView()
387
            ]);
388
        }
389
390
        return [
391
            'form' => $form->createView()
392
        ];
393
    }
394
395
    /**
396
     * @Route("/mail")
397
     * @PermissionCheck({"$_zkModule::MailUsers", "::", "comment"})
398
     */
399
    public function mailUsersAction(
400
        Request $request,
401
        UserRepositoryInterface $userRepository,
402
        VariableApiInterface $variableApi,
403
        MailHelper $mailHelper
404
    ): RedirectResponse {
405
        $mailForm = $this->buildMailForm($variableApi);
406
        $mailForm->handleRequest($request);
407
        if ($mailForm->isSubmitted() && $mailForm->isValid()) {
408
            $data = $mailForm->getData();
409
            $users = $userRepository->query(['uid' => ['operator' => 'in', 'operand' => explode(',', $data['userIds'])]]);
410
            if (empty($users)) {
411
                throw new InvalidArgumentException($this->trans('No users found.'));
412
            }
413
            if ($mailHelper->mailUsers($users, $data)) {
414
                $this->addFlash('success', 'Done! Mail sent.');
415
            } else {
416
                $this->addFlash('error', 'Could not send mail.');
417
            }
418
        } else {
419
            $this->addFlash('error', 'Could not send mail.');
420
        }
421
422
        return $this->redirectToRoute('zikulausersmodule_useradministration_search');
423
    }
424
425
    private function buildMailForm(VariableApiInterface $variableApi): FormInterface
426
    {
427
        return $this->createForm(MailType::class, [
428
            'from' => $variableApi->getSystemVar('sitename'),
429
            'replyto' => $variableApi->getSystemVar('adminmail'),
430
            'format' => 'text',
431
            'batchsize' => 100
432
        ], [
433
            'action' => $this->generateUrl('zikulausersmodule_useradministration_mailusers')
434
        ]);
435
    }
436
437
    /**
438
     * Prevent user from modifying certain aspects of self.
439
     */
440
    private function checkSelf(
441
        CurrentUserApiInterface $currentUserApi,
442
        VariableApiInterface $variableApi,
443
        UserEntity $userBeingModified,
444
        array $originalGroups = []
445
    ): void {
446
        $currentUserId = $currentUserApi->get('uid');
447
        if ($currentUserId !== $userBeingModified->getUid()) {
448
            return;
449
        }
450
451
        // current user not allowed to deactivate self
452
        if (UsersConstant::ACTIVATED_ACTIVE !== $userBeingModified->getActivated()) {
453
            $this->addFlash('info', 'You are not allowed to alter your own active state.');
454
            $userBeingModified->setActivated(UsersConstant::ACTIVATED_ACTIVE);
455
        }
456
        // current user not allowed to remove self from default group
457
        $defaultGroup = $variableApi->get('ZikulaGroupsModule', 'defaultgroup', 1);
458
        if (!$userBeingModified->getGroups()->containsKey($defaultGroup)) {
459
            $this->addFlash('info', 'You are not allowed to remove yourself from the default group.');
460
            $userBeingModified->getGroups()->add($originalGroups[$defaultGroup]);
461
        }
462
        // current user not allowed to remove self from admin group if currently a member
463
        if (isset($originalGroups[Constant::GROUP_ID_ADMIN]) && !$userBeingModified->getGroups()->containsKey(Constant::GROUP_ID_ADMIN)) {
464
            $this->addFlash('info', 'You are not allowed to remove yourself from the primary administrator group.');
465
            $userBeingModified->getGroups()->add($originalGroups[Constant::GROUP_ID_ADMIN]);
466
        }
467
    }
468
}
469