templateNamePattern = trim($templateNamePattern, '/'); } public function withTemplateNamePattern(string $templateNamePattern): static { return new static($this->cache, $this->eventDispatcher, $this->namespace, $this->templatePaths, $templateNamePattern, $this->additionalArgumentsAllowed); } public function withAdditionalArgumentsAllowed(bool $additionalArgumentsAllowed): static { return new static($this->cache, $this->eventDispatcher, $this->namespace, $this->templatePaths, $this->templateNamePattern, $additionalArgumentsAllowed); } public function resolveTemplateName(string $viewHelperName): string { $fragments = array_map(ucfirst(...), explode('.', $viewHelperName)); $name = array_pop($fragments); $path = implode('/', $fragments); return ltrim(str_replace(['{path}', '{name}'], [$path, $name], $this->templateNamePattern), '/'); } public function getAvailableComponents(): array { $availableTemplates = $this->getTemplatePaths()->resolveAvailableTemplateFiles(null, null, true); $templateNamePattern = self::convertTemplatePatternToRegularExpression($this->templateNamePattern); $availableComponents = []; foreach ($availableTemplates as $templatePath) { // Remove template root path foreach ($this->getTemplatePaths()->getTemplateRootPaths() as $rootPath) { if (str_starts_with($templatePath, $rootPath)) { $templatePath = substr($templatePath, strlen($rootPath)); break; } } // Convert template name into ViewHelper name and validate directory structure // (resolveTemplateName() in reverse) if (!preg_match($templateNamePattern, $templatePath, $matches)) { continue; } $fragments = $matches['path'] ? GeneralUtility::trimExplode('/', $matches['path'], true) : []; $fragments[] = $matches['name']; $availableComponents[] = implode('.', array_map(lcfirst(...), $fragments)); } return array_values(array_unique($availableComponents)); } public function getTemplatePaths(): TemplatePaths { $templatePaths = new TemplatePaths(); $templatePaths->setTemplateRootPaths($this->templatePaths); return $templatePaths; } public function getAdditionalVariables(string $viewHelperName): array { // Allow to provide additional variables to the component template. // Note that this deliberately cannot depend on runtime characteristics, // such as the request, as this should be done in the renderer. $event = $this->eventDispatcher->dispatch( new ProvideStaticVariablesToComponentEvent($this, $viewHelperName) ); return $event->getStaticVariables(); } public function getComponentDefinition(string $viewHelperName): ComponentDefinition { $cacheIdentifier = hash('xxh3', $this->namespace . '--' . $viewHelperName); $componentDefinition = $this->cache->get($cacheIdentifier); if ($componentDefinition instanceof ComponentDefinition) { return $componentDefinition; } $templateName = $this->resolveTemplateName($viewHelperName); /** * Extract component definition from component template * This part is an ugly workaround because of shortcomings in the Fluid parser. * Once this has been resolved on the Fluid side, there will most likely be a better API. * @see \TYPO3Fluid\Fluid\Core\Component\AbstractComponentCollection */ $renderingContext = new RenderingContext(); $renderingContext->setViewHelperResolver(new TemplateStructureViewHelperResolver()); $parsedTemplate = $renderingContext->getTemplateParser()->parse( $this->getTemplatePaths()->getTemplateSource('Default', $templateName), $this->getTemplatePaths()->getTemplateIdentifier('Default', $templateName), $this->getTemplatePaths()->resolveTemplateFileForControllerAndActionAndFormat('Default', $templateName), ); $componentDefinition = new ComponentDefinition( $viewHelperName, $parsedTemplate->getArgumentDefinitions(), $this->additionalArgumentsAllowed, $parsedTemplate->getAvailableSlots(), ); // Allow modification of component definition before it's written to cache. // Note that this deliberately cannot depend on runtime characteristics, // such as the request, as this should be done during rendering (e. g. by allowing // arbitrary arguments) $event = $this->eventDispatcher->dispatch( new ModifyComponentDefinitionEvent($this->namespace, $componentDefinition) ); $componentDefinition = $event->getComponentDefinition(); $this->cache->set($cacheIdentifier, $componentDefinition); return $componentDefinition; } public function getComponentRenderer(): ComponentRendererInterface { return new EventBasedComponentRenderer($this->eventDispatcher, $this); } public function resolveViewHelperClassName(string $name): string { $expectedTemplateName = $this->resolveTemplateName($name); try { $this->getTemplatePaths()->resolveTemplateFileForControllerAndActionAndFormat('Default', $expectedTemplateName, null, true); } catch (InvalidTemplateResourceException $e) { throw new UnresolvableViewHelperException(sprintf( 'The component template "%s" in format ".%s" could not be found in the configured template paths. %s', $expectedTemplateName, $this->getTemplatePaths()->getFormat(), $e->evaluatedTemplatePaths !== [] ? 'The following file paths were evaluated: "' . implode('", "', $e->evaluatedTemplatePaths) . '"' : 'No paths configured.', ), 1765711586); } return ComponentAdapter::class; } public function getNamespace(): string { return $this->namespace; } private static function convertTemplatePatternToRegularExpression(string $templateNamePattern): string { $delimiter = '~'; $pathMarker = preg_quote('{path}/', $delimiter); $nameMarker = preg_quote('{name}', $delimiter); $templateNamePattern = preg_quote($templateNamePattern, $delimiter); if (str_contains($templateNamePattern, $pathMarker)) { [$beforePath, $afterPath] = explode($pathMarker, $templateNamePattern, 2); $templateNamePattern = $beforePath . '(?(?:.+?/)?)' . str_replace($pathMarker, '(?P=path)', $afterPath); } if (str_contains($templateNamePattern, $nameMarker)) { [$beforeName, $afterName] = explode($nameMarker, $templateNamePattern, 2); $templateNamePattern = $beforeName . '(?[^/]+?)' . str_replace($nameMarker, '(?P=name)', $afterName); } return $delimiter . '^' . $templateNamePattern . '$' . $delimiter; } }