[CLEANUP] The correct case must be used for standard PHP types in phpdoc
[Packages/TYPO3.CMS.git] / typo3 / sysext / form / Classes / Controller / FormManagerController.php
1 <?php
2 declare(strict_types=1);
3 namespace TYPO3\CMS\Form\Controller;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
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.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
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;
34
35 /**
36 * The form manager controller
37 *
38 * Scope: backend
39 */
40 class FormManagerController extends AbstractBackendController
41 {
42
43 /**
44 * Default View Container
45 *
46 * @var BackendTemplateView
47 */
48 protected $defaultViewObjectName = BackendTemplateView::class;
49
50 /**
51 * Initialize the references action.
52 * This action use the Fluid JsonView::class as view.
53 *
54 * @internal
55 */
56 public function initializeReferencesAction()
57 {
58 $this->defaultViewObjectName = JsonView::class;
59 }
60
61 /**
62 * Displays the Form Manager
63 *
64 * @internal
65 */
66 public function indexAction()
67 {
68 $this->registerDocheaderButtons();
69 $this->view->getModuleTemplate()->setModuleName($this->request->getPluginName() . '_' . $this->request->getControllerName());
70 $this->view->getModuleTemplate()->setFlashMessageQueue($this->controllerContext->getFlashMessageQueue());
71
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']);
78 }
79 }
80
81 /**
82 * Creates a new Form and redirects to the Form Editor
83 *
84 * @param string $formName
85 * @param string $templatePath
86 * @param string $prototypeName
87 * @param string $savePath
88 * @return string
89 * @throws FormException
90 * @internal
91 */
92 public function createAction(string $formName, string $templatePath, string $prototypeName, string $savePath): string
93 {
94 if (!$this->isValidTemplatePath($prototypeName, $templatePath)) {
95 throw new FormException(sprintf('The template path "%s" is not allowed', $templatePath), 1329233410);
96 }
97 if (empty($formName)) {
98 throw new FormException(sprintf('No form name', $templatePath), 1472312204);
99 }
100
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;
106
107 $formPersistenceIdentifier = $this->formPersistenceManager->getUniquePersistenceIdentifier($form['identifier'], $savePath);
108
109 if (
110 isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormCreate'])
111 && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormCreate'])
112 ) {
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,
118 $form
119 );
120 }
121 }
122 }
123
124 $this->formPersistenceManager->save($formPersistenceIdentifier, $form);
125
126 return $this->controllerContext->getUriBuilder()->uriFor('index', ['formPersistenceIdentifier' => $formPersistenceIdentifier], 'FormEditor');
127 }
128
129 /**
130 * Duplicates a given formDefinition and redirects to the Form Editor
131 *
132 * @param string $formName
133 * @param string $formPersistenceIdentifier persistence identifier of the form to duplicate
134 * @param string $savePath
135 * @return string
136 * @internal
137 */
138 public function duplicateAction(string $formName, string $formPersistenceIdentifier, string $savePath): string
139 {
140 $formToDuplicate = $this->formPersistenceManager->load($formPersistenceIdentifier);
141 $formToDuplicate['label'] = $formName;
142 $formToDuplicate['identifier'] = $this->formPersistenceManager->getUniqueIdentifier($this->convertFormNameToIdentifier($formName));
143
144 $formPersistenceIdentifier = $this->formPersistenceManager->getUniquePersistenceIdentifier($formToDuplicate['identifier'], $savePath);
145
146 if (
147 isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormDuplicate'])
148 && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormDuplicate'])
149 ) {
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,
155 $formToDuplicate
156 );
157 }
158 }
159 }
160
161 $this->formPersistenceManager->save($formPersistenceIdentifier, $formToDuplicate);
162
163 return $this->controllerContext->getUriBuilder()->uriFor('index', ['formPersistenceIdentifier' => $formPersistenceIdentifier], 'FormEditor');
164 }
165
166 /**
167 * Show references to this persistence identifier
168 *
169 * @param string $formPersistenceIdentifier persistence identifier of the form to duplicate
170 * @internal
171 */
172 public function referencesAction(string $formPersistenceIdentifier)
173 {
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([
179 'references',
180 'formPersistenceIdentifier'
181 ]);
182 }
183
184 /**
185 * Delete a formDefinition identified by the $formPersistenceIdentifier.
186 *
187 * @param string $formPersistenceIdentifier persistence identifier to delete
188 * @internal
189 */
190 public function deleteAction(string $formPersistenceIdentifier)
191 {
192 if (empty($this->getReferences($formPersistenceIdentifier))) {
193 if (
194 isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormDelete'])
195 && is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['beforeFormDelete'])
196 ) {
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
202 );
203 }
204 }
205 }
206
207 $this->formPersistenceManager->delete($formPersistenceIdentifier);
208 } else {
209 $controllerConfiguration = TranslationService::getInstance()->translateValuesRecursive(
210 $this->formSettings['formManager']['controller'],
211 $this->formSettings['formManager']['translationFile']
212 );
213
214 $this->addFlashMessage(
215 sprintf($controllerConfiguration['deleteAction']['errorMessage'], $formPersistenceIdentifier),
216 $controllerConfiguration['deleteAction']['errorTitle'],
217 AbstractMessage::ERROR,
218 true
219 );
220 }
221 $this->redirect('index');
222 }
223
224 /**
225 * Return a list of all accessible file mountpoints.
226 *
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.
231 *
232 * @return array
233 */
234 protected function getAccessibleFormStorageFolders(): array
235 {
236 $preparedAccessibleFormStorageFolders = [];
237 foreach ($this->formPersistenceManager->getAccessibleFormStorageFolders() as $identifier => $folder) {
238 $preparedAccessibleFormStorageFolders[] = [
239 'label' => $folder->getName(),
240 'value' => $identifier
241 ];
242 }
243
244 if ($this->formSettings['persistenceManager']['allowSaveToExtensionPaths']) {
245 foreach ($this->formPersistenceManager->getAccessibleExtensionFolders() as $relativePath => $fullPath) {
246 $preparedAccessibleFormStorageFolders[] = [
247 'label' => $relativePath,
248 'value' => $relativePath
249 ];
250 }
251 }
252
253 return $preparedAccessibleFormStorageFolders;
254 }
255
256 /**
257 * Returns the json encoded data which is used by the form editor
258 * JavaScript app.
259 *
260 * @return string
261 */
262 protected function getFormManagerAppInitialData(): string
263 {
264 $formManagerAppInitialData = [
265 'selectablePrototypesConfiguration' => $this->formSettings['formManager']['selectablePrototypesConfiguration'],
266 'accessibleFormStorageFolders' => $this->getAccessibleFormStorageFolders(),
267 'endpoints' => [
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')
272 ],
273 ];
274
275 $formManagerAppInitialData = ArrayUtility::reIndexNumericArrayKeysRecursive($formManagerAppInitialData);
276 $formManagerAppInitialData = TranslationService::getInstance()->translateValuesRecursive(
277 $formManagerAppInitialData,
278 $this->formSettings['formManager']['translationFile']
279 );
280 return json_encode($formManagerAppInitialData);
281 }
282
283 /**
284 * List all formDefinitions which can be loaded through t form persistence
285 * manager. Enrich this data by a reference counter.
286 * @return array
287 */
288 protected function getAvailableFormDefinitions(): array
289 {
290 $availableFormDefinitions = [];
291 foreach ($this->formPersistenceManager->listForms() as $formDefinition) {
292 $referenceCount = count($this->getReferences($formDefinition['persistenceIdentifier']));
293 $formDefinition['referenceCount'] = $referenceCount;
294 $availableFormDefinitions[] = $formDefinition;
295 }
296 return $availableFormDefinitions;
297 }
298
299 /**
300 * Returns an array with informations about the references for a
301 * formDefinition identified by $persistenceIdentifier.
302 *
303 * @param string $persistenceIdentifier
304 * @return array
305 * @throws \InvalidArgumentException
306 */
307 protected function getProcessedReferencesRows(string $persistenceIdentifier): array
308 {
309 if (empty($persistenceIdentifier)) {
310 throw new \InvalidArgumentException('$persistenceIdentifier must not be empty.', 1477071939);
311 }
312
313 $references = [];
314 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
315
316 $referenceRows = $this->getReferences($persistenceIdentifier);
317 foreach ($referenceRows as &$referenceRow) {
318 $record = $this->getRecord($referenceRow['tablename'], $referenceRow['recuid']);
319 if (!$record) {
320 continue;
321 }
322 $pageRecord = $this->getRecord('pages', $record['pid']);
323 $urlParameters = [
324 'edit' => [
325 $referenceRow['tablename'] => [
326 $referenceRow['recuid'] => 'edit'
327 ]
328 ],
329 'returnUrl' => $this->getModuleUrl('web_FormFormbuilder')
330 ];
331
332 $references[] = [
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),
338 ];
339 }
340 return $references;
341 }
342
343 /**
344 * Returns an array with all sys_refindex database rows which be
345 * connected to a formDefinition identified by $persistenceIdentifier
346 *
347 * @param string $persistenceIdentifier
348 * @return array
349 * @throws \InvalidArgumentException
350 */
351 protected function getReferences(string $persistenceIdentifier): array
352 {
353 if (empty($persistenceIdentifier)) {
354 throw new \InvalidArgumentException('$persistenceIdentifier must not be empty.', 1472238493);
355 }
356
357 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
358 $referenceRows = $queryBuilder
359 ->select('*')
360 ->from('sys_refindex')
361 ->where(
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))
366 )
367 ->execute()
368 ->fetchAll();
369 return $referenceRows;
370 }
371
372 /**
373 * Check if a given $templatePath for a given $prototypeName is valid
374 * and accessible.
375 *
376 * Valid template paths has to be configured within
377 * TYPO3.CMS.Form.formManager.selectablePrototypesConfiguration.[('identifier': $prototypeName)].newFormTemplates.[('templatePath': $templatePath)]
378 *
379 * @param string $prototypeName
380 * @param string $templatePath
381 * @return bool
382 */
383 protected function isValidTemplatePath(string $prototypeName, string $templatePath): bool
384 {
385 $isValid = false;
386 foreach ($this->formSettings['formManager']['selectablePrototypesConfiguration'] as $prototypesConfiguration) {
387 if ($prototypesConfiguration['identifier'] !== $prototypeName) {
388 continue;
389 }
390 foreach ($prototypesConfiguration['newFormTemplates'] as $templatesConfiguration) {
391 if ($templatesConfiguration['templatePath'] !== $templatePath) {
392 continue;
393 }
394 $isValid = true;
395 break;
396 }
397 }
398
399 $templatePath = GeneralUtility::getFileAbsFileName($templatePath);
400 if (!is_file($templatePath)) {
401 $isValid = false;
402 }
403
404 return $isValid;
405 }
406
407 /**
408 * Registers the Icons into the docheader
409 *
410 * @throws \InvalidArgumentException
411 */
412 protected function registerDocheaderButtons()
413 {
414 /** @var ButtonBar $buttonBar */
415 $buttonBar = $this->view->getModuleTemplate()->getDocHeaderComponent()->getButtonBar();
416 $currentRequest = $this->request;
417 $moduleName = $currentRequest->getPluginName();
418 $getVars = $this->request->getArguments();
419
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];
426 }
427
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);
433 }
434
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);
441 } else {
442 $addFormButton = $buttonBar->makeLinkButton()
443 ->setDataAttributes(['identifier' => 'newForm'])
444 ->setHref('#')
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);
448 }
449 }
450
451 /**
452 * Returns a form identifier which is the lower cased form name.
453 *
454 * @param string $formName
455 * @return string
456 */
457 protected function convertFormNameToIdentifier(string $formName): string
458 {
459 $formIdentifier = preg_replace('/[^a-zA-Z0-9-_]/', '', $formName);
460 $formIdentifier = lcfirst($formIdentifier);
461 return $formIdentifier;
462 }
463
464 /**
465 * Wrapper used for unit testing.
466 *
467 * @param string $table
468 * @param int $uid
469 * @return array|null
470 */
471 protected function getRecord(string $table, int $uid)
472 {
473 return BackendUtility::getRecord($table, $uid);
474 }
475
476 /**
477 * Wrapper used for unit testing.
478 *
479 * @param string $table
480 * @param array $row
481 * @param bool $prep
482 * @return string
483 */
484 protected function getRecordTitle(string $table, array $row, bool $prep = false): string
485 {
486 return BackendUtility::getRecordTitle($table, $row, $prep);
487 }
488
489 /**
490 * Wrapper used for unit testing.
491 *
492 * @param string $moduleName
493 * @param array $urlParameters
494 * @return string
495 */
496 protected function getModuleUrl(string $moduleName, array $urlParameters = []): string
497 {
498 return BackendUtility::getModuleUrl($moduleName, $urlParameters);
499 }
500
501 /**
502 * Returns the current BE user.
503 *
504 * @return BackendUserAuthentication
505 */
506 protected function getBackendUser(): BackendUserAuthentication
507 {
508 return $GLOBALS['BE_USER'];
509 }
510
511 /**
512 * Returns the Language Service
513 *
514 * @return LanguageService
515 */
516 protected function getLanguageService(): LanguageService
517 {
518 return $GLOBALS['LANG'];
519 }
520
521 /**
522 * Returns the page renderer
523 *
524 * @return PageRenderer
525 */
526 protected function getPageRenderer(): PageRenderer
527 {
528 return GeneralUtility::makeInstance(PageRenderer::class);
529 }
530 }