first commit
This commit is contained in:
commit
1218dd1517
12716 files changed
+989233
No files matched your search
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Controller;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\PathUtility;
|
||||
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
||||
use TYPO3\CMS\Form\Mvc\Configuration\ConfigurationManagerInterface;
|
||||
|
||||
/**
|
||||
* The abstract form backend controller
|
||||
*
|
||||
* Scope: backend
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractBackendController extends ActionController
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $formSettings;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManagerInterface
|
||||
*/
|
||||
protected $formPersistenceManager;
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManagerInterface $formPersistenceManager
|
||||
* @internal
|
||||
*/
|
||||
public function injectFormPersistenceManager(\TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManagerInterface $formPersistenceManager)
|
||||
{
|
||||
$this->formPersistenceManager = $formPersistenceManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function initializeObject()
|
||||
{
|
||||
$this->formSettings = $this->objectManager->get(ConfigurationManagerInterface::class)
|
||||
->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_YAML_SETTINGS, 'form');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert arrays with EXT: resource paths to web paths
|
||||
*
|
||||
* Input:
|
||||
* [
|
||||
* 100 => 'EXT:form/Resources/Public/Css/form.css'
|
||||
* ]
|
||||
*
|
||||
* Output:
|
||||
*
|
||||
* [
|
||||
* 0 => 'typo3/sysext/form/Resources/Public/Css/form.css'
|
||||
* ]
|
||||
*
|
||||
* @param array $resourcePaths
|
||||
* @return array
|
||||
*/
|
||||
protected function resolveResourcePaths(array $resourcePaths): array
|
||||
{
|
||||
$return = [];
|
||||
foreach ($resourcePaths as $resourcePath) {
|
||||
$fullResourcePath = GeneralUtility::getFileAbsFileName($resourcePath);
|
||||
$resourcePath = PathUtility::getAbsoluteWebPath($fullResourcePath);
|
||||
if (empty($resourcePath)) {
|
||||
continue;
|
||||
}
|
||||
$return[] = $resourcePath;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,647 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Controller;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Backend\Routing\UriBuilder;
|
||||
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
|
||||
use TYPO3\CMS\Backend\View\BackendTemplateView;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Page\PageRenderer;
|
||||
use TYPO3\CMS\Core\Site\Entity\Site;
|
||||
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Mvc\View\JsonView;
|
||||
use TYPO3\CMS\Fluid\View\TemplateView;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ConfigurationService;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinitionConversionService;
|
||||
use TYPO3\CMS\Form\Domain\Exception\RenderingException;
|
||||
use TYPO3\CMS\Form\Domain\Factory\ArrayFormFactory;
|
||||
use TYPO3\CMS\Form\Exception;
|
||||
use TYPO3\CMS\Form\Mvc\Persistence\Exception\PersistenceManagerException;
|
||||
use TYPO3\CMS\Form\Service\TranslationService;
|
||||
use TYPO3\CMS\Form\Type\FormDefinitionArray;
|
||||
|
||||
/**
|
||||
* The form editor controller
|
||||
*
|
||||
* Scope: backend
|
||||
* @internal
|
||||
*/
|
||||
class FormEditorController extends AbstractBackendController
|
||||
{
|
||||
|
||||
/**
|
||||
* Default View Container
|
||||
*
|
||||
* @var BackendTemplateView
|
||||
*/
|
||||
protected $defaultViewObjectName = BackendTemplateView::class;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $prototypeConfiguration;
|
||||
|
||||
/**
|
||||
* Displays the form editor
|
||||
*
|
||||
* @param string $formPersistenceIdentifier
|
||||
* @param string $prototypeName
|
||||
* @throws PersistenceManagerException
|
||||
* @internal
|
||||
*/
|
||||
public function indexAction(string $formPersistenceIdentifier, string $prototypeName = null)
|
||||
{
|
||||
$this->registerDocheaderButtons();
|
||||
$this->view->getModuleTemplate()->setModuleName($this->request->getPluginName() . '_' . $this->request->getControllerName());
|
||||
$this->view->getModuleTemplate()->setFlashMessageQueue($this->controllerContext->getFlashMessageQueue());
|
||||
|
||||
if (!$this->formPersistenceManager->isAllowedPersistencePath($formPersistenceIdentifier)) {
|
||||
throw new PersistenceManagerException(sprintf('Read "%s" is not allowed', $formPersistenceIdentifier), 1614500662);
|
||||
}
|
||||
|
||||
if (
|
||||
strpos($formPersistenceIdentifier, 'EXT:') === 0
|
||||
&& !$this->formSettings['persistenceManager']['allowSaveToExtensionPaths']
|
||||
) {
|
||||
throw new PersistenceManagerException('Edit a extension formDefinition is not allowed.', 1478265661);
|
||||
}
|
||||
|
||||
$configurationService = $this->objectManager->get(ConfigurationService::class);
|
||||
$formDefinition = $this->formPersistenceManager->load($formPersistenceIdentifier);
|
||||
|
||||
if ($prototypeName === null) {
|
||||
$prototypeName = $formDefinition['prototypeName'] ?? 'standard';
|
||||
} else {
|
||||
// Loading a form definition with another prototype is currently not implemented but is planned in the future.
|
||||
// This safety check is a preventive measure.
|
||||
$selectablePrototypeNames = $configurationService->getSelectablePrototypeNamesDefinedInFormEditorSetup();
|
||||
if (!in_array($prototypeName, $selectablePrototypeNames, true)) {
|
||||
throw new Exception(sprintf('The prototype name "%s" is not configured within "formManager.selectablePrototypesConfiguration" ', $prototypeName), 1528625039);
|
||||
}
|
||||
}
|
||||
|
||||
$formDefinition['prototypeName'] = $prototypeName;
|
||||
$this->prototypeConfiguration = $configurationService->getPrototypeConfiguration($prototypeName);
|
||||
|
||||
$formDefinition = $this->transformFormDefinitionForFormEditor($formDefinition);
|
||||
$formEditorDefinitions = $this->getFormEditorDefinitions();
|
||||
|
||||
$formEditorAppInitialData = [
|
||||
'formEditorDefinitions' => $formEditorDefinitions,
|
||||
'formDefinition' => $formDefinition,
|
||||
'formPersistenceIdentifier' => $formPersistenceIdentifier,
|
||||
'prototypeName' => $prototypeName,
|
||||
'endpoints' => [
|
||||
'formPageRenderer' => $this->controllerContext->getUriBuilder()->uriFor('renderFormPage'),
|
||||
'saveForm' => $this->controllerContext->getUriBuilder()->uriFor('saveForm')
|
||||
],
|
||||
'additionalViewModelModules' => $this->prototypeConfiguration['formEditor']['dynamicRequireJsModules']['additionalViewModelModules'],
|
||||
'maximumUndoSteps' => $this->prototypeConfiguration['formEditor']['maximumUndoSteps'],
|
||||
];
|
||||
|
||||
$this->view->assign('formEditorAppInitialData', json_encode($formEditorAppInitialData));
|
||||
$this->view->assign('stylesheets', $this->resolveResourcePaths($this->prototypeConfiguration['formEditor']['stylesheets']));
|
||||
$this->view->assign('formEditorTemplates', $this->renderFormEditorTemplates($formEditorDefinitions));
|
||||
$this->view->assign('dynamicRequireJsModules', $this->prototypeConfiguration['formEditor']['dynamicRequireJsModules']);
|
||||
|
||||
$this->getPageRenderer()->addInlineLanguageLabelFile('EXT:form/Resources/Private/Language/locallang_formEditor_failSafeErrorHandling_javascript.xlf');
|
||||
|
||||
$popupWindowWidth = 700;
|
||||
$popupWindowHeight = 750;
|
||||
$popupWindowSize = \trim($this->getBackendUser()->getTSConfig()['options.']['popupWindowSize'] ?? '');
|
||||
if (!empty($popupWindowSize)) {
|
||||
list($popupWindowWidth, $popupWindowHeight) = GeneralUtility::intExplode('x', $popupWindowSize);
|
||||
}
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
$addInlineSettings = [
|
||||
'FormEditor' => [
|
||||
'typo3WinBrowserUrl' => (string)$uriBuilder->buildUriFromRoute('wizard_element_browser'),
|
||||
],
|
||||
'Popup' => [
|
||||
'PopupWindow' => [
|
||||
'width' => $popupWindowWidth,
|
||||
'height' => $popupWindowHeight
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
$addInlineSettings = array_replace_recursive(
|
||||
$addInlineSettings,
|
||||
$this->prototypeConfiguration['formEditor']['addInlineSettings']
|
||||
);
|
||||
$this->view->assign('addInlineSettings', $addInlineSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the save action.
|
||||
* This action uses the Fluid JsonView::class as view.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function initializeSaveFormAction()
|
||||
{
|
||||
$this->defaultViewObjectName = JsonView::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a formDefinition which was build by the form editor.
|
||||
*
|
||||
* @param string $formPersistenceIdentifier
|
||||
* @param FormDefinitionArray $formDefinition
|
||||
* @internal
|
||||
*/
|
||||
public function saveFormAction(string $formPersistenceIdentifier, FormDefinitionArray $formDefinition)
|
||||
{
|
||||
$formDefinition = $formDefinition->getArrayCopy();
|
||||
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormSave'] ?? [] as $className) {
|
||||
$hookObj = GeneralUtility::makeInstance($className);
|
||||
if (method_exists($hookObj, 'beforeFormSave')) {
|
||||
$formDefinition = $hookObj->beforeFormSave(
|
||||
$formPersistenceIdentifier,
|
||||
$formDefinition
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$response = [
|
||||
'status' => 'success',
|
||||
];
|
||||
|
||||
try {
|
||||
if (!$this->formPersistenceManager->isAllowedPersistencePath($formPersistenceIdentifier)) {
|
||||
throw new PersistenceManagerException(sprintf('Save "%s" is not allowed', $formPersistenceIdentifier), 1614500663);
|
||||
}
|
||||
$this->formPersistenceManager->save($formPersistenceIdentifier, $formDefinition);
|
||||
$configurationService = $this->objectManager->get(ConfigurationService::class);
|
||||
$this->prototypeConfiguration = $configurationService->getPrototypeConfiguration($formDefinition['prototypeName']);
|
||||
$formDefinition = $this->transformFormDefinitionForFormEditor($formDefinition);
|
||||
$response['formDefinition'] = $formDefinition;
|
||||
} catch (PersistenceManagerException $e) {
|
||||
$response = [
|
||||
'status' => 'error',
|
||||
'message' => $e->getMessage(),
|
||||
'code' => $e->getCode(),
|
||||
];
|
||||
}
|
||||
|
||||
$this->view->assign('response', $response);
|
||||
// saveFormAction uses the extbase JsonView::class.
|
||||
// That's why we have to set the view variables in this way.
|
||||
$this->view->setVariablesToRender([
|
||||
'response',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a page from the formDefinition which was build by the form editor.
|
||||
* Use the frontend rendering and set the form framework to preview mode.
|
||||
*
|
||||
* @param FormDefinitionArray $formDefinition
|
||||
* @param int $pageIndex
|
||||
* @param string $prototypeName
|
||||
* @return string
|
||||
* @internal
|
||||
*/
|
||||
public function renderFormPageAction(FormDefinitionArray $formDefinition, int $pageIndex, string $prototypeName = null): string
|
||||
{
|
||||
$prototypeName = $prototypeName ?: $formDefinition['prototypeName'] ?? 'standard';
|
||||
$formDefinition = $formDefinition->getArrayCopy();
|
||||
|
||||
$formFactory = $this->objectManager->get(ArrayFormFactory::class);
|
||||
$formDefinition = $formFactory->build($formDefinition, $prototypeName);
|
||||
$formDefinition->setRenderingOption('previewMode', true);
|
||||
$form = $formDefinition->bind($this->request, $this->response);
|
||||
$form->setCurrentSiteLanguage($this->buildFakeSiteLanguage(0, 0));
|
||||
$form->overrideCurrentPage($pageIndex);
|
||||
|
||||
return $form->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a SiteLanguage object to render the form preview with a
|
||||
* specific language.
|
||||
*
|
||||
* @param int $pageId
|
||||
* @param int $languageId
|
||||
* @return SiteLanguage
|
||||
*/
|
||||
protected function buildFakeSiteLanguage(int $pageId, int $languageId): SiteLanguage
|
||||
{
|
||||
$fakeSiteConfiguration = [
|
||||
'languages' => [
|
||||
[
|
||||
'languageId' => $languageId,
|
||||
'title' => 'Dummy',
|
||||
'navigationTitle' => '',
|
||||
'typo3Language' => '',
|
||||
'flag' => '',
|
||||
'locale' => '',
|
||||
'iso-639-1' => '',
|
||||
'hreflang' => '',
|
||||
'direction' => '',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/** @var \TYPO3\CMS\Core\Site\Entity\SiteLanguage $currentSiteLanguage */
|
||||
$currentSiteLanguage = GeneralUtility::makeInstance(Site::class, 'form-dummy', $pageId, $fakeSiteConfiguration)
|
||||
->getLanguageById($languageId);
|
||||
return $currentSiteLanguage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the formElements.*.formEditor section from the YAML settings.
|
||||
* Sort all formElements into groups and add additional data.
|
||||
*
|
||||
* @param array $formElementsDefinition
|
||||
* @return array
|
||||
*/
|
||||
protected function getInsertRenderablesPanelConfiguration(array $formElementsDefinition): array
|
||||
{
|
||||
$formElementsByGroup = [];
|
||||
|
||||
foreach ($formElementsDefinition as $formElementName => $formElementConfiguration) {
|
||||
if (!isset($formElementConfiguration['group'])) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($formElementsByGroup[$formElementConfiguration['group']])) {
|
||||
$formElementsByGroup[$formElementConfiguration['group']] = [];
|
||||
}
|
||||
|
||||
$formElementConfiguration = TranslationService::getInstance()->translateValuesRecursive(
|
||||
$formElementConfiguration,
|
||||
$this->prototypeConfiguration['formEditor']['translationFile'] ?? null
|
||||
);
|
||||
|
||||
$formElementsByGroup[$formElementConfiguration['group']][] = [
|
||||
'key' => $formElementName,
|
||||
'cssKey' => preg_replace('/[^a-z0-9]/', '-', strtolower($formElementName)),
|
||||
'label' => $formElementConfiguration['label'],
|
||||
'sorting' => $formElementConfiguration['groupSorting'],
|
||||
'iconIdentifier' => $formElementConfiguration['iconIdentifier'],
|
||||
];
|
||||
}
|
||||
|
||||
$formGroups = [];
|
||||
foreach ($this->prototypeConfiguration['formEditor']['formElementGroups'] ?? [] as $groupName => $groupConfiguration) {
|
||||
if (!isset($formElementsByGroup[$groupName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
usort($formElementsByGroup[$groupName], function ($a, $b) {
|
||||
return $a['sorting'] - $b['sorting'];
|
||||
});
|
||||
unset($formElementsByGroup[$groupName]['sorting']);
|
||||
|
||||
$groupConfiguration = TranslationService::getInstance()->translateValuesRecursive(
|
||||
$groupConfiguration,
|
||||
$this->prototypeConfiguration['formEditor']['translationFile'] ?? null
|
||||
);
|
||||
|
||||
$formGroups[] = [
|
||||
'key' => $groupName,
|
||||
'elements' => $formElementsByGroup[$groupName],
|
||||
'label' => $groupConfiguration['label'],
|
||||
];
|
||||
}
|
||||
|
||||
return $formGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce the YAML settings by the 'formEditor' keyword.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getFormEditorDefinitions(): array
|
||||
{
|
||||
$formEditorDefinitions = [];
|
||||
foreach ([$this->prototypeConfiguration, $this->prototypeConfiguration['formEditor']] as $configuration) {
|
||||
foreach ($configuration as $firstLevelItemKey => $firstLevelItemValue) {
|
||||
if (substr($firstLevelItemKey, -10) !== 'Definition') {
|
||||
continue;
|
||||
}
|
||||
$reducedKey = substr($firstLevelItemKey, 0, -10);
|
||||
foreach ($configuration[$firstLevelItemKey] as $formEditorDefinitionKey => $formEditorDefinitionValue) {
|
||||
if (isset($formEditorDefinitionValue['formEditor'])) {
|
||||
$formEditorDefinitionValue = array_intersect_key($formEditorDefinitionValue, array_flip(['formEditor']));
|
||||
$formEditorDefinitions[$reducedKey][$formEditorDefinitionKey] = $formEditorDefinitionValue['formEditor'];
|
||||
} else {
|
||||
$formEditorDefinitions[$reducedKey][$formEditorDefinitionKey] = $formEditorDefinitionValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$formEditorDefinitions = ArrayUtility::reIndexNumericArrayKeysRecursive($formEditorDefinitions);
|
||||
$formEditorDefinitions = TranslationService::getInstance()->translateValuesRecursive(
|
||||
$formEditorDefinitions,
|
||||
$this->prototypeConfiguration['formEditor']['translationFile'] ?? null
|
||||
);
|
||||
return $formEditorDefinitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the Icons into the docheader
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function registerDocheaderButtons()
|
||||
{
|
||||
/** @var ButtonBar $buttonBar */
|
||||
$buttonBar = $this->view->getModuleTemplate()->getDocHeaderComponent()->getButtonBar();
|
||||
$getVars = $this->request->getArguments();
|
||||
|
||||
if (isset($getVars['action']) && $getVars['action'] === 'index') {
|
||||
$newPageButton = $buttonBar->makeInputButton()
|
||||
->setDataAttributes(['action' => 'formeditor-new-page', 'identifier' => 'headerNewPage'])
|
||||
->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.new_page_button'))
|
||||
->setName('formeditor-new-page')
|
||||
->setValue('new-page')
|
||||
->setClasses('t3-form-element-new-page-button hidden')
|
||||
->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-page-new', Icon::SIZE_SMALL));
|
||||
/** @var UriBuilder $uriBuilder */
|
||||
$uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
|
||||
|
||||
$closeButton = $buttonBar->makeLinkButton()
|
||||
->setDataAttributes(['identifier' => 'closeButton'])
|
||||
->setHref((string)$uriBuilder->buildUriFromRoute('web_FormFormbuilder'))
|
||||
->setClasses('t3-form-element-close-form-button hidden')
|
||||
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.closeDoc'))
|
||||
->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-close', Icon::SIZE_SMALL));
|
||||
|
||||
$saveButton = $buttonBar->makeInputButton()
|
||||
->setDataAttributes(['identifier' => 'saveButton'])
|
||||
->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.save_button'))
|
||||
->setName('formeditor-save-form')
|
||||
->setValue('save')
|
||||
->setClasses('t3-form-element-save-form-button hidden')
|
||||
->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-document-save', Icon::SIZE_SMALL))
|
||||
->setShowLabelText(true);
|
||||
|
||||
$formSettingsButton = $buttonBar->makeInputButton()
|
||||
->setDataAttributes(['identifier' => 'formSettingsButton'])
|
||||
->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.form_settings_button'))
|
||||
->setName('formeditor-form-settings')
|
||||
->setValue('settings')
|
||||
->setClasses('t3-form-element-form-settings-button hidden')
|
||||
->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-system-extension-configure', Icon::SIZE_SMALL))
|
||||
->setShowLabelText(true);
|
||||
|
||||
$undoButton = $buttonBar->makeInputButton()
|
||||
->setDataAttributes(['identifier' => 'undoButton'])
|
||||
->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.undo_button'))
|
||||
->setName('formeditor-undo-form')
|
||||
->setValue('undo')
|
||||
->setClasses('t3-form-element-undo-form-button hidden disabled')
|
||||
->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-edit-undo', Icon::SIZE_SMALL));
|
||||
|
||||
$redoButton = $buttonBar->makeInputButton()
|
||||
->setDataAttributes(['identifier' => 'redoButton'])
|
||||
->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formEditor.redo_button'))
|
||||
->setName('formeditor-redo-form')
|
||||
->setValue('redo')
|
||||
->setClasses('t3-form-element-redo-form-button hidden disabled')
|
||||
->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-edit-redo', Icon::SIZE_SMALL));
|
||||
|
||||
$buttonBar->addButton($newPageButton, ButtonBar::BUTTON_POSITION_LEFT, 1);
|
||||
$buttonBar->addButton($closeButton, ButtonBar::BUTTON_POSITION_LEFT, 2);
|
||||
$buttonBar->addButton($saveButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
|
||||
$buttonBar->addButton($formSettingsButton, ButtonBar::BUTTON_POSITION_LEFT, 4);
|
||||
$buttonBar->addButton($undoButton, ButtonBar::BUTTON_POSITION_LEFT, 5);
|
||||
$buttonBar->addButton($redoButton, ButtonBar::BUTTON_POSITION_LEFT, 5);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the "text/x-formeditor-template" templates.
|
||||
*
|
||||
* @param array $formEditorDefinitions
|
||||
* @return string
|
||||
*/
|
||||
protected function renderFormEditorTemplates(array $formEditorDefinitions): string
|
||||
{
|
||||
$fluidConfiguration = $this->prototypeConfiguration['formEditor']['formEditorFluidConfiguration'] ?? null;
|
||||
$formEditorPartials = $this->prototypeConfiguration['formEditor']['formEditorPartials'] ?? null;
|
||||
|
||||
if (!isset($fluidConfiguration['templatePathAndFilename'])) {
|
||||
throw new RenderingException(
|
||||
'The option templatePathAndFilename must be set.',
|
||||
1485636499
|
||||
);
|
||||
}
|
||||
if (
|
||||
!isset($fluidConfiguration['layoutRootPaths'])
|
||||
|| !is_array($fluidConfiguration['layoutRootPaths'])
|
||||
) {
|
||||
throw new RenderingException(
|
||||
'The option layoutRootPaths must be set.',
|
||||
1480294721
|
||||
);
|
||||
}
|
||||
if (
|
||||
!isset($fluidConfiguration['partialRootPaths'])
|
||||
|| !is_array($fluidConfiguration['partialRootPaths'])
|
||||
) {
|
||||
throw new RenderingException(
|
||||
'The option partialRootPaths must be set.',
|
||||
1480294722
|
||||
);
|
||||
}
|
||||
|
||||
$insertRenderablesPanelConfiguration = $this->getInsertRenderablesPanelConfiguration($formEditorDefinitions['formElements']);
|
||||
|
||||
$view = $this->objectManager->get(TemplateView::class);
|
||||
$view->setControllerContext(clone $this->controllerContext);
|
||||
$view->getRenderingContext()->getTemplatePaths()->fillFromConfigurationArray($fluidConfiguration);
|
||||
$view->setTemplatePathAndFilename($fluidConfiguration['templatePathAndFilename']);
|
||||
$view->assignMultiple([
|
||||
'insertRenderablesPanelConfiguration' => $insertRenderablesPanelConfiguration,
|
||||
'formEditorPartials' => $formEditorPartials,
|
||||
]);
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo move this to FormDefinitionConversionService
|
||||
* @param array $formDefinition
|
||||
* @return array
|
||||
*/
|
||||
protected function transformFormDefinitionForFormEditor(array $formDefinition): array
|
||||
{
|
||||
$multiValueProperties = [];
|
||||
foreach ($this->prototypeConfiguration['formElementsDefinition'] as $type => $configuration) {
|
||||
if (!isset($configuration['formEditor']['editors'])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($configuration['formEditor']['editors'] as $editorConfiguration) {
|
||||
if ($editorConfiguration['templateName'] === 'Inspector-PropertyGridEditor') {
|
||||
$multiValueProperties[$type][] = $editorConfiguration['propertyPath'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$formDefinition = $this->filterEmptyArrays($formDefinition);
|
||||
|
||||
// @todo: replace with rte parsing
|
||||
$formDefinition = ArrayUtility::stripTagsFromValuesRecursive($formDefinition);
|
||||
$formDefinition = $this->transformMultiValueElementsForFormEditor($formDefinition, $multiValueProperties);
|
||||
|
||||
$formDefinitionConversionService = $this->getFormDefinitionConversionService();
|
||||
$formDefinition = $formDefinitionConversionService->addHmacData($formDefinition);
|
||||
$formDefinition = $formDefinitionConversionService->migrateFinisherConfiguration($formDefinition);
|
||||
|
||||
return $formDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Some data needs a transformation before it can be used by the
|
||||
* form editor. This rules for multivalue elements like select
|
||||
* elements. To ensure the right sorting if the data goes into
|
||||
* javascript, we need to do transformations:
|
||||
*
|
||||
* [
|
||||
* '5' => '5',
|
||||
* '4' => '4',
|
||||
* '3' => '3'
|
||||
* ]
|
||||
*
|
||||
*
|
||||
* This method transform this into:
|
||||
*
|
||||
* [
|
||||
* [
|
||||
* _label => '5'
|
||||
* _value => 5
|
||||
* ],
|
||||
* [
|
||||
* _label => '4'
|
||||
* _value => 4
|
||||
* ],
|
||||
* [
|
||||
* _label => '3'
|
||||
* _value => 3
|
||||
* ],
|
||||
* ]
|
||||
*
|
||||
* @param array $formDefinition
|
||||
* @param array $multiValueProperties
|
||||
* @return array
|
||||
*/
|
||||
protected function transformMultiValueElementsForFormEditor(
|
||||
array $formDefinition,
|
||||
array $multiValueProperties
|
||||
): array {
|
||||
$output = $formDefinition;
|
||||
foreach ($formDefinition as $key => $value) {
|
||||
if (isset($value['type']) && array_key_exists($value['type'], $multiValueProperties)) {
|
||||
$multiValuePropertiesForType = $multiValueProperties[$value['type']];
|
||||
foreach ($multiValuePropertiesForType as $multiValueProperty) {
|
||||
if (!ArrayUtility::isValidPath($value, $multiValueProperty, '.')) {
|
||||
continue;
|
||||
}
|
||||
$multiValuePropertyData = ArrayUtility::getValueByPath($value, $multiValueProperty, '.');
|
||||
if (!is_array($multiValuePropertyData)) {
|
||||
continue;
|
||||
}
|
||||
$newMultiValuePropertyData = [];
|
||||
foreach ($multiValuePropertyData as $k => $v) {
|
||||
$newMultiValuePropertyData[] = [
|
||||
'_label' => $v,
|
||||
'_value' => $k
|
||||
];
|
||||
}
|
||||
$value = ArrayUtility::setValueByPath($value, $multiValueProperty, $newMultiValuePropertyData, '.');
|
||||
}
|
||||
}
|
||||
|
||||
$output[$key] = $value;
|
||||
if (is_array($value)) {
|
||||
$output[$key] = $this->transformMultiValueElementsForFormEditor($value, $multiValueProperties);
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove keys from an array if the key value is an empty array
|
||||
*
|
||||
* @param array $array
|
||||
* @return array
|
||||
*/
|
||||
protected function filterEmptyArrays(array $array): array
|
||||
{
|
||||
foreach ($array as $key => $value) {
|
||||
if (!is_array($value)) {
|
||||
continue;
|
||||
}
|
||||
if (empty($value)) {
|
||||
unset($array[$key]);
|
||||
continue;
|
||||
}
|
||||
$array[$key] = $this->filterEmptyArrays($value);
|
||||
if (empty($array[$key])) {
|
||||
unset($array[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FormDefinitionConversionService
|
||||
*/
|
||||
protected function getFormDefinitionConversionService(): FormDefinitionConversionService
|
||||
{
|
||||
return GeneralUtility::makeInstance(FormDefinitionConversionService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current BE user.
|
||||
*
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the language service
|
||||
*
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the page renderer
|
||||
*
|
||||
* @return PageRenderer
|
||||
*/
|
||||
protected function getPageRenderer(): PageRenderer
|
||||
{
|
||||
return GeneralUtility::makeInstance(PageRenderer::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Controller;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
|
||||
use TYPO3\CMS\Core\Service\FlexFormService;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessing;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ConfigurationService;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters\FinisherOptionsFlexFormOverridesConverter;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters\FlexFormFinisherOverridesConverterDto;
|
||||
use TYPO3\CMS\Form\Mvc\Configuration\TypoScriptService;
|
||||
|
||||
/**
|
||||
* The frontend controller
|
||||
*
|
||||
* Scope: frontend
|
||||
* @internal
|
||||
*/
|
||||
class FormFrontendController extends ActionController
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManagerInterface
|
||||
*/
|
||||
protected $formPersistenceManager;
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManagerInterface $formPersistenceManager
|
||||
* @internal
|
||||
*/
|
||||
public function injectFormPersistenceManager(\TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManagerInterface $formPersistenceManager)
|
||||
{
|
||||
$this->formPersistenceManager = $formPersistenceManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the form which should be rendered from the plugin settings
|
||||
* and overlay the formDefinition with additional data from
|
||||
* flexform and typoscript settings.
|
||||
* This method is used directly to display the first page from the
|
||||
* formDefinition because its cached.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function renderAction()
|
||||
{
|
||||
$formDefinition = [];
|
||||
if (!empty($this->settings['persistenceIdentifier'])) {
|
||||
$formDefinition = $this->formPersistenceManager->load($this->settings['persistenceIdentifier']);
|
||||
$formDefinition['persistenceIdentifier'] = $this->settings['persistenceIdentifier'];
|
||||
$formDefinition = $this->overrideByTypoScriptSettings($formDefinition);
|
||||
$formDefinition = $this->overrideByFlexFormSettings($formDefinition);
|
||||
$formDefinition = ArrayUtility::setValueByPath($formDefinition, 'renderingOptions._originalIdentifier', $formDefinition['identifier'], '.');
|
||||
$formDefinition['identifier'] .= '-' . $this->configurationManager->getContentObject()->data['uid'];
|
||||
}
|
||||
$this->view->assign('formConfiguration', $formDefinition);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to display all pages / finishers except the
|
||||
* first page because its non cached.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function performAction()
|
||||
{
|
||||
$this->forward('render');
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the formDefinition with additional data from the Flexform
|
||||
* settings. For now, only finisher settings are overridable.
|
||||
*
|
||||
* @param array $formDefinition
|
||||
* @return array
|
||||
*/
|
||||
protected function overrideByFlexFormSettings(array $formDefinition): array
|
||||
{
|
||||
$flexFormData = GeneralUtility::xml2array($this->configurationManager->getContentObject()->data['pi_flexform'] ?? '');
|
||||
|
||||
if (!is_array($flexFormData)) {
|
||||
return $formDefinition;
|
||||
}
|
||||
|
||||
if (isset($formDefinition['finishers'])) {
|
||||
$prototypeName = $formDefinition['prototypeName'] ?? 'standard';
|
||||
$configurationService = $this->objectManager->get(ConfigurationService::class);
|
||||
$prototypeConfiguration = $configurationService->getPrototypeConfiguration($prototypeName);
|
||||
|
||||
foreach ($formDefinition['finishers'] as $index => $formFinisherDefinition) {
|
||||
$finisherIdentifier = $formFinisherDefinition['identifier'];
|
||||
|
||||
$sheetIdentifier = $this->getFlexformSheetIdentifier($formDefinition, $prototypeName, $finisherIdentifier);
|
||||
$flexFormSheetSettings = $this->getFlexFormSettingsFromSheet($flexFormData, $sheetIdentifier);
|
||||
|
||||
if ($this->settings['overrideFinishers'] && isset($flexFormSheetSettings['finishers'][$finisherIdentifier])) {
|
||||
$prototypeFinisherDefinition = $prototypeConfiguration['finishersDefinition'][$finisherIdentifier] ?? [];
|
||||
$converterDto = GeneralUtility::makeInstance(
|
||||
FlexFormFinisherOverridesConverterDto::class,
|
||||
$formFinisherDefinition,
|
||||
$finisherIdentifier,
|
||||
$flexFormSheetSettings
|
||||
);
|
||||
|
||||
// Iterate over all `TYPO3.CMS.Form.prototypes.<prototypeName>.finishersDefinition.<finisherIdentifier>.FormEngine.elements` values
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $prototypeFinisherDefinition['FormEngine']['elements'])->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'modifyFinisherOptionsFromFlexFormOverrides',
|
||||
'^(.*)\.config\.type$',
|
||||
GeneralUtility::makeInstance(FinisherOptionsFlexFormOverridesConverter::class, $converterDto)
|
||||
)
|
||||
);
|
||||
|
||||
$formDefinition['finishers'][$index] = $converterDto->getFinisherDefinition();
|
||||
}
|
||||
}
|
||||
}
|
||||
return $formDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Every formDefinition setting are overridable by typoscript.
|
||||
* If the typoscript configuration path
|
||||
* plugin.tx_form.settings.formDefinitionOverrides.<identifier>
|
||||
* exists, this settings are merged into the formDefinition.
|
||||
*
|
||||
* @param array $formDefinition
|
||||
* @return array
|
||||
*/
|
||||
protected function overrideByTypoScriptSettings(array $formDefinition): array
|
||||
{
|
||||
if (
|
||||
isset($this->settings['formDefinitionOverrides'][$formDefinition['identifier']])
|
||||
&& !empty($this->settings['formDefinitionOverrides'][$formDefinition['identifier']])
|
||||
) {
|
||||
ArrayUtility::mergeRecursiveWithOverrule(
|
||||
$formDefinition,
|
||||
$this->settings['formDefinitionOverrides'][$formDefinition['identifier']]
|
||||
);
|
||||
$formDefinition = $this->objectManager->get(TypoScriptService::class)
|
||||
->resolvePossibleTypoScriptConfiguration($formDefinition);
|
||||
}
|
||||
return $formDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $formDefinition
|
||||
* @param string $prototypeName
|
||||
* @param string $finisherIdentifier
|
||||
* @return string
|
||||
*/
|
||||
protected function getFlexformSheetIdentifier(
|
||||
array $formDefinition,
|
||||
string $prototypeName,
|
||||
string $finisherIdentifier
|
||||
): string {
|
||||
return md5(
|
||||
implode('', [
|
||||
$formDefinition['persistenceIdentifier'],
|
||||
$prototypeName,
|
||||
$formDefinition['identifier'],
|
||||
$finisherIdentifier
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $flexForm
|
||||
* @param string $sheetIdentifier
|
||||
* @return array
|
||||
*/
|
||||
protected function getFlexFormSettingsFromSheet(
|
||||
array $flexForm,
|
||||
string $sheetIdentifier
|
||||
): array {
|
||||
$sheetData['data'] = array_filter(
|
||||
$flexForm['data'] ?? [],
|
||||
function ($key) use ($sheetIdentifier) {
|
||||
return $key === $sheetIdentifier;
|
||||
},
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
|
||||
if (empty($sheetData['data'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
|
||||
$flexFormTools = GeneralUtility::makeInstance(FlexFormTools::class);
|
||||
|
||||
$sheetDataXml = $flexFormTools->flexArray2Xml($sheetData);
|
||||
return $flexFormService->convertFlexFormContentToArray($sheetDataXml)['settings'] ?? [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,606 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Controller;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use TYPO3\CMS\Backend\Template\Components\ButtonBar;
|
||||
use TYPO3\CMS\Backend\Utility\BackendUtility;
|
||||
use TYPO3\CMS\Backend\View\BackendTemplateView;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Charset\CharsetConverter;
|
||||
use TYPO3\CMS\Core\Imaging\Icon;
|
||||
use TYPO3\CMS\Core\Imaging\IconFactory;
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Messaging\AbstractMessage;
|
||||
use TYPO3\CMS\Core\Page\PageRenderer;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Mvc\View\JsonView;
|
||||
use TYPO3\CMS\Form\Exception as FormException;
|
||||
use TYPO3\CMS\Form\Mvc\Persistence\Exception\PersistenceManagerException;
|
||||
use TYPO3\CMS\Form\Service\DatabaseService;
|
||||
use TYPO3\CMS\Form\Service\TranslationService;
|
||||
|
||||
/**
|
||||
* The form manager controller
|
||||
*
|
||||
* Scope: backend
|
||||
* @internal
|
||||
*/
|
||||
class FormManagerController extends AbstractBackendController
|
||||
{
|
||||
|
||||
/**
|
||||
* @var DatabaseService
|
||||
*/
|
||||
protected $databaseService;
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Form\Service\DatabaseService $databaseService
|
||||
* @internal
|
||||
*/
|
||||
public function injectDatabaseService(\TYPO3\CMS\Form\Service\DatabaseService $databaseService)
|
||||
{
|
||||
$this->databaseService = $databaseService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default View Container
|
||||
*
|
||||
* @var BackendTemplateView
|
||||
*/
|
||||
protected $defaultViewObjectName = BackendTemplateView::class;
|
||||
|
||||
/**
|
||||
* Displays the Form Manager
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function indexAction()
|
||||
{
|
||||
$this->registerDocheaderButtons();
|
||||
$this->view->getModuleTemplate()->setModuleName($this->request->getPluginName() . '_' . $this->request->getControllerName());
|
||||
$this->view->getModuleTemplate()->setFlashMessageQueue($this->controllerContext->getFlashMessageQueue());
|
||||
|
||||
$this->view->assign('forms', $this->getAvailableFormDefinitions());
|
||||
$this->view->assign('stylesheets', $this->resolveResourcePaths($this->formSettings['formManager']['stylesheets']));
|
||||
$this->view->assign('dynamicRequireJsModules', $this->formSettings['formManager']['dynamicRequireJsModules']);
|
||||
$this->view->assign('formManagerAppInitialData', $this->getFormManagerAppInitialData());
|
||||
if (!empty($this->formSettings['formManager']['javaScriptTranslationFile'])) {
|
||||
$this->getPageRenderer()->addInlineLanguageLabelFile($this->formSettings['formManager']['javaScriptTranslationFile']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the create action.
|
||||
* This action uses the Fluid JsonView::class as view.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function initializeCreateAction()
|
||||
{
|
||||
$this->defaultViewObjectName = JsonView::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Form and redirects to the Form Editor
|
||||
*
|
||||
* @param string $formName
|
||||
* @param string $templatePath
|
||||
* @param string $prototypeName
|
||||
* @param string $savePath
|
||||
* @throws FormException
|
||||
* @throws PersistenceManagerException
|
||||
* @internal
|
||||
*/
|
||||
public function createAction(string $formName, string $templatePath, string $prototypeName, string $savePath)
|
||||
{
|
||||
if (!$this->formPersistenceManager->isAllowedPersistencePath($savePath)) {
|
||||
throw new PersistenceManagerException(sprintf('Save to path "%s" is not allowed', $savePath), 1614500657);
|
||||
}
|
||||
|
||||
if (!$this->isValidTemplatePath($prototypeName, $templatePath)) {
|
||||
throw new FormException(sprintf('The template path "%s" is not allowed', $templatePath), 1329233410);
|
||||
}
|
||||
if (empty($formName)) {
|
||||
throw new FormException('No form name', 1472312204);
|
||||
}
|
||||
|
||||
$templatePath = GeneralUtility::getFileAbsFileName($templatePath);
|
||||
$form = Yaml::parse(file_get_contents($templatePath));
|
||||
$form['label'] = $formName;
|
||||
$form['identifier'] = $this->formPersistenceManager->getUniqueIdentifier($this->convertFormNameToIdentifier($formName));
|
||||
$form['prototypeName'] = $prototypeName;
|
||||
|
||||
$formPersistenceIdentifier = $this->formPersistenceManager->getUniquePersistenceIdentifier($form['identifier'], $savePath);
|
||||
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormCreate'] ?? [] as $className) {
|
||||
$hookObj = GeneralUtility::makeInstance($className);
|
||||
if (method_exists($hookObj, 'beforeFormCreate')) {
|
||||
$form = $hookObj->beforeFormCreate(
|
||||
$formPersistenceIdentifier,
|
||||
$form
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$response = [
|
||||
'status' => 'success',
|
||||
'url' => $this->controllerContext->getUriBuilder()->uriFor('index', ['formPersistenceIdentifier' => $formPersistenceIdentifier], 'FormEditor')
|
||||
];
|
||||
|
||||
try {
|
||||
$this->formPersistenceManager->save($formPersistenceIdentifier, $form);
|
||||
} catch (PersistenceManagerException $e) {
|
||||
$response = [
|
||||
'status' => 'error',
|
||||
'message' => $e->getMessage(),
|
||||
'code' => $e->getCode(),
|
||||
];
|
||||
}
|
||||
|
||||
$this->view->assign('response', $response);
|
||||
// createAction uses the Extbase JsonView::class.
|
||||
// That's why we have to set the view variables in this way.
|
||||
$this->view->setVariablesToRender([
|
||||
'response',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the duplicate action.
|
||||
* This action uses the Fluid JsonView::class as view.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function initializeDuplicateAction()
|
||||
{
|
||||
$this->defaultViewObjectName = JsonView::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicates a given formDefinition and redirects to the Form Editor
|
||||
*
|
||||
* @param string $formName
|
||||
* @param string $formPersistenceIdentifier persistence identifier of the form to duplicate
|
||||
* @param string $savePath
|
||||
* @throws PersistenceManagerException
|
||||
* @internal
|
||||
*/
|
||||
public function duplicateAction(string $formName, string $formPersistenceIdentifier, string $savePath)
|
||||
{
|
||||
if (!$this->formPersistenceManager->isAllowedPersistencePath($savePath)) {
|
||||
throw new PersistenceManagerException(sprintf('Save to path "%s" is not allowed', $savePath), 1614500658);
|
||||
}
|
||||
if (!$this->formPersistenceManager->isAllowedPersistencePath($formPersistenceIdentifier)) {
|
||||
throw new PersistenceManagerException(sprintf('Read of "%s" is not allowed', $formPersistenceIdentifier), 1614500659);
|
||||
}
|
||||
|
||||
$formToDuplicate = $this->formPersistenceManager->load($formPersistenceIdentifier);
|
||||
$formToDuplicate['label'] = $formName;
|
||||
$formToDuplicate['identifier'] = $this->formPersistenceManager->getUniqueIdentifier($this->convertFormNameToIdentifier($formName));
|
||||
|
||||
$formPersistenceIdentifier = $this->formPersistenceManager->getUniquePersistenceIdentifier($formToDuplicate['identifier'], $savePath);
|
||||
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormDuplicate'] ?? [] as $className) {
|
||||
$hookObj = GeneralUtility::makeInstance($className);
|
||||
if (method_exists($hookObj, 'beforeFormDuplicate')) {
|
||||
$formToDuplicate = $hookObj->beforeFormDuplicate(
|
||||
$formPersistenceIdentifier,
|
||||
$formToDuplicate
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$response = [
|
||||
'status' => 'success',
|
||||
'url' => $this->controllerContext->getUriBuilder()->uriFor('index', ['formPersistenceIdentifier' => $formPersistenceIdentifier], 'FormEditor')
|
||||
];
|
||||
|
||||
try {
|
||||
$this->formPersistenceManager->save($formPersistenceIdentifier, $formToDuplicate);
|
||||
} catch (PersistenceManagerException $e) {
|
||||
$response = [
|
||||
'status' => 'error',
|
||||
'message' => $e->getMessage(),
|
||||
'code' => $e->getCode(),
|
||||
];
|
||||
}
|
||||
|
||||
$this->view->assign('response', $response);
|
||||
// createAction uses the Extbase JsonView::class.
|
||||
// That's why we have to set the view variables in this way.
|
||||
$this->view->setVariablesToRender([
|
||||
'response',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the references action.
|
||||
* This action uses the Fluid JsonView::class as view.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function initializeReferencesAction()
|
||||
{
|
||||
$this->defaultViewObjectName = JsonView::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show references to this persistence identifier
|
||||
*
|
||||
* @param string $formPersistenceIdentifier persistence identifier of the form to duplicate
|
||||
* @throws PersistenceManagerException
|
||||
* @internal
|
||||
*/
|
||||
public function referencesAction(string $formPersistenceIdentifier)
|
||||
{
|
||||
if (!$this->formPersistenceManager->isAllowedPersistencePath($formPersistenceIdentifier)) {
|
||||
throw new PersistenceManagerException(sprintf('Read from "%s" is not allowed', $formPersistenceIdentifier), 1614500660);
|
||||
}
|
||||
|
||||
$this->view->assign('references', $this->getProcessedReferencesRows($formPersistenceIdentifier));
|
||||
$this->view->assign('formPersistenceIdentifier', $formPersistenceIdentifier);
|
||||
// referencesAction uses the extbase JsonView::class.
|
||||
// That's why we have to set the view variables in this way.
|
||||
$this->view->setVariablesToRender([
|
||||
'references',
|
||||
'formPersistenceIdentifier'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a formDefinition identified by the $formPersistenceIdentifier.
|
||||
*
|
||||
* @param string $formPersistenceIdentifier persistence identifier to delete
|
||||
* @throws PersistenceManagerException
|
||||
* @internal
|
||||
*/
|
||||
public function deleteAction(string $formPersistenceIdentifier)
|
||||
{
|
||||
if (!$this->formPersistenceManager->isAllowedPersistencePath($formPersistenceIdentifier)) {
|
||||
throw new PersistenceManagerException(sprintf('Delete "%s" is not allowed', $formPersistenceIdentifier), 1614500661);
|
||||
}
|
||||
|
||||
if (empty($this->databaseService->getReferencesByPersistenceIdentifier($formPersistenceIdentifier))) {
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormDelete'] ?? [] as $className) {
|
||||
$hookObj = GeneralUtility::makeInstance($className);
|
||||
if (method_exists($hookObj, 'beforeFormDelete')) {
|
||||
$hookObj->beforeFormDelete(
|
||||
$formPersistenceIdentifier
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->formPersistenceManager->delete($formPersistenceIdentifier);
|
||||
} else {
|
||||
$controllerConfiguration = TranslationService::getInstance()->translateValuesRecursive(
|
||||
$this->formSettings['formManager']['controller'],
|
||||
$this->formSettings['formManager']['translationFile']
|
||||
);
|
||||
|
||||
$this->addFlashMessage(
|
||||
sprintf($controllerConfiguration['deleteAction']['errorMessage'], $formPersistenceIdentifier),
|
||||
$controllerConfiguration['deleteAction']['errorTitle'],
|
||||
AbstractMessage::ERROR,
|
||||
true
|
||||
);
|
||||
}
|
||||
$this->redirect('index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of all accessible file mountpoints.
|
||||
*
|
||||
* Only registered mountpoints from
|
||||
* TYPO3.CMS.Form.persistenceManager.allowedFileMounts
|
||||
* are listet. This is list will be reduced by the configured
|
||||
* mountpoints for the current backend user.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getAccessibleFormStorageFolders(): array
|
||||
{
|
||||
$preparedAccessibleFormStorageFolders = [];
|
||||
foreach ($this->formPersistenceManager->getAccessibleFormStorageFolders() as $identifier => $folder) {
|
||||
// TODO: deprecated since TYPO3 v9, will be removed in TYPO3 v10.0
|
||||
if ($folder->getCombinedIdentifier() === '1:/user_upload/') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$preparedAccessibleFormStorageFolders[] = [
|
||||
'label' => $folder->getName(),
|
||||
'value' => $identifier
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->formSettings['persistenceManager']['allowSaveToExtensionPaths']) {
|
||||
foreach ($this->formPersistenceManager->getAccessibleExtensionFolders() as $relativePath => $fullPath) {
|
||||
$preparedAccessibleFormStorageFolders[] = [
|
||||
'label' => $relativePath,
|
||||
'value' => $relativePath
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $preparedAccessibleFormStorageFolders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the json encoded data which is used by the form editor
|
||||
* JavaScript app.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getFormManagerAppInitialData(): string
|
||||
{
|
||||
$formManagerAppInitialData = [
|
||||
'selectablePrototypesConfiguration' => $this->formSettings['formManager']['selectablePrototypesConfiguration'],
|
||||
'accessibleFormStorageFolders' => $this->getAccessibleFormStorageFolders(),
|
||||
'endpoints' => [
|
||||
'create' => $this->controllerContext->getUriBuilder()->uriFor('create'),
|
||||
'duplicate' => $this->controllerContext->getUriBuilder()->uriFor('duplicate'),
|
||||
'delete' => $this->controllerContext->getUriBuilder()->uriFor('delete'),
|
||||
'references' => $this->controllerContext->getUriBuilder()->uriFor('references')
|
||||
],
|
||||
];
|
||||
|
||||
$formManagerAppInitialData = ArrayUtility::reIndexNumericArrayKeysRecursive($formManagerAppInitialData);
|
||||
$formManagerAppInitialData = TranslationService::getInstance()->translateValuesRecursive(
|
||||
$formManagerAppInitialData,
|
||||
$this->formSettings['formManager']['translationFile'] ?? null
|
||||
);
|
||||
return json_encode($formManagerAppInitialData);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all formDefinitions which can be loaded through t form persistence
|
||||
* manager. Enrich this data by a reference counter.
|
||||
* @return array
|
||||
*/
|
||||
protected function getAvailableFormDefinitions(): array
|
||||
{
|
||||
$allReferencesForFileUid = $this->databaseService->getAllReferencesForFileUid();
|
||||
$allReferencesForPersistenceIdentifier = $this->databaseService->getAllReferencesForPersistenceIdentifier();
|
||||
|
||||
$availableFormDefinitions = [];
|
||||
foreach ($this->formPersistenceManager->listForms() as $formDefinition) {
|
||||
$referenceCount = 0;
|
||||
if (
|
||||
isset($formDefinition['fileUid'])
|
||||
&& array_key_exists($formDefinition['fileUid'], $allReferencesForFileUid)
|
||||
) {
|
||||
$referenceCount = $allReferencesForFileUid[$formDefinition['fileUid']];
|
||||
} elseif (array_key_exists($formDefinition['persistenceIdentifier'], $allReferencesForPersistenceIdentifier)) {
|
||||
$referenceCount = $allReferencesForPersistenceIdentifier[$formDefinition['persistenceIdentifier']];
|
||||
}
|
||||
|
||||
$formDefinition['referenceCount'] = $referenceCount;
|
||||
$availableFormDefinitions[] = $formDefinition;
|
||||
}
|
||||
|
||||
return $availableFormDefinitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with informations about the references for a
|
||||
* formDefinition identified by $persistenceIdentifier.
|
||||
*
|
||||
* @param string $persistenceIdentifier
|
||||
* @return array
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function getProcessedReferencesRows(string $persistenceIdentifier): array
|
||||
{
|
||||
if (empty($persistenceIdentifier)) {
|
||||
throw new \InvalidArgumentException('$persistenceIdentifier must not be empty.', 1477071939);
|
||||
}
|
||||
|
||||
$references = [];
|
||||
$iconFactory = GeneralUtility::makeInstance(IconFactory::class);
|
||||
|
||||
$referenceRows = $this->databaseService->getReferencesByPersistenceIdentifier($persistenceIdentifier);
|
||||
foreach ($referenceRows as &$referenceRow) {
|
||||
$record = $this->getRecord($referenceRow['tablename'], $referenceRow['recuid']);
|
||||
if (!$record) {
|
||||
continue;
|
||||
}
|
||||
$pageRecord = $this->getRecord('pages', $record['pid']);
|
||||
$urlParameters = [
|
||||
'edit' => [
|
||||
$referenceRow['tablename'] => [
|
||||
$referenceRow['recuid'] => 'edit'
|
||||
]
|
||||
],
|
||||
'returnUrl' => $this->getModuleUrl('web_FormFormbuilder')
|
||||
];
|
||||
|
||||
$references[] = [
|
||||
'recordPageTitle' => is_array($pageRecord) ? $this->getRecordTitle('pages', $pageRecord) : '',
|
||||
'recordTitle' => $this->getRecordTitle($referenceRow['tablename'], $record, true),
|
||||
'recordIcon' => $iconFactory->getIconForRecord($referenceRow['tablename'], $record, Icon::SIZE_SMALL)->render(),
|
||||
'recordUid' => $referenceRow['recuid'],
|
||||
'recordEditUrl' => $this->getModuleUrl('record_edit', $urlParameters),
|
||||
];
|
||||
}
|
||||
return $references;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given $templatePath for a given $prototypeName is valid
|
||||
* and accessible.
|
||||
*
|
||||
* Valid template paths has to be configured within
|
||||
* TYPO3.CMS.Form.formManager.selectablePrototypesConfiguration.[('identifier': $prototypeName)].newFormTemplates.[('templatePath': $templatePath)]
|
||||
*
|
||||
* @param string $prototypeName
|
||||
* @param string $templatePath
|
||||
* @return bool
|
||||
*/
|
||||
protected function isValidTemplatePath(string $prototypeName, string $templatePath): bool
|
||||
{
|
||||
$isValid = false;
|
||||
foreach ($this->formSettings['formManager']['selectablePrototypesConfiguration'] as $prototypesConfiguration) {
|
||||
if ($prototypesConfiguration['identifier'] !== $prototypeName) {
|
||||
continue;
|
||||
}
|
||||
foreach ($prototypesConfiguration['newFormTemplates'] as $templatesConfiguration) {
|
||||
if ($templatesConfiguration['templatePath'] !== $templatePath) {
|
||||
continue;
|
||||
}
|
||||
$isValid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$templatePath = GeneralUtility::getFileAbsFileName($templatePath);
|
||||
if (!is_file($templatePath)) {
|
||||
$isValid = false;
|
||||
}
|
||||
|
||||
return $isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register document header buttons
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function registerDocheaderButtons()
|
||||
{
|
||||
/** @var ButtonBar $buttonBar */
|
||||
$buttonBar = $this->view->getModuleTemplate()->getDocHeaderComponent()->getButtonBar();
|
||||
$currentRequest = $this->request;
|
||||
$moduleName = $currentRequest->getPluginName();
|
||||
$getVars = $this->request->getArguments();
|
||||
|
||||
// Create new
|
||||
$addFormButton = $buttonBar->makeLinkButton()
|
||||
->setDataAttributes(['identifier' => 'newForm'])
|
||||
->setHref('#')
|
||||
->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formManager.create_new_form'))
|
||||
->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-add', Icon::SIZE_SMALL));
|
||||
$buttonBar->addButton($addFormButton, ButtonBar::BUTTON_POSITION_LEFT);
|
||||
|
||||
// Reload
|
||||
$reloadButton = $buttonBar->makeLinkButton()
|
||||
->setHref(GeneralUtility::getIndpEnv('REQUEST_URI'))
|
||||
->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.reload'))
|
||||
->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-refresh', Icon::SIZE_SMALL));
|
||||
$buttonBar->addButton($reloadButton, ButtonBar::BUTTON_POSITION_RIGHT);
|
||||
|
||||
// Shortcut
|
||||
$mayMakeShortcut = $this->getBackendUser()->mayMakeShortcut();
|
||||
if ($mayMakeShortcut) {
|
||||
$extensionName = $currentRequest->getControllerExtensionName();
|
||||
if (count($getVars) === 0) {
|
||||
$modulePrefix = strtolower('tx_' . $extensionName . '_' . $moduleName);
|
||||
$getVars = ['id', 'route', $modulePrefix];
|
||||
}
|
||||
|
||||
$shortcutButton = $buttonBar->makeShortcutButton()
|
||||
->setModuleName($moduleName)
|
||||
->setDisplayName($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:module.shortcut_name'))
|
||||
->setGetVariables($getVars);
|
||||
$buttonBar->addButton($shortcutButton, ButtonBar::BUTTON_POSITION_RIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a form identifier which is the lower cased form name.
|
||||
*
|
||||
* @param string $formName
|
||||
* @return string
|
||||
*/
|
||||
protected function convertFormNameToIdentifier(string $formName): string
|
||||
{
|
||||
$csConverter = GeneralUtility::makeInstance(CharsetConverter::class);
|
||||
|
||||
$formIdentifier = $csConverter->specCharsToASCII('utf-8', $formName);
|
||||
$formIdentifier = preg_replace('/[^a-zA-Z0-9-_]/', '', $formIdentifier);
|
||||
$formIdentifier = lcfirst($formIdentifier);
|
||||
return $formIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper used for unit testing.
|
||||
*
|
||||
* @param string $table
|
||||
* @param int $uid
|
||||
* @return array|null
|
||||
*/
|
||||
protected function getRecord(string $table, int $uid)
|
||||
{
|
||||
return BackendUtility::getRecord($table, $uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper used for unit testing.
|
||||
*
|
||||
* @param string $table
|
||||
* @param array $row
|
||||
* @param bool $prep
|
||||
* @return string
|
||||
*/
|
||||
protected function getRecordTitle(string $table, array $row, bool $prep = false): string
|
||||
{
|
||||
return BackendUtility::getRecordTitle($table, $row, $prep);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper used for unit testing.
|
||||
*
|
||||
* @param string $moduleName
|
||||
* @param array $urlParameters
|
||||
* @return string
|
||||
*/
|
||||
protected function getModuleUrl(string $moduleName, array $urlParameters = []): string
|
||||
{
|
||||
/** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
|
||||
$uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
|
||||
return (string)$uriBuilder->buildUriFromRoute($moduleName, $urlParameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current BE user.
|
||||
*
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Language Service
|
||||
*
|
||||
* @return LanguageService
|
||||
*/
|
||||
protected function getLanguageService(): LanguageService
|
||||
{
|
||||
return $GLOBALS['LANG'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the page renderer
|
||||
*
|
||||
* @return PageRenderer
|
||||
*/
|
||||
protected function getPageRenderer(): PageRenderer
|
||||
{
|
||||
return GeneralUtility::makeInstance(PageRenderer::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Condition;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\ExpressionLanguage\AbstractProvider;
|
||||
use TYPO3\CMS\Form\Domain\Condition\Functions\FormConditionFunctionsProvider;
|
||||
|
||||
/**
|
||||
* Scope: frontend
|
||||
* **This classis NOT meant to be sub classed by developers .**
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ConditionProvider extends AbstractProvider
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->expressionLanguageProviders = [
|
||||
FormConditionFunctionsProvider::class
|
||||
];
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace TYPO3\CMS\Form\Domain\Condition\Functions;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionFunction;
|
||||
use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class FormConditionFunctionsProvider implements ExpressionFunctionProviderInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @return ExpressionFunction[] An array of Function instances
|
||||
*/
|
||||
public function getFunctions()
|
||||
{
|
||||
return [
|
||||
$this->getFormValueFunction()
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut function to access field values
|
||||
*
|
||||
* @return \Symfony\Component\ExpressionLanguage\ExpressionFunction
|
||||
*/
|
||||
protected function getFormValueFunction(): ExpressionFunction
|
||||
{
|
||||
return new ExpressionFunction(
|
||||
'getFormValue',
|
||||
function () {
|
||||
// Not implemented, we only use the evaluator
|
||||
},
|
||||
function ($arguments, $field) {
|
||||
return $arguments['formValues'][$field] ?? null;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper for array processing
|
||||
*
|
||||
* Scope: frontend / backend
|
||||
* @internal
|
||||
*/
|
||||
class ArrayProcessing
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $identifier;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $expression;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
protected $processor;
|
||||
|
||||
/**
|
||||
* @param string $identifier
|
||||
* @param string $expression
|
||||
* @param callable $processor
|
||||
*/
|
||||
public function __construct(string $identifier, string $expression, callable $processor)
|
||||
{
|
||||
$this->identifier = $identifier;
|
||||
$this->expression = $expression;
|
||||
$this->processor = $processor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getExpression(): string
|
||||
{
|
||||
return $this->expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return callable
|
||||
*/
|
||||
public function getProcessor(): callable
|
||||
{
|
||||
return $this->processor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\Exception\ArrayProcessorException;
|
||||
|
||||
/**
|
||||
* Helper for array processing
|
||||
*
|
||||
* Scope: frontend / backend
|
||||
* @internal
|
||||
*/
|
||||
class ArrayProcessor
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->data = ArrayUtility::flatten($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ArrayProcessing[] $processings
|
||||
* @return array
|
||||
*/
|
||||
public function forEach(...$processings): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
$processings = $this->getValidProcessings($processings);
|
||||
foreach ($this->data as $key => $value) {
|
||||
foreach ($processings as $processing) {
|
||||
// explicitly escaping non-escaped '#' which is used
|
||||
// as PCRE delimiter in the following processing
|
||||
$expression = preg_replace(
|
||||
'/(?<!\\\\)#/',
|
||||
'\\#',
|
||||
$processing->getExpression()
|
||||
);
|
||||
|
||||
if (preg_match('#' . $expression . '#', $key, $matches)) {
|
||||
$identifier = $processing->getIdentifier();
|
||||
$processor = $processing->getProcessor();
|
||||
$result[$identifier] = $result[$identifier] ?? [];
|
||||
$result[$identifier][$key] = $processor($key, $value, $matches);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $allProcessings
|
||||
* @return ArrayProcessing[]
|
||||
* @throws ArrayProcessorException
|
||||
*/
|
||||
protected function getValidProcessings(array $allProcessings): array
|
||||
{
|
||||
$validProcessings = [];
|
||||
$identifiers = [];
|
||||
foreach ($allProcessings as $processing) {
|
||||
if ($processing instanceof ArrayProcessing) {
|
||||
if (in_array($processing->getIdentifier(), $identifiers, true)) {
|
||||
throw new ArrayProcessorException(
|
||||
'ArrayProcessing identifier must be unique.',
|
||||
1528638085
|
||||
);
|
||||
}
|
||||
$identifiers[] = $processing->getIdentifier();
|
||||
$validProcessings[] = $processing;
|
||||
}
|
||||
}
|
||||
return $validProcessings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,928 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Cache\CacheManager;
|
||||
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
|
||||
use TYPO3\CMS\Core\SingletonInterface;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessing;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\Exception\PropertyException;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\Exception\PrototypeNotFoundException;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\ValidationDto;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AdditionalElementPropertyPathsExtractor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\ExtractorDto;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\FormElement\IsCreatableFormElementExtractor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\FormElement\MultiValuePropertiesExtractor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\FormElement\PredefinedDefaultsExtractor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\FormElement\PropertyPathsExtractor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\FormElement\SelectOptionsExtractor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\PropertyCollectionElement\IsCreatablePropertyCollectionElementExtractor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\PropertyCollectionElement\MultiValuePropertiesExtractor as CollectionMultiValuePropertiesExtractor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\PropertyCollectionElement\PredefinedDefaultsExtractor as CollectionPredefinedDefaultsExtractor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\PropertyCollectionElement\PropertyPathsExtractor as CollectionPropertyPathsExtractor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\PropertyCollectionElement\SelectOptionsExtractor as CollectionSelectOptionsExtractor;
|
||||
use TYPO3\CMS\Form\Mvc\Configuration\ConfigurationManagerInterface;
|
||||
use TYPO3\CMS\Form\Service\TranslationService;
|
||||
|
||||
/**
|
||||
* Helper for configuration settings
|
||||
* Scope: frontend / backend
|
||||
*/
|
||||
class ConfigurationService implements SingletonInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $formSettings;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $firstLevelCache = [];
|
||||
|
||||
/**
|
||||
* @var TranslationService
|
||||
*/
|
||||
protected $translationService;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function initializeObject(): void
|
||||
{
|
||||
$this->formSettings = GeneralUtility::makeInstance(ObjectManager::class)
|
||||
->get(ConfigurationManagerInterface::class)
|
||||
->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_YAML_SETTINGS, 'form');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the prototype configuration
|
||||
*
|
||||
* @param string $prototypeName name of the prototype to get the configuration for
|
||||
* @return array the prototype configuration
|
||||
* @throws PrototypeNotFoundException if prototype with the name $prototypeName was not found
|
||||
*/
|
||||
public function getPrototypeConfiguration(string $prototypeName): array
|
||||
{
|
||||
if (!isset($this->formSettings['prototypes'][$prototypeName])) {
|
||||
throw new PrototypeNotFoundException(
|
||||
sprintf('The Prototype "%s" was not found.', $prototypeName),
|
||||
1475924277
|
||||
);
|
||||
}
|
||||
return $this->formSettings['prototypes'][$prototypeName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all prototype names which are defined within "formManager.selectablePrototypesConfiguration.*.identifier"
|
||||
*
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
public function getSelectablePrototypeNamesDefinedInFormEditorSetup(): array
|
||||
{
|
||||
$returnValue = GeneralUtility::makeInstance(
|
||||
ArrayProcessor::class,
|
||||
$this->formSettings['formManager']['selectablePrototypesConfiguration'] ?? []
|
||||
)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'selectablePrototypeNames',
|
||||
'^([\d]+)\.identifier$',
|
||||
function ($_, $value) {
|
||||
return $value;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
return array_values($returnValue['selectablePrototypeNames'] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a form element property is defined in the form setup.
|
||||
* If a form element property is defined in the form setup then it
|
||||
* means that the form element property can be written by the form editor.
|
||||
* A form element property can be written if the property path is defined within
|
||||
* the following form editor properties:
|
||||
* * formElementsDefinition.<formElementType>.formEditor.editors.<index>.propertyPath
|
||||
* * formElementsDefinition.<formElementType>.formEditor.editors.<index>.*.propertyPath
|
||||
* * formElementsDefinition.<formElementType>.formEditor.editors.<index>.additionalElementPropertyPaths
|
||||
* * formElementsDefinition.<formElementType>.formEditor.propertyCollections.<finishers|validators>.<index>.editors.<index>.additionalElementPropertyPaths
|
||||
* If a form editor property "templateName" is
|
||||
* "Inspector-PropertyGridEditor" or "Inspector-MultiSelectEditor" or "Inspector-ValidationErrorMessageEditor"
|
||||
* it means that the form editor property "propertyPath" is interpreted as a so called "multiValueProperty".
|
||||
* A "multiValueProperty" can contain any subproperties relative to the value from "propertyPath" which are valid.
|
||||
* If "formElementsDefinition.<formElementType>.formEditor.editors.<index>.templateName = Inspector-PropertyGridEditor"
|
||||
* and
|
||||
* "formElementsDefinition.<formElementType>.formEditor.editors.<index>.propertyPath = options.xxx"
|
||||
* then (for example) "options.xxx.yyy" is a valid property path to write.
|
||||
* If you use a custom form editor "inspector editor" implementation which does not define the writable
|
||||
* property paths by one of the above described inspector editor properties (e.g "propertyPath") within
|
||||
* the form setup, you must provide the writable property paths with a hook.
|
||||
*
|
||||
* @see $this->executeBuildFormDefinitionValidationConfigurationHooks()
|
||||
* @param ValidationDto $dto
|
||||
* @return bool
|
||||
* @internal
|
||||
*/
|
||||
public function isFormElementPropertyDefinedInFormEditorSetup(ValidationDto $dto): bool
|
||||
{
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
|
||||
$subConfig = $formDefinitionValidationConfiguration['formElements'][$dto->getFormElementType()] ?? [];
|
||||
return $this->isPropertyDefinedInFormEditorSetup($dto->getPropertyPath(), $subConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a form elements finisher|validator property is defined in the form setup.
|
||||
* If a form elements finisher|validator property is defined in the form setup then it
|
||||
* means that the form elements finisher|validator property can be written by the form editor.
|
||||
* A form elements finisher|validator property can be written if the property path is defined within
|
||||
* the following form editor properties:
|
||||
* * formElementsDefinition.<formElementType>.formEditor.propertyCollections.<finishers|validators>.<index>.editors.<index>.propertyPath
|
||||
* * formElementsDefinition.<formElementType>.formEditor.propertyCollections.<finishers|validators>.<index>.editors.<index>.*.propertyPath
|
||||
* If a form elements finisher|validator property "templateName" is
|
||||
* "Inspector-PropertyGridEditor" or "Inspector-MultiSelectEditor" or "Inspector-ValidationErrorMessageEditor"
|
||||
* it means that the form editor property "propertyPath" is interpreted as a so called "multiValueProperty".
|
||||
* A "multiValueProperty" can contain any subproperties relative to the value from "propertyPath" which are valid.
|
||||
* If "formElementsDefinition.<formElementType>.formEditor.propertyCollections.<finishers|validators>.<index>.editors.<index>.templateName = Inspector-PropertyGridEditor"
|
||||
* and
|
||||
* "formElementsDefinition.<formElementType>.formEditor.propertyCollections.<finishers|validators>.<index>.editors.<index>.propertyPath = options.xxx"
|
||||
* that (for example) "options.xxx.yyy" is a valid property path to write.
|
||||
* If you use a custom form editor "inspector editor" implementation which not defines the writable
|
||||
* property paths by one of the above described inspector editor properties (e.g "propertyPath") within
|
||||
* the form setup, you must provide the writable property paths with a hook.
|
||||
*
|
||||
* @see $this->executeBuildFormDefinitionValidationConfigurationHooks()
|
||||
* @param ValidationDto $dto
|
||||
* @return bool
|
||||
* @internal
|
||||
*/
|
||||
public function isPropertyCollectionPropertyDefinedInFormEditorSetup(ValidationDto $dto): bool
|
||||
{
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
$subConfig = $formDefinitionValidationConfiguration['formElements'][$dto->getFormElementType()]['collections'][$dto->getPropertyCollectionName()][$dto->getPropertyCollectionElementIdentifier()] ?? [];
|
||||
|
||||
return $this->isPropertyDefinedInFormEditorSetup($dto->getPropertyPath(), $subConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* If a form element editor has a property called "selectOptions"
|
||||
* (e.g. editors with templateName "Inspector-SingleSelectEditor" or "Inspector-MultiSelectEditor")
|
||||
* then only the defined values within the selectOptions are allowed to be written
|
||||
* by the form editor.
|
||||
*
|
||||
* @param ValidationDto $dto
|
||||
* @return bool
|
||||
* @internal
|
||||
*/
|
||||
public function formElementPropertyHasLimitedAllowedValuesDefinedWithinFormEditorSetup(
|
||||
ValidationDto $dto
|
||||
): bool {
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
|
||||
$propertyPath = $this->getBasePropertyPathFromMultiValueFormElementProperty($dto);
|
||||
return isset(
|
||||
$formDefinitionValidationConfiguration['formElements'][$dto->getFormElementType()]['selectOptions'][$propertyPath]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "selectOptions" value for a form element property from the form setup.
|
||||
*
|
||||
* @param ValidationDto $dto
|
||||
* @return array
|
||||
* @param bool $translated
|
||||
* @throws PropertyException
|
||||
* @internal
|
||||
*/
|
||||
public function getAllowedValuesForFormElementPropertyFromFormEditorSetup(
|
||||
ValidationDto $dto,
|
||||
bool $translated = true
|
||||
): array {
|
||||
if (!$this->formElementPropertyHasLimitedAllowedValuesDefinedWithinFormEditorSetup($dto)) {
|
||||
throw new PropertyException(
|
||||
sprintf(
|
||||
'No selectOptions found for form element type "%s" and property path "%s"',
|
||||
$dto->getFormElementType(),
|
||||
$dto->getPropertyPath()
|
||||
),
|
||||
1614264312
|
||||
);
|
||||
}
|
||||
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
|
||||
$property = $translated ? 'selectOptions' : 'untranslatedSelectOptions';
|
||||
$propertyPath = $this->getBasePropertyPathFromMultiValueFormElementProperty($dto);
|
||||
return $formDefinitionValidationConfiguration['formElements'][$dto->getFormElementType()][$property][$propertyPath];
|
||||
}
|
||||
|
||||
/**
|
||||
* If a form elements finisher|validator editor has a property called "selectOptions"
|
||||
* (e.g. editors with templateName "Inspector-SingleSelectEditor" or "Inspector-MultiSelectEditor")
|
||||
* then only the defined values within the selectOptions are allowed to be written
|
||||
* by the form editor.
|
||||
*
|
||||
* @param ValidationDto $dto
|
||||
* @return bool
|
||||
* @internal
|
||||
*/
|
||||
public function propertyCollectionPropertyHasLimitedAllowedValuesDefinedWithinFormEditorSetup(
|
||||
ValidationDto $dto
|
||||
): bool {
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
|
||||
$propertyPath = $this->getBasePropertyPathFromMultiValuePropertyCollectionElement($dto);
|
||||
return isset(
|
||||
$formDefinitionValidationConfiguration['collections'][$dto->getPropertyCollectionName()][$dto->getPropertyCollectionElementIdentifier()]['selectOptions'][$propertyPath]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "selectOptions" value for a form elements finisher|validator property from the form setup.
|
||||
*
|
||||
* @param ValidationDto $dto
|
||||
* @param bool $translated
|
||||
* @return array
|
||||
* @throws PropertyException
|
||||
* @internal
|
||||
*/
|
||||
public function getAllowedValuesForPropertyCollectionPropertyFromFormEditorSetup(
|
||||
ValidationDto $dto,
|
||||
bool $translated = true
|
||||
): array {
|
||||
if (!$this->propertyCollectionPropertyHasLimitedAllowedValuesDefinedWithinFormEditorSetup($dto)) {
|
||||
throw new PropertyException(
|
||||
sprintf(
|
||||
'No selectOptions found for property collection "%s" and identifier "%s" and property path "%s"',
|
||||
$dto->getPropertyCollectionName(),
|
||||
$dto->getPropertyCollectionElementIdentifier(),
|
||||
$dto->getPropertyPath()
|
||||
),
|
||||
1614264313
|
||||
);
|
||||
}
|
||||
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
|
||||
$property = $translated ? 'selectOptions' : 'untranslatedSelectOptions';
|
||||
$propertyPath = $this->getBasePropertyPathFromMultiValuePropertyCollectionElement($dto);
|
||||
return $formDefinitionValidationConfiguration['collections'][$dto->getPropertyCollectionName()][$dto->getPropertyCollectionElementIdentifier()][$property][$propertyPath];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ValidationDto $dto
|
||||
* @return string
|
||||
*/
|
||||
protected function getBasePropertyPathFromMultiValueFormElementProperty(
|
||||
ValidationDto $dto
|
||||
): string {
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
|
||||
$propertyPath = $dto->getPropertyPath();
|
||||
$multiValueProperties = $formDefinitionValidationConfiguration['formElements'][$dto->getFormElementType()]['multiValueProperties'] ?? [];
|
||||
foreach ($multiValueProperties as $multiValueProperty) {
|
||||
if (strpos($propertyPath, $multiValueProperty) === 0) {
|
||||
$propertyPath = $multiValueProperty;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return $propertyPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ValidationDto $dto
|
||||
* @return string
|
||||
*/
|
||||
protected function getBasePropertyPathFromMultiValuePropertyCollectionElement(
|
||||
ValidationDto $dto
|
||||
): string {
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
|
||||
$propertyPath = $dto->getPropertyPath();
|
||||
$multiValueProperties = $formDefinitionValidationConfiguration['collections'][$dto->getPropertyCollectionName()][$dto->getPropertyCollectionElementIdentifier()]['multiValueProperties'] ?? [];
|
||||
foreach ($multiValueProperties as $multiValueProperty) {
|
||||
if (strpos($propertyPath, $multiValueProperty) === 0) {
|
||||
$propertyPath = $multiValueProperty;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return $propertyPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a form element property is defined in "predefinedDefaults" in the form setup.
|
||||
* If a form element property is defined in the "predefinedDefaults" in the form setup then it
|
||||
* means that the form element property can be written by the form editor.
|
||||
* A form element default property is defined within the following form editor properties:
|
||||
* * formElementsDefinition.<formElementType>.formEditor.predefinedDefaults.<propertyPath> = "default value"
|
||||
*
|
||||
* @param ValidationDto $dto
|
||||
* @return bool
|
||||
* @internal
|
||||
*/
|
||||
public function isFormElementPropertyDefinedInPredefinedDefaultsInFormEditorSetup(
|
||||
ValidationDto $dto
|
||||
): bool {
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
return isset(
|
||||
$formDefinitionValidationConfiguration['formElements'][$dto->getFormElementType()]['predefinedDefaults'][$dto->getPropertyPath()]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "predefinedDefaults" value for a form element property from the form setup.
|
||||
* A form element default property is defined within the following form editor properties:
|
||||
* * formElementsDefinition.<formElementType>.formEditor.predefinedDefaults.<propertyPath> = "default value"
|
||||
*
|
||||
* @param ValidationDto $dto
|
||||
* @param bool $translated
|
||||
* @return mixed
|
||||
* @throws PropertyException
|
||||
* @internal
|
||||
*/
|
||||
public function getFormElementPredefinedDefaultValueFromFormEditorSetup(ValidationDto $dto, bool $translated = true)
|
||||
{
|
||||
if (!$this->isFormElementPropertyDefinedInPredefinedDefaultsInFormEditorSetup($dto)) {
|
||||
throw new PropertyException(
|
||||
sprintf(
|
||||
'No predefinedDefaults found for form element type "%s" and property path "%s"',
|
||||
$dto->getFormElementType(),
|
||||
$dto->getPropertyPath()
|
||||
),
|
||||
1528578401
|
||||
);
|
||||
}
|
||||
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
|
||||
$property = $translated ? 'predefinedDefaults' : 'untranslatedPredefinedDefaults';
|
||||
return $formDefinitionValidationConfiguration['formElements'][$dto->getFormElementType()][$property][$dto->getPropertyPath()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a form elements finisher|validator property is defined in "predefinedDefaults" in the form setup.
|
||||
* If a form elements finisher|validator property is defined in "predefinedDefaults" in the form setup then it
|
||||
* means that the form elements finisher|validator property can be written by the form editor.
|
||||
* A form elements finisher|validator default property is defined within the following form editor properties:
|
||||
* * <validatorsDefinition|finishersDefinition>.<index>.formEditor.predefinedDefaults.<propertyPath> = "default value"
|
||||
*
|
||||
* @param ValidationDto $dto
|
||||
* @return bool
|
||||
* @internal
|
||||
*/
|
||||
public function isPropertyCollectionPropertyDefinedInPredefinedDefaultsInFormEditorSetup(
|
||||
ValidationDto $dto
|
||||
): bool {
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
return isset(
|
||||
$formDefinitionValidationConfiguration['collections'][$dto->getPropertyCollectionName()][$dto->getPropertyCollectionElementIdentifier()]['predefinedDefaults'][$dto->getPropertyPath()]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "predefinedDefaults" value for a form elements finisher|validator property from the form setup.
|
||||
* A form elements finisher|validator default property is defined within the following form editor properties:
|
||||
* * <validatorsDefinition|finishersDefinition>.<index>.formEditor.predefinedDefaults.<propertyPath> = "default value"
|
||||
*
|
||||
* @param ValidationDto $dto
|
||||
* @param bool $translated
|
||||
* @return mixed
|
||||
* @throws PropertyException
|
||||
* @internal
|
||||
*/
|
||||
public function getPropertyCollectionPredefinedDefaultValueFromFormEditorSetup(ValidationDto $dto, bool $translated = true)
|
||||
{
|
||||
if (!$this->isPropertyCollectionPropertyDefinedInPredefinedDefaultsInFormEditorSetup($dto)) {
|
||||
throw new PropertyException(
|
||||
sprintf(
|
||||
'No predefinedDefaults found for property collection "%s" and identifier "%s" and property path "%s"',
|
||||
$dto->getPropertyCollectionName(),
|
||||
$dto->getPropertyCollectionElementIdentifier(),
|
||||
$dto->getPropertyPath()
|
||||
),
|
||||
1528578402
|
||||
);
|
||||
}
|
||||
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
|
||||
$property = $translated ? 'predefinedDefaults' : 'untranslatedPredefinedDefaults';
|
||||
return $formDefinitionValidationConfiguration['collections'][$dto->getPropertyCollectionName()][$dto->getPropertyCollectionElementIdentifier()][$property][$dto->getPropertyPath()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the form element is creatable through the form editor.
|
||||
* A form element is creatable if the following properties are set:
|
||||
* * formElementsDefinition.<formElementType>.formEditor.group
|
||||
* * formElementsDefinition.<formElementType>.formEditor.groupSorting
|
||||
* And the value from "formElementsDefinition.<formElementType>.formEditor.group" is
|
||||
* one of the keys within "formEditor.formElementGroups"
|
||||
*
|
||||
* @param ValidationDto $dto
|
||||
* @return bool
|
||||
* @internal
|
||||
*/
|
||||
public function isFormElementTypeCreatableByFormEditor(ValidationDto $dto): bool
|
||||
{
|
||||
if ($dto->getFormElementType() === 'Form') {
|
||||
return true;
|
||||
}
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
return $formDefinitionValidationConfiguration['formElements'][$dto->getFormElementType()]['creatable'] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the form elements finisher|validator is creatable through the form editor.
|
||||
* A form elements finisher|validator is creatable if the following conditions are true:
|
||||
* "formElementsDefinition.<formElementType>.formEditor.editors.<index>.templateName = Inspector-FinishersEditor"
|
||||
* or
|
||||
* "formElementsDefinition.<formElementType>.formEditor.editors.<index>.templateName = Inspector-ValidatorsEditor"
|
||||
* and
|
||||
* "formElementsDefinition.<formElementType>.formEditor.editors.<index>.selectOptions.<index>.value = <finisherIdentifier|validatorIdentifier>"
|
||||
*
|
||||
* @param ValidationDto $dto
|
||||
* @return bool
|
||||
* @internal
|
||||
*/
|
||||
public function isPropertyCollectionElementIdentifierCreatableByFormEditor(ValidationDto $dto): bool
|
||||
{
|
||||
$formDefinitionValidationConfiguration = $this->buildFormDefinitionValidationConfigurationFromFormEditorSetup(
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
return $formDefinitionValidationConfiguration['formElements'][$dto->getFormElementType()]['collections'][$dto->getPropertyCollectionName()][$dto->getPropertyCollectionElementIdentifier()]['creatable'] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the form elements type is defined within the form setup.
|
||||
*
|
||||
* @param ValidationDto $dto
|
||||
* @return bool
|
||||
* @internal
|
||||
*/
|
||||
public function isFormElementTypeDefinedInFormSetup(ValidationDto $dto): bool
|
||||
{
|
||||
$prototypeConfiguration = $this->getPrototypeConfiguration($dto->getPrototypeName());
|
||||
return ArrayUtility::isValidPath(
|
||||
$prototypeConfiguration,
|
||||
'formElementsDefinition.' . $dto->getFormElementType(),
|
||||
'.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $keys
|
||||
* @param string $prototypeName
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
public function getAllBackendTranslationsForTranslationKeys(array $keys, string $prototypeName): array
|
||||
{
|
||||
$translations = [];
|
||||
foreach ($keys as $key) {
|
||||
if (!is_string($key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$translations[$key] = $this->getAllBackendTranslationsForTranslationKey($key, $prototypeName);
|
||||
}
|
||||
|
||||
return $translations;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param string $prototypeName
|
||||
* @return array
|
||||
*/
|
||||
public function getAllBackendTranslationsForTranslationKey(string $key, string $prototypeName): array
|
||||
{
|
||||
$prototypeConfiguration = $this->getPrototypeConfiguration($prototypeName);
|
||||
|
||||
$translationFiles = $prototypeConfiguration['formEditor']['translationFile'] ?? [];
|
||||
if (is_string($translationFiles)) {
|
||||
$translationFiles = [$translationFiles];
|
||||
}
|
||||
|
||||
return $this->getTranslationService()->translateToAllBackendLanguages(
|
||||
$key,
|
||||
[],
|
||||
$translationFiles
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all the form editor configurations which are needed to check if a
|
||||
* form definition property can be written or not.
|
||||
*
|
||||
* @param string $prototypeName
|
||||
* @return array
|
||||
*/
|
||||
protected function buildFormDefinitionValidationConfigurationFromFormEditorSetup(string $prototypeName): array
|
||||
{
|
||||
$cacheKey = implode('_', ['buildFormDefinitionValidationConfigurationFromFormEditorSetup', $prototypeName]);
|
||||
$configuration = $this->getCacheEntry($cacheKey);
|
||||
|
||||
if ($configuration === null) {
|
||||
$prototypeConfiguration = $this->getPrototypeConfiguration($prototypeName);
|
||||
$extractorDto = GeneralUtility::makeInstance(ExtractorDto::class, $prototypeConfiguration);
|
||||
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $prototypeConfiguration)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'formElementPropertyPaths',
|
||||
'^formElementsDefinition\.(.*)\.formEditor\.editors\.([\d]+)\.(propertyPath|.*\.propertyPath)$',
|
||||
GeneralUtility::makeInstance(PropertyPathsExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'formElementAdditionalElementPropertyPaths',
|
||||
'^formElementsDefinition\.(.*)\.formEditor\.editors\.([\d]+)\.additionalElementPropertyPaths\.([\d]+)',
|
||||
GeneralUtility::makeInstance(AdditionalElementPropertyPathsExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'formElementRelativeMultiValueProperties',
|
||||
'^formElementsDefinition\.(.*)\.formEditor\.editors\.([\d]+)\.templateName$',
|
||||
GeneralUtility::makeInstance(MultiValuePropertiesExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'formElementSelectOptions',
|
||||
'^formElementsDefinition\.(.*)\.formEditor\.editors\.([\d]+)\.selectOptions\.([\d]+)\.value$',
|
||||
GeneralUtility::makeInstance(SelectOptionsExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'formElementPredefinedDefaults',
|
||||
'^formElementsDefinition\.(.*)\.formEditor\.predefinedDefaults\.(.+)$',
|
||||
GeneralUtility::makeInstance(PredefinedDefaultsExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'formElementCreatable',
|
||||
'^formElementsDefinition\.(.*)\.formEditor.group$',
|
||||
GeneralUtility::makeInstance(IsCreatableFormElementExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'propertyCollectionCreatable',
|
||||
'^formElementsDefinition\.(.*)\.formEditor\.editors\.([\d]+)\.templateName$',
|
||||
GeneralUtility::makeInstance(IsCreatablePropertyCollectionElementExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'propertyCollectionPropertyPaths',
|
||||
'^formElementsDefinition\.(.*)\.formEditor\.propertyCollections\.(finishers|validators)\.([\d]+)\.editors\.([\d]+)\.(propertyPath|.*\.propertyPath)$',
|
||||
GeneralUtility::makeInstance(CollectionPropertyPathsExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'propertyCollectionAdditionalElementPropertyPaths',
|
||||
'^formElementsDefinition\.(.*)\.formEditor\.propertyCollections\.(finishers|validators)\.([\d]+)\.editors\.([\d]+)\.additionalElementPropertyPaths\.([\d]+)',
|
||||
GeneralUtility::makeInstance(AdditionalElementPropertyPathsExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'propertyCollectionRelativeMultiValueProperties',
|
||||
'^formElementsDefinition\.(.*)\.formEditor\.propertyCollections\.(finishers|validators)\.([\d]+)\.editors\.([\d]+)\.templateName$',
|
||||
GeneralUtility::makeInstance(CollectionMultiValuePropertiesExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'propertyCollectionSelectOptions',
|
||||
'^formElementsDefinition\.(.*)\.formEditor\.propertyCollections\.(finishers|validators)\.([\d]+)\.editors\.([\d]+)\.selectOptions\.([\d]+)\.value$',
|
||||
GeneralUtility::makeInstance(CollectionSelectOptionsExtractor::class, $extractorDto)
|
||||
),
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'propertyCollectionPredefinedDefaults',
|
||||
'^(validatorsDefinition|finishersDefinition)\.(.*)\.formEditor\.predefinedDefaults\.(.+)$',
|
||||
GeneralUtility::makeInstance(CollectionPredefinedDefaultsExtractor::class, $extractorDto)
|
||||
)
|
||||
);
|
||||
$configuration = $extractorDto->getResult();
|
||||
|
||||
$configuration = $this->translateValues($prototypeConfiguration, $configuration);
|
||||
|
||||
$configuration = $this->executeBuildFormDefinitionValidationConfigurationHooks(
|
||||
$prototypeName,
|
||||
$configuration
|
||||
);
|
||||
|
||||
$this->setCacheEntry($cacheKey, $configuration);
|
||||
}
|
||||
|
||||
return $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* If you use a custom form editor "inspector editor" implementation which does not define the writable
|
||||
* property paths by one of the described inspector editor properties (e.g "propertyPath") within
|
||||
* the form setup, you must provide the writable property paths with a hook.
|
||||
*
|
||||
* @see $this->isFormElementPropertyDefinedInFormEditorSetup()
|
||||
* @see $this->isPropertyCollectionPropertyDefinedInFormEditorSetup()
|
||||
* Connect to the hook:
|
||||
* $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['buildFormDefinitionValidationConfiguration'][] = \Vendor\YourNamespace\YourClass::class;
|
||||
* Use the hook:
|
||||
* public function addAdditionalPropertyPaths(\TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\ValidationDto $validationDto): array
|
||||
* {
|
||||
* $textValidationDto = $validationDto->withFormElementType('Text');
|
||||
* $textValidatorsValidationDto = $textValidationDto->withPropertyCollectionName('validators');
|
||||
* $dateValidationDto = $validationDto->withFormElementType('Date');
|
||||
* $propertyPaths = [
|
||||
* $textValidationDto->withPropertyPath('properties.my.custom.property'),
|
||||
* $textValidationDto->withPropertyPath('properties.my.other.custom.property'),
|
||||
* $textValidatorsValidationDto->withPropertyCollectionElementIdentifier('StringLength')->withPropertyPath('options.custom.property'),
|
||||
* $textValidatorsValidationDto->withPropertyCollectionElementIdentifier('CustomValidator')->withPropertyPath('options.other.custom.property'),
|
||||
* $dateValidationDto->withPropertyPath('properties.custom.property'),
|
||||
* // ..
|
||||
* ];
|
||||
* return $propertyPaths;
|
||||
* }
|
||||
* @param string $prototypeName
|
||||
* @param array $configuration
|
||||
* @return array
|
||||
* @throws PropertyException
|
||||
*/
|
||||
protected function executeBuildFormDefinitionValidationConfigurationHooks(
|
||||
string $prototypeName,
|
||||
array $configuration
|
||||
): array {
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['buildFormDefinitionValidationConfiguration'] ?? [] as $className) {
|
||||
$hookObj = GeneralUtility::makeInstance($className);
|
||||
if (method_exists($hookObj, 'addAdditionalPropertyPaths')) {
|
||||
$validationDto = GeneralUtility::makeInstance(ValidationDto::class, $prototypeName);
|
||||
$propertyPathsFromHook = $hookObj->addAdditionalPropertyPaths($validationDto);
|
||||
if (!is_array($propertyPathsFromHook)) {
|
||||
$message = 'Return value of "%s->addAdditionalPropertyPaths() must be type "array"';
|
||||
throw new PropertyException(sprintf($message, $className), 1528633965);
|
||||
}
|
||||
$configuration = $this->addAdditionalPropertyPathsFromHook(
|
||||
$className,
|
||||
$prototypeName,
|
||||
$propertyPathsFromHook,
|
||||
$configuration
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $hookClassName
|
||||
* @param string $prototypeName
|
||||
* @param array $propertyPathsFromHook
|
||||
* @param array $configuration
|
||||
* @return array
|
||||
* @throws PropertyException
|
||||
*/
|
||||
protected function addAdditionalPropertyPathsFromHook(
|
||||
string $hookClassName,
|
||||
string $prototypeName,
|
||||
array $propertyPathsFromHook,
|
||||
array $configuration
|
||||
): array {
|
||||
foreach ($propertyPathsFromHook as $index => $validationDto) {
|
||||
if (!($validationDto instanceof ValidationDto)) {
|
||||
$message = 'Return value of "%s->addAdditionalPropertyPaths()[%s] must be an instance of "%s"';
|
||||
throw new PropertyException(
|
||||
sprintf($message, $hookClassName, $index, ValidationDto::class),
|
||||
1528633966
|
||||
);
|
||||
}
|
||||
|
||||
if ($validationDto->getPrototypeName() !== $prototypeName) {
|
||||
$message = 'The prototype name "%s" does not match "%s" on "%s->addAdditionalPropertyPaths()[%s]';
|
||||
throw new PropertyException(
|
||||
sprintf(
|
||||
$message,
|
||||
$validationDto->getPrototypeName(),
|
||||
$prototypeName,
|
||||
$hookClassName,
|
||||
$index,
|
||||
ValidationDto::class
|
||||
),
|
||||
1528634966
|
||||
);
|
||||
}
|
||||
|
||||
$formElementType = $validationDto->getFormElementType();
|
||||
if (!$this->isFormElementTypeDefinedInFormSetup($validationDto)) {
|
||||
$message = 'Form element type "%s" does not exists in prototype configuration "%s"';
|
||||
throw new PropertyException(
|
||||
sprintf($message, $formElementType, $validationDto->getPrototypeName()),
|
||||
1528633967
|
||||
);
|
||||
}
|
||||
|
||||
if ($validationDto->hasPropertyCollectionName() &&
|
||||
$validationDto->hasPropertyCollectionElementIdentifier()) {
|
||||
$propertyCollectionName = $validationDto->getPropertyCollectionName();
|
||||
$propertyCollectionElementIdentifier = $validationDto->getPropertyCollectionElementIdentifier();
|
||||
|
||||
if ($propertyCollectionName !== 'finishers' && $propertyCollectionName !== 'validators') {
|
||||
$message = 'The property collection name "%s" for form element "%s" must be "finishers" or "validators"';
|
||||
throw new PropertyException(
|
||||
sprintf($message, $propertyCollectionName, $formElementType),
|
||||
1528636941
|
||||
);
|
||||
}
|
||||
|
||||
$configuration['formElements'][$formElementType]['collections'][$propertyCollectionName][$propertyCollectionElementIdentifier]['additionalPropertyPaths'][]
|
||||
= $validationDto->getPropertyPath();
|
||||
} else {
|
||||
$configuration['formElements'][$formElementType]['additionalPropertyPaths'][]
|
||||
= $validationDto->getPropertyPath();
|
||||
}
|
||||
}
|
||||
|
||||
return $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $propertyPath
|
||||
* @param array $subConfig
|
||||
* @return bool
|
||||
*/
|
||||
protected function isPropertyDefinedInFormEditorSetup(string $propertyPath, array $subConfig): bool
|
||||
{
|
||||
if (empty($subConfig)) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
in_array($propertyPath, $subConfig['propertyPaths'] ?? [], true)
|
||||
|| in_array($propertyPath, $subConfig['additionalElementPropertyPaths'] ?? [], true)
|
||||
|| in_array($propertyPath, $subConfig['additionalPropertyPaths'] ?? [], true)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
foreach ($subConfig['multiValueProperties'] ?? [] as $relativeMultiValueProperty) {
|
||||
if (strpos($propertyPath, $relativeMultiValueProperty) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $prototypeConfiguration
|
||||
* @param array $configuration
|
||||
* @return array
|
||||
*/
|
||||
protected function translateValues(array $prototypeConfiguration, array $configuration): array
|
||||
{
|
||||
if (isset($configuration['formElements'])) {
|
||||
$configuration['formElements'] = $this->translatePredefinedDefaults(
|
||||
$prototypeConfiguration,
|
||||
$configuration['formElements']
|
||||
);
|
||||
|
||||
$configuration['formElements'] = $this->translateSelectOptions(
|
||||
$prototypeConfiguration,
|
||||
$configuration['formElements']
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($configuration['collections'] ?? [] as $name => $collections) {
|
||||
$configuration['collections'][$name] = $this->translatePredefinedDefaults($prototypeConfiguration, $collections);
|
||||
$configuration['collections'][$name] = $this->translateSelectOptions($prototypeConfiguration, $configuration['collections'][$name]);
|
||||
}
|
||||
return $configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $prototypeConfiguration
|
||||
* @param array $formElements
|
||||
* @return array
|
||||
*/
|
||||
protected function translatePredefinedDefaults(array $prototypeConfiguration, array $formElements): array
|
||||
{
|
||||
foreach ($formElements ?? [] as $name => $formElement) {
|
||||
if (!isset($formElement['predefinedDefaults'])) {
|
||||
continue;
|
||||
}
|
||||
$formElement['untranslatedPredefinedDefaults'] = $formElement['predefinedDefaults'];
|
||||
$formElement['predefinedDefaults'] = $this->getTranslationService()->translateValuesRecursive(
|
||||
$formElement['predefinedDefaults'],
|
||||
$prototypeConfiguration['formEditor']['translationFile'] ?? null
|
||||
);
|
||||
$formElements[$name] = $formElement;
|
||||
}
|
||||
return $formElements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $prototypeConfiguration
|
||||
* @param array $formElements
|
||||
* @return array
|
||||
*/
|
||||
protected function translateSelectOptions(array $prototypeConfiguration, array $formElements): array
|
||||
{
|
||||
foreach ($formElements ?? [] as $name => $formElement) {
|
||||
if (empty($formElement['selectOptions']) || !is_array($formElement['selectOptions'])) {
|
||||
continue;
|
||||
}
|
||||
$formElement['untranslatedSelectOptions'] = $formElement['selectOptions'];
|
||||
|
||||
$formElement['selectOptions'] = $this->getTranslationService()->translateValuesRecursive(
|
||||
$formElement['selectOptions'],
|
||||
$prototypeConfiguration['formEditor']['translationFiles'] ?? []
|
||||
);
|
||||
$formElements[$name] = $formElement;
|
||||
}
|
||||
return $formElements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cacheKey
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getCacheEntry(string $cacheKey)
|
||||
{
|
||||
if (isset($this->firstLevelCache[$cacheKey])) {
|
||||
return $this->firstLevelCache[$cacheKey];
|
||||
}
|
||||
return $this->getCacheFrontend()->has('form_' . $cacheKey)
|
||||
? $this->getCacheFrontend()->get('form_' . $cacheKey)
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cacheKey
|
||||
* @param mixed $value
|
||||
*/
|
||||
protected function setCacheEntry(string $cacheKey, $value): void
|
||||
{
|
||||
$this->getCacheFrontend()->set('form_' . $cacheKey, $value);
|
||||
$this->firstLevelCache[$cacheKey] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TranslationService
|
||||
*/
|
||||
protected function getTranslationService(): TranslationService
|
||||
{
|
||||
return $this->translationService instanceof TranslationService
|
||||
? $this->translationService
|
||||
: TranslationService::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FrontendInterface
|
||||
*/
|
||||
protected function getCacheFrontend(): FrontendInterface
|
||||
{
|
||||
return GeneralUtility::makeInstance(CacheManager::class)->getCache('assets');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\Exception;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Exception;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ArrayProcessorException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\Exception;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown if a form setup property was not found.
|
||||
*/
|
||||
class PropertyException extends Exception
|
||||
{
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\Exception;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown if a form prototype for a given name was not found.
|
||||
*/
|
||||
class PrototypeNotFoundException extends Exception
|
||||
{
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FlexformConfiguration\Processors;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractProcessor implements ProcessorInterface
|
||||
{
|
||||
/**
|
||||
* @var ProcessorDto
|
||||
*/
|
||||
protected $converterDto;
|
||||
|
||||
/**
|
||||
* @param ProcessorDto $converterDto
|
||||
*/
|
||||
public function __construct(ProcessorDto $converterDto)
|
||||
{
|
||||
$this->converterDto = $converterDto;
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FlexformConfiguration\Processors;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Localization\LanguageService;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Generate a FlexForm element for a finisher option
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class FinisherOptionGenerator extends AbstractProcessor
|
||||
{
|
||||
/**
|
||||
* @var \TYPO3\CMS\Core\Localization\LanguageService
|
||||
*/
|
||||
protected $languageService;
|
||||
|
||||
/**
|
||||
* @param ProcessorDto $converterDto
|
||||
*/
|
||||
public function __construct(ProcessorDto $converterDto)
|
||||
{
|
||||
parent::__construct($converterDto);
|
||||
|
||||
$this->languageService = GeneralUtility::makeInstance(LanguageService::class);
|
||||
$this->languageService->includeLLFile('EXT:form/Resources/Private/Language/Database.xlf');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $_ unused in this context
|
||||
* @param mixed $__ unused in this context
|
||||
* @param array $matches the expression matches from the ArrayProcessor - for example matches of ^(.*)\.config\.type$
|
||||
*/
|
||||
public function __invoke(string $_, $__, array $matches)
|
||||
{
|
||||
[, $optionKey] = $matches;
|
||||
|
||||
$finisherIdentifier = $this->converterDto->getFinisherIdentifier();
|
||||
$finisherDefinitionFromSetup = $this->converterDto->getFinisherDefinitionFromSetup();
|
||||
$finisherDefinitionFromFormDefinition = $this->converterDto->getFinisherDefinitionFromFormDefinition();
|
||||
|
||||
try {
|
||||
$elementConfiguration = ArrayUtility::getValueByPath(
|
||||
$finisherDefinitionFromSetup['FormEngine']['elements'],
|
||||
$optionKey,
|
||||
'.'
|
||||
);
|
||||
} catch (MissingArrayPathException $exception) {
|
||||
return;
|
||||
}
|
||||
|
||||
// use the option value from the ext:form setup from the current finisher as default value
|
||||
try {
|
||||
$optionValue = ArrayUtility::getValueByPath(
|
||||
$finisherDefinitionFromSetup,
|
||||
sprintf('options.%s', $optionKey),
|
||||
'.'
|
||||
);
|
||||
} catch (MissingArrayPathException $exception) {
|
||||
$optionValue = null;
|
||||
}
|
||||
|
||||
// use the option value from the form definition from the current finisher (if exists) as default value
|
||||
try {
|
||||
$optionValue = ArrayUtility::getValueByPath(
|
||||
$finisherDefinitionFromFormDefinition,
|
||||
sprintf('options.%s', $optionKey),
|
||||
'.'
|
||||
);
|
||||
} catch (MissingArrayPathException $exception) {
|
||||
}
|
||||
|
||||
if (empty($optionValue)) {
|
||||
$elementConfiguration['label'] .= sprintf(' (%s: "%s")', $this->languageService->getLL('default'), $this->languageService->getLL('empty'));
|
||||
} else {
|
||||
$elementConfiguration['label'] .= sprintf(' (%s: "' . $optionValue . '")', $this->languageService->getLL('default'));
|
||||
}
|
||||
|
||||
$elementConfiguration['config']['default'] = $optionValue;
|
||||
|
||||
$sheetElements = $this->converterDto->getResult();
|
||||
$sheetElements['settings.finishers.' . $finisherIdentifier . '.' . $optionKey]['TCEforms'] = $elementConfiguration;
|
||||
|
||||
$this->converterDto->setResult($sheetElements);
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FlexformConfiguration\Processors;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data container for finisher FlexForm processing
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ProcessorDto
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $finisherIdentifier;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $finisherDefinitionFromSetup;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $finisherDefinitionFromFormDefinition;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $result = [];
|
||||
|
||||
/**
|
||||
* @param string $finisherIdentifier
|
||||
* @param array $finisherDefinitionFromSetup
|
||||
* @param array $finisherDefinitionFromFormDefinition
|
||||
*/
|
||||
public function __construct(
|
||||
string $finisherIdentifier,
|
||||
array $finisherDefinitionFromSetup,
|
||||
array $finisherDefinitionFromFormDefinition
|
||||
) {
|
||||
$this->finisherIdentifier = $finisherIdentifier;
|
||||
$this->finisherDefinitionFromSetup = $finisherDefinitionFromSetup;
|
||||
$this->finisherDefinitionFromFormDefinition = $finisherDefinitionFromFormDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFinisherIdentifier(): string
|
||||
{
|
||||
return $this->finisherIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFinisherDefinitionFromSetup(): array
|
||||
{
|
||||
return $this->finisherDefinitionFromSetup;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFinisherDefinitionFromFormDefinition(): array
|
||||
{
|
||||
return $this->finisherDefinitionFromFormDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getResult(): array
|
||||
{
|
||||
return $this->result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $result
|
||||
* @return ProcessorDto
|
||||
*/
|
||||
public function setResult(array $result): ProcessorDto
|
||||
{
|
||||
$this->result = $result;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FlexformConfiguration\Processors;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface for FlexForm processors
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface ProcessorInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param ProcessorDto $converterDto
|
||||
*/
|
||||
public function __construct(ProcessorDto $converterDto);
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param array $matches
|
||||
*/
|
||||
public function __invoke(string $key, $value, array $matches);
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractConverter implements ConverterInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sessionToken;
|
||||
|
||||
/**
|
||||
* @var ConverterDto
|
||||
*/
|
||||
protected $converterDto;
|
||||
|
||||
/**
|
||||
* @param ConverterDto $converterDto
|
||||
* @param string $sessionToken
|
||||
*/
|
||||
public function __construct(ConverterDto $converterDto, string $sessionToken = '')
|
||||
{
|
||||
$this->converterDto = $converterDto;
|
||||
$this->sessionToken = $sessionToken;
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessing;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class AddHmacDataConverter extends AbstractConverter
|
||||
{
|
||||
|
||||
/**
|
||||
* Add a new value "_orig_<propertyName>" as a sibling of the property key.
|
||||
* "_orig_<propertyName>" is an array which contains the property value
|
||||
* and a hmac hash for the property value.
|
||||
* "_orig_<propertyName>" will be used to validate the form definition on saving.
|
||||
* @see \TYPO3\CMS\Form\Domain\Configuration\FormDefinitionValidationService::validateFormDefinitionProperties()
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value): void
|
||||
{
|
||||
$formDefinition = $this->converterDto->getFormDefinition();
|
||||
|
||||
$renderablePathParts = explode('.', $key);
|
||||
array_pop($renderablePathParts);
|
||||
|
||||
if (count($renderablePathParts) > 1) {
|
||||
$renderablePath = implode('.', $renderablePathParts);
|
||||
$currentFormElement = ArrayUtility::getValueByPath($formDefinition, $renderablePath, '.');
|
||||
} else {
|
||||
$currentFormElement = $formDefinition;
|
||||
}
|
||||
|
||||
$propertyCollectionElements = $currentFormElement['finishers'] ?? $currentFormElement['validators'] ?? [];
|
||||
$propertyCollectionName = $currentFormElement['type'] === 'Form' ? 'finishers' : 'validators';
|
||||
unset($currentFormElement['renderables'], $currentFormElement['finishers'], $currentFormElement['validators']);
|
||||
|
||||
$this->converterDto
|
||||
->setRenderablePathParts($renderablePathParts)
|
||||
->setFormElementIdentifier($value);
|
||||
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $currentFormElement)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'addHmacData',
|
||||
'^(?!(.*\._label|.*\._value)$).*',
|
||||
GeneralUtility::makeInstance(
|
||||
AddHmacDataToFormElementPropertyConverter::class,
|
||||
$this->converterDto,
|
||||
$this->sessionToken
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$this->converterDto->setPropertyCollectionName($propertyCollectionName);
|
||||
foreach ($propertyCollectionElements as $propertyCollectionIndex => $propertyCollectionElement) {
|
||||
$this->converterDto
|
||||
->setPropertyCollectionIndex((int)$propertyCollectionIndex)
|
||||
->setPropertyCollectionElementIdentifier($propertyCollectionElement['identifier']);
|
||||
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $propertyCollectionElement)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'addHmacData',
|
||||
'^(?!(.*\._label|.*\._value)$).*',
|
||||
GeneralUtility::makeInstance(
|
||||
AddHmacDataToPropertyCollectionElementConverter::class,
|
||||
$this->converterDto,
|
||||
$this->sessionToken
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class AddHmacDataToFormElementPropertyConverter extends AbstractConverter
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value): void
|
||||
{
|
||||
$formDefinition = $this->converterDto->getFormDefinition();
|
||||
|
||||
$propertyPathParts = explode('.', $key);
|
||||
$lastKeySegment = array_pop($propertyPathParts);
|
||||
$propertyPathParts[] = '_orig_' . $lastKeySegment;
|
||||
|
||||
$hmacValuePath = implode('.', array_merge($this->converterDto->getRenderablePathParts(), $propertyPathParts));
|
||||
$hmacValue = [
|
||||
'value' => $value,
|
||||
'hmac' => GeneralUtility::hmac(serialize([$this->converterDto->getFormElementIdentifier(), $key, $value]), $this->sessionToken)
|
||||
];
|
||||
|
||||
$formDefinition = ArrayUtility::setValueByPath($formDefinition, $hmacValuePath, $hmacValue, '.');
|
||||
|
||||
$this->converterDto->setFormDefinition($formDefinition);
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class AddHmacDataToPropertyCollectionElementConverter extends AbstractConverter
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value): void
|
||||
{
|
||||
$formDefinition = $this->converterDto->getFormDefinition();
|
||||
|
||||
$propertyPathParts = explode('.', $key);
|
||||
$lastKeySegment = array_pop($propertyPathParts);
|
||||
$propertyPathParts[] = '_orig_' . $lastKeySegment;
|
||||
|
||||
$hmacValuePath = implode('.', array_merge(
|
||||
$this->converterDto->getRenderablePathParts(),
|
||||
[$this->converterDto->getPropertyCollectionName(), $this->converterDto->getPropertyCollectionIndex()],
|
||||
$propertyPathParts
|
||||
));
|
||||
|
||||
$hmacValue = [
|
||||
'value' => $value,
|
||||
'hmac' => GeneralUtility::hmac(
|
||||
serialize([
|
||||
$this->converterDto->getFormElementIdentifier(),
|
||||
$this->converterDto->getPropertyCollectionName(),
|
||||
$this->converterDto->getPropertyCollectionElementIdentifier(),
|
||||
$key,
|
||||
$value
|
||||
]),
|
||||
$this->sessionToken
|
||||
)
|
||||
];
|
||||
|
||||
$formDefinition = ArrayUtility::setValueByPath($formDefinition, $hmacValuePath, $hmacValue, '.');
|
||||
|
||||
$this->converterDto->setFormDefinition($formDefinition);
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ConverterDto
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $formDefinition = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $renderablePathParts = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $formElementIdentifier = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $propertyCollectionIndex = 0;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $propertyCollectionName = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $propertyCollectionElementIdentifier = '';
|
||||
|
||||
/**
|
||||
* @param array $formDefinition
|
||||
*/
|
||||
public function __construct(array $formDefinition)
|
||||
{
|
||||
$this->formDefinition = $formDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFormDefinition(): array
|
||||
{
|
||||
return $this->formDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $formDefinition
|
||||
* @return ConverterDto
|
||||
*/
|
||||
public function setFormDefinition(array $formDefinition): ConverterDto
|
||||
{
|
||||
$this->formDefinition = $formDefinition;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getRenderablePathParts(): array
|
||||
{
|
||||
return $this->renderablePathParts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $renderablePathParts
|
||||
* @return ConverterDto
|
||||
*/
|
||||
public function setRenderablePathParts(array $renderablePathParts): ConverterDto
|
||||
{
|
||||
$this->renderablePathParts = $renderablePathParts;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFormElementIdentifier(): string
|
||||
{
|
||||
return $this->formElementIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $formElementIdentifier
|
||||
* @return ConverterDto
|
||||
*/
|
||||
public function setFormElementIdentifier(string $formElementIdentifier): ConverterDto
|
||||
{
|
||||
$this->formElementIdentifier = $formElementIdentifier;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getPropertyCollectionIndex(): int
|
||||
{
|
||||
return $this->propertyCollectionIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $propertyCollectionIndex
|
||||
* @return ConverterDto
|
||||
*/
|
||||
public function setPropertyCollectionIndex(int $propertyCollectionIndex): ConverterDto
|
||||
{
|
||||
$this->propertyCollectionIndex = $propertyCollectionIndex;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPropertyCollectionName(): string
|
||||
{
|
||||
return $this->propertyCollectionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $propertyCollectionName
|
||||
* @return ConverterDto
|
||||
*/
|
||||
public function setPropertyCollectionName(string $propertyCollectionName): ConverterDto
|
||||
{
|
||||
$this->propertyCollectionName = $propertyCollectionName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPropertyCollectionElementIdentifier(): string
|
||||
{
|
||||
return $this->propertyCollectionElementIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $propertyCollectionElementIdentifier
|
||||
* @return ConverterDto
|
||||
*/
|
||||
public function setPropertyCollectionElementIdentifier(string $propertyCollectionElementIdentifier): ConverterDto
|
||||
{
|
||||
$this->propertyCollectionElementIdentifier = $propertyCollectionElementIdentifier;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface ConverterInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param ConverterDto $converterDto
|
||||
* @param string $sessionToken
|
||||
*/
|
||||
public function __construct(ConverterDto $converterDto, string $sessionToken = '');
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value);
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
|
||||
|
||||
/**
|
||||
* Apply FlexForm finisher option overrides
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class FinisherOptionsFlexFormOverridesConverter
|
||||
{
|
||||
/**
|
||||
* @var FlexFormFinisherOverridesConverterDto
|
||||
*/
|
||||
protected $converterDto;
|
||||
|
||||
/**
|
||||
* @param FlexFormFinisherOverridesConverterDto $converterDto
|
||||
*/
|
||||
public function __construct(FlexFormFinisherOverridesConverterDto $converterDto)
|
||||
{
|
||||
$this->converterDto = $converterDto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for overriding finisher options with flexform settings
|
||||
* Flexform settings "win": When a setting is set in the form
|
||||
* definition and in flexform the one in flexform will overwrite the
|
||||
* one defined in the form definition.
|
||||
*
|
||||
* Here we adjust the parsed configuration and apply the overrides.
|
||||
*
|
||||
* @param string $_ unused in this context
|
||||
* @param mixed $__ unused in this context
|
||||
* @param array $matches the expression matches from the ArrayProcessor - for example matches of ^(.*)\.config\.type$
|
||||
*/
|
||||
public function __invoke(string $_, $__, array $matches): void
|
||||
{
|
||||
[, $optionKey] = $matches;
|
||||
$finisherDefinition = $this->converterDto->getFinisherDefinition();
|
||||
$finisherIdentifier = $this->converterDto->getFinisherIdentifier();
|
||||
$flexFormSheetSettings = $this->converterDto->getFlexFormSheetSettings();
|
||||
|
||||
try {
|
||||
$value = ArrayUtility::getValueByPath(
|
||||
$flexFormSheetSettings['finishers'][$finisherIdentifier],
|
||||
$optionKey,
|
||||
'.'
|
||||
);
|
||||
} catch (MissingArrayPathException $exception) {
|
||||
return;
|
||||
}
|
||||
|
||||
$finisherDefinition = ArrayUtility::setValueByPath($finisherDefinition, 'options.' . $optionKey, $value, '.');
|
||||
|
||||
$this->converterDto->setFinisherDefinition($finisherDefinition);
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class FinisherTranslationLanguageConverter extends AbstractConverter
|
||||
{
|
||||
|
||||
/**
|
||||
* If "finishers.x.options.translation.language" is empty then set the value to "default" and remove
|
||||
* the hmac.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value): void
|
||||
{
|
||||
if (!empty($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$formDefinition = $this->converterDto->getFormDefinition();
|
||||
|
||||
$formDefinition = ArrayUtility::setValueByPath($formDefinition, $key, 'default', '.');
|
||||
|
||||
$hmacPropertyPathParts = explode('.', $key);
|
||||
$lastKeySegment = array_pop($hmacPropertyPathParts);
|
||||
$hmacPropertyPathParts[] = '_orig_' . $lastKeySegment;
|
||||
$hmacValuePath = implode('.', $hmacPropertyPathParts);
|
||||
|
||||
if (ArrayUtility::isValidPath($formDefinition, $hmacValuePath, '.')) {
|
||||
$formDefinition = ArrayUtility::removeByPath($formDefinition, $hmacValuePath, '.');
|
||||
}
|
||||
|
||||
$this->converterDto->setFormDefinition($formDefinition);
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class FlexFormFinisherOverridesConverterDto
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $finisherDefinition = [];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $finisherIdentifier = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $flexFormSheetSettings = [];
|
||||
|
||||
/**
|
||||
* @param array $finisherDefinition
|
||||
* @param string $finisherIdentifier
|
||||
* @param array $flexFormSheetSettings
|
||||
*/
|
||||
public function __construct(
|
||||
array $finisherDefinition,
|
||||
string $finisherIdentifier,
|
||||
array $flexFormSheetSettings
|
||||
) {
|
||||
$this->finisherDefinition = $finisherDefinition;
|
||||
$this->finisherIdentifier = $finisherIdentifier;
|
||||
$this->flexFormSheetSettings = $flexFormSheetSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFinisherDefinition(): array
|
||||
{
|
||||
return $this->finisherDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $finisherDefinition
|
||||
* @return FlexFormFinisherOverridesConverterDto
|
||||
*/
|
||||
public function setFinisherDefinition(array $finisherDefinition): FlexFormFinisherOverridesConverterDto
|
||||
{
|
||||
$this->finisherDefinition = $finisherDefinition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFinisherIdentifier(): string
|
||||
{
|
||||
return $this->finisherIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFlexFormSheetSettings(): array
|
||||
{
|
||||
return $this->flexFormSheetSettings;
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class RemoveHmacDataConverter extends AbstractConverter
|
||||
{
|
||||
|
||||
/**
|
||||
* Remove the hmac data ("_orig_<propertyName>") for the corresponding property.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value): void
|
||||
{
|
||||
$formDefinition = $this->converterDto->getFormDefinition();
|
||||
|
||||
$propertyPathParts = explode('.', $key);
|
||||
array_pop($propertyPathParts);
|
||||
$propertyPath = implode('.', $propertyPathParts);
|
||||
$formDefinition = ArrayUtility::removeByPath($formDefinition, $propertyPath, '.');
|
||||
|
||||
$this->converterDto->setFormDefinition($formDefinition);
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ConfigurationService;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinitionValidationService;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractValidator implements ValidatorInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var ConfigurationService
|
||||
*/
|
||||
protected $configurationService;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $currentElement;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $sessionToken;
|
||||
|
||||
/**
|
||||
* @var ValidationDto
|
||||
*/
|
||||
protected $validationDto;
|
||||
|
||||
/**
|
||||
* @param array $currentElement
|
||||
* @param string $sessionToken
|
||||
* @param ValidationDto $validationDto
|
||||
*/
|
||||
public function __construct(array $currentElement, string $sessionToken, ValidationDto $validationDto)
|
||||
{
|
||||
$this->currentElement = $currentElement;
|
||||
$this->sessionToken = $sessionToken;
|
||||
$this->validationDto = $validationDto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the path in which the hmac value is expected based on the property path.
|
||||
*
|
||||
* @param string $propertyPath
|
||||
* @return string
|
||||
*/
|
||||
protected function buildHmacDataPath(string $propertyPath): string
|
||||
{
|
||||
$pathParts = explode('.', $propertyPath);
|
||||
$lastPathSegment = array_pop($pathParts);
|
||||
$pathParts[] = '_orig_' . $lastPathSegment;
|
||||
|
||||
return implode('.', $pathParts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FormDefinitionValidationService
|
||||
*/
|
||||
protected function getFormDefinitionValidationService(): FormDefinitionValidationService
|
||||
{
|
||||
return GeneralUtility::makeInstance(FormDefinitionValidationService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ConfigurationService
|
||||
*/
|
||||
protected function getConfigurationService(): ConfigurationService
|
||||
{
|
||||
if (!($this->configurationService instanceof ConfigurationService)) {
|
||||
$this->configurationService = $this->getObjectManager()->get(ConfigurationService::class);
|
||||
}
|
||||
return $this->configurationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ObjectManager
|
||||
*/
|
||||
protected function getObjectManager(): ObjectManager
|
||||
{
|
||||
return GeneralUtility::makeInstance(ObjectManager::class);
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\Exception\PropertyException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class CollectionBasedValidator extends AbstractValidator
|
||||
{
|
||||
|
||||
/**
|
||||
* Throws an exception if value from a property collection property
|
||||
* does not match its hmac hash or if there is no hmac hash
|
||||
* available for the value.
|
||||
*
|
||||
* @param array $currentElement
|
||||
* @param mixed $value
|
||||
* @param string $sessionToken
|
||||
* @param ValidationDto $dto
|
||||
* @throws PropertyException
|
||||
*/
|
||||
public function validatePropertyCollectionElementPropertyValueByHmacData(
|
||||
array $currentElement,
|
||||
$value,
|
||||
string $sessionToken,
|
||||
ValidationDto $dto
|
||||
): void {
|
||||
$hmacDataPath = $this->buildHmacDataPath($dto->getPropertyPath());
|
||||
if (ArrayUtility::isValidPath($currentElement, $hmacDataPath, '.')) {
|
||||
$hmacData = ArrayUtility::getValueByPath($currentElement, $hmacDataPath, '.');
|
||||
|
||||
$hmacContent = [
|
||||
$dto->getFormElementIdentifier(),
|
||||
$dto->getPropertyCollectionName(),
|
||||
$dto->getPropertyCollectionElementIdentifier(),
|
||||
$dto->getPropertyPath()
|
||||
];
|
||||
|
||||
if (!$this->getFormDefinitionValidationService()->isPropertyValueEqualToHistoricalValue($hmacContent, $value, $hmacData, $sessionToken)) {
|
||||
$message = 'The value "%s" of property "%s" (form element "%s" / "%s.%s") is not equal to the historical value "%s" #1528591586';
|
||||
throw new PropertyException(
|
||||
sprintf(
|
||||
$message,
|
||||
$value,
|
||||
$dto->getPropertyPath(),
|
||||
$dto->getFormElementIdentifier(),
|
||||
$dto->getPropertyCollectionName(),
|
||||
$dto->getPropertyCollectionElementIdentifier(),
|
||||
$hmacData['value'] ?? ''
|
||||
),
|
||||
1528591586
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$message = 'No hmac found for property "%s" (form element "%s" / "%s.%s") #1528591585';
|
||||
throw new PropertyException(
|
||||
sprintf(
|
||||
$message,
|
||||
$dto->getPropertyPath(),
|
||||
$dto->getFormElementIdentifier(),
|
||||
$dto->getPropertyCollectionName(),
|
||||
$dto->getPropertyCollectionElementIdentifier()
|
||||
),
|
||||
1528591585
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\Exception\PropertyException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class CreatableFormElementPropertiesValidator extends ElementBasedValidator
|
||||
{
|
||||
|
||||
/**
|
||||
* Checks if the form element property is defined within the form editor setup
|
||||
* or if the property is definied within the "predefinedDefaults" in the form editor setup
|
||||
* and the property value matches the predefined value
|
||||
* or if there is a valid hmac hash for the value.
|
||||
* If the form element property is defined within the form editor setup
|
||||
* and there is no valid hmac hash for the value
|
||||
* and is the form element property configured to only allow a limited set of values,
|
||||
* check the current (submitted) value against the allowed set of values (defined within the form setup).
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value)
|
||||
{
|
||||
$dto = $this->validationDto->withPropertyPath($key);
|
||||
|
||||
if ($this->getConfigurationService()->isFormElementPropertyDefinedInFormEditorSetup($dto)) {
|
||||
if ($this->getConfigurationService()->formElementPropertyHasLimitedAllowedValuesDefinedWithinFormEditorSetup($dto)) {
|
||||
$this->validateFormElementValue($value, $dto);
|
||||
}
|
||||
} elseif (
|
||||
$this->getConfigurationService()->isFormElementPropertyDefinedInPredefinedDefaultsInFormEditorSetup($dto)
|
||||
&& !ArrayUtility::isValidPath($this->currentElement, $this->buildHmacDataPath($dto->getPropertyPath()), '.')
|
||||
) {
|
||||
$this->validateFormElementPredefinedDefaultValue($value, $dto);
|
||||
} else {
|
||||
$this->validateFormElementPropertyValueByHmacData(
|
||||
$this->currentElement,
|
||||
$value,
|
||||
$this->sessionToken,
|
||||
$dto
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception if the value from a form element property
|
||||
* does not match the default value from the form editor setup.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param ValidationDto $dto
|
||||
* @throws PropertyException
|
||||
*/
|
||||
protected function validateFormElementPredefinedDefaultValue(
|
||||
$value,
|
||||
ValidationDto $dto
|
||||
): void {
|
||||
// If the form element is newely created, we have to compare the $value (form definition) with $predefinedDefaultValue (form setup)
|
||||
// to check the integrity (at this time we don't have a hmac for the $value to check the integrity)
|
||||
$predefinedDefaultValue = $this->getConfigurationService()->getFormElementPredefinedDefaultValueFromFormEditorSetup($dto);
|
||||
if ($value !== $predefinedDefaultValue) {
|
||||
$throwException = true;
|
||||
|
||||
if (is_string($predefinedDefaultValue)) {
|
||||
// Last chance:
|
||||
// Get all translations (from all backend languages) for the untranslated! $predefinedDefaultValue and
|
||||
// compare the (already translated) $value (from the form definition) against the possible
|
||||
// translations from $predefinedDefaultValue.
|
||||
// Usecase:
|
||||
// * backend language is EN
|
||||
// * open the form edtior and add a ContentElement form element
|
||||
// * switch to another browser tab and change the backend language to DE
|
||||
// * clear the cache
|
||||
// * go back to the form editor and click the save button
|
||||
// Out of scope:
|
||||
// * the same scenario as above + delete the previous chosen backend language within the maintenance tool
|
||||
$untranslatedPredefinedDefaultValue = $this->getConfigurationService()->getFormElementPredefinedDefaultValueFromFormEditorSetup($dto, false);
|
||||
$translations = $this->getConfigurationService()->getAllBackendTranslationsForTranslationKey(
|
||||
$untranslatedPredefinedDefaultValue,
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
|
||||
if (in_array($value, $translations, true)) {
|
||||
$throwException = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($throwException) {
|
||||
$message = 'The value "%s" of property "%s" (form element "%s") is not equal to the default value "%s" #1528588035';
|
||||
throw new PropertyException(
|
||||
sprintf(
|
||||
$message,
|
||||
$value,
|
||||
$dto->getPropertyPath(),
|
||||
$dto->getFormElementIdentifier(),
|
||||
$predefinedDefaultValue
|
||||
),
|
||||
1528588035
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception if the value from a form element property
|
||||
* does not match the allowed set of values (defined within the form setup).
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param ValidationDto $dto
|
||||
* @throws PropertyException
|
||||
*/
|
||||
protected function validateFormElementValue(
|
||||
$value,
|
||||
ValidationDto $dto
|
||||
): void {
|
||||
$allowedValues = $this->getConfigurationService()->getAllowedValuesForFormElementPropertyFromFormEditorSetup($dto);
|
||||
|
||||
if (!in_array($value, $allowedValues, true)) {
|
||||
$untranslatedAllowedValues = $this->getConfigurationService()->getAllowedValuesForFormElementPropertyFromFormEditorSetup($dto, false);
|
||||
// Compare the $value against the untranslated set of allowed values
|
||||
if (in_array($value, $untranslatedAllowedValues, true)) {
|
||||
// All good, $value is within the untranslated set of allowed values
|
||||
return;
|
||||
}
|
||||
// Get all translations (from all backend languages) for the untranslated! $allowedValues and
|
||||
// compare the (already translated) $value (from the form definition) against all possible
|
||||
// translations for $untranslatedAllowedValues.
|
||||
$allPossibleAllowedValuesTranslations = $this->getConfigurationService()->getAllBackendTranslationsForTranslationKeys(
|
||||
$untranslatedAllowedValues,
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
|
||||
foreach ($allPossibleAllowedValuesTranslations as $translations) {
|
||||
if (in_array($value, $translations, true)) {
|
||||
// All good, $value is within the set of translated allowed values
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Last chance:
|
||||
// If $value is not configured within the form setup as an allowed value
|
||||
// but was written within the form definition by hand (and therefore contains a hmac),
|
||||
// check if $value is manipulated.
|
||||
// If $value has no hmac or if the hmac exists but is not valid,
|
||||
// then $this->validatePropertyCollectionElementPropertyValueByHmacData() will
|
||||
// throw an exception.
|
||||
$this->validateFormElementPropertyValueByHmacData(
|
||||
$this->currentElement,
|
||||
$value,
|
||||
$this->sessionToken,
|
||||
$dto
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\Exception\PropertyException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class CreatablePropertyCollectionElementPropertiesValidator extends CollectionBasedValidator
|
||||
{
|
||||
|
||||
/**
|
||||
* Checks if the property collection element property is defined
|
||||
* within the form editor setup or if the property is definied within
|
||||
* the "predefinedDefaults" in the form editor setup
|
||||
* and the property value matches the predefined value
|
||||
* or if there is a valid hmac hash for the value.
|
||||
* If the property collection element property is defined within the form editor setup
|
||||
* and there is no valid hmac hash for the value
|
||||
* and is the form property collection element property configured to only allow a limited set of values,
|
||||
* check the current (submitted) value against the allowed set of values (defined within the form setup).
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value)
|
||||
{
|
||||
$dto = $this->validationDto->withPropertyPath($key);
|
||||
|
||||
if ($this->getConfigurationService()->isPropertyCollectionPropertyDefinedInFormEditorSetup($dto)) {
|
||||
if ($this->getConfigurationService()->propertyCollectionPropertyHasLimitedAllowedValuesDefinedWithinFormEditorSetup($dto)) {
|
||||
$this->validatePropertyCollectionPropertyValue($value, $dto);
|
||||
}
|
||||
} elseif (
|
||||
$this->getConfigurationService()->isPropertyCollectionPropertyDefinedInPredefinedDefaultsInFormEditorSetup($dto)
|
||||
&& !ArrayUtility::isValidPath($this->currentElement, $this->buildHmacDataPath($dto->getPropertyPath()), '.')
|
||||
) {
|
||||
$this->validatePropertyCollectionElementPredefinedDefaultValue($value, $dto);
|
||||
} else {
|
||||
$this->validatePropertyCollectionElementPropertyValueByHmacData(
|
||||
$this->currentElement,
|
||||
$value,
|
||||
$this->sessionToken,
|
||||
$dto
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception if the value from a property collection property
|
||||
* does not match the default value from the form editor setup.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param ValidationDto $dto
|
||||
* @throws PropertyException
|
||||
*/
|
||||
protected function validatePropertyCollectionElementPredefinedDefaultValue(
|
||||
$value,
|
||||
ValidationDto $dto
|
||||
): void {
|
||||
// If the property collection element is newely created, we have to compare the $value (form definition) with $predefinedDefaultValue (form setup)
|
||||
// to check the integrity (at this time we don't have a hmac on the value to check the integrity)
|
||||
$predefinedDefaultValue = $this->getConfigurationService()->getPropertyCollectionPredefinedDefaultValueFromFormEditorSetup($dto);
|
||||
if ($value !== $predefinedDefaultValue) {
|
||||
$throwException = true;
|
||||
|
||||
if (is_string($predefinedDefaultValue)) {
|
||||
// Last chance:
|
||||
// Get all translations (from all backend languages) for the untranslated! $predefinedDefaultValue and
|
||||
// compare the (already translated) $value (from the form definition) against the possible
|
||||
// translations from $predefinedDefaultValue.
|
||||
$untranslatedPredefinedDefaultValue = $this->getConfigurationService()->getPropertyCollectionPredefinedDefaultValueFromFormEditorSetup($dto, false);
|
||||
$translations = $this->getConfigurationService()->getAllBackendTranslationsForTranslationKey(
|
||||
$untranslatedPredefinedDefaultValue,
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
|
||||
if (in_array($value, $translations, true)) {
|
||||
$throwException = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($throwException) {
|
||||
$message = 'The value "%s" of property "%s" (form element "%s" / "%s.%s") is not equal to the default value "%s" #1528591502';
|
||||
throw new PropertyException(
|
||||
sprintf(
|
||||
$message,
|
||||
$value,
|
||||
$dto->getPropertyPath(),
|
||||
$dto->getFormElementIdentifier(),
|
||||
$dto->getPropertyCollectionName(),
|
||||
$dto->getPropertyCollectionElementIdentifier(),
|
||||
$predefinedDefaultValue
|
||||
),
|
||||
1528591502
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception if the value from a property collection property
|
||||
* does not match the allowed set of values (defined within the form setup).
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param ValidationDto $dto
|
||||
* @throws PropertyException
|
||||
*/
|
||||
protected function validatePropertyCollectionPropertyValue(
|
||||
$value,
|
||||
ValidationDto $dto
|
||||
): void {
|
||||
$allowedValues = $this->getConfigurationService()->getAllowedValuesForPropertyCollectionPropertyFromFormEditorSetup($dto);
|
||||
|
||||
if (!in_array($value, $allowedValues, true)) {
|
||||
$untranslatedAllowedValues = $this->getConfigurationService()->getAllowedValuesForPropertyCollectionPropertyFromFormEditorSetup($dto, false);
|
||||
// Compare the $value against the untranslated set of allowed values
|
||||
if (in_array($value, $untranslatedAllowedValues, true)) {
|
||||
// All good, $value is within the untranslated set of allowed values
|
||||
return;
|
||||
}
|
||||
// Get all translations (from all backend languages) for the untranslated! $allowedValues and
|
||||
// compare the (already translated) $value (from the form definition) against all possible
|
||||
// translations for $untranslatedAllowedValues.
|
||||
$allPossibleAllowedValuesTranslations = $this->getConfigurationService()->getAllBackendTranslationsForTranslationKeys(
|
||||
$untranslatedAllowedValues,
|
||||
$dto->getPrototypeName()
|
||||
);
|
||||
|
||||
foreach ($allPossibleAllowedValuesTranslations as $translations) {
|
||||
if (in_array($value, $translations, true)) {
|
||||
// All good, $value is within the set of translated allowed values
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Last chance:
|
||||
// If $value is not configured within the form setup as an allowed value
|
||||
// but was written within the form definition by hand (and therefore contains a hmac),
|
||||
// check if $value is manipulated.
|
||||
// If $value has no hmac or if the hmac exists but is not valid,
|
||||
// then $this->validatePropertyCollectionElementPropertyValueByHmacData() will
|
||||
// throw an exception.
|
||||
$this->validatePropertyCollectionElementPropertyValueByHmacData(
|
||||
$this->currentElement,
|
||||
$value,
|
||||
$this->sessionToken,
|
||||
$dto
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\Exception\PropertyException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class ElementBasedValidator extends AbstractValidator
|
||||
{
|
||||
|
||||
/**
|
||||
* Throws an exception if value from a form element property
|
||||
* does not match its hmac hash or if there is no hmac hash
|
||||
* available for the value.
|
||||
*
|
||||
* @param array $currentElement
|
||||
* @param mixed $value
|
||||
* @param string $sessionToken
|
||||
* @param ValidationDto $dto
|
||||
* @throws PropertyException
|
||||
*/
|
||||
public function validateFormElementPropertyValueByHmacData(
|
||||
array $currentElement,
|
||||
$value,
|
||||
string $sessionToken,
|
||||
ValidationDto $dto
|
||||
): void {
|
||||
$hmacDataPath = $this->buildHmacDataPath($dto->getPropertyPath());
|
||||
if (ArrayUtility::isValidPath($currentElement, $hmacDataPath, '.')) {
|
||||
$hmacData = ArrayUtility::getValueByPath($currentElement, $hmacDataPath, '.');
|
||||
|
||||
$hmacContent = [$dto->getFormElementIdentifier(), $dto->getPropertyPath()];
|
||||
if (!$this->getFormDefinitionValidationService()->isPropertyValueEqualToHistoricalValue($hmacContent, $value, $hmacData, $sessionToken)) {
|
||||
$message = 'The value "%s" of property "%s" (form element "%s") is not equal to the historical value "%s" #1528588036';
|
||||
throw new PropertyException(
|
||||
sprintf(
|
||||
$message,
|
||||
$value,
|
||||
$dto->getPropertyPath(),
|
||||
$dto->getFormElementIdentifier(),
|
||||
$hmacData['value'] ?? ''
|
||||
),
|
||||
1528588036
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$message = 'No hmac found for property "%s" (form element "%s") #1528588037';
|
||||
throw new PropertyException(
|
||||
sprintf($message, $dto->getPropertyPath(), $dto->getFormElementIdentifier()),
|
||||
1528588037
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class FormElementHmacDataValidator extends ElementBasedValidator
|
||||
{
|
||||
|
||||
/**
|
||||
* Checks if the form element property value matches to its hmac hash.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value): void
|
||||
{
|
||||
$dto = $this->validationDto->withPropertyPath($key);
|
||||
$this->validateFormElementPropertyValueByHmacData(
|
||||
$this->currentElement,
|
||||
$value,
|
||||
$this->sessionToken,
|
||||
$dto
|
||||
);
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class PropertyCollectionElementHmacDataValidator extends CollectionBasedValidator
|
||||
{
|
||||
|
||||
/**
|
||||
* Checks if the property collection element values matches to its hmac hash.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value): void
|
||||
{
|
||||
$dto = $this->validationDto->withPropertyPath($key)->withPropertyCollectionElementIdentifier(
|
||||
$this->currentElement['identifier']
|
||||
);
|
||||
$this->validatePropertyCollectionElementPropertyValueByHmacData(
|
||||
$this->currentElement,
|
||||
$value,
|
||||
$this->sessionToken,
|
||||
$dto
|
||||
);
|
||||
}
|
||||
}
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
*/
|
||||
class ValidationDto
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $prototypeName;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $formElementType;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $formElementIdentifier;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $propertyPath;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $propertyCollectionName;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $propertyCollectionElementIdentifier;
|
||||
|
||||
/**
|
||||
* @param string $prototypeName
|
||||
* @param string $formElementType
|
||||
* @param string $formElementIdentifier
|
||||
* @param string $propertyPath
|
||||
* @param string $propertyCollectionName
|
||||
* @param string $propertyCollectionElementIdentifier
|
||||
*/
|
||||
public function __construct(
|
||||
string $prototypeName = null,
|
||||
string $formElementType = null,
|
||||
string $formElementIdentifier = null,
|
||||
string $propertyPath = null,
|
||||
string $propertyCollectionName = null,
|
||||
string $propertyCollectionElementIdentifier = null
|
||||
) {
|
||||
$this->prototypeName = $prototypeName;
|
||||
$this->formElementType = $formElementType;
|
||||
$this->formElementIdentifier = $formElementIdentifier;
|
||||
$this->propertyPath = $propertyPath;
|
||||
$this->propertyCollectionName = $propertyCollectionName;
|
||||
$this->propertyCollectionElementIdentifier = $propertyCollectionElementIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPrototypeName(): string
|
||||
{
|
||||
return $this->prototypeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFormElementType(): string
|
||||
{
|
||||
return $this->formElementType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFormElementIdentifier(): string
|
||||
{
|
||||
return $this->formElementIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPropertyPath(): string
|
||||
{
|
||||
return $this->propertyPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPropertyCollectionName(): string
|
||||
{
|
||||
return $this->propertyCollectionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPropertyCollectionElementIdentifier(): string
|
||||
{
|
||||
return $this->propertyCollectionElementIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPrototypeName(): bool
|
||||
{
|
||||
return !empty($this->prototypeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasFormElementType(): bool
|
||||
{
|
||||
return !empty($this->formElementType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasFormElementIdentifier(): bool
|
||||
{
|
||||
return !empty($this->formElementIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPropertyPath(): bool
|
||||
{
|
||||
return !empty($this->propertyPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPropertyCollectionName(): bool
|
||||
{
|
||||
return !empty($this->propertyCollectionName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPropertyCollectionElementIdentifier(): bool
|
||||
{
|
||||
return !empty($this->propertyCollectionElementIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $prototypeName
|
||||
* @return ValidationDto
|
||||
*/
|
||||
public function withPrototypeName(string $prototypeName): ValidationDto
|
||||
{
|
||||
return GeneralUtility::makeInstance(self::class, $prototypeName, $this->formElementType, $this->formElementIdentifier, $this->propertyPath, $this->propertyCollectionName, $this->propertyCollectionElementIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $formElementType
|
||||
* @return ValidationDto
|
||||
*/
|
||||
public function withFormElementType(string $formElementType): ValidationDto
|
||||
{
|
||||
return GeneralUtility::makeInstance(self::class, $this->prototypeName, $formElementType, $this->formElementIdentifier, $this->propertyPath, $this->propertyCollectionName, $this->propertyCollectionElementIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $formElementIdentifier
|
||||
* @return ValidationDto
|
||||
*/
|
||||
public function withFormElementIdentifier(string $formElementIdentifier): ValidationDto
|
||||
{
|
||||
return GeneralUtility::makeInstance(self::class, $this->prototypeName, $this->formElementType, $formElementIdentifier, $this->propertyPath, $this->propertyCollectionName, $this->propertyCollectionElementIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $propertyPath
|
||||
* @return ValidationDto
|
||||
*/
|
||||
public function withPropertyPath(string $propertyPath): ValidationDto
|
||||
{
|
||||
return GeneralUtility::makeInstance(self::class, $this->prototypeName, $this->formElementType, $this->formElementIdentifier, $propertyPath, $this->propertyCollectionName, $this->propertyCollectionElementIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $propertyCollectionName
|
||||
* @return ValidationDto
|
||||
*/
|
||||
public function withPropertyCollectionName(string $propertyCollectionName): ValidationDto
|
||||
{
|
||||
return GeneralUtility::makeInstance(self::class, $this->prototypeName, $this->formElementType, $this->formElementIdentifier, $this->propertyPath, $propertyCollectionName, $this->propertyCollectionElementIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $propertyCollectionElementIdentifier
|
||||
* @return ValidationDto
|
||||
*/
|
||||
public function withPropertyCollectionElementIdentifier(string $propertyCollectionElementIdentifier): ValidationDto
|
||||
{
|
||||
return GeneralUtility::makeInstance(self::class, $this->prototypeName, $this->formElementType, $this->formElementIdentifier, $this->propertyPath, $this->propertyCollectionName, $propertyCollectionElementIdentifier);
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface ValidatorInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param array $currentElement
|
||||
* @param string $sessionToken
|
||||
* @param ValidationDto $validationDto
|
||||
*/
|
||||
public function __construct(array $currentElement, string $sessionToken, ValidationDto $validationDto);
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function __invoke(string $key, $value);
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Crypto\Random;
|
||||
use TYPO3\CMS\Core\SingletonInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessing;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters\AddHmacDataConverter;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters\ConverterDto;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters\FinisherTranslationLanguageConverter;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Converters\RemoveHmacDataConverter;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class FormDefinitionConversionService implements SingletonInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Add a new value "_orig_<propertyName>" for each scalar property value
|
||||
* within the form definition as a sibling of the property key.
|
||||
* "_orig_<propertyName>" is an array which contains the property value
|
||||
* and a hmac hash for the property value.
|
||||
* "_orig_<propertyName>" will be used to validate the form definition on saving.
|
||||
* @see \TYPO3\CMS\Form\Domain\Configuration\FormDefinitionValidationService::validateFormDefinitionProperties()
|
||||
*
|
||||
* @param array $formDefinition
|
||||
* @return array
|
||||
*/
|
||||
public function addHmacData(array $formDefinition): array
|
||||
{
|
||||
// Extend the hmac hashing key with a "per form editor session" unique key.
|
||||
$sessionToken = $this->generateSessionToken();
|
||||
$this->persistSessionToken($sessionToken);
|
||||
|
||||
$converterDto = GeneralUtility::makeInstance(ConverterDto::class, $formDefinition);
|
||||
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $formDefinition)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'addHmacData',
|
||||
'(^identifier$|renderables\.([\d]+)\.identifier$)',
|
||||
GeneralUtility::makeInstance(
|
||||
AddHmacDataConverter::class,
|
||||
$converterDto,
|
||||
$sessionToken
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return $converterDto->getFormDefinition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the "_orig_<propertyName>" values from the form definition.
|
||||
*
|
||||
* @param array $formDefinition
|
||||
* @return array
|
||||
*/
|
||||
public function removeHmacData(array $formDefinition): array
|
||||
{
|
||||
$converterDto = GeneralUtility::makeInstance(ConverterDto::class, $formDefinition);
|
||||
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $formDefinition)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'removeHmacData',
|
||||
'(_orig_.*|.*\._orig_.*)\.hmac',
|
||||
GeneralUtility::makeInstance(
|
||||
RemoveHmacDataConverter::class,
|
||||
$converterDto
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return $converterDto->getFormDefinition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate various finisher options
|
||||
*
|
||||
* @param array $formDefinition
|
||||
* @return array
|
||||
*/
|
||||
public function migrateFinisherConfiguration(array $formDefinition): array
|
||||
{
|
||||
$converterDto = GeneralUtility::makeInstance(ConverterDto::class, $formDefinition);
|
||||
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $formDefinition)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'migrateFinisherLanguageSettings',
|
||||
'^finishers\.([\d]+)\.options.translation.language$',
|
||||
GeneralUtility::makeInstance(
|
||||
FinisherTranslationLanguageConverter::class,
|
||||
$converterDto
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return $converterDto->getFormDefinition();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sessionToken
|
||||
*/
|
||||
protected function persistSessionToken(string $sessionToken)
|
||||
{
|
||||
$this->getBackendUser()->setAndSaveSessionData('extFormProtectionSessionToken', $sessionToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the random token which is used in the hash for the form tokens.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generateSessionToken()
|
||||
{
|
||||
return GeneralUtility::makeInstance(Random::class)->generateRandomHexString(64);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BackendUserAuthentication
|
||||
*/
|
||||
protected function getBackendUser(): BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\SingletonInterface;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessing;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessor;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\Exception\PropertyException;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\CreatableFormElementPropertiesValidator;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\CreatablePropertyCollectionElementPropertiesValidator;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\FormElementHmacDataValidator;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\PropertyCollectionElementHmacDataValidator;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FormDefinition\Validators\ValidationDto;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class FormDefinitionValidationService implements SingletonInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var ConfigurationService
|
||||
*/
|
||||
protected $configurationService;
|
||||
|
||||
/**
|
||||
* Validate the form definition properties using the form setup.
|
||||
* Pseudo workflow:
|
||||
* Is the form element type creatable by the form editor?
|
||||
* YES
|
||||
* foreach(form element properties) (without finishers|validators)
|
||||
* is the form element property defined in the form setup (can be manipulated)?
|
||||
* YES
|
||||
* is the form element property configured to only allow a limited set of values (e.g. Inspector-MultiSelectEditor, Inspector-SingleSelectEditor ...)
|
||||
* YES
|
||||
* is the form element property value within the set of allowed values?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* valid!
|
||||
* NO
|
||||
* is the form element property defined in "predefinedDefaults" in the form setup (cannot be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form element property value equals to the value defined in "predefinedDefaults" in the form setup?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* is there a hmac hash available for the form element property value (cannot be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form element property value equals the historical value (and is the historical value valid)?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* foreach(form elements finishers|validators)
|
||||
* is the form elements finisher|validator creatable by the form editor?
|
||||
* YES
|
||||
* foreach(form elements finisher|validator properties)
|
||||
* is the form elements finisher|validator property defined in the form setup (can be manipulated)?
|
||||
* YES
|
||||
* is the form elements finisher|validator property configured to only allow a limited set of values (e.g. Inspector-MultiSelectEditor, Inspector-SingleSelectEditor ...)
|
||||
* YES
|
||||
* is the form elements finisher|validator property value within the set of allowed values?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* valid!
|
||||
* NO
|
||||
* is the form elements finisher|validator property defined in "predefinedDefaults" in the form setup (cannot be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form elements finisher|validator property value equals to the value defined in "predefinedDefaults" in the form setup?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* is there a hmac hash available for the form elements finisher|validator property value (can not be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form elements finisher|validator property value equals the historical value (and is the historical value valid)?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* foreach(form elements finisher|validator properties)
|
||||
* is there a hmac hash available for the form elements finisher|validator property value (can not be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form elements finisher|validator property value equals the historical value (and is the historical value valid)?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* foreach(form element properties) (without finishers|validators)
|
||||
* is there a hmac hash available for the form element property value (cannot be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form element property value equals the historical value (and is the historical value valid)?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* foreach(form elements finisher|validator properties)
|
||||
* is there a hmac hash available for the form elements finisher|validator property value (can not be manipulated but should be written)?
|
||||
* YES
|
||||
* is the form elements finisher|validator property value equals the historical value (and is the historical value valid)?
|
||||
* YES
|
||||
* valid!
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
* NO
|
||||
* invalid! throw exception
|
||||
*
|
||||
* @param array $currentFormElement
|
||||
* @param string $prototypeName
|
||||
* @param string $sessionToken
|
||||
* @throws PropertyException
|
||||
*/
|
||||
public function validateFormDefinitionProperties(
|
||||
array $currentFormElement,
|
||||
string $prototypeName,
|
||||
string $sessionToken
|
||||
): void {
|
||||
$renderables = $currentFormElement['renderables'] ?? [];
|
||||
$propertyCollectionElements = $currentFormElement['finishers'] ?? $currentFormElement['validators'] ?? [];
|
||||
$propertyCollectionName = $currentFormElement['type'] === 'Form' ? 'finishers' : 'validators';
|
||||
unset($currentFormElement['renderables'], $currentFormElement['finishers'], $currentFormElement['validators']);
|
||||
|
||||
$validationDto = GeneralUtility::makeInstance(
|
||||
ValidationDto::class,
|
||||
$prototypeName,
|
||||
$currentFormElement['type'],
|
||||
$currentFormElement['identifier'],
|
||||
null,
|
||||
$propertyCollectionName
|
||||
);
|
||||
|
||||
if ($this->getConfigurationService()->isFormElementTypeCreatableByFormEditor($validationDto)) {
|
||||
$this->validateAllPropertyValuesFromCreatableFormElement(
|
||||
$currentFormElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
);
|
||||
|
||||
foreach ($propertyCollectionElements as $propertyCollectionElement) {
|
||||
$validationDto = $validationDto->withPropertyCollectionElementIdentifier(
|
||||
$propertyCollectionElement['identifier']
|
||||
);
|
||||
|
||||
if ($this->getConfigurationService()->isPropertyCollectionElementIdentifierCreatableByFormEditor($validationDto)) {
|
||||
$this->validateAllPropertyValuesFromCreatablePropertyCollectionElement(
|
||||
$propertyCollectionElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
);
|
||||
} else {
|
||||
$this->validateAllPropertyCollectionElementValuesByHmac(
|
||||
$propertyCollectionElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->validateAllFormElementPropertyValuesByHmac($currentFormElement, $sessionToken, $validationDto);
|
||||
|
||||
foreach ($propertyCollectionElements as $propertyCollectionElement) {
|
||||
$this->validateAllPropertyCollectionElementValuesByHmac(
|
||||
$propertyCollectionElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($renderables as $renderable) {
|
||||
$this->validateFormDefinitionProperties($renderable, $prototypeName, $sessionToken);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns TRUE if a property value is equals to the historical value
|
||||
* and FALSE if not.
|
||||
* "Historical values" means values which are available within the form definition
|
||||
* while the form editor is loaded and the values which are available after a
|
||||
* successful validation of the form definition on a save operation.
|
||||
* The value must be equal to the historical value if the property key for the value
|
||||
* is not defined within the form setup.
|
||||
* This means that the property can not be changed by the form editor but we want to keep the value
|
||||
* in its original state.
|
||||
* If this is not the case (return value is FALSE), an exception must be thrown.
|
||||
*
|
||||
* @param array $hmacContent
|
||||
* @param mixed $propertyValue
|
||||
* @param array $hmacData
|
||||
* @param string $sessionToken
|
||||
* @return bool
|
||||
* @throws PropertyException
|
||||
*/
|
||||
public function isPropertyValueEqualToHistoricalValue(
|
||||
array $hmacContent,
|
||||
$propertyValue,
|
||||
array $hmacData,
|
||||
string $sessionToken
|
||||
): bool {
|
||||
$this->checkHmacDataIntegrity($hmacData, $hmacContent, $sessionToken);
|
||||
$hmacContent[] = $propertyValue;
|
||||
|
||||
$expectedHash = GeneralUtility::hmac(serialize($hmacContent), $sessionToken);
|
||||
return hash_equals($expectedHash, $hmacData['hmac']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the historical value and the hmac hash to ensure the integrity
|
||||
* of the data.
|
||||
* An exception will be thrown if the value is modified.
|
||||
*
|
||||
* @param array $hmacData
|
||||
* @param array $hmacContent
|
||||
* @param string $sessionToken
|
||||
* @throws PropertyException
|
||||
*/
|
||||
protected function checkHmacDataIntegrity(array $hmacData, array $hmacContent, string $sessionToken)
|
||||
{
|
||||
$hmac = $hmacData['hmac'] ?? null;
|
||||
if (empty($hmac)) {
|
||||
throw new PropertyException('Hmac must not be empty. #1528538222', 1528538222);
|
||||
}
|
||||
|
||||
$hmacContent[] = $hmacData['value'] ?? '';
|
||||
$expectedHash = GeneralUtility::hmac(serialize($hmacContent), $sessionToken);
|
||||
|
||||
if (!hash_equals($expectedHash, $hmac)) {
|
||||
throw new PropertyException('Unauthorized modification of historical data. #1528538252', 1528538252);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk through all form element properties and checks
|
||||
* if the values matches to their hmac hashes.
|
||||
*
|
||||
* @param array $currentElement
|
||||
* @param string $sessionToken
|
||||
* @param ValidationDto $validationDto
|
||||
*/
|
||||
protected function validateAllFormElementPropertyValuesByHmac(
|
||||
array $currentElement,
|
||||
$sessionToken,
|
||||
ValidationDto $validationDto
|
||||
): void {
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $currentElement)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'validateProperties',
|
||||
'^(?!(_orig_.*|.*\._orig_.*)$).*',
|
||||
GeneralUtility::makeInstance(
|
||||
FormElementHmacDataValidator::class,
|
||||
$currentElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk through all property collection properties and checks
|
||||
* if the values matches to their hmac hashes.
|
||||
*
|
||||
* @param array $currentElement
|
||||
* @param string $sessionToken
|
||||
* @param ValidationDto $validationDto
|
||||
*/
|
||||
protected function validateAllPropertyCollectionElementValuesByHmac(
|
||||
array $currentElement,
|
||||
$sessionToken,
|
||||
ValidationDto $validationDto
|
||||
): void {
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $currentElement)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'validateProperties',
|
||||
'^(?!(_orig_.*|.*\._orig_.*)$).*',
|
||||
GeneralUtility::makeInstance(
|
||||
PropertyCollectionElementHmacDataValidator::class,
|
||||
$currentElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk through all form element properties and checks
|
||||
* if the property is defined within the form editor setup
|
||||
* or if the property is definied within the "predefinedDefaults" in the form editor setup
|
||||
* and the property value matches the predefined value
|
||||
* or if there is a valid hmac hash for the value.
|
||||
*
|
||||
* @param array $currentElement
|
||||
* @param string $sessionToken
|
||||
* @param ValidationDto $validationDto
|
||||
*/
|
||||
protected function validateAllPropertyValuesFromCreatableFormElement(
|
||||
array $currentElement,
|
||||
$sessionToken,
|
||||
ValidationDto $validationDto
|
||||
): void {
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $currentElement)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'validateProperties',
|
||||
'^(?!(_orig_.*|.*\._orig_.*|type|identifier)$).*',
|
||||
GeneralUtility::makeInstance(
|
||||
CreatableFormElementPropertiesValidator::class,
|
||||
$currentElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk through all property collection properties and checks
|
||||
* if the property is defined within the form editor setup
|
||||
* or if the property is definied within the "predefinedDefaults" in the form editor setup
|
||||
* and the property value matches the predefined value
|
||||
* or if there is a valid hmac hash for the value.
|
||||
*
|
||||
* @param array $currentElement
|
||||
* @param string $sessionToken
|
||||
* @param ValidationDto $validationDto
|
||||
*/
|
||||
protected function validateAllPropertyValuesFromCreatablePropertyCollectionElement(
|
||||
array $currentElement,
|
||||
$sessionToken,
|
||||
ValidationDto $validationDto
|
||||
): void {
|
||||
GeneralUtility::makeInstance(ArrayProcessor::class, $currentElement)->forEach(
|
||||
GeneralUtility::makeInstance(
|
||||
ArrayProcessing::class,
|
||||
'validateProperties',
|
||||
'^(?!(_orig_.*|.*\._orig_.*|identifier)$).*',
|
||||
GeneralUtility::makeInstance(
|
||||
CreatablePropertyCollectionElementPropertiesValidator::class,
|
||||
$currentElement,
|
||||
$sessionToken,
|
||||
$validationDto
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ConfigurationService
|
||||
*/
|
||||
protected function getConfigurationService(): ConfigurationService
|
||||
{
|
||||
if (!($this->configurationService instanceof ConfigurationService)) {
|
||||
$this->configurationService = $this->getObjectManager()->get(ConfigurationService::class);
|
||||
}
|
||||
return $this->configurationService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ObjectManager
|
||||
*/
|
||||
protected function getObjectManager(): ObjectManager
|
||||
{
|
||||
return GeneralUtility::makeInstance(ObjectManager::class);
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractExtractor implements ExtractorInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var ExtractorDto
|
||||
*/
|
||||
protected $extractorDto;
|
||||
|
||||
/**
|
||||
* @param ExtractorDto $extractorDto
|
||||
*/
|
||||
public function __construct(ExtractorDto $extractorDto)
|
||||
{
|
||||
$this->extractorDto = $extractorDto;
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class AdditionalElementPropertyPathsExtractor extends AbstractExtractor
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $_
|
||||
* @param mixed $value
|
||||
* @param array $matches
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
[, $formElementType] = $matches;
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
$result['formElements'][$formElementType]['additionalElementPropertyPaths'][] = $value;
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ExtractorDto
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $prototypeConfiguration;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $result = [];
|
||||
|
||||
/**
|
||||
* @param array $prototypeConfiguration
|
||||
*/
|
||||
public function __construct(array $prototypeConfiguration)
|
||||
{
|
||||
$this->prototypeConfiguration = $prototypeConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getPrototypeConfiguration(): array
|
||||
{
|
||||
return $this->prototypeConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getResult(): array
|
||||
{
|
||||
return $this->result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $result
|
||||
* @return ExtractorDto
|
||||
*/
|
||||
public function setResult(array $result): ExtractorDto
|
||||
{
|
||||
$this->result = $result;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
interface ExtractorInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param ExtractorDto $extractorDto
|
||||
*/
|
||||
public function __construct(ExtractorDto $extractorDto);
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param array $matches
|
||||
*/
|
||||
public function __invoke(string $key, $value, array $matches);
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\FormElement;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AbstractExtractor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class IsCreatableFormElementExtractor extends AbstractExtractor
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $_
|
||||
* @param mixed $value
|
||||
* @param array $matches
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
[, $formElementType] = $matches;
|
||||
|
||||
$formElementGroup = $value;
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
|
||||
if (!ArrayUtility::isValidPath(
|
||||
$this->extractorDto->getPrototypeConfiguration(),
|
||||
'formElementsDefinition.' . $formElementType . '.formEditor.groupSorting',
|
||||
'.'
|
||||
)) {
|
||||
$result['formElements'][$formElementType]['creatable'] = false;
|
||||
$this->extractorDto->setResult($result);
|
||||
return;
|
||||
}
|
||||
|
||||
$formElementGroups = array_keys(
|
||||
ArrayUtility::getValueByPath($this->extractorDto->getPrototypeConfiguration(), 'formEditor.formElementGroups', '.')
|
||||
);
|
||||
|
||||
$result['formElements'][$formElementType]['creatable'] = in_array(
|
||||
$formElementGroup,
|
||||
$formElementGroups,
|
||||
true
|
||||
);
|
||||
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\FormElement;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AbstractExtractor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class MultiValuePropertiesExtractor extends AbstractExtractor
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $_
|
||||
* @param mixed $value
|
||||
* @param array $matches
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
[, $formElementType, $formEditorIndex] = $matches;
|
||||
|
||||
if (
|
||||
$value !== 'Inspector-PropertyGridEditor'
|
||||
&& $value !== 'Inspector-MultiSelectEditor'
|
||||
&& $value !== 'Inspector-ValidationErrorMessageEditor'
|
||||
&& $value !== 'Inspector-RequiredValidatorEditor'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($value === 'Inspector-RequiredValidatorEditor') {
|
||||
$propertyPath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'editors',
|
||||
$formEditorIndex,
|
||||
'configurationOptions',
|
||||
'validationErrorMessage',
|
||||
'propertyPath',
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$propertyPath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'editors',
|
||||
$formEditorIndex,
|
||||
'propertyPath',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
|
||||
if (ArrayUtility::isValidPath($this->extractorDto->getPrototypeConfiguration(), $propertyPath, '.')) {
|
||||
$result['formElements'][$formElementType]['multiValueProperties'][] = ArrayUtility::getValueByPath(
|
||||
$this->extractorDto->getPrototypeConfiguration(),
|
||||
$propertyPath,
|
||||
'.'
|
||||
);
|
||||
}
|
||||
|
||||
if ($value === 'Inspector-PropertyGridEditor') {
|
||||
$result['formElements'][$formElementType]['multiValueProperties'][] = 'defaultValue';
|
||||
}
|
||||
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\FormElement;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AbstractExtractor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class PredefinedDefaultsExtractor extends AbstractExtractor
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $_
|
||||
* @param mixed $value
|
||||
* @param array $matches
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
[, $formElementType, $propertyPath] = $matches;
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
$result['formElements'][$formElementType]['predefinedDefaults'][$propertyPath] = $value;
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\FormElement;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AbstractExtractor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class PropertyPathsExtractor extends AbstractExtractor
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $_
|
||||
* @param mixed $value
|
||||
* @param array $matches
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
$formElementPropertyPaths = $this->getPropertyPaths($value, $matches);
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
$result = array_merge_recursive($result, ['formElements' => $formElementPropertyPaths]);
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
* @param array $matches
|
||||
* @return array
|
||||
*/
|
||||
protected function getPropertyPaths(string $value, array $matches): array
|
||||
{
|
||||
[, $formElementType, $formEditorIndex] = $matches;
|
||||
|
||||
$paths[$formElementType]['propertyPaths'] = [];
|
||||
$templateNamePath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'editors',
|
||||
$formEditorIndex,
|
||||
'templateName',
|
||||
]
|
||||
);
|
||||
$templateName = ArrayUtility::getValueByPath(
|
||||
$this->extractorDto->getPrototypeConfiguration(),
|
||||
$templateNamePath,
|
||||
'.'
|
||||
);
|
||||
|
||||
// Special processing of "Inspector-GridColumnViewPortConfigurationEditor" inspector editors.
|
||||
// Expand the property path which contains a "{@viewPortIdentifier}" placeholder
|
||||
// to X property paths which contain all available placeholder replacements.
|
||||
if ($templateName === 'Inspector-GridColumnViewPortConfigurationEditor') {
|
||||
$viewPortsPath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'editors',
|
||||
$formEditorIndex,
|
||||
'configurationOptions',
|
||||
'viewPorts',
|
||||
]
|
||||
);
|
||||
$viewPorts = ArrayUtility::getValueByPath($this->extractorDto->getPrototypeConfiguration(), $viewPortsPath, '.');
|
||||
foreach ($viewPorts as $viewPort) {
|
||||
$viewPortIdentifier = $viewPort['viewPortIdentifier'];
|
||||
$propertyPath = str_replace('{@viewPortIdentifier}', $viewPortIdentifier, $value);
|
||||
$paths[$formElementType]['propertyPaths'][] = $propertyPath;
|
||||
}
|
||||
} else {
|
||||
$paths[$formElementType]['propertyPaths'][] = $value;
|
||||
}
|
||||
return $paths;
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\FormElement;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AbstractExtractor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class SelectOptionsExtractor extends AbstractExtractor
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $_
|
||||
* @param mixed $value
|
||||
* @param array $matches
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
[, $formElementType, $formEditorIndex] = $matches;
|
||||
|
||||
$templateName = ArrayUtility::getValueByPath(
|
||||
$this->extractorDto->getPrototypeConfiguration(),
|
||||
implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'editors',
|
||||
$formEditorIndex,
|
||||
'templateName',
|
||||
]
|
||||
),
|
||||
'.'
|
||||
);
|
||||
|
||||
if ($templateName === 'Inspector-FinishersEditor') {
|
||||
$propertyPath = '_finishers';
|
||||
} elseif ($templateName === 'Inspector-ValidatorsEditor') {
|
||||
$propertyPath = '_validators';
|
||||
} else {
|
||||
if ($templateName === 'Inspector-RequiredValidatorEditor') {
|
||||
$propertyPath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'editors',
|
||||
$formEditorIndex,
|
||||
'configurationOptions',
|
||||
'validationErrorMessage',
|
||||
'propertyPath',
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$propertyPath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'editors',
|
||||
$formEditorIndex,
|
||||
'propertyPath',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
$propertyPath = ArrayUtility::getValueByPath(
|
||||
$this->extractorDto->getPrototypeConfiguration(),
|
||||
$propertyPath,
|
||||
'.'
|
||||
);
|
||||
}
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
$result['formElements'][$formElementType]['selectOptions'][$propertyPath][] = $value;
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\PropertyCollectionElement;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AbstractExtractor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class IsCreatablePropertyCollectionElementExtractor extends AbstractExtractor
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $_
|
||||
* @param mixed $value
|
||||
* @param array $matches
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
[, $formElementType, $formEditorIndex] = $matches;
|
||||
|
||||
if (
|
||||
$value !== 'Inspector-FinishersEditor'
|
||||
&& $value !== 'Inspector-ValidatorsEditor'
|
||||
&& $value !== 'Inspector-RequiredValidatorEditor'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$propertyCollectionName = $value === 'Inspector-FinishersEditor' ? 'finishers' : 'validators';
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
|
||||
if (
|
||||
$value === 'Inspector-FinishersEditor'
|
||||
|| $value === 'Inspector-ValidatorsEditor'
|
||||
) {
|
||||
$selectOptionsPath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'editors',
|
||||
$formEditorIndex,
|
||||
'selectOptions',
|
||||
]
|
||||
);
|
||||
if (!ArrayUtility::isValidPath($this->extractorDto->getPrototypeConfiguration(), $selectOptionsPath, '.')) {
|
||||
return;
|
||||
}
|
||||
$selectOptions = ArrayUtility::getValueByPath(
|
||||
$this->extractorDto->getPrototypeConfiguration(),
|
||||
$selectOptionsPath,
|
||||
'.'
|
||||
);
|
||||
foreach ($selectOptions as $selectOption) {
|
||||
$validatorIdentifier = $selectOption['value'] ?? '';
|
||||
if (empty($validatorIdentifier)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result['formElements'][$formElementType]['collections'][$propertyCollectionName][$validatorIdentifier]['creatable'] = true;
|
||||
}
|
||||
} else {
|
||||
$validatorIdentifierPath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'editors',
|
||||
$formEditorIndex,
|
||||
'validatorIdentifier',
|
||||
]
|
||||
);
|
||||
if (!ArrayUtility::isValidPath($this->extractorDto->getPrototypeConfiguration(), $validatorIdentifierPath, '.')) {
|
||||
return;
|
||||
}
|
||||
$validatorIdentifier = ArrayUtility::getValueByPath(
|
||||
$this->extractorDto->getPrototypeConfiguration(),
|
||||
$validatorIdentifierPath,
|
||||
'.'
|
||||
);
|
||||
$result['formElements'][$formElementType]['collections'][$propertyCollectionName][$validatorIdentifier]['creatable'] = true;
|
||||
}
|
||||
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\PropertyCollectionElement;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AbstractExtractor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class MultiValuePropertiesExtractor extends AbstractExtractor
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $_
|
||||
* @param mixed $value
|
||||
* @param array $matches
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
[, $formElementType, $propertyCollectionName, $propertyCollectionIndex, $propertyCollectionEditorIndex] = $matches;
|
||||
|
||||
if (
|
||||
$value !== 'Inspector-PropertyGridEditor'
|
||||
&& $value !== 'Inspector-MultiSelectEditor'
|
||||
&& $value !== 'Inspector-ValidationErrorMessageEditor'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$propertyPath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'propertyCollections',
|
||||
$propertyCollectionName,
|
||||
$propertyCollectionIndex,
|
||||
'editors',
|
||||
$propertyCollectionEditorIndex,
|
||||
'propertyPath',
|
||||
]
|
||||
);
|
||||
$propertyValue = ArrayUtility::getValueByPath($this->extractorDto->getPrototypeConfiguration(), $propertyPath, '.');
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
|
||||
if (
|
||||
$value === 'Inspector-PropertyGridEditor'
|
||||
|| $value === 'Inspector-MultiSelectEditor'
|
||||
) {
|
||||
$identifierPath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'propertyCollections',
|
||||
$propertyCollectionName,
|
||||
$propertyCollectionIndex,
|
||||
'identifier',
|
||||
]
|
||||
);
|
||||
$identifier = ArrayUtility::getValueByPath($this->extractorDto->getPrototypeConfiguration(), $identifierPath, '.');
|
||||
|
||||
$result['formElements'][$formElementType]['collections'][$propertyCollectionName][$identifier]['multiValueProperties'][] = $propertyValue;
|
||||
if ($value === 'Inspector-PropertyGridEditor') {
|
||||
$result['formElements'][$formElementType]['collections'][$propertyCollectionName][$identifier]['multiValueProperties'][] = 'defaultValue';
|
||||
}
|
||||
} else {
|
||||
$result['formElements'][$formElementType]['multiValueProperties'][] = $propertyValue;
|
||||
}
|
||||
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\PropertyCollectionElement;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AbstractExtractor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class PredefinedDefaultsExtractor extends AbstractExtractor
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $_
|
||||
* @param mixed $value
|
||||
* @param array $matches
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
[, $propertyCollectionName, $propertyCollectionElementIdentifier, $propertyPath] = $matches;
|
||||
$propertyCollectionName = str_replace('Definition', '', $propertyCollectionName);
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
$result['collections'][$propertyCollectionName][$propertyCollectionElementIdentifier]['predefinedDefaults'][$propertyPath] = $value;
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\PropertyCollectionElement;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AbstractExtractor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class PropertyPathsExtractor extends AbstractExtractor
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $_
|
||||
* @param mixed $value
|
||||
* @param array $matches
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
[, $formElementType, $propertyCollectionName, $propertyCollectionIndex] = $matches;
|
||||
|
||||
$identifierPath = implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'propertyCollections',
|
||||
$propertyCollectionName,
|
||||
$propertyCollectionIndex,
|
||||
'identifier',
|
||||
]
|
||||
);
|
||||
$identifier = ArrayUtility::getValueByPath($this->extractorDto->getPrototypeConfiguration(), $identifierPath, '.');
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
$result['formElements'][$formElementType]['collections'][$propertyCollectionName][$identifier]['propertyPaths'][] = $value;
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\PropertyCollectionElement;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\FrameworkConfiguration\Extractors\AbstractExtractor;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class SelectOptionsExtractor extends AbstractExtractor
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $_
|
||||
* @param mixed $value
|
||||
* @param array $matches
|
||||
*/
|
||||
public function __invoke(string $_, $value, array $matches)
|
||||
{
|
||||
[, $formElementType, $propertyCollectionName, $propertyCollectionIndex, $propertyCollectionEditorIndex] = $matches;
|
||||
|
||||
$propertyPath = ArrayUtility::getValueByPath(
|
||||
$this->extractorDto->getPrototypeConfiguration(),
|
||||
implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'propertyCollections',
|
||||
$propertyCollectionName,
|
||||
$propertyCollectionIndex,
|
||||
'editors',
|
||||
$propertyCollectionEditorIndex,
|
||||
'propertyPath',
|
||||
]
|
||||
),
|
||||
'.'
|
||||
);
|
||||
|
||||
$propertyCollectionElementIdentifier = ArrayUtility::getValueByPath(
|
||||
$this->extractorDto->getPrototypeConfiguration(),
|
||||
implode(
|
||||
'.',
|
||||
[
|
||||
'formElementsDefinition',
|
||||
$formElementType,
|
||||
'formEditor',
|
||||
'propertyCollections',
|
||||
$propertyCollectionName,
|
||||
$propertyCollectionIndex,
|
||||
'identifier'
|
||||
]
|
||||
),
|
||||
'.'
|
||||
);
|
||||
|
||||
$propertyCollectionName = str_replace('Definition', '', $propertyCollectionName);
|
||||
|
||||
$result = $this->extractorDto->getResult();
|
||||
$result['collections'][$propertyCollectionName][$propertyCollectionElementIdentifier]['selectOptions'][$propertyPath][] = $value;
|
||||
$this->extractorDto->setResult($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Exception as FormException;
|
||||
|
||||
/**
|
||||
* A generic Form domain Exception
|
||||
*/
|
||||
class Exception extends FormException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Exception;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown if the "identifier" for a Form, a Page or a Form Element
|
||||
* is invalid (i.e. empty or not a string)
|
||||
*/
|
||||
class IdentifierNotValidException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Exception;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown if a rendering error occurs
|
||||
*/
|
||||
class RenderingException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Exception;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown if a Type Definition for a form element was not found,
|
||||
* or if the implementationClassName was not set.
|
||||
*/
|
||||
class TypeDefinitionNotFoundException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Exception;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown if a Type Definition for a form element was not valid,
|
||||
* i.e. it has properties which are not supported.
|
||||
*/
|
||||
class TypeDefinitionNotValidException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Exception;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown if the ArrayFormFactory want to create child
|
||||
* elements within a unknown composite renderable.
|
||||
*/
|
||||
class UnknownCompositRenderableException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Factory;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Form\Domain\Model\FormDefinition;
|
||||
|
||||
/**
|
||||
* Base class for custom *Form Factories*. A Form Factory is responsible for building
|
||||
* a {@link TYPO3\CMS\Form\Domain\Model\FormDefinition}.
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Example
|
||||
* =======
|
||||
*
|
||||
* Generally, you should use this class as follows:
|
||||
*
|
||||
* <pre>
|
||||
* class MyFooBarFactory extends AbstractFormFactory {
|
||||
* public function build(array $configuration, $prototypeName) {
|
||||
* $configurationService = GeneralUtility::makeInstance(ObjectManager::class)->get(ConfigurationService::class);
|
||||
* $prototypeConfiguration = $configurationService->getPrototypeConfiguration($prototypeName);
|
||||
* $formDefinition = GeneralUtility::makeInstance(ObjectManager::class)->get(FormDefinition::class, 'nameOfMyForm', $prototypeConfiguration);
|
||||
*
|
||||
* // now, you should call methods on $formDefinition to add pages and form elements
|
||||
*
|
||||
* return $formDefinition;
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Scope: frontend / backend
|
||||
* **This class is meant to be sub classed by developers.**
|
||||
*/
|
||||
abstract class AbstractFormFactory implements FormFactoryInterface
|
||||
{
|
||||
/**
|
||||
* Helper to be called by every AbstractFormFactory after everything has been built to call the "afterBuildingFinished"
|
||||
* hook on all form elements.
|
||||
*
|
||||
* @param FormDefinition $form
|
||||
*/
|
||||
protected function triggerFormBuildingFinished(FormDefinition $form)
|
||||
{
|
||||
foreach ($form->getRenderablesRecursively() as $renderable) {
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterBuildingFinished'] ?? [] as $className) {
|
||||
$hookObj = GeneralUtility::makeInstance($className);
|
||||
if (method_exists($hookObj, 'afterBuildingFinished')) {
|
||||
$hookObj->afterBuildingFinished(
|
||||
$renderable
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Factory;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
use TYPO3\CMS\Form\Domain\Configuration\ConfigurationService;
|
||||
use TYPO3\CMS\Form\Domain\Exception\IdentifierNotValidException;
|
||||
use TYPO3\CMS\Form\Domain\Exception\RenderingException;
|
||||
use TYPO3\CMS\Form\Domain\Exception\UnknownCompositRenderableException;
|
||||
use TYPO3\CMS\Form\Domain\Model\FormDefinition;
|
||||
use TYPO3\CMS\Form\Domain\Model\FormElements\AbstractSection;
|
||||
use TYPO3\CMS\Form\Domain\Model\Renderable\CompositeRenderableInterface;
|
||||
|
||||
/**
|
||||
* A factory that creates a FormDefinition from an array
|
||||
*
|
||||
* Scope: frontend / backend
|
||||
*/
|
||||
class ArrayFormFactory extends AbstractFormFactory
|
||||
{
|
||||
|
||||
/**
|
||||
* Build a form definition, depending on some configuration.
|
||||
*
|
||||
* @param array $configuration
|
||||
* @param string $prototypeName
|
||||
* @return FormDefinition
|
||||
* @throws RenderingException
|
||||
* @internal
|
||||
*/
|
||||
public function build(array $configuration, string $prototypeName = null): FormDefinition
|
||||
{
|
||||
if (empty($prototypeName)) {
|
||||
$prototypeName = $configuration['prototypeName'] ?? 'standard';
|
||||
}
|
||||
$persistenceIdentifier = $configuration['persistenceIdentifier'] ?? null;
|
||||
|
||||
if ($configuration['invalid'] === true) {
|
||||
throw new RenderingException($configuration['label'], 1529710560);
|
||||
}
|
||||
|
||||
$prototypeConfiguration = GeneralUtility::makeInstance(ObjectManager::class)
|
||||
->get(ConfigurationService::class)
|
||||
->getPrototypeConfiguration($prototypeName);
|
||||
|
||||
$form = GeneralUtility::makeInstance(ObjectManager::class)->get(
|
||||
FormDefinition::class,
|
||||
$configuration['identifier'],
|
||||
$prototypeConfiguration,
|
||||
'Form',
|
||||
$persistenceIdentifier
|
||||
);
|
||||
if (isset($configuration['renderables'])) {
|
||||
foreach ($configuration['renderables'] as $pageConfiguration) {
|
||||
$this->addNestedRenderable($pageConfiguration, $form);
|
||||
}
|
||||
}
|
||||
|
||||
unset($configuration['persistenceIdentifier']);
|
||||
unset($configuration['prototypeName']);
|
||||
unset($configuration['renderables']);
|
||||
unset($configuration['type']);
|
||||
unset($configuration['identifier']);
|
||||
$form->setOptions($configuration);
|
||||
|
||||
$this->triggerFormBuildingFinished($form);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add form elements to the $parentRenderable
|
||||
*
|
||||
* @param array $nestedRenderableConfiguration
|
||||
* @param CompositeRenderableInterface $parentRenderable
|
||||
* @return mixed
|
||||
* @throws IdentifierNotValidException
|
||||
* @throws UnknownCompositRenderableException
|
||||
*/
|
||||
protected function addNestedRenderable(array $nestedRenderableConfiguration, CompositeRenderableInterface $parentRenderable)
|
||||
{
|
||||
if (!isset($nestedRenderableConfiguration['identifier'])) {
|
||||
throw new IdentifierNotValidException('Identifier not set.', 1329289436);
|
||||
}
|
||||
if ($parentRenderable instanceof FormDefinition) {
|
||||
$renderable = $parentRenderable->createPage($nestedRenderableConfiguration['identifier'], $nestedRenderableConfiguration['type']);
|
||||
} elseif ($parentRenderable instanceof AbstractSection) {
|
||||
$renderable = $parentRenderable->createElement($nestedRenderableConfiguration['identifier'], $nestedRenderableConfiguration['type']);
|
||||
} else {
|
||||
throw new UnknownCompositRenderableException('Unknown composit renderable "' . get_class($parentRenderable) . '"', 1479593622);
|
||||
}
|
||||
|
||||
if (isset($nestedRenderableConfiguration['renderables']) && is_array($nestedRenderableConfiguration['renderables'])) {
|
||||
$childRenderables = $nestedRenderableConfiguration['renderables'];
|
||||
} else {
|
||||
$childRenderables = [];
|
||||
}
|
||||
|
||||
unset($nestedRenderableConfiguration['type']);
|
||||
unset($nestedRenderableConfiguration['identifier']);
|
||||
unset($nestedRenderableConfiguration['renderables']);
|
||||
|
||||
$renderable->setOptions($nestedRenderableConfiguration);
|
||||
|
||||
if ($renderable instanceof CompositeRenderableInterface) {
|
||||
foreach ($childRenderables as $elementConfiguration) {
|
||||
$this->addNestedRenderable($elementConfiguration, $renderable);
|
||||
}
|
||||
}
|
||||
|
||||
return $renderable;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Factory;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Model\FormDefinition;
|
||||
|
||||
/**
|
||||
* A Form Factory is responsible for building a {@link TYPO3\CMS\Form\Domain\Model\FormDefinition}.
|
||||
* **Instead of implementing this interface, subclassing {@link AbstractFormFactory} is more appropriate
|
||||
* in most cases**.
|
||||
*
|
||||
* A Form Factory can be called anytime a FormDefinition should be built; in most cases
|
||||
* it is done through an invocation of a Form Rendering ViewHelper.
|
||||
*
|
||||
* Scope: frontend / backend
|
||||
*/
|
||||
interface FormFactoryInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Build a form definition, depending on some configuration.
|
||||
*
|
||||
* The configuration array is factory-specific; for example a YAML or JSON factory
|
||||
* could retrieve the path to the YAML / JSON file via the configuration array.
|
||||
*
|
||||
* @param array $configuration factory-specific configuration array
|
||||
* @param string $prototypeName The name of the "PrototypeName" to use; it is factory-specific to implement this.
|
||||
* @return FormDefinition a newly built form definition
|
||||
*/
|
||||
public function build(array $configuration, string $prototypeName = null): FormDefinition;
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Finishers;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
|
||||
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
|
||||
use TYPO3\CMS\Form\Domain\Finishers\Exception\FinisherException;
|
||||
use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
|
||||
use TYPO3\CMS\Form\Service\TranslationService;
|
||||
use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
|
||||
|
||||
/**
|
||||
* Finisher base class.
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is meant to be sub classed by developers**
|
||||
*/
|
||||
abstract class AbstractFinisher implements FinisherInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
|
||||
*/
|
||||
protected $objectManager;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $finisherIdentifier = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $shortFinisherIdentifier = '';
|
||||
|
||||
/**
|
||||
* The options which have been set from the outside. Instead of directly
|
||||
* accessing them, you should rather use parseOption().
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
* These are the default options of the finisher.
|
||||
* Override them in your concrete implementation.
|
||||
* Default options should not be changed from "outside"
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultOptions = [];
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Form\Domain\Finishers\FinisherContext
|
||||
*/
|
||||
protected $finisherContext;
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
|
||||
* @internal
|
||||
*/
|
||||
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
|
||||
{
|
||||
$this->objectManager = $objectManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $finisherIdentifier The identifier for this finisher
|
||||
*/
|
||||
public function __construct(string $finisherIdentifier = '')
|
||||
{
|
||||
if (empty($finisherIdentifier)) {
|
||||
$this->finisherIdentifier = (new \ReflectionClass($this))->getShortName();
|
||||
} else {
|
||||
$this->finisherIdentifier = $finisherIdentifier;
|
||||
}
|
||||
|
||||
$this->shortFinisherIdentifier = preg_replace('/Finisher$/', '', $this->finisherIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFinisherIdentifier(): string
|
||||
{
|
||||
return $this->finisherIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options configuration options in the format ['option1' => 'value1', 'option2' => 'value2', ...]
|
||||
*/
|
||||
public function setOptions(array $options)
|
||||
{
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a single finisher option (@see setOptions())
|
||||
*
|
||||
* @param string $optionName name of the option to be set
|
||||
* @param mixed $optionValue value of the option
|
||||
*/
|
||||
public function setOption(string $optionName, $optionValue)
|
||||
{
|
||||
$this->options[$optionName] = $optionValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the finisher
|
||||
*
|
||||
* @param FinisherContext $finisherContext The Finisher context that contains the current Form Runtime and Response
|
||||
* @return string|null
|
||||
*/
|
||||
final public function execute(FinisherContext $finisherContext)
|
||||
{
|
||||
$this->finisherContext = $finisherContext;
|
||||
|
||||
if (!$this->isEnabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->executeInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called in the concrete finisher whenever self::execute() is called.
|
||||
*
|
||||
* Override and fill with your own implementation!
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
abstract protected function executeInternal();
|
||||
|
||||
/**
|
||||
* Read the option called $optionName from $this->options, and parse {...}
|
||||
* as object accessors.
|
||||
*
|
||||
* Then translate the value.
|
||||
*
|
||||
* If $optionName was not found, the corresponding default option is returned (from $this->defaultOptions)
|
||||
*
|
||||
* @param string $optionName
|
||||
* @return string|array|null
|
||||
*/
|
||||
protected function parseOption(string $optionName)
|
||||
{
|
||||
if ($optionName === 'translation') {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$optionValue = ArrayUtility::getValueByPath($this->options, $optionName, '.');
|
||||
} catch (MissingArrayPathException $exception) {
|
||||
$optionValue = null;
|
||||
}
|
||||
try {
|
||||
$defaultValue = ArrayUtility::getValueByPath($this->defaultOptions, $optionName, '.');
|
||||
} catch (MissingArrayPathException $exception) {
|
||||
$defaultValue = null;
|
||||
}
|
||||
|
||||
if ($optionValue === null && $defaultValue !== null) {
|
||||
$optionValue = $defaultValue;
|
||||
}
|
||||
|
||||
if ($optionValue === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!is_string($optionValue) && !is_array($optionValue)) {
|
||||
return $optionValue;
|
||||
}
|
||||
|
||||
$formRuntime = $this->finisherContext->getFormRuntime();
|
||||
$optionValue = $this->substituteRuntimeReferences($optionValue, $formRuntime);
|
||||
|
||||
if (is_string($optionValue)) {
|
||||
$translationOptions = isset($this->options['translation']) && \is_array($this->options['translation'])
|
||||
? $this->options['translation']
|
||||
: [];
|
||||
|
||||
$optionValue = $this->translateFinisherOption(
|
||||
$optionValue,
|
||||
$formRuntime,
|
||||
$optionName,
|
||||
$optionValue,
|
||||
$translationOptions
|
||||
);
|
||||
|
||||
$optionValue = $this->substituteRuntimeReferences($optionValue, $formRuntime);
|
||||
}
|
||||
|
||||
if (empty($optionValue)) {
|
||||
if ($defaultValue !== null) {
|
||||
$optionValue = $defaultValue;
|
||||
}
|
||||
}
|
||||
return $optionValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps TranslationService::translateFinisherOption to recursively
|
||||
* invoke all array items of resolved form state values or nested
|
||||
* finisher option configuration settings.
|
||||
*
|
||||
* @param string|array $subject
|
||||
* @param FormRuntime $formRuntime
|
||||
* @param string $optionName
|
||||
* @param string|array $optionValue
|
||||
* @param array $translationOptions
|
||||
* @return array|string
|
||||
*/
|
||||
protected function translateFinisherOption(
|
||||
$subject,
|
||||
FormRuntime $formRuntime,
|
||||
string $optionName,
|
||||
$optionValue,
|
||||
array $translationOptions
|
||||
) {
|
||||
if (is_array($subject)) {
|
||||
foreach ($subject as $key => $value) {
|
||||
$subject[$key] = $this->translateFinisherOption(
|
||||
$value,
|
||||
$formRuntime,
|
||||
$optionName . '.' . $value,
|
||||
$value,
|
||||
$translationOptions
|
||||
);
|
||||
}
|
||||
return $subject;
|
||||
}
|
||||
|
||||
return TranslationService::getInstance()->translateFinisherOption(
|
||||
$formRuntime,
|
||||
$this->finisherIdentifier,
|
||||
$optionName,
|
||||
$optionValue,
|
||||
$translationOptions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* You can encapsulate a option value with {}.
|
||||
* This enables you to access every getable property from the
|
||||
* TYPO3\CMS\Form\Domain\Runtime\FormRuntime.
|
||||
*
|
||||
* For example: {formState.formValues.<elemenIdentifier>}
|
||||
* or {<elemenIdentifier>}
|
||||
*
|
||||
* Both examples are equal to "$formRuntime->getFormState()->getFormValues()[<elemenIdentifier>]"
|
||||
* There is a special option value '{__currentTimestamp}'.
|
||||
* This will be replaced with the current timestamp.
|
||||
*
|
||||
* @param string|array $needle
|
||||
* @param FormRuntime $formRuntime
|
||||
* @return mixed
|
||||
*/
|
||||
protected function substituteRuntimeReferences($needle, FormRuntime $formRuntime)
|
||||
{
|
||||
// neither array nor string, directly return
|
||||
if (!is_array($needle) && !is_string($needle)) {
|
||||
return $needle;
|
||||
}
|
||||
|
||||
// resolve (recursively) all array items
|
||||
if (is_array($needle)) {
|
||||
return array_map(
|
||||
function ($item) use ($formRuntime) {
|
||||
return $this->substituteRuntimeReferences($item, $formRuntime);
|
||||
},
|
||||
$needle
|
||||
);
|
||||
}
|
||||
|
||||
// substitute one(!) variable in string which either could result
|
||||
// again in a string or an array representing multiple values
|
||||
if (preg_match('/^{([^}]+)}$/', $needle, $matches)) {
|
||||
return $this->resolveRuntimeReference(
|
||||
$matches[1],
|
||||
$formRuntime
|
||||
);
|
||||
}
|
||||
|
||||
// in case string contains more than just one variable or just a static
|
||||
// value that does not need to be substituted at all, candidates are:
|
||||
// * "prefix{variable}suffix
|
||||
// * "{variable-1},{variable-2}"
|
||||
// * "some static value"
|
||||
// * mixed cases of the above
|
||||
return preg_replace_callback(
|
||||
'/{([^}]+)}/',
|
||||
function ($matches) use ($formRuntime) {
|
||||
$value = $this->resolveRuntimeReference(
|
||||
$matches[1],
|
||||
$formRuntime
|
||||
);
|
||||
|
||||
// substitute each match by returning the resolved value
|
||||
if (!is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// now the resolve value is an array that shall substitute
|
||||
// a variable in a string that probably is not the only one
|
||||
// or is wrapped with other static string content (see above)
|
||||
// ... which is just not possible
|
||||
throw new FinisherException(
|
||||
'Cannot convert array to string',
|
||||
1519239265
|
||||
);
|
||||
},
|
||||
$needle
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolving property by name from submitted form data.
|
||||
*
|
||||
* @param string $property
|
||||
* @param FormRuntime $formRuntime
|
||||
* @return int|string|array
|
||||
*/
|
||||
protected function resolveRuntimeReference(string $property, FormRuntime $formRuntime)
|
||||
{
|
||||
if ($property === '__currentTimestamp') {
|
||||
return time();
|
||||
}
|
||||
// try to resolve the path '{...}' within the FormRuntime
|
||||
$value = ObjectAccess::getPropertyPath($formRuntime, $property);
|
||||
if ($value === null) {
|
||||
// try to resolve the path '{...}' within the FinisherVariableProvider
|
||||
$value = ObjectAccess::getPropertyPath(
|
||||
$this->finisherContext->getFinisherVariableProvider(),
|
||||
$property
|
||||
);
|
||||
}
|
||||
if ($value !== null) {
|
||||
return $value;
|
||||
}
|
||||
// in case no value could be resolved
|
||||
return '{' . $property . '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this finisher is enabled
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return !isset($this->options['renderingOptions']['enabled']) || (bool)$this->parseOption('renderingOptions.enabled') === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TypoScriptFrontendController
|
||||
*/
|
||||
protected function getTypoScriptFrontendController()
|
||||
{
|
||||
return $GLOBALS['TSFE'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Finishers;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Finishers\Exception\FinisherException;
|
||||
|
||||
/**
|
||||
* A simple finisher that invokes a closure when executed
|
||||
*
|
||||
* Usage:
|
||||
* //...
|
||||
* $closureFinisher = $this->objectManager->get(ClosureFinisher::class);
|
||||
* $closureFinisher->setOption('closure', function($finisherContext) {
|
||||
* $formRuntime = $finisherContext->getFormRuntime();
|
||||
* // ...
|
||||
* });
|
||||
* $formDefinition->addFinisher($closureFinisher);
|
||||
* // ...
|
||||
*
|
||||
* Scope: frontend
|
||||
*/
|
||||
class ClosureFinisher extends AbstractFinisher
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultOptions = [
|
||||
'closure' => null
|
||||
];
|
||||
|
||||
/**
|
||||
* Executes this finisher
|
||||
* @see AbstractFinisher::execute()
|
||||
*
|
||||
* @throws FinisherException
|
||||
*/
|
||||
protected function executeInternal()
|
||||
{
|
||||
/** @var \Closure $closure */
|
||||
$closure = $this->parseOption('closure');
|
||||
if ($closure === null) {
|
||||
return;
|
||||
}
|
||||
if (!$closure instanceof \Closure) {
|
||||
throw new FinisherException(sprintf('The option "closure" must be of type Closure, "%s" given.', gettype($closure)), 1332155239);
|
||||
}
|
||||
$closure($this->finisherContext);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Finishers;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
use TYPO3\CMS\Form\Domain\Finishers\Exception\FinisherException;
|
||||
use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
|
||||
use TYPO3\CMS\Form\ViewHelpers\RenderRenderableViewHelper;
|
||||
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
|
||||
|
||||
/**
|
||||
* A finisher that outputs a given text
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - message: A hard-coded message to be rendered
|
||||
* - contentElementUid: A content element uid to be rendered
|
||||
*
|
||||
* Usage:
|
||||
* //...
|
||||
* $confirmationFinisher = $this->objectManager->get(ConfirmationFinisher::class);
|
||||
* $confirmationFinisher->setOptions(
|
||||
* [
|
||||
* 'message' => 'foo',
|
||||
* ]
|
||||
* );
|
||||
* $formDefinition->addFinisher($confirmationFinisher);
|
||||
* // ...
|
||||
*
|
||||
* Scope: frontend
|
||||
*/
|
||||
class ConfirmationFinisher extends AbstractFinisher
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultOptions = [
|
||||
'message' => 'The form has been submitted.',
|
||||
'contentElementUid' => 0,
|
||||
'typoscriptObjectPath' => 'lib.tx_form.contentElementRendering'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $typoScriptSetup = [];
|
||||
|
||||
/**
|
||||
* @var ConfigurationManagerInterface
|
||||
*/
|
||||
protected $configurationManager;
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
|
||||
*/
|
||||
public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager)
|
||||
{
|
||||
$this->configurationManager = $configurationManager;
|
||||
$this->typoScriptSetup = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ContentObjectRenderer $contentObjectRenderer
|
||||
*/
|
||||
public function injectContentObjectRenderer(ContentObjectRenderer $contentObjectRenderer)
|
||||
{
|
||||
$this->contentObjectRenderer = $contentObjectRenderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes this finisher
|
||||
*
|
||||
* @see AbstractFinisher::execute()
|
||||
* @return string
|
||||
*
|
||||
* @throws FinisherException
|
||||
*/
|
||||
protected function executeInternal()
|
||||
{
|
||||
$contentElementUid = $this->parseOption('contentElementUid');
|
||||
$typoscriptObjectPath = $this->parseOption('typoscriptObjectPath');
|
||||
if (!empty($contentElementUid)) {
|
||||
$pathSegments = GeneralUtility::trimExplode('.', $typoscriptObjectPath);
|
||||
$lastSegment = array_pop($pathSegments);
|
||||
$setup = $this->typoScriptSetup;
|
||||
foreach ($pathSegments as $segment) {
|
||||
if (!array_key_exists($segment . '.', $setup)) {
|
||||
throw new FinisherException(
|
||||
sprintf('TypoScript object path "%s" does not exist', $typoscriptObjectPath),
|
||||
1489238980
|
||||
);
|
||||
}
|
||||
$setup = $setup[$segment . '.'];
|
||||
}
|
||||
$this->contentObjectRenderer->start([$contentElementUid], '');
|
||||
$this->contentObjectRenderer->setCurrentVal((string)$contentElementUid);
|
||||
$message = $this->contentObjectRenderer->cObjGetSingle($setup[$lastSegment], $setup[$lastSegment . '.'], $lastSegment);
|
||||
} else {
|
||||
$message = $this->parseOption('message');
|
||||
}
|
||||
|
||||
$standaloneView = $this->initializeStandaloneView(
|
||||
$this->finisherContext->getFormRuntime()
|
||||
);
|
||||
|
||||
$standaloneView->assignMultiple([
|
||||
'message' => $message,
|
||||
'isPreparedMessage' => !empty($contentElementUid),
|
||||
]);
|
||||
|
||||
return $standaloneView->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormRuntime $formRuntime
|
||||
* @return StandaloneView
|
||||
* @throws FinisherException
|
||||
*/
|
||||
protected function initializeStandaloneView(FormRuntime $formRuntime): StandaloneView
|
||||
{
|
||||
$standaloneView = $this->objectManager->get(StandaloneView::class);
|
||||
|
||||
if (!isset($this->options['templateName'])) {
|
||||
throw new FinisherException(
|
||||
'The option "templateName" must be set for the ConfirmationFinisher.',
|
||||
1521573955
|
||||
);
|
||||
}
|
||||
|
||||
$standaloneView->setTemplate($this->options['templateName']);
|
||||
$standaloneView->getTemplatePaths()->fillFromConfigurationArray($this->options);
|
||||
|
||||
if (isset($this->options['variables']) && is_array($this->options['variables'])) {
|
||||
$standaloneView->assignMultiple($this->options['variables']);
|
||||
}
|
||||
|
||||
$standaloneView->assign('form', $formRuntime);
|
||||
$standaloneView->assign('finisherVariableProvider', $this->finisherContext->getFinisherVariableProvider());
|
||||
|
||||
$standaloneView->getRenderingContext()
|
||||
->getViewHelperVariableContainer()
|
||||
->addOrUpdate(RenderRenderableViewHelper::class, 'formRuntime', $formRuntime);
|
||||
|
||||
return $standaloneView;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Finishers;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Resource\Folder;
|
||||
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
|
||||
use TYPO3\CMS\Form\Domain\Model\FormElements\FileUpload;
|
||||
|
||||
/**
|
||||
* This finisher remove the submited files.
|
||||
* Use this e.g after the email finisher if you don't want
|
||||
* to keep the files online.
|
||||
*
|
||||
* Scope: frontend
|
||||
*/
|
||||
class DeleteUploadsFinisher extends AbstractFinisher
|
||||
{
|
||||
|
||||
/**
|
||||
* Executes this finisher
|
||||
* @see AbstractFinisher::execute()
|
||||
*/
|
||||
protected function executeInternal()
|
||||
{
|
||||
$formRuntime = $this->finisherContext->getFormRuntime();
|
||||
|
||||
$uploadFolders = [];
|
||||
$elements = $formRuntime->getFormDefinition()->getRenderablesRecursively();
|
||||
foreach ($elements as $element) {
|
||||
if (!$element instanceof FileUpload) {
|
||||
continue;
|
||||
}
|
||||
$file = $formRuntime[$element->getIdentifier()];
|
||||
if (!$file) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($file instanceof FileReference) {
|
||||
$file = $file->getOriginalResource();
|
||||
}
|
||||
|
||||
$folder = $file->getParentFolder();
|
||||
$uploadFolders[$folder->getCombinedIdentifier()] = $folder;
|
||||
|
||||
$file->getStorage()->deleteFile($file->getOriginalFile());
|
||||
}
|
||||
|
||||
$this->deleteEmptyUploadFolders($uploadFolders);
|
||||
}
|
||||
|
||||
/**
|
||||
* note:
|
||||
* TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter::importUploadedResource()
|
||||
* creates a sub-folder for file uploads (e.g. .../form_<40-chars-hash>/actual.file)
|
||||
* @param Folder[] $folders
|
||||
*/
|
||||
protected function deleteEmptyUploadFolders(array $folders): void
|
||||
{
|
||||
foreach ($folders as $folder) {
|
||||
if ($this->isEmptyFolder($folder)) {
|
||||
$folder->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Folder $folder
|
||||
* @return bool
|
||||
*/
|
||||
protected function isEmptyFolder(Folder $folder): bool
|
||||
{
|
||||
return $folder->getFileCount() === 0
|
||||
&& $folder->getStorage()->countFoldersInFolder($folder) === 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Finishers;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Mail\MailMessage;
|
||||
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
|
||||
use TYPO3\CMS\Fluid\View\StandaloneView;
|
||||
use TYPO3\CMS\Form\Domain\Finishers\Exception\FinisherException;
|
||||
use TYPO3\CMS\Form\Domain\Model\FormElements\FileUpload;
|
||||
use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
|
||||
use TYPO3\CMS\Form\Service\TranslationService;
|
||||
use TYPO3\CMS\Form\ViewHelpers\RenderRenderableViewHelper;
|
||||
|
||||
/**
|
||||
* This finisher sends an email to one recipient
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - templatePathAndFilename (mandatory): Template path and filename for the mail body
|
||||
* - layoutRootPath: root path for the layouts
|
||||
* - partialRootPath: root path for the partials
|
||||
* - variables: associative array of variables which are available inside the Fluid template
|
||||
*
|
||||
* The following options control the mail sending. In all of them, placeholders in the form
|
||||
* of {...} are replaced with the corresponding form value; i.e. {email} as recipientAddress
|
||||
* makes the recipient address configurable.
|
||||
*
|
||||
* - subject (mandatory): Subject of the email
|
||||
* - recipientAddress (mandatory): Email address of the recipient
|
||||
* - recipientName: Human-readable name of the recipient
|
||||
* - senderAddress (mandatory): Email address of the sender
|
||||
* - senderName: Human-readable name of the sender
|
||||
* - replyToAddress: Email address of to be used as reply-to email (use multiple addresses with an array)
|
||||
* - carbonCopyAddress: Email address of the copy recipient (use multiple addresses with an array)
|
||||
* - blindCarbonCopyAddress: Email address of the blind copy recipient (use multiple addresses with an array)
|
||||
* - format: format of the email (one of the FORMAT_* constants). By default mails are sent as HTML
|
||||
*
|
||||
* Scope: frontend
|
||||
*/
|
||||
class EmailFinisher extends AbstractFinisher
|
||||
{
|
||||
const FORMAT_PLAINTEXT = 'plaintext';
|
||||
const FORMAT_HTML = 'html';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultOptions = [
|
||||
'recipientName' => '',
|
||||
'senderName' => '',
|
||||
'format' => self::FORMAT_HTML,
|
||||
'attachUploads' => true
|
||||
];
|
||||
|
||||
/**
|
||||
* Executes this finisher
|
||||
* @see AbstractFinisher::execute()
|
||||
*
|
||||
* @throws FinisherException
|
||||
*/
|
||||
protected function executeInternal()
|
||||
{
|
||||
$formRuntime = $this->finisherContext->getFormRuntime();
|
||||
$standaloneView = $this->initializeStandaloneView($formRuntime);
|
||||
|
||||
$translationService = TranslationService::getInstance();
|
||||
if (isset($this->options['translation']['language']) && !empty($this->options['translation']['language'])) {
|
||||
$languageBackup = $translationService->getLanguage();
|
||||
$translationService->setLanguage($this->options['translation']['language']);
|
||||
}
|
||||
$message = $standaloneView->render();
|
||||
if (!empty($languageBackup)) {
|
||||
$translationService->setLanguage($languageBackup);
|
||||
}
|
||||
|
||||
$subject = $this->parseOption('subject');
|
||||
$recipientAddress = $this->parseOption('recipientAddress');
|
||||
$recipientName = $this->parseOption('recipientName');
|
||||
$senderAddress = $this->parseOption('senderAddress');
|
||||
$senderName = $this->parseOption('senderName');
|
||||
$replyToAddress = $this->parseOption('replyToAddress');
|
||||
$carbonCopyAddress = $this->parseOption('carbonCopyAddress');
|
||||
$blindCarbonCopyAddress = $this->parseOption('blindCarbonCopyAddress');
|
||||
$format = $this->parseOption('format');
|
||||
$attachUploads = $this->parseOption('attachUploads');
|
||||
|
||||
if (empty($subject)) {
|
||||
throw new FinisherException('The option "subject" must be set for the EmailFinisher.', 1327060320);
|
||||
}
|
||||
if (empty($recipientAddress)) {
|
||||
throw new FinisherException('The option "recipientAddress" must be set for the EmailFinisher.', 1327060200);
|
||||
}
|
||||
if (empty($senderAddress)) {
|
||||
throw new FinisherException('The option "senderAddress" must be set for the EmailFinisher.', 1327060210);
|
||||
}
|
||||
|
||||
$mail = $this->objectManager->get(MailMessage::class);
|
||||
|
||||
$mail->setFrom([$senderAddress => $senderName])
|
||||
->setTo([$recipientAddress => $recipientName])
|
||||
->setSubject($subject);
|
||||
|
||||
if (!empty($replyToAddress)) {
|
||||
$mail->setReplyTo($replyToAddress);
|
||||
}
|
||||
|
||||
if (!empty($carbonCopyAddress)) {
|
||||
$mail->setCc($carbonCopyAddress);
|
||||
}
|
||||
|
||||
if (!empty($blindCarbonCopyAddress)) {
|
||||
$mail->setBcc($blindCarbonCopyAddress);
|
||||
}
|
||||
|
||||
if ($format === self::FORMAT_PLAINTEXT) {
|
||||
$mail->setBody($message, 'text/plain');
|
||||
} else {
|
||||
$mail->setBody($message, 'text/html');
|
||||
}
|
||||
|
||||
$elements = $formRuntime->getFormDefinition()->getRenderablesRecursively();
|
||||
|
||||
if ($attachUploads) {
|
||||
foreach ($elements as $element) {
|
||||
if (!$element instanceof FileUpload) {
|
||||
continue;
|
||||
}
|
||||
$file = $formRuntime[$element->getIdentifier()];
|
||||
if ($file) {
|
||||
if ($file instanceof FileReference) {
|
||||
$file = $file->getOriginalResource();
|
||||
}
|
||||
|
||||
$mail->attach(\Swift_Attachment::newInstance($file->getContents(), $file->getName(), $file->getMimeType()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$mail->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormRuntime $formRuntime
|
||||
* @return StandaloneView
|
||||
* @throws FinisherException
|
||||
*/
|
||||
protected function initializeStandaloneView(FormRuntime $formRuntime): StandaloneView
|
||||
{
|
||||
$format = ucfirst($this->parseOption('format'));
|
||||
$standaloneView = $this->objectManager->get(StandaloneView::class);
|
||||
|
||||
if (isset($this->options['templatePathAndFilename'])) {
|
||||
$this->options['templatePathAndFilename'] = strtr($this->options['templatePathAndFilename'], [
|
||||
'{@format}' => $format
|
||||
]);
|
||||
$standaloneView->setTemplatePathAndFilename($this->options['templatePathAndFilename']);
|
||||
} else {
|
||||
if (!isset($this->options['templateName'])) {
|
||||
throw new FinisherException('The option "templateName" must be set for the EmailFinisher.', 1327058829);
|
||||
}
|
||||
$this->options['templateName'] = strtr($this->options['templateName'], [
|
||||
'{@format}' => $format
|
||||
]);
|
||||
$standaloneView->setTemplate($this->options['templateName']);
|
||||
}
|
||||
|
||||
$standaloneView->assign('finisherVariableProvider', $this->finisherContext->getFinisherVariableProvider());
|
||||
|
||||
if (isset($this->options['templateRootPaths']) && is_array($this->options['templateRootPaths'])) {
|
||||
$standaloneView->setTemplateRootPaths($this->options['templateRootPaths']);
|
||||
}
|
||||
|
||||
if (isset($this->options['partialRootPaths']) && is_array($this->options['partialRootPaths'])) {
|
||||
$standaloneView->setPartialRootPaths($this->options['partialRootPaths']);
|
||||
}
|
||||
|
||||
if (isset($this->options['layoutRootPaths']) && is_array($this->options['layoutRootPaths'])) {
|
||||
$standaloneView->setLayoutRootPaths($this->options['layoutRootPaths']);
|
||||
}
|
||||
|
||||
if (isset($this->options['variables'])) {
|
||||
$standaloneView->assignMultiple($this->options['variables']);
|
||||
}
|
||||
|
||||
$standaloneView->assign('form', $formRuntime);
|
||||
$standaloneView->getRenderingContext()
|
||||
->getViewHelperVariableContainer()
|
||||
->addOrUpdate(RenderRenderableViewHelper::class, 'formRuntime', $formRuntime);
|
||||
|
||||
return $standaloneView;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Finishers\Exception;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown in Form Finishers
|
||||
*/
|
||||
class FinisherException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Finishers;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
|
||||
|
||||
/**
|
||||
* The context that is passed to each finisher when executed.
|
||||
* It acts like an EventObject that is able to stop propagation.
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
* @internal
|
||||
*/
|
||||
class FinisherContext
|
||||
{
|
||||
|
||||
/**
|
||||
* If TRUE further finishers won't be invoked
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $cancelled = false;
|
||||
|
||||
/**
|
||||
* A reference to the Form Runtime that the finisher belongs to
|
||||
*
|
||||
* @var \TYPO3\CMS\Form\Domain\Runtime\FormRuntime
|
||||
*/
|
||||
protected $formRuntime;
|
||||
|
||||
/**
|
||||
* The assigned controller context which might be needed by the finisher.
|
||||
*
|
||||
* @var \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext
|
||||
*/
|
||||
protected $controllerContext;
|
||||
|
||||
/**
|
||||
* The assigned controller context which might be needed by the finisher.
|
||||
*
|
||||
* @var FinisherVariableProvider
|
||||
*/
|
||||
protected $finisherVariableProvider;
|
||||
|
||||
/**
|
||||
* @param FormRuntime $formRuntime
|
||||
* @param ControllerContext $controllerContext
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(FormRuntime $formRuntime, ControllerContext $controllerContext)
|
||||
{
|
||||
$this->formRuntime = $formRuntime;
|
||||
$this->controllerContext = $controllerContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the FinisherVariableProvider
|
||||
*/
|
||||
public function initializeObject()
|
||||
{
|
||||
$this->finisherVariableProvider = GeneralUtility::makeInstance(ObjectManager::class)
|
||||
->get(FinisherVariableProvider::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the finisher invocation after the current finisher
|
||||
*/
|
||||
public function cancel()
|
||||
{
|
||||
$this->cancelled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* TRUE if no further finishers should be invoked. Defaults to FALSE
|
||||
*
|
||||
* @return bool
|
||||
* @internal
|
||||
*/
|
||||
public function isCancelled(): bool
|
||||
{
|
||||
return $this->cancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Form Runtime that is associated with the current finisher
|
||||
*
|
||||
* @return FormRuntime
|
||||
*/
|
||||
public function getFormRuntime(): FormRuntime
|
||||
{
|
||||
return $this->formRuntime;
|
||||
}
|
||||
|
||||
/**
|
||||
* The values of the submitted form (after validation and property mapping)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFormValues(): array
|
||||
{
|
||||
return $this->formRuntime->getFormState()->getFormValues();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ControllerContext
|
||||
*/
|
||||
public function getControllerContext(): ControllerContext
|
||||
{
|
||||
return $this->controllerContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FinisherVariableProvider
|
||||
*/
|
||||
public function getFinisherVariableProvider(): FinisherVariableProvider
|
||||
{
|
||||
return $this->finisherVariableProvider;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Finishers;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Finisher that can be attached to a form in order to be invoked
|
||||
* as soon as the complete form is submitted
|
||||
*
|
||||
* Scope: frontend
|
||||
*/
|
||||
interface FinisherInterface
|
||||
{
|
||||
/**
|
||||
* Executes the finisher
|
||||
*
|
||||
* @param FinisherContext $finisherContext The Finisher context that contains the current Form Runtime and Response
|
||||
* @return string|null
|
||||
*/
|
||||
public function execute(FinisherContext $finisherContext);
|
||||
|
||||
/**
|
||||
* @param array $options configuration options in the format ['option1' => 'value1', 'option2' => 'value2', ...]
|
||||
*/
|
||||
public function setOptions(array $options);
|
||||
|
||||
/**
|
||||
* Sets a single finisher option (@see setOptions())
|
||||
*
|
||||
* @param string $optionName name of the option to be set
|
||||
* @param mixed $optionValue value of the option
|
||||
*/
|
||||
public function setOption(string $optionName, $optionValue);
|
||||
|
||||
/**
|
||||
* Returns whether this finisher is enabled
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEnabled(): bool;
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Finishers;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
|
||||
|
||||
/**
|
||||
* Store data for usage between the finishers.
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
* @internal
|
||||
*/
|
||||
final class FinisherVariableProvider implements \ArrayAccess, \IteratorAggregate, \Countable
|
||||
{
|
||||
|
||||
/**
|
||||
* Two-dimensional object array storing the values. The first dimension is the finisher identifier,
|
||||
* and the second dimension is the identifier for the data the finisher wants to store.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $objects = [];
|
||||
|
||||
/**
|
||||
* Add a variable to the finisher container.
|
||||
*
|
||||
* @param string $finisherIdentifier
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function add(string $finisherIdentifier, string $key, $value)
|
||||
{
|
||||
$this->addOrUpdate($finisherIdentifier, $key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a variable to the Variable Container.
|
||||
* In case the value is already inside, it is silently overridden.
|
||||
*
|
||||
* @param string $finisherIdentifier
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function addOrUpdate(string $finisherIdentifier, string $key, $value)
|
||||
{
|
||||
if (!array_key_exists($finisherIdentifier, $this->objects)) {
|
||||
$this->objects[$finisherIdentifier] = [];
|
||||
}
|
||||
$this->objects[$finisherIdentifier] = ArrayUtility::setValueByPath(
|
||||
$this->objects[$finisherIdentifier],
|
||||
$key,
|
||||
$value,
|
||||
'.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a variable which is stored
|
||||
*
|
||||
* @param string $finisherIdentifier
|
||||
* @param string $key
|
||||
* @param mixed $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $finisherIdentifier, string $key, $default = null)
|
||||
{
|
||||
if ($this->exists($finisherIdentifier, $key)) {
|
||||
return ArrayUtility::getValueByPath($this->objects[$finisherIdentifier], $key, '.');
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether there is a variable stored for the given key
|
||||
*
|
||||
* @param string $finisherIdentifier
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function exists($finisherIdentifier, $key): bool
|
||||
{
|
||||
try {
|
||||
ArrayUtility::getValueByPath($this->objects[$finisherIdentifier], $key, '.');
|
||||
} catch (MissingArrayPathException $e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a value from the variable container
|
||||
*
|
||||
* @param string $finisherIdentifier
|
||||
* @param string $key
|
||||
*/
|
||||
public function remove(string $finisherIdentifier, string $key)
|
||||
{
|
||||
if ($this->exists($finisherIdentifier, $key)) {
|
||||
$this->objects[$finisherIdentifier] = ArrayUtility::removeByPath(
|
||||
$this->objects[$finisherIdentifier],
|
||||
$key,
|
||||
'.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up for serializing.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function __sleep()
|
||||
{
|
||||
return ['objects'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an offset exists
|
||||
*
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetexists.php
|
||||
* @param mixed $offset An offset to check for.
|
||||
* @return bool TRUE on success or FALSE on failure.
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return isset($this->objects[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to retrieve
|
||||
*
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetget.php
|
||||
* @param mixed $offset The offset to retrieve.
|
||||
* @return mixed Can return all value types.
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->objects[$offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to set
|
||||
*
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetset.php
|
||||
* @param mixed $offset The offset to assign the value to.
|
||||
* @param mixed $value The value to set.
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
$this->objects[$offset] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to unset
|
||||
*
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetunset.php
|
||||
* @param mixed $offset The offset to unset.
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
unset($this->objects[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Traversable
|
||||
*/
|
||||
public function getIterator(): \Traversable
|
||||
{
|
||||
foreach ($this->objects as $offset => $value) {
|
||||
yield $offset => $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count elements of an object
|
||||
*
|
||||
* @link http://php.net/manual/en/countable.count.php
|
||||
* @return int The custom count as an integer.
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return count($this->objects);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Finishers;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Messaging\AbstractMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Extbase\Error\Error;
|
||||
use TYPO3\CMS\Extbase\Error\Message;
|
||||
use TYPO3\CMS\Extbase\Error\Notice;
|
||||
use TYPO3\CMS\Extbase\Error\Warning;
|
||||
use TYPO3\CMS\Form\Domain\Finishers\Exception\FinisherException;
|
||||
|
||||
/**
|
||||
* A simple finisher that adds a message to the FlashMessageContainer
|
||||
*
|
||||
* Usage:
|
||||
* //...
|
||||
* $flashMessageFinisher = $this->objectManager->get(FlashMessageFinisher::class);
|
||||
* $flashMessageFinisher->setOptions(
|
||||
* [
|
||||
* 'messageBody' => 'Some message body',
|
||||
* 'messageTitle' => 'Some message title',
|
||||
* 'messageArguments' => ['foo' => 'bar'],
|
||||
* 'severity' => \TYPO3\CMS\Core\Messaging\AbstractMessage::ERROR
|
||||
* ]
|
||||
* );
|
||||
* $formDefinition->addFinisher($flashMessageFinisher);
|
||||
* // ...
|
||||
*
|
||||
* Scope: frontend
|
||||
*/
|
||||
class FlashMessageFinisher extends AbstractFinisher
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultOptions = [
|
||||
'messageBody' => null,
|
||||
'messageTitle' => '',
|
||||
'messageArguments' => [],
|
||||
'messageCode' => null,
|
||||
'severity' => AbstractMessage::OK,
|
||||
];
|
||||
|
||||
/**
|
||||
* Executes this finisher
|
||||
* @see AbstractFinisher::execute()
|
||||
*
|
||||
* @throws FinisherException
|
||||
*/
|
||||
protected function executeInternal()
|
||||
{
|
||||
$messageBody = $this->parseOption('messageBody');
|
||||
if (!is_string($messageBody)) {
|
||||
throw new FinisherException(sprintf('The message body must be of type string, "%s" given.', gettype($messageBody)), 1335980069);
|
||||
}
|
||||
$messageTitle = $this->parseOption('messageTitle');
|
||||
$messageArguments = $this->parseOption('messageArguments');
|
||||
$messageCode = $this->parseOption('messageCode');
|
||||
$severity = $this->parseOption('severity');
|
||||
switch ($severity) {
|
||||
case AbstractMessage::NOTICE:
|
||||
$message = $this->objectManager->get(Notice::class, $messageBody, $messageCode, $messageArguments, $messageTitle);
|
||||
break;
|
||||
case AbstractMessage::WARNING:
|
||||
$message = $this->objectManager->get(Warning::class, $messageBody, $messageCode, $messageArguments, $messageTitle);
|
||||
break;
|
||||
case AbstractMessage::ERROR:
|
||||
$message = $this->objectManager->get(Error::class, $messageBody, $messageCode, $messageArguments, $messageTitle);
|
||||
break;
|
||||
default:
|
||||
$message = $this->objectManager->get(Message::class, $messageBody, $messageCode, $messageArguments, $messageTitle);
|
||||
break;
|
||||
}
|
||||
|
||||
$flashMessage = $this->objectManager->get(
|
||||
FlashMessage::class,
|
||||
$message->render(),
|
||||
$message->getTitle(),
|
||||
$severity,
|
||||
true
|
||||
);
|
||||
|
||||
$this->finisherContext->getControllerContext()->getFlashMessageQueue()->addMessage($flashMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Finishers;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
|
||||
use TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException;
|
||||
use TYPO3\CMS\Extbase\Mvc\Web\Request;
|
||||
use TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder;
|
||||
|
||||
/**
|
||||
* This finisher redirects to another Controller.
|
||||
*
|
||||
* Scope: frontend
|
||||
*/
|
||||
class RedirectFinisher extends AbstractFinisher
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultOptions = [
|
||||
'pageUid' => 1,
|
||||
'additionalParameters' => '',
|
||||
'delay' => 0,
|
||||
'statusCode' => 303,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extbase\Mvc\Web\Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extbase\Mvc\Web\Response
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder
|
||||
*/
|
||||
protected $uriBuilder;
|
||||
|
||||
/**
|
||||
* Executes this finisher
|
||||
* @see AbstractFinisher::execute()
|
||||
*/
|
||||
protected function executeInternal()
|
||||
{
|
||||
$formRuntime = $this->finisherContext->getFormRuntime();
|
||||
$this->request = $formRuntime->getRequest();
|
||||
$this->response = $formRuntime->getResponse();
|
||||
$this->uriBuilder = $this->objectManager->get(UriBuilder::class);
|
||||
$this->uriBuilder->setRequest($this->request);
|
||||
|
||||
$pageUid = (int)str_replace('pages_', '', $this->parseOption('pageUid'));
|
||||
$additionalParameters = $this->parseOption('additionalParameters');
|
||||
$additionalParameters = '&' . ltrim($additionalParameters, '&');
|
||||
$delay = (int)$this->parseOption('delay');
|
||||
$statusCode = (int)$this->parseOption('statusCode');
|
||||
|
||||
$this->finisherContext->cancel();
|
||||
$this->redirect($pageUid, $additionalParameters, $delay, $statusCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects the request to another page.
|
||||
*
|
||||
* Redirect will be sent to the client which then performs another request to the new URI.
|
||||
*
|
||||
* NOTE: This method only supports web requests and will thrown an exception
|
||||
* if used with other request types.
|
||||
*
|
||||
* @param int $pageUid Target page uid. If NULL, the current page uid is used
|
||||
* @param string $additionalParameters
|
||||
* @param int $delay (optional) The delay in seconds. Default is no delay.
|
||||
* @param int $statusCode (optional) The HTTP status code for the redirect. Default is "303 See Other
|
||||
* @throws UnsupportedRequestTypeException If the request is not a web request
|
||||
* @see forward()
|
||||
*/
|
||||
protected function redirect(int $pageUid = 1, string $additionalParameters = '', int $delay = 0, int $statusCode = 303)
|
||||
{
|
||||
if (!$this->request instanceof Request) {
|
||||
throw new UnsupportedRequestTypeException('redirect() only supports web requests.', 1471776457);
|
||||
}
|
||||
|
||||
$typolinkConfiguration = [
|
||||
'parameter' => $pageUid,
|
||||
'additionalParams' => $additionalParameters,
|
||||
];
|
||||
$redirectUri = $this->getTypoScriptFrontendController()->cObj->typoLink_URL($typolinkConfiguration);
|
||||
$this->redirectToUri($redirectUri, $delay, $statusCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects the web request to another uri.
|
||||
*
|
||||
* NOTE: This method only supports web requests and will thrown an exception if used with other request types.
|
||||
*
|
||||
* @param string $uri A string representation of a URI
|
||||
* @param int $delay (optional) The delay in seconds. Default is no delay.
|
||||
* @param int $statusCode (optional) The HTTP status code for the redirect. Default is "303 See Other
|
||||
* @throws UnsupportedRequestTypeException If the request is not a web request
|
||||
* @throws StopActionException
|
||||
*/
|
||||
protected function redirectToUri(string $uri, int $delay = 0, int $statusCode = 303)
|
||||
{
|
||||
if (!$this->request instanceof Request) {
|
||||
throw new UnsupportedRequestTypeException('redirect() only supports web requests.', 1471776458);
|
||||
}
|
||||
|
||||
$uri = $this->addBaseUriIfNecessary($uri);
|
||||
$escapedUri = htmlentities($uri, ENT_QUOTES, 'utf-8');
|
||||
|
||||
$this->response->setContent('<html><head><meta http-equiv="refresh" content="' . (int)$delay . ';url=' . $escapedUri . '"/></head></html>');
|
||||
if ($this->response instanceof \TYPO3\CMS\Extbase\Mvc\Web\Response) {
|
||||
$this->response->setStatus($statusCode);
|
||||
$this->response->setHeader('Location', (string)$uri);
|
||||
}
|
||||
throw new StopActionException('redirectToUri', 1477070964);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the base uri if not already in place.
|
||||
*
|
||||
* @param string $uri The URI
|
||||
* @return string
|
||||
*/
|
||||
protected function addBaseUriIfNecessary(string $uri): string
|
||||
{
|
||||
return GeneralUtility::locationHeaderUrl((string)$uri);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Finishers;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
|
||||
use TYPO3\CMS\Form\Domain\Finishers\Exception\FinisherException;
|
||||
use TYPO3\CMS\Form\Domain\Model\FormElements\FormElementInterface;
|
||||
|
||||
/**
|
||||
* This finisher saves the data from a submitted form into
|
||||
* a database table.
|
||||
*
|
||||
* Configuration
|
||||
* =============
|
||||
*
|
||||
* options.table (mandatory)
|
||||
* -------------
|
||||
* Save or update values into this table
|
||||
*
|
||||
* options.mode (default: insert)
|
||||
* ------------
|
||||
* Possible values are 'insert' or 'update'.
|
||||
*
|
||||
* insert: will create a new database row with the values from the
|
||||
* submitted form and/or some predefined values.
|
||||
* @see options.elements and options.databaseFieldMappings
|
||||
* update: will update a given database row with the values from the
|
||||
* submitted form and/or some predefined values.
|
||||
* 'options.whereClause' is then required.
|
||||
*
|
||||
* options.whereClause
|
||||
* -------------------
|
||||
* This where clause will be used for an database update action
|
||||
*
|
||||
* options.elements
|
||||
* ----------------
|
||||
* Use this to map form element values to existing database columns.
|
||||
* Each key within options.elements has to match with a
|
||||
* form element identifier within your form definition.
|
||||
* The value for each key within options.elements is an array with
|
||||
* additional information.
|
||||
*
|
||||
* options.elements.<elementIdentifier>.mapOnDatabaseColumn (mandatory)
|
||||
* --------------------------------------------------------
|
||||
* The value from the submitted form element with the identifier
|
||||
* '<elementIdentifier>' will be written into this database column
|
||||
*
|
||||
* options.elements.<elementIdentifier>.skipIfValueIsEmpty (default: false)
|
||||
* ------------------------------------------------------
|
||||
* Set this to true if the database column should not be written
|
||||
* if the value from the submitted form element with the identifier
|
||||
* '<elementIdentifier>' is empty (think about password fields etc.)
|
||||
*
|
||||
* options.elements.<elementIdentifier>.saveFileIdentifierInsteadOfUid (default: false)
|
||||
* -------------------------------------------------------------------
|
||||
* This setting only rules for form elements which creates a FAL object
|
||||
* like FileUpload or ImageUpload.
|
||||
* By default, the uid of the FAL object will be written into
|
||||
* the database column. Set this to true if you want to store the
|
||||
* FAL identifier (1:/user_uploads/some_uploaded_pic.jpg) instead.
|
||||
*
|
||||
* options.databaseColumnMappings
|
||||
* ------------------------------
|
||||
* Use this to map database columns to static values (which can be
|
||||
* made dynamic through typoscript overrides of course).
|
||||
* Each key within options.databaseColumnMappings has to match with a
|
||||
* existing database column.
|
||||
* The value for each key within options.databaseColumnMappings is an
|
||||
* array with additional information.
|
||||
*
|
||||
* This mapping is done *before* the options.elements mapping.
|
||||
* This means if you map a database column to a value through
|
||||
* options.databaseColumnMappings and map a submitted form element
|
||||
* value to the same database column, the submitted form element value
|
||||
* will override the value you set within options.databaseColumnMappings.
|
||||
*
|
||||
* options.databaseColumnMappings.<databaseColumnName>.value
|
||||
* ---------------------------------------------------------
|
||||
* The value which will be written to the database column.
|
||||
* You can use the FormRuntime accessor feature to access every
|
||||
* getable property from the TYPO3\CMS\Form\Domain\Runtime\FormRuntime
|
||||
* Read the description within
|
||||
* TYPO3\CMS\Form\Domain\Finishers\AbstractFinisher::parseOption
|
||||
* In short: use something like {<elementIdentifier>} to get the value
|
||||
* from the submitted form element with the identifier
|
||||
* <elementIdentifier>
|
||||
*
|
||||
* Don't be confused. If you use the FormRuntime accessor feature within
|
||||
* options.databaseColumnMappings, the functionality is nearly equal
|
||||
* to the the options.elements configuration.
|
||||
*
|
||||
* options.databaseColumnMappings.<databaseColumnName>.skipIfValueIsEmpty (default: false)
|
||||
* ---------------------------------------------------------------------
|
||||
* Set this to true if the database column should not be written
|
||||
* if the value from
|
||||
* options.databaseColumnMappings.<databaseColumnName>.value is empty.
|
||||
*
|
||||
* Example
|
||||
* =======
|
||||
*
|
||||
* finishers:
|
||||
* -
|
||||
* identifier: SaveToDatabase
|
||||
* options:
|
||||
* table: 'fe_users'
|
||||
* mode: update
|
||||
* whereClause:
|
||||
* uid: 1
|
||||
* databaseColumnMappings:
|
||||
* pid:
|
||||
* value: 1
|
||||
* elements:
|
||||
* text-1:
|
||||
* mapOnDatabaseColumn: 'first_name'
|
||||
* text-2:
|
||||
* mapOnDatabaseColumn: 'last_name'
|
||||
* text-3:
|
||||
* mapOnDatabaseColumn: 'username'
|
||||
* advancedpassword-1:
|
||||
* mapOnDatabaseColumn: 'password'
|
||||
* skipIfValueIsEmpty: true
|
||||
*
|
||||
* Multiple database operations
|
||||
* ============================
|
||||
*
|
||||
* You can write options as an array to perform multiple database operations.
|
||||
*
|
||||
* finishers:
|
||||
* -
|
||||
* identifier: SaveToDatabase
|
||||
* options:
|
||||
* 1:
|
||||
* table: 'my_table'
|
||||
* mode: insert
|
||||
* databaseColumnMappings:
|
||||
* some_column:
|
||||
* value: 'cool'
|
||||
* 2:
|
||||
* table: 'my_other_table'
|
||||
* mode: update
|
||||
* whereClause:
|
||||
* pid: 1
|
||||
* databaseColumnMappings:
|
||||
* some_other_column:
|
||||
* value: '{SaveToDatabase.insertedUids.1}'
|
||||
*
|
||||
* This would perform 2 database operations.
|
||||
* One insert and one update.
|
||||
* You can access the inserted uids with '{SaveToDatabase.insertedUids.<theArrayKeyNumberWithinOptions>}'
|
||||
* If you perform an insert operation, the value of the inserted database row will be stored
|
||||
* within the FinisherVariableProvider.
|
||||
* <theArrayKeyNumberWithinOptions> references to the numeric key within options
|
||||
* within which the insert operation is executed.
|
||||
*
|
||||
* Scope: frontend
|
||||
*/
|
||||
class SaveToDatabaseFinisher extends AbstractFinisher
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultOptions = [
|
||||
'table' => null,
|
||||
'mode' => 'insert',
|
||||
'whereClause' => [],
|
||||
'elements' => [],
|
||||
'databaseColumnMappings' => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Core\Database\Connection
|
||||
*/
|
||||
protected $databaseConnection;
|
||||
|
||||
/**
|
||||
* Executes this finisher
|
||||
* @see AbstractFinisher::execute()
|
||||
*
|
||||
* @throws FinisherException
|
||||
*/
|
||||
protected function executeInternal()
|
||||
{
|
||||
if (isset($this->options['table'])) {
|
||||
$options[] = $this->options;
|
||||
} else {
|
||||
$options = $this->options;
|
||||
}
|
||||
|
||||
foreach ($options as $optionKey => $option) {
|
||||
$this->options = $option;
|
||||
$this->process($optionKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare data for saving to database
|
||||
*
|
||||
* @param array $elementsConfiguration
|
||||
* @param array $databaseData
|
||||
* @return mixed
|
||||
*/
|
||||
protected function prepareData(array $elementsConfiguration, array $databaseData)
|
||||
{
|
||||
foreach ($this->getFormValues() as $elementIdentifier => $elementValue) {
|
||||
if (
|
||||
($elementValue === null || $elementValue === '')
|
||||
&& isset($elementsConfiguration[$elementIdentifier])
|
||||
&& isset($elementsConfiguration[$elementIdentifier]['skipIfValueIsEmpty'])
|
||||
&& $elementsConfiguration[$elementIdentifier]['skipIfValueIsEmpty'] === true
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$element = $this->getElementByIdentifier($elementIdentifier);
|
||||
if (
|
||||
!$element instanceof FormElementInterface
|
||||
|| !isset($elementsConfiguration[$elementIdentifier])
|
||||
|| !isset($elementsConfiguration[$elementIdentifier]['mapOnDatabaseColumn'])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($elementValue instanceof FileReference) {
|
||||
if (isset($elementsConfiguration[$elementIdentifier]['saveFileIdentifierInsteadOfUid'])) {
|
||||
$saveFileIdentifierInsteadOfUid = (bool)$elementsConfiguration[$elementIdentifier]['saveFileIdentifierInsteadOfUid'];
|
||||
} else {
|
||||
$saveFileIdentifierInsteadOfUid = false;
|
||||
}
|
||||
|
||||
if ($saveFileIdentifierInsteadOfUid) {
|
||||
$elementValue = $elementValue->getOriginalResource()->getCombinedIdentifier();
|
||||
} else {
|
||||
$elementValue = $elementValue->getOriginalResource()->getProperty('uid_local');
|
||||
}
|
||||
} elseif (is_array($elementValue)) {
|
||||
$elementValue = implode(',', $elementValue);
|
||||
} elseif ($elementValue instanceof \DateTimeInterface) {
|
||||
$format = $elementsConfiguration[$elementIdentifier]['dateFormat'] ?? 'U';
|
||||
$elementValue = $elementValue->format($format);
|
||||
}
|
||||
|
||||
$databaseData[$elementsConfiguration[$elementIdentifier]['mapOnDatabaseColumn']] = $elementValue;
|
||||
}
|
||||
return $databaseData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the current database operation
|
||||
*
|
||||
* @param int $iterationCount
|
||||
*/
|
||||
protected function process(int $iterationCount)
|
||||
{
|
||||
$this->throwExceptionOnInconsistentConfiguration();
|
||||
|
||||
$table = $this->parseOption('table');
|
||||
$elementsConfiguration = $this->parseOption('elements');
|
||||
$databaseColumnMappingsConfiguration = $this->parseOption('databaseColumnMappings');
|
||||
|
||||
$this->databaseConnection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($table);
|
||||
|
||||
$databaseData = [];
|
||||
foreach ($databaseColumnMappingsConfiguration as $databaseColumnName => $databaseColumnConfiguration) {
|
||||
$value = $this->parseOption('databaseColumnMappings.' . $databaseColumnName . '.value');
|
||||
if (
|
||||
empty($value)
|
||||
&& $databaseColumnConfiguration['skipIfValueIsEmpty'] === true
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$databaseData[$databaseColumnName] = $value;
|
||||
}
|
||||
|
||||
$databaseData = $this->prepareData($elementsConfiguration, $databaseData);
|
||||
|
||||
$this->saveToDatabase($databaseData, $table, $iterationCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save or insert the values from
|
||||
* $databaseData into the table $table
|
||||
*
|
||||
* @param array $databaseData
|
||||
* @param string $table
|
||||
* @param int $iterationCount
|
||||
*/
|
||||
protected function saveToDatabase(array $databaseData, string $table, int $iterationCount)
|
||||
{
|
||||
if (!empty($databaseData)) {
|
||||
if ($this->options['mode'] === 'update') {
|
||||
$whereClause = $this->options['whereClause'];
|
||||
foreach ($whereClause as $columnName => $columnValue) {
|
||||
$whereClause[$columnName] = $this->parseOption('whereClause.' . $columnName);
|
||||
}
|
||||
$this->databaseConnection->update(
|
||||
$table,
|
||||
$databaseData,
|
||||
$whereClause
|
||||
);
|
||||
} else {
|
||||
$this->databaseConnection->insert($table, $databaseData);
|
||||
$insertedUid = (int)$this->databaseConnection->lastInsertId($table);
|
||||
$this->finisherContext->getFinisherVariableProvider()->add(
|
||||
$this->shortFinisherIdentifier,
|
||||
'insertedUids.' . $iterationCount,
|
||||
$insertedUid
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception if some inconsistent configuration
|
||||
* are detected.
|
||||
*
|
||||
* @throws FinisherException
|
||||
*/
|
||||
protected function throwExceptionOnInconsistentConfiguration()
|
||||
{
|
||||
if (
|
||||
$this->options['mode'] === 'update'
|
||||
&& empty($this->options['whereClause'])
|
||||
) {
|
||||
throw new FinisherException(
|
||||
'An empty option "whereClause" is not allowed in update mode.',
|
||||
1480469086
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the values of the submitted form
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getFormValues(): array
|
||||
{
|
||||
return $this->finisherContext->getFormValues();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a form element object for a given identifier.
|
||||
*
|
||||
* @param string $elementIdentifier
|
||||
* @return FormElementInterface|null
|
||||
*/
|
||||
protected function getElementByIdentifier(string $elementIdentifier)
|
||||
{
|
||||
return $this
|
||||
->finisherContext
|
||||
->getFormRuntime()
|
||||
->getFormDefinition()
|
||||
->getElementByIdentifier($elementIdentifier);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Exception as DomainException;
|
||||
|
||||
/**
|
||||
* A generic Form model Exception
|
||||
*/
|
||||
class Exception extends DomainException
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\Exception;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown if two Form Elements with the same Identifier are added
|
||||
* to a form.
|
||||
*/
|
||||
class DuplicateFormElementException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\Exception;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown if a Finisher Preset was not found,
|
||||
* or if the implementationClassName was not set.
|
||||
*/
|
||||
class FinisherPresetNotFoundException extends Exception
|
||||
{
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\Exception;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown if the form definition would get an inconsistent state, like
|
||||
* adding a page to two different forms
|
||||
*/
|
||||
class FormDefinitionConsistencyException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\Exception;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception;
|
||||
|
||||
/**
|
||||
* This exception is thrown if a Validator Preset was not found,
|
||||
* or if the implementationClassName was not set.
|
||||
*/
|
||||
class ValidatorPresetNotFoundException extends Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,725 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Extbase\Mvc\Web\Request;
|
||||
use TYPO3\CMS\Extbase\Mvc\Web\Response;
|
||||
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
|
||||
use TYPO3\CMS\Form\Domain\Exception\IdentifierNotValidException;
|
||||
use TYPO3\CMS\Form\Domain\Exception\TypeDefinitionNotFoundException;
|
||||
use TYPO3\CMS\Form\Domain\Finishers\FinisherInterface;
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception\DuplicateFormElementException;
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception\FinisherPresetNotFoundException;
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception\FormDefinitionConsistencyException;
|
||||
use TYPO3\CMS\Form\Domain\Model\FormElements\FormElementInterface;
|
||||
use TYPO3\CMS\Form\Domain\Model\FormElements\Page;
|
||||
use TYPO3\CMS\Form\Domain\Model\Renderable\AbstractCompositeRenderable;
|
||||
use TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface;
|
||||
use TYPO3\CMS\Form\Domain\Model\Renderable\VariableRenderableInterface;
|
||||
use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
|
||||
use TYPO3\CMS\Form\Exception as FormException;
|
||||
use TYPO3\CMS\Form\Mvc\ProcessingRule;
|
||||
|
||||
/**
|
||||
* This class encapsulates a complete *Form Definition*, with all of its pages,
|
||||
* form elements, validation rules which apply and finishers which should be
|
||||
* executed when the form is completely filled in.
|
||||
*
|
||||
* It is *not modified* when the form executes.
|
||||
*
|
||||
* The Anatomy Of A Form
|
||||
* =====================
|
||||
*
|
||||
* A FormDefinition consists of multiple *Page* ({@link Page}) objects. When a
|
||||
* form is displayed to the user, only one *Page* is visible at any given time,
|
||||
* and there is a navigation to go back and forth between the pages.
|
||||
*
|
||||
* A *Page* consists of multiple *FormElements* ({@link FormElementInterface}, {@link AbstractFormElement}),
|
||||
* which represent the input fields, textareas, checkboxes shown inside the page.
|
||||
*
|
||||
* *FormDefinition*, *Page* and *FormElement* have *identifier* properties, which
|
||||
* must be unique for each given type (i.e. it is allowed that the FormDefinition and
|
||||
* a FormElement have the *same* identifier, but two FormElements are not allowed to
|
||||
* have the same identifier.
|
||||
*
|
||||
* Simple Example
|
||||
* --------------
|
||||
*
|
||||
* Generally, you can create a FormDefinition manually by just calling the API
|
||||
* methods on it, or you use a *Form Definition Factory* to build the form from
|
||||
* another representation format such as YAML.
|
||||
*
|
||||
* /---code php
|
||||
* $formDefinition = $this->objectManager->get(FormDefinition::class, 'myForm');
|
||||
*
|
||||
* $page1 = $this->objectManager->get(Page::class, 'page1');
|
||||
* $formDefinition->addPage($page);
|
||||
*
|
||||
* $element1 = $this->objectManager->get(GenericFormElement::class, 'title', 'Textfield'); # the second argument is the type of the form element
|
||||
* $page1->addElement($element1);
|
||||
* \---
|
||||
*
|
||||
* Creating a Form, Using Abstract Form Element Types
|
||||
* =====================================================
|
||||
*
|
||||
* While you can use the {@link FormDefinition::addPage} or {@link Page::addElement}
|
||||
* methods and create the Page and FormElement objects manually, it is often better
|
||||
* to use the corresponding create* methods ({@link FormDefinition::createPage}
|
||||
* and {@link Page::createElement}), as you pass them an abstract *Form Element Type*
|
||||
* such as *Text* or *Page*, and the system **automatically
|
||||
* resolves the implementation class name and sets default values**.
|
||||
*
|
||||
* So the simple example from above should be rewritten as follows:
|
||||
*
|
||||
* /---code php
|
||||
* $prototypeConfiguration = []; // We'll talk about this later
|
||||
*
|
||||
* $formDefinition = $this->objectManager->get(FormDefinition::class, 'myForm', $prototypeConfiguration);
|
||||
* $page1 = $formDefinition->createPage('page1');
|
||||
* $element1 = $page1->addElement('title', 'Textfield');
|
||||
* \---
|
||||
*
|
||||
* Now, you might wonder how the system knows that the element *Textfield*
|
||||
* is implemented using a GenericFormElement: **This is configured in the $prototypeConfiguration**.
|
||||
*
|
||||
* To make the example from above actually work, we need to add some sensible
|
||||
* values to *$prototypeConfiguration*:
|
||||
*
|
||||
* <pre>
|
||||
* $prototypeConfiguration = [
|
||||
* 'formElementsDefinition' => [
|
||||
* 'Page' => [
|
||||
* 'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\Page'
|
||||
* ],
|
||||
* 'Textfield' => [
|
||||
* 'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement'
|
||||
* ]
|
||||
* ]
|
||||
* ]
|
||||
* </pre>
|
||||
*
|
||||
* For each abstract *Form Element Type* we add some configuration; in the above
|
||||
* case only the *implementation class name*. Still, it is possible to set defaults
|
||||
* for *all* configuration options of such an element, as the following example
|
||||
* shows:
|
||||
*
|
||||
* <pre>
|
||||
* $prototypeConfiguration = [
|
||||
* 'formElementsDefinition' => [
|
||||
* 'Page' => [
|
||||
* 'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\Page',
|
||||
* 'label' => 'this is the label of the page if nothing is specified'
|
||||
* ],
|
||||
* 'Textfield' => [
|
||||
* 'implementationClassName' => 'TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement',
|
||||
* 'label' = >'Default Label',
|
||||
* 'defaultValue' => 'Default form element value',
|
||||
* 'properties' => [
|
||||
* 'placeholder' => 'Text which is shown if element is empty'
|
||||
* ]
|
||||
* ]
|
||||
* ]
|
||||
* ]
|
||||
* </pre>
|
||||
*
|
||||
* Using Preconfigured $prototypeConfiguration
|
||||
* ---------------------------------
|
||||
*
|
||||
* Often, it is not really useful to manually create the $prototypeConfiguration array.
|
||||
*
|
||||
* Most of it comes pre-configured inside the YAML settings of the extensions,
|
||||
* and the {@link \TYPO3\CMS\Form\Domain\Configuration\ConfigurationService} contains helper methods
|
||||
* which return the ready-to-use *$prototypeConfiguration*.
|
||||
*
|
||||
* Property Mapping and Validation Rules
|
||||
* =====================================
|
||||
*
|
||||
* Besides Pages and FormElements, the FormDefinition can contain information
|
||||
* about the *format of the data* which is inputted into the form. This generally means:
|
||||
*
|
||||
* - expected Data Types
|
||||
* - Property Mapping Configuration to be used
|
||||
* - Validation Rules which should apply
|
||||
*
|
||||
* Background Info
|
||||
* ---------------
|
||||
* You might wonder why Data Types and Validation Rules are *not attached
|
||||
* to each FormElement itself*.
|
||||
*
|
||||
* If the form should create a *hierarchical output structure* such as a multi-
|
||||
* dimensional array or a PHP object, your expected data structure might look as follows:
|
||||
* <pre>
|
||||
* - person
|
||||
* -- firstName
|
||||
* -- lastName
|
||||
* -- address
|
||||
* --- street
|
||||
* --- city
|
||||
* </pre>
|
||||
*
|
||||
* Now, let's imagine you want to edit *person.address.street* and *person.address.city*,
|
||||
* but want to validate that the *combination* of *street* and *city* is valid
|
||||
* according to some address database.
|
||||
*
|
||||
* In this case, the form elements would be configured to fill *street* and *city*,
|
||||
* but the *validator* needs to be attached to the *compound object* *address*,
|
||||
* as both parts need to be validated together.
|
||||
*
|
||||
* Connecting FormElements to the output data structure
|
||||
* ====================================================
|
||||
*
|
||||
* The *identifier* of the *FormElement* is most important, as it determines
|
||||
* where in the output structure the value which is entered by the user is placed,
|
||||
* and thus also determines which validation rules need to apply.
|
||||
*
|
||||
* Using the above example, if you want to create a FormElement for the *street*,
|
||||
* you should use the identifier *person.address.street*.
|
||||
*
|
||||
* Rendering a FormDefinition
|
||||
* ==========================
|
||||
*
|
||||
* In order to trigger *rendering* on a FormDefinition,
|
||||
* the current {@link \TYPO3\CMS\Extbase\Mvc\Web\Request} needs to be bound to the FormDefinition,
|
||||
* resulting in a {@link \TYPO3\CMS\Form\Domain\Runtime\FormRuntime} object which contains the *Runtime State* of the form
|
||||
* (such as the currently inserted values).
|
||||
*
|
||||
* /---code php
|
||||
* # $currentRequest and $currentResponse need to be available, f.e. inside a controller you would
|
||||
* # use $this->request and $this->response; inside a ViewHelper you would use $this->controllerContext->getRequest()
|
||||
* # and $this->controllerContext->getResponse()
|
||||
* $form = $formDefinition->bind($currentRequest, $currentResponse);
|
||||
*
|
||||
* # now, you can use the $form object to get information about the currently
|
||||
* # entered values into the form, etc.
|
||||
* \---
|
||||
*
|
||||
* Refer to the {@link \TYPO3\CMS\Form\Domain\Runtime\FormRuntime} API doc for further information.
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
*/
|
||||
class FormDefinition extends AbstractCompositeRenderable implements VariableRenderableInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
|
||||
*/
|
||||
protected $objectManager;
|
||||
|
||||
/**
|
||||
* The finishers for this form
|
||||
*
|
||||
* @var \TYPO3\CMS\Form\Domain\Finishers\FinisherInterface[]
|
||||
*/
|
||||
protected $finishers = [];
|
||||
|
||||
/**
|
||||
* Property Mapping Rules, indexed by element identifier
|
||||
*
|
||||
* @var \TYPO3\CMS\Form\Mvc\ProcessingRule[]
|
||||
*/
|
||||
protected $processingRules = [];
|
||||
|
||||
/**
|
||||
* Contains all elements of the form, indexed by identifier.
|
||||
* Is used as internal cache as we need this really often.
|
||||
*
|
||||
* @var \TYPO3\CMS\Form\Domain\Model\FormElements\FormElementInterface[]
|
||||
*/
|
||||
protected $elementsByIdentifier = [];
|
||||
|
||||
/**
|
||||
* Form element default values in the format ['elementIdentifier' => 'default value']
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $elementDefaultValues = [];
|
||||
|
||||
/**
|
||||
* Renderer class name to be used.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $rendererClassName = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $typeDefinitions;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $validatorsDefinition;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $finishersDefinition;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $conditionContextDefinition;
|
||||
|
||||
/**
|
||||
* The persistence identifier of the form
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $persistenceIdentifier;
|
||||
|
||||
/**
|
||||
* @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
|
||||
* @internal
|
||||
*/
|
||||
public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
|
||||
{
|
||||
$this->objectManager = $objectManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor. Creates a new FormDefinition with the given identifier.
|
||||
*
|
||||
* @param string $identifier The Form Definition's identifier, must be a non-empty string.
|
||||
* @param array $prototypeConfiguration overrides form defaults of this definition
|
||||
* @param string $type element type of this form
|
||||
* @param string $persistenceIdentifier the persistence identifier of the form
|
||||
* @throws IdentifierNotValidException if the identifier was not valid
|
||||
*/
|
||||
public function __construct(
|
||||
string $identifier,
|
||||
array $prototypeConfiguration = [],
|
||||
string $type = 'Form',
|
||||
string $persistenceIdentifier = null
|
||||
) {
|
||||
$this->typeDefinitions = $prototypeConfiguration['formElementsDefinition'] ?? [];
|
||||
$this->validatorsDefinition = $prototypeConfiguration['validatorsDefinition'] ?? [];
|
||||
$this->finishersDefinition = $prototypeConfiguration['finishersDefinition'] ?? [];
|
||||
$this->conditionContextDefinition = $prototypeConfiguration['conditionContextDefinition'] ?? [];
|
||||
|
||||
if (!is_string($identifier) || strlen($identifier) === 0) {
|
||||
throw new IdentifierNotValidException('The given identifier was not a string or the string was empty.', 1477082503);
|
||||
}
|
||||
|
||||
$this->identifier = $identifier;
|
||||
$this->type = $type;
|
||||
$this->persistenceIdentifier = $persistenceIdentifier;
|
||||
|
||||
if ($prototypeConfiguration !== []) {
|
||||
$this->initializeFromFormDefaults();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the form defaults of the current type
|
||||
*
|
||||
* @throws TypeDefinitionNotFoundException
|
||||
* @internal
|
||||
*/
|
||||
protected function initializeFromFormDefaults()
|
||||
{
|
||||
if (!isset($this->typeDefinitions[$this->type])) {
|
||||
throw new TypeDefinitionNotFoundException(sprintf('Type "%s" not found. Probably some configuration is missing.', $this->type), 1474905835);
|
||||
}
|
||||
$typeDefinition = $this->typeDefinitions[$this->type];
|
||||
$this->setOptions($typeDefinition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set multiple properties of this object at once.
|
||||
* Every property which has a corresponding set* method can be set using
|
||||
* the passed $options array.
|
||||
*
|
||||
* @param array $options
|
||||
* @param bool $resetFinishers
|
||||
* @internal
|
||||
*/
|
||||
public function setOptions(array $options, bool $resetFinishers = false)
|
||||
{
|
||||
if (isset($options['rendererClassName'])) {
|
||||
$this->setRendererClassName($options['rendererClassName']);
|
||||
}
|
||||
if (isset($options['label'])) {
|
||||
$this->setLabel($options['label']);
|
||||
}
|
||||
if (isset($options['renderingOptions'])) {
|
||||
foreach ($options['renderingOptions'] as $key => $value) {
|
||||
$this->setRenderingOption($key, $value);
|
||||
}
|
||||
}
|
||||
if (isset($options['finishers'])) {
|
||||
if ($resetFinishers) {
|
||||
$this->finishers = [];
|
||||
}
|
||||
foreach ($options['finishers'] as $finisherConfiguration) {
|
||||
$this->createFinisher($finisherConfiguration['identifier'], $finisherConfiguration['options'] ?? []);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['variants'])) {
|
||||
foreach ($options['variants'] as $variantConfiguration) {
|
||||
$this->createVariant($variantConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
ArrayUtility::assertAllArrayKeysAreValid(
|
||||
$options,
|
||||
['rendererClassName', 'renderingOptions', 'finishers', 'formEditor', 'label', 'variants']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a page with the given $identifier and attach this page to the form.
|
||||
*
|
||||
* - Create Page object based on the given $typeName
|
||||
* - set defaults inside the Page object
|
||||
* - attach Page object to this form
|
||||
* - return the newly created Page object
|
||||
*
|
||||
* @param string $identifier Identifier of the new page
|
||||
* @param string $typeName Type of the new page
|
||||
* @return Page the newly created page
|
||||
* @throws TypeDefinitionNotFoundException
|
||||
*/
|
||||
public function createPage(string $identifier, string $typeName = 'Page'): Page
|
||||
{
|
||||
if (!isset($this->typeDefinitions[$typeName])) {
|
||||
throw new TypeDefinitionNotFoundException(sprintf('Type "%s" not found. Probably some configuration is missing.', $typeName), 1474905953);
|
||||
}
|
||||
|
||||
$typeDefinition = $this->typeDefinitions[$typeName];
|
||||
|
||||
if (!isset($typeDefinition['implementationClassName'])) {
|
||||
throw new TypeDefinitionNotFoundException(sprintf('The "implementationClassName" was not set in type definition "%s".', $typeName), 1477083126);
|
||||
}
|
||||
$implementationClassName = $typeDefinition['implementationClassName'];
|
||||
$page = $this->objectManager->get($implementationClassName, $identifier, $typeName);
|
||||
|
||||
if (isset($typeDefinition['label'])) {
|
||||
$page->setLabel($typeDefinition['label']);
|
||||
}
|
||||
|
||||
if (isset($typeDefinition['renderingOptions'])) {
|
||||
foreach ($typeDefinition['renderingOptions'] as $key => $value) {
|
||||
$page->setRenderingOption($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
ArrayUtility::assertAllArrayKeysAreValid(
|
||||
$typeDefinition,
|
||||
['implementationClassName', 'label', 'renderingOptions', 'formEditor']
|
||||
);
|
||||
|
||||
$this->addPage($page);
|
||||
return $page;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new page at the end of the form.
|
||||
*
|
||||
* Instead of this method, you should often use {@link createPage} instead.
|
||||
*
|
||||
* @param Page $page
|
||||
* @throws FormDefinitionConsistencyException if Page is already added to a FormDefinition
|
||||
* @see createPage
|
||||
*/
|
||||
public function addPage(Page $page)
|
||||
{
|
||||
$this->addRenderable($page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Form's pages
|
||||
*
|
||||
* @return array<Page> The Form's pages in the correct order
|
||||
*/
|
||||
public function getPages(): array
|
||||
{
|
||||
return $this->renderables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a page with the given $index exists
|
||||
*
|
||||
* @param int $index
|
||||
* @return bool TRUE if a page with the given $index exists, otherwise FALSE
|
||||
*/
|
||||
public function hasPageWithIndex(int $index): bool
|
||||
{
|
||||
return isset($this->renderables[$index]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the page with the passed index. The first page has index zero.
|
||||
*
|
||||
* If page at $index does not exist, an exception is thrown. @see hasPageWithIndex()
|
||||
*
|
||||
* @param int $index
|
||||
* @return Page the page, or NULL if none found.
|
||||
* @throws FormException if the specified index does not exist
|
||||
*/
|
||||
public function getPageByIndex(int $index)
|
||||
{
|
||||
if (!$this->hasPageWithIndex($index)) {
|
||||
throw new FormException(sprintf('There is no page with an index of %d', $index), 1329233627);
|
||||
}
|
||||
return $this->renderables[$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified finisher to this form
|
||||
*
|
||||
* @param FinisherInterface $finisher
|
||||
*/
|
||||
public function addFinisher(FinisherInterface $finisher)
|
||||
{
|
||||
$this->finishers[] = $finisher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $finisherIdentifier identifier of the finisher as registered in the current form (for example: "Redirect")
|
||||
* @param array $options options for this finisher in the format ['option1' => 'value1', 'option2' => 'value2', ...]
|
||||
* @return FinisherInterface
|
||||
* @throws FinisherPresetNotFoundException
|
||||
*/
|
||||
public function createFinisher(string $finisherIdentifier, array $options = []): FinisherInterface
|
||||
{
|
||||
if (isset($this->finishersDefinition[$finisherIdentifier]) && is_array($this->finishersDefinition[$finisherIdentifier]) && isset($this->finishersDefinition[$finisherIdentifier]['implementationClassName'])) {
|
||||
$implementationClassName = $this->finishersDefinition[$finisherIdentifier]['implementationClassName'];
|
||||
$defaultOptions = $this->finishersDefinition[$finisherIdentifier]['options'] ?? [];
|
||||
ArrayUtility::mergeRecursiveWithOverrule($defaultOptions, $options);
|
||||
|
||||
$finisher = $this->objectManager->get($implementationClassName, $finisherIdentifier);
|
||||
$finisher->setOptions($defaultOptions);
|
||||
$this->addFinisher($finisher);
|
||||
return $finisher;
|
||||
}
|
||||
throw new FinisherPresetNotFoundException('The finisher preset identified by "' . $finisherIdentifier . '" could not be found, or the implementationClassName was not specified.', 1328709784);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all finishers of this form
|
||||
*
|
||||
* @return \TYPO3\CMS\Form\Domain\Finishers\FinisherInterface[]
|
||||
*/
|
||||
public function getFinishers(): array
|
||||
{
|
||||
return $this->finishers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an element to the ElementsByIdentifier Cache.
|
||||
*
|
||||
* @param RenderableInterface $renderable
|
||||
* @throws DuplicateFormElementException
|
||||
* @internal
|
||||
*/
|
||||
public function registerRenderable(RenderableInterface $renderable)
|
||||
{
|
||||
if ($renderable instanceof FormElementInterface) {
|
||||
if (isset($this->elementsByIdentifier[$renderable->getIdentifier()])) {
|
||||
throw new DuplicateFormElementException(sprintf('A form element with identifier "%s" is already part of the form.', $renderable->getIdentifier()), 1325663761);
|
||||
}
|
||||
$this->elementsByIdentifier[$renderable->getIdentifier()] = $renderable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an element from the ElementsByIdentifier cache
|
||||
*
|
||||
* @param RenderableInterface $renderable
|
||||
* @internal
|
||||
*/
|
||||
public function unregisterRenderable(RenderableInterface $renderable)
|
||||
{
|
||||
if ($renderable instanceof FormElementInterface) {
|
||||
unset($this->elementsByIdentifier[$renderable->getIdentifier()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Form Element by its identifier
|
||||
*
|
||||
* If identifier does not exist, returns NULL.
|
||||
*
|
||||
* @param string $elementIdentifier
|
||||
* @return FormElementInterface The element with the given $elementIdentifier or NULL if none found
|
||||
*/
|
||||
public function getElementByIdentifier(string $elementIdentifier)
|
||||
{
|
||||
return $this->elementsByIdentifier[$elementIdentifier] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default value of a form element
|
||||
*
|
||||
* @param string $elementIdentifier identifier of the form element. This supports property paths!
|
||||
* @param mixed $defaultValue
|
||||
* @internal
|
||||
*/
|
||||
public function addElementDefaultValue(string $elementIdentifier, $defaultValue)
|
||||
{
|
||||
$this->elementDefaultValues = ArrayUtility::setValueByPath(
|
||||
$this->elementDefaultValues,
|
||||
$elementIdentifier,
|
||||
$defaultValue,
|
||||
'.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the default value of the specified form element
|
||||
* or NULL if no default value was set
|
||||
*
|
||||
* @param string $elementIdentifier identifier of the form element. This supports property paths!
|
||||
* @return mixed The elements default value
|
||||
* @internal
|
||||
*/
|
||||
public function getElementDefaultValueByIdentifier(string $elementIdentifier)
|
||||
{
|
||||
return ObjectAccess::getPropertyPath($this->elementDefaultValues, $elementIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move $pageToMove before $referencePage
|
||||
*
|
||||
* @param Page $pageToMove
|
||||
* @param Page $referencePage
|
||||
*/
|
||||
public function movePageBefore(Page $pageToMove, Page $referencePage)
|
||||
{
|
||||
$this->moveRenderableBefore($pageToMove, $referencePage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move $pageToMove after $referencePage
|
||||
*
|
||||
* @param Page $pageToMove
|
||||
* @param Page $referencePage
|
||||
*/
|
||||
public function movePageAfter(Page $pageToMove, Page $referencePage)
|
||||
{
|
||||
$this->moveRenderableAfter($pageToMove, $referencePage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove $pageToRemove from form
|
||||
*
|
||||
* @param Page $pageToRemove
|
||||
*/
|
||||
public function removePage(Page $pageToRemove)
|
||||
{
|
||||
$this->removeRenderable($pageToRemove);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind the current request & response to this form instance, effectively creating
|
||||
* a new "instance" of the Form.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
* @return FormRuntime
|
||||
*/
|
||||
public function bind(Request $request, Response $response): FormRuntime
|
||||
{
|
||||
return $this->objectManager->get(FormRuntime::class, $this, $request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $propertyPath
|
||||
* @return ProcessingRule
|
||||
*/
|
||||
public function getProcessingRule(string $propertyPath): ProcessingRule
|
||||
{
|
||||
if (!isset($this->processingRules[$propertyPath])) {
|
||||
$this->processingRules[$propertyPath] = $this->objectManager->get(ProcessingRule::class);
|
||||
}
|
||||
return $this->processingRules[$propertyPath];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all mapping rules
|
||||
*
|
||||
* @return \TYPO3\CMS\Form\Mvc\ProcessingRule[]
|
||||
* @internal
|
||||
*/
|
||||
public function getProcessingRules(): array
|
||||
{
|
||||
return $this->processingRules;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
public function getTypeDefinitions(): array
|
||||
{
|
||||
return $this->typeDefinitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
public function getValidatorsDefinition(): array
|
||||
{
|
||||
return $this->validatorsDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
public function getConditionContextDefinition(): array
|
||||
{
|
||||
return $this->conditionContextDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the persistence identifier of the form
|
||||
*
|
||||
* @return string
|
||||
* @internal
|
||||
*/
|
||||
public function getPersistenceIdentifier(): string
|
||||
{
|
||||
return $this->persistenceIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the renderer class name
|
||||
*
|
||||
* @param string $rendererClassName
|
||||
*/
|
||||
public function setRendererClassName(string $rendererClassName)
|
||||
{
|
||||
$this->rendererClassName = $rendererClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the classname of the renderer
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRendererClassName(): string
|
||||
{
|
||||
return $this->rendererClassName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator;
|
||||
use TYPO3\CMS\Form\Domain\Exception\IdentifierNotValidException;
|
||||
use TYPO3\CMS\Form\Domain\Model\Renderable\AbstractRenderable;
|
||||
|
||||
/**
|
||||
* A base form element, which is the starting point for creating custom (PHP-based)
|
||||
* Form Elements.
|
||||
*
|
||||
* A *FormElement* is a part of a *Page*, which in turn is part of a FormDefinition.
|
||||
* See {@link FormDefinition} for an in-depth explanation.
|
||||
*
|
||||
* Subclassing this class is a good starting-point for implementing custom PHP-based
|
||||
* Form Elements.
|
||||
*
|
||||
* Most of the functionality and API is implemented in {@link \TYPO3\CMS\Form\Domain\Model\Renderable\AbstractRenderable}, so
|
||||
* make sure to check out this class as well.
|
||||
*
|
||||
* Still, it is quite rare that you need to subclass this class; often
|
||||
* you can just use the {@link \TYPO3\CMS\Form\Domain\Model\FormElements\GenericFormElement} and replace some templates.
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is meant to be sub classed by developers.**
|
||||
*/
|
||||
abstract class AbstractFormElement extends AbstractRenderable implements FormElementInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $properties = [];
|
||||
|
||||
/**
|
||||
* Constructor. Needs this FormElement's identifier and the FormElement type
|
||||
*
|
||||
* @param string $identifier The FormElement's identifier
|
||||
* @param string $type The Form Element Type
|
||||
* @throws IdentifierNotValidException
|
||||
*/
|
||||
public function __construct(string $identifier, string $type)
|
||||
{
|
||||
if (!is_string($identifier) || strlen($identifier) === 0) {
|
||||
throw new IdentifierNotValidException('The given identifier was not a string or the string was empty.', 1477082502);
|
||||
}
|
||||
$this->identifier = $identifier;
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method in your custom FormElements if needed
|
||||
*/
|
||||
public function initializeFormElement()
|
||||
{
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['initializeFormElement'] ?? [] as $className) {
|
||||
$hookObj = GeneralUtility::makeInstance($className);
|
||||
if (method_exists($hookObj, 'initializeFormElement')) {
|
||||
$hookObj->initializeFormElement(
|
||||
$this
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the global unique identifier of the element
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUniqueIdentifier(): string
|
||||
{
|
||||
$formDefinition = $this->getRootForm();
|
||||
$uniqueIdentifier = sprintf('%s-%s', $formDefinition->getIdentifier(), $this->identifier);
|
||||
$uniqueIdentifier = preg_replace('/[^a-zA-Z0-9_-]/', '_', $uniqueIdentifier);
|
||||
return lcfirst($uniqueIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default value of the element
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getDefaultValue()
|
||||
{
|
||||
$formDefinition = $this->getRootForm();
|
||||
return $formDefinition->getElementDefaultValueByIdentifier($this->identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default value of the element
|
||||
*
|
||||
* @param mixed $defaultValue
|
||||
*/
|
||||
public function setDefaultValue($defaultValue)
|
||||
{
|
||||
$formDefinition = $this->getRootForm();
|
||||
$currentDefaultValue = $formDefinition->getElementDefaultValueByIdentifier($this->identifier);
|
||||
if (is_array($currentDefaultValue) && is_array($defaultValue)) {
|
||||
ArrayUtility::mergeRecursiveWithOverrule($currentDefaultValue, $defaultValue);
|
||||
$defaultValue = ArrayUtility::removeNullValuesRecursive($currentDefaultValue);
|
||||
}
|
||||
$formDefinition->addElementDefaultValue($this->identifier, $defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the element is required
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isRequired(): bool
|
||||
{
|
||||
foreach ($this->getValidators() as $validator) {
|
||||
if ($validator instanceof NotEmptyValidator) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a property of the element
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function setProperty(string $key, $value)
|
||||
{
|
||||
if (is_array($value) && isset($this->properties[$key]) && is_array($this->properties[$key])) {
|
||||
ArrayUtility::mergeRecursiveWithOverrule($this->properties[$key], $value);
|
||||
$this->properties[$key] = ArrayUtility::removeNullValuesRecursive($this->properties[$key]);
|
||||
} elseif ($value === null) {
|
||||
unset($this->properties[$key]);
|
||||
} else {
|
||||
$this->properties[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all properties
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getProperties(): array
|
||||
{
|
||||
return $this->properties;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
use TYPO3\CMS\Form\Domain\Exception\IdentifierNotValidException;
|
||||
use TYPO3\CMS\Form\Domain\Exception\TypeDefinitionNotFoundException;
|
||||
use TYPO3\CMS\Form\Domain\Exception\TypeDefinitionNotValidException;
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception\FormDefinitionConsistencyException;
|
||||
use TYPO3\CMS\Form\Domain\Model\Renderable\AbstractCompositeRenderable;
|
||||
|
||||
/**
|
||||
* A base class for "section-like" form parts like "Page" or "Section" (which
|
||||
* is rendered as "Fieldset")
|
||||
*
|
||||
* This class contains multiple FormElements ({@link FormElementInterface}).
|
||||
*
|
||||
* Please see {@link FormDefinition} for an in-depth explanation.
|
||||
*
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
* Scope: frontend
|
||||
*/
|
||||
abstract class AbstractSection extends AbstractCompositeRenderable
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructor. Needs the identifier and type of this element
|
||||
*
|
||||
* @param string $identifier The Section identifier
|
||||
* @param string $type The Section type
|
||||
* @throws IdentifierNotValidException if the identifier was no non-empty string
|
||||
*/
|
||||
public function __construct(string $identifier, string $type)
|
||||
{
|
||||
if (!is_string($identifier) || strlen($identifier) === 0) {
|
||||
throw new IdentifierNotValidException('The given identifier was not a string or the string was empty.', 1477082501);
|
||||
}
|
||||
|
||||
$this->identifier = $identifier;
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the child Form Elements
|
||||
*
|
||||
* @return FormElementInterface[] The Page's elements
|
||||
*/
|
||||
public function getElements(): array
|
||||
{
|
||||
return $this->renderables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the child Form Elements
|
||||
*
|
||||
* @return FormElementInterface[] The Page's elements
|
||||
*/
|
||||
public function getElementsRecursively(): array
|
||||
{
|
||||
return $this->getRenderablesRecursively();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new form element at the end of the section
|
||||
*
|
||||
* @param FormElementInterface $formElement The form element to add
|
||||
* @throws FormDefinitionConsistencyException if FormElement is already added to a section
|
||||
*/
|
||||
public function addElement(FormElementInterface $formElement)
|
||||
{
|
||||
$this->addRenderable($formElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a form element with the given $identifier and attach it to this section/page.
|
||||
*
|
||||
* - Create Form Element object based on the given $typeName
|
||||
* - set defaults inside the Form Element (based on the parent form's field defaults)
|
||||
* - attach Form Element to this Section/Page
|
||||
* - return the newly created Form Element object
|
||||
*
|
||||
*
|
||||
* @param string $identifier Identifier of the new form element
|
||||
* @param string $typeName type of the new form element
|
||||
* @return FormElementInterface the newly created form element
|
||||
* @throws TypeDefinitionNotFoundException
|
||||
* @throws TypeDefinitionNotValidException
|
||||
*/
|
||||
public function createElement(string $identifier, string $typeName): FormElementInterface
|
||||
{
|
||||
$formDefinition = $this->getRootForm();
|
||||
|
||||
$typeDefinitions = $formDefinition->getTypeDefinitions();
|
||||
if (isset($typeDefinitions[$typeName])) {
|
||||
$typeDefinition = $typeDefinitions[$typeName];
|
||||
} else {
|
||||
$renderingOptions = $formDefinition->getRenderingOptions();
|
||||
$skipUnknownElements = isset($renderingOptions['skipUnknownElements']) && $renderingOptions['skipUnknownElements'] === true;
|
||||
if (!$skipUnknownElements) {
|
||||
throw new TypeDefinitionNotFoundException(sprintf('Type "%s" not found. Probably some configuration is missing.', $typeName), 1382364019);
|
||||
}
|
||||
|
||||
$element = GeneralUtility::makeInstance(ObjectManager::class)
|
||||
->get(UnknownFormElement::class, $identifier, $typeName);
|
||||
$this->addElement($element);
|
||||
return $element;
|
||||
}
|
||||
|
||||
if (!isset($typeDefinition['implementationClassName'])) {
|
||||
throw new TypeDefinitionNotFoundException(sprintf('The "implementationClassName" was not set in type definition "%s".', $typeName), 1325689855);
|
||||
}
|
||||
|
||||
$implementationClassName = $typeDefinition['implementationClassName'];
|
||||
$element = GeneralUtility::makeInstance(ObjectManager::class)
|
||||
->get($implementationClassName, $identifier, $typeName);
|
||||
if (!$element instanceof FormElementInterface) {
|
||||
throw new TypeDefinitionNotValidException(sprintf('The "implementationClassName" for element "%s" ("%s") does not implement the FormElementInterface.', $identifier, $implementationClassName), 1327318156);
|
||||
}
|
||||
unset($typeDefinition['implementationClassName']);
|
||||
|
||||
$this->addElement($element);
|
||||
$element->setOptions($typeDefinition);
|
||||
|
||||
$element->initializeFormElement();
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move FormElement $element before $referenceElement.
|
||||
*
|
||||
* Both $element and $referenceElement must be direct descendants of this Section/Page.
|
||||
*
|
||||
* @param FormElementInterface $elementToMove
|
||||
* @param FormElementInterface $referenceElement
|
||||
*/
|
||||
public function moveElementBefore(FormElementInterface $elementToMove, FormElementInterface $referenceElement)
|
||||
{
|
||||
$this->moveRenderableBefore($elementToMove, $referenceElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move FormElement $element after $referenceElement
|
||||
*
|
||||
* Both $element and $referenceElement must be direct descendants of this Section/Page.
|
||||
*
|
||||
* @param FormElementInterface $elementToMove
|
||||
* @param FormElementInterface $referenceElement
|
||||
*/
|
||||
public function moveElementAfter(FormElementInterface $elementToMove, FormElementInterface $referenceElement)
|
||||
{
|
||||
$this->moveRenderableAfter($elementToMove, $referenceElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove $elementToRemove from this Section/Page
|
||||
*
|
||||
* @param FormElementInterface $elementToRemove
|
||||
*/
|
||||
public function removeElement(FormElementInterface $elementToRemove)
|
||||
{
|
||||
$this->removeRenderable($elementToRemove);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* A date picker form element
|
||||
*
|
||||
* Scope: frontend
|
||||
*/
|
||||
class DatePicker extends AbstractFormElement
|
||||
{
|
||||
|
||||
/**
|
||||
* Initializes the Form Element by setting the data type to "DateTime"
|
||||
* @internal
|
||||
*/
|
||||
public function initializeFormElement()
|
||||
{
|
||||
$this->setDataType('DateTime');
|
||||
parent::initializeFormElement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* A generic file upload form element
|
||||
*
|
||||
* Scope: frontend
|
||||
*/
|
||||
class FileUpload extends AbstractFormElement
|
||||
{
|
||||
|
||||
/**
|
||||
* Initializes the Form Element by setting the data type to a Extbase File Reference
|
||||
* @internal
|
||||
*/
|
||||
public function initializeFormElement()
|
||||
{
|
||||
$this->setDataType('TYPO3\CMS\Extbase\Domain\Model\FileReference');
|
||||
parent::initializeFormElement();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface;
|
||||
use TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface;
|
||||
|
||||
/**
|
||||
* A base form element interface, which can be the starting point for creating
|
||||
* custom (PHP-based) Form Elements.
|
||||
*
|
||||
* A *FormElement* is a part of a *Page*, which in turn is part of a FormDefinition.
|
||||
* See {@link FormDefinition} for an in-depth explanation.
|
||||
*
|
||||
* **Often, you should rather subclass {@link AbstractFormElement} instead of
|
||||
* implementing this interface.**
|
||||
*
|
||||
* Scope: frontend
|
||||
*/
|
||||
interface FormElementInterface extends RenderableInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Will be called as soon as the element is (tried to be) added to a form
|
||||
* @see registerInFormIfPossible()
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function initializeFormElement();
|
||||
|
||||
/**
|
||||
* Returns a unique identifier of this element.
|
||||
* While element identifiers are only unique within one form,
|
||||
* this includes the identifier of the form itself, making it "globally" unique
|
||||
*
|
||||
* @return string the "globally" unique identifier of this element
|
||||
*/
|
||||
public function getUniqueIdentifier(): string;
|
||||
|
||||
/**
|
||||
* Get the default value with which the Form Element should be initialized
|
||||
* during display.
|
||||
*
|
||||
* @return mixed the default value for this Form Element
|
||||
*/
|
||||
public function getDefaultValue();
|
||||
|
||||
/**
|
||||
* Set the default value with which the Form Element should be initialized
|
||||
* during display.
|
||||
*
|
||||
* @param mixed $defaultValue the default value for this Form Element
|
||||
*/
|
||||
public function setDefaultValue($defaultValue);
|
||||
|
||||
/**
|
||||
* Set an element-specific configuration property.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function setProperty(string $key, $value);
|
||||
|
||||
/**
|
||||
* Get all element-specific configuration properties
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getProperties(): array;
|
||||
|
||||
/**
|
||||
* Set a rendering option
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function setRenderingOption(string $key, $value);
|
||||
|
||||
/**
|
||||
* Returns the child validators of the ConjunctionValidator that is registered for this element
|
||||
*
|
||||
* @return \SplObjectStorage<ValidatorInterface>
|
||||
* @internal
|
||||
*/
|
||||
public function getValidators(): \SplObjectStorage;
|
||||
|
||||
/**
|
||||
* Registers a validator for this element
|
||||
*
|
||||
* @param ValidatorInterface $validator
|
||||
*/
|
||||
public function addValidator(ValidatorInterface $validator);
|
||||
|
||||
/**
|
||||
* Set the target data type for this element
|
||||
*
|
||||
* @param string $dataType the target data type
|
||||
*/
|
||||
public function setDataType(string $dataType);
|
||||
|
||||
/**
|
||||
* Whether or not this element is required
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isRequired(): bool;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* A generic form element
|
||||
*
|
||||
* Scope: frontend
|
||||
*/
|
||||
class GenericFormElement extends AbstractFormElement
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Exception\TypeDefinitionNotValidException;
|
||||
|
||||
/**
|
||||
* A GridContainer, being part of a bigger Page
|
||||
*
|
||||
* This class contains multiple GridRow elements.
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
*/
|
||||
class GridContainer extends Section implements GridContainerInterface
|
||||
{
|
||||
/**
|
||||
* Initializes the Form Element
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function initializeFormElement()
|
||||
{
|
||||
trigger_error(
|
||||
'"GridContainer" form elements will be removed in TYPO3 v10.0. Use "GridRow" form elements instead.',
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
parent::initializeFormElement();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register this element at the parent form, if there is a connection to the parent form.
|
||||
*
|
||||
* @throws TypeDefinitionNotValidException
|
||||
* @internal
|
||||
*/
|
||||
public function registerInFormIfPossible()
|
||||
{
|
||||
foreach ($this->getElementsRecursively() as $renderable) {
|
||||
if ($renderable instanceof GridContainerInterface) {
|
||||
throw new TypeDefinitionNotValidException(
|
||||
sprintf('Grid containers ("%s") within grid containers ("%s") are not allowed.', $renderable->getIdentifier(), $this->getIdentifier()),
|
||||
1489412790
|
||||
);
|
||||
}
|
||||
}
|
||||
parent::registerInFormIfPossible();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new row element at the end of the grid container
|
||||
*
|
||||
* @param FormElementInterface $formElement The form element to add
|
||||
*/
|
||||
public function addElement(FormElementInterface $formElement)
|
||||
{
|
||||
if (!$formElement instanceof GridRowInterface) {
|
||||
throw new TypeDefinitionNotValidException(
|
||||
sprintf('The "implementationClassName" for element "%s" (type "%s") does not implement the GridRowInterface.', $formElement->getIdentifier(), $formElement->getType()),
|
||||
1489486301
|
||||
);
|
||||
}
|
||||
$this->addRenderable($formElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a form element with the given $identifier and attach it to this container.
|
||||
*
|
||||
* @param string $identifier Identifier of the new form element
|
||||
* @param string $typeName type of the new form element
|
||||
* @return FormElementInterface the newly created grid row
|
||||
* @throws TypeDefinitionNotValidException
|
||||
*/
|
||||
public function createElement(string $identifier, string $typeName): FormElementInterface
|
||||
{
|
||||
$element = parent::createElement($identifier, $typeName);
|
||||
|
||||
if (!$element instanceof GridRowInterface) {
|
||||
throw new TypeDefinitionNotValidException(
|
||||
sprintf('The "implementationClassName" for element "%s" (type "%s") does not implement the GridRowInterface.', $identifier, $typeName),
|
||||
1489486302
|
||||
);
|
||||
}
|
||||
return $element;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Scope: frontend
|
||||
*/
|
||||
interface GridContainerInterface extends FormElementInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Exception\TypeDefinitionNotValidException;
|
||||
|
||||
/**
|
||||
* A grid row, being part of a grid container
|
||||
*
|
||||
* This class contains multiple FormElements ({@link FormElementInterface}).
|
||||
*
|
||||
* Please see {@link FormDefinition} for an in-depth explanation.
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
*/
|
||||
class GridRow extends Section implements GridRowInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Add a new form element at the end of the grid row
|
||||
*
|
||||
* @param FormElementInterface $formElement The form element to add
|
||||
* @throws TypeDefinitionNotValidException if FormElement is already added to a section
|
||||
*/
|
||||
public function addElement(FormElementInterface $formElement)
|
||||
{
|
||||
if ($formElement instanceof GridContainerInterface) {
|
||||
throw new TypeDefinitionNotValidException(
|
||||
sprintf('Grid containers ("%s") within grid rows ("%s") are not allowed.', $formElement->getIdentifier(), $this->getIdentifier()),
|
||||
1489413379
|
||||
);
|
||||
}
|
||||
|
||||
$this->addRenderable($formElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a form element with the given $identifier and attach it to this container.
|
||||
*
|
||||
* @param string $identifier Identifier of the new form element
|
||||
* @param string $typeName type of the new form element
|
||||
* @return GridRowInterface the newly created frid row
|
||||
* @throws TypeDefinitionNotValidException
|
||||
*/
|
||||
public function createElement(string $identifier, string $typeName): FormElementInterface
|
||||
{
|
||||
$element = parent::createElement($identifier, $typeName);
|
||||
|
||||
if ($element instanceof GridContainerInterface) {
|
||||
throw new TypeDefinitionNotValidException(
|
||||
sprintf('Grid containers ("%s") within grid rows ("%s") are not allowed.', $element->getIdentifier(), $this->getIdentifier()),
|
||||
1489413538
|
||||
);
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Scope: frontend
|
||||
*/
|
||||
interface GridRowInterface extends FormElementInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Model\FormDefinition;
|
||||
use TYPO3\CMS\Form\Domain\Model\Renderable\CompositeRenderableInterface;
|
||||
use TYPO3\CMS\Form\Exception as FormException;
|
||||
|
||||
/**
|
||||
* A Page, being part of a bigger FormDefinition. It contains numerous FormElements
|
||||
* as children.
|
||||
*
|
||||
* A FormDefinition consists of multiple Pages, where only one page is visible
|
||||
* at any given time.
|
||||
*
|
||||
* Most of the API of this object is implemented in {@link AbstractSection},
|
||||
* so make sure to review this class as well.
|
||||
*
|
||||
* Please see {@link FormDefinition} for an in-depth explanation.
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
*/
|
||||
class Page extends AbstractSection
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructor. Needs this Page's identifier
|
||||
*
|
||||
* @param string $identifier The Page's identifier
|
||||
* @param string $type The Page's type
|
||||
* @throws \TYPO3\CMS\Form\Domain\Exception\IdentifierNotValidException if the identifier was no non-empty string
|
||||
*/
|
||||
public function __construct(string $identifier, string $type = 'Page')
|
||||
{
|
||||
parent::__construct($identifier, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parent renderable
|
||||
*
|
||||
* @param CompositeRenderableInterface $parentRenderable
|
||||
* @throws FormException
|
||||
*/
|
||||
public function setParentRenderable(CompositeRenderableInterface $parentRenderable)
|
||||
{
|
||||
if (!($parentRenderable instanceof FormDefinition)) {
|
||||
throw new FormException(sprintf('The specified parentRenderable must be a FormDefinition, got "%s"', is_object($parentRenderable) ? get_class($parentRenderable) : gettype($parentRenderable)), 1329233747);
|
||||
}
|
||||
parent::setParentRenderable($parentRenderable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator;
|
||||
|
||||
/**
|
||||
* A Section, being part of a bigger Page
|
||||
*
|
||||
* This class contains multiple FormElements ({@link FormElementInterface}).
|
||||
*
|
||||
* Please see {@link FormDefinition} for an in-depth explanation.
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
*/
|
||||
class Section extends AbstractSection implements FormElementInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $properties = [];
|
||||
|
||||
/**
|
||||
* Will be called as soon as the element is (tried to be) added to a form
|
||||
* @see registerInFormIfPossible()
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function initializeFormElement()
|
||||
{
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['initializeFormElement'] ?? [] as $className) {
|
||||
$hookObj = GeneralUtility::makeInstance($className);
|
||||
if (method_exists($hookObj, 'initializeFormElement')) {
|
||||
$hookObj->initializeFormElement(
|
||||
$this
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a unique identifier of this element.
|
||||
* While element identifiers are only unique within one form,
|
||||
* this includes the identifier of the form itself, making it "globally" unique
|
||||
*
|
||||
* @return string the "globally" unique identifier of this element
|
||||
*/
|
||||
public function getUniqueIdentifier(): string
|
||||
{
|
||||
$formDefinition = $this->getRootForm();
|
||||
return sprintf('%s-%s', $formDefinition->getIdentifier(), $this->identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default value with which the Form Element should be initialized
|
||||
* during display.
|
||||
* Note: This is currently not used for section elements
|
||||
*
|
||||
* @return mixed the default value for this Form Element
|
||||
*/
|
||||
public function getDefaultValue()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default value with which the Form Element should be initialized
|
||||
* during display.
|
||||
* Note: This is currently ignored for section elements
|
||||
*
|
||||
* @param mixed $defaultValue the default value for this Form Element
|
||||
*/
|
||||
public function setDefaultValue($defaultValue)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all element-specific configuration properties
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getProperties(): array
|
||||
{
|
||||
return $this->properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an element-specific configuration property.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function setProperty(string $key, $value)
|
||||
{
|
||||
if (is_array($value) && isset($this->properties[$key]) && is_array($this->properties[$key])) {
|
||||
ArrayUtility::mergeRecursiveWithOverrule($this->properties[$key], $value);
|
||||
$this->properties[$key] = ArrayUtility::removeNullValuesRecursive($this->properties[$key]);
|
||||
} elseif ($value === null) {
|
||||
unset($this->properties[$key]);
|
||||
} else {
|
||||
$this->properties[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this element is required
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isRequired(): bool
|
||||
{
|
||||
foreach ($this->getValidators() as $validator) {
|
||||
if ($validator instanceof NotEmptyValidator) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\FormElements;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Form\Domain\Exception\IdentifierNotValidException;
|
||||
use TYPO3\CMS\Form\Domain\Model\Renderable\AbstractRenderable;
|
||||
|
||||
/**
|
||||
* A Form Element that has no definition.
|
||||
*
|
||||
* Scope: frontend
|
||||
*/
|
||||
class UnknownFormElement extends AbstractRenderable implements FormElementInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructor. Needs this FormElement's identifier and the FormElement type
|
||||
*
|
||||
* @param string $identifier The FormElement's identifier
|
||||
* @param string $type The Form Element Type
|
||||
* @throws IdentifierNotValidException
|
||||
*/
|
||||
public function __construct(string $identifier, string $type)
|
||||
{
|
||||
if (!is_string($identifier) || strlen($identifier) === 0) {
|
||||
throw new IdentifierNotValidException('The given identifier was not a string or the string was empty.', 1382364370);
|
||||
}
|
||||
$this->identifier = $identifier;
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the form element
|
||||
*/
|
||||
public function initializeFormElement()
|
||||
{
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['initializeFormElement'] ?? [] as $className) {
|
||||
$hookObj = GeneralUtility::makeInstance($className);
|
||||
if (method_exists($hookObj, 'initializeFormElement')) {
|
||||
$hookObj->initializeFormElement(
|
||||
$this
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a unique identifier of this element.
|
||||
* While element identifiers are only unique within one form,
|
||||
* this includes the identifier of the form itself, making it "globally" unique
|
||||
*
|
||||
* @return string the "globally" unique identifier of this element
|
||||
*/
|
||||
public function getUniqueIdentifier(): string
|
||||
{
|
||||
$formDefinition = $this->getRootForm();
|
||||
$uniqueIdentifier = sprintf('%s-%s', $formDefinition->getIdentifier(), $this->identifier);
|
||||
$uniqueIdentifier = preg_replace('/[^a-zA-Z0-9-_]/', '_', $uniqueIdentifier);
|
||||
return lcfirst($uniqueIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template name of the renderable
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTemplateName(): string
|
||||
{
|
||||
return 'UnknownElement';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed the default value for this Form Element
|
||||
* @internal
|
||||
*/
|
||||
public function getDefaultValue()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not used in this implementation
|
||||
*
|
||||
* @param mixed $defaultValue the default value for this Form Element
|
||||
* @internal
|
||||
*/
|
||||
public function setDefaultValue($defaultValue)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Not used in this implementation
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @internal
|
||||
*/
|
||||
public function setProperty(string $key, $value)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
public function getProperties(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @internal
|
||||
*/
|
||||
public function isRequired(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\Renderable;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception\FormDefinitionConsistencyException;
|
||||
|
||||
/**
|
||||
* Convenience base class which implements common functionality for most
|
||||
* classes which implement CompositeRenderableInterface, i.e. have **child renderable elements**.
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
*/
|
||||
abstract class AbstractCompositeRenderable extends AbstractRenderable implements CompositeRenderableInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* array of child renderables
|
||||
*
|
||||
* @var \TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface[]
|
||||
*/
|
||||
protected $renderables = [];
|
||||
|
||||
/**
|
||||
* Add a renderable to the list of child renderables.
|
||||
*
|
||||
* This function will be wrapped by the subclasses, f.e. with an "addPage"
|
||||
* or "addElement" method with the correct type hint.
|
||||
*
|
||||
* @param RenderableInterface $renderable
|
||||
* @throws FormDefinitionConsistencyException
|
||||
* @internal
|
||||
*/
|
||||
protected function addRenderable(RenderableInterface $renderable)
|
||||
{
|
||||
if ($renderable->getParentRenderable() !== null) {
|
||||
throw new FormDefinitionConsistencyException(sprintf('The renderable with identifier "%s" is already added to another element (element identifier: "%s").', $renderable->getIdentifier(), $renderable->getParentRenderable()->getIdentifier()), 1325665144);
|
||||
}
|
||||
$renderable->setIndex(count($this->renderables));
|
||||
$renderable->setParentRenderable($this);
|
||||
$this->renderables[] = $renderable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move $renderableToMove before $referenceRenderable
|
||||
*
|
||||
* This function will be wrapped by the subclasses, f.e. with an "movePageBefore"
|
||||
* or "moveElementBefore" method with the correct type hint.
|
||||
*
|
||||
* @param RenderableInterface $renderableToMove
|
||||
* @param RenderableInterface $referenceRenderable
|
||||
* @throws FormDefinitionConsistencyException
|
||||
* @internal
|
||||
*/
|
||||
protected function moveRenderableBefore(RenderableInterface $renderableToMove, RenderableInterface $referenceRenderable)
|
||||
{
|
||||
if ($renderableToMove->getParentRenderable() !== $referenceRenderable->getParentRenderable() || $renderableToMove->getParentRenderable() !== $this) {
|
||||
throw new FormDefinitionConsistencyException('Moved renderables need to be part of the same parent element.', 1326089744);
|
||||
}
|
||||
|
||||
$reorderedRenderables = [];
|
||||
$i = 0;
|
||||
foreach ($this->renderables as $renderable) {
|
||||
if ($renderable === $renderableToMove) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($renderable === $referenceRenderable) {
|
||||
$reorderedRenderables[] = $renderableToMove;
|
||||
$renderableToMove->setIndex($i);
|
||||
$i++;
|
||||
}
|
||||
$reorderedRenderables[] = $renderable;
|
||||
$renderable->setIndex($i);
|
||||
$i++;
|
||||
}
|
||||
$this->renderables = $reorderedRenderables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move $renderableToMove after $referenceRenderable
|
||||
*
|
||||
* This function will be wrapped by the subclasses, f.e. with an "movePageAfter"
|
||||
* or "moveElementAfter" method with the correct type hint.
|
||||
*
|
||||
* @param RenderableInterface $renderableToMove
|
||||
* @param RenderableInterface $referenceRenderable
|
||||
* @throws FormDefinitionConsistencyException
|
||||
* @internal
|
||||
*/
|
||||
protected function moveRenderableAfter(RenderableInterface $renderableToMove, RenderableInterface $referenceRenderable)
|
||||
{
|
||||
if ($renderableToMove->getParentRenderable() !== $referenceRenderable->getParentRenderable() || $renderableToMove->getParentRenderable() !== $this) {
|
||||
throw new FormDefinitionConsistencyException('Moved renderables need to be part of the same parent element.', 1477083145);
|
||||
}
|
||||
|
||||
$reorderedRenderables = [];
|
||||
$i = 0;
|
||||
foreach ($this->renderables as $renderable) {
|
||||
if ($renderable === $renderableToMove) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$reorderedRenderables[] = $renderable;
|
||||
$renderable->setIndex($i);
|
||||
$i++;
|
||||
|
||||
if ($renderable === $referenceRenderable) {
|
||||
$reorderedRenderables[] = $renderableToMove;
|
||||
$renderableToMove->setIndex($i);
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
$this->renderables = $reorderedRenderables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all RenderableInterface instances of this composite renderable recursively
|
||||
*
|
||||
* @return RenderableInterface[]
|
||||
* @internal
|
||||
*/
|
||||
public function getRenderablesRecursively(): array
|
||||
{
|
||||
$renderables = [];
|
||||
foreach ($this->renderables as $renderable) {
|
||||
$renderables[] = $renderable;
|
||||
if ($renderable instanceof CompositeRenderableInterface) {
|
||||
$renderables = array_merge($renderables, $renderable->getRenderablesRecursively());
|
||||
}
|
||||
}
|
||||
return $renderables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a renderable from this renderable.
|
||||
*
|
||||
* This function will be wrapped by the subclasses, f.e. with an "removePage"
|
||||
* or "removeElement" method with the correct type hint.
|
||||
*
|
||||
* @param RenderableInterface $renderableToRemove
|
||||
* @throws FormDefinitionConsistencyException
|
||||
* @internal
|
||||
*/
|
||||
protected function removeRenderable(RenderableInterface $renderableToRemove)
|
||||
{
|
||||
if ($renderableToRemove->getParentRenderable() !== $this) {
|
||||
throw new FormDefinitionConsistencyException('The renderable to be removed must be part of the calling parent renderable.', 1326090127);
|
||||
}
|
||||
|
||||
$updatedRenderables = [];
|
||||
foreach ($this->renderables as $renderable) {
|
||||
if ($renderable === $renderableToRemove) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$updatedRenderables[] = $renderable;
|
||||
}
|
||||
$this->renderables = $updatedRenderables;
|
||||
|
||||
$renderableToRemove->onRemoveFromParentRenderable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register this element at the parent form, if there is a connection to the parent form.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function registerInFormIfPossible()
|
||||
{
|
||||
parent::registerInFormIfPossible();
|
||||
foreach ($this->renderables as $renderable) {
|
||||
$renderable->registerInFormIfPossible();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called after a renderable has been removed from its parent
|
||||
* renderable.
|
||||
* This just passes the event down to all child renderables of this composite renderable.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function onRemoveFromParentRenderable()
|
||||
{
|
||||
foreach ($this->renderables as $renderable) {
|
||||
$renderable->onRemoveFromParentRenderable();
|
||||
}
|
||||
parent::onRemoveFromParentRenderable();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,482 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\Renderable;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\Cache\CacheManager;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Extbase\Object\ObjectManager;
|
||||
use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface;
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception\FormDefinitionConsistencyException;
|
||||
use TYPO3\CMS\Form\Domain\Model\Exception\ValidatorPresetNotFoundException;
|
||||
use TYPO3\CMS\Form\Domain\Model\FormDefinition;
|
||||
|
||||
/**
|
||||
* Convenience base class which implements common functionality for most
|
||||
* classes which implement RenderableInterface.
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractRenderable implements RenderableInterface, VariableRenderableInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Abstract "type" of this Renderable. Is used during the rendering process
|
||||
* to determine the template file or the View PHP class being used to render
|
||||
* the particular element.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* The identifier of this renderable
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $identifier;
|
||||
|
||||
/**
|
||||
* The parent renderable
|
||||
*
|
||||
* @var CompositeRenderableInterface
|
||||
*/
|
||||
protected $parentRenderable;
|
||||
|
||||
/**
|
||||
* The label of this renderable
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label = '';
|
||||
|
||||
/**
|
||||
* associative array of rendering options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $renderingOptions = [];
|
||||
|
||||
/**
|
||||
* The position of this renderable inside the parent renderable.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $index = 0;
|
||||
|
||||
/**
|
||||
* The name of the template file of the renderable.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $templateName = '';
|
||||
|
||||
/**
|
||||
* associative array of rendering variants
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $variants = [];
|
||||
|
||||
/**
|
||||
* Get the type of the renderable
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the identifier of the element
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the identifier of the element
|
||||
*
|
||||
* @param string $identifier
|
||||
*/
|
||||
public function setIdentifier(string $identifier)
|
||||
{
|
||||
$this->identifier = $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set multiple properties of this object at once.
|
||||
* Every property which has a corresponding set* method can be set using
|
||||
* the passed $options array.
|
||||
*
|
||||
* @param array $options
|
||||
* @param bool $resetValidators
|
||||
*/
|
||||
public function setOptions(array $options, bool $resetValidators = false)
|
||||
{
|
||||
if (isset($options['label'])) {
|
||||
$this->setLabel($options['label']);
|
||||
}
|
||||
|
||||
if (isset($options['defaultValue'])) {
|
||||
$this->setDefaultValue($options['defaultValue']);
|
||||
}
|
||||
|
||||
if (isset($options['properties'])) {
|
||||
foreach ($options['properties'] as $key => $value) {
|
||||
$this->setProperty($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['renderingOptions'])) {
|
||||
foreach ($options['renderingOptions'] as $key => $value) {
|
||||
$this->setRenderingOption($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['validators'])) {
|
||||
$runtimeCache = GeneralUtility::makeInstance(CacheManager::class)->getCache('cache_runtime');
|
||||
$configurationHashes = $runtimeCache->get('formAbstractRenderableConfigurationHashes') ?: [];
|
||||
|
||||
if ($resetValidators) {
|
||||
$processingRule = $this->getRootForm()->getProcessingRule($this->getIdentifier());
|
||||
foreach ($this->getValidators() as $validator) {
|
||||
$processingRule->removeValidator($validator);
|
||||
}
|
||||
$configurationHashes = [];
|
||||
}
|
||||
|
||||
foreach ($options['validators'] as $validatorConfiguration) {
|
||||
$configurationHash = md5(
|
||||
spl_object_hash($this) .
|
||||
json_encode($validatorConfiguration)
|
||||
);
|
||||
if (in_array($configurationHash, $configurationHashes)) {
|
||||
continue;
|
||||
}
|
||||
$this->createValidator($validatorConfiguration['identifier'], $validatorConfiguration['options'] ?? []);
|
||||
$configurationHashes[] = $configurationHash;
|
||||
$runtimeCache->set('formAbstractRenderableConfigurationHashes', $configurationHashes);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['variants'])) {
|
||||
foreach ($options['variants'] as $variantConfiguration) {
|
||||
$this->createVariant($variantConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
ArrayUtility::assertAllArrayKeysAreValid(
|
||||
$options,
|
||||
['label', 'defaultValue', 'properties', 'renderingOptions', 'validators', 'formEditor', 'variants']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a validator for the element
|
||||
*
|
||||
* @param string $validatorIdentifier
|
||||
* @param array $options
|
||||
* @return mixed
|
||||
* @throws ValidatorPresetNotFoundException
|
||||
*/
|
||||
public function createValidator(string $validatorIdentifier, array $options = [])
|
||||
{
|
||||
$validatorsDefinition = $this->getRootForm()->getValidatorsDefinition();
|
||||
if (isset($validatorsDefinition[$validatorIdentifier]) && is_array($validatorsDefinition[$validatorIdentifier]) && isset($validatorsDefinition[$validatorIdentifier]['implementationClassName'])) {
|
||||
$implementationClassName = $validatorsDefinition[$validatorIdentifier]['implementationClassName'];
|
||||
$defaultOptions = $validatorsDefinition[$validatorIdentifier]['options'] ?? [];
|
||||
|
||||
ArrayUtility::mergeRecursiveWithOverrule($defaultOptions, $options);
|
||||
|
||||
$validator = GeneralUtility::makeInstance(ObjectManager::class)
|
||||
->get($implementationClassName, $defaultOptions);
|
||||
$this->addValidator($validator);
|
||||
return $validator;
|
||||
}
|
||||
throw new ValidatorPresetNotFoundException('The validator preset identified by "' . $validatorIdentifier . '" could not be found, or the implementationClassName was not specified.', 1328710202);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a validator to the element
|
||||
*
|
||||
* @param ValidatorInterface $validator
|
||||
*/
|
||||
public function addValidator(ValidatorInterface $validator)
|
||||
{
|
||||
$formDefinition = $this->getRootForm();
|
||||
$formDefinition->getProcessingRule($this->getIdentifier())->addValidator($validator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all validators on the element
|
||||
*
|
||||
* @return \SplObjectStorage
|
||||
* @internal
|
||||
*/
|
||||
public function getValidators(): \SplObjectStorage
|
||||
{
|
||||
$formDefinition = $this->getRootForm();
|
||||
return $formDefinition->getProcessingRule($this->getIdentifier())->getValidators();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the datatype
|
||||
*
|
||||
* @param string $dataType
|
||||
*/
|
||||
public function setDataType(string $dataType)
|
||||
{
|
||||
$formDefinition = $this->getRootForm();
|
||||
$formDefinition->getProcessingRule($this->getIdentifier())->setDataType($dataType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the classname of the renderer
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRendererClassName(): string
|
||||
{
|
||||
return $this->getRootForm()->getRendererClassName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all rendering options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRenderingOptions(): array
|
||||
{
|
||||
return $this->renderingOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the rendering option $key to $value.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
public function setRenderingOption(string $key, $value)
|
||||
{
|
||||
if (is_array($value) && isset($this->renderingOptions[$key]) && is_array($this->renderingOptions[$key])) {
|
||||
ArrayUtility::mergeRecursiveWithOverrule($this->renderingOptions[$key], $value);
|
||||
$this->renderingOptions[$key] = ArrayUtility::removeNullValuesRecursive($this->renderingOptions[$key]);
|
||||
} elseif ($value === null) {
|
||||
unset($this->renderingOptions[$key]);
|
||||
} else {
|
||||
$this->renderingOptions[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent renderable
|
||||
*
|
||||
* @return CompositeRenderableInterface|null
|
||||
*/
|
||||
public function getParentRenderable()
|
||||
{
|
||||
return $this->parentRenderable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parent renderable
|
||||
*
|
||||
* @param CompositeRenderableInterface $parentRenderable
|
||||
*/
|
||||
public function setParentRenderable(CompositeRenderableInterface $parentRenderable)
|
||||
{
|
||||
$this->parentRenderable = $parentRenderable;
|
||||
$this->registerInFormIfPossible();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the root form this element belongs to
|
||||
*
|
||||
* @return FormDefinition
|
||||
* @throws FormDefinitionConsistencyException
|
||||
*/
|
||||
public function getRootForm(): FormDefinition
|
||||
{
|
||||
$rootRenderable = $this->parentRenderable;
|
||||
while ($rootRenderable !== null && !($rootRenderable instanceof FormDefinition)) {
|
||||
$rootRenderable = $rootRenderable->getParentRenderable();
|
||||
}
|
||||
if ($rootRenderable === null) {
|
||||
throw new FormDefinitionConsistencyException(sprintf('The form element "%s" is not attached to a parent form.', $this->identifier), 1326803398);
|
||||
}
|
||||
|
||||
return $rootRenderable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register this element at the parent form, if there is a connection to the parent form.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function registerInFormIfPossible()
|
||||
{
|
||||
try {
|
||||
$rootForm = $this->getRootForm();
|
||||
$rootForm->registerRenderable($this);
|
||||
} catch (FormDefinitionConsistencyException $exception) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when the renderable is removed from it's parent
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function onRemoveFromParentRenderable()
|
||||
{
|
||||
foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeRemoveFromParentRenderable'] ?? [] as $className) {
|
||||
$hookObj = GeneralUtility::makeInstance($className);
|
||||
if (method_exists($hookObj, 'beforeRemoveFromParentRenderable')) {
|
||||
$hookObj->beforeRemoveFromParentRenderable(
|
||||
$this
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$rootForm = $this->getRootForm();
|
||||
$rootForm->unregisterRenderable($this);
|
||||
} catch (FormDefinitionConsistencyException $exception) {
|
||||
}
|
||||
$this->parentRenderable = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of the renderable
|
||||
*
|
||||
* @return int
|
||||
* @internal
|
||||
*/
|
||||
public function getIndex(): int
|
||||
{
|
||||
return $this->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the index of the renderable
|
||||
*
|
||||
* @param int $index
|
||||
* @internal
|
||||
*/
|
||||
public function setIndex(int $index)
|
||||
{
|
||||
$this->index = $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the label of the renderable
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLabel(): string
|
||||
{
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the label which shall be displayed next to the form element
|
||||
*
|
||||
* @param string $label
|
||||
*/
|
||||
public function setLabel(string $label)
|
||||
{
|
||||
$this->label = $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the templateName name of the renderable
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTemplateName(): string
|
||||
{
|
||||
return empty($this->renderingOptions['templateName'])
|
||||
? $this->type
|
||||
: $this->renderingOptions['templateName'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this renderable is enabled
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return !isset($this->renderingOptions['enabled']) || (bool)$this->renderingOptions['enabled'] === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all rendering variants
|
||||
*
|
||||
* @return RenderableVariantInterface[]
|
||||
*/
|
||||
public function getVariants(): array
|
||||
{
|
||||
return $this->variants;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @return RenderableVariantInterface
|
||||
*/
|
||||
public function createVariant(array $options): RenderableVariantInterface
|
||||
{
|
||||
$identifier = $options['identifier'] ?? '';
|
||||
unset($options['identifier']);
|
||||
|
||||
$variant = GeneralUtility::makeInstance(ObjectManager::class)
|
||||
->get(RenderableVariant::class, $identifier, $options, $this);
|
||||
|
||||
$this->addVariant($variant);
|
||||
return $variant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified variant to this form element
|
||||
*
|
||||
* @param RenderableVariantInterface $variant
|
||||
*/
|
||||
public function addVariant(RenderableVariantInterface $variant)
|
||||
{
|
||||
$this->variants[$variant->getIdentifier()] = $variant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the specified variant to this form element
|
||||
* regardless of their conditions
|
||||
*
|
||||
* @param RenderableVariantInterface $variant
|
||||
*/
|
||||
public function applyVariant(RenderableVariantInterface $variant)
|
||||
{
|
||||
$variant->apply();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\Renderable;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface which all Form Parts must adhere to **when they have sub elements**.
|
||||
* This includes especially "FormDefinition" and "Page".
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
*/
|
||||
interface CompositeRenderableInterface extends RenderableInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns all RenderableInterface instances of this composite renderable recursively
|
||||
*
|
||||
* @return \TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface[]
|
||||
* @internal
|
||||
*/
|
||||
public function getRenderablesRecursively(): array;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\Renderable;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base interface which all Form Parts except the FormDefinition must adhere
|
||||
* to (i.e. all elements which are NOT the root of a Form).
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
*/
|
||||
interface RenderableInterface extends RootRenderableInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Return the parent renderable
|
||||
*
|
||||
* @return CompositeRenderableInterface|null the parent renderable
|
||||
* @internal
|
||||
*/
|
||||
public function getParentRenderable();
|
||||
|
||||
/**
|
||||
* Set the new parent renderable. You should not call this directly;
|
||||
* it is automatically called by addRenderable.
|
||||
*
|
||||
* This method should also register itself at the parent form, if possible.
|
||||
*
|
||||
* @param CompositeRenderableInterface $renderable
|
||||
* @internal
|
||||
*/
|
||||
public function setParentRenderable(CompositeRenderableInterface $renderable);
|
||||
|
||||
/**
|
||||
* Set the index of this renderable inside the parent renderable
|
||||
*
|
||||
* @param int $index
|
||||
* @internal
|
||||
*/
|
||||
public function setIndex(int $index);
|
||||
|
||||
/**
|
||||
* Get the index inside the parent renderable
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getIndex(): int;
|
||||
|
||||
/**
|
||||
* This function is called after a renderable has been removed from its parent
|
||||
* renderable. The function should make sure to clean up the internal state,
|
||||
* like reseting $this->parentRenderable or deregistering the renderable
|
||||
* at the form.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function onRemoveFromParentRenderable();
|
||||
|
||||
/**
|
||||
* Register this element at the parent form, if there is a connection to the parent form.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function registerInFormIfPossible();
|
||||
|
||||
/**
|
||||
* Get the template name of the renderable
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTemplateName(): string;
|
||||
|
||||
/**
|
||||
* Returns whether this renderable is enabled
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isEnabled(): bool;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\Renderable;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\ExpressionLanguage\Resolver;
|
||||
use TYPO3\CMS\Form\Domain\Exception\IdentifierNotValidException;
|
||||
|
||||
/**
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
* @internal
|
||||
*/
|
||||
class RenderableVariant implements RenderableVariantInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $identifier;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* @var VariableRenderableInterface
|
||||
*/
|
||||
protected $renderable;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $condition = '';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $applied = false;
|
||||
|
||||
/**
|
||||
* @param string $identifier
|
||||
* @param array $options
|
||||
* @param VariableRenderableInterface $renderable
|
||||
* @throws IdentifierNotValidException
|
||||
*/
|
||||
public function __construct(
|
||||
string $identifier,
|
||||
array $options,
|
||||
VariableRenderableInterface $renderable
|
||||
) {
|
||||
if ('' === $identifier) {
|
||||
throw new IdentifierNotValidException('The given variant identifier was empty.', 1519998923);
|
||||
}
|
||||
$this->identifier = $identifier;
|
||||
$this->renderable = $renderable;
|
||||
|
||||
if (isset($options['condition']) && is_string($options['condition'])) {
|
||||
$this->condition = $options['condition'];
|
||||
}
|
||||
|
||||
unset($options['condition'], $options['identifier'], $options['variants']);
|
||||
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the specified variant to this form element
|
||||
* regardless of their conditions
|
||||
*/
|
||||
public function apply(): void
|
||||
{
|
||||
$this->renderable->setOptions($this->options, true);
|
||||
$this->applied = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Resolver $conditionResolver
|
||||
* @return bool
|
||||
*/
|
||||
public function conditionMatches(Resolver $conditionResolver): bool
|
||||
{
|
||||
if (empty($this->condition)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $conditionResolver->evaluate($this->condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return $this->identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isApplied(): bool
|
||||
{
|
||||
return $this->applied;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\Renderable;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Core\ExpressionLanguage\Resolver;
|
||||
|
||||
/**
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
* @internal
|
||||
*/
|
||||
interface RenderableVariantInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIdentifier(): string;
|
||||
|
||||
/**
|
||||
* Apply the specified variant to this form element
|
||||
* regardless of their conditions
|
||||
*/
|
||||
public function apply(): void;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isApplied(): bool;
|
||||
|
||||
/**
|
||||
* @param Resolver $conditionResolver
|
||||
* @return bool
|
||||
*/
|
||||
public function conditionMatches(Resolver $conditionResolver): bool;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\Renderable;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base interface which all parts of a form must adhere to.
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
*/
|
||||
interface RootRenderableInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Abstract "type" of this Renderable. Is used during the rendering process
|
||||
* to determine the template file or the View PHP class being used to render
|
||||
* the particular element.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string;
|
||||
|
||||
/**
|
||||
* The identifier of this renderable
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIdentifier(): string;
|
||||
|
||||
/**
|
||||
* Get the label which shall be displayed next to the form element
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLabel(): string;
|
||||
|
||||
/**
|
||||
* Get the renderer class name to be used to display this form;
|
||||
* must implement RendererInterface
|
||||
*
|
||||
* @return string the renderer class name
|
||||
*/
|
||||
public function getRendererClassName(): string;
|
||||
|
||||
/**
|
||||
* Get all rendering options
|
||||
*
|
||||
* @return array associative array of rendering options
|
||||
*/
|
||||
public function getRenderingOptions(): array;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Model\Renderable;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
/**
|
||||
* Scope: frontend
|
||||
* **This class is NOT meant to be sub classed by developers.**
|
||||
*/
|
||||
interface VariableRenderableInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* Set multiple properties of this object at once.
|
||||
* Every property which has a corresponding set* method can be set using
|
||||
* the passed $options array.
|
||||
*
|
||||
* @param array $options
|
||||
* @param bool $reset
|
||||
* @internal
|
||||
*/
|
||||
public function setOptions(array $options, bool $reset = false);
|
||||
|
||||
/**
|
||||
* Get all rendering variants
|
||||
*
|
||||
* @return RenderableVariantInterface[]
|
||||
*/
|
||||
public function getVariants(): array;
|
||||
|
||||
/**
|
||||
* Adds the specified variant to this form element
|
||||
*
|
||||
* @param RenderableVariantInterface $variant
|
||||
*/
|
||||
public function addVariant(RenderableVariantInterface $variant);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
namespace TYPO3\CMS\Form\Domain\Renderer;
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It originated from the Neos.Form package (www.neos.io)
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
|
||||
|
||||
/**
|
||||
* Abstract renderer which can be used as base class for custom renderers.
|
||||
*
|
||||
* Scope: frontend
|
||||
* **This class is meant to be sub classed by developers**.
|
||||
*/
|
||||
abstract class AbstractElementRenderer implements RendererInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* The assigned controller context which might be needed by the renderer.
|
||||
*
|
||||
* @var \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext
|
||||
*/
|
||||
protected $controllerContext;
|
||||
|
||||
/**
|
||||
* @var \TYPO3\CMS\Form\Domain\Runtime\FormRuntime
|
||||
*/
|
||||
protected $formRuntime;
|
||||
|
||||
/**
|
||||
* Set the controller context which should be used
|
||||
*
|
||||
* @param \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext
|
||||
*/
|
||||
public function setControllerContext(\TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext)
|
||||
{
|
||||
$this->controllerContext = $controllerContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FormRuntime $formRuntime
|
||||
*/
|
||||
public function setFormRuntime(FormRuntime $formRuntime)
|
||||
{
|
||||
$this->formRuntime = $formRuntime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FormRuntime
|
||||
*/
|
||||
public function getFormRuntime(): FormRuntime
|
||||
{
|
||||
return $this->formRuntime;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 12716 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user