2 declare(strict_types
=1);
3 namespace TYPO3\CMS\Form\Controller
;
6 * This file is part of the TYPO3 CMS project.
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
15 * The TYPO3 project - inspiring people to share!
18 use Symfony\Component\Yaml\Yaml
;
19 use TYPO3\CMS\Backend\Template\Components\ButtonBar
;
20 use TYPO3\CMS\Backend\Utility\BackendUtility
;
21 use TYPO3\CMS\Backend\View\BackendTemplateView
;
22 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication
;
23 use TYPO3\CMS\Core\Database\ConnectionPool
;
24 use TYPO3\CMS\Core\Imaging\Icon
;
25 use TYPO3\CMS\Core\Imaging\IconFactory
;
26 use TYPO3\CMS\Core\Messaging\AbstractMessage
;
27 use TYPO3\CMS\Core\Page\PageRenderer
;
28 use TYPO3\CMS\Core\Utility\ArrayUtility
;
29 use TYPO3\CMS\Core\Utility\GeneralUtility
;
30 use TYPO3\CMS\Extbase\Mvc\View\JsonView
;
31 use TYPO3\CMS\Form\Exception
as FormException
;
32 use TYPO3\CMS\Form\Service\TranslationService
;
33 use TYPO3\CMS\Lang\LanguageService
;
36 * The form manager controller
40 class FormManagerController
extends AbstractBackendController
44 * Default View Container
46 * @var BackendTemplateView
48 protected $defaultViewObjectName = BackendTemplateView
::class;
51 * Initialize the references action.
52 * This action use the Fluid JsonView::class as view.
56 public function initializeReferencesAction()
58 $this->defaultViewObjectName
= JsonView
::class;
62 * Displays the Form Manager
66 public function indexAction()
68 $this->registerDocheaderButtons();
69 $this->view
->getModuleTemplate()->setModuleName($this->request
->getPluginName() . '_' . $this->request
->getControllerName());
70 $this->view
->getModuleTemplate()->setFlashMessageQueue($this->controllerContext
->getFlashMessageQueue());
72 $this->view
->assign('forms', $this->getAvailableFormDefinitions());
73 $this->view
->assign('stylesheets', $this->resolveResourcePaths($this->formSettings
['formManager']['stylesheets']));
74 $this->view
->assign('dynamicRequireJsModules', $this->formSettings
['formManager']['dynamicRequireJsModules']);
75 $this->view
->assign('formManagerAppInitialData', $this->getFormManagerAppInitialData());
76 if (!empty($this->formSettings
['formManager']['javaScriptTranslationFile'])) {
77 $this->getPageRenderer()->addInlineLanguageLabelFile($this->formSettings
['formManager']['javaScriptTranslationFile']);
82 * Creates a new Form and redirects to the Form Editor
84 * @param string $formName
85 * @param string $templatePath
86 * @param string $prototypeName
87 * @param string $savePath
89 * @throws FormException
92 public function createAction(string $formName, string $templatePath, string $prototypeName, string $savePath): string
94 if (!$this->isValidTemplatePath($prototypeName, $templatePath)) {
95 throw new FormException(sprintf('The template path "%s" is not allowed', $templatePath), 1329233410);
97 if (empty($formName)) {
98 throw new FormException(sprintf('No form name', $templatePath), 1472312204);
101 $templatePath = GeneralUtility
::getFileAbsFileName($templatePath);
102 $form = Yaml
::parse(file_get_contents($templatePath));
103 $form['label'] = $formName;
104 $form['identifier'] = $this->formPersistenceManager
->getUniqueIdentifier($this->convertFormNameToIdentifier($formName));
105 $form['prototypeName'] = $prototypeName;
107 $formPersistenceIdentifier = $this->formPersistenceManager
->getUniquePersistenceIdentifier($form['identifier'], $savePath);
110 isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormCreate'])
111 && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormCreate'])
113 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormCreate'] as $className) {
114 $hookObj = GeneralUtility
::makeInstance($className);
115 if (method_exists($hookObj, 'beforeFormCreate')) {
116 $form = $hookObj->beforeFormCreate(
117 $formPersistenceIdentifier,
124 $this->formPersistenceManager
->save($formPersistenceIdentifier, $form);
126 return $this->controllerContext
->getUriBuilder()->uriFor('index', ['formPersistenceIdentifier' => $formPersistenceIdentifier], 'FormEditor');
130 * Duplicates a given formDefinition and redirects to the Form Editor
132 * @param string $formName
133 * @param string $formPersistenceIdentifier persistence identifier of the form to duplicate
134 * @param string $savePath
138 public function duplicateAction(string $formName, string $formPersistenceIdentifier, string $savePath): string
140 $formToDuplicate = $this->formPersistenceManager
->load($formPersistenceIdentifier);
141 $formToDuplicate['label'] = $formName;
142 $formToDuplicate['identifier'] = $this->formPersistenceManager
->getUniqueIdentifier($this->convertFormNameToIdentifier($formName));
144 $formPersistenceIdentifier = $this->formPersistenceManager
->getUniquePersistenceIdentifier($formToDuplicate['identifier'], $savePath);
147 isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormDuplicate'])
148 && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormDuplicate'])
150 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormDuplicate'] as $className) {
151 $hookObj = GeneralUtility
::makeInstance($className);
152 if (method_exists($hookObj, 'beforeFormDuplicate')) {
153 $formToDuplicate = $hookObj->beforeFormDuplicate(
154 $formPersistenceIdentifier,
161 $this->formPersistenceManager
->save($formPersistenceIdentifier, $formToDuplicate);
163 return $this->controllerContext
->getUriBuilder()->uriFor('index', ['formPersistenceIdentifier' => $formPersistenceIdentifier], 'FormEditor');
167 * Show references to this persistence identifier
169 * @param string $formPersistenceIdentifier persistence identifier of the form to duplicate
172 public function referencesAction(string $formPersistenceIdentifier)
174 $this->view
->assign('references', $this->getProcessedReferencesRows($formPersistenceIdentifier));
175 $this->view
->assign('formPersistenceIdentifier', $formPersistenceIdentifier);
176 // referencesAction uses the extbase JsonView::class.
177 // That's why we have to set the view variables in this way.
178 $this->view
->setVariablesToRender([
180 'formPersistenceIdentifier'
185 * Delete a formDefinition identified by the $formPersistenceIdentifier.
187 * @param string $formPersistenceIdentifier persistence identifier to delete
190 public function deleteAction(string $formPersistenceIdentifier)
192 if (empty($this->getReferences($formPersistenceIdentifier))) {
194 isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormDelete'])
195 && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormDelete'])
197 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormDelete'] as $className) {
198 $hookObj = GeneralUtility
::makeInstance($className);
199 if (method_exists($hookObj, 'beforeFormDelete')) {
200 $hookObj->beforeFormDelete(
201 $formPersistenceIdentifier
207 $this->formPersistenceManager
->delete($formPersistenceIdentifier);
209 $controllerConfiguration = TranslationService
::getInstance()->translateValuesRecursive(
210 $this->formSettings
['formManager']['controller'],
211 $this->formSettings
['formManager']['translationFile']
214 $this->addFlashMessage(
215 sprintf($controllerConfiguration['deleteAction']['errorMessage'], $formPersistenceIdentifier),
216 $controllerConfiguration['deleteAction']['errorTitle'],
217 AbstractMessage
::ERROR
,
221 $this->redirect('index');
225 * Return a list of all accessible file mountpoints.
227 * Only registered mountpoints from
228 * TYPO3.CMS.Form.persistenceManager.allowedFileMounts
229 * are listet. This is list will be reduced by the configured
230 * mountpoints for the current backend user.
234 protected function getAccessibleFormStorageFolders(): array
236 $preparedAccessibleFormStorageFolders = [];
237 foreach ($this->formPersistenceManager
->getAccessibleFormStorageFolders() as $identifier => $folder) {
238 $preparedAccessibleFormStorageFolders[] = [
239 'label' => $folder->getName(),
240 'value' => $identifier
244 if ($this->formSettings
['persistenceManager']['allowSaveToExtensionPaths']) {
245 foreach ($this->formPersistenceManager
->getAccessibleExtensionFolders() as $relativePath => $fullPath) {
246 $preparedAccessibleFormStorageFolders[] = [
247 'label' => $relativePath,
248 'value' => $relativePath
253 return $preparedAccessibleFormStorageFolders;
257 * Returns the json encoded data which is used by the form editor
262 protected function getFormManagerAppInitialData(): string
264 $formManagerAppInitialData = [
265 'selectablePrototypesConfiguration' => $this->formSettings
['formManager']['selectablePrototypesConfiguration'],
266 'accessibleFormStorageFolders' => $this->getAccessibleFormStorageFolders(),
268 'create' => $this->controllerContext
->getUriBuilder()->uriFor('create'),
269 'duplicate' => $this->controllerContext
->getUriBuilder()->uriFor('duplicate'),
270 'delete' => $this->controllerContext
->getUriBuilder()->uriFor('delete'),
271 'references' => $this->controllerContext
->getUriBuilder()->uriFor('references')
275 $formManagerAppInitialData = ArrayUtility
::reIndexNumericArrayKeysRecursive($formManagerAppInitialData);
276 $formManagerAppInitialData = TranslationService
::getInstance()->translateValuesRecursive(
277 $formManagerAppInitialData,
278 $this->formSettings
['formManager']['translationFile']
280 return json_encode($formManagerAppInitialData);
284 * List all formDefinitions which can be loaded through t form persistence
285 * manager. Enrich this data by a reference counter.
288 protected function getAvailableFormDefinitions(): array
290 $availableFormDefinitions = [];
291 foreach ($this->formPersistenceManager
->listForms() as $formDefinition) {
292 $referenceCount = count($this->getReferences($formDefinition['persistenceIdentifier']));
293 $formDefinition['referenceCount'] = $referenceCount;
294 $availableFormDefinitions[] = $formDefinition;
296 return $availableFormDefinitions;
300 * Returns an array with informations about the references for a
301 * formDefinition identified by $persistenceIdentifier.
303 * @param string $persistenceIdentifier
305 * @throws \InvalidArgumentException
307 protected function getProcessedReferencesRows(string $persistenceIdentifier): array
309 if (empty($persistenceIdentifier)) {
310 throw new \
InvalidArgumentException('$persistenceIdentifier must not be empty.', 1477071939);
314 $iconFactory = GeneralUtility
::makeInstance(IconFactory
::class);
316 $referenceRows = $this->getReferences($persistenceIdentifier);
317 foreach ($referenceRows as &$referenceRow) {
318 $record = $this->getRecord($referenceRow['tablename'], $referenceRow['recuid']);
322 $pageRecord = $this->getRecord('pages', $record['pid']);
325 $referenceRow['tablename'] => [
326 $referenceRow['recuid'] => 'edit'
329 'returnUrl' => $this->getModuleUrl('web_FormFormbuilder')
333 'recordPageTitle' => is_array($pageRecord) ?
$this->getRecordTitle('pages', $pageRecord) : '',
334 'recordTitle' => $this->getRecordTitle($referenceRow['tablename'], $record, true
),
335 'recordIcon' => $iconFactory->getIconForRecord($referenceRow['tablename'], $record, Icon
::SIZE_SMALL
)->render(),
336 'recordUid' => $referenceRow['recuid'],
337 'recordEditUrl' => $this->getModuleUrl('record_edit', $urlParameters),
344 * Returns an array with all sys_refindex database rows which be
345 * connected to a formDefinition identified by $persistenceIdentifier
347 * @param string $persistenceIdentifier
349 * @throws \InvalidArgumentException
351 protected function getReferences(string $persistenceIdentifier): array
353 if (empty($persistenceIdentifier)) {
354 throw new \
InvalidArgumentException('$persistenceIdentifier must not be empty.', 1472238493);
357 $queryBuilder = GeneralUtility
::makeInstance(ConnectionPool
::class)->getQueryBuilderForTable('sys_refindex');
358 $referenceRows = $queryBuilder
360 ->from('sys_refindex')
362 $queryBuilder->expr()->eq('deleted', 0),
363 $queryBuilder->expr()->eq('softref_key', $queryBuilder->createNamedParameter('formPersistenceIdentifier', \PDO
::PARAM_STR
)),
364 $queryBuilder->expr()->eq('ref_string', $queryBuilder->createNamedParameter($persistenceIdentifier, \PDO
::PARAM_STR
)),
365 $queryBuilder->expr()->eq('tablename', $queryBuilder->createNamedParameter('tt_content', \PDO
::PARAM_STR
))
369 return $referenceRows;
373 * Check if a given $templatePath for a given $prototypeName is valid
376 * Valid template paths has to be configured within
377 * TYPO3.CMS.Form.formManager.selectablePrototypesConfiguration.[('identifier': $prototypeName)].newFormTemplates.[('templatePath': $templatePath)]
379 * @param string $prototypeName
380 * @param string $templatePath
383 protected function isValidTemplatePath(string $prototypeName, string $templatePath): bool
386 foreach ($this->formSettings
['formManager']['selectablePrototypesConfiguration'] as $prototypesConfiguration) {
387 if ($prototypesConfiguration['identifier'] !== $prototypeName) {
390 foreach ($prototypesConfiguration['newFormTemplates'] as $templatesConfiguration) {
391 if ($templatesConfiguration['templatePath'] !== $templatePath) {
399 $templatePath = GeneralUtility
::getFileAbsFileName($templatePath);
400 if (!is_file($templatePath)) {
408 * Registers the Icons into the docheader
410 * @throws \InvalidArgumentException
412 protected function registerDocheaderButtons()
414 /** @var ButtonBar $buttonBar */
415 $buttonBar = $this->view
->getModuleTemplate()->getDocHeaderComponent()->getButtonBar();
416 $currentRequest = $this->request
;
417 $moduleName = $currentRequest->getPluginName();
418 $getVars = $this->request
->getArguments();
420 $mayMakeShortcut = $this->getBackendUser()->mayMakeShortcut();
421 if ($mayMakeShortcut) {
422 $extensionName = $currentRequest->getControllerExtensionName();
423 if (count($getVars) === 0) {
424 $modulePrefix = strtolower('tx_' . $extensionName . '_' . $moduleName);
425 $getVars = ['id', 'M', $modulePrefix];
428 $shortcutButton = $buttonBar->makeShortcutButton()
429 ->setModuleName($moduleName)
430 ->setDisplayName($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:module.shortcut_name'))
431 ->setGetVariables($getVars);
432 $buttonBar->addButton($shortcutButton);
435 if (isset($getVars['action']) && $getVars['action'] !== 'index') {
436 $backButton = $buttonBar->makeLinkButton()
437 ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:back'))
438 ->setIcon($this->view
->getModuleTemplate()->getIconFactory()->getIcon('actions-view-go-up', Icon
::SIZE_SMALL
))
439 ->setHref($this->getModuleUrl($moduleName));
440 $buttonBar->addButton($backButton);
442 $addFormButton = $buttonBar->makeLinkButton()
443 ->setDataAttributes(['identifier' => 'newForm'])
445 ->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formManager.create_new_form'))
446 ->setIcon($this->view
->getModuleTemplate()->getIconFactory()->getIcon('actions-document-new', Icon
::SIZE_SMALL
));
447 $buttonBar->addButton($addFormButton, ButtonBar
::BUTTON_POSITION_LEFT
);
452 * Returns a form identifier which is the lower cased form name.
454 * @param string $formName
457 protected function convertFormNameToIdentifier(string $formName): string
459 $formIdentifier = preg_replace('/[^a-zA-Z0-9-_]/', '', $formName);
460 $formIdentifier = lcfirst($formIdentifier);
461 return $formIdentifier;
465 * Wrapper used for unit testing.
467 * @param string $table
471 protected function getRecord(string $table, int $uid)
473 return BackendUtility
::getRecord($table, $uid);
477 * Wrapper used for unit testing.
479 * @param string $table
484 protected function getRecordTitle(string $table, array $row, bool
$prep = false
): string
486 return BackendUtility
::getRecordTitle($table, $row, $prep);
490 * Wrapper used for unit testing.
492 * @param string $moduleName
493 * @param array $urlParameters
496 protected function getModuleUrl(string $moduleName, array $urlParameters = []): string
498 return BackendUtility
::getModuleUrl($moduleName, $urlParameters);
502 * Returns the current BE user.
504 * @return BackendUserAuthentication
506 protected function getBackendUser(): BackendUserAuthentication
508 return $GLOBALS['BE_USER'];
512 * Returns the Language Service
514 * @return LanguageService
516 protected function getLanguageService(): LanguageService
518 return $GLOBALS['LANG'];
522 * Returns the page renderer
524 * @return PageRenderer
526 protected function getPageRenderer(): PageRenderer
528 return GeneralUtility
::makeInstance(PageRenderer
::class);