first commit

This commit is contained in:
salam committed 2026-10-06 19:40:28 +02:00
commit e81864e683
2984 files changed
+267797

No files matched your search

@@ -0,0 +1,546 @@
<?php
declare(strict_types=1);
namespace In2code\Femanager\Controller;
use In2code\Femanager\DataProcessor\DataProcessorRunner;
use In2code\Femanager\Domain\Model\Log;
use In2code\Femanager\Domain\Model\User;
use In2code\Femanager\Domain\Repository\UserGroupRepository;
use In2code\Femanager\Domain\Repository\UserRepository;
use In2code\Femanager\Domain\Service\SendMailService;
use In2code\Femanager\Event\FinalCreateEvent;
use In2code\Femanager\Event\FinalUpdateEvent;
use In2code\Femanager\Finisher\FinisherRunner;
use In2code\Femanager\Utility\BackendUtility;
use In2code\Femanager\Utility\ConfigurationUtility;
use In2code\Femanager\Utility\FrontendUtility;
use In2code\Femanager\Utility\HashUtility;
use In2code\Femanager\Utility\LocalizationUtility;
use In2code\Femanager\Utility\LogUtility;
use In2code\Femanager\Utility\StringUtility;
use In2code\Femanager\Utility\UserUtility;
use TYPO3\CMS\Backend\Utility\BackendUtility as BackendUtilityCore;
use TYPO3\CMS\Core\Http\ApplicationType;
use TYPO3\CMS\Core\Messaging\AbstractMessage;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException;
use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
/**
* Class AbstractController
*/
abstract class AbstractController extends ActionController
{
/**
* @var UserRepository
*/
protected $userRepository;
/**
* @var UserGroupRepository
*/
protected $userGroupRepository;
/**
* @var PersistenceManager
*/
protected $persistenceManager;
/**
* @var SendMailService
*/
protected $sendMailService;
/**
* @var FinisherRunner
*/
protected $finisherRunner;
/**
* @var LogUtility
*/
protected $logUtility;
/**
* Content Object
*
* @var ContentObjectRenderer
*/
public $contentObject;
/**
* Former known as piVars
*
* @var array
*/
public $pluginVariables;
/**
* TypoScript
*
* @var array
*/
public $config;
/**
* Complete Configuration
*
* @var array
*/
public $allConfig;
/**
* Module Configuration for Backend
* this is a merge configuration -> TypoScript -> PageTSConfig -> UserTSConfig
*
* @var array
*/
public $moduleConfig;
/**
* Current logged in user object
*
* @var User
*/
public $user;
/**
* All available usergroups
*
* @var object
*/
public $allUserGroups;
/**
* AbstractController constructor.
*
* @param UserRepository $userRepository
* @param UserGroupRepository $userGroupRepository
* @param PersistenceManager $persistenceManager
* @param SendMailService $sendMailService
* @param FinisherRunner $finisherRunner
* @param LogUtility $logUtility
*/
public function __construct(
UserRepository $userRepository,
UserGroupRepository $userGroupRepository,
PersistenceManager $persistenceManager,
SendMailService $sendMailService,
FinisherRunner $finisherRunner,
LogUtility $logUtility
) {
$this->userRepository = $userRepository;
$this->userGroupRepository = $userGroupRepository;
$this->persistenceManager = $persistenceManager;
$this->sendMailService = $sendMailService;
$this->finisherRunner = $finisherRunner;
$this->logUtility = $logUtility;
}
/**
* Prefix method to createAction()
* Create Confirmation from Admin is not necessary
*
* @param User $user
*/
public function createAllConfirmed(User $user)
{
$this->userRepository->add($user);
$this->persistenceManager->persistAll();
$this->logUtility->log(Log::STATUS_NEWREGISTRATION, $user);
$this->finalCreate($user, 'new', 'createStatus');
}
/**
* Prefix method to updateAction()
* Update Confirmation from Admin is not necessary
*
* @param User $user
*/
public function updateAllConfirmed(User $user)
{
// send notify email to admin
$existingUser = clone $this->userRepository->findByUid($user->getUid());
if (ConfigurationUtility::notifyAdminAboutEdits($this->settings) === true) {
$this->sendMailService->send(
'updateNotify',
StringUtility::makeEmailArray(
ConfigurationUtility::getValue('edit/email/notifyAdmin/receiver/email/value', $this->settings) ?:
ConfigurationUtility::getValue('edit/notifyAdmin', $this->settings),
$this->settings['edit']['email']['notifyAdmin']['receiver']['name']['value'] ?? null
),
StringUtility::makeEmailArray($user->getEmail(), $user->getUsername()),
'Profile update',
[
'user' => $user,
'changes' => UserUtility::getDirtyPropertiesFromUser($user),
'settings' => $this->settings,
],
$this->config['edit.']['email.']['notifyAdmin.'] ?? []
);
}
$this->userRepository->update($user);
$this->persistenceManager->persistAll();
$this->eventDispatcher->dispatch(new FinalUpdateEvent($user));
$this->logUtility->log(Log::STATUS_PROFILEUPDATED, $user, ['existingUser' => $existingUser]);
$this->redirectByAction('edit');
$this->addFlashMessage(LocalizationUtility::translate('update'));
}
/**
* Prefix method to updateAction(): Update must be confirmed by Admin
*
* @param User $user
*/
public function updateRequest($user)
{
if ($this->settings['edit']['confirmByAdmin'] ?? null) {
$dirtyProperties = UserUtility::getDirtyPropertiesFromUser($user);
$user = UserUtility::rollbackUserWithChangeRequest($user, $dirtyProperties);
$this->sendMailService->send(
'updateRequest',
StringUtility::makeEmailArray(
$this->settings['edit']['confirmByAdmin'] ?? '',
$this->settings['edit']['email']['updateRequest']['sender']['name']['value'] ?? null
),
StringUtility::makeEmailArray($user->getEmail(), $user->getUsername()),
'New Profile change request',
[
'user' => $user,
'changes' => $dirtyProperties,
'hash' => HashUtility::createHashForUser($user),
],
$this->config['edit.']['email.']['updateRequest.'] ?? []
);
$this->logUtility->log(
Log::STATUS_PROFILEUPDATEREFUSEDADMIN,
$user,
['dirtyProperties' => $dirtyProperties]
);
$this->redirectByAction('edit', 'requestRedirect');
$this->addFlashMessage(LocalizationUtility::translate('updateRequest'));
} else {
$this->logUtility->log(
Log::STATUS_PROFILEUPDATEREFUSEDADMIN,
$user,
['message' => 'settings[edit][confirmByAdmin] is missing!']
);
}
}
/**
* Some additional actions after profile creation
*
* @param User $user
* @param string $action
* @param string $redirectByActionName Action to redirect
* @param bool $login Login after creation
* @param string $status
* @param bool $backend Don't redirect if called from backend action
*/
public function finalCreate(
$user,
string $action,
string $redirectByActionName,
bool $login = true,
string $status = '',
bool $backend = false
): void {
$this->loginPreflight($user, $login);
$variables = ['user' => $user, 'settings' => $this->settings, 'hash' => HashUtility::createHashForUser($user)];
if (ConfigurationUtility::getValue(
'new./email./createUserNotify./sender./email./value',
$this->config
) && ConfigurationUtility::getValue('new./email./createUserNotify./sender./name./value', $this->config)) {
$this->sendMailService->send(
'createUserNotify',
StringUtility::makeEmailArray($user->getEmail(), $user->getFirstName() . ' ' . $user->getLastName()),
StringUtility::makeEmailArray(
ConfigurationUtility::getValue(
'new./email./createUserNotify./sender./email./value',
$this->config
),
ConfigurationUtility::getValue('new./email./createUserNotify./sender./name./value', $this->config)
),
$this->contentObject->cObjGetSingle(
(string)ConfigurationUtility::getValue('new./email./createUserNotify./subject', $this->config),
(array)ConfigurationUtility::getValue('new./email./createUserNotify./subject.', $this->config),
),
$variables,
ConfigurationUtility::getValue('new./email./createUserNotify.', $this->config)
);
}
$createAdminNotify = ConfigurationUtility::getValue(
'new./email./createAdminNotify./receiver./email./value',
$this->config
);
if (!$createAdminNotify) {
$createAdminNotify = $this->settings['new']['notifyAdmin'];
}
// send notify email to admin
if ($createAdminNotify) {
$this->sendMailService->send(
'createNotify',
StringUtility::makeEmailArray(
$createAdminNotify
),
StringUtility::makeEmailArray($user->getEmail(), $user->getUsername()),
$this->contentObject->cObjGetSingle(
(string)ConfigurationUtility::getValue('new./email./createAdminNotify./subject', $this->config),
(array)ConfigurationUtility::getValue('new./email./createAdminNotify./subject.', $this->config)
),
$variables,
ConfigurationUtility::getValue('new./email./createAdminNotify.', $this->config)
);
}
$this->eventDispatcher->dispatch(new FinalCreateEvent($user, $action));
$this->finisherRunner->callFinishers($user, $this->actionMethodName, $this->settings, $this->contentObject);
if ($backend === false) {
$this->redirectByAction($action, ($status ? $status . 'Redirect' : 'redirect'));
$this->addFlashMessage(LocalizationUtility::translate('create'));
$this->redirect($redirectByActionName);
}
}
/**
* Log user in
*
* @param User $user
* @param $login
* @throws IllegalObjectTypeException
*/
protected function loginPreflight(User $user, $login)
{
if ($login) {
// persist user (otherwise login may not be possible)
$this->userRepository->update($user);
$this->persistenceManager->persistAll();
if (ConfigurationUtility::getValue('new./login', $this->config) === '1') {
UserUtility::login($user, ConfigurationUtility::getValue('persistence./storagePid', $this->allConfig));
$this->addFlashMessage(LocalizationUtility::translate('login'), '', AbstractMessage::NOTICE);
}
}
}
/**
* Redirect by TypoScript setting
* [userConfirmation|userConfirmationRefused|adminConfirmation|
* adminConfirmationRefused|adminConfirmationRefusedSilent]Redirect
*
* @param string $action "new", "edit"
* @param string $category "redirect", "requestRedirect" value from TypoScript
*/
protected function redirectByAction($action = 'new', $category = 'redirect')
{
$target = null;
// redirect from TypoScript cObject
if ($this->contentObject->cObjGetSingle(
(string)ConfigurationUtility::getValue($action . './' . $category, $this->config),
(array)ConfigurationUtility::getValue($action . './' . $category . '.', $this->config),
)
) {
$target = $this->contentObject->cObjGetSingle(
(string)ConfigurationUtility::getValue($action . './' . $category, $this->config),
array_merge_recursive(
(array)ConfigurationUtility::getValue($action . './' . $category . '.', $this->config),
[
'linkAccessRestrictedPages' => 1,
]
)
);
}
// if redirect target
if ($target) {
$this->redirectToUri(StringUtility::removeDoubleSlashesFromUri($target));
}
}
/**
* Check if user is authenticated and params are valid
*
* @param User $user
* @param int $uid Given fe_users uid
* @param string $receivedToken Token
*/
protected function isSpoof($user, $uid, $receivedToken)
{
$errorOnProfileUpdate = false;
$knownToken = GeneralUtility::hmac($user->getUid(), (string)$user->getCrdate()->getTimestamp());
//check if the params are valid
if (!is_string($receivedToken) || !hash_equals($knownToken, $receivedToken)) {
$errorOnProfileUpdate = true;
}
//check if the logged user is allowed to edit / delete this record
if ($user->getUid() !== (int)$uid && $uid > 0) {
$errorOnProfileUpdate = true;
}
if ($errorOnProfileUpdate === true) {
return true;
}
return false;
}
/**
* Assigns all values, which should be available in all views
*/
public function assignForAll()
{
$jsLabels = [
'loading_states' => LocalizationUtility::translate('js.loading_states'),
'please_choose' => LocalizationUtility::translate('pleaseChoose'),
];
$this->view->assignMultiple(
[
'languageUid' => FrontendUtility::getFrontendLanguageUid(),
'storagePid' => $this->allConfig['persistence']['storagePid'] ?? 0,
'Pid' => FrontendUtility::getCurrentPid(),
'data' => $this->contentObject->data,
'useStaticInfoTables' => ExtensionManagementUtility::isLoaded('static_info_tables'),
'jsLabels' => json_encode($jsLabels),
]
);
}
public function initializeAction()
{
$this->controllerContext = $this->buildControllerContext();
$this->user = UserUtility::getCurrentUser();
$this->contentObject = $this->configurationManager->getContentObject();
$this->pluginVariables = $this->request->getArguments();
$this->moduleConfig = [];
$this->allConfig = $this->configurationManager->getConfiguration(
ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK
);
$this->config = $this->configurationManager->getConfiguration(
ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT
);
$this->config = $this->config[BackendUtility::getPluginOrModuleString() . '.']['tx_femanager.']['settings.'] ?? [];
if (ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend()) {
$config = BackendUtility::loadTS($this->allConfig['settings']['configPID'] ?? null);
if (is_array($config['plugin.']['tx_femanager.']['settings.'] ?? null)) {
$this->config = $config['plugin.']['tx_femanager.']['settings.'];
$this->settings = $this->config;
}
$this->moduleConfig = $config['module.']['tx_femanager.'] ?? '';
// Retrieve page TSconfig of the current page
$pageTsConfig = BackendUtilityCore::getPagesTSconfig(BackendUtility::getPageIdentifier());
if (is_array($pageTsConfig['module.']['tx_femanager.'] ?? null)) {
$this->moduleConfig = array_merge($this->moduleConfig, $pageTsConfig['module.']['tx_femanager.'] ?? []);
}
// Retrieve user TSconfig of currently logged in user
$userTsConfig = $GLOBALS['BE_USER']->getTSConfig();
if ((is_array($userTsConfig['tx_femanager.']) ?? null) && (is_array($this->moduleConfig)?? null)) {
$this->moduleConfig = array_merge_recursive($this->moduleConfig, $userTsConfig['tx_femanager.']);
}
}
$this->setAllUserGroups();
$this->checkTypoScript();
$this->checkStoragePid();
$dataProcessorRunner = $this->objectManager->get(DataProcessorRunner::class);
$this->pluginVariables = $dataProcessorRunner->callClasses(
$this->request->getArguments(),
$this->settings,
$this->contentObject,
$this->arguments
);
$this->request->setArguments($this->pluginVariables);
}
/**
* Deactivate errormessages in flashmessages
*
* @return bool
*/
protected function getErrorFlashMessage()
{
return false;
}
protected function checkStoragePid()
{
if ((int)($this->allConfig['persistence']['storagePid'] ?? 0) === 0
&& GeneralUtility::_GP('type') !== '1548935210'
) {
$this->addFlashMessage(LocalizationUtility::translate('error_no_storagepid'), '', AbstractMessage::ERROR);
}
}
protected function checkTypoScript()
{
if (ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'] ?? null)->isBackend()) {
if (($this->config['_TypoScriptIncluded'] ?? '1') !== '1') {
$this->addFlashMessage(
(string)LocalizationUtility::translate('error_no_typoscript_be'),
'',
AbstractMessage::ERROR
);
}
} else {
$typoscriptIncluded = ConfigurationUtility::getValue('_TypoScriptIncluded', $this->settings);
if ($typoscriptIncluded !== '1' && !GeneralUtility::_GP('eID')) {
$this->addFlashMessage(
(string)LocalizationUtility::translate('error_no_typoscript'),
'',
AbstractMessage::ERROR
);
}
}
}
protected function setAllUserGroups()
{
$controllerName = strtolower($this->controllerContext->getRequest()->getControllerName());
$removeFromUserGroupSelection = $this->settings[$controllerName]['misc']['removeFromUserGroupSelection'] ?? '';
$this->allUserGroups = $this->userGroupRepository->findAllForFrontendSelection($removeFromUserGroupSelection);
}
/**
* Send email to user for confirmation
*
* @param User $user
*/
public function sendCreateUserConfirmationMail(User $user)
{
$this->sendMailService->send(
'createUserConfirmation',
StringUtility::makeEmailArray($user->getEmail(), $user->getUsername()),
[
ConfigurationUtility::getValue('new./email./createUserConfirmation./sender./email./value', $this->config) =>
ConfigurationUtility::getValue('new./email./createUserConfirmation./sender./name./value', $this->config),
],
$this->contentObject->cObjGetSingle(
(string)ConfigurationUtility::getValue('new./email./createUserConfirmation./subject', $this->config),
(array)ConfigurationUtility::getValue('new./email./createUserConfirmation./subject.', $this->config)
),
[
'user' => $user,
'hash' => HashUtility::createHashForUser($user),
],
ConfigurationUtility::getValue('new./email./createUserConfirmation.', $this->config)
);
}
}
@@ -0,0 +1,18 @@
<?php
namespace In2code\Femanager\Controller;
use In2code\Femanager\Domain\Service\RatelimiterService;
abstract class AbstractFrontendController extends AbstractController
{
/**
* @var RatelimiterService
*/
protected $ratelimiterService;
public function injectRatelimiterService(RatelimiterService $ratelimiterService): void
{
$this->ratelimiterService = $ratelimiterService;
}
}
@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace In2code\Femanager\Controller;
use In2code\Femanager\DataProvider\CountryZonesDataProvider;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class DataController extends ActionController
{
protected $countryZonesDataProvider;
public function __construct(CountryZonesDataProvider $countryZonesDataProvider)
{
$this->countryZonesDataProvider = $countryZonesDataProvider;
}
public function getStatesForCountryAction(string $country): ResponseInterface
{
$countryZones = $this->countryZonesDataProvider->getCountryZonesForCountryIso3($country);
$jsonData = [];
foreach ($countryZones as $countryZone) {
$jsonData[$countryZone->getIsoCode()] = $countryZone->getLocalName();
}
return $this->jsonResponse(json_encode($jsonData));
}
}
@@ -0,0 +1,255 @@
<?php
declare(strict_types=1);
namespace In2code\Femanager\Controller;
use In2code\Femanager\Domain\Model\Log;
use In2code\Femanager\Domain\Model\User;
use In2code\Femanager\Domain\Model\UserGroup;
use In2code\Femanager\Event\AfterUserUpdateEvent;
use In2code\Femanager\Event\BeforeUpdateUserEvent;
use In2code\Femanager\Event\DeleteUserEvent;
use In2code\Femanager\Utility\ConfigurationUtility;
use In2code\Femanager\Utility\FrontendUtility;
use In2code\Femanager\Utility\HashUtility;
use In2code\Femanager\Utility\LocalizationUtility;
use In2code\Femanager\Utility\ObjectUtility;
use In2code\Femanager\Utility\StringUtility;
use In2code\Femanager\Utility\UserUtility;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Messaging\AbstractMessage;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Annotation\Validate;
use TYPO3\CMS\Extbase\Http\ForwardResponse;
/**
* Class EditController
*/
class EditController extends AbstractFrontendController
{
public function editAction(): ResponseInterface
{
$token = '';
if ($this->user) {
$token = GeneralUtility::hmac($this->user->getUid(), (string)$this->user->getCrdate()->getTimestamp());
}
$this->view->assignMultiple([
'user' => $this->user,
'allUserGroups' => $this->allUserGroups,
'token' => $token
]);
$this->assignForAll();
return $this->htmlResponse();
}
public function initializeUpdateAction()
{
if ($this->keepPassword()) {
unset($this->pluginVariables['user']['password']);
unset($this->pluginVariables['password_repeat']);
}
$this->request->setArguments($this->pluginVariables);
}
/**
* @param User $user
* @Validate("In2code\Femanager\Domain\Validator\ServersideValidator", param="user")
* @Validate("In2code\Femanager\Domain\Validator\PasswordValidator", param="user")
* @Validate("In2code\Femanager\Domain\Validator\CaptchaValidator", param="user")
*/
public function updateAction(User $user)
{
$currentUser = UserUtility::getCurrentUser();
$userValues = $this->request->getArgument('user') ?? [];
$token = $this->request->getArgument('token') ?? null;
$identity = (int)($userValues['__identity'] ?? 0);
$isSpoof = $this->isSpoof($currentUser, $identity, $token);
if (!$currentUser instanceof User || $identity === 0 || $token === null || $isSpoof) {
$logStatus = $isSpoof ? Log::STATUS_PROFILEUPDATEATTEMPTEDSPOOF : Log::STATUS_PROFILEUPDATEREFUSEDSECURITY;
$logContext = $isSpoof ? $currentUser : $user;
$this->logUtility->log($logStatus, $logContext);
$this->addFlashMessage(
LocalizationUtility::translateByState(Log::STATUS_PROFILEUPDATEREFUSEDSECURITY),
'',
AbstractMessage::ERROR
);
return new ForwardResponse('edit');
}
$response = $this->redirectIfNoChangesOnObject($user);
if ($response !== null) {
return $response;
}
$user = FrontendUtility::forceValues(
$user,
ConfigurationUtility::getValue('edit./forceValues./beforeAnyConfirmation.', $this->config)
);
$this->emailForUsername($user);
UserUtility::convertPassword(
$user,
ConfigurationUtility::getValue('edit/misc/passwordSave', $this->settings)
);
$this->eventDispatcher->dispatch(new BeforeUpdateUserEvent($user));
if (!empty($this->settings['edit']['confirmByAdmin'])) {
$this->updateRequest($user);
} else {
$this->updateAllConfirmed($user);
}
$this->redirect('edit');
}
/**
* @param User $user User object
* @param string $hash
* @param string $status could be "confirm", "refuse", "silentRefuse"
*/
public function confirmUpdateRequestAction(User $user, $hash, $status = 'confirm'): ResponseInterface
{
$this->view->assign('user', $user);
if (!HashUtility::validHash($hash, $user) || !$user->getTxFemanagerChangerequest()) {
$this->addFlashMessage(LocalizationUtility::translate('updateFailedProfile'), '', AbstractMessage::ERROR);
return $this->htmlResponse(null);
}
switch ($status) {
case 'confirm':
$this->statusConfirm($user);
break;
case 'refuse':
$this->statusRefuse($user);
break;
case 'silentRefuse':
$this->logUtility->log(Log::STATUS_PROFILEUPDATEREFUSEDADMIN, $user);
$this->addFlashMessage(LocalizationUtility::translateByState(Log::STATUS_PROFILEUPDATEREFUSEDADMIN));
break;
default:
}
$user->setTxFemanagerChangerequest('');
$this->userRepository->update($user);
$this->eventDispatcher->dispatch(new AfterUserUpdateEvent($user, $hash, $status));
return $this->htmlResponse();
}
/**
* Status update confirmation
*
* @param User $user
*/
protected function statusConfirm(User $user)
{
$values = GeneralUtility::xml2array($user->getTxFemanagerChangerequest());
foreach ((array)$values as $field => $value) {
if ($field !== 'usergroup' && method_exists($user, 'set' . ucfirst($field))) {
$user->{'set' . ucfirst($field)}($value['new']);
} else {
$user->removeAllUsergroups();
$usergroupUids = GeneralUtility::trimExplode(',', $value['new'], true);
foreach ($usergroupUids as $usergroupUid) {
/** @var UserGroup $usergroup */
$usergroup = $this->userGroupRepository->findByUid($usergroupUid);
$user->addUsergroup($usergroup);
}
}
}
$user = FrontendUtility::forceValues($user, $this->config['edit.']['forceValues.']['onAdminConfirmation.']);
$this->logUtility->log(Log::STATUS_PROFILEUPDATECONFIRMEDADMIN, $user);
$this->addFlashMessage(LocalizationUtility::translate('updateProfile'));
}
/**
* Status update refused
*
* @param User $user
*/
protected function statusRefuse(User $user)
{
$this->sendMailService->send(
'updateRequestRefused',
StringUtility::makeEmailArray($user->getEmail(), $user->getFirstName() . ' ' . $user->getLastName()),
['sender@femanager.org' => 'Sender Name'],
'Your change request was refused',
[
'user' => $user,
'settings' => $this->settings
],
$this->config['edit.']['email.']['updateRequestRefused.']
);
$this->logUtility->log(Log::STATUS_PROFILEUPDATEREFUSEDADMIN, $user);
$this->addFlashMessage(LocalizationUtility::translateByState(Log::STATUS_PROFILEUPDATEREFUSEDADMIN));
}
/**
* action delete
*
* @param User $user
*/
public function deleteAction(User $user)
{
$currentUser = UserUtility::getCurrentUser();
$token = $this->request->hasArgument('token') ? $this->request->getArgument('token') : null;
$uid = $this->request->hasArgument('user') ? $this->request->getArgument('user') : null;
if ($currentUser === null ||
$token === null ||
$uid === null ||
$this->isSpoof($currentUser, (int)$uid, $token)
) {
$this->logUtility->log(Log::STATUS_PROFILEUPDATEREFUSEDSECURITY, $user);
$this->addFlashMessage(
LocalizationUtility::translateByState(Log::STATUS_PROFILEUPDATEREFUSEDSECURITY),
'',
AbstractMessage::ERROR
);
return new ForwardResponse('edit');
}
$this->eventDispatcher->dispatch(new DeleteUserEvent($user));
$this->logUtility->log(Log::STATUS_PROFILEDELETE, $user);
$this->addFlashMessage(LocalizationUtility::translateByState(Log::STATUS_PROFILEDELETE));
$this->userRepository->remove($user);
$this->redirectByAction('delete');
$this->redirect('edit');
}
/**
* Check if password should be kept
*
* If password is empty
* If password repeat is also empty
* If keepPasswordIfEmpty configuration is turned on
*
* @return bool
*/
protected function keepPassword()
{
return !empty($this->settings['edit']['misc']['keepPasswordIfEmpty']) &&
empty($this->pluginVariables['user']['password']) &&
empty($this->pluginVariables['password_repeat']);
}
/**
* Check: If there are no changes, simple redirect back
*/
protected function redirectIfNoChangesOnObject(User $user)
{
if (!ObjectUtility::isDirtyObject($user)) {
$this->addFlashMessage(LocalizationUtility::translate('noChanges'), '', AbstractMessage::NOTICE);
return $this->redirect('edit');
}
return null;
}
/**
* @param User $user
*/
protected function emailForUsername(User $user)
{
$fillEmailWithUsername = ConfigurationUtility::getValue('edit/fillEmailWithUsername', $this->settings);
if ($fillEmailWithUsername === '1') {
$user->setEmail($user->getUsername());
}
}
}
@@ -0,0 +1,306 @@
<?php
declare(strict_types=1);
namespace In2code\Femanager\Controller;
use In2code\Femanager\Domain\Model\Log;
use In2code\Femanager\Domain\Model\User;
use In2code\Femanager\Event\InviteUserConfirmedEvent;
use In2code\Femanager\Event\InviteUserCreateEvent;
use In2code\Femanager\Event\InviteUserEditEvent;
use In2code\Femanager\Event\InviteUserUpdateEvent;
use In2code\Femanager\Utility\ConfigurationUtility;
use In2code\Femanager\Utility\FrontendUtility;
use In2code\Femanager\Utility\HashUtility;
use In2code\Femanager\Utility\LocalizationUtility;
use In2code\Femanager\Utility\StringUtility;
use In2code\Femanager\Utility\UserUtility;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Messaging\AbstractMessage;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Annotation\Validate;
use TYPO3\CMS\Extbase\Http\ForwardResponse;
/**
* Class InvitationController
*/
class InvitationController extends AbstractFrontendController
{
/**
* action new
*/
public function newAction(): ResponseInterface
{
$permissionCheck = $this->allowedUserForInvitationNewAndCreate();
if ($permissionCheck instanceof ResponseInterface) {
return $permissionCheck;
}
$this->view->assign('allUserGroups', $this->allUserGroups);
$this->assignForAll();
return $this->htmlResponse();
}
/**
* action create
*
* @param User $user
* @Validate("In2code\Femanager\Domain\Validator\ServersideValidator", param="user")
* @Validate("In2code\Femanager\Domain\Validator\PasswordValidator", param="user")
* @Validate("In2code\Femanager\Domain\Validator\CaptchaValidator", param="user")
*/
public function createAction(User $user)
{
if ($this->ratelimiterService->isLimited()) {
$this->addFlashMessage(
LocalizationUtility::translate('ratelimiter_too_many_attempts'),
'',
AbstractMessage::ERROR
);
$this->redirect('status');
}
$permissionCheck = $this->allowedUserForInvitationNewAndCreate();
if ($permissionCheck instanceof ResponseInterface) {
return $permissionCheck;
}
$user->setDisable(true);
$user = FrontendUtility::forceValues(
$user,
ConfigurationUtility::getValue('invitation./forceValues./beforeAnyConfirmation.', $this->config)
);
$user = UserUtility::fallbackUsernameAndPassword($user);
if (ConfigurationUtility::getValue('invitation/fillEmailWithUsername', $this->settings) === '1') {
$user->setEmail($user->getUsername());
}
UserUtility::hashPassword(
$user,
ConfigurationUtility::getValue('invitation/misc/passwordSave', $this->settings)
);
$this->eventDispatcher->dispatch(new InviteUserCreateEvent($user));
$this->ratelimiterService->consumeSlot();
$this->createAllConfirmed($user);
}
/**
* Prefix method to createAction()
* Create Confirmation from Admin is not necessary
*
* @param User $user
*/
public function createAllConfirmed(User $user)
{
$this->userRepository->add($user);
$this->persistenceManager->persistAll();
$this->addFlashMessage(LocalizationUtility::translate('createAndInvited'));
$this->logUtility->log(Log::STATUS_INVITATIONPROFILECREATED, $user);
// send confirmation mail to user
$this->sendMailService->send(
'invitation',
StringUtility::makeEmailArray($user->getEmail(), $user->getUsername()),
StringUtility::makeEmailArray($user->getEmail(), $user->getUsername()),
'Profile creation with invitation',
[
'user' => $user,
'settings' => $this->settings,
'hash' => HashUtility::createHashForUser($user)
],
$this->config['invitation.']['email.']['invitation.']
);
// send notify email to admin
$notifyAdminStep1 = ConfigurationUtility::getValue('invitation/notifyAdminStep1', $this->settings);
if ($notifyAdminStep1) {
$this->sendMailService->send(
'invitationNotifyStep1',
StringUtility::makeEmailArray(
$notifyAdminStep1,
ConfigurationUtility::getValue('invitation/email/invitationAdminNotifyStep1/receiver/name/value', $this->settings)
),
StringUtility::makeEmailArray($user->getEmail(), $user->getUsername()),
'Profile creation with invitation - Step 1',
[
'user' => $user,
'settings' => $this->settings
],
ConfigurationUtility::getValue('invitation./email./invitationAdminNotifyStep1.', $this->config)
);
}
$this->eventDispatcher->dispatch(new InviteUserConfirmedEvent($user));
$this->redirectByAction('invitation', 'redirectStep1');
$this->redirect('new');
}
/**
* action edit
*
* @param int $user User UID
* @param string $hash
*/
public function editAction($user, $hash = null): ResponseInterface
{
$user = $this->userRepository->findByUid($user);
// User must exist and hash must be valid
if ($user === null || !HashUtility::validHash($hash, $user)) {
$this->addFlashMessage(LocalizationUtility::translate('createFailedProfile'), '', AbstractMessage::ERROR);
$this->redirect('status');
}
// User must not be deleted (deleted = 0) and not be activated (disable = 1)
if ($user->getDisable() == 0) {
$this->addFlashMessage(LocalizationUtility::translate('userAlreadyConfirmed'), '', AbstractMessage::ERROR);
$this->redirect('status');
}
$user->setDisable(false);
$this->userRepository->update($user);
$this->persistenceManager->persistAll();
$this->eventDispatcher->dispatch(new InviteUserEditEvent($user, $hash));
$this->view->assignMultiple(
[
'user' => $user,
'hash' => $hash
]
);
$this->assignForAll();
return $this->htmlResponse();
}
/**
* action update
*
* @param User $user
* @param string $hash
* @Validate("In2code\Femanager\Domain\Validator\ServersideValidator", param="user")
* @Validate("In2code\Femanager\Domain\Validator\PasswordValidator", param="user")
*/
public function updateAction(User $user, $hash = null)
{
if (!HashUtility::validHash($hash, $user)) {
$this->addFlashMessage(
LocalizationUtility::translateByState(Log::STATUS_PROFILEUPDATEREFUSEDSECURITY),
'',
AbstractMessage::ERROR
);
$this->redirect('status');
}
$this->addFlashMessage(LocalizationUtility::translate('createAndInvitedFinished'));
$this->logUtility->log(Log::STATUS_INVITATIONPROFILEENABLED, $user);
$notifyAdmin = ConfigurationUtility::getValue('invitation/notifyAdmin', $this->settings);
if ($notifyAdmin) {
$this->sendMailService->send(
'invitationNotify',
StringUtility::makeEmailArray(
$notifyAdmin,
ConfigurationUtility::getValue('invitation/email/invitationAdminNotify/receiver/name/value', $this->settings)
),
StringUtility::makeEmailArray($user->getEmail(), $user->getUsername()),
'Profile creation with invitation - Final',
[
'user' => $user,
'settings' => $this->settings
],
ConfigurationUtility::getValue('invitation./email./invitationAdminNotify.', $this->config)
);
}
$user = UserUtility::overrideUserGroup($user, $this->settings, 'invitation');
UserUtility::hashPassword($user, ConfigurationUtility::getValue('invitation/misc/passwordSave', $this->settings));
$this->userRepository->update($user);
$this->persistenceManager->persistAll();
$this->eventDispatcher->dispatch(new InviteUserUpdateEvent($user));
$this->redirectByAction('invitation', 'redirectPasswordChanged');
$this->redirect('status');
}
/**
* action delete
*
* @param int $user User UID
* @param string $hash
*/
public function deleteAction($user, $hash = null)
{
$user = $this->userRepository->findByUid($user);
if ($user !== null && HashUtility::validHash($hash, $user)) {
$this->logUtility->log(Log::STATUS_PROFILEDELETE, $user);
$this->addFlashMessage(LocalizationUtility::translateByState(Log::STATUS_INVITATIONPROFILEDELETEDUSER));
// send notify email to admin
$notifyAdmin = ConfigurationUtility::getValue('invitation/notifyAdmin', $this->settings);
if ($notifyAdmin) {
$this->sendMailService->send(
'invitationRefused',
StringUtility::makeEmailArray(
$notifyAdmin,
ConfigurationUtility::getValue('invitation/email/invitationRefused/receiver/name/value', $this->settings)
),
StringUtility::makeEmailArray($user->getEmail(), $user->getUsername()),
'Profile deleted from User after invitation - Step 1',
[
'user' => $user,
'settings' => $this->settings
],
$this->config['invitation.']['email.']['invitationRefused.']
);
}
$this->userRepository->remove($user);
$this->redirectByAction('invitation', 'redirectDelete');
$this->redirect('status');
} else {
$this->addFlashMessage(
LocalizationUtility::translateByState(Log::STATUS_INVITATIONHASHERROR),
'',
AbstractMessage::ERROR
);
$this->redirect('status');
}
}
/**
* Restricted Action to show messages
*/
public function statusAction(): ResponseInterface
{
return $this->htmlResponse();
}
/**
* Check if user is allowed to see this action
*
* @return true|ForwardResponse
*/
protected function allowedUserForInvitationNewAndCreate()
{
if (empty($this->settings['invitation']['allowedUserGroups'] ?? [])) {
return true;
}
$allowedUsergroupUids = GeneralUtility::trimExplode(
',',
$this->settings['invitation']['allowedUserGroups'] ?? [],
true
);
$currentUsergroupUids = UserUtility::getCurrentUsergroupUids();
// compare allowedUsergroups with currentUsergroups
if (count(array_intersect($allowedUsergroupUids, $currentUsergroupUids))) {
return true;
}
// current user is not allowed
$this->addFlashMessage(
LocalizationUtility::translateByState(Log::STATUS_INVITATIONRESTRICTEDPAGE),
'',
AbstractMessage::ERROR
);
return new ForwardResponse('status');
}
}
@@ -0,0 +1,562 @@
<?php
declare(strict_types=1);
namespace In2code\Femanager\Controller;
use In2code\Femanager\Domain\Model\Log;
use In2code\Femanager\Domain\Model\User;
use In2code\Femanager\Domain\Service\AutoAdminConfirmationService;
use In2code\Femanager\Event\BeforeUserConfirmEvent;
use In2code\Femanager\Event\BeforeUserCreateEvent;
use In2code\Femanager\Event\CreateConfirmationRequestEvent;
use In2code\Femanager\Utility\ConfigurationUtility;
use In2code\Femanager\Utility\FrontendUtility;
use In2code\Femanager\Utility\HashUtility;
use In2code\Femanager\Utility\LocalizationUtility;
use In2code\Femanager\Utility\StringUtility;
use In2code\Femanager\Utility\UserUtility;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Crypto\PasswordHashing\InvalidPasswordHashException;
use TYPO3\CMS\Core\Http\ServerRequestFactory;
use TYPO3\CMS\Core\Messaging\AbstractMessage;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Annotation\Validate;
use TYPO3\CMS\Extbase\Event\Mvc\AfterRequestDispatchedEvent;
use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
use TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException;
use TYPO3\CMS\Extbase\Persistence\Exception\IllegalObjectTypeException;
/**
* Class NewController
*/
class NewController extends AbstractFrontendController
{
/**
* Render registration form
*
*/
public function newAction(): ResponseInterface
{
$this->view->assignMultiple(
[
'allUserGroups' => $this->allUserGroups
]
);
$this->assignForAll();
return $this->htmlResponse();
}
/**
* action create
*
* @param User $user
* @throws InvalidPasswordHashException
* @throws StopActionException
* @Validate("In2code\Femanager\Domain\Validator\ServersideValidator", param="user")
* @Validate("In2code\Femanager\Domain\Validator\PasswordValidator", param="user")
* @Validate("In2code\Femanager\Domain\Validator\CaptchaValidator", param="user")
*/
public function createAction(User $user)
{
if ($this->ratelimiterService->isLimited()) {
$this->addFlashMessage(
LocalizationUtility::translate('ratelimiter_too_many_attempts'),
'',
AbstractMessage::ERROR
);
$this->redirect('createStatus');
}
$user = UserUtility::overrideUserGroup($user, $this->settings);
$configuration = ConfigurationUtility::getValue('new./forceValues./beforeAnyConfirmation.', $this->config);
$user = FrontendUtility::forceValues($user, $configuration);
$user = UserUtility::fallbackUsernameAndPassword($user);
$user = UserUtility::takeEmailAsUsername($user, $this->settings);
UserUtility::hashPassword($user, ConfigurationUtility::getValue('new/misc/passwordSave', $this->settings));
$this->eventDispatcher->dispatch(new BeforeUserCreateEvent($user));
$this->ratelimiterService->consumeSlot();
if ($this->isAllConfirmed()) {
$this->createAllConfirmed($user);
} else {
$this->createRequest($user);
}
$this->redirect('createStatus');
}
/**
* Dispatcher action for every confirmation request
*
* @param int $user User UID (user could be hidden)
* @param string $hash Given hash
* @param string $status
* "userConfirmation", "userConfirmationRefused", "adminConfirmation",
* "adminConfirmationRefused", "adminConfirmationRefusedSilent"
* @param string $adminHash
* @throws IllegalObjectTypeException
* @throws StopActionException
*/
public function confirmCreateRequestAction(int $user, string $hash, string $status = 'adminConfirmation', string $adminHash = null)
{
$backend = false;
$furtherFunctions = false;
$user = $this->userRepository->findByUid($user);
$this->eventDispatcher->dispatch(new BeforeUserConfirmEvent($user, $hash, $status));
if ($user === null) {
$this->addFlashMessage(LocalizationUtility::translate('missingUserInDatabase'), '', AbstractMessage::ERROR);
$this->redirect('new');
}
$request = ServerRequestFactory::fromGlobals();
// check if the the request was triggered via Backend
if ($request->hasHeader('Accept')) {
$accept = $request->getHeader('Accept')[0];
if (false !== strpos($accept, 'application/json')) {
$backend = true;
}
}
switch ($status) {
case 'userConfirmation':
if ($status === 'userConfirmation' && ConfigurationUtility::getValue(
'new./email./createUserConfirmation./confirmUserConfirmation',
$this->config
) == '1') {
$this->view->assignMultiple(
[
'user' => $user,
'status' => 'confirmUser',
'hash' => $hash,
]
);
$this->assignForAll();
return $this->htmlResponse();
}
$furtherFunctions = $this->statusUserConfirmation($user, $hash, $status);
break;
case 'confirmDeletion':
$furtherFunctions = $this->statusUserConfirmationRefused($user, $hash);
break;
case 'confirmedByUser':
$furtherFunctions = $this->statusUserConfirmation($user, $hash, $status);
break;
case 'userConfirmationRefused':
if (ConfigurationUtility::getValue('new./email./createUserConfirmation./confirmUserConfirmationRefused', $this->config) == '1') {
$this->view->assignMultiple(
[
'user' => $user,
'status' => 'confirmDeletion',
'hash' =>$hash
]
);
$this->assignForAll();
return $this->htmlResponse();
}
$furtherFunctions = $this->statusUserConfirmationRefused($user, $hash);
break;
case 'adminConfirmation':
if ($status === 'adminConfirmation' && ConfigurationUtility::getValue(
'new./email./createUserConfirmation./confirmAdminConfirmation',
$this->config
) == '1') {
if (!HashUtility::validHash($adminHash, $user, 'admin')) {
$this->addFlashMessage(
LocalizationUtility::translate('error_not_authorized'),
'',
ContextualFeedbackSeverity::ERROR
);
throw new PropagateResponseException($this->redirect('new'), 1743766811);
}
$this->view->assignMultiple(
[
'user' => $user,
'status' => 'confirmAdmin',
'hash' => $hash,
]
);
$this->assignForAll();
return $this->htmlResponse();
}
if (($status === 'adminConfirmationRefused' || $status === 'adminConfirmationRefusedSilent') &&
ConfigurationUtility::getValue(
'new./email./createUserConfirmation./confirmAdminConfirmation',
$this->config
) == '1') {
if (!HashUtility::validHash($adminHash, $user, 'admin')) {
$this->addFlashMessage(
LocalizationUtility::translate('error_not_authorized'),
'',
ContextualFeedbackSeverity::ERROR
);
throw new PropagateResponseException($this->redirect('new'), 1743766811);
}
$this->view->assignMultiple(
[
'user' => $user,
'status' => 'confirmAdminRefused',
'silent' => $status === 'adminConfirmationRefusedSilent',
'hash' => $hash,
]
);
$this->assignForAll();
return $this->htmlResponse();
}
$furtherFunctions = $this->statusAdminConfirmation($user, $hash, $status, $backend);
break;
case 'confirmedByAdmin':
$furtherFunctions = $this->statusAdminConfirmation($user, $hash, $status, $backend);
break;
case 'confirmedByAdminRefused':
$furtherFunctions = $this->statusAdminConfirmationRefused($user, $hash, $status, $backend);
break;
case 'adminConfirmationRefused':
// Admin refuses profile
if (ConfigurationUtility::getValue(
'new./email./createUserConfirmation./confirmAdminConfirmation',
$this->config
) == '1') {
$this->view->assignMultiple(
[
'user' => $user,
'status' => 'confirmAdminRefused',
'hash' => $hash,
]
);
$this->assignForAll();
return $this->htmlResponse();
}
break;
case 'adminConfirmationRefusedSilent':
// Admin refuses profile
if (ConfigurationUtility::getValue(
'new./email./createUserConfirmation./confirmAdminConfirmation',
$this->config
) == '1') {
$this->view->assignMultiple(
[
'user' => $user,
'status' => 'confirmAdminRefused',
'hash' => $hash,
]
);
$this->assignForAll();
return $this->htmlResponse();
}
$furtherFunctions = $this->statusAdminConfirmationRefused($user, $hash, $status);
break;
case 'confirmedByAdminRefusedSilent':
$furtherFunctions = $this->statusAdminConfirmationRefused($user, $hash, $status);
break;
default:
$furtherFunctions = false;
}
if ($backend) {
$this->eventDispatcher->dispatch(new AfterRequestDispatchedEvent($this->request, $this->response));
$this->persistenceManager->persistAll();
// this request was triggered via Backend Module "Frontend users", so we stop here and provide a feedback to the BE
echo json_encode(['status' => 'okay']) . PHP_EOL;
die();
}
if ($furtherFunctions) {
$this->redirectByAction('new', $status . 'Redirect');
}
$this->redirect('new');
}
/**
* Status action: User confirmation
*
* @param User $user
* @param string $hash
* @param string $status
* @return bool allow further functions
* @throws UnsupportedRequestTypeException
* @throws IllegalObjectTypeException
*/
protected function statusUserConfirmation(User $user, string $hash, string $status)
{
if (HashUtility::validHash($hash, $user)) {
if ($user->getTxFemanagerConfirmedbyuser()) {
$this->addFlashMessage(LocalizationUtility::translate('userAlreadyConfirmed'), '', AbstractMessage::ERROR);
$this->redirect('new');
}
$user = FrontendUtility::forceValues($user, ConfigurationUtility::getValue('new./forceValues./onUserConfirmation.', $this->config));
$user->setTxFemanagerConfirmedbyuser(true);
$this->userRepository->update($user);
$this->persistenceManager->persistAll();
$this->logUtility->log(Log::STATUS_REGISTRATIONCONFIRMEDUSER, $user);
if ($this->isAdminConfirmationMissing($user)) {
$this->createAdminConfirmationRequest($user);
} else {
$user->setDisable(false);
$this->logUtility->log(Log::STATUS_NEWREGISTRATION, $user);
$this->finalCreate($user, 'new', 'createStatus', true, $status);
}
} else {
$this->addFlashMessage(LocalizationUtility::translate('createFailedProfile'), '', AbstractMessage::ERROR);
return false;
}
return true;
}
/**
* Status action: User confirmation refused
*
* @param User $user
* @param string $hash
* @return bool allow further functions
* @throws IllegalObjectTypeException
*/
protected function statusUserConfirmationRefused(User $user, $hash)
{
if (HashUtility::validHash($hash, $user)) {
$this->logUtility->log(Log::STATUS_REGISTRATIONREFUSEDUSER, $user);
$this->addFlashMessage(LocalizationUtility::translate('createProfileDeleted'));
$this->userRepository->remove($user);
} else {
$this->addFlashMessage(LocalizationUtility::translate('createFailedProfile'), '', AbstractMessage::ERROR);
return false;
}
return true;
}
/**
* Status action: Admin confirmation
*
* @param User $user
* @param string $hash
* @param string $status
* @return bool allow further functions
*/
protected function statusAdminConfirmation(User $user, $hash, $status, $backend = false)
{
if (HashUtility::validHash($hash, $user)) {
if ($user->getTxFemanagerConfirmedbyadmin()) {
$this->addFlashMessage(LocalizationUtility::translate('userAlreadyConfirmed'), '', AbstractMessage::ERROR);
$this->redirect('new');
}
$user = FrontendUtility::forceValues($user, ConfigurationUtility::getValue('new./forceValues./onAdminConfirmation.', $this->config));
$user->setTxFemanagerConfirmedbyadmin(true);
$user->setDisable(false);
$this->userRepository->update($user);
$this->logUtility->log(Log::STATUS_REGISTRATIONCONFIRMEDADMIN, $user);
$this->finalCreate($user, 'new', 'createStatus', false, $status, $backend);
} else {
$this->addFlashMessage(LocalizationUtility::translate('createFailedProfile'), '', AbstractMessage::ERROR);
return false;
}
return true;
}
/**
* Status action: Admin refused profile creation (normal or silent)
*
* @param User $user
* @param $hash
* @param $status
* @return bool allow further functions
* @throws IllegalObjectTypeException
*/
protected function statusAdminConfirmationRefused(User $user, $hash, $status)
{
if (HashUtility::validHash($hash, $user)) {
$this->logUtility->log(Log::STATUS_REGISTRATIONREFUSEDADMIN, $user);
$this->addFlashMessage(LocalizationUtility::translate('createProfileDeleted'));
if ($status !== 'adminConfirmationRefusedSilent') {
$this->sendMailService->send(
'CreateUserNotifyRefused',
StringUtility::makeEmailArray(
$user->getEmail(),
$user->getFirstName() . ' ' . $user->getLastName()
),
['sender@femanager.org' => 'Sender Name'],
'Your profile was refused',
['user' => $user],
ConfigurationUtility::getValue('new./email./createUserNotifyRefused.', $this->config)
);
}
$this->userRepository->remove($user);
} else {
$this->addFlashMessage(LocalizationUtility::translate('createFailedProfile'), '', AbstractMessage::ERROR);
return false;
}
return true;
}
/**
* Just for showing informations after user creation
*/
public function createStatusAction(): ResponseInterface
{
$this->assignForAll();
return $this->htmlResponse();
}
/**
* Postfix method to createAction(): Create must be confirmed by Admin or User
*
* @param User $user
*/
protected function createRequest(User $user)
{
$user->setDisable(true);
$this->userRepository->add($user);
$this->persistenceManager->persistAll();
$this->logUtility->log(Log::STATUS_PROFILECREATIONREQUEST, $user);
if (!empty($this->settings['new']['confirmByUser'])) {
$this->createUserConfirmationRequest($user);
$this->redirectByAction('new', 'requestRedirect');
} elseif (!empty($this->settings['new']['confirmByAdmin'])) {
$this->createAdminConfirmationRequest($user);
$this->redirectByAction('new', 'requestRedirect');
}
}
/**
* Send email to user for confirmation
*
* @param User $user
* @throws UnsupportedRequestTypeException
*/
protected function createUserConfirmationRequest(User $user)
{
$this->sendCreateUserConfirmationMail($user);
$this->addFlashMessage(LocalizationUtility::translate('createRequestWaitingForUserConfirm'));
$this->redirectByAction('new', 'requestRedirect');
}
/**
* Send email to admin for confirmation
*
* @param User $user
* @throws UnsupportedRequestTypeException
*/
protected function createAdminConfirmationRequest(User $user)
{
$aacService = $this->objectManager->get(
AutoAdminConfirmationService::class,
$user,
$this->settings,
$this->contentObject
);
if ($aacService->isAutoAdminConfirmationFullfilled()) {
$user->setDisable(false);
$this->eventDispatcher->dispatch(
new CreateConfirmationRequestEvent($user, CreateConfirmationRequestEvent::MODE_AUTOMATIC)
);
$this->createAllConfirmed($user);
} else {
$this->eventDispatcher->dispatch(
new CreateConfirmationRequestEvent($user, CreateConfirmationRequestEvent::MODE_MANUAL)
);
$this->sendMailService->send(
'createAdminConfirmation',
StringUtility::makeEmailArray(
$this->settings['new']['confirmByAdmin'] ?? '',
$this->settings['new']['email']['createAdminConfirmation']['receiver']['name']['value'] ?? ''
),
StringUtility::makeEmailArray($user->getEmail(), $user->getUsername()),
'New Registration request',
[
'user' => $user,
'hash' => HashUtility::createHashForUser($user),
'adminHash' => HashUtility::createHashForUser($user, 'admin'),
],
ConfigurationUtility::getValue('new./email./createAdminConfirmation.', $this->config)
);
$this->addFlashMessage(LocalizationUtility::translate('createRequestWaitingForAdminConfirm'));
}
}
/**
* @return bool
*/
protected function isAllConfirmed()
{
return empty($this->settings['new']['confirmByUser']) && empty($this->settings['new']['confirmByAdmin']);
}
/**
* @param User $user
* @return bool
*/
protected function isAdminConfirmationMissing(User $user)
{
return !empty($this->settings['new']['confirmByAdmin']) && !$user->getTxFemanagerConfirmedbyadmin();
}
/**
* Just for showing empty dialogue to resend confirmation mail
*/
public function resendConfirmationDialogueAction(): ResponseInterface
{
return $this->htmlResponse();
}
/**
* re-sends a confirmation email if given mail is valid
*
* @throws UnsupportedRequestTypeException
* @throws StopActionException
*/
public function resendConfirmationMailAction()
{
// @todo find a better way to fetch the data
$result = GeneralUtility::_GP('tx_femanager_pi1');
if (is_array($result)) {
if (GeneralUtility::validEmail($result['user']['email'])) {
$user = $this->userRepository->findFirstByEmail($result['user']['email']);
if (is_a($user, User::class)) {
$this->sendCreateUserConfirmationMail($user);
$this->addFlashMessage(
LocalizationUtility::translate('resendConfirmationMailSend'),
'',
AbstractMessage::INFO
);
$this->redirect('resendConfirmationDialogue');
}
}
}
$this->addFlashMessage(
LocalizationUtility::translate('resendConfirmationMailFail'),
LocalizationUtility::translate('validationError'),
AbstractMessage::ERROR
);
$this->redirect('resendConfirmationDialogue');
}
}
@@ -0,0 +1,276 @@
<?php
declare(strict_types=1);
namespace In2code\Femanager\Controller;
use In2code\Femanager\Domain\Model\User;
use In2code\Femanager\Event\AdminConfirmationUserEvent;
use In2code\Femanager\Event\RefuseUserEvent;
use In2code\Femanager\Utility\ConfigurationUtility;
use In2code\Femanager\Utility\HashUtility;
use In2code\Femanager\Utility\LocalizationUtility;
use In2code\Femanager\Utility\UserUtility;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\Core\Messaging\AbstractMessage;
use TYPO3\CMS\Core\Site\SiteFinder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Http\ForwardResponse;
/**
* Class UserBackendController
*/
class UserBackendController extends AbstractController
{
protected $configPID;
/**
* @param array $filter
*/
public function listAction(array $filter = []): ResponseInterface
{
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
$this->view->assignMultiple(
[
'users' => $this->userRepository->findAllInBackend($filter),
'moduleUri' => $uriBuilder->buildUriFromRoute('tce_db'),
'action' => 'list',
'loginAsEnabled' => $this->loginAsEnabled()
]
);
return $this->htmlResponse();
}
/**
* @param array $filter
*/
public function confirmationAction(array $filter = []): ResponseInterface
{
$this->configPID = $this->getConfigPID();
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
$this->view->assignMultiple(
[
'users' => $this->userRepository->findAllInBackendForConfirmation(
$filter,
ConfigurationUtility::isBackendModuleFilterUserConfirmation()
),
'moduleUri' => $uriBuilder->buildUriFromRoute('tce_db'),
'action' => 'confirmation'
]
);
return $this->htmlResponse();
}
/**
* @param User $user
*/
public function userLogoutAction(User $user)
{
$queryParams = $this->request->getQueryParams();
if (empty($queryParams['id']) || $queryParams['id'] !== (string)$user->getPid()) {
return new ForwardResponse('list');
}
UserUtility::removeFrontendSessionToUser($user);
$this->addFlashMessage('User successfully logged out');
$this->redirect('list');
}
/**
* @param int $userIdentifier
*/
public function confirmUserAction(int $userIdentifier)
{
$this->configPID = $this->getConfigPID();
$user = $this->userRepository->findByUid($userIdentifier);
$queryParams = $this->request->getQueryParams();
if (empty($queryParams['id']) || $user === null || $queryParams['id'] !== (string)$user->getPid()) {
return new ForwardResponse('list');
}
$this->eventDispatcher->dispatch(new AdminConfirmationUserEvent($user));
$jsonResult = $this->getFrontendRequestResult('adminConfirmation', $userIdentifier, $user);
if ($jsonResult['status'] ?? false) {
$this->addFlashMessage(
LocalizationUtility::translate(
'BackendConfirmationFlashMessageConfirmed',
'femanager',
[$user->getUsername()]
),
'User Confirmation'
);
} else {
$this->addFlashMessage(
LocalizationUtility::translate(
'BackendConfirmationFlashMessageFailed',
'femanager',
[$user->getUsername()]
),
'User Confirmation',
AbstractMessage::ERROR
);
}
$this->redirect('confirmation');
}
/**
* @param int $userIdentifier
*/
public function refuseUserAction(int $userIdentifier)
{
$this->configPID = $this->getConfigPID();
$user = $this->userRepository->findByUid($userIdentifier);
$queryParams = $this->request->getQueryParams();
if (empty($queryParams['id']) || $user === null || $queryParams['id'] !== (string)$user->getPid()) {
return new ForwardResponse('list');
}
$this->eventDispatcher->dispatch(new RefuseUserEvent($user));
$jsonResult = $this->getFrontendRequestResult('adminConfirmationRefused', $userIdentifier, $user);
if ($jsonResult['status'] ?? false) {
$this->addFlashMessage(
LocalizationUtility::translate(
'BackendConfirmationFlashMessageRefused',
'femanager',
[$user->getUsername()]
)
);
} else {
$this->addFlashMessage(
LocalizationUtility::translate(
'BackendConfirmationFlashMessageFailed',
'femanager',
[$user->getUsername()]
),
'User Confirmation',
AbstractMessage::ERROR
);
}
$this->redirect('confirmation');
}
/**
* @param array $filter
*/
public function listOpenUserConfirmationsAction(array $filter = []): ResponseInterface
{
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
$this->view->assignMultiple(
[
'users' => $this->userRepository->findAllInBackendForConfirmation(
$filter,
false
),
'moduleUri' => $uriBuilder->buildUriFromRoute('tce_db'),
'action' => 'listOpenUserConfirmations'
]
);
return $this->htmlResponse();
}
/**
* @param int $userIdentifier
*/
public function resendUserConfirmationRequestAction(int $userIdentifier)
{
$user = $this->userRepository->findByUid($userIdentifier);
$queryParams = $this->request->getQueryParams();
if (empty($queryParams['id']) || $user === null || $queryParams['id'] !== (string)$user->getPid()) {
return new ForwardResponse('list');
}
$this->sendCreateUserConfirmationMail($user);
$this->addFlashMessage(
LocalizationUtility::translate(
'BackendConfirmationFlashMessageReSend',
'femanager',
[$user->getUsername()]
),
'',
AbstractMessage::OK
);
$this->redirect('listOpenUserConfirmations');
}
/**
* @return int
*/
public function getConfigPID()
{
if (isset($this->moduleConfig['settings.']['configPID']) && $this->moduleConfig['settings.']['configPID'] > 0) {
return (int)$this->moduleConfig['settings.']['configPID'];
}
$this->addFlashMessage(
LocalizationUtility::translate(
'BackendMissingConfigPID',
'femanager'
),
'Backend Configuration',
AbstractMessage::ERROR
);
return 0;
}
/**
* @param $status
* @param $userIdentifier
* @param $user
*/
public function getFrontendRequestResult($status, $userIdentifier, $user)
{
/** @var SiteFinder $siteFinder */
$siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
$site = $siteFinder->getSiteByPageId($this->configPID);
$url = $site->getRouter()->generateUri(
$this->configPID,
[
'tx_femanager_pi1' => [
'user' => $userIdentifier,
'hash' => HashUtility::createHashForUser($user),
'status' => $status,
'action' => 'confirmCreateRequest',
'controller' => 'New'
]
]
);
$response = GeneralUtility::makeInstance(RequestFactory::class)->request((string)$url, 'GET', ['headers' => ['accept' => 'application/json']]);
if ($response->getStatusCode() >= 300) {
$content = $response->getReasonPhrase();
$GLOBALS['BE_USER']->writelog(4, 0, 1, 0, 'femanager: Frontend request failed.', $content);
$this->addFlashMessage(
LocalizationUtility::translate(
'BackendConfirmationFlashMessageFailed',
'femanager'
),
'User Confirmation',
AbstractMessage::ERROR
);
} else {
$content = $response->getBody()->getContents();
return json_decode($content, true);
}
}
private function loginAsEnabled(): bool
{
if ($GLOBALS['BE_USER']->user['admin'] === 1) {
return true;
}
$tsConfigEnableLoginAs = (int)($GLOBALS['BE_USER']->getTSConfig()['tx_femanager.']['UserBackend.']['enableLoginAs'] ?? 0);
return $tsConfigEnableLoginAs === 1;
}
}
@@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
namespace In2code\Femanager\Controller;
use In2code\Femanager\Domain\Model\Log;
use In2code\Femanager\Domain\Model\User;
use In2code\Femanager\Domain\Validator\ClientsideValidator;
use In2code\Femanager\Event\ImpersonateEvent;
use In2code\Femanager\Utility\BackendUserUtility;
use In2code\Femanager\Utility\FrontendUtility;
use In2code\Femanager\Utility\LocalizationUtility;
use In2code\Femanager\Utility\UserUtility;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Error\Http\UnauthorizedException;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
/**
* Class UserController
*/
class UserController extends AbstractFrontendController
{
/**
* @param array $filter
*/
public function listAction(array $filter = []): ResponseInterface
{
$this->view->assignMultiple(
[
'users' => $this->userRepository->findByUsergroups(
$this->settings['list']['usergroup'] ?? '',
$this->settings,
$filter
),
'filter' => $filter
]
);
$this->assignForAll();
return $this->htmlResponse();
}
/**
* Enforce user setting from FlexForm and ignore &tx_femanager_pi1[user]=421
*/
public function initializeShowAction()
{
$arguments = $this->request->getArguments();
if (!empty($this->settings['show']['user']) ?? '') {
unset($arguments['user']);
}
$this->request->setArguments($arguments);
}
/**
* @param User $user
*/
public function showAction(User $user = null): ResponseInterface
{
$this->view->assign('user', $this->getUser($user));
$this->assignForAll();
return $this->htmlResponse();
}
/**
* @param User $user
* @throws \Exception
*/
public function imageDeleteAction(User $user)
{
if (UserUtility::getCurrentUser() !== $user) {
throw new UnauthorizedException('You are not allowed to delete this image', 1516373759972);
}
$user->setImage($this->objectManager->get(ObjectStorage::class));
$this->userRepository->update($user);
$this->logUtility->log(Log::STATUS_PROFILEUPDATEIMAGEDELETE, $user);
$this->addFlashMessage(LocalizationUtility::translateByState(Log::STATUS_PROFILEUPDATEIMAGEDELETE));
$this->redirectToUri(FrontendUtility::getUriToCurrentPage());
}
/**
* Call this Action from eID to validate field values
*
* @param string $validation Validation string like "required, email, min(10)"
* @param string $value Given Field value
* @param string $field Fieldname like "username" or "email"
* @param User $user Existing User
* @param string $additionalValue Additional Values
* @param int $plugin tt_content.uid of the femanager plugin
* @param string $referrerAction current action name
*/
public function validateAction(
$validation = null,
$value = null,
$field = null,
User $user = null,
$additionalValue = '',
int $plugin = 0,
string $referrerAction = ''
): ResponseInterface {
$clientsideValidator = $this->objectManager->get(ClientsideValidator::class);
$result = $clientsideValidator
->setValidationSettingsString($validation)
->setValue($value)
->setFieldName($field)
->setUser($user)
->setAdditionalValue($additionalValue)
->setPlugin($plugin)
->setActionName($referrerAction)
->validateField();
$this->view->assignMultiple(
[
'isValid' => $result,
'messages' => $clientsideValidator->getMessages(),
'validation' => $validation,
'value' => $value,
'fieldname' => $field,
'user' => $user
]
);
return $this->htmlResponse();
}
/**
* Simulate frontenduser login for backend adminstrators only
*
* @param User $user
* @throws UnauthorizedException
*/
public function loginAsAction(User $user)
{
$this->eventDispatcher->dispatch(new ImpersonateEvent($user));
if (!BackendUserUtility::isAdminAuthentication()) {
throw new UnauthorizedException(LocalizationUtility::translate('error_not_authorized'), 1516373787864);
}
UserUtility::login($user);
$this->redirectByAction('loginAs', 'redirect');
$this->redirectToUri('/');
}
/**
* @param User $user
* @return User
*/
protected function getUser(User $user = null)
{
if ($user === null) {
if (is_numeric($this->settings['show']['user'])) {
$user = $this->userRepository->findByUid($this->settings['show']['user']);
} elseif ($this->settings['show']['user'] === '[this]') {
$user = $this->user;
}
}
return $user;
}
}