first commit

This commit is contained in:
salam committed 2026-10-19 08:18:39 +02:00
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);
}
}