diff --git a/Classes/ViewHelpers/Form/RegisterFieldViewHelper.php b/Classes/ViewHelpers/Form/RegisterFieldViewHelper.php new file mode 100644 index 00000000..475630ac --- /dev/null +++ b/Classes/ViewHelpers/Form/RegisterFieldViewHelper.php @@ -0,0 +1,76 @@ +registerArgument('name', 'string', 'Name of input tag'); + $this->registerArgument('value', 'mixed', 'Value of input tag'); + $this->registerArgument( + 'property', + 'string', + 'Name of Object Property. If used in conjunction with , "name" and "value" properties will be ignored.' + ); + $this->registerArgument('additionalAttributes', 'array', 'Additional tag attributes. They will be added directly to the resulting HTML tag.', false); + $this->registerArgument('checked', 'bool', 'Specifies that the input element should be preselected'); + $this->registerArgument('multiple', 'bool', 'Specifies whether this checkbox belongs to a multivalue (is part of a checkbox group)', false, false); + } + + /** + * @return string|void + */ + public function render() + { + $nameAttribute = $this->getName(); + + $checked = $this->arguments['checked']; + $multiple = $this->arguments['multiple']; + + $valueAttribute = $this->getValueAttribute(); + $propertyValue = null; + if ($this->hasMappingErrorOccurred()) { + $propertyValue = $this->getLastSubmittedFormData(); + } + if ($checked === null && $propertyValue === null) { + $propertyValue = $this->getPropertyValue(); + } + + if ($propertyValue instanceof \Traversable) { + $propertyValue = iterator_to_array($propertyValue); + } + if (is_array($propertyValue)) { + $propertyValue = array_map([$this, 'convertToPlainValue'], $propertyValue); + if ($checked === null) { + $checked = in_array($valueAttribute, $propertyValue); + } + $nameAttribute .= '[]'; + } elseif ($multiple === true) { + $nameAttribute .= '[]'; + } elseif ($propertyValue !== null) { + $checked = (bool)$propertyValue === (bool)$valueAttribute; + } + + $this->registerFieldNameForFormTokenGeneration($nameAttribute); + } +} diff --git a/Classes/ViewHelpers/LoginFormViewHelper.php b/Classes/ViewHelpers/LoginFormViewHelper.php new file mode 100644 index 00000000..6276fe51 --- /dev/null +++ b/Classes/ViewHelpers/LoginFormViewHelper.php @@ -0,0 +1,273 @@ +` Tag. + * + * Basic usage + * =========== + * + * Use :html:`` to output an HTML :html:`
` tag which is targeted + * at the specified action, in the current controller and package. + * It will submit the form data via a POST request. If you want to change this, + * use :html:`method="get"` as an argument. + * + * Examples + * ======== + * + * A complex form with a specified encoding type + * --------------------------------------------- + * + * Form with enctype set:: + * + * ... + * + * A Form which should render a domain object + * ------------------------------------------ + * + * Binding a domain object to a form:: + * + * + * + * + * + * + * This automatically inserts the value of ``{customer.name}`` inside the + * textbox and adjusts the name of the textbox accordingly. + */ +class LoginFormViewHelper extends \TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper +{ + /** + * @var array + */ + protected $data = []; + + protected $i = 0; + + /** + * Render the form. + * + * @return string rendered form + */ + public function render() + { + $this->setFormActionUri(); + if (isset($this->arguments['method']) && strtolower($this->arguments['method']) === 'get') { + $this->tag->addAttribute('method', 'get'); + } else { + $this->tag->addAttribute('method', 'post'); + } + + if (isset($this->arguments['novalidate']) && $this->arguments['novalidate'] === true) { + $this->tag->addAttribute('novalidate', 'novalidate'); + } + + $this->addFormObjectNameToViewHelperVariableContainer(); + $this->addFormObjectToViewHelperVariableContainer(); + $this->addFieldNamePrefixToViewHelperVariableContainer(); + $this->addFormFieldNamesToViewHelperVariableContainer(); + $this->data['pages'] = $this->renderChildren(); + + $this->renderHiddenIdentityField($this->arguments['object'] ?? null, $this->getFormObjectName()); + $this->renderAdditionalIdentityFields(); + $this->renderHiddenReferrerFields(); + + // Render the trusted list of all properties after everything else has been rendered + $this->renderTrustedPropertiesField(); + + $this->removeFieldNamePrefixFromViewHelperVariableContainer(); + $this->removeFormObjectFromViewHelperVariableContainer(); + $this->removeFormObjectNameFromViewHelperVariableContainer(); + $this->removeFormFieldNamesFromViewHelperVariableContainer(); + $this->removeCheckboxFieldNamesFromViewHelperVariableContainer(); + + return json_encode($this->data); + } + + /** + * Sets the "action" attribute of the form tag + */ + protected function setFormActionUri() + { + if ($this->hasArgument('actionUri')) { + $formActionUri = $this->arguments['actionUri']; + } else { + if (isset($this->arguments['noCacheHash'])) { + trigger_error( + 'Using the argument "noCacheHash" in ViewHelper has no effect anymore. Remove the argument in your fluid template, as it will result in a fatal error.', + E_USER_DEPRECATED + ); + } + /** @var UriBuilder $uriBuilder */ + $uriBuilder = $this->renderingContext->getControllerContext()->getUriBuilder(); + $uriBuilder + ->reset() + ->setTargetPageType($this->arguments['pageType'] ?? 0) + ->setNoCache($this->arguments['noCache'] ?? false) + ->setSection($this->arguments['section'] ?? '') + ->setCreateAbsoluteUri($this->arguments['absolute'] ?? false) + ->setArguments(isset($this->arguments['additionalParams']) ? (array)$this->arguments['additionalParams'] : []) + ->setAddQueryString($this->arguments['addQueryString'] ?? false) + ->setArgumentsToBeExcludedFromQueryString(isset($this->arguments['argumentsToBeExcludedFromQueryString']) ? (array)$this->arguments['argumentsToBeExcludedFromQueryString'] : []) + ->setFormat($this->arguments['format'] ?? ''); + + $addQueryStringMethod = $this->arguments['addQueryStringMethod'] ?? null; + if (is_string($addQueryStringMethod)) { + $uriBuilder->setAddQueryStringMethod($addQueryStringMethod); + } + + $pageUid = (int)($this->arguments['pageUid'] ?? 0); + if ($pageUid > 0) { + $uriBuilder->setTargetPageUid($pageUid); + } + + $formActionUri = $uriBuilder->uriFor( + $this->arguments['action'] ?? null, + $this->arguments['arguments'] ?? [], + $this->arguments['controller'] ?? null, + $this->arguments['extensionName'] ?? null, + $this->arguments['pluginName'] ?? null + ); + $this->formActionUriArguments = $uriBuilder->getArguments(); + } + $this->data['action'] = $formActionUri; + } + + /** + * Render additional identity fields which were registered by form elements. + * This happens if a form field is defined like property="bla.blubb" - then we might need an identity property for the sub-object "bla". + * + * @return string HTML-string for the additional identity properties + */ + protected function renderAdditionalIdentityFields() + { + if ($this->viewHelperVariableContainer->exists(\TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'additionalIdentityProperties')) { + $additionalIdentityProperties = $this->viewHelperVariableContainer->get(\TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'additionalIdentityProperties'); + $output = ''; + foreach ($additionalIdentityProperties as $identity) { + $this->addHiddenField('identity', $identity); + } + } + + return ''; + } + + /** + * Renders hidden form fields for referrer information about + * the current controller and action. + * + * @return string Hidden fields with referrer information + * @todo filter out referrer information that is equal to the target (e.g. same packageKey) + */ + protected function renderHiddenReferrerFields() + { + $request = $this->renderingContext->getControllerContext() + ->getRequest(); + $extensionName = $request->getControllerExtensionName(); + $controllerName = $request->getControllerName(); + $actionName = $request->getControllerActionName(); + $actionRequest = [ + '@extension' => $extensionName, + '@controller' => $controllerName, + '@action' => $actionName, + ]; + $this->addHiddenField( + '__referrer[@extension]', + $extensionName + ); + $this->addHiddenField( + '__referrer[@controller]', + $controllerName + ); + $this->addHiddenField( + '__referrer[@action]', + $actionName + ); + $this->addHiddenField( + '__referrer[@request]', + $this->hashService->appendHmac(json_encode($actionRequest)) + ); + } + + /** + * Adds the field name prefix to the ViewHelperVariableContainer + */ + protected function addFieldNamePrefixToViewHelperVariableContainer() + { + $fieldNamePrefix = $this->getFieldNamePrefix(); + $this->viewHelperVariableContainer->add(\TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'fieldNamePrefix', $fieldNamePrefix); + } + + /** + * Renders a hidden form field containing the technical identity of the given object. + * + * @param object $object Object to create the identity field for + * @param string $name Name + * + * @return string A hidden field containing the Identity (UID in TYPO3 Flow, uid in Extbase) of the given object or NULL if the object is unknown to the persistence framework + * @see \TYPO3\CMS\Extbase\Mvc\Controller\Argument::setValue() + */ + protected function renderHiddenIdentityField($object, $name) + { + if ($object instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy) { + $object = $object->_loadRealInstance(); + } + if (!is_object($object) + || !($object instanceof \TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject) + || ($object->_isNew() && !$object->_isClone()) + ) { + return ''; + } + // Intentionally NOT using PersistenceManager::getIdentifierByObject here!! + // Using that one breaks re-submission of data in forms in case of an error. + $identifier = $object->getUid(); + if ($identifier === null) { + return ''; + } + $name = $this->prefixFieldName($name) . '[__identity]'; + $this->registerFieldNameForFormTokenGeneration($name); + + $this->addHiddenField($name, $identifier); + } + + /** + * Render the request hash field + */ + protected function renderTrustedPropertiesField() + { + $formFieldNames + = $this->viewHelperVariableContainer->get( + \TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, + 'formFieldNames' + ); + $requestHash + = $this->mvcPropertyMappingConfigurationService->generateTrustedPropertiesToken( + $formFieldNames, + $this->getFieldNamePrefix() + ); + $this->addHiddenField('__trustedProperties', $requestHash); + } + + protected function addHiddenField($name, $value) + { + $tmp = []; + $tmp['name'] = $name; + $tmp['type'] = 'hidden'; + $tmp['value'] = $value; + $this->data['pages'][] = $tmp; + } +} diff --git a/Configuration/TypoScript/Configuration/FeLogin.typoscript b/Configuration/TypoScript/Configuration/FeLogin.typoscript new file mode 100644 index 00000000..6b785af4 --- /dev/null +++ b/Configuration/TypoScript/Configuration/FeLogin.typoscript @@ -0,0 +1,7 @@ +plugin.tx_felogin_login { + view { + templateRootPaths.10 = EXT:headless/Resources/Private/FeLogin/Templates/ + partialRootPaths.10 = EXT:headless/Resources/Private/FeLogin/Partials/ + layoutRootPaths.10 = EXT:headless//Resources/Private/FeLogin/Layouts/ + } +} diff --git a/Configuration/TypoScript/ContentElement/FeLogin.typoscript b/Configuration/TypoScript/ContentElement/FeLogin.typoscript new file mode 100644 index 00000000..10b77fd9 --- /dev/null +++ b/Configuration/TypoScript/ContentElement/FeLogin.typoscript @@ -0,0 +1,20 @@ +tt_content.felogin_login =< lib.contentElementWithHeader +tt_content.felogin_login { + fields { + content { + fields { + data = USER + data { + userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run + vendorName = TYPO3\CMS + extensionName = Felogin + pluginName = Login + controller = Login + persistence < plugin.tx_felogin_login.persistence + settings < plugin.tx_felogin_login.settings + stdWrap.trim = 1 + } + } + } + } +} diff --git a/Resources/Private/FeLogin/Partials/FlashMessages.html b/Resources/Private/FeLogin/Partials/FlashMessages.html new file mode 100644 index 00000000..48b22f84 --- /dev/null +++ b/Resources/Private/FeLogin/Partials/FlashMessages.html @@ -0,0 +1,29 @@ + + + + [ + + + {f:if(condition: msgIterator.isLast, else: ',')} + + ] + + + + + + + notice + info + success + warning + danger + + + + diff --git a/Resources/Private/FeLogin/Partials/LoginFields.html b/Resources/Private/FeLogin/Partials/LoginFields.html new file mode 100644 index 00000000..be85573d --- /dev/null +++ b/Resources/Private/FeLogin/Partials/LoginFields.html @@ -0,0 +1,103 @@ + + + + + + + [ + , + , + + + + , + + + , + + + + , + , + + , + , + , + , + + ] + + diff --git a/Resources/Private/FeLogin/Partials/LogoutFields.html b/Resources/Private/FeLogin/Partials/LogoutFields.html new file mode 100644 index 00000000..8a04f1bf --- /dev/null +++ b/Resources/Private/FeLogin/Partials/LogoutFields.html @@ -0,0 +1,26 @@ + + + + + [ + , + , + + ] + + \ No newline at end of file diff --git a/Resources/Private/FeLogin/Partials/Recovery.html b/Resources/Private/FeLogin/Partials/Recovery.html new file mode 100644 index 00000000..b5d25b02 --- /dev/null +++ b/Resources/Private/FeLogin/Partials/Recovery.html @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/Resources/Private/FeLogin/Partials/ValidationErrors.html b/Resources/Private/FeLogin/Partials/ValidationErrors.html new file mode 100644 index 00000000..a0ddd855 --- /dev/null +++ b/Resources/Private/FeLogin/Partials/ValidationErrors.html @@ -0,0 +1,27 @@ + + + + + + {f:if(condition: propertyIterator.isLast, else: ',')} + + + + + + + + [ + + + {f:if(condition: errorIterator.isLast, else: ',')} + + ] + + + diff --git a/Resources/Private/FeLogin/Templates/Login/Login.html b/Resources/Private/FeLogin/Templates/Login/Login.html new file mode 100644 index 00000000..e0fdf233 --- /dev/null +++ b/Resources/Private/FeLogin/Templates/Login/Login.html @@ -0,0 +1,18 @@ + + + + + diff --git a/Resources/Private/FeLogin/Templates/Login/Logout.html b/Resources/Private/FeLogin/Templates/Login/Logout.html new file mode 100644 index 00000000..ef4ce873 --- /dev/null +++ b/Resources/Private/FeLogin/Templates/Login/Logout.html @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/Resources/Private/FeLogin/Templates/PasswordRecovery/Recovery.html b/Resources/Private/FeLogin/Templates/PasswordRecovery/Recovery.html new file mode 100644 index 00000000..68b14c8b --- /dev/null +++ b/Resources/Private/FeLogin/Templates/PasswordRecovery/Recovery.html @@ -0,0 +1,52 @@ + + + + + + + + + [ + , + + ] + + + diff --git a/Resources/Private/FeLogin/Templates/PasswordRecovery/ShowChangePassword.html b/Resources/Private/FeLogin/Templates/PasswordRecovery/ShowChangePassword.html new file mode 100644 index 00000000..1434be91 --- /dev/null +++ b/Resources/Private/FeLogin/Templates/PasswordRecovery/ShowChangePassword.html @@ -0,0 +1,76 @@ + + + + + + + + + [ + , + , + , + + ] + + + +