‪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 Symfony\Component\Yaml\Exception\ParseException;
25 use Symfony\Component\Yaml\Yaml;
51 
58 {
59  public const ‪FORM_DEFINITION_FILE_EXTENSION = '.form.yaml';
60 
65  protected array ‪$formSettings;
66  protected array ‪$typoScriptSettings;
68 
69  public function ‪__construct(
74  ‪ConfigurationManagerInterface $configurationManager,
75  ‪CacheManager $cacheManager
76  ) {
77  $this->yamlSource = ‪$yamlSource;
78  $this->storageRepository = ‪$storageRepository;
79  $this->filePersistenceSlot = ‪$filePersistenceSlot;
80  $this->resourceFactory = ‪$resourceFactory;
81  $fakeRequest = false;
82  if (!isset(‪$GLOBALS['TYPO3_REQUEST'])) {
83  // @todo: FormPersistenceManager is sometimes triggered via CLI without request. In this
84  // case we fake a request so extbase ConfigurationManager still works.
85  $request = (new ‪ServerRequest())->withAttribute('applicationType', ‪SystemEnvironmentBuilder::REQUESTTYPE_BE);
86  ‪$GLOBALS['TYPO3_REQUEST'] = $request;
87  $fakeRequest = true;
88  }
89  $this->formSettings = $configurationManager->‪getConfiguration(‪ConfigurationManagerInterface::CONFIGURATION_TYPE_YAML_SETTINGS, 'form');
90  $this->typoScriptSettings = $configurationManager->‪getConfiguration(‪ConfigurationManagerInterface::CONFIGURATION_TYPE_SETTINGS, 'form');
91  if ($fakeRequest) {
92  unset(‪$GLOBALS['TYPO3_REQUEST']);
93  }
94  $this->runtimeCache = $cacheManager->‪getCache('runtime');
95  }
96 
104  public function ‪load(string $persistenceIdentifier): array
105  {
106  $cacheKey = 'formLoad' . md5($persistenceIdentifier);
107 
108  $yaml = $this->runtimeCache->get($cacheKey);
109  if ($yaml !== false) {
110  return $this->overrideByTypoScriptSettings($yaml);
111  }
112 
113  if (‪PathUtility::isExtensionPath($persistenceIdentifier)) {
114  $this->‪ensureValidPersistenceIdentifier($persistenceIdentifier);
115  $file = $persistenceIdentifier;
116  } else {
117  $file = $this->‪retrieveFileByPersistenceIdentifier($persistenceIdentifier);
118  }
119 
120  try {
121  $yaml = $this->yamlSource->load([$file]);
122  $this->‪generateErrorsIfFormDefinitionIsValidButHasInvalidFileExtension($yaml, $persistenceIdentifier);
123  } catch (\‪Exception $e) {
124  $yaml = [
125  'type' => 'Form',
126  'identifier' => $persistenceIdentifier,
127  'label' => $e->getMessage(),
128  'invalid' => true,
129  ];
130  }
131  $this->runtimeCache->set($cacheKey, $yaml);
132 
133  return $this->overrideByTypoScriptSettings($yaml);
134  }
135 
147  public function ‪save(string $persistenceIdentifier, array ‪$formDefinition)
148  {
149  if (!$this->‪hasValidFileExtension($persistenceIdentifier)) {
150  throw new ‪PersistenceManagerException(sprintf('The file "%s" could not be saved.', $persistenceIdentifier), 1477679820);
151  }
152 
153  if ($this->‪pathIsIntendedAsExtensionPath($persistenceIdentifier)) {
154  if (!$this->formSettings['persistenceManager']['allowSaveToExtensionPaths']) {
155  throw new ‪PersistenceManagerException('Save to extension paths is not allowed.', 1477680881);
156  }
157  if (!$this->‪isFileWithinAccessibleExtensionFolders($persistenceIdentifier)) {
158  $message = sprintf('The file "%s" could not be saved. Please check your configuration option "persistenceManager.allowedExtensionPaths"', $persistenceIdentifier);
159  throw new ‪PersistenceManagerException($message, 1484073571);
160  }
161  $fileToSave = GeneralUtility::getFileAbsFileName($persistenceIdentifier);
162  } else {
163  $fileToSave = $this->‪getOrCreateFile($persistenceIdentifier);
164  }
165 
166  try {
167  $this->yamlSource->save($fileToSave, ‪$formDefinition);
168  } catch (‪FileWriteException $e) {
169  throw new ‪PersistenceManagerException(sprintf(
170  'The file "%s" could not be saved: %s',
171  $persistenceIdentifier,
172  $e->getMessage()
173  ), 1512582637, $e);
174  }
175  }
176 
184  public function delete(string $persistenceIdentifier)
185  {
186  if (!$this->‪hasValidFileExtension($persistenceIdentifier)) {
187  throw new ‪PersistenceManagerException(sprintf('The file "%s" could not be removed.', $persistenceIdentifier), 1472239534);
188  }
189  if (!$this->‪exists($persistenceIdentifier)) {
190  throw new ‪PersistenceManagerException(sprintf('The file "%s" could not be removed.', $persistenceIdentifier), 1472239535);
191  }
192  if ($this->‪pathIsIntendedAsExtensionPath($persistenceIdentifier)) {
193  if (!$this->formSettings['persistenceManager']['allowDeleteFromExtensionPaths']) {
194  throw new ‪PersistenceManagerException(sprintf('The file "%s" could not be removed.', $persistenceIdentifier), 1472239536);
195  }
196  if (!$this->‪isFileWithinAccessibleExtensionFolders($persistenceIdentifier)) {
197  $message = sprintf('The file "%s" could not be removed. Please check your configuration option "persistenceManager.allowedExtensionPaths"', $persistenceIdentifier);
198  throw new ‪PersistenceManagerException($message, 1484073878);
199  }
200  $fileToDelete = GeneralUtility::getFileAbsFileName($persistenceIdentifier);
201  unlink($fileToDelete);
202  } else {
203  [$storageUid, $fileIdentifier] = explode(':', $persistenceIdentifier, 2);
204  $storage = $this->‪getStorageByUid((int)$storageUid);
205  $file = $storage->getFile($fileIdentifier);
206  if (!$storage->checkFileActionPermission('delete', $file)) {
207  throw new ‪PersistenceManagerException(sprintf('No delete access to file "%s".', $persistenceIdentifier), 1472239516);
208  }
209  $storage->deleteFile($file);
210  }
211  }
212 
219  public function ‪exists(string $persistenceIdentifier): bool
220  {
221  $exists = false;
222  if ($this->‪hasValidFileExtension($persistenceIdentifier)) {
223  if ($this->‪pathIsIntendedAsExtensionPath($persistenceIdentifier)) {
224  if ($this->‪isFileWithinAccessibleExtensionFolders($persistenceIdentifier)) {
225  $exists = file_exists(GeneralUtility::getFileAbsFileName($persistenceIdentifier));
226  }
227  } else {
228  [$storageUid, $fileIdentifier] = explode(':', $persistenceIdentifier, 2);
229  $storage = $this->‪getStorageByUid((int)$storageUid);
230  $exists = $storage->hasFile($fileIdentifier);
231  }
232  }
233  return $exists;
234  }
235 
246  public function ‪listForms(): array
247  {
248  $identifiers = [];
249  $forms = [];
250 
251  foreach ($this->‪retrieveYamlFilesFromStorageFolders() as $file) {
252  $form = $this->‪loadMetaData($file);
253 
254  if (!$this->‪looksLikeAFormDefinition($form)) {
255  continue;
256  }
257 
258  $persistenceIdentifier = $file->getCombinedIdentifier();
259  if ($this->‪hasValidFileExtension($persistenceIdentifier)) {
260  $forms[] = [
261  'identifier' => $form['identifier'],
262  'name' => $form['label'] ?? $form['identifier'],
263  'persistenceIdentifier' => $persistenceIdentifier,
264  'readOnly' => false,
265  'removable' => true,
266  'location' => 'storage',
267  'duplicateIdentifier' => false,
268  'invalid' => $form['invalid'] ?? false,
269  'fileUid' => $form['fileUid'] ?? 0,
270  ];
271  if (!isset($identifiers[$form['identifier']])) {
272  $identifiers[$form['identifier']] = 0;
273  }
274  $identifiers[$form['identifier']]++;
275  }
276  }
277 
278  foreach ($this->‪retrieveYamlFilesFromExtensionFolders() as $file) {
279  $form = $this->‪loadMetaData($file);
280 
281  if ($this->‪looksLikeAFormDefinition($form)) {
282  if ($this->‪hasValidFileExtension($file)) {
283  $forms[] = [
284  'identifier' => $form['identifier'],
285  'name' => $form['label'] ?? $form['identifier'],
286  'persistenceIdentifier' => $file,
287  'readOnly' => $this->formSettings['persistenceManager']['allowSaveToExtensionPaths'] ? false : true,
288  'removable' => $this->formSettings['persistenceManager']['allowDeleteFromExtensionPaths'] ? true : false,
289  'location' => 'extension',
290  'duplicateIdentifier' => false,
291  'invalid' => $form['invalid'] ?? false,
292  'fileUid' => $form['fileUid'] ?? 0,
293  ];
294  if (!isset($identifiers[$form['identifier']])) {
295  $identifiers[$form['identifier']] = 0;
296  }
297  $identifiers[$form['identifier']]++;
298  }
299  }
300  }
301 
302  foreach ($identifiers as ‪$identifier => $count) {
303  if ($count > 1) {
304  foreach ($forms as &‪$formDefinition) {
305  if (‪$formDefinition['identifier'] === ‪$identifier) {
306  ‪$formDefinition['duplicateIdentifier'] = true;
307  }
308  }
309  }
310  }
311 
312  return $this->‪sortForms($forms);
313  }
314 
320  public function ‪hasForms(): bool
321  {
322  foreach ($this->‪retrieveYamlFilesFromStorageFolders() as $file) {
323  $form = $this->‪loadMetaData($file);
324 
325  if ($this->‪looksLikeAFormDefinition($form)) {
326  return true;
327  }
328  }
329  foreach ($this->‪retrieveYamlFilesFromExtensionFolders() as $file) {
330  $form = $this->‪loadMetaData($file);
331 
332  if ($this->‪looksLikeAFormDefinition($form)) {
333  return true;
334  }
335  }
336  return false;
337  }
338 
346  public function ‪retrieveYamlFilesFromStorageFolders(): array
347  {
348  $filesFromStorageFolders = [];
349 
350  $fileExtensionFilter = GeneralUtility::makeInstance(FileExtensionFilter::class);
351  $fileExtensionFilter->setAllowedFileExtensions(['yaml']);
352 
353  foreach ($this->‪getAccessibleFormStorageFolders() as $folder) {
354  $storage = $folder->getStorage();
355  $storage->setFileAndFolderNameFilters([
356  [$fileExtensionFilter, 'filterFileList'],
357  ]);
358 
359  $files = $folder->getFiles(
360  0,
361  0,
363  true
364  );
365  $filesFromStorageFolders = array_merge($filesFromStorageFolders, array_values($files));
366  $storage->resetFileAndFolderNameFiltersToDefault();
367  }
368 
369  return $filesFromStorageFolders;
370  }
371 
380  {
381  $filesFromExtensionFolders = [];
382 
383  foreach ($this->‪getAccessibleExtensionFolders() as $relativePath => $fullPath) {
384  foreach (new \DirectoryIterator($fullPath) as $fileInfo) {
385  if ($fileInfo->getExtension() !== 'yaml') {
386  continue;
387  }
388  $filesFromExtensionFolders[] = $relativePath . $fileInfo->getFilename();
389  }
390  }
391 
392  return $filesFromExtensionFolders;
393  }
394 
406  public function ‪getAccessibleFormStorageFolders(): array
407  {
408  $storageFolders = [];
409 
410  if (
411  !isset($this->formSettings['persistenceManager']['allowedFileMounts'])
412  || !is_array($this->formSettings['persistenceManager']['allowedFileMounts'])
413  || empty($this->formSettings['persistenceManager']['allowedFileMounts'])
414  ) {
415  return $storageFolders;
416  }
417 
418  foreach ($this->formSettings['persistenceManager']['allowedFileMounts'] as $allowedFileMount) {
419  $allowedFileMount = rtrim($allowedFileMount, '/') . '/';
420  // $fileMountPath is like "/form_definitions/" or "/group_homes/1/form_definitions/"
421  [$storageUid, $fileMountPath] = explode(':', $allowedFileMount, 2);
422 
423  try {
424  $storage = $this->‪getStorageByUid((int)$storageUid);
425  } catch (‪PersistenceManagerException $e) {
426  continue;
427  }
428 
429  $isStorageFileMount = false;
430  $parentFolder = $storage->getRootLevelFolder(false);
431 
432  foreach ($storage->getFileMounts() as $storageFileMount) {
434  $storageFileMountFolder = $storageFileMount['folder'];
435 
436  // 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.
437  if (str_starts_with($fileMountPath, $storageFileMountFolder->getIdentifier())) {
438  $isStorageFileMount = true;
439  $parentFolder = $storageFileMountFolder;
440  }
441  }
442 
443  // Get storage folder object, create it if missing
444  try {
445  $fileMountFolder = $storage->getFolder($fileMountPath);
447  continue;
448  } catch (‪FolderDoesNotExistException $e) {
449  if ($isStorageFileMount) {
450  $fileMountPath = substr(
451  $fileMountPath,
452  strlen($parentFolder->getIdentifier())
453  );
454  }
455 
456  try {
457  $fileMountFolder = $storage->createFolder($fileMountPath, $parentFolder);
459  continue;
460  }
461  }
462 
463  $storageFolders[$allowedFileMount] = $fileMountFolder;
464  }
465  return $storageFolders;
466  }
467 
477  public function ‪getAccessibleExtensionFolders(): array
478  {
479  $extensionFolders = $this->runtimeCache->get('formAccessibleExtensionFolders');
480 
481  if ($extensionFolders !== false) {
482  return $extensionFolders;
483  }
484 
485  $extensionFolders = [];
486  if (
487  !isset($this->formSettings['persistenceManager']['allowedExtensionPaths'])
488  || !is_array($this->formSettings['persistenceManager']['allowedExtensionPaths'])
489  || empty($this->formSettings['persistenceManager']['allowedExtensionPaths'])
490  ) {
491  $this->runtimeCache->set('formAccessibleExtensionFolders', $extensionFolders);
492  return $extensionFolders;
493  }
494 
495  foreach ($this->formSettings['persistenceManager']['allowedExtensionPaths'] as $allowedExtensionPath) {
496  if (!$this->‪pathIsIntendedAsExtensionPath($allowedExtensionPath)) {
497  continue;
498  }
499 
500  $allowedExtensionFullPath = GeneralUtility::getFileAbsFileName($allowedExtensionPath);
501  if (!file_exists($allowedExtensionFullPath)) {
502  continue;
503  }
504  $allowedExtensionPath = rtrim($allowedExtensionPath, '/') . '/';
505  $extensionFolders[$allowedExtensionPath] = $allowedExtensionFullPath;
506  }
507 
508  $this->runtimeCache->set('formAccessibleExtensionFolders', $extensionFolders);
509  return $extensionFolders;
510  }
511 
522  public function ‪getUniquePersistenceIdentifier(string $formIdentifier, string $savePath): string
523  {
524  $savePath = rtrim($savePath, '/') . '/';
525  $formPersistenceIdentifier = $savePath . $formIdentifier . ‪self::FORM_DEFINITION_FILE_EXTENSION;
526  if (!$this->‪exists($formPersistenceIdentifier)) {
527  return $formPersistenceIdentifier;
528  }
529  for ($attempts = 1; $attempts < 100; $attempts++) {
530  $formPersistenceIdentifier = $savePath . sprintf('%s_%d', $formIdentifier, $attempts) . ‪self::FORM_DEFINITION_FILE_EXTENSION;
531  if (!$this->‪exists($formPersistenceIdentifier)) {
532  return $formPersistenceIdentifier;
533  }
534  }
535  $formPersistenceIdentifier = $savePath . sprintf('%s_%d', $formIdentifier, time()) . ‪self::FORM_DEFINITION_FILE_EXTENSION;
536  if (!$this->‪exists($formPersistenceIdentifier)) {
537  return $formPersistenceIdentifier;
538  }
539 
541  sprintf('Could not find a unique persistence identifier for form identifier "%s" after %d attempts', $formIdentifier, $attempts),
542  1476010403
543  );
544  }
545 
555  public function ‪getUniqueIdentifier(string ‪$identifier): string
556  {
557  $originalIdentifier = ‪$identifier;
558  if ($this->‪checkForDuplicateIdentifier($identifier)) {
559  for ($attempts = 1; $attempts < 100; $attempts++) {
560  ‪$identifier = sprintf('%s_%d', $originalIdentifier, $attempts);
561  if (!$this->‪checkForDuplicateIdentifier($identifier)) {
562  return ‪$identifier;
563  }
564  }
565  ‪$identifier = $originalIdentifier . '_' . time();
566  if ($this->‪checkForDuplicateIdentifier($identifier)) {
568  sprintf('Could not find a unique identifier for form identifier "%s" after %d attempts', ‪$identifier, $attempts),
569  1477688567
570  );
571  }
572  }
573  return ‪$identifier;
574  }
575 
581  public function ‪checkForDuplicateIdentifier(string ‪$identifier): bool
582  {
583  $identifierUsed = false;
584  foreach ($this->‪listForms() as ‪$formDefinition) {
585  if (‪$formDefinition['identifier'] === ‪$identifier) {
586  $identifierUsed = true;
587  break;
588  }
589  }
590  return $identifierUsed;
591  }
592 
602  public function ‪isAllowedPersistencePath(string $persistencePath): bool
603  {
604  $pathinfo = ‪PathUtility::pathinfo($persistencePath);
605  $persistencePathIsFile = isset($pathinfo['extension']);
606 
607  if (
608  $persistencePathIsFile
609  && $this->‪pathIsIntendedAsExtensionPath($persistencePath)
610  && $this->‪hasValidFileExtension($persistencePath)
611  && $this->‪isFileWithinAccessibleExtensionFolders($persistencePath)
612  ) {
613  return true;
614  }
615  if (
616  $persistencePathIsFile
617  && $this->‪pathIsIntendedAsFileMountPath($persistencePath)
618  && $this->‪hasValidFileExtension($persistencePath)
619  && $this->‪isFileWithinAccessibleFormStorageFolders($persistencePath)
620  ) {
621  return true;
622  }
623  if (
624  !$persistencePathIsFile
625  && $this->‪pathIsIntendedAsExtensionPath($persistencePath)
626  && $this->‪isAccessibleExtensionFolder($persistencePath)
627  ) {
628  return true;
629  }
630  if (
631  !$persistencePathIsFile
632  && $this->‪pathIsIntendedAsFileMountPath($persistencePath)
633  && $this->‪isAccessibleFormStorageFolder($persistencePath)
634  ) {
635  return true;
636  }
637 
638  return false;
639  }
640 
650  protected function overrideByTypoScriptSettings(array ‪$formDefinition): array
651  {
652  if (!empty($this->typoScriptSettings['formDefinitionOverrides'][‪$formDefinition['identifier']] ?? null)) {
653  $formDefinitionOverrides = GeneralUtility::makeInstance(TypoScriptService::class)
654  ->resolvePossibleTypoScriptConfiguration($this->typoScriptSettings['formDefinitionOverrides'][‪$formDefinition['identifier']]);
655 
656  ArrayUtility::mergeRecursiveWithOverrule(
658  $formDefinitionOverrides
659  );
660  }
661 
663  }
664 
665  protected function ‪pathIsIntendedAsExtensionPath(string $path): bool
666  {
667  return ‪PathUtility::isExtensionPath($path);
668  }
669 
670  protected function ‪pathIsIntendedAsFileMountPath(string $path): bool
671  {
672  if (empty($path)) {
673  return false;
674  }
675 
676  [$storageUid, $pathIdentifier] = explode(':', $path, 2);
677  if (empty($storageUid) || empty($pathIdentifier)) {
678  return false;
679  }
680 
681  return ‪MathUtility::canBeInterpretedAsInteger($storageUid);
682  }
683 
691  protected function ‪getOrCreateFile(string $persistenceIdentifier): ‪File
692  {
693  [$storageUid, $fileIdentifier] = explode(':', $persistenceIdentifier, 2);
694  $storage = $this->‪getStorageByUid((int)$storageUid);
695  $pathinfo = ‪PathUtility::pathinfo($fileIdentifier);
696 
697  if (!$storage->hasFolder($pathinfo['dirname'])) {
698  throw new ‪PersistenceManagerException(sprintf('Could not create folder "%s".', $pathinfo['dirname']), 1471630579);
699  }
700 
701  try {
702  $folder = $storage->getFolder($pathinfo['dirname']);
704  throw new ‪PersistenceManagerException(sprintf('No read access to folder "%s".', $pathinfo['dirname']), 1512583307);
705  }
706 
707  if (!$storage->checkFolderActionPermission('write', $folder)) {
708  throw new ‪PersistenceManagerException(sprintf('No write access to folder "%s".', $pathinfo['dirname']), 1471630580);
709  }
710 
711  if (!$storage->hasFile($fileIdentifier)) {
712  $this->filePersistenceSlot->allowInvocation(
714  $folder->getCombinedIdentifier() . $pathinfo['basename']
715  );
716  $file = $folder->createFile($pathinfo['basename']);
717  } else {
718  $file = $storage->getFile($fileIdentifier);
719  }
720  return $file;
721  }
722 
728  protected function ‪getStorageByUid(int $storageUid): ‪ResourceStorage
729  {
730  $storage = $this->storageRepository->findByUid($storageUid);
731  if (
732  !$storage instanceof ‪ResourceStorage
733  || !$storage->‪isBrowsable()
734  ) {
735  throw new ‪PersistenceManagerException(sprintf('Could not access storage with uid "%d".', $storageUid), 1471630581);
736  }
737  return $storage;
738  }
739 
744  protected function ‪loadMetaData($persistenceIdentifier): array
745  {
746  $file = null;
747  if ($persistenceIdentifier instanceof ‪File) {
748  $file = $persistenceIdentifier;
749  $persistenceIdentifier = $file->getCombinedIdentifier();
750  $rawYamlContent = $file->getContents();
751  } elseif (‪PathUtility::isExtensionPath($persistenceIdentifier)) {
752  $this->‪ensureValidPersistenceIdentifier($persistenceIdentifier);
753  $rawYamlContent = false;
754  $absoluteFilePath = GeneralUtility::getFileAbsFileName($persistenceIdentifier);
755  if ($absoluteFilePath !== '' && file_exists($absoluteFilePath)) {
756  $rawYamlContent = file_get_contents($absoluteFilePath);
757  }
758  } else {
759  $file = $this->‪retrieveFileByPersistenceIdentifier($persistenceIdentifier);
760  $rawYamlContent = $file->getContents();
761  }
762 
763  try {
764  if ($rawYamlContent === false) {
765  throw new ‪NoSuchFileException(sprintf('YAML file "%s" could not be loaded', $persistenceIdentifier), 1524684462);
766  }
767 
768  $yaml = $this->‪extractMetaDataFromCouldBeFormDefinition($rawYamlContent);
769  $this->‪generateErrorsIfFormDefinitionIsValidButHasInvalidFileExtension($yaml, $persistenceIdentifier);
770  if ($file !== null) {
771  $yaml['fileUid'] = $file->getUid();
772  }
773  } catch (\‪Exception $e) {
774  $yaml = [
775  'type' => 'Form',
776  'identifier' => $persistenceIdentifier,
777  'label' => $e->getMessage(),
778  'invalid' => true,
779  ];
780  }
781 
782  return $yaml;
783  }
784 
785  protected function ‪extractMetaDataFromCouldBeFormDefinition(string $maybeRawFormDefinition): array
786  {
787  $metaDataProperties = ['identifier', 'type', 'label', 'prototypeName'];
788  $metaData = [];
789  foreach (explode(LF, $maybeRawFormDefinition) as $line) {
790  if (empty($line) || $line[0] === ' ') {
791  continue;
792  }
793 
794  $parts = explode(':', $line, 2);
795  $key = trim($parts[0]);
796  if (!($parts[1] ?? null) || !in_array($key, $metaDataProperties, true)) {
797  continue;
798  }
799 
800  if ($key === 'label') {
801  try {
802  $parsedLabelLine = Yaml::parse($line);
803  $value = $parsedLabelLine['label'] ?? '';
804  } catch (ParseException $e) {
805  $value = '';
806  }
807  } else {
808  $value = trim($parts[1], " '\"\r");
809  }
810 
811  $metaData[$key] = $value;
812  }
813 
814  return $metaData;
815  }
816 
820  protected function ‪generateErrorsIfFormDefinitionIsValidButHasInvalidFileExtension(array ‪$formDefinition, string $persistenceIdentifier): void
821  {
822  if (
823  $this->‪looksLikeAFormDefinition($formDefinition)
824  && !$this->‪hasValidFileExtension($persistenceIdentifier)
825  ) {
826  throw new ‪PersistenceManagerException(sprintf('Form definition "%s" does not end with ".form.yaml".', $persistenceIdentifier), 1531160649);
827  }
828  }
829 
834  protected function ‪retrieveFileByPersistenceIdentifier(string $persistenceIdentifier): ‪File
835  {
836  $this->‪ensureValidPersistenceIdentifier($persistenceIdentifier);
837 
838  try {
839  $file = $this->resourceFactory->retrieveFileOrFolderObject($persistenceIdentifier);
840  } catch (\‪Exception $e) {
841  // Top level catch to ensure useful following exception handling, because FAL throws top level exceptions.
842  $file = null;
843  }
844 
845  if ($file === null) {
846  throw new ‪NoSuchFileException(sprintf('YAML file "%s" could not be loaded', $persistenceIdentifier), 1524684442);
847  }
848 
849  if (!$file->getStorage()->checkFileActionPermission('read', $file)) {
850  throw new ‪PersistenceManagerException(sprintf('No read access to file "%s".', $persistenceIdentifier), 1471630578);
851  }
852 
853  return $file;
854  }
855 
860  protected function ‪ensureValidPersistenceIdentifier(string $persistenceIdentifier): void
861  {
862  if (pathinfo($persistenceIdentifier, PATHINFO_EXTENSION) !== 'yaml') {
863  throw new ‪PersistenceManagerException(sprintf('The file "%s" could not be loaded.', $persistenceIdentifier), 1477679819);
864  }
865 
866  if (
867  $this->‪pathIsIntendedAsExtensionPath($persistenceIdentifier)
868  && !$this->‪isFileWithinAccessibleExtensionFolders($persistenceIdentifier)
869  ) {
870  $message = sprintf('The file "%s" could not be loaded. Please check your configuration option "persistenceManager.allowedExtensionPaths"', $persistenceIdentifier);
871  throw new ‪PersistenceManagerException($message, 1484071985);
872  }
873  }
874 
878  public function ‪hasValidFileExtension(string $fileName): bool
879  {
880  return str_ends_with($fileName, self::FORM_DEFINITION_FILE_EXTENSION);
881  }
882 
883  protected function ‪isFileWithinAccessibleExtensionFolders(string $fileName): bool
884  {
885  $pathInfo = ‪PathUtility::pathinfo($fileName, PATHINFO_DIRNAME);
886  $pathInfo = is_string($pathInfo) ? $pathInfo : '';
887  $dirName = rtrim($pathInfo, '/') . '/';
888  return array_key_exists($dirName, $this->‪getAccessibleExtensionFolders());
889  }
890 
891  protected function ‪isFileWithinAccessibleFormStorageFolders(string $fileName): bool
892  {
893  $pathInfo = ‪PathUtility::pathinfo($fileName, PATHINFO_DIRNAME);
894  $pathInfo = is_string($pathInfo) ? $pathInfo : '';
895  $dirName = rtrim($pathInfo, '/') . '/';
896 
897  foreach (array_keys($this->‪getAccessibleFormStorageFolders()) as $allowedPath) {
898  if (str_starts_with($dirName, $allowedPath)) {
899  return true;
900  }
901  }
902  return false;
903  }
904 
905  protected function ‪isAccessibleExtensionFolder(string $folderName): bool
906  {
907  $folderName = rtrim($folderName, '/') . '/';
908  return array_key_exists($folderName, $this->‪getAccessibleExtensionFolders());
909  }
910 
911  protected function ‪isAccessibleFormStorageFolder(string $folderName): bool
912  {
913  $folderName = rtrim($folderName, '/') . '/';
914  return array_key_exists($folderName, $this->‪getAccessibleFormStorageFolders());
915  }
916 
917  protected function ‪looksLikeAFormDefinition(array $data): bool
918  {
919  return isset($data['identifier'], $data['type']) && !empty($data['identifier']) && trim($data['type']) === 'Form';
920  }
921 
922  protected function ‪sortForms(array $forms): array
923  {
924  $keys = $this->formSettings['persistenceManager']['sortByKeys'] ?? ['name', 'fileUid'];
925  $ascending = $this->formSettings['persistenceManager']['sortAscending'] ?? true;
926 
927  usort($forms, static function (array $a, array $b) use ($keys) {
928  foreach ($keys as $key) {
929  if (isset($a[$key]) && isset($b[$key])) {
930  $diff = strcasecmp((string)$a[$key], (string)$b[$key]);
931  if ($diff) {
932  return $diff;
933  }
934  }
935  }
936  });
937 
938  return ($ascending) ? $forms : array_reverse($forms);
939  }
940 }
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\getOrCreateFile
‪getOrCreateFile(string $persistenceIdentifier)
Definition: FormPersistenceManager.php:691
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\pathIsIntendedAsFileMountPath
‪pathIsIntendedAsFileMountPath(string $path)
Definition: FormPersistenceManager.php:670
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\listForms
‪array listForms()
Definition: FormPersistenceManager.php:246
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\ensureValidPersistenceIdentifier
‪ensureValidPersistenceIdentifier(string $persistenceIdentifier)
Definition: FormPersistenceManager.php:860
‪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:63
‪TYPO3\CMS\Form\Mvc\Configuration\Exception\FileWriteException
Definition: FileWriteException.php:25
‪TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException
Definition: InsufficientFolderAccessPermissionsException.php:24
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\pathIsIntendedAsExtensionPath
‪pathIsIntendedAsExtensionPath(string $path)
Definition: FormPersistenceManager.php:665
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\$storageRepository
‪StorageRepository $storageRepository
Definition: FormPersistenceManager.php:62
‪TYPO3\CMS\Form\Mvc\Persistence\Exception\PersistenceManagerException
Definition: PersistenceManagerException.php:28
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\isFileWithinAccessibleExtensionFolders
‪isFileWithinAccessibleExtensionFolders(string $fileName)
Definition: FormPersistenceManager.php:883
‪TYPO3\CMS\Core\Core\SystemEnvironmentBuilder
Definition: SystemEnvironmentBuilder.php:41
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\isAccessibleExtensionFolder
‪isAccessibleExtensionFolder(string $folderName)
Definition: FormPersistenceManager.php:905
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\$formDefinition
‪return $formDefinition
Definition: FormPersistenceManager.php:662
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\load
‪load(string $persistenceIdentifier)
Definition: FormPersistenceManager.php:104
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\isFileWithinAccessibleFormStorageFolders
‪isFileWithinAccessibleFormStorageFolders(string $fileName)
Definition: FormPersistenceManager.php:891
‪TYPO3\CMS\Form\Mvc\Persistence\Exception\NoUniqueIdentifierException
Definition: NoUniqueIdentifierException.php:26
‪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:69
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\hasValidFileExtension
‪hasValidFileExtension(string $fileName)
Definition: FormPersistenceManager.php:878
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\getStorageByUid
‪getStorageByUid(int $storageUid)
Definition: FormPersistenceManager.php:728
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\isAllowedPersistencePath
‪isAllowedPersistencePath(string $persistencePath)
Definition: FormPersistenceManager.php:602
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\getAccessibleExtensionFolders
‪getAccessibleExtensionFolders()
Definition: FormPersistenceManager.php:477
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\sortForms
‪sortForms(array $forms)
Definition: FormPersistenceManager.php:922
‪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:581
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\hasForms
‪hasForms()
Definition: FormPersistenceManager.php:320
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\$formSettings
‪array $formSettings
Definition: FormPersistenceManager.php:65
‪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:66
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\getAccessibleFormStorageFolders
‪Folder[] getAccessibleFormStorageFolders()
Definition: FormPersistenceManager.php:406
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\isAccessibleFormStorageFolder
‪isAccessibleFormStorageFolder(string $folderName)
Definition: FormPersistenceManager.php:911
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager
Definition: FormPersistenceManager.php:58
‪TYPO3\CMS\Core\Resource\ResourceStorage\isBrowsable
‪bool isBrowsable()
Definition: ResourceStorage.php:401
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\$yamlSource
‪YamlSource $yamlSource
Definition: FormPersistenceManager.php:61
‪TYPO3\CMS\Core\Resource\Folder
Definition: Folder.php:37
‪TYPO3\CMS\Core\Resource\ResourceFactory
Definition: ResourceFactory.php:41
‪TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException
Definition: FolderDoesNotExistException.php:22
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\exists
‪bool exists(string $persistenceIdentifier)
Definition: FormPersistenceManager.php:219
‪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:917
‪TYPO3\CMS\Core\Cache\CacheManager
Definition: CacheManager.php:36
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\__construct
‪__construct(YamlSource $yamlSource, StorageRepository $storageRepository, FilePersistenceSlot $filePersistenceSlot, ResourceFactory $resourceFactory, ConfigurationManagerInterface $configurationManager, CacheManager $cacheManager)
Definition: FormPersistenceManager.php:69
‪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:522
‪TYPO3\CMS\Form\Mvc\Configuration\TypoScriptService
Definition: TypoScriptService.php:29
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\$runtimeCache
‪FrontendInterface $runtimeCache
Definition: FormPersistenceManager.php:67
‪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:785
‪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:127
‪TYPO3\CMS\Form\Mvc\Persistence\Exception
Definition: Exception.php:28
‪TYPO3\CMS\Core\Utility\ArrayUtility
Definition: ArrayUtility.php:26
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\retrieveYamlFilesFromExtensionFolders
‪string[] retrieveYamlFilesFromExtensionFolders()
Definition: FormPersistenceManager.php:379
‪$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:834
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\getUniqueIdentifier
‪string getUniqueIdentifier(string $identifier)
Definition: FormPersistenceManager.php:555
‪TYPO3\CMS\Core\Utility\MathUtility
Definition: MathUtility.php:24
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\save
‪save(string $persistenceIdentifier, array $formDefinition)
Definition: FormPersistenceManager.php:147
‪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:64
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\generateErrorsIfFormDefinitionIsValidButHasInvalidFileExtension
‪generateErrorsIfFormDefinitionIsValidButHasInvalidFileExtension(array $formDefinition, string $persistenceIdentifier)
Definition: FormPersistenceManager.php:820
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManagerInterface
Definition: FormPersistenceManagerInterface.php:32
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\FORM_DEFINITION_FILE_EXTENSION
‪const FORM_DEFINITION_FILE_EXTENSION
Definition: FormPersistenceManager.php:59
‪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:744
‪TYPO3\CMS\Webhooks\Message\$identifier
‪identifier readonly string $identifier
Definition: FileAddedMessage.php:37
‪TYPO3\CMS\Form\Mvc\Configuration\Exception\NoSuchFileException
Definition: NoSuchFileException.php:25
‪TYPO3\CMS\Form\Mvc\Persistence\Exception\NoUniquePersistenceIdentifierException
Definition: NoUniquePersistenceIdentifierException.php:26
‪TYPO3\CMS\Form\Mvc\Persistence\FormPersistenceManager\retrieveYamlFilesFromStorageFolders
‪File[] retrieveYamlFilesFromStorageFolders()
Definition: FormPersistenceManager.php:346
‪TYPO3\CMS\Form\Mvc\Configuration\ConfigurationManagerInterface
Definition: ConfigurationManagerInterface.php:29