Completed
Pull Request — master (#4481)
by Axel
24:43 queued 19:42
created

UserAdministrationController::checkSelf()   A

Complexity

Conditions 6
Paths 9

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 13
nc 9
nop 4
dl 0
loc 26
rs 9.2222
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Zikula package.
7
 *
8
 * Copyright Zikula - 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\CoreBundle\Site\SiteDefinitionInterface;
31
use Zikula\Bundle\HookBundle\Dispatcher\HookDispatcherInterface;
32
use Zikula\Bundle\HookBundle\Hook\ProcessHook;
33
use Zikula\Bundle\HookBundle\Hook\ValidationHook;
34
use Zikula\Bundle\HookBundle\Hook\ValidationProviders;
35
use Zikula\Component\SortableColumns\Column;
36
use Zikula\Component\SortableColumns\SortableColumns;
37
use Zikula\ExtensionsModule\Api\ApiInterface\VariableApiInterface;
38
use Zikula\GroupsModule\Constant;
39
use Zikula\PermissionsModule\Annotation\PermissionCheck;
40
use Zikula\ThemeModule\Engine\Annotation\Theme;
41
use Zikula\UsersModule\Api\ApiInterface\CurrentUserApiInterface;
42
use Zikula\UsersModule\Collector\AuthenticationMethodCollector;
43
use Zikula\UsersModule\Constant as UsersConstant;
44
use Zikula\UsersModule\Entity\RepositoryInterface\UserRepositoryInterface;
45
use Zikula\UsersModule\Entity\UserEntity;
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\RegistrationPostUpdatedEvent;
52
use Zikula\UsersModule\Form\Type\AdminModifyUserType;
53
use Zikula\UsersModule\Form\Type\DeleteConfirmationType;
54
use Zikula\UsersModule\Form\Type\DeleteType;
55
use Zikula\UsersModule\Form\Type\MailType;
56
use Zikula\UsersModule\Form\Type\RegistrationType\ApproveRegistrationConfirmationType;
57
use Zikula\UsersModule\Form\Type\SearchUserType;
58
use Zikula\UsersModule\Helper\AdministrationActionsHelper;
59
use Zikula\UsersModule\Helper\DeleteHelper;
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 listUsers(
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->paginatedQuery($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 getUsersByFragmentAsTable(
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 modify(
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 approve(
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 delete(
285
        Request $request,
286
        CurrentUserApiInterface $currentUserApi,
287
        UserRepositoryInterface $userRepository,
288
        HookDispatcherInterface $hookDispatcher,
289
        EventDispatcherInterface $eventDispatcher,
290
        DeleteHelper $deleteHelper,
291
        UserEntity $user = null
292
    ) {
293
        $uids = [];
294
        if (!isset($user) && Request::METHOD_POST === $request->getMethod() && $request->request->has('zikulausersmodule_delete')) {
295
            $deletionData = $request->request->get('zikulausersmodule_delete');
296
            if (isset($deletionData['users']) && !empty($deletionData['users'])) {
297
                $uids = $deletionData['users'];
298
            }
299
        } elseif (isset($user)) {
300
            $uids = [$user->getUid()];
301
        }
302
        if (!count($uids)) {
303
            $this->addFlash('warning', 'No users selected.');
304
305
            return $this->redirectToRoute('zikulausersmodule_useradministration_list');
306
        }
307
        $usersImploded = implode(',', $uids);
308
309
        $deleteConfirmationForm = $this->createForm(DeleteConfirmationType::class, [
310
            'users' => $usersImploded
311
        ]);
312
        $deleteUserFormPostCreatedEvent = new DeleteUserFormPostCreatedEvent($deleteConfirmationForm);
313
        $eventDispatcher->dispatch($deleteUserFormPostCreatedEvent);
314
        $deleteConfirmationForm->handleRequest($request);
315
        if (empty($uids) && !$deleteConfirmationForm->isSubmitted()) {
316
            $this->addFlash('warning', 'No users selected.');
317
318
            return $this->redirectToRoute('zikulausersmodule_useradministration_list');
319
        }
320
        if ($deleteConfirmationForm->isSubmitted()) {
321
            if ($deleteConfirmationForm->get('cancel')->isClicked()) {
322
                $this->addFlash('success', 'Operation cancelled.');
323
324
                return $this->redirectToRoute('zikulausersmodule_useradministration_list');
325
            }
326
            $userIdsImploded = $deleteConfirmationForm->get('users')->getData();
327
            $userIds = explode(',', $userIdsImploded);
328
            $valid = true;
329
            foreach ($userIds as $k => $uid) {
330
                if (in_array($uid, [UsersConstant::USER_ID_ANONYMOUS, UsersConstant::USER_ID_ADMIN, $currentUserApi->get('uid')], true)) {
331
                    unset($userIds[$k]);
332
                    $this->addFlash('danger', $this->trans('You are not allowed to delete user id %uid%', ['%uid%' => $uid]));
333
                    continue;
334
                }
335
                $hookDispatcher->dispatch(UserManagementUiHooksSubscriber::DELETE_VALIDATE, $hook = new ValidationHook());
336
                if ($hook->getValidators()->hasErrors()) {
337
                    $valid = false;
338
                }
339
            }
340
            if ($valid && $deleteConfirmationForm->isValid()) {
341
                $deletedUsers = $userRepository->query(['uid' => ['operator' => 'in', 'operand' => $userIds]]);
342
                $force = $deleteConfirmationForm->get('force')->getData();
343
                foreach ($deletedUsers as $deletedUser) {
344
                    $deleteHelper->deleteUser($deletedUser, $force);
345
                    $eventDispatcher->dispatch(new DeleteUserFormPostValidatedEvent($deleteConfirmationForm, $deletedUser));
346
                }
347
                $this->addFlash(
348
                    'success',
349
                    /** @Desc("{count, plural,\n  one   {User deleted!}\n  other {# users deleted!}\n}") */
350
                    $this->getTranslator()->trans(
351
                        'plural_n.users.deleted',
352
                        ['%count%' => count($deletedUsers)]
353
                    )
354
                );
355
356
                return $this->redirectToRoute('zikulausersmodule_useradministration_list');
357
            }
358
        }
359
        $users = $userRepository->findByUids($uids);
360
361
        return [
362
            'users' => $users,
363
            'form' => $deleteConfirmationForm->createView(),
364
            'additionalTemplates' => isset($deleteUserFormPostCreatedEvent) ? $deleteUserFormPostCreatedEvent->getTemplates() : []
365
        ];
366
    }
367
368
    /**
369
     * @Route("/search")
370
     * @PermissionCheck("moderate")
371
     * @Theme("admin")
372
     * @Template("@ZikulaUsersModule/UserAdministration/search.html.twig")
373
     *
374
     * @return array|Response
375
     */
376
    public function search(
377
        Request $request,
378
        UserRepositoryInterface $userRepository,
379
        VariableApiInterface $variableApi,
380
        SiteDefinitionInterface $site
381
    ) {
382
        $form = $this->createForm(SearchUserType::class, []);
383
        $form->handleRequest($request);
384
        if ($form->isSubmitted()) {
385
            $resultsForm = $this->createForm(DeleteType::class, [], [
386
                'choices' => $userRepository->queryBySearchForm($form->getData()),
387
                'action' => $this->generateUrl('zikulausersmodule_useradministration_delete')
388
            ]);
389
390
            return $this->render('@ZikulaUsersModule/UserAdministration/searchResults.html.twig', [
391
                'resultsForm' => $resultsForm->createView(),
392
                'mailForm' => $this->buildMailForm($variableApi, $site)->createView()
393
            ]);
394
        }
395
396
        return [
397
            'form' => $form->createView()
398
        ];
399
    }
400
401
    /**
402
     * @Route("/mail")
403
     * @PermissionCheck({"$_zkModule::MailUsers", "::", "comment"})
404
     */
405
    public function mailUsers(
406
        Request $request,
407
        UserRepositoryInterface $userRepository,
408
        VariableApiInterface $variableApi,
409
        MailHelper $mailHelper,
410
        SiteDefinitionInterface $site
411
    ): RedirectResponse {
412
        $mailForm = $this->buildMailForm($variableApi, $site);
413
        $mailForm->handleRequest($request);
414
        if ($mailForm->isSubmitted() && $mailForm->isValid()) {
415
            $data = $mailForm->getData();
416
            $users = $userRepository->query(['uid' => ['operator' => 'in', 'operand' => explode(',', $data['userIds'])]]);
417
            if (empty($users)) {
418
                throw new InvalidArgumentException($this->trans('No users found.'));
419
            }
420
            if ($mailHelper->mailUsers($users, $data)) {
421
                $this->addFlash('success', 'Done! Mail sent.');
422
            } else {
423
                $this->addFlash('error', 'Could not send mail.');
424
            }
425
        } else {
426
            $this->addFlash('error', 'Could not send mail.');
427
        }
428
429
        return $this->redirectToRoute('zikulausersmodule_useradministration_search');
430
    }
431
432
    private function buildMailForm(
433
        VariableApiInterface $variableApi,
434
        SiteDefinitionInterface $site
435
    ): FormInterface {
436
        return $this->createForm(MailType::class, [
437
            'from' => $site->getName(),
438
            'replyto' => $variableApi->getSystemVar('adminmail'),
439
            'format' => 'text',
440
            'batchsize' => 100
441
        ], [
442
            'action' => $this->generateUrl('zikulausersmodule_useradministration_mailusers')
443
        ]);
444
    }
445
446
    /**
447
     * Prevent user from modifying certain aspects of self.
448
     */
449
    private function checkSelf(
450
        CurrentUserApiInterface $currentUserApi,
451
        VariableApiInterface $variableApi,
452
        UserEntity $userBeingModified,
453
        array $originalGroups = []
454
    ): void {
455
        $currentUserId = $currentUserApi->get('uid');
456
        if ($currentUserId !== $userBeingModified->getUid()) {
457
            return;
458
        }
459
460
        // current user not allowed to deactivate self
461
        if (UsersConstant::ACTIVATED_ACTIVE !== $userBeingModified->getActivated()) {
462
            $this->addFlash('info', 'You are not allowed to alter your own active state.');
463
            $userBeingModified->setActivated(UsersConstant::ACTIVATED_ACTIVE);
464
        }
465
        // current user not allowed to remove self from default group
466
        $defaultGroup = $variableApi->get('ZikulaGroupsModule', 'defaultgroup', 1);
467
        if (!$userBeingModified->getGroups()->containsKey($defaultGroup)) {
468
            $this->addFlash('info', 'You are not allowed to remove yourself from the default group.');
469
            $userBeingModified->getGroups()->add($originalGroups[$defaultGroup]);
470
        }
471
        // current user not allowed to remove self from admin group if currently a member
472
        if (isset($originalGroups[Constant::GROUP_ID_ADMIN]) && !$userBeingModified->getGroups()->containsKey(Constant::GROUP_ID_ADMIN)) {
473
            $this->addFlash('info', 'You are not allowed to remove yourself from the primary administrator group.');
474
            $userBeingModified->getGroups()->add($originalGroups[Constant::GROUP_ID_ADMIN]);
475
        }
476
    }
477
}
478