findViewHelpersInComposerProject($this->classLoader); $errors = $viewHelperFinder->getLastErrors(); // Get available component definitions and merge with ViewHelpers $viewHelperMetadataFactory = new ViewHelperMetadataFactory(); foreach ($this->viewHelperResolverDelegateRegistry->getAll() as $delegate) { if ( $delegate instanceof ComponentListProviderInterface && $delegate instanceof ComponentDefinitionProviderInterface ) { foreach ($delegate->getAvailableComponents() as $componentName) { $allViewHelpers[] = $viewHelperMetadataFactory->createFromComponentDefinition( $delegate, $delegate->getComponentDefinition($componentName) ); } } } $xsdFiles = $this->combineViewHelperNamespaces($allViewHelpers, $this->viewHelperResolverFactory->create()->getNamespaces()); // Create transient folder if necessary $temporaryPath = Environment::getVarPath() . '/transient/'; if (!is_dir($temporaryPath)) { GeneralUtility::mkdir_deep($temporaryPath); } // Remove existing schema files in transient folder $existingSchemaFiles = GeneralUtility::getFilesInDir($temporaryPath, 'xsd'); foreach ($existingSchemaFiles as $file) { if (str_starts_with($file, 'schema_')) { unlink($temporaryPath . $file); } } // Write schema files to transient folder foreach ($xsdFiles as $xmlNamespace => $viewHelpers) { $schema = (new SchemaGenerator())->generate($xmlNamespace, $viewHelpers); $fileName = str_replace('http://typo3.org/ns/', '', $xmlNamespace); $fileName = str_replace('/', '_', $fileName); $fileName = preg_replace('#[^0-9a-zA-Z_]#', '', $fileName); GeneralUtility::writeFile($temporaryPath . 'schema_' . $fileName . '.xsd', $schema->asXml(), true); $output->writeln(sprintf('Generated schema file %s', $temporaryPath . 'schema_' . $fileName . '.xsd'), OutputInterface::VERBOSITY_DEBUG); } if ($errors !== []) { $output->writeln('Reported errors:'); $table = new Table($output); $table->setHeaders(['Class', 'Message']); foreach ($errors as $error) { $table->addRow([ $error->getFile(), $error->getMessage(), ]); } $table->render(); $output->writeln('Successfully generated all schemas except those listed with errors.'); } return Command::SUCCESS; } /** * @param ViewHelperMetadata[] $viewHelpers * @param array $globalNamespaces * @return array */ private static function combineViewHelperNamespaces(array $viewHelpers, array $globalNamespaces): array { // Group ViewHelpers by xml namespace to split them into xsd files later $viewHelperNamespaces = $groupedByNamespace = []; foreach ($viewHelpers as $viewHelper) { $viewHelperNamespaces[$viewHelper->xmlNamespace] ??= []; $viewHelperNamespaces[$viewHelper->xmlNamespace][] = $viewHelper; $groupedByNamespace[$viewHelper->namespace] ??= []; $groupedByNamespace[$viewHelper->namespace][] = $viewHelper; } // Special handling of TYPO3's global ViewHelper namespaces which allows // merging of several PHP namespaces into one Fluid namespace. If a configured // global Fluid namespace has more than one PHP namespace, ViewHelpers can be // overridden by subsequent namespaces if they are defined with the same name. // For example, both Fluid Standalone and EXT:fluid define , // but EXT:fluid is the higher item in the namespace array, so it will be part // of the xsd file, while the from Fluid Standalone will be omitted. foreach ($globalNamespaces as $mergedNamespace) { // If a global namespace has only one item, it is already covered by the // default handling above if (count($mergedNamespace) < 2) { continue; } // Last PHP namespace defines the xml namespace $targetNamespace = end($mergedNamespace); if (!isset($groupedByNamespace[$targetNamespace])) { continue; } $xmlNamespace = $groupedByNamespace[$targetNamespace][0]->xmlNamespace; // Combine PHP namespaces into one XML namespace; basically, all previous // namespaces are "pulled" into the current namespace and then overlayed with // it, so that ViewHelpers with the same name can override $viewHelperNamespaces[$xmlNamespace] = []; foreach ($mergedNamespace as $namespace) { foreach ($groupedByNamespace[$namespace] ?? [] as $viewHelper) { $viewHelperNamespaces[$xmlNamespace][$viewHelper->tagName] = $viewHelper; } } $viewHelperNamespaces[$xmlNamespace] = array_values($viewHelperNamespaces[$xmlNamespace]); } return $viewHelperNamespaces; } }