‪TYPO3CMS  ‪main
SetupModuleController.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\EventDispatcher\EventDispatcherInterface;
21 use Psr\Http\Message\ResponseInterface;
22 use Psr\Http\Message\ServerRequestInterface;
28 use TYPO3\CMS\Backend\Utility\BackendUtility;
42 use TYPO3\CMS\Core\Page\PageRenderer;
49 use ‪TYPO3\CMS\Core\SysLog\Action\Setting as SystemLogSettingAction;
50 use ‪TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
51 use ‪TYPO3\CMS\Core\SysLog\Type as SystemLogType;
55 
62 {
63  protected const ‪PASSWORD_NOT_UPDATED = 0;
64  protected const ‪PASSWORD_UPDATED = 1;
65  protected const ‪PASSWORD_NOT_THE_SAME = 2;
66  protected const ‪PASSWORD_OLD_WRONG = 3;
67  protected const ‪PASSWORD_POLICY_FAILED = 4;
68 
69  protected array ‪$overrideConf = [];
70  protected bool ‪$languageUpdate = false;
71  protected bool ‪$pagetreeNeedsRefresh = false;
72  protected array ‪$tsFieldConf = [];
74  protected bool ‪$passwordIsSubmitted = false;
75  protected bool ‪$setupIsUpdated = false;
76  protected bool ‪$settingsAreResetToDefault = false;
77 
79 
80  public function ‪__construct(
81  protected readonly EventDispatcherInterface $eventDispatcher,
82  protected readonly ‪MfaProviderRegistry $mfaProviderRegistry,
83  protected readonly ‪IconFactory $iconFactory,
84  protected readonly PageRenderer $pageRenderer,
85  protected readonly ‪ModuleTemplateFactory $moduleTemplateFactory,
86  protected readonly ‪LanguageServiceFactory $languageServiceFactory,
87  protected readonly ‪ModuleProvider $moduleProvider,
88  protected readonly ‪UriBuilder $uriBuilder,
89  protected readonly ‪FormProtectionFactory $formProtectionFactory
90  ) {
91  $passwordPolicy = ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['passwordPolicy'] ?? 'default';
92 
93  $action = PasswordPolicyAction::UPDATE_USER_PASSWORD;
94  if ($this->‪getBackendUser()->getOriginalUserIdWhenInSwitchUserMode()) {
95  $action = PasswordPolicyAction::UPDATE_USER_PASSWORD_SWITCH_USER_MODE;
96  }
97 
98  $this->passwordPolicyValidator = GeneralUtility::makeInstance(
99  PasswordPolicyValidator::class,
100  $action,
101  is_string($passwordPolicy) ? $passwordPolicy : ''
102  );
103  }
104 
108  public function ‪mainAction(ServerRequestInterface $request): ResponseInterface
109  {
110  $view = $this->‪initialize($request);
111  $this->‪storeIncomingData($request);
112  if ($this->pagetreeNeedsRefresh) {
113  BackendUtility::setUpdateSignal('updatePageTree');
114  }
115  $formProtection = $this->formProtectionFactory->createFromRequest($request);
116  $this->‪addFlashMessages($view);
117  $this->‪getButtons($view);
118  $view->assignMultiple([
119  'isLanguageUpdate' => $this->languageUpdate,
120  'menuItems' => $this->‪renderUserSetup(),
121  'menuId' => 'DTM-375167ed176e8c9caf4809cee7df156c',
122  'formToken' => $formProtection->generateToken('BE user setup', 'edit'),
123  ]);
124  return $view->renderResponse('Main');
125  }
126 
130  protected function ‪initialize(ServerRequestInterface $request): ‪ModuleTemplate
131  {
132  $languageService = $this->‪getLanguageService();
133  $backendUser = $this->‪getBackendUser();
134  $view = $this->moduleTemplateFactory->create($request);
135  $this->pageRenderer->loadJavaScriptModule('@typo3/backend/modal.js');
136  $this->pageRenderer->loadJavaScriptModule('@typo3/backend/form-engine.js');
137  $this->pageRenderer->loadJavaScriptModule('@typo3/setup/setup-module.js');
139  $this->pageRenderer->addInlineSetting('FormEngine', 'formName', 'editform');
140  $this->pageRenderer->addInlineLanguageLabelArray([
141  'FormEngine.remainingCharacters' => $languageService->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.remainingCharacters'),
142  ]);
143  $languageService->includeLLFile('EXT:setup/Resources/Private/Language/locallang.xlf');
144  $view->setTitle($languageService->getLL('UserSettings'));
145  // Getting the 'override' values as set might be set in user TSconfig
146  $this->overrideConf = $backendUser->getTSConfig()['setup.']['override.'] ?? [];
147  // Getting the disabled fields might be set in user TSconfig (eg setup.fields.password.disabled=1)
148  $this->tsFieldConf = $backendUser->getTSConfig()['setup.']['fields.'] ?? [];
149  // if password is disabled, disable repeat of password too (password2)
150  if ($this->tsFieldConf['password.']['disabled'] ?? false) {
151  $this->tsFieldConf['password2.']['disabled'] = 1;
152  $this->tsFieldConf['passwordCurrent.']['disabled'] = 1;
153  }
154  return $view;
155  }
156 
157  protected function ‪processAdditionalJavaScriptModules(): void
158  {
159  $event = new ‪AddJavaScriptModulesEvent();
160  $event = $this->eventDispatcher->dispatch($event);
161  foreach ($event->getJavaScriptModules() as $specifier) {
162  $this->pageRenderer->loadJavaScriptModule($specifier);
163  }
164  foreach ($event->getModules() as $moduleName) {
165  // The deprecation is added in AddJavaScriptModulesEvent::addModule, and therefore silenced here.
166  $this->pageRenderer->loadRequireJsModule($moduleName, null, true);
167  }
168  }
169 
173  protected function ‪storeIncomingData(ServerRequestInterface $request): void
174  {
175  $postData = $request->getParsedBody();
176  if (!is_array($postData) || empty($postData)) {
177  return;
178  }
179 
180  $formProtection = $this->formProtectionFactory->createFromRequest($request);
181  // First check if something is submitted in the data-array from POST vars
182  $d = $postData['data'] ?? null;
183  $columns = ‪$GLOBALS['TYPO3_USER_SETTINGS']['columns'];
184  $backendUser = $this->‪getBackendUser();
185  $beUserId = $backendUser->user['uid'];
186  $storeRec = [];
187  $doSaveData = false;
188  $fieldList = $this->‪getFieldsFromShowItem();
189  if (is_array($d) && $formProtection->validateToken((string)($postData['formToken'] ?? ''), 'BE user setup', 'edit')) {
190  // UC hashed before applying changes
191  $save_before = md5(serialize($backendUser->uc));
192  // PUT SETTINGS into the ->uc array:
193  // Reload left frame when switching BE language
194  if (isset($d['be_users']['lang']) && $d['be_users']['lang'] !== $backendUser->user['lang']) {
195  $this->languageUpdate = true;
196  }
197  // Reload pagetree if the title length is changed
198  if (isset($d['titleLen']) && $d['titleLen'] !== $backendUser->uc['titleLen']) {
199  $this->pagetreeNeedsRefresh = true;
200  }
201  if ($d['setValuesToDefault']) {
202  // If every value should be default
203  $backendUser->resetUC();
204  $this->settingsAreResetToDefault = true;
205  } elseif ($d['save']) {
206  // Save all submitted values if they are no array (arrays are with table=be_users) and exists in $GLOBALS['TYPO3_USER_SETTINGS'][columns]
207  foreach ($columns as $field => $config) {
208  if (!in_array($field, $fieldList, true)) {
209  continue;
210  }
211  if (($config['table'] ?? '') === 'be_users' && !in_array($field, ['password', 'password2', 'passwordCurrent', 'email', 'realName', 'admin', 'avatar'], true)) {
212  if (!isset($config['access']) || $this->‪checkAccess($config) && ($backendUser->user[$field] !== $d['be_users'][$field])) {
213  if (($config['type'] ?? false) === 'check') {
214  $fieldValue = isset($d['be_users'][$field]) ? 1 : 0;
215  } else {
216  $fieldValue = $d['be_users'][$field];
217  }
218  $storeRec['be_users'][$beUserId][$field] = $fieldValue;
219  $backendUser->user[$field] = $fieldValue;
220  }
221  }
222  if (($config['type'] ?? false) === 'check') {
223  $backendUser->uc[$field] = isset($d[$field]) ? 1 : 0;
224  } else {
225  $backendUser->uc[$field] = htmlspecialchars($d[$field] ?? '');
226  }
227  }
228  // Personal data for the users be_user-record (email, name, password...)
229  // If email and name is changed, set it in the users record:
230  $be_user_data = $d['be_users'];
231  // Possibility to modify the transmitted values. Useful to do transformations, like RSA password decryption
232  foreach (‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/setup/mod/index.php']['modifyUserDataBeforeSave'] ?? [] as $function) {
233  $params = ['be_user_data' => &$be_user_data];
234  GeneralUtility::callUserFunction($function, $params, $this);
235  }
236  $this->passwordIsSubmitted = (string)$be_user_data['password'] !== '';
237  $passwordIsConfirmed = $this->passwordIsSubmitted && $be_user_data['password'] === $be_user_data['password2'];
238 
239  // Validate password against password policy
240  $contextData = new ‪ContextData(
241  loginMode: 'BE',
242  currentPasswordHash: $this->‪getBackendUser()->user['password'],
243  newUserFullName: $be_user_data['realName']
244  );
245  $contextData->setData('currentUsername', $this->‪getBackendUser()->user['username']);
246  $event = $this->eventDispatcher->dispatch(
248  $contextData,
249  $be_user_data,
250  self::class
251  )
252  );
253  $contextData = $event->getContextData();
254 
255  $passwordValid = true;
256  if ($passwordIsConfirmed &&
257  !$this->passwordPolicyValidator->isValidPassword($be_user_data['password'], $contextData)
258  ) {
259  $passwordValid = false;
260  $this->passwordIsUpdated = ‪self::PASSWORD_POLICY_FAILED;
261  }
262 
263  // Update the real name:
264  if (isset($be_user_data['realName']) && $be_user_data['realName'] !== $backendUser->user['realName']) {
265  $backendUser->user['realName'] = ($storeRec['be_users'][$beUserId]['realName'] = substr($be_user_data['realName'], 0, 80));
266  }
267  // Update the email address:
268  if (isset($be_user_data['email']) && $be_user_data['email'] !== $backendUser->user['email']) {
269  $backendUser->user['email'] = ($storeRec['be_users'][$beUserId]['email'] = substr($be_user_data['email'], 0, 255));
270  }
271  // Update the password:
272  if ($passwordIsConfirmed && $passwordValid) {
273  if ($backendUser->isAdmin()) {
274  $passwordOk = true;
275  } else {
276  $currentPasswordHashed = $backendUser->user['password'];
277  $passwordOk = false;
278  $saltFactory = GeneralUtility::makeInstance(PasswordHashFactory::class);
279  try {
280  $hashInstance = $saltFactory->get($currentPasswordHashed, 'BE');
281  $passwordOk = $hashInstance->checkPassword($be_user_data['passwordCurrent'], $currentPasswordHashed);
282  } catch (‪InvalidPasswordHashException $e) {
283  // Could not find hash class responsible for existing password. This is a
284  // misconfiguration and user can not change its password.
285  }
286  }
287  if ($passwordOk) {
288  $this->passwordIsUpdated = ‪self::PASSWORD_UPDATED;
289  $storeRec['be_users'][$beUserId]['password'] = $be_user_data['password'];
290  } else {
291  $this->passwordIsUpdated = ‪self::PASSWORD_OLD_WRONG;
292  }
293  } elseif ($passwordIsConfirmed) {
294  $this->passwordIsUpdated = ‪self::PASSWORD_POLICY_FAILED;
295  } else {
296  $this->passwordIsUpdated = ‪self::PASSWORD_NOT_THE_SAME;
297  }
298 
299  $this->‪setAvatarFileUid($beUserId, $be_user_data['avatar'], $storeRec);
300 
301  $doSaveData = true;
302  }
303  // Inserts the overriding values.
304  $backendUser->overrideUC();
305  $save_after = md5(serialize($backendUser->uc));
306  // If something in the uc-array of the user has changed, we save the array...
307  if ($save_before != $save_after) {
308  $backendUser->writeUC();
309  $backendUser->writelog(SystemLogType::SETTING, SystemLogSettingAction::CHANGE, SystemLogErrorClassification::MESSAGE, 1, 'Personal settings changed', []);
310  $this->setupIsUpdated = true;
311  }
312  // Persist data if something has changed:
313  if (!empty($storeRec) && $doSaveData) {
314  // Set user to admin to circumvent DataHandler restrictions.
315  // Not using isAdmin() to fetch the original value, just in case it has been boolean casted.
316  $savedUserAdminState = $backendUser->user['admin'];
317  $backendUser->user['admin'] = true;
318  // Make dedicated instance of TCE for storing the changes.
319  $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
320  $dataHandler->start($storeRec, [], $backendUser);
321  // This is to make sure that the users record can be updated even if in another workspace. This is tolerated.
322  $dataHandler->bypassWorkspaceRestrictions = true;
323  $dataHandler->process_datamap();
324  // reset the user record admin flag to previous value, just in case it gets used any further.
325  $backendUser->user['admin'] = $savedUserAdminState;
326  if ($this->passwordIsUpdated === self::PASSWORD_NOT_UPDATED || count($storeRec['be_users'][$beUserId]) > 1) {
327  $this->setupIsUpdated = true;
328  }
329  BackendUtility::setUpdateSignal('updateTopbar');
330  }
331  }
332  }
333 
337  protected function ‪getButtons(‪ModuleTemplate $view): void
338  {
339  $buttonBar = $view->‪getDocHeaderComponent()->getButtonBar();
340 
341  $saveButton = $buttonBar->makeInputButton()
342  ->setName('data[save]')
343  ->setTitle($this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.saveDoc'))
344  ->setValue('1')
345  ->setForm('SetupModuleController')
346  ->setShowLabelText(true)
347  ->setIcon($this->iconFactory->getIcon('actions-document-save', ‪Icon::SIZE_SMALL));
348 
349  $buttonBar->addButton($saveButton);
350  $shortcutButton = $buttonBar->makeShortcutButton()
351  ->setRouteIdentifier('user_setup')
352  ->setDisplayName($this->‪getLanguageService()->sL('LLL:EXT:setup/Resources/Private/Language/locallang_mod.xlf:mlang_labels_tablabel'));
353  $buttonBar->addButton($shortcutButton);
354  }
355 
362  protected function ‪renderUserSetup(): array
363  {
364  $backendUser = $this->‪getBackendUser();
365  $html = '';
366  $result = [];
367  $firstTabLabel = '';
368  $code = [];
369  $fieldArray = $this->‪getFieldsFromShowItem();
370  $tabLabel = '';
371  foreach ($fieldArray as $fieldName) {
372  if (str_starts_with($fieldName, '--div--;')) {
373  if ($firstTabLabel === '') {
374  // First tab
375  $tabLabel = $this->‪getLabel(substr($fieldName, 8), '', false);
376  $firstTabLabel = $tabLabel;
377  } else {
378  $result[] = [
379  'label' => $tabLabel,
380  'content' => count($code) ? implode(LF, $code) : '',
381  ];
382  $tabLabel = $this->‪getLabel(substr($fieldName, 8), '', false);
383  $code = [];
384  }
385  continue;
386  }
387 
388  $config = ‪$GLOBALS['TYPO3_USER_SETTINGS']['columns'][$fieldName] ?? null;
389  if ($config && isset($config['access']) && !$this->‪checkAccess($config)) {
390  continue;
391  }
392 
393  $label = $this->‪getLabel($config['label'] ?? '', $fieldName);
394 
395  $type = $config['type'] ?? '';
396  $class = $config['class'] ?? '';
397  if ($type !== 'check' && $type !== 'select') {
398  $class .= ' form-control';
399  }
400  if ($type === 'select') {
401  $class .= ' form-select';
402  }
403  $more = '';
404  if ($class) {
405  $more .= ' class="' . htmlspecialchars($class) . '"';
406  }
407  $style = $config['style'] ?? '';
408  if ($style) {
409  $more .= ' style="' . htmlspecialchars($style) . '"';
410  }
411  if (isset($this->overrideConf[$fieldName])) {
412  $more .= ' disabled="disabled"';
413  }
414  $isBeUsersTable = ($config['table'] ?? false) === 'be_users';
415  $value = $isBeUsersTable ? ($backendUser->user[$fieldName] ?? false) : ($backendUser->uc[$fieldName] ?? false);
416  if (!$value && isset($config['default'])) {
417  $value = $config['default'];
418  }
419  $dataAdd = $isBeUsersTable ? '[be_users]' : '';
420 
421  switch ($type) {
422  case 'text':
423  case 'number':
424  case 'email':
425  case 'password':
426  $noAutocomplete = '';
427 
428  $maxLength = $config['max'] ?? 0;
429  if ((int)$maxLength > 0) {
430  $more .= ' maxlength="' . (int)$maxLength . '"';
431  }
432 
433  if ($type === 'password') {
434  $value = '';
435  $noAutocomplete = 'autocomplete="new-password" ';
436  }
437  $html = '<input id="field_' . htmlspecialchars($fieldName) . '"
438  type="' . htmlspecialchars($type) . '"
439  name="data' . $dataAdd . '[' . htmlspecialchars($fieldName) . ']" ' .
440  $noAutocomplete .
441  'value="' . htmlspecialchars((string)$value) . '" ' .
442  $more .
443  ' />';
444 
445  if ($fieldName === 'password' && $this->passwordPolicyValidator->isEnabled() && $this->passwordPolicyValidator->hasRequirements()) {
446  $description = $this->‪getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_password_policy.xlf:passwordRequirements.description');
447  $html .= '<p class="mt-2 mb-1 text-muted">' . htmlspecialchars($description) . '</p>';
448  $html .= '<ul class="mb-0"><li class="text-muted">' . implode('</li><li class="text-muted">', $this->passwordPolicyValidator->getRequirements()) . '</li></ul>';
449  }
450 
451  break;
452  case 'check':
453  $html = $label . '<div class="form-check form-switch"><input id="field_' . htmlspecialchars($fieldName) . '"
454  type="checkbox"
455  class="form-check-input"
456  name="data' . $dataAdd . '[' . htmlspecialchars($fieldName) . ']"' .
457  ($value ? ' checked="checked"' : '') .
458  $more .
459  ' /></div>';
460  $label = '';
461  break;
462  case 'language':
463  $html = $this->‪renderLanguageSelect();
464  break;
465  case 'select':
466  if ($config['itemsProcFunc'] ?? false) {
467  $html = GeneralUtility::callUserFunction($config['itemsProcFunc'], $config, $this);
468  } else {
469  $html = '<select id="field_' . htmlspecialchars($fieldName) . '"
470  name="data' . $dataAdd . '[' . htmlspecialchars($fieldName) . ']"' .
471  $more . '>' . LF;
472  foreach ($config['items'] as $key => $optionLabel) {
473  $html .= '<option value="' . htmlspecialchars($key) . '"' . ($value == $key ? ' selected="selected"' : '') . '>' . $this->‪getLabel($optionLabel, '', false) . '</option>' . LF;
474  }
475  $html .= '</select>';
476  }
477  break;
478  case 'user':
479  $html = GeneralUtility::callUserFunction($config['userFunc'], $config, $this);
480  break;
481  case 'button':
482  $label = $this->‪getLabel($config['label'] ?? '');
483  if (!empty($config['clickData'])) {
484  $clickData = $config['clickData'];
485  $buttonAttributes = [
486  'type' => 'button',
487  'class' => 'btn btn-default',
488  'value' => $this->‪getLabel($config['buttonlabel'], '', false),
489  ];
490  if (isset($clickData['eventName'])) {
491  $buttonAttributes['data-event'] = 'click';
492  $buttonAttributes['data-event-name'] = htmlspecialchars($clickData['eventName']);
493  $buttonAttributes['data-event-payload'] = htmlspecialchars($fieldName);
494  }
495  $html = '<br><input '
496  . GeneralUtility::implodeAttributes($buttonAttributes, false) . ' />';
497  }
498  if (!empty($config['confirm'])) {
499  $confirmData = $config['confirmData'];
500  // cave: values must be processed by `htmlspecialchars()`
501  $buttonAttributes = [
502  'type' => 'button',
503  'class' => 'btn btn-default t3js-modal-trigger',
504  'data-severity' => 'warning',
505  'data-title' => $this->‪getLabel($config['label'], '', false),
506  'data-bs-content' => $this->‪getLabel($confirmData['message'], '', false),
507  'value' => htmlspecialchars($this->‪getLabel($config['buttonlabel'], '', false)),
508  ];
509  if (isset($confirmData['eventName'])) {
510  $buttonAttributes['data-event'] = 'confirm';
511  $buttonAttributes['data-event-name'] = htmlspecialchars($confirmData['eventName']);
512  $buttonAttributes['data-event-payload'] = htmlspecialchars($fieldName);
513  }
514  $html = '<br><input '
515  . GeneralUtility::implodeAttributes($buttonAttributes, false) . ' />';
516  }
517  break;
518  case 'avatar':
519  // Get current avatar image
520  $html = '<br>';
521  $avatarFileUid = $this->‪getAvatarFileUid($backendUser->user['uid']);
522 
523  if ($avatarFileUid) {
524  $defaultAvatarProvider = GeneralUtility::makeInstance(DefaultAvatarProvider::class);
525  $avatarImage = $defaultAvatarProvider->getImage($backendUser->user, 32);
526  if ($avatarImage) {
527  $icon = '<span class="avatar avatar-size-medium"><span class="avatar-image">' .
528  '<img alt="" src="' . htmlspecialchars($avatarImage->getUrl()) . '"' .
529  ' width="' . (int)$avatarImage->getWidth() . '"' .
530  ' height="' . (int)$avatarImage->getHeight() . '" />' .
531  '</span></span>';
532  $html .= '<span class="float-start" style="padding-right: 10px" id="image_' . htmlspecialchars($fieldName) . '">' . $icon . ' </span>';
533  }
534  }
535  $html .= '<input id="field_' . htmlspecialchars($fieldName) . '" type="hidden" ' .
536  'name="data' . $dataAdd . '[' . htmlspecialchars($fieldName) . ']"' . $more .
537  ' value="' . $avatarFileUid . '" data-setup-avatar-field="' . htmlspecialchars($fieldName) . '" />';
538 
539  $html .= '<div class="btn-group">';
540  if ($avatarFileUid) {
541  $html .=
542  '<button type="button" id="clear_button_' . htmlspecialchars($fieldName) . '" aria-label="' . htmlspecialchars($this->‪getLanguageService()->getLL('avatar.clear')) . '" '
543  . ' class="btn btn-default">'
544  . $this->iconFactory->getIcon('actions-delete', ‪Icon::SIZE_SMALL)
545  . '</button>';
546  }
547  $html .=
548  '<button type="button" id="add_button_' . htmlspecialchars($fieldName) . '" class="btn btn-default btn-add-avatar"'
549  . ' aria-label="' . htmlspecialchars($this->‪getLanguageService()->getLL('avatar.openFileBrowser')) . '"'
550  . ' data-setup-avatar-url="' . htmlspecialchars((string)$this->uriBuilder->buildUriFromRoute('wizard_element_browser', ['mode' => 'file', 'bparams' => '||||-0-be_users-avatar-avatar'])) . '"'
551  . '>' . $this->iconFactory->getIcon('actions-insert-record', ‪Icon::SIZE_SMALL)
552  . '</button></div>';
553  break;
554  case 'mfa':
555  $label = $this->‪getLabel($config['label'] ?? '');
556  $html = '';
557  $lang = $this->‪getLanguageService();
558  $hasActiveProviders = $this->mfaProviderRegistry->hasActiveProviders($backendUser);
559  if ($hasActiveProviders) {
560  if ($this->mfaProviderRegistry->hasLockedProviders($backendUser)) {
561  $html .= ' <span class="badge badge-danger">' . htmlspecialchars($lang->getLL('mfaProviders.lockedMfaProviders')) . '</span>';
562  } else {
563  $html .= ' <span class="badge badge-success">' . htmlspecialchars($lang->getLL('mfaProviders.enabled')) . '</span>';
564  }
565  }
566  $html .= '<p class="text-muted">' . nl2br(htmlspecialchars($lang->getLL('mfaProviders.description'))) . '</p>';
567  if (!$this->mfaProviderRegistry->hasProviders()) {
568  $html .= '<span class="badge badge-danger">' . htmlspecialchars($lang->getLL('mfaProviders.notAvailable')) . '</span>';
569  break;
570  }
571  $html .= '<a href="' . htmlspecialchars((string)$this->uriBuilder->buildUriFromRoute('mfa')) . '" class="btn btn-' . ($hasActiveProviders ? 'default' : 'success') . '">';
572  $html .= $this->iconFactory->getIcon($hasActiveProviders ? 'actions-cog' : 'actions-plus', ‪Icon::SIZE_SMALL);
573  $html .= ' <span>' . htmlspecialchars($lang->getLL('mfaProviders.' . ($hasActiveProviders ? 'manageLinkTitle' : 'setupLinkTitle'))) . '</span>';
574  $html .= '</a>';
575  break;
576  default:
577  $html = '';
578  }
579 
580  $code[] = '<div class="form-section"><div class="row"><div class="form-group t3js-formengine-field-item col-md-12">' .
581  $label .
582  $html .
583  '</div></div></div>';
584  }
585 
586  $result[] = [
587  'label' => $tabLabel,
588  'content' => count($code) ? implode(LF, $code) : '',
589  ];
590  return $result;
591  }
592 
599  protected function ‪renderLanguageSelect()
600  {
601  $tcaConfig = ‪$GLOBALS['TCA']['be_users']['columns']['lang']['config'];
602  $items = $tcaConfig['items'];
603  $itemsProcFunc = [
604  'items' => &$items,
605  ];
606  GeneralUtility::callUserFunction($tcaConfig['itemsProcFunc'], $itemsProcFunc);
607  $backendUser = $this->‪getBackendUser();
608  $currentSelectedLanguage = (string)($backendUser->user['lang'] ?? 'default');
609  $languageService = $this->‪getLanguageService();
610  $content = '';
611  // get all labels in default language as well
612  $defaultLanguageLabelService = $this->languageServiceFactory->create('default');
613  $defaultLanguageLabelService->includeLLFile('EXT:setup/Resources/Private/Language/locallang.xlf');
614  foreach ($items as $item) {
615  $languageCode = $item['value'];
616  $name = $item['label'];
617  $available = in_array($languageCode, ‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['lang']['availableLanguages'] ?? [], true) || is_dir(‪Environment::getLabelsPath() . '/' . $languageCode);
618  if ($available || $languageCode === 'default') {
619  $localizedName = htmlspecialchars($languageService->getLL('lang_' . $languageCode) ?: $name);
620  $defaultName = $defaultLanguageLabelService->getLL('lang_' . $languageCode);
621  if ($defaultName === $localizedName || $defaultName === '') {
622  $defaultName = $languageCode;
623  }
624  if ($defaultName !== $languageCode) {
625  $defaultName .= ' - ' . $languageCode;
626  }
627  $localLabel = ' [' . htmlspecialchars($defaultName) . ']';
628  $content .= '<option value="' . $languageCode . '"' . ($currentSelectedLanguage === $languageCode ? ' selected="selected"' : '') . '>' . $localizedName . $localLabel . '</option>';
629  }
630  }
631  $content = '<select id="field_lang" name="data[be_users][lang]" class="form-select">' . $content . '</select>';
632  if ($currentSelectedLanguage !== 'default' && !@is_dir(‪Environment::getLabelsPath() . '/' . $currentSelectedLanguage)) {
633  $languageUnavailableWarning = htmlspecialchars(sprintf($languageService->getLL('languageUnavailable'), $languageService->getLL('lang_' . $currentSelectedLanguage))) . '&nbsp;&nbsp;<br />&nbsp;&nbsp;' . htmlspecialchars($languageService->getLL('languageUnavailable.' . ($backendUser->isAdmin() ? 'admin' : 'user')));
634  $content = '<br /><span class="badge badge-danger">' . $languageUnavailableWarning . '</span><br /><br />' . $content;
635  }
636  return $content;
637  }
638 
645  public function ‪renderStartModuleSelect(): string
646  {
647  // Load available backend modules
648  $startModuleSelect = '<option value="">' . htmlspecialchars($this->‪getLanguageService()->getLL('startModule.firstInMenu')) . '</option>';
649  foreach ($this->moduleProvider->getModules($this->getBackendUser(), false) as ‪$identifier => $module) {
650  if ($module->hasSubModules() || $module->isStandalone()) {
651  $modules = '';
652  if ($module->hasSubModules()) {
653  foreach ($module->getSubModules() as $subModuleIdentifier => $subModule) {
654  $modules .= '<option value="' . htmlspecialchars($subModuleIdentifier) . '"';
655  $modules .= ($this->‪getBackendUser()->uc['startModule'] ?? '') === $subModuleIdentifier ? ' selected="selected"' : '';
656  $modules .= '>' . htmlspecialchars($this->‪getLanguageService()->sL($subModule->getTitle())) . '</option>';
657  }
658  } elseif ($module->isStandalone()) {
659  $modules .= '<option value="' . htmlspecialchars(‪$identifier) . '"';
660  $modules .= ($this->‪getBackendUser()->uc['startModule'] ?? '') === ‪$identifier ? ' selected="selected"' : '';
661  $modules .= '>' . htmlspecialchars($this->‪getLanguageService()->sL($module->getTitle())) . '</option>';
662  }
663  $groupLabel = htmlspecialchars($this->‪getLanguageService()->sL($module->getTitle()));
664  $startModuleSelect .= '<optgroup label="' . htmlspecialchars($groupLabel) . '">' . $modules . '</optgroup>';
665  }
666  }
667  return '<select id="field_startModule" name="data[startModule]" class="form-select">' . $startModuleSelect . '</select>';
668  }
669 
676  protected function ‪checkAccess(array $config)
677  {
678  $access = $config['access'];
679  if (isset(‪$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['setup']['accessLevelCheck'][$access])) {
680  if (class_exists($access)) {
681  $accessObject = GeneralUtility::makeInstance($access);
682  if (method_exists($accessObject, 'accessLevelCheck')) {
683  // Initialize vars. If method fails, $set will be set to FALSE
684  return $accessObject->accessLevelCheck($config);
685  }
686  }
687  } elseif ($access === 'admin') {
688  return $this->‪getBackendUser()->isAdmin();
689  }
690 
691  return false;
692  }
693 
702  protected function ‪getLabel($str, $key = '', $addLabelTag = true)
703  {
704  $out = htmlspecialchars($this->‪getLanguageService()->sL($str));
705  if (isset($this->overrideConf[$key ?: $str])) {
706  $out = '<span style="color:#999999">' . $out . '</span>';
707  }
708  if ($addLabelTag) {
709  if ($key !== '') {
710  $out = '<label for="field_' . htmlspecialchars($key) . '">' . $out . '</label>';
711  } else {
712  $out = '<label>' . $out . '</label>';
713  }
714  }
715  return $out;
716  }
717 
724  protected function ‪getFieldsFromShowItem()
725  {
726  $allowedFields = ‪GeneralUtility::trimExplode(',', ‪$GLOBALS['TYPO3_USER_SETTINGS']['showitem'], true);
727  if ($this->‪getBackendUser()->isAdmin()) {
728  // Do not ask for current password if admin (unknown for other users and no security gain)
729  $key = array_search('passwordCurrent', $allowedFields);
730  if ($key !== false) {
731  unset($allowedFields[$key]);
732  }
733  }
734 
735  $backendUser = $this->‪getBackendUser();
736  $systemMaintainers = array_map('intval', ‪$GLOBALS['TYPO3_CONF_VARS']['SYS']['systemMaintainers'] ?? []);
737  if ($backendUser->getOriginalUserIdWhenInSwitchUserMode() && in_array((int)$backendUser->user['uid'], $systemMaintainers, true)) {
738  // DataHandler denies changing password of system maintainer users in switch user mode.
739  // Do not show the password fields is this case.
740  $key = array_search('password', $allowedFields);
741  if ($key !== false) {
742  unset($allowedFields[$key]);
743  }
744  $key = array_search('password2', $allowedFields);
745  if ($key !== false) {
746  unset($allowedFields[$key]);
747  }
748  }
749 
750  foreach ($this->tsFieldConf as $fieldName => $userTsFieldConfig) {
751  if (!empty($userTsFieldConfig['disabled'])) {
752  $fieldName = rtrim($fieldName, '.');
753  $key = array_search($fieldName, $allowedFields);
754  if ($key !== false) {
755  unset($allowedFields[$key]);
756  }
757  }
758  }
759  return $allowedFields;
760  }
761 
768  protected function ‪getAvatarFileUid($beUserId)
769  {
770  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_reference');
771  $file = $queryBuilder
772  ->select('uid_local')
773  ->from('sys_file_reference')
774  ->where(
775  $queryBuilder->expr()->eq(
776  'tablenames',
777  $queryBuilder->createNamedParameter('be_users')
778  ),
779  $queryBuilder->expr()->eq(
780  'fieldname',
781  $queryBuilder->createNamedParameter('avatar')
782  ),
783  $queryBuilder->expr()->eq(
784  'uid_foreign',
785  $queryBuilder->createNamedParameter($beUserId, ‪Connection::PARAM_INT)
786  )
787  )
788  ->executeQuery()
789  ->fetchOne();
790  return (int)$file;
791  }
792 
799  protected function ‪setAvatarFileUid($beUserId, $fileUid, array &$storeRec)
800  {
801  // Update is only needed when new fileUid is set
802  if ((int)$fileUid === $this->‪getAvatarFileUid($beUserId)) {
803  return;
804  }
805 
806  // If user is not allowed to modify avatar $fileUid is empty - so don't overwrite existing avatar
807  if (empty($fileUid)) {
808  return;
809  }
810 
811  $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file_reference');
812  $queryBuilder->getRestrictions()->removeAll();
813  $queryBuilder
814  ->delete('sys_file_reference')
815  ->where(
816  $queryBuilder->expr()->eq(
817  'tablenames',
818  $queryBuilder->createNamedParameter('be_users')
819  ),
820  $queryBuilder->expr()->eq(
821  'fieldname',
822  $queryBuilder->createNamedParameter('avatar')
823  ),
824  $queryBuilder->expr()->eq(
825  'uid_foreign',
826  $queryBuilder->createNamedParameter($beUserId, ‪Connection::PARAM_INT)
827  )
828  )
829  ->executeStatement();
830 
831  // If Avatar is marked for delete => set it to empty string so it will be updated properly
832  if ($fileUid === 'delete') {
833  $fileUid = '';
834  }
835 
836  // Create new reference
837  if ((int)$fileUid > 0) {
838  // Get file object
839  try {
840  $file = GeneralUtility::makeInstance(ResourceFactory::class)->getFileObject((int)$fileUid);
841  } catch (‪FileDoesNotExistException $e) {
842  $file = false;
843  }
844 
845  // Check if user is allowed to use the image (only when not in simulation mode)
846  if ($file && !$file->getStorage()->checkFileActionPermission('read', $file)) {
847  $file = false;
848  }
849 
850  // Check if extension is allowed
851  if ($file && $file->isImage()) {
852  // Create new file reference
853  $storeRec['sys_file_reference']['NEW1234'] = [
854  'uid_local' => (int)$fileUid,
855  'uid_foreign' => (int)$beUserId,
856  'tablenames' => 'be_users',
857  'fieldname' => 'avatar',
858  'pid' => 0,
859  ];
860  $storeRec['be_users'][(int)$beUserId]['avatar'] = 'NEW1234';
861  }
862  }
863  }
864 
868  protected function ‪addFlashMessages(‪ModuleTemplate $view): void
869  {
870  $languageService = $this->‪getLanguageService();
871  if ($this->setupIsUpdated && !$this->settingsAreResetToDefault) {
872  $view->‪addFlashMessage($languageService->getLL('setupWasUpdated'), $languageService->getLL('UserSettings'));
873  }
874  if ($this->settingsAreResetToDefault) {
875  $view->‪addFlashMessage($languageService->getLL('settingsAreReset'), $languageService->getLL('resetConfiguration'));
876  }
877  if ($this->setupIsUpdated || $this->settingsAreResetToDefault) {
878  $view->‪addFlashMessage($languageService->getLL('activateChanges'), '', ContextualFeedbackSeverity::INFO);
879  }
880  if ($this->passwordIsSubmitted) {
881  switch ($this->passwordIsUpdated) {
883  $view->‪addFlashMessage($languageService->getLL('oldPassword_failed'), $languageService->getLL('newPassword'), ContextualFeedbackSeverity::ERROR);
884  break;
886  $view->‪addFlashMessage($languageService->getLL('newPassword_failed'), $languageService->getLL('newPassword'), ContextualFeedbackSeverity::ERROR);
887  break;
889  $view->‪addFlashMessage($languageService->getLL('newPassword_ok'), $languageService->getLL('newPassword'));
890  break;
892  $view->‪addFlashMessage($languageService->getLL('passwordPolicyFailed'), $languageService->getLL('newPassword'), ContextualFeedbackSeverity::ERROR);
893  break;
894  }
895  }
896  }
897 
899  {
900  return ‪$GLOBALS['BE_USER'];
901  }
902 
904  {
905  return ‪$GLOBALS['LANG'];
906  }
907 }
‪TYPO3\CMS\Core\Localization\LanguageServiceFactory
Definition: LanguageServiceFactory.php:25
‪TYPO3\CMS\Core\DataHandling\DataHandler
Definition: DataHandler.php:97
‪TYPO3\CMS\Setup\Controller\SetupModuleController\getFieldsFromShowItem
‪string[] getFieldsFromShowItem()
Definition: SetupModuleController.php:724
‪TYPO3\CMS\Core\Imaging\Icon\SIZE_SMALL
‪const SIZE_SMALL
Definition: Icon.php:35
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
Definition: GeneralUtility.php:916
‪TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory
Definition: PasswordHashFactory.php:27
‪TYPO3\CMS\Core\Database\Connection\PARAM_INT
‪const PARAM_INT
Definition: Connection.php:47
‪TYPO3\CMS\Setup\Controller\SetupModuleController\PASSWORD_OLD_WRONG
‪const PASSWORD_OLD_WRONG
Definition: SetupModuleController.php:66
‪TYPO3\CMS\Setup\Controller\SetupModuleController\getButtons
‪getButtons(ModuleTemplate $view)
Definition: SetupModuleController.php:337
‪TYPO3\CMS\Core\Imaging\Icon
Definition: Icon.php:26
‪TYPO3\CMS\Backend\Template\ModuleTemplateFactory
Definition: ModuleTemplateFactory.php:33
‪TYPO3\CMS\Setup\Controller\SetupModuleController\$overrideConf
‪array $overrideConf
Definition: SetupModuleController.php:69
‪TYPO3\CMS\Setup\Controller\SetupModuleController
Definition: SetupModuleController.php:62
‪TYPO3\CMS\Core\Crypto\PasswordHashing\InvalidPasswordHashException
Definition: InvalidPasswordHashException.php:26
‪TYPO3\CMS\Setup\Controller\SetupModuleController\$passwordIsUpdated
‪int $passwordIsUpdated
Definition: SetupModuleController.php:73
‪TYPO3\CMS\Core\Core\Environment\getLabelsPath
‪static getLabelsPath()
Definition: Environment.php:233
‪TYPO3\CMS\Setup\Controller\SetupModuleController\$languageUpdate
‪bool $languageUpdate
Definition: SetupModuleController.php:70
‪TYPO3\CMS\Setup\Controller\SetupModuleController\storeIncomingData
‪storeIncomingData(ServerRequestInterface $request)
Definition: SetupModuleController.php:173
‪TYPO3\CMS\Setup\Controller\SetupModuleController\PASSWORD_UPDATED
‪const PASSWORD_UPDATED
Definition: SetupModuleController.php:64
‪TYPO3\CMS\Backend\Backend\Avatar\DefaultAvatarProvider
Definition: DefaultAvatarProvider.php:30
‪TYPO3\CMS\Setup\Controller\SetupModuleController\getLanguageService
‪getLanguageService()
Definition: SetupModuleController.php:903
‪TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException
Definition: FileDoesNotExistException.php:22
‪TYPO3\CMS\Core\Imaging\IconFactory
Definition: IconFactory.php:34
‪TYPO3\CMS\Setup\Controller\SetupModuleController\PASSWORD_POLICY_FAILED
‪const PASSWORD_POLICY_FAILED
Definition: SetupModuleController.php:67
‪TYPO3\CMS\Setup\Controller\SetupModuleController\processAdditionalJavaScriptModules
‪processAdditionalJavaScriptModules()
Definition: SetupModuleController.php:157
‪TYPO3\CMS\Setup\Controller\SetupModuleController\$setupIsUpdated
‪bool $setupIsUpdated
Definition: SetupModuleController.php:75
‪TYPO3\CMS\Backend\Module\ModuleProvider
Definition: ModuleProvider.php:29
‪TYPO3\CMS\Setup\Controller\SetupModuleController\renderLanguageSelect
‪string renderLanguageSelect()
Definition: SetupModuleController.php:599
‪TYPO3\CMS\Setup\Controller\SetupModuleController\getLabel
‪string getLabel($str, $key='', $addLabelTag=true)
Definition: SetupModuleController.php:702
‪TYPO3\CMS\Backend\Template\ModuleTemplate
Definition: ModuleTemplate.php:48
‪TYPO3\CMS\Core\Type\ContextualFeedbackSeverity
‪ContextualFeedbackSeverity
Definition: ContextualFeedbackSeverity.php:25
‪TYPO3\CMS\Setup\Controller\SetupModuleController\PASSWORD_NOT_UPDATED
‪const PASSWORD_NOT_UPDATED
Definition: SetupModuleController.php:63
‪TYPO3\CMS\Core\SysLog\Action\Setting
Definition: Setting.php:24
‪TYPO3\CMS\Setup\Controller\SetupModuleController\$passwordIsSubmitted
‪bool $passwordIsSubmitted
Definition: SetupModuleController.php:74
‪TYPO3\CMS\Backend\Routing\UriBuilder
Definition: UriBuilder.php:42
‪TYPO3\CMS\Setup\Controller\SetupModuleController\addFlashMessages
‪addFlashMessages(ModuleTemplate $view)
Definition: SetupModuleController.php:868
‪TYPO3\CMS\Core\PasswordPolicy\PasswordPolicyValidator
Definition: PasswordPolicyValidator.php:29
‪TYPO3\CMS\Core\Resource\ResourceFactory
Definition: ResourceFactory.php:41
‪TYPO3\CMS\Core\SysLog\Error
Definition: Error.php:24
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:66
‪TYPO3\CMS\Setup\Controller\SetupModuleController\renderUserSetup
‪array renderUserSetup()
Definition: SetupModuleController.php:362
‪TYPO3\CMS\Setup\Controller\SetupModuleController\initialize
‪initialize(ServerRequestInterface $request)
Definition: SetupModuleController.php:130
‪TYPO3\CMS\Core\Database\Connection
Definition: Connection.php:36
‪TYPO3\CMS\Backend\Template\ModuleTemplate\getDocHeaderComponent
‪getDocHeaderComponent()
Definition: ModuleTemplate.php:192
‪TYPO3\CMS\Setup\Controller\SetupModuleController\$settingsAreResetToDefault
‪bool $settingsAreResetToDefault
Definition: SetupModuleController.php:76
‪TYPO3\CMS\Core\FormProtection\FormProtectionFactory
Definition: FormProtectionFactory.php:44
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\PasswordPolicy\Event\EnrichPasswordValidationContextDataEvent
Definition: EnrichPasswordValidationContextDataEvent.php:30
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:41
‪TYPO3\CMS\Backend\Template\ModuleTemplate\addFlashMessage
‪addFlashMessage(string $messageBody, string $messageTitle='', int|ContextualFeedbackSeverity $severity=ContextualFeedbackSeverity::OK, bool $storeInSession=true)
Definition: ModuleTemplate.php:242
‪TYPO3\CMS\Setup\Controller\SetupModuleController\PASSWORD_NOT_THE_SAME
‪const PASSWORD_NOT_THE_SAME
Definition: SetupModuleController.php:65
‪TYPO3\CMS\Setup\Controller\SetupModuleController\mainAction
‪mainAction(ServerRequestInterface $request)
Definition: SetupModuleController.php:108
‪TYPO3\CMS\Setup\Controller\SetupModuleController\$passwordPolicyValidator
‪PasswordPolicyValidator $passwordPolicyValidator
Definition: SetupModuleController.php:78
‪TYPO3\CMS\Setup\Controller\SetupModuleController\renderStartModuleSelect
‪string renderStartModuleSelect()
Definition: SetupModuleController.php:645
‪TYPO3\CMS\Core\PasswordPolicy\PasswordPolicyAction
‪PasswordPolicyAction
Definition: PasswordPolicyAction.php:24
‪TYPO3\CMS\Core\Localization\LanguageService
Definition: LanguageService.php:46
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:51
‪TYPO3\CMS\Setup\Controller\SetupModuleController\$pagetreeNeedsRefresh
‪bool $pagetreeNeedsRefresh
Definition: SetupModuleController.php:71
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Setup\Controller
Definition: SetupModuleController.php:18
‪TYPO3\CMS\Setup\Controller\SetupModuleController\setAvatarFileUid
‪setAvatarFileUid($beUserId, $fileUid, array &$storeRec)
Definition: SetupModuleController.php:799
‪TYPO3\CMS\Setup\Controller\SetupModuleController\__construct
‪__construct(protected readonly EventDispatcherInterface $eventDispatcher, protected readonly MfaProviderRegistry $mfaProviderRegistry, protected readonly IconFactory $iconFactory, protected readonly PageRenderer $pageRenderer, protected readonly ModuleTemplateFactory $moduleTemplateFactory, protected readonly LanguageServiceFactory $languageServiceFactory, protected readonly ModuleProvider $moduleProvider, protected readonly UriBuilder $uriBuilder, protected readonly FormProtectionFactory $formProtectionFactory)
Definition: SetupModuleController.php:80
‪TYPO3\CMS\Setup\Controller\SetupModuleController\$tsFieldConf
‪array $tsFieldConf
Definition: SetupModuleController.php:72
‪TYPO3\CMS\Setup\Controller\SetupModuleController\checkAccess
‪bool checkAccess(array $config)
Definition: SetupModuleController.php:676
‪TYPO3\CMS\Setup\Controller\SetupModuleController\getAvatarFileUid
‪int getAvatarFileUid($beUserId)
Definition: SetupModuleController.php:768
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37
‪TYPO3\CMS\Core\SysLog\Type
Definition: Type.php:28
‪TYPO3\CMS\Setup\Event\AddJavaScriptModulesEvent
Definition: AddJavaScriptModulesEvent.php:24
‪TYPO3\CMS\Setup\Controller\SetupModuleController\getBackendUser
‪getBackendUser()
Definition: SetupModuleController.php:898
‪TYPO3\CMS\Core\Authentication\Mfa\MfaProviderRegistry
Definition: MfaProviderRegistry.php:28
‪TYPO3\CMS\Core\PasswordPolicy\Validator\Dto\ContextData
Definition: ContextData.php:28