Test Setup Failed
Push — master ( 57ccaa...f9310d )
by Craig
06:25
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 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 Doctrine\Common\Collections\ArrayCollection;
17
use InvalidArgumentException;
18
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
19
use Symfony\Component\Form\FormInterface;
20
use Symfony\Component\HttpFoundation\RedirectResponse;
21
use Symfony\Component\HttpFoundation\Request;
22
use Symfony\Component\HttpFoundation\Response;
23
use Symfony\Component\Routing\Annotation\Route;
24
use Symfony\Component\Routing\RouterInterface;
25
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
26
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
27
use Translation\Extractor\Annotation\Desc;
28
use Zikula\Bundle\CoreBundle\Controller\AbstractController;
29
use Zikula\Bundle\CoreBundle\Event\GenericEvent;
30
use Zikula\Bundle\CoreBundle\Response\PlainResponse;
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\UserFormAwareEvent;
47
use Zikula\UsersModule\Event\UserFormDataEvent;
48
use Zikula\UsersModule\Form\Type\AdminModifyUserType;
49
use Zikula\UsersModule\Form\Type\DeleteConfirmationType;
50
use Zikula\UsersModule\Form\Type\DeleteType;
51
use Zikula\UsersModule\Form\Type\MailType;
52
use Zikula\UsersModule\Form\Type\RegistrationType\ApproveRegistrationConfirmationType;
53
use Zikula\UsersModule\Form\Type\SearchUserType;
54
use Zikula\UsersModule\Helper\AdministrationActionsHelper;
55
use Zikula\UsersModule\Helper\MailHelper;
56
use Zikula\UsersModule\Helper\RegistrationHelper;
57
use Zikula\UsersModule\HookSubscriber\UserManagementUiHooksSubscriber;
58
use Zikula\UsersModule\RegistrationEvents;
59
use Zikula\UsersModule\UserEvents;
60
61
/**
62
 * Class UserAdministrationController
63
 *
64
 * @Route("/admin")
65
 */
66
class UserAdministrationController extends AbstractController
67
{
68
    /**
69
     * @Route("/list/{sort}/{sortdir}/{letter}/{startnum}")
70
     * @PermissionCheck("moderate")
71
     * @Theme("admin")
72
     * @Template("@ZikulaUsersModule/UserAdministration/list.html.twig")
73
     */
74
    public function listAction(
75
        Request $request,
76
        UserRepositoryInterface $userRepository,
77
        RouterInterface $router,
78
        AdministrationActionsHelper $actionsHelper,
79
        AuthenticationMethodCollector $authenticationMethodCollector,
80
        string $sort = 'uid',
81
        string $sortdir = 'DESC',
82
        string $letter = 'all',
83
        int $startnum = 0
84
    ): array {
85
        $startnum = $startnum > 0 ? $startnum - 1 : 0;
86
87
        $sortableColumns = new SortableColumns($router, 'zikulausersmodule_useradministration_list', 'sort', 'sortdir');
88
        $sortableColumns->addColumns([new Column('uname'), new Column('uid'), new Column('registrationDate'), new Column('lastLogin'), new Column('activated')]);
89
        $sortableColumns->setOrderByFromRequest($request);
90
        $sortableColumns->setAdditionalUrlParameters([
91
            'letter' => $letter,
92
            'startnum' => $startnum
93
        ]);
94
95
        $filter = [];
96
        if (!empty($letter) && 'all' !== $letter) {
97
            $filter['uname'] = ['operator' => 'like', 'operand' => "${letter}%"];
98
        }
99
        $limit = $this->getVar(UsersConstant::MODVAR_ITEMS_PER_PAGE, UsersConstant::DEFAULT_ITEMS_PER_PAGE);
100
        $users = $userRepository->query($filter, [$sort => $sortdir], $limit, $startnum);
101
102
        return [
103
            'sort' => $sortableColumns->generateSortableColumns(),
104
            'pager' => [
105
                'count' => $users->count(),
106
                'limit' => $limit
107
            ],
108
            'actionsHelper' => $actionsHelper,
109
            'authMethodCollector' => $authenticationMethodCollector,
110
            'users' => $users
111
        ];
112
    }
113
114
    /**
115
     * Called from UsersModule/Resources/public/js/Zikula.Users.Admin.View.js
116
     * to populate a username search
117
     *
118
     * @Route("/getusersbyfragmentastable", methods = {"POST"}, options={"expose"=true, "i18n"=false})
119
     */
120
    public function getUsersByFragmentAsTableAction(
121
        Request $request,
122
        UserRepositoryInterface $userRepository,
123
        AdministrationActionsHelper $actionsHelper
124
    ): Response {
125
        if (!$this->hasPermission('ZikulaUsersModule', '::', ACCESS_MODERATE)) {
126
            return new PlainResponse('');
127
        }
128
        $fragment = $request->request->get('fragment');
129
        $filter = [
130
            'activated' => ['operator' => 'notIn', 'operand' => [
131
                UsersConstant::ACTIVATED_PENDING_REG,
132
                UsersConstant::ACTIVATED_PENDING_DELETE
133
            ]],
134
            'uname' => ['operator' => 'like', 'operand' => "${fragment}%"]
135
        ];
136
        $users = $userRepository->query($filter);
137
138
        return $this->render('@ZikulaUsersModule/UserAdministration/userlist.html.twig', [
139
            'users' => $users,
140
            'actionsHelper' => $actionsHelper
141
        ], new PlainResponse());
142
    }
143
144
    /**
145
     * @Route("/user/modify/{user}", requirements={"user" = "^[1-9]\d*$"})
146
     * @Theme("admin")
147
     * @Template("@ZikulaUsersModule/UserAdministration/modify.html.twig")
148
     *
149
     * @return array|RedirectResponse
150
     * @throws AccessDeniedException Thrown if the user hasn't edit permissions for the user record
151
     */
152
    public function modifyAction(
153
        Request $request,
154
        UserEntity $user,
155
        CurrentUserApiInterface $currentUserApi,
156
        VariableApiInterface $variableApi,
157
        EventDispatcherInterface $eventDispatcher,
158
        HookDispatcherInterface $hookDispatcher
159
    ) {
160
        if (!$this->hasPermission('ZikulaUsersModule::', $user->getUname() . '::' . $user->getUid(), ACCESS_EDIT)) {
161
            throw new AccessDeniedException();
162
        }
163
        if (UsersConstant::USER_ID_ANONYMOUS === $user->getUid()) {
164
            throw new AccessDeniedException($this->trans("Error! You can't edit the guest account."));
165
        }
166
167
        $form = $this->createForm(AdminModifyUserType::class, $user);
168
        $originalUserName = $user->getUname();
169
        $originalGroups = $user->getGroups()->toArray();
170
        $formEvent = new UserFormAwareEvent($form);
171
        $eventDispatcher->dispatch($formEvent, UserEvents::EDIT_FORM);
172
        $form->handleRequest($request);
173
174
        $hook = new ValidationHook(new ValidationProviders());
175
        $hookDispatcher->dispatch(UserManagementUiHooksSubscriber::EDIT_VALIDATE, $hook);
176
        $validators = $hook->getValidators();
177
178
        if ($form->isSubmitted() && $form->isValid() && !$validators->hasErrors()) {
179
            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

179
            if ($form->get('submit')->/** @scrutinizer ignore-call */ isClicked()) {
Loading history...
180
                $user = $form->getData();
181
                $this->checkSelf($currentUserApi, $variableApi, $user, $originalGroups);
182
183
                $formDataEvent = new UserFormDataEvent($user, $form);
184
                $eventDispatcher->dispatch($formDataEvent, UserEvents::EDIT_FORM_HANDLE);
185
186
                $this->getDoctrine()->getManager()->flush();
187
188
                $eventArgs = [
189
                    'action'    => 'setVar',
190
                    'field'     => 'uname',
191
                    'attribute' => null,
192
                ];
193
                $eventData = ['old_value' => $originalUserName];
194
                $updateEvent = new GenericEvent($user, $eventArgs, $eventData);
195
                $eventDispatcher->dispatch($updateEvent, UserEvents::UPDATE_ACCOUNT);
196
197
                $hookDispatcher->dispatch(UserManagementUiHooksSubscriber::EDIT_PROCESS, new ProcessHook($user->getUid()));
198
199
                $this->addFlash('status', "Done! Saved user's account information.");
200
            } elseif ($form->get('cancel')->isClicked()) {
201
                $this->addFlash('status', 'Operation cancelled.');
202
            }
203
204
            return $this->redirectToRoute('zikulausersmodule_useradministration_list');
205
        }
206
207
        return [
208
            'form' => $form->createView(),
209
            'additional_templates' => isset($formEvent) ? $formEvent->getTemplates() : []
210
        ];
211
    }
212
213
    /**
214
     * @Route("/approve/{user}/{force}", requirements={"user" = "^[1-9]\d*$"})
215
     * @PermissionCheck("moderate")
216
     * @Theme("admin")
217
     * @Template("@ZikulaUsersModule/UserAdministration/approve.html.twig")
218
     *
219
     * @return array|RedirectResponse
220
     */
221
    public function approveAction(
222
        Request $request,
223
        UserEntity $user,
224
        RegistrationHelper $registrationHelper,
225
        MailHelper $mailHelper,
226
        bool $force = false
227
    ) {
228
        $forceVerification = $this->hasPermission('ZikulaUsersModule', '::', ACCESS_ADMIN) && $force;
229
        $form = $this->createForm(ApproveRegistrationConfirmationType::class, [
230
            'user' => $user->getUid(),
231
            'force' => $forceVerification
232
        ], [
233
            'buttonLabel' => $this->trans('Approve')
234
        ]);
235
        $redirectToRoute = 'zikulausersmodule_useradministration_list';
236
237
        if (!$forceVerification) {
238
            if ($user->isApproved()) {
239
                $this->addFlash('error', $this->trans('Warning! Nothing to do! %sub% is already approved.', ['%sub%' => $user->getUname()]));
240
241
                return $this->redirectToRoute($redirectToRoute);
242
            }
243
            if (!$user->isApproved() && !$this->hasPermission('ZikulaUsersModule::', '::', ACCESS_ADMIN)) {
244
                $this->addFlash('error', $this->trans('Error! %sub% cannot be approved.', ['%sub%' => $user->getUname()]));
245
246
                return $this->redirectToRoute($redirectToRoute);
247
            }
248
        }
249
250
        $form->handleRequest($request);
251
        if ($form->isSubmitted() && $form->isValid()) {
252
            if ($form->get('confirm')->isClicked()) {
253
                $registrationHelper->approve($user);
254
                if (UsersConstant::ACTIVATED_PENDING_REG === $user->getActivated()) {
255
                    $notificationErrors = $mailHelper->createAndSendRegistrationMail($user, true, false);
256
                } else {
257
                    $notificationErrors = $mailHelper->createAndSendUserMail($user, true, false);
258
                }
259
260
                if ($notificationErrors) {
261
                    $this->addFlash('error', implode('<br />', $notificationErrors));
262
                }
263
                $this->addFlash('status', $this->trans('Done! %sub% has been approved.', ['%sub%' => $user->getUname()]));
264
            } elseif ($form->get('cancel')->isClicked()) {
265
                $this->addFlash('status', 'Operation cancelled.');
266
            }
267
268
            return $this->redirectToRoute($redirectToRoute);
269
        }
270
271
        return [
272
            'form' => $form->createView(),
273
            'user' => $user
274
        ];
275
    }
276
277
    /**
278
     * @Route("/delete/{user}", requirements={"user" = "^[1-9]\d*$"})
279
     * @PermissionCheck("delete")
280
     * @Theme("admin")
281
     * @Template("@ZikulaUsersModule/UserAdministration/delete.html.twig")
282
     *
283
     * @return array|RedirectResponse
284
     */
285
    public function deleteAction(
286
        Request $request,
287
        CurrentUserApiInterface $currentUserApi,
288
        UserRepositoryInterface $userRepository,
289
        HookDispatcherInterface $hookDispatcher,
290
        EventDispatcherInterface $eventDispatcher,
291
        UserEntity $user = null
292
    ) {
293
        $uids = [];
294
        if (!isset($user) && 'POST' === $request->getMethod() && $request->request->has('zikulausersmodule_delete')) {
295
            $uids = $request->request->get('zikulausersmodule_delete')['users'];
296
        } elseif (isset($user)) {
297
            $uids = [$user->getUid()];
298
        }
299
        $usersImploded = implode(',', $uids);
300
301
        $deleteConfirmationForm = $this->createForm(DeleteConfirmationType::class, [
302
            'users' => $usersImploded
303
        ]);
304
        $deleteConfirmationForm->handleRequest($request);
305
        if (empty($uids) && !$deleteConfirmationForm->isSubmitted()) {
306
            throw new InvalidArgumentException($this->trans('No users selected.'));
307
        }
308
        if ($deleteConfirmationForm->isSubmitted()) {
309
            if ($deleteConfirmationForm->get('cancel')->isClicked()) {
310
                $this->addFlash('success', 'Operation cancelled.');
311
312
                return $this->redirectToRoute('zikulausersmodule_useradministration_list');
313
            }
314
            $userIdsImploded = $deleteConfirmationForm->get('users')->getData();
315
            $userIds = explode(',', $userIdsImploded);
316
            $valid = true;
317
            foreach ($userIds as $k => $uid) {
318
                if (in_array($uid, [UsersConstant::USER_ID_ANONYMOUS, UsersConstant::USER_ID_ADMIN, $currentUserApi->get('uid')], true)) {
319
                    unset($userIds[$k]);
320
                    $this->addFlash('danger', $this->trans('You are not allowed to delete user id %uid%', ['%uid%' => $uid]));
321
                    continue;
322
                }
323
                $event = new GenericEvent(null, ['id' => $uid], new ValidationProviders());
324
                $validators = $eventDispatcher->dispatch($event, UserEvents::DELETE_VALIDATE)->getData();
325
                $hook = new ValidationHook($validators);
326
                $hookDispatcher->dispatch(UserManagementUiHooksSubscriber::DELETE_VALIDATE, $hook);
327
                $validators = $hook->getValidators();
328
                if ($validators->hasErrors()) {
329
                    $valid = false;
330
                }
331
            }
332
            if ($valid && $deleteConfirmationForm->isValid()) {
333
                // send email to 'denied' registrations. see MailHelper::sendNotification (regdeny) #2915
334
                $deletedUsers = $userRepository->query(['uid' => ['operator' => 'in', 'operand' => $userIds]]);
335
                foreach ($deletedUsers as $deletedUser) {
336
                    $eventName = UsersConstant::ACTIVATED_ACTIVE === $deletedUser->getActivated() ? UserEvents::DELETE_ACCOUNT : RegistrationEvents::DELETE_REGISTRATION;
337
                    $eventDispatcher->dispatch(new GenericEvent($deletedUser->getUid()), $eventName);
338
                    $eventDispatcher->dispatch(new GenericEvent(null, ['id' => $deletedUser->getUid()]), UserEvents::DELETE_PROCESS);
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
        ];
360
    }
361
362
    /**
363
     * @Route("/search")
364
     * @PermissionCheck("moderate")
365
     * @Theme("admin")
366
     * @Template("@ZikulaUsersModule/UserAdministration/search.html.twig")
367
     *
368
     * @return array|Response
369
     */
370
    public function searchAction(
371
        Request $request,
372
        UserRepositoryInterface $userRepository,
373
        VariableApiInterface $variableApi
374
    ) {
375
        $form = $this->createForm(SearchUserType::class, []);
376
        $form->handleRequest($request);
377
        if ($form->isSubmitted()) {
378
            $resultsForm = $this->createForm(DeleteType::class, [], [
379
                '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

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