Passed
Push — master ( 25a010...d9c103 )
by
unknown
14:59
created

BackendUserController::initializeAction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Beuser\Controller;
17
18
use Psr\Http\Message\ResponseInterface;
19
use Psr\Http\Message\ServerRequestInterface;
20
use TYPO3\CMS\Backend\Authentication\Event\SwitchUserEvent;
21
use TYPO3\CMS\Backend\Authentication\PasswordReset;
22
use TYPO3\CMS\Backend\Routing\UriBuilder;
23
use TYPO3\CMS\Backend\Utility\BackendUtility;
24
use TYPO3\CMS\Beuser\Domain\Model\BackendUser;
25
use TYPO3\CMS\Beuser\Domain\Model\Demand;
26
use TYPO3\CMS\Beuser\Domain\Model\ModuleData;
27
use TYPO3\CMS\Beuser\Domain\Repository\BackendUserGroupRepository;
28
use TYPO3\CMS\Beuser\Domain\Repository\BackendUserRepository;
29
use TYPO3\CMS\Beuser\Domain\Repository\BackendUserSessionRepository;
30
use TYPO3\CMS\Beuser\Service\UserInformationService;
31
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
32
use TYPO3\CMS\Core\Context\Context;
33
use TYPO3\CMS\Core\Http\PropagateResponseException;
34
use TYPO3\CMS\Core\Http\RedirectResponse;
35
use TYPO3\CMS\Core\Messaging\FlashMessage;
36
use TYPO3\CMS\Core\Pagination\SimplePagination;
37
use TYPO3\CMS\Core\Utility\GeneralUtility;
38
use TYPO3\CMS\Extbase\Http\ForwardResponse;
39
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
40
use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
41
use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
42
use TYPO3\CMS\Extbase\Pagination\QueryResultPaginator;
43
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
44
45
/**
46
 * Backend module user administration controller
47
 * @internal This class is a TYPO3 Backend implementation and is not considered part of the Public TYPO3 API.
48
 */
49
class BackendUserController extends ActionController
50
{
51
    /**
52
     * @var int
53
     */
54
    const RECENT_USERS_LIMIT = 3;
55
56
    protected ?ModuleData $moduleData = null;
57
    protected BackendUserRepository $backendUserRepository;
58
    protected BackendUserGroupRepository $backendUserGroupRepository;
59
    protected BackendUserSessionRepository $backendUserSessionRepository;
60
    protected UserInformationService $userInformationService;
61
62
    public function __construct(
63
        BackendUserRepository $backendUserRepository,
64
        BackendUserGroupRepository $backendUserGroupRepository,
65
        BackendUserSessionRepository $backendUserSessionRepository,
66
        UserInformationService $userInformationService
67
    ) {
68
        $this->backendUserRepository = $backendUserRepository;
69
        $this->backendUserGroupRepository = $backendUserGroupRepository;
70
        $this->backendUserSessionRepository = $backendUserSessionRepository;
71
        $this->userInformationService = $userInformationService;
72
    }
73
74
    /**
75
     * Init module state.
76
     * This isn't done within __construct() since the controller
77
     * object is only created once in extbase when multiple actions are called in
78
     * one call. When those change module state, the second action would see old state.
79
     */
80
    public function initializeAction(): void
81
    {
82
        $this->moduleData = ModuleData::fromUc((array)($this->getBackendUser()->getModuleData('tx_beuser')));
83
    }
84
85
    /**
86
     * Assign default variables to view
87
     * @param ViewInterface $view
88
     */
89
    protected function initializeView(ViewInterface $view): void
90
    {
91
        $view->assignMultiple([
92
            'shortcutLabel' => 'backendUsers',
93
            'route' => $this->getRequest()->getAttribute('route')->getPath(),
94
            'action' => $this->request->getControllerActionName(),
95
            'controller' => $this->request->getControllerName(),
96
            'dateFormat' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'],
97
            'timeFormat' => $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'],
98
        ]);
99
    }
100
101
    /**
102
     * Displays all BackendUsers
103
     * - Switch session to different user
104
     *
105
     * @param Demand|null $demand
106
     * @param int $currentPage
107
     * @param string $operation
108
     * @return ResponseInterface
109
     */
110
    public function indexAction(Demand $demand = null, int $currentPage = 1, string $operation = ''): ResponseInterface
111
    {
112
        $backendUser = $this->getBackendUser();
113
114
        if ($operation === 'reset-filters') {
115
            // Reset the module data demand object
116
            $this->moduleData->setDemand(new Demand());
0 ignored issues
show
Bug introduced by
The method setDemand() does not exist on null. ( Ignorable by Annotation )

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

116
            $this->moduleData->/** @scrutinizer ignore-call */ 
117
                               setDemand(new Demand());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
117
            $demand = null;
118
        }
119
        if ($demand === null) {
120
            $demand = $this->moduleData->getDemand();
121
        } else {
122
            $this->moduleData->setDemand($demand);
123
        }
124
        $backendUser->pushModuleData('tx_beuser', $this->moduleData->forUc());
125
126
        // Switch user until logout
127
        $switchUser = (int)GeneralUtility::_GP('SwitchUser');
128
        if ($switchUser > 0) {
129
            $this->switchUser($switchUser);
130
        }
131
        $compareUserList = $this->moduleData->getCompareUserList();
132
133
        $backendUsers = $this->backendUserRepository->findDemanded($demand);
134
        $paginator = new QueryResultPaginator($backendUsers, $currentPage, 50);
135
        $pagination = new SimplePagination($paginator);
136
137
        $this->view->assignMultiple([
138
            'onlineBackendUsers' => $this->getOnlineBackendUsers(),
139
            'demand' => $demand,
140
            'paginator' => $paginator,
141
            'pagination' => $pagination,
142
            'totalAmountOfBackendUsers' => $backendUsers->count(),
143
            'backendUserGroups' => array_merge([''], $this->backendUserGroupRepository->findAll()->toArray()),
144
            'compareUserUidList' => array_combine($compareUserList, $compareUserList),
145
            'currentUserUid' => $backendUser->user['uid'],
146
            'compareUserList' => !empty($compareUserList) ? $this->backendUserRepository->findByUidList($compareUserList) : '',
147
        ]);
148
149
        return $this->htmlResponse($this->view->render());
150
    }
151
152
    /**
153
     * Views all currently logged in BackendUsers and their sessions
154
     */
155
    public function onlineAction(): ResponseInterface
156
    {
157
        $onlineUsersAndSessions = [];
158
        $onlineUsers = $this->backendUserRepository->findOnline();
159
        foreach ($onlineUsers as $onlineUser) {
160
            $onlineUsersAndSessions[] = [
161
                'backendUser' => $onlineUser,
162
                'sessions' => $this->backendUserSessionRepository->findByBackendUser($onlineUser)
163
            ];
164
        }
165
166
        $currentSessionId = $this->backendUserSessionRepository->getPersistedSessionIdentifier($this->getBackendUser());
167
168
        $this->view->assignMultiple([
169
            'shortcutLabel' => 'onlineUsers',
170
            'onlineUsersAndSessions' => $onlineUsersAndSessions,
171
            'currentSessionId' => $currentSessionId,
172
        ]);
173
174
        return $this->htmlResponse($this->view->render());
175
    }
176
177
    public function showAction(int $uid = 0): ResponseInterface
178
    {
179
        $data = $this->userInformationService->getUserInformation($uid);
180
        $this->view->assignMultiple([
181
            'shortcutLabel' => 'showUser',
182
            'data' => $data
183
        ]);
184
185
        return $this->htmlResponse($this->view->render());
186
    }
187
188
    /**
189
     * Compare backend users from demand
190
     */
191
    public function compareAction(): ResponseInterface
192
    {
193
        $compareUserList = $this->moduleData->getCompareUserList();
194
        if (empty($compareUserList)) {
195
            $this->redirect('index');
196
        }
197
198
        $compareData = [];
199
        foreach ($compareUserList as $uid) {
200
            if ($compareInformation = $this->userInformationService->getUserInformation($uid)) {
201
                $compareData[] = $compareInformation;
202
            }
203
        }
204
205
        $this->view->assignMultiple([
206
            'shortcutLabel' => 'compareUsers',
207
            'compareUserList' => $compareData,
208
            'onlineBackendUsers' => $this->getOnlineBackendUsers()
209
        ]);
210
211
        return $this->htmlResponse($this->view->render());
212
    }
213
214
    /**
215
     * Starts the password reset process for a selected user.
216
     *
217
     * @param int $user
218
     */
219
    public function initiatePasswordResetAction(int $user): ResponseInterface
220
    {
221
        $context = GeneralUtility::makeInstance(Context::class);
222
        /** @var BackendUser $user */
223
        $user = $this->backendUserRepository->findByUid($user);
0 ignored issues
show
Bug introduced by
$user of type TYPO3\CMS\Beuser\Domain\Model\BackendUser is incompatible with the type integer expected by parameter $uid of TYPO3\CMS\Extbase\Persis...Repository::findByUid(). ( Ignorable by Annotation )

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

223
        $user = $this->backendUserRepository->findByUid(/** @scrutinizer ignore-type */ $user);
Loading history...
224
        if (!$user || !$user->isPasswordResetEnabled() || !$context->getAspect('backend.user')->isAdmin()) {
0 ignored issues
show
introduced by
$user is of type TYPO3\CMS\Beuser\Domain\Model\BackendUser, thus it always evaluated to true.
Loading history...
225
            // Add an error message
226
            $this->addFlashMessage(
227
                LocalizationUtility::translate('LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:flashMessage.resetPassword.error.text', 'beuser') ?? '',
228
                LocalizationUtility::translate('LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:flashMessage.resetPassword.error.title', 'beuser') ?? '',
229
                FlashMessage::ERROR
230
            );
231
        } else {
232
            GeneralUtility::makeInstance(PasswordReset::class)->initiateReset(
233
                $this->getRequest(),
234
                $context,
235
                $user->getEmail()
236
            );
237
            $this->addFlashMessage(
238
                LocalizationUtility::translate('LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:flashMessage.resetPassword.success.text', 'beuser', [$user->getEmail()]) ?? '',
239
                LocalizationUtility::translate('LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:flashMessage.resetPassword.success.title', 'beuser') ?? '',
240
                FlashMessage::OK
241
            );
242
        }
243
        return new ForwardResponse('index');
244
    }
245
246
    /**
247
     * Attaches one backend user to the compare list
248
     *
249
     * @param int $uid
250
     */
251
    public function addToCompareListAction($uid)
252
    {
253
        $this->moduleData->attachUidCompareUser($uid);
254
        $this->getBackendUser()->pushModuleData('tx_beuser', $this->moduleData->forUc());
255
        return new ForwardResponse('index');
256
    }
257
258
    /**
259
     * Removes given backend user to the compare list
260
     *
261
     * @param int $uid
262
     * @param int $redirectToCompare
263
     */
264
    public function removeFromCompareListAction($uid, int $redirectToCompare = 0)
265
    {
266
        $this->moduleData->detachUidCompareUser($uid);
267
        $this->getBackendUser()->pushModuleData('tx_beuser', $this->moduleData->forUc());
268
        if ($redirectToCompare) {
269
            $this->redirect('compare');
270
        } else {
271
            $this->redirect('index');
272
        }
273
    }
274
275
    /**
276
     * Removes all backend users from the compare list
277
     * @throws StopActionException
278
     */
279
    public function removeAllFromCompareListAction(): void
280
    {
281
        $this->moduleData->resetCompareUserList();
282
        $this->getBackendUser()->pushModuleData('tx_beuser', $this->moduleData->forUc());
283
        $this->redirect('index');
284
    }
285
286
    /**
287
     * Terminate BackendUser session and logout corresponding client
288
     * Redirects to onlineAction with message
289
     *
290
     * @param BackendUser $backendUser
291
     * @param string $sessionId
292
     */
293
    protected function terminateBackendUserSessionAction(BackendUser $backendUser, $sessionId)
294
    {
295
        // terminating value of persisted session ID
296
        $success = $this->backendUserSessionRepository->terminateSessionByIdentifier($sessionId);
297
        if ($success) {
298
            $this->addFlashMessage(LocalizationUtility::translate('LLL:EXT:beuser/Resources/Private/Language/locallang.xlf:terminateSessionSuccess', 'beuser') ?? '');
299
        }
300
        return new ForwardResponse('online');
301
    }
302
303
    /**
304
     * Switches to a given user (SU-mode) and then redirects to the start page of the backend to refresh the navigation etc.
305
     *
306
     * @param int $switchUser BE-user record that will be switched to
307
     */
308
    protected function switchUser($switchUser)
309
    {
310
        $backendUser = $this->getBackendUser();
311
        $targetUser = BackendUtility::getRecord('be_users', $switchUser);
312
        if (is_array($targetUser) && $backendUser->isAdmin()) {
313
            // Set backend user listing module as starting module for switchback
314
            $backendUser->uc['startModuleOnFirstLogin'] = 'system_BeuserTxBeuser';
315
            $backendUser->uc['recentSwitchedToUsers'] = $this->generateListOfMostRecentSwitchedUsers($targetUser['uid']);
316
            $backendUser->writeUC();
317
318
            // User switch   written to log
319
            $backendUser->writelog(
320
                255,
321
                2,
322
                0,
323
                1,
324
                'User %s switched to user %s (be_users:%s)',
325
                [
326
                    $backendUser->user['username'],
327
                    $targetUser['username'],
328
                    $targetUser['uid'],
329
                ]
330
            );
331
332
            $this->backendUserSessionRepository->switchToUser($backendUser, (int)$targetUser['uid']);
333
334
            $event = new SwitchUserEvent(
335
                $backendUser->getSession()->getIdentifier(),
336
                $targetUser,
337
                (array)$backendUser->user
338
            );
339
            $this->eventDispatcher->dispatch($event);
340
341
            $redirectUri = GeneralUtility::makeInstance(UriBuilder::class)->buildUriFromRoute(
342
                'main',
343
                $GLOBALS['TYPO3_CONF_VARS']['BE']['interfaces'] ? [] : ['commandLI' => '1']
344
            );
345
            throw new PropagateResponseException(new RedirectResponse($redirectUri, 303), 1607271592);
346
        }
347
    }
348
349
    /**
350
     * Generates a list of users to whom where switched in the past. This is limited by RECENT_USERS_LIMIT.
351
     *
352
     * @param int $targetUserUid
353
     * @return int[]
354
     */
355
    protected function generateListOfMostRecentSwitchedUsers(int $targetUserUid): array
356
    {
357
        $latestUserUids = [];
358
        $backendUser = $this->getBackendUser();
359
360
        if (isset($backendUser->uc['recentSwitchedToUsers']) && is_array($backendUser->uc['recentSwitchedToUsers'])) {
361
            $latestUserUids = $backendUser->uc['recentSwitchedToUsers'];
362
        }
363
364
        // Remove potentially existing user in that list
365
        $index = array_search($targetUserUid, $latestUserUids, true);
366
        if ($index !== false) {
367
            unset($latestUserUids[$index]);
368
        }
369
370
        array_unshift($latestUserUids, $targetUserUid);
371
        $latestUserUids = array_slice($latestUserUids, 0, static::RECENT_USERS_LIMIT);
372
373
        return $latestUserUids;
374
    }
375
376
    /**
377
     * @return BackendUserAuthentication
378
     */
379
    protected function getBackendUser(): BackendUserAuthentication
380
    {
381
        return $GLOBALS['BE_USER'];
382
    }
383
384
    /**
385
     * @return ServerRequestInterface
386
     */
387
    protected function getRequest(): ServerRequestInterface
388
    {
389
        return $GLOBALS['TYPO3_REQUEST'];
390
    }
391
392
    /**
393
     * Create an array with the uids of online users as the keys
394
     * [
395
     *   1 => true,
396
     *   5 => true
397
     * ]
398
     * @return array
399
     */
400
    protected function getOnlineBackendUsers(): array
401
    {
402
        $onlineUsers = $this->backendUserSessionRepository->findAllActive();
403
        $onlineBackendUsers = [];
404
        foreach ($onlineUsers as $onlineUser) {
405
            $onlineBackendUsers[$onlineUser['ses_userid']] = true;
406
        }
407
        return $onlineBackendUsers;
408
    }
409
}
410