runtimeCache->has($cacheKey)) { $formDefinition = $this->runtimeCache->get($cacheKey); } else { $formDefinition = $this->loadFromStorage($persistenceIdentifier, $request); $this->runtimeCache->set($cacheKey, $formDefinition); } $formDefinition = $this->eventDispatcher ->dispatch(new AfterFormDefinitionLoadedEvent($formDefinition, $persistenceIdentifier, $cacheKey)) ->getFormDefinition(); if ($request !== null && !empty($typoScriptSettings['formDefinitionOverrides'][$formDefinition['identifier']] ?? null)) { $formDefinitionOverrides = $this->typoScriptService->resolvePossibleTypoScriptConfiguration( $typoScriptSettings['formDefinitionOverrides'][$formDefinition['identifier']], $request ); ArrayUtility::mergeRecursiveWithOverrule($formDefinition, $formDefinitionOverrides); } return $formDefinition; } /** * Save form definition to appropriate storage * * @throws PersistenceManagerException */ public function save(string $persistenceIdentifier, array $formDefinition, array $formSettings, ?string $storageLocation = null): FormIdentifier { if (!$this->isAllowedPersistenceIdentifier($persistenceIdentifier)) { throw new PersistenceManagerException( sprintf('Save to path "%s" is not allowed.', $persistenceIdentifier), 1477680881 ); } $identifier = new FormIdentifier($persistenceIdentifier); $adapter = $this->storageAdapterFactory->getAdapterForIdentifier($persistenceIdentifier); $formData = FormData::fromArray($formDefinition); $context = null; if (MathUtility::canBeInterpretedAsInteger($storageLocation)) { $context = StorageContext::create((int)$storageLocation); } $savedIdentifier = $adapter->write($identifier, $formData, $context); $this->clearFormCache($savedIdentifier->identifier); return $savedIdentifier; } /** * Delete form definition from storage */ public function delete(string $persistenceIdentifier, array $formSettings): void { $identifier = new FormIdentifier($persistenceIdentifier); $adapter = $this->storageAdapterFactory->getAdapterForIdentifier($persistenceIdentifier); if (!$adapter->exists($identifier)) { throw new PersistenceManagerException( sprintf('The form "%s" does not exist.', $persistenceIdentifier), 1472239535 ); } $adapter->delete($identifier); $this->clearFormCache($persistenceIdentifier); } /** * List all form definitions from all available storages */ public function listForms(array $formSettings, SearchCriteria $searchCriteria): array { $identifiers = []; $forms = []; foreach ($this->storageAdapterFactory->getAllAdapters() as $adapter) { try { $formDataList = $adapter->findAll($searchCriteria); foreach ($formDataList as $formData) { if ($formData->storageType === null) { $formData = $formData->withStorageType($adapter->getTypeIdentifier()); } $forms[] = $formData; if (!isset($identifiers[$formData->identifier])) { $identifiers[$formData->identifier] = 0; } $identifiers[$formData->identifier]++; } } catch (\Exception $e) { continue; } } foreach ($identifiers as $identifier => $count) { if ($count > 1) { foreach ($forms as $index => $formMetadata) { if ($formMetadata->identifier === $identifier) { $forms[$index] = $formMetadata->withDuplicateIdentifier(true); } } } } $allReferencesForFileUid = $this->databaseService->getAllReferencesForFileUid(); $allReferencesForPersistenceIdentifier = $this->databaseService->getAllReferencesForPersistenceIdentifier(); $allReferencesForFormDefinitionUid = $this->databaseService->getAllReferencesForFormDefinitionUid(); foreach ($forms as $index => $formMetadata) { if (isset($formMetadata->fileUid) && array_key_exists($formMetadata->fileUid, $allReferencesForFileUid)) { $referenceCount = $allReferencesForFileUid[$formMetadata->fileUid]; } elseif ($formMetadata->persistenceIdentifier && array_key_exists($formMetadata->persistenceIdentifier, $allReferencesForFormDefinitionUid)) { $referenceCount = $allReferencesForFormDefinitionUid[$formMetadata->persistenceIdentifier]; } elseif ($formMetadata->persistenceIdentifier && array_key_exists($formMetadata->persistenceIdentifier, $allReferencesForPersistenceIdentifier)) { $referenceCount = $allReferencesForPersistenceIdentifier[$formMetadata->persistenceIdentifier]; } else { $referenceCount = 0; } if ($referenceCount > 0) { $forms[$index] = $formMetadata->withReferenceCount($referenceCount); } } return $this->sortForms($forms, $formSettings, $searchCriteria->getOrderField(), $searchCriteria->getOrderDirection()); } /** * Check if any forms are available */ public function hasForms(array $formSettings): bool { foreach ($this->storageAdapterFactory->getAllAdapters() as $adapter) { try { $forms = $adapter->findAll(new SearchCriteria(limit: 1)); if (!empty($forms)) { return true; } } catch (\Exception) { continue; } } return false; } /** * Get unique persistence identifier for a new form */ public function getUniquePersistenceIdentifier(string $storage, string $formIdentifier, ?string $savePath): string { return $this->storageAdapterFactory->getAdapterByType($storage)->getUniquePersistenceIdentifier($formIdentifier, $savePath); } /** * Get unique identifier (not persistence identifier) */ public function getUniqueIdentifier(string $identifier): string { $originalIdentifier = $identifier; if ($this->checkForDuplicateIdentifier($identifier)) { for ($attempts = 1; $attempts < 100; $attempts++) { $identifier = sprintf('%s_%d', $originalIdentifier, $attempts); if (!$this->checkForDuplicateIdentifier($identifier)) { return $identifier; } } $identifier = $originalIdentifier . '_' . time(); if ($this->checkForDuplicateIdentifier($identifier)) { throw new NoUniqueIdentifierException( sprintf('Could not find a unique identifier for form identifier "%s" after %d attempts', $identifier, $attempts), 1477688567 ); } } return $identifier; } /** * Check if a storage location is allowed * * For database storage: storageLocation is a PID * For file storage: storageLocation is a folder path (e.g., "1:/forms/") */ public function isAllowedStorageLocation(string $storageLocation): bool { try { $adapter = $this->storageAdapterFactory->getAdapterForIdentifier($storageLocation); return $adapter->isAllowedStorageLocation($storageLocation); } catch (\Exception) { return false; } } /** * Check if a persistence identifier is allowed * * For database storage: identifier is a UID or NEW* * For file storage: identifier is a full file path (e.g., "1:/forms/contact.form.yaml") */ public function isAllowedPersistenceIdentifier(string $persistenceIdentifier): bool { try { $adapter = $this->storageAdapterFactory->getAdapterForIdentifier($persistenceIdentifier); return $adapter->isAllowedPersistenceIdentifier($persistenceIdentifier); } catch (\Exception) { return false; } } /** * Check if file has valid extension */ public function hasValidFileExtension(string $fileName): bool { return str_ends_with($fileName, self::FORM_DEFINITION_FILE_EXTENSION); } /** * Load form definition from storage */ private function loadFromStorage(string $persistenceIdentifier, ?ServerRequestInterface $request = null): array { try { $identifier = new FormIdentifier($persistenceIdentifier); $adapter = $this->storageAdapterFactory->getAdapterForIdentifier($persistenceIdentifier); return $adapter->read($identifier, $request)->toArray(); } catch (\Exception $e) { return [ 'type' => 'Form', 'identifier' => $persistenceIdentifier, 'label' => $e->getMessage(), 'invalid' => true, ]; } } /** * Check if form with persistence identifier exists */ private function exists(string $persistenceIdentifier): bool { try { $identifier = new FormIdentifier($persistenceIdentifier); $adapter = $this->storageAdapterFactory->getAdapterForIdentifier($persistenceIdentifier); return $adapter->exists($identifier); } catch (\Exception) { return false; } } /** * Check if a form with given identifier already exists in any storage */ private function checkForDuplicateIdentifier(string $identifier): bool { foreach ($this->storageAdapterFactory->getAllAdapters() as $adapter) { try { if ($adapter->existsByFormIdentifier($identifier)) { return true; } } catch (\Exception) { continue; } } return false; } protected function sortForms(array $forms, array $formSettings, string $orderField = '', ?string $orderDirection = null): array { $persistenceConfiguration = PersistenceManagerConfiguration::fromArray($formSettings['persistenceManager'] ?? []); if ($orderDirection) { $ascending = $orderDirection === 'asc'; } else { $ascending = $persistenceConfiguration->sortAscending; } $sortMultiplier = $ascending ? 1 : -1; $keys = $orderField ? [$orderField] : $persistenceConfiguration->sortByKeys; usort($forms, static function (FormMetadata $a, FormMetadata $b) use ($keys, $sortMultiplier) { foreach ($keys as $key) { $aValue = $a->getSortableValue($key); $bValue = $b->getSortableValue($key); if ($aValue === null || $bValue === null) { continue; } $diff = (is_int($aValue) && is_int($bValue)) ? $aValue - $bValue : strcasecmp((string)$aValue, (string)$bValue); if ($diff !== 0) { return $diff * $sortMultiplier; } } return 0; }); return $forms; } /** * Clear cache for specific form */ private function clearFormCache(string $persistenceIdentifier): void { $cacheKey = 'ext-form-load-' . hash('xxh3', $persistenceIdentifier); $this->runtimeCache->remove($cacheKey); } public function getAccessibleStorageAdapters(): array { $storageAdapters = []; foreach ($this->storageAdapterFactory->getAllAdapters() as $adapter) { if ($adapter->isAccessible() === false) { continue; } $storageAdapters[] = [ 'typeIdentifier' => $adapter->getTypeIdentifier(), 'label' => $adapter->getLabel(), 'description' => $adapter->getDescription(), 'iconIdentifier' => $adapter->getIconIdentifier(), 'options' => $adapter->getFormManagerOptions(), ]; } return $storageAdapters; } }