‪TYPO3CMS  ‪main
FormPersistenceManager.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
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 /*
19  * Inspired by and partially taken from the Neos.Form package (www.neos.io)
20  */
21 
23 
24 use Psr\EventDispatcher\EventDispatcherInterface;
25 use Psr\Http\Message\ServerRequestInterface;
26 use Symfony\Component\Yaml\Exception\ParseException;
27 use Symfony\Component\Yaml\Yaml;
55 
62 {
63  public const ‪FORM_DEFINITION_FILE_EXTENSION = '.form.yaml';
64 
69  protected array ‪$formSettings;
70  protected array ‪$typoScriptSettings;
72  protected EventDispatcherInterface ‪$eventDispatcher;
73 
74  public function ‪__construct(
79  ‪ConfigurationManagerInterface $configurationManager,
80  ‪CacheManager $cacheManager,
81  EventDispatcherInterface ‪$eventDispatcher,
82  ) {
83  $this->yamlSource = ‪$yamlSource;
84  $this->storageRepository = ‪$storageRepository;
85  $this->filePersistenceSlot = ‪$filePersistenceSlot;
86  $this->resourceFactory = ‪$resourceFactory;
87  $this->eventDispatcher = ‪$eventDispatcher;
88  // @todo: FormPersistenceManager is sometimes triggered via CLI without request (why/where?).
89  // In this case we fake a request so extbase ConfigurationManager still works.
90  // This of course needs to fall! The code below needs to be moved out of __construct()
91  // and must be added to methods that need this. Request then needs to be hand over to
92  // those methods, with $GLOBALS['TYPO3_REQUEST'] being only a b/w compat layer for one
93  // version. If CLI uses this class, it should properly set up and hand over a request.
94  if ((‪$GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface) {
95  $request = ‪$GLOBALS['TYPO3_REQUEST'];
96  } else {
97  $request = (new ‪ServerRequest())->withAttribute('applicationType', ‪SystemEnvironmentBuilder::REQUESTTYPE_BE);
98  $request = $request->withAttribute('normalizedParams', ‪NormalizedParams::createFromRequest($request));
99  }
100  $configurationManager->‪setRequest($request);
101  $this->formSettings = $configurationManager->‪getConfiguration(‪ConfigurationManagerInterface::CONFIGURATION_TYPE_YAML_SETTINGS, 'form');
102  $this->typoScriptSettings = $configurationManager->‪getConfiguration(‪ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS, 'form');
103  $this->runtimeCache = $cacheManager->‪getCache('runtime');
104  }
105 
113  public function ‪load(string $persistenceIdentifier): array
114  {
115  $cacheKey = 'formLoad' . md5($persistenceIdentifier);
116 
117  $yaml = $this->runtimeCache->get($cacheKey);
118  if ($yaml !== false) {
119  return $this->overrideFormDefinition($yaml, $persistenceIdentifier, $cacheKey);
120  }
121 
122  if (‪PathUtility::isExtensionPath($persistenceIdentifier)) {
123  $this->‪ensureValidPersistenceIdentifier($persistenceIdentifier);
124  $file = $persistenceIdentifier;
125  } else {
126  $file = $this->‪retrieveFileByPersistenceIdentifier($persistenceIdentifier);
127  }
128 
129  try {
130  $yaml = $this->yamlSource->load([$file]);
131  $this->‪generateErrorsIfFormDefinitionIsValidButHasInvalidFileExtension($yaml, $persistenceIdentifier);
132  } catch (\‪Exception $e) {
133  $yaml = [
134  'type' => 'Form',
135  'identifier' => $persistenceIdentifier,
136  'label' => $e->getMessage(),
137  'invalid' => true,
138  ];
139  }
140  $this->runtimeCache->set($cacheKey, $yaml);
141 
142  return $this->overrideFormDefinition($yaml, $persistenceIdentifier, $cacheKey);
143  }
144 
156  public function ‪save(string $persistenceIdentifier, array ‪$formDefinition)
157  {
158  if (!$this->‪hasValidFileExtension($persistenceIdentifier)) {
159  throw new ‪PersistenceManagerException(sprintf('The file "%s" could not be saved.', $persistenceIdentifier), 1477679820);
160  }
161 
162  if ($this->‪pathIsIntendedAsExtensionPath($persistenceIdentifier)) {
163  if (!$this->formSettings['persistenceManager']['allowSaveToExtensionPaths']) {
164  throw new ‪PersistenceManagerException('Save to extension paths is not allowed.', 1477680881);
165  }
166  if (!$this->‪isFileWithinAccessibleExtensionFolders($persistenceIdentifier)) {
167  $message = sprintf('The file "%s" could not be saved. Please check your configuration option "persistenceManager.allowedExtensionPaths"', $persistenceIdentifier);
168  throw new ‪PersistenceManagerException($message, 1484073571);
169  }
170  $fileToSave = GeneralUtility::getFileAbsFileName($persistenceIdentifier);
171  } else {
172  $fileToSave = $this->‪getOrCreateFile($persistenceIdentifier);
173  }
174 
175  try {
176  $this->yamlSource->save($fileToSave, ‪$formDefinition);
177  } catch (‪FileWriteException $e) {
178  throw new ‪PersistenceManagerException(sprintf(
179  'The file "%s" could not be saved: %s',
180  $persistenceIdentifier,
181  $e->getMessage()
182  ), 1512582637, $e);
183  }
184  }
185 
193  public function delete(string $persistenceIdentifier)
194  {
195  if (!$this->‪hasValidFileExtension($persistenceIdentifier)) {
196  throw new ‪PersistenceManagerException(sprintf('The file "%s" could not be removed.', $persistenceIdentifier), 1472239534);
197  }
198  if (!$this->‪exists($persistenceIdentifier)) {
199  throw new ‪PersistenceManagerException(sprintf('The file "%s" could not be removed.', $persistenceIdentifier), 1472239535);
200  }
201  if ($this->‪pathIsIntendedAsExtensionPath($persistenceIdentifier)) {
202  if (!$this->formSettings['persistenceManager']['allowDeleteFromExtensionPaths']) {
203  throw new ‪PersistenceManagerException(sprintf('The file "%s" could not be removed.', $persistenceIdentifier), 1472239536);
204  }
205  if (!$this->‪isFileWithinAccessibleExtensionFolders($persistenceIdentifier)) {
206  $message = sprintf('The file "%s" could not be removed. Please check your configuration option "persistenceManager.allowedExtensionPaths"', $persistenceIdentifier);
207  throw new ‪PersistenceManagerException($message, 1484073878);
208  }
209  $fileToDelete = GeneralUtility::getFileAbsFileName($persistenceIdentifier);
210  unlink($fileToDelete);
211  } else {
212  [$storageUid, $fileIdentifier] = explode(':', $persistenceIdentifier, 2);
213  $storage = $this->‪getStorageByUid((int)$storageUid);
214  $file = $storage->getFile($fileIdentifier);
215  if (!$storage->checkFileActionPermission('delete', $file)) {
216  throw new ‪PersistenceManagerException(sprintf('No delete access to file "%s".', $persistenceIdentifier), 1472239516);
217  }
218  $storage->deleteFile($file);
219  }
220  }
221 
228  public function ‪exists(string $persistenceIdentifier): bool
229  {
230  $exists = false;
231  if ($this->‪hasValidFileExtension($persistenceIdentifier)) {
232  if ($this->‪pathIsIntendedAsExtensionPath($persistenceIdentifier)) {
233  if ($this->‪isFileWithinAccessibleExtensionFolders($persistenceIdentifier)) {
234  $exists = file_exists(GeneralUtility::getFileAbsFileName($persistenceIdentifier));
235  }
236  } else {
237  [$storageUid, $fileIdentifier] = explode(':', $persistenceIdentifier, 2);
238  $storage = $this->‪getStorageByUid((int)$storageUid);
239  $exists = $storage->hasFile($fileIdentifier);
240  }
241  }
242  return $exists;
243  }
244 
255  public function ‪listForms(): array
256  {
257  $identifiers = [];
258  $forms = [];
259 
260  foreach ($this->‪retrieveYamlFilesFromStorageFolders() as $file) {
261  $form = $this->‪loadMetaData($file);
262 
263  if (!$this->‪looksLikeAFormDefinition($form)) {
264  continue;
265  }
266 
267  $persistenceIdentifier = $file->getCombinedIdentifier();
268  if ($this->‪hasValidFileExtension($persistenceIdentifier)) {
269  $forms[] = [
270  'identifier' => $form['identifier'],
271  'name' => $form['label'] ?? $form['identifier'],
272  'persistenceIdentifier' => $persistenceIdentifier,
273  'readOnly' => false,
274  'removable' => true,
275  'location' => 'storage',
276  'duplicateIdentifier' => false,
277  'invalid' => $form['invalid'] ?? false,
278  'fileUid' => $form['fileUid'] ?? 0,
279  ];
280  if (!isset($identifiers[$form['identifier']])) {
281  $identifiers[$form['identifier']] = 0;
282  }
283  $identifiers[$form['identifier']]++;
284  }
285  }
286 
287  foreach ($this->‪retrieveYamlFilesFromExtensionFolders() as $file) {
288  $form = $this->‪loadMetaData($file);
289 
290  if ($this->‪looksLikeAFormDefinition($form)) {
291  if ($this->‪hasValidFileExtension($file)) {
292  $forms[] = [
293  'identifier' => $form['identifier'],
294  'name' => $form['label'] ?? $form['identifier'],
295  'persistenceIdentifier' => $file,
296  'readOnly' => $this->formSettings['persistenceManager']['allowSaveToExtensionPaths'] ? false : true,
297  'removable' => $this->formSettings['persistenceManager']['allowDeleteFromExtensionPaths'] ? true : false,
298  'location' => 'extension',
299  'duplicateIdentifier' => false,
300  'invalid' => $form['invalid'] ?? false,
301  'fileUid' => $form['fileUid'] ?? 0,
302  ];
303  if (!isset($identifiers[$form['identifier']])) {
304  $identifiers[$form['identifier']] = 0;
305  }
306  $identifiers[$form['identifier']]++;
307  }
308  }
309  }
310 
311  foreach ($identifiers as ‪$identifier => $count) {
312  if ($count > 1) {
313  foreach ($forms as &‪$formDefinition) {
314  if (‪$formDefinition['identifier'] === ‪$identifier) {
315  ‪$formDefinition['duplicateIdentifier'] = true;
316  }
317  }
318  }
319  }
320 
321  return $this->‪sortForms($forms);
322  }
323 
329  public function ‪hasForms(): bool
330  {
331  foreach ($this->‪retrieveYamlFilesFromStorageFolders() as $file) {
332  $form = $this->‪loadMetaData($file);
333 
334  if ($this->‪looksLikeAFormDefinition($form)) {
335  return true;
336  }
337  }
338  foreach ($this->‪retrieveYamlFilesFromExtensionFolders() as $file) {
339  $form = $this->‪loadMetaData($file);
340 
341  if ($this->‪looksLikeAFormDefinition($form)) {
342  return true;
343  }
344  }
345  return false;
346  }
347 
355  public function ‪retrieveYamlFilesFromStorageFolders(): array
356  {
357  $filesFromStorageFolders = [];
358 
359  $fileExtensionFilter = GeneralUtility::makeInstance(FileExtensionFilter::class);
360  $fileExtensionFilter->setAllowedFileExtensions(['yaml']);
361 
362  foreach ($this->‪getAccessibleFormStorageFolders() as $folder) {
363  $storage = $folder->getStorage();
364  $storage->setFileAndFolderNameFilters([
365  [$fileExtensionFilter, 'filterFileList'],
366  ]);
367 
368  $files = $folder->getFiles(
369  0,
370  0,
372  true
373  );
374  $filesFromStorageFolders = array_merge($filesFromStorageFolders, array_values($files));
375  $storage->resetFileAndFolderNameFiltersToDefault();
376  }
377 
378  return $filesFromStorageFolders;
379  }
380 
389  {
390  $filesFromExtensionFolders = [];
391 
392  foreach ($this->‪getAccessibleExtensionFolders() as $relativePath => $fullPath) {
393  foreach (new \DirectoryIterator($fullPath) as $fileInfo) {
394  if ($fileInfo->getExtension() !== 'yaml') {
395  continue;
396  }
397  $filesFromExtensionFolders[] = $relativePath . $fileInfo->getFilename();
398  }
399  }
400 
401  return $filesFromExtensionFolders;
402  }
403 
415  public function ‪getAccessibleFormStorageFolders(): array
416  {
417  $storageFolders = [];
418 
419  if (
420  !isset($this->formSettings['persistenceManager']['allowedFileMounts'])
421  || !is_array($this->formSettings['persistenceManager']['allowedFileMounts'])
422  || empty($this->formSettings['persistenceManager']['allowedFileMounts'])
423  ) {
424  return $storageFolders;
425  }
426 
427  foreach ($this->formSettings['persistenceManager']['allowedFileMounts'] as $allowedFileMount) {
428  $allowedFileMount = rtrim($allowedFileMount, '/') . '/';
429  // $fileMountPath is like "/form_definitions/" or "/group_homes/1/form_definitions/"
430  [$storageUid, $fileMountPath] = explode(':', $allowedFileMount, 2);
431 
432  try {
433  $storage = $this->‪getStorageByUid((int)$storageUid);
434  } catch (‪PersistenceManagerException $e) {
435  continue;
436  }
437 
438  $isStorageFileMount = false;
439  $parentFolder = $storage->getRootLevelFolder(false);
440 
441  foreach ($storage->getFileMounts() as $storageFileMount) {
443  $storageFileMountFolder = $storageFileMount['folder'];
444 
445  // Normally should use ResourceStorage::isWithinFolder() to check if the configured file mount path is within a storage file mount but this requires a valid Folder object and thus a directory which already exists. And the folder could simply not exist yet.
446  if (str_starts_with($fileMountPath, $storageFileMountFolder->getIdentifier())) {
447  $isStorageFileMount = true;
448  $parentFolder = $storageFileMountFolder;
449  }
450  }
451 
452  // Get storage folder object, create it if missing
453  try {
454  $fileMountFolder = $storage->getFolder($fileMountPath);
456  continue;
457  } catch (‪FolderDoesNotExistException $e) {
458  if ($isStorageFileMount) {
459  $fileMountPath = substr(
460  $fileMountPath,
461  strlen($parentFolder->getIdentifier())
462  );
463  }
464 
465  try {
466  $fileMountFolder = $storage->createFolder($fileMountPath, $parentFolder);
468  continue;
469  }
470  }
471 
472  $storageFolders[$allowedFileMount] = $fileMountFolder;
473  }
474  return $storageFolders;
475  }
476 
486  public function ‪getAccessibleExtensionFolders(): array
487  {
488  $extensionFolders = $this->runtimeCache->get('formAccessibleExtensionFolders');
489 
490  if ($extensionFolders !== false) {
491  return $extensionFolders;
492  }
493 
494  $extensionFolders = [];
495  if (
496  !isset($this->formSettings['persistenceManager']['allowedExtensionPaths'])
497  || !is_array($this->formSettings['persistenceManager']['allowedExtensionPaths'])
498  || empty($this->formSettings['persistenceManager']['allowedExtensionPaths'])
499  ) {
500  $this->runtimeCache->set('formAccessibleExtensionFolders', $extensionFolders);
501  return $extensionFolders;
502  }
503 
504  foreach ($this->formSettings['persistenceManager']['allowedExtensionPaths'] as $allowedExtensionPath) {
505  if (!$this->‪pathIsIntendedAsExtensionPath($allowedExtensionPath)) {
506  continue;
507  }
508 
509  $allowedExtensionFullPath = GeneralUtility::getFileAbsFileName($allowedExtensionPath);
510  if (!file_exists($allowedExtensionFullPath)) {
511  continue;
512  }
513  $allowedExtensionPath = rtrim($allowedExtensionPath, '/') . '/';
514  $extensionFolders[$allowedExtensionPath] = $allowedExtensionFullPath;
515  }
516 
517  $this->runtimeCache->set('formAccessibleExtensionFolders', $extensionFolders);
518  return $extensionFolders;
519  }
520 
531  public function ‪getUniquePersistenceIdentifier(string $formIdentifier, string $savePath): string
532  {
533  $savePath = rtrim($savePath, '/') . '/';
534  $formPersistenceIdentifier = $savePath . $formIdentifier . ‪self::FORM_DEFINITION_FILE_EXTENSION;
535  if (!$this->‪exists($formPersistenceIdentifier)) {
536  return $formPersistenceIdentifier;
537  }
538  for ($attempts = 1; $attempts < 100; $attempts++) {
539  $formPersistenceIdentifier = $savePath . sprintf('%s_%d', $formIdentifier, $attempts) . ‪self::FORM_DEFINITION_FILE_EXTENSION;
540  if (!$this->‪exists($formPersistenceIdentifier)) {
541  return $formPersistenceIdentifier;
542  }
543  }
544  $formPersistenceIdentifier = $savePath . sprintf('%s_%d', $formIdentifier, time()) . ‪self::FORM_DEFINITION_FILE_EXTENSION;
545  if (!$this->‪exists($formPersistenceIdentifier)) {
546  return $formPersistenceIdentifier;
547  }
548 
550  sprintf('Could not find a unique persistence identifier for form identifier "%s" after %d attempts', $formIdentifier, $attempts),
551  1476010403
552  );
553  }
554 
564  public function ‪getUniqueIdentifier(string ‪$identifier): string
565  {
566  $originalIdentifier = ‪$identifier;
567  if ($this->‪checkForDuplicateIdentifier($identifier)) {
568  for ($attempts = 1; $attempts < 100; $attempts++) {
569  ‪$identifier = sprintf('%s_%d', $originalIdentifier, $attempts);
570  if (!$this->‪checkForDuplicateIdentifier($identifier)) {
571  return ‪$identifier;
572  }
573  }
574  ‪$identifier = $originalIdentifier . '_' . time();
575  if ($this->‪checkForDuplicateIdentifier($identifier)) {
577  sprintf('Could not find a unique identifier for form identifier "%s" after %d attempts', ‪$identifier, $attempts),
578  1477688567
579  );
580  }
581  }
582  return ‪$identifier;
583  }
584 
590  public function ‪checkForDuplicateIdentifier(string ‪$identifier): bool
591  {
592  $identifierUsed = false;
593  foreach ($this->‪listForms() as ‪$formDefinition) {
594  if (‪$formDefinition['identifier'] === ‪$identifier) {
595  $identifierUsed = true;
596  break;
597  }
598  }
599  return $identifierUsed;
600  }
601 
611  public function ‪isAllowedPersistencePath(string $persistencePath): bool
612  {
613  $pathinfo = ‪PathUtility::pathinfo($persistencePath);
614  $persistencePathIsFile = isset($pathinfo['extension']);
615 
616  if (
617  $persistencePathIsFile
618  && $this->‪pathIsIntendedAsExtensionPath($persistencePath)
619  && $this->‪hasValidFileExtension($persistencePath)
620  && $this->‪isFileWithinAccessibleExtensionFolders($persistencePath)
621  ) {
622  return true;
623  }
624  if (
625  $persistencePathIsFile
626  && $this->‪pathIsIntendedAsFileMountPath($persistencePath)
627  && $this->‪hasValidFileExtension($persistencePath)
628  && $this->‪isFileWithinAccessibleFormStorageFolders($persistencePath)
629  ) {
630  return true;
631  }
632  if (
633  !$persistencePathIsFile
634  && $this->‪pathIsIntendedAsExtensionPath($persistencePath)
635  && $this->‪isAccessibleExtensionFolder($persistencePath)
636  ) {
637  return true;
638  }
639  if (
640  !$persistencePathIsFile
641  && $this->‪pathIsIntendedAsFileMountPath($persistencePath)
642  && $this->‪isAccessibleFormStorageFolder($persistencePath)
643  ) {
644  return true;
645  }
646 
647  return false;
648  }
649 
659  protected function overrideFormDefinition(array ‪$formDefinition, string $persistenceIdentifier, string $cacheKey): array
660  {
661  ‪$formDefinition = $this->eventDispatcher->dispatch(
663  formDefinition: ‪$formDefinition,
664  persistenceIdentifier: $persistenceIdentifier,
665  cacheKey: $cacheKey
666  )
667  )->getFormDefinition();
668 
669  if (empty($this->typoScriptSettings['formDefinitionOverrides'][‪$formDefinition['identifier']] ?? null)) {
670  return ‪$formDefinition;
671  }
672 
673  ‪$formDefinitionOverrides = GeneralUtility::makeInstance(TypoScriptService::class)
674  ->resolvePossibleTypoScriptConfiguration($this->typoScriptSettings['formDefinitionOverrides'][‪$formDefinition['identifier']]);
675 
676  ArrayUtility::mergeRecursiveWithOverrule(
679  );
680 
682  }
683 
684  protected function ‪pathIsIntendedAsExtensionPath(string $path): bool
685  {
686  return ‪PathUtility::isExtensionPath($path);
687  }
688 
689  protected function ‪pathIsIntendedAsFileMountPath(string $path): bool
690  {
691  if (empty($path)) {
692  return false;
693  }
694 
695  [$storageUid, $pathIdentifier] = explode(':', $path, 2);
696  if (empty($storageUid) || empty($pathIdentifier)) {
697  return false;
698  }
699 
700  return ‪MathUtility::canBeInterpretedAsInteger($storageUid);
701  }
702 
710  protected function ‪getOrCreateFile(string $persistenceIdentifier): ‪File
711  {
712  [$storageUid, $fileIdentifier] = explode(':', $persistenceIdentifier, 2);
713  $storage = $this->‪getStorageByUid((int)$storageUid);
714  $pathinfo = ‪PathUtility::pathinfo($fileIdentifier);
715 
716  if (!$storage->hasFolder($pathinfo['dirname'])) {
717  throw new ‪PersistenceManagerException(sprintf('Could not create folder "%s".', $pathinfo['dirname']), 1471630579);
718  }
719 
720  try {
721  $folder = $storage->getFolder($pathinfo['dirname']);
723  throw new ‪PersistenceManagerException(sprintf('No read access to folder "%s".', $pathinfo['dirname']), 1512583307);
724  }
725 
726  if (!$storage->checkFolderActionPermission('write', $folder)) {
727  throw new ‪PersistenceManagerException(sprintf('No write access to folder "%s".', $pathinfo['dirname']), 1471630580);
728  }
729 
730  if (!$storage->hasFile($fileIdentifier)) {
731  $this->filePersistenceSlot->allowInvocation(
733  $folder->getCombinedIdentifier() . $pathinfo['basename']
734  );
735  $file = $folder->createFile($pathinfo['basename']);
736  } else {
737  $file = $storage->getFile($fileIdentifier);
738  }
739  return $file;
740  }
741 
747  protected function ‪getStorageByUid(int $storageUid): ‪ResourceStorage
748  {
749  $storage = $this->storageRepository->findByUid($storageUid);
750  if (!$storage?->isBrowsable()) {
751  throw new ‪PersistenceManagerException(sprintf('Could not access storage with uid "%d".', $storageUid), 1471630581);
752  }
753  return $storage;
754  }
755 
760  protected function ‪loadMetaData($persistenceIdentifier): array
761  {
762  $file = null;
763  if ($persistenceIdentifier instanceof ‪File) {
764  $file = $persistenceIdentifier;
765  $persistenceIdentifier = $file->getCombinedIdentifier();
766  $rawYamlContent = $file->getContents();
767  } elseif (‪PathUtility::isExtensionPath($persistenceIdentifier)) {
768  $this->‪ensureValidPersistenceIdentifier($persistenceIdentifier);
769  $rawYamlContent = false;
770  $absoluteFilePath = GeneralUtility::getFileAbsFileName($persistenceIdentifier);
771  if ($absoluteFilePath !== '' && file_exists($absoluteFilePath)) {
772  $rawYamlContent = file_get_contents($absoluteFilePath);
773  }
774  } else {
775  $file = $this->‪retrieveFileByPersistenceIdentifier($persistenceIdentifier);
776  $rawYamlContent = $file->getContents();
777  }
778 
779  try {
780  if ($rawYamlContent === false) {
781  throw new ‪NoSuchFileException(sprintf('YAML file "%s" could not be loaded', $persistenceIdentifier), 1524684462);
782  }
783 
784  $yaml = $this->‪extractMetaDataFromCouldBeFormDefinition($rawYamlContent);
785  $this->‪generateErrorsIfFormDefinitionIsValidButHasInvalidFileExtension($yaml, $persistenceIdentifier);
786  if ($file !== null) {
787  $yaml['fileUid'] = $file->getUid();
788  }
789  } catch (\‪Exception $e) {
790  $yaml = [
791  'type' => 'Form',
792  'identifier' => $persistenceIdentifier,
793  'label' => $e->getMessage(),
794  'invalid' => true,
795  ];
796  }
797 
798  return $yaml;
799  }
800 
801  protected function ‪extractMetaDataFromCouldBeFormDefinition(string $maybeRawFormDefinition): array
802  {
803  $metaDataProperties = ['identifier', 'type', 'label', 'prototypeName'];
804  $metaData = [];
805  foreach (explode(LF, $maybeRawFormDefinition) as $line) {
806  if (empty($line) || $line[0] === ' ') {
807  continue;
808  }
809 
810  $parts = explode(':', $line, 2);
811  $key = trim($parts[0]);
812  if (!($parts[1] ?? null) || !in_array($key, $metaDataProperties, true)) {
813  continue;
814  }
815 
816  if ($key === 'label') {
817  try {
818  $parsedLabelLine = Yaml::parse($line);
819  $value = $parsedLabelLine['label'] ?? '';
820  } catch (ParseException $e) {
821  $value = '';
822  }
823  } else {
824  $value = trim($parts[1], " '\"\r");
825  }
826 
827  $metaData[$key] = $value;
828  }
829 
830  return $metaData;
831  }
832 
836  protected function ‪generateErrorsIfFormDefinitionIsValidButHasInvalidFileExtension(array ‪$formDefinition, string $persistenceIdentifier): void
837  {
838  if (
839  $this->‪looksLikeAFormDefinition($formDefinition)
840  && !$this->‪hasValidFileExtension($persistenceIdentifier)
841  ) {
842  throw new ‪PersistenceManagerException(sprintf('Form definition "%s" does not end with ".form.yaml".', $persistenceIdentifier), 1531160649);
843  }
844  }
845 
850  protected function ‪retrieveFileByPersistenceIdentifier(string $persistenceIdentifier): ‪File
851  {
852  $this->‪ensureValidPersistenceIdentifier($persistenceIdentifier);
853 
854  try {
855  $file = $this->resourceFactory->retrieveFileOrFolderObject($persistenceIdentifier);
856  } catch (\‪Exception $e) {
857  // Top level catch to ensure useful following exception handling, because FAL throws top level exceptions.
858  $file = null;
859  }
860 
861  if ($file === null) {
862  throw new ‪NoSuchFileException(sprintf('YAML file "%s" could not be loaded', $persistenceIdentifier), 1524684442);
863  }
864 
865  if (!$file->getStorage()->checkFileActionPermission('read', $file)) {
866  throw new ‪PersistenceManagerException(sprintf('No read access to file "%s".', $persistenceIdentifier), 1471630578);
867  }
868 
869  return $file;
870  }
871 
876  protected function ‪ensureValidPersistenceIdentifier(string $persistenceIdentifier): void
877  {
878  if (pathinfo($persistenceIdentifier, PATHINFO_EXTENSION) !== 'yaml') {
879  throw new ‪PersistenceManagerException(sprintf('The file "%s" could not be loaded.', $persistenceIdentifier), 1477679819);
880  }
881 
882  if (
883  $this->‪pathIsIntendedAsExtensionPath($persistenceIdentifier)
884  && !$this->‪isFileWithinAccessibleExtensionFolders($persistenceIdentifier)
885  ) {
886  $message = sprintf('The file "%s" could not be loaded. Please check your configuration option "persistenceManager.allowedExtensionPaths"', $persistenceIdentifier);
887  throw new ‪PersistenceManagerException($message, 1484071985);
888  }
889  }
890 
894  public function ‪hasValidFileExtension(string $fileName): bool
895  {
896  return str_ends_with($fileName, self::FORM_DEFINITION_FILE_EXTENSION);
897  }
898 
899  protected function ‪isFileWithinAccessibleExtensionFolders(string $fileName): bool
900  {
901  $pathInfo = ‪PathUtility::pathinfo($fileName, PATHINFO_DIRNAME);
902  $pathInfo = is_string($pathInfo) ? $pathInfo : '';
903  $dirName = rtrim($pathInfo, '/') . '/';
904  return array_key_exists($dirName, $this->‪getAccessibleExtensionFolders());
905  }
906 
907  protected function ‪isFileWithinAccessibleFormStorageFolders(string $fileName): bool
908  {
909  $pathInfo = ‪PathUtility::pathinfo($fileName, PATHINFO_DIRNAME);
910  $pathInfo = is_string($pathInfo) ? $pathInfo : '';
911  $dirName = rtrim($pathInfo, '/') . '/';
912 
913  foreach (array_keys($this->‪getAccessibleFormStorageFolders()) as $allowedPath) {
914  if (str_starts_with($dirName, $allowedPath)) {
915  return true;
916  }
917  }
918  return false;
919  }
920 
921  protected function ‪isAccessibleExtensionFolder(string $folderName): bool
922  {
923  $folderName = rtrim($folderName, '/') . '/';
924  return array_key_exists($folderName, $this->‪getAccessibleExtensionFolders());
925  }
926 
927  protected function ‪isAccessibleFormStorageFolder(string $folderName): bool
928  {
929  $folderName = rtrim($folderName, '/') . '/';
930  return array_key_exists($folderName, $this->‪getAccessibleFormStorageFolders());
931  }
932 
933  protected function ‪looksLikeAFormDefinition(array $data): bool
934  {
935  return isset($data['identifier'], $data['type']) && !empty($data['identifier']) && trim($data['type']) === 'Form';
936  }
937 
938  protected function ‪sortForms(array $forms): array
939  {
940  $keys = $this->formSettings['persistenceManager']['sortByKeys'] ?? ['name', 'fileUid'];
941  $ascending = $this->formSettings['persistenceManager']['sortAscending'] ?? true;
942 
943  usort($forms, static function (array $a, array $b) use ($keys) {
944  foreach ($keys as $key) {
945  if (isset($a[$key]) && isset($b[$key])) {
946  $diff = strcasecmp((string)$a[$key], (string)$b[$key]);
947  if ($diff) {
948  return $diff;
949  }
950  }
951  }
952  });
953 
954  return ($ascending) ? $forms : array_reverse($forms);
955  }
956 }
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\getOrCreateFile
‪getOrCreateFile(string $persistenceIdentifier)
Definition: FormPersistenceManager.php:710
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\pathIsIntendedAsFileMountPath
‪pathIsIntendedAsFileMountPath(string $path)
Definition: FormPersistenceManager.php:689
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\__construct
‪__construct(YamlSource $yamlSource, StorageRepository $storageRepository, FilePersistenceSlot $filePersistenceSlot, ResourceFactory $resourceFactory, ConfigurationManagerInterface $configurationManager, CacheManager $cacheManager, EventDispatcherInterface $eventDispatcher,)
Definition: FormPersistenceManager.php:74
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\listForms
‪array listForms()
Definition: FormPersistenceManager.php:255
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\ensureValidPersistenceIdentifier
‪ensureValidPersistenceIdentifier(string $persistenceIdentifier)
Definition: FormPersistenceManager.php:876
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\$eventDispatcher
‪EventDispatcherInterface $eventDispatcher
Definition: FormPersistenceManager.php:72
‪TYPO3\CMS\Core\Utility\PathUtility\isExtensionPath
‪static isExtensionPath(string $path)
Definition: PathUtility.php:117
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:27
‪TYPO3\CMS\Core\Cache\CacheManager\getCache
‪FrontendInterface getCache($identifier)
Definition: CacheManager.php:128
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\$filePersistenceSlot
‪FilePersistenceSlot $filePersistenceSlot
Definition: FormPersistenceManager.php:67
‪TYPO3\CMS\Form\Mvc\Configuration\Exception\FileWriteException
Definition: FileWriteException.php:24
‪TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException
Definition: InsufficientFolderAccessPermissionsException.php:23
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\pathIsIntendedAsExtensionPath
‪pathIsIntendedAsExtensionPath(string $path)
Definition: FormPersistenceManager.php:684
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\$storageRepository
‪StorageRepository $storageRepository
Definition: FormPersistenceManager.php:66
‪TYPO3\CMS\Form\Mvc\Persistence\Exception\PersistenceManagerException
Definition: PersistenceManagerException.php:27
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\isFileWithinAccessibleExtensionFolders
‪isFileWithinAccessibleExtensionFolders(string $fileName)
Definition: FormPersistenceManager.php:899
‪TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface\setRequest
‪setRequest(ServerRequestInterface $request)
‪TYPO3\CMS\Core\Core\SystemEnvironmentBuilder
Definition: SystemEnvironmentBuilder.php:41
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\isAccessibleExtensionFolder
‪isAccessibleExtensionFolder(string $folderName)
Definition: FormPersistenceManager.php:921
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\$formDefinition
‪return $formDefinition
Definition: FormPersistenceManager.php:681
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\load
‪load(string $persistenceIdentifier)
Definition: FormPersistenceManager.php:113
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\isFileWithinAccessibleFormStorageFolders
‪isFileWithinAccessibleFormStorageFolders(string $fileName)
Definition: FormPersistenceManager.php:907
‪TYPO3\CMS\Form\Mvc\Persistence\Exception\NoUniqueIdentifierException
Definition: NoUniqueIdentifierException.php:25
‪TYPO3\CMS\Core\Core\SystemEnvironmentBuilder\REQUESTTYPE_BE
‪const REQUESTTYPE_BE
Definition: SystemEnvironmentBuilder.php:45
‪TYPO3\CMS\Core\Resource\Folder\FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS
‪const FILTER_MODE_USE_OWN_AND_STORAGE_FILTERS
Definition: Folder.php:70
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\hasValidFileExtension
‪hasValidFileExtension(string $fileName)
Definition: FormPersistenceManager.php:894
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\getStorageByUid
‪getStorageByUid(int $storageUid)
Definition: FormPersistenceManager.php:747
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\isAllowedPersistencePath
‪isAllowedPersistencePath(string $persistencePath)
Definition: FormPersistenceManager.php:611
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\getAccessibleExtensionFolders
‪getAccessibleExtensionFolders()
Definition: FormPersistenceManager.php:486
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\sortForms
‪sortForms(array $forms)
Definition: FormPersistenceManager.php:938
‪TYPO3\CMS\Form\Mvc\Persistence
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot\COMMAND_FILE_CREATE
‪const COMMAND_FILE_CREATE
Definition: FilePersistenceSlot.php:40
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\checkForDuplicateIdentifier
‪checkForDuplicateIdentifier(string $identifier)
Definition: FormPersistenceManager.php:590
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\hasForms
‪hasForms()
Definition: FormPersistenceManager.php:329
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\$formSettings
‪array $formSettings
Definition: FormPersistenceManager.php:69
‪TYPO3\CMS\Core\Utility\MathUtility\canBeInterpretedAsInteger
‪static bool canBeInterpretedAsInteger(mixed $var)
Definition: MathUtility.php:69
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\$typoScriptSettings
‪array $typoScriptSettings
Definition: FormPersistenceManager.php:70
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\getAccessibleFormStorageFolders
‪Folder[] getAccessibleFormStorageFolders()
Definition: FormPersistenceManager.php:415
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\isAccessibleFormStorageFolder
‪isAccessibleFormStorageFolder(string $folderName)
Definition: FormPersistenceManager.php:927
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager
Definition: FormPersistenceManager.php:62
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\$yamlSource
‪YamlSource $yamlSource
Definition: FormPersistenceManager.php:65
‪TYPO3\CMS\Core\Resource\Folder
Definition: Folder.php:38
‪TYPO3\CMS\Core\Resource\ResourceFactory
Definition: ResourceFactory.php:42
‪TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException
Definition: FolderDoesNotExistException.php:21
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\exists
‪bool exists(string $persistenceIdentifier)
Definition: FormPersistenceManager.php:228
‪TYPO3\CMS\Core\Resource\StorageRepository
Definition: StorageRepository.php:38
‪TYPO3\CMS\Core\Resource\File
Definition: File.php:26
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\looksLikeAFormDefinition
‪looksLikeAFormDefinition(array $data)
Definition: FormPersistenceManager.php:933
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:36
‪TYPO3\CMS\Core\Http\ServerRequest
Definition: ServerRequest.php:39
‪TYPO3\CMS\Core\Resource\Filter\FileExtensionFilter
Definition: FileExtensionFilter.php:31
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\getUniquePersistenceIdentifier
‪string getUniquePersistenceIdentifier(string $formIdentifier, string $savePath)
Definition: FormPersistenceManager.php:531
‪TYPO3\CMS\Form\Mvc\Configuration\TypoScriptService
Definition: TypoScriptService.php:29
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\$runtimeCache
‪FrontendInterface $runtimeCache
Definition: FormPersistenceManager.php:71
‪TYPO3\CMS\Form\Slot\FilePersistenceSlot
Definition: FilePersistenceSlot.php:38
‪TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface\getConfiguration
‪array getConfiguration(string $configurationType, ?string $extensionName=null, ?string $pluginName=null)
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\extractMetaDataFromCouldBeFormDefinition
‪extractMetaDataFromCouldBeFormDefinition(string $maybeRawFormDefinition)
Definition: FormPersistenceManager.php:801
‪TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface\CONFIGURATION_TYPE_SETTINGS
‪const CONFIGURATION_TYPE_SETTINGS
Definition: ConfigurationManagerInterface.php:30
‪TYPO3\CMS\Core\Cache\Frontend\FrontendInterface
Definition: FrontendInterface.php:22
‪TYPO3\CMS\Form\Mvc\Configuration\YamlSource
Definition: YamlSource.php:44
‪TYPO3\CMS\Core\Resource\ResourceStorage
Definition: ResourceStorage.php:128
‪TYPO3\CMS\Form\Mvc\Persistence\Exception
Definition: Exception.php:27
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:26
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\retrieveYamlFilesFromExtensionFolders
‪string[] retrieveYamlFilesFromExtensionFolders()
Definition: FormPersistenceManager.php:388
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\retrieveFileByPersistenceIdentifier
‪retrieveFileByPersistenceIdentifier(string $persistenceIdentifier)
Definition: FormPersistenceManager.php:850
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\getUniqueIdentifier
‪string getUniqueIdentifier(string $identifier)
Definition: FormPersistenceManager.php:564
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\save
‪save(string $persistenceIdentifier, array $formDefinition)
Definition: FormPersistenceManager.php:156
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\$formDefinitionOverrides
‪if(empty($this->typoScriptSettings['formDefinitionOverrides'][$formDefinition['identifier']] ?? null)) $formDefinitionOverrides
Definition: FormPersistenceManager.php:673
‪TYPO3\CMS\Form\Mvc\Configuration\ConfigurationManagerInterface\CONFIGURATION_TYPE_YAML_SETTINGS
‪const CONFIGURATION_TYPE_YAML_SETTINGS
Definition: ConfigurationManagerInterface.php:30
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\$resourceFactory
‪ResourceFactory $resourceFactory
Definition: FormPersistenceManager.php:68
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\generateErrorsIfFormDefinitionIsValidButHasInvalidFileExtension
‪generateErrorsIfFormDefinitionIsValidButHasInvalidFileExtension(array $formDefinition, string $persistenceIdentifier)
Definition: FormPersistenceManager.php:836
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManagerInterface
Definition: FormPersistenceManagerInterface.php:32
‪TYPO3\CMS\Core\Http\NormalizedParams\createFromRequest
‪static static createFromRequest(ServerRequestInterface $request, array $systemConfiguration=null)
Definition: NormalizedParams.php:840
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\FORM_DEFINITION_FILE_EXTENSION
‪const FORM_DEFINITION_FILE_EXTENSION
Definition: FormPersistenceManager.php:63
‪TYPO3\CMS\Form\Mvc\Persistence\Event\AfterFormDefinitionLoadedEvent
Definition: AfterFormDefinitionLoadedEvent.php:24
‪TYPO3\CMS\Core\Utility\PathUtility\pathinfo
‪static string string[] pathinfo(string $path, int $options=PATHINFO_ALL)
Definition: PathUtility.php:270
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\loadMetaData
‪loadMetaData($persistenceIdentifier)
Definition: FormPersistenceManager.php:760
‪TYPO3\CMS\Core\Http\NormalizedParams
Definition: NormalizedParams.php:38
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37
‪TYPO3\CMS\Form\Mvc\Configuration\Exception\NoSuchFileException
Definition: NoSuchFileException.php:24
‪TYPO3\CMS\Form\Mvc\Persistence\Exception\NoUniquePersistenceIdentifierException
Definition: NoUniquePersistenceIdentifierException.php:25
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\retrieveYamlFilesFromStorageFolders
‪File[] retrieveYamlFilesFromStorageFolders()
Definition: FormPersistenceManager.php:355
‪TYPO3\CMS\Form\Mvc\Configuration\ConfigurationManagerInterface
Definition: ConfigurationManagerInterface.php:29