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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user