[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\Localization\LanguageService;
27 use TYPO3\CMS\Core\Messaging\AbstractMessage;
28 use TYPO3\CMS\Core\Page\PageRenderer;
29 use TYPO3\CMS\Core\Utility\ArrayUtility;
30 use TYPO3\CMS\Core\Utility\GeneralUtility;
31 use TYPO3\CMS\Extbase\Mvc\View\JsonView;
32 use TYPO3\CMS\Form\Exception as FormException;
33 use TYPO3\CMS\Form\Service\TranslationService;
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 // TODO: deprecated since TYPO3 v9, will be removed in TYPO3 v10
239 if ($folder->getCombinedIdentifier() === '1:/user_upload/') {
240 continue;
241 }
242
243 $preparedAccessibleFormStorageFolders[] = [
244 'label' => $folder->getName(),
245 'value' => $identifier
246 ];
247 }
248
249 if ($this->formSettings['persistenceManager']['allowSaveToExtensionPaths']) {
250 foreach ($this->formPersistenceManager->getAccessibleExtensionFolders() as $relativePath => $fullPath) {
251 $preparedAccessibleFormStorageFolders[] = [
252 'label' => $relativePath,
253 'value' => $relativePath
254 ];
255 }
256 }
257
258 return $preparedAccessibleFormStorageFolders;
259 }
260
261 /**
262 * Returns the json encoded data which is used by the form editor
263 * JavaScript app.
264 *
265 * @return string
266 */
267 protected function getFormManagerAppInitialData(): string
268 {
269 $formManagerAppInitialData = [
270 'selectablePrototypesConfiguration' => $this->formSettings['formManager']['selectablePrototypesConfiguration'],
271 'accessibleFormStorageFolders' => $this->getAccessibleFormStorageFolders(),
272 'endpoints' => [
273 'create' => $this->controllerContext->getUriBuilder()->uriFor('create'),
274 'duplicate' => $this->controllerContext->getUriBuilder()->uriFor('duplicate'),
275 'delete' => $this->controllerContext->getUriBuilder()->uriFor('delete'),
276 'references' => $this->controllerContext->getUriBuilder()->uriFor('references')
277 ],
278 ];
279
280 $formManagerAppInitialData = ArrayUtility::reIndexNumericArrayKeysRecursive($formManagerAppInitialData);
281 $formManagerAppInitialData = TranslationService::getInstance()->translateValuesRecursive(
282 $formManagerAppInitialData,
283 $this->formSettings['formManager']['translationFile']
284 );
285 return json_encode($formManagerAppInitialData);
286 }
287
288 /**
289 * List all formDefinitions which can be loaded through t form persistence
290 * manager. Enrich this data by a reference counter.
291 * @return array
292 */
293 protected function getAvailableFormDefinitions(): array
294 {
295 $availableFormDefinitions = [];
296 foreach ($this->formPersistenceManager->listForms() as $formDefinition) {
297 $referenceCount = count($this->getReferences($formDefinition['persistenceIdentifier']));
298 $formDefinition['referenceCount'] = $referenceCount;
299 $availableFormDefinitions[] = $formDefinition;
300 }
301 return $availableFormDefinitions;
302 }
303
304 /**
305 * Returns an array with informations about the references for a
306 * formDefinition identified by $persistenceIdentifier.
307 *
308 * @param string $persistenceIdentifier
309 * @return array
310 * @throws \InvalidArgumentException
311 */
312 protected function getProcessedReferencesRows(string $persistenceIdentifier): array
313 {
314 if (empty($persistenceIdentifier)) {
315 throw new \InvalidArgumentException('$persistenceIdentifier must not be empty.', 1477071939);
316 }
317
318 $references = [];
319 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
320
321 $referenceRows = $this->getReferences($persistenceIdentifier);
322 foreach ($referenceRows as &$referenceRow) {
323 $record = $this->getRecord($referenceRow['tablename'], $referenceRow['recuid']);
324 if (!$record) {
325 continue;
326 }
327 $pageRecord = $this->getRecord('pages', $record['pid']);
328 $urlParameters = [
329 'edit' => [
330 $referenceRow['tablename'] => [
331 $referenceRow['recuid'] => 'edit'
332 ]
333 ],
334 'returnUrl' => $this->getModuleUrl('web_FormFormbuilder')
335 ];
336
337 $references[] = [
338 'recordPageTitle' => is_array($pageRecord) ? $this->getRecordTitle('pages', $pageRecord) : '',
339 'recordTitle' => $this->getRecordTitle($referenceRow['tablename'], $record, true),
340 'recordIcon' => $iconFactory->getIconForRecord($referenceRow['tablename'], $record, Icon::SIZE_SMALL)->render(),
341 'recordUid' => $referenceRow['recuid'],
342 'recordEditUrl' => $this->getModuleUrl('record_edit', $urlParameters),
343 ];
344 }
345 return $references;
346 }
347
348 /**
349 * Returns an array with all sys_refindex database rows which be
350 * connected to a formDefinition identified by $persistenceIdentifier
351 *
352 * @param string $persistenceIdentifier
353 * @return array
354 * @throws \InvalidArgumentException
355 */
356 protected function getReferences(string $persistenceIdentifier): array
357 {
358 if (empty($persistenceIdentifier)) {
359 throw new \InvalidArgumentException('$persistenceIdentifier must not be empty.', 1472238493);
360 }
361
362 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_refindex');
363 $referenceRows = $queryBuilder
364 ->select('*')
365 ->from('sys_refindex')
366 ->where(
367 $queryBuilder->expr()->eq('deleted', 0),
368 $queryBuilder->expr()->eq('softref_key', $queryBuilder->createNamedParameter('formPersistenceIdentifier', \PDO::PARAM_STR)),
369 $queryBuilder->expr()->eq('ref_string', $queryBuilder->createNamedParameter($persistenceIdentifier, \PDO::PARAM_STR)),
370 $queryBuilder->expr()->eq('tablename', $queryBuilder->createNamedParameter('tt_content', \PDO::PARAM_STR))
371 )
372 ->execute()
373 ->fetchAll();
374 return $referenceRows;
375 }
376
377 /**
378 * Check if a given $templatePath for a given $prototypeName is valid
379 * and accessible.
380 *
381 * Valid template paths has to be configured within
382 * TYPO3.CMS.Form.formManager.selectablePrototypesConfiguration.[('identifier': $prototypeName)].newFormTemplates.[('templatePath': $templatePath)]
383 *
384 * @param string $prototypeName
385 * @param string $templatePath
386 * @return bool
387 */
388 protected function isValidTemplatePath(string $prototypeName, string $templatePath): bool
389 {
390 $isValid = false;
391 foreach ($this->formSettings['formManager']['selectablePrototypesConfiguration'] as $prototypesConfiguration) {
392 if ($prototypesConfiguration['identifier'] !== $prototypeName) {
393 continue;
394 }
395 foreach ($prototypesConfiguration['newFormTemplates'] as $templatesConfiguration) {
396 if ($templatesConfiguration['templatePath'] !== $templatePath) {
397 continue;
398 }
399 $isValid = true;
400 break;
401 }
402 }
403
404 $templatePath = GeneralUtility::getFileAbsFileName($templatePath);
405 if (!is_file($templatePath)) {
406 $isValid = false;
407 }
408
409 return $isValid;
410 }
411
412 /**
413 * Registers the Icons into the docheader
414 *
415 * @throws \InvalidArgumentException
416 */
417 protected function registerDocheaderButtons()
418 {
419 /** @var ButtonBar $buttonBar */
420 $buttonBar = $this->view->getModuleTemplate()->getDocHeaderComponent()->getButtonBar();
421 $currentRequest = $this->request;
422 $moduleName = $currentRequest->getPluginName();
423 $getVars = $this->request->getArguments();
424
425 $mayMakeShortcut = $this->getBackendUser()->mayMakeShortcut();
426 if ($mayMakeShortcut) {
427 $extensionName = $currentRequest->getControllerExtensionName();
428 if (count($getVars) === 0) {
429 $modulePrefix = strtolower('tx_' . $extensionName . '_' . $moduleName);
430 $getVars = ['id', 'route', $modulePrefix];
431 }
432
433 $shortcutButton = $buttonBar->makeShortcutButton()
434 ->setModuleName($moduleName)
435 ->setDisplayName($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:module.shortcut_name'))
436 ->setGetVariables($getVars);
437 $buttonBar->addButton($shortcutButton);
438 }
439
440 if (isset($getVars['action']) && $getVars['action'] !== 'index') {
441 $backButton = $buttonBar->makeLinkButton()
442 ->setTitle($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:back'))
443 ->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-view-go-up', Icon::SIZE_SMALL))
444 ->setHref($this->getModuleUrl($moduleName));
445 $buttonBar->addButton($backButton);
446 } else {
447 $addFormButton = $buttonBar->makeLinkButton()
448 ->setDataAttributes(['identifier' => 'newForm'])
449 ->setHref('#')
450 ->setTitle($this->getLanguageService()->sL('LLL:EXT:form/Resources/Private/Language/Database.xlf:formManager.create_new_form'))
451 ->setIcon($this->view->getModuleTemplate()->getIconFactory()->getIcon('actions-add', Icon::SIZE_SMALL));
452 $buttonBar->addButton($addFormButton, ButtonBar::BUTTON_POSITION_LEFT);
453 }
454 }
455
456 /**
457 * Returns a form identifier which is the lower cased form name.
458 *
459 * @param string $formName
460 * @return string
461 */
462 protected function convertFormNameToIdentifier(string $formName): string
463 {
464 $formIdentifier = preg_replace('/[^a-zA-Z0-9-_]/', '', $formName);
465 $formIdentifier = lcfirst($formIdentifier);
466 return $formIdentifier;
467 }
468
469 /**
470 * Wrapper used for unit testing.
471 *
472 * @param string $table
473 * @param int $uid
474 * @return array|null
475 */
476 protected function getRecord(string $table, int $uid)
477 {
478 return BackendUtility::getRecord($table, $uid);
479 }
480
481 /**
482 * Wrapper used for unit testing.
483 *
484 * @param string $table
485 * @param array $row
486 * @param bool $prep
487 * @return string
488 */
489 protected function getRecordTitle(string $table, array $row, bool $prep = false): string
490 {
491 return BackendUtility::getRecordTitle($table, $row, $prep);
492 }
493
494 /**
495 * Wrapper used for unit testing.
496 *
497 * @param string $moduleName
498 * @param array $urlParameters
499 * @return string
500 */
501 protected function getModuleUrl(string $moduleName, array $urlParameters = []): string
502 {
503 return BackendUtility::getModuleUrl($moduleName, $urlParameters);
504 }
505
506 /**
507 * Returns the current BE user.
508 *
509 * @return BackendUserAuthentication
510 */
511 protected function getBackendUser(): BackendUserAuthentication
512 {
513 return $GLOBALS['BE_USER'];
514 }
515
516 /**
517 * Returns the Language Service
518 *
519 * @return LanguageService
520 */
521 protected function getLanguageService(): LanguageService
522 {
523 return $GLOBALS['LANG'];
524 }
525
526 /**
527 * Returns the page renderer
528 *
529 * @return PageRenderer
530 */
531 protected function getPageRenderer(): PageRenderer
532 {
533 return GeneralUtility::makeInstance(PageRenderer::class);
534 }
535 }