TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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 Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\FormatterHelper;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Fluid\Core\Rendering\RenderingContextFactory;
|
||||
use TYPO3\CMS\Fluid\Service\TemplateFinder;
|
||||
use TYPO3Fluid\Fluid\Validation\TemplateValidator;
|
||||
use TYPO3Fluid\Fluid\Validation\TemplateValidatorResult;
|
||||
|
||||
/**
|
||||
* Analyzes Fluid templates for syntax errors and deprecated functionality
|
||||
*
|
||||
* @internal: Specific command implementation, not API itself.
|
||||
*/
|
||||
#[AsCommand(
|
||||
'fluid:analyze',
|
||||
'Analyzes Fluid templates for syntax errors and deprecated functionality.',
|
||||
['fluid:analyse'],
|
||||
)]
|
||||
final class AnalyzeCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private readonly TemplateFinder $templateFinder,
|
||||
private readonly RenderingContextFactory $renderingContextFactory,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->addOption(
|
||||
'include-system-extensions',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Include template files that belong to TYPO3 system extensions',
|
||||
);
|
||||
$this->addOption(
|
||||
'stdin',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Analyze template string that is provided via STDIN',
|
||||
);
|
||||
$this->addOption(
|
||||
'json',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Output results as JSON',
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$templates = $input->getOption('stdin')
|
||||
? ['php://stdin']
|
||||
: $this->templateFinder->findTemplatesInAllPackages($input->getOption('include-system-extensions'));
|
||||
|
||||
if ($input->getOption('json')) {
|
||||
$result = $this->validateTemplateFiles($templates);
|
||||
$result = $input->getOption('stdin') ? $result['php://stdin'] : $result;
|
||||
$output->writeln(json_encode($result));
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$formatter = new FormatterHelper();
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$io->note('This command only analyzes templates that are using the *.fluid.* file extension.');
|
||||
|
||||
$templatesCount = count($templates);
|
||||
if ($output->isVeryVerbose()) {
|
||||
$io->success(sprintf('%d templates will be analyzed:', $templatesCount));
|
||||
$index = 0;
|
||||
foreach ($templates as $template) {
|
||||
$index++;
|
||||
$output->writeln(sprintf('<info>%d</info> %s', $index, $template));
|
||||
}
|
||||
}
|
||||
$results = $this->validateTemplateFiles($templates);
|
||||
$errors = $deprecations = 0;
|
||||
foreach ($results as $result) {
|
||||
$templateFile = $result->path;
|
||||
if (str_starts_with($templateFile, Environment::getProjectPath())) {
|
||||
$templateFile = substr($templateFile, strlen(Environment::getProjectPath()) + 1);
|
||||
}
|
||||
foreach ($result->errors as $error) {
|
||||
$errors++;
|
||||
$output->writeln($formatter->formatSection(
|
||||
'ERROR',
|
||||
$templateFile . ': ' . $error->getMessage(),
|
||||
'error',
|
||||
));
|
||||
}
|
||||
foreach ($result->deprecations as $deprecation) {
|
||||
$deprecations++;
|
||||
$output->writeln($formatter->formatSection(
|
||||
'DEPRECATION',
|
||||
$templateFile . ': ' . $deprecation->message,
|
||||
'info',
|
||||
));
|
||||
}
|
||||
}
|
||||
if ($output->isVerbose()) {
|
||||
if ($errors > 0) {
|
||||
$output->writeln('');
|
||||
$io->error(sprintf('%d error(s) found in %d analyzed templates.', $errors, $templatesCount));
|
||||
}
|
||||
if ($deprecations > 0) {
|
||||
$output->writeln('');
|
||||
$io->warning(sprintf('%d deprecation(s) found in %d analyzed templates.', $deprecations, $templatesCount));
|
||||
}
|
||||
if ($errors === 0 && $deprecations === 0) {
|
||||
$io->success(sprintf('%d templates analyzed without errors or deprecations.', $templatesCount));
|
||||
}
|
||||
}
|
||||
return $errors > 0 ? Command::FAILURE : Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TemplateValidatorResult[]
|
||||
*/
|
||||
private function validateTemplateFiles(array $templates): array
|
||||
{
|
||||
return (new TemplateValidator())->validateTemplateFiles(
|
||||
$templates,
|
||||
$this->renderingContextFactory->create(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?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 Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Helper\TableCell;
|
||||
use Symfony\Component\Console\Helper\TableSeparator;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use TYPO3\CMS\Fluid\Core\ViewHelper\ViewHelperResolverFactoryInterface;
|
||||
|
||||
/**
|
||||
* Lists all registered global Fluid ViewHelper namespaces
|
||||
*
|
||||
* @internal: Specific command implementation, not API itself.
|
||||
*/
|
||||
#[AsCommand('fluid:namespaces', 'Lists all registered global Fluid ViewHelper namespaces.')]
|
||||
final class NamespacesCommand extends Command
|
||||
{
|
||||
public function __construct(private readonly ViewHelperResolverFactoryInterface $viewHelperResolverFactory)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->addOption(
|
||||
'json',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Output namespaces as JSON',
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$globalNamespaces = $this->viewHelperResolverFactory->create()->getNamespaces();
|
||||
|
||||
if ($input->getOption('json')) {
|
||||
$output->writeln(json_encode($globalNamespaces));
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$table = new Table($output);
|
||||
$table->setHeaders(['Alias', 'Namespace(s)']);
|
||||
$isFirst = true;
|
||||
foreach ($globalNamespaces as $alias => $namespaceChain) {
|
||||
if (!$isFirst) {
|
||||
$table->addRow(new TableSeparator());
|
||||
}
|
||||
$table->addRow([
|
||||
$alias,
|
||||
new TableCell(implode("\n", $namespaceChain), ['rowspan' => count($namespaceChain)]),
|
||||
]);
|
||||
$isFirst = false;
|
||||
}
|
||||
$table->render();
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* 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 Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\FormatterHelper;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Fluid\Service\CacheWarmupService;
|
||||
|
||||
/**
|
||||
* Warmup Fluid cache for detected template files
|
||||
*
|
||||
* @internal: Specific command implementation, not API itself.
|
||||
*/
|
||||
#[AsCommand('fluid:cache:warmup', 'Performs a cache warmup for detected Fluid templates.')]
|
||||
final class WarmupCommand extends Command
|
||||
{
|
||||
public function __construct(private readonly CacheWarmupService $cacheWarmupService)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$formatter = new FormatterHelper();
|
||||
$io->note('This command only considers templates that are using the *.fluid.* file extension.');
|
||||
$errors = $deprecations = 0;
|
||||
$results = $this->cacheWarmupService->warmupTemplatesInAllPackages();
|
||||
$templatesCount = count($results);
|
||||
foreach ($results as $result) {
|
||||
$templateFile = $result->path;
|
||||
if (str_starts_with($templateFile, Environment::getProjectPath())) {
|
||||
$templateFile = substr($templateFile, strlen(Environment::getProjectPath()) + 1);
|
||||
}
|
||||
foreach ($result->errors as $error) {
|
||||
$errors++;
|
||||
$output->writeln($formatter->formatSection(
|
||||
'ERROR',
|
||||
$templateFile . ': ' . $error->getMessage(),
|
||||
'error',
|
||||
));
|
||||
}
|
||||
foreach ($result->deprecations as $deprecation) {
|
||||
$deprecations++;
|
||||
$output->writeln($formatter->formatSection(
|
||||
'DEPRECATION',
|
||||
$templateFile . ': ' . $deprecation->message,
|
||||
'info',
|
||||
));
|
||||
}
|
||||
}
|
||||
if ($output->isVerbose()) {
|
||||
if ($errors > 0) {
|
||||
$output->writeln('');
|
||||
$io->error(sprintf('%d error(s) found in %d warmed up templates.', $errors, $templatesCount));
|
||||
}
|
||||
if ($deprecations > 0) {
|
||||
$output->writeln('');
|
||||
$io->warning(sprintf('%d deprecation(s) found in %d warmed up templates.', $deprecations, $templatesCount));
|
||||
}
|
||||
if ($errors === 0 && $deprecations === 0) {
|
||||
$io->success(sprintf('%d templates warmed up without errors or deprecations.', $templatesCount));
|
||||
}
|
||||
}
|
||||
return $errors > 0 ? Command::FAILURE : Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user