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;
}
}