168 lines
7.4 KiB
PHP
168 lines
7.4 KiB
PHP
<?php
|
|
|
|
/*
|
|
* This file is part of the TYPO3 CMS project.
|
|
*
|
|
* It is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License, either version 2
|
|
* of the License, or any later version.
|
|
*
|
|
* For the full copyright and license information, please read the
|
|
* LICENSE.txt file that was distributed with this source code.
|
|
*
|
|
* The TYPO3 project - inspiring people to share!
|
|
*/
|
|
|
|
namespace TYPO3\CMS\Fluid\Command;
|
|
|
|
use Composer\Autoload\ClassLoader;
|
|
use Symfony\Component\Console\Attribute\AsCommand;
|
|
use Symfony\Component\Console\Command\Command;
|
|
use Symfony\Component\Console\Helper\Table;
|
|
use Symfony\Component\Console\Input\InputInterface;
|
|
use Symfony\Component\Console\Output\OutputInterface;
|
|
use TYPO3\CMS\Core\Core\Environment;
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|
use TYPO3\CMS\Fluid\Core\ViewHelper\ViewHelperResolverDelegateRegistry;
|
|
use TYPO3\CMS\Fluid\Core\ViewHelper\ViewHelperResolverFactoryInterface;
|
|
use TYPO3Fluid\Fluid\Core\Component\ComponentDefinitionProviderInterface;
|
|
use TYPO3Fluid\Fluid\Core\Component\ComponentListProviderInterface;
|
|
use TYPO3Fluid\Fluid\Schema\SchemaGenerator;
|
|
use TYPO3Fluid\Fluid\Schema\ViewHelperFinder;
|
|
use TYPO3Fluid\Fluid\Schema\ViewHelperMetadata;
|
|
use TYPO3Fluid\Fluid\Schema\ViewHelperMetadataFactory;
|
|
|
|
/**
|
|
* Generate schema files from fluid view helpers
|
|
*
|
|
* @internal: Specific command implementation, not API itself.
|
|
*/
|
|
#[AsCommand('fluid:schema:generate', 'Generate XSD schema files for all available ViewHelpers in var/transient/.')]
|
|
final class SchemaCommand extends Command
|
|
{
|
|
public function __construct(
|
|
private readonly ClassLoader $classLoader,
|
|
private readonly ViewHelperResolverFactoryInterface $viewHelperResolverFactory,
|
|
private readonly ViewHelperResolverDelegateRegistry $viewHelperResolverDelegateRegistry,
|
|
) {
|
|
parent::__construct();
|
|
}
|
|
|
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
|
{
|
|
$viewHelperFinder = new ViewHelperFinder();
|
|
$allViewHelpers = $viewHelperFinder->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 <info>%s</info>', $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<string, string[]> $globalNamespaces
|
|
* @return array<string, ViewHelperMetadata[]>
|
|
*/
|
|
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 <f:render>,
|
|
// but EXT:fluid is the higher item in the namespace array, so it will be part
|
|
// of the xsd file, while the <f:render> 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;
|
|
}
|
|
}
|