* * .foo { color: black; } * * * See also :ref:`changelog-Feature-90522-IntroduceAssetCollector` */ final class CssViewHelper extends AbstractTagBasedViewHelper { /** * This VH does not produce direct output, thus does not need to be wrapped in an escaping node * * @var bool */ protected $escapeOutput = false; /** * Rendered children string is passed as CSS code, * there is no point in HTML encoding anything from that. * * @var bool */ protected $escapeChildren = true; protected AssetCollector $assetCollector; public function injectAssetCollector(AssetCollector $assetCollector): void { $this->assetCollector = $assetCollector; } public function initialize(): void { // Add a tag builder, that does not html encode values, because rendering with encoding happens in AssetRenderer $this->setTagBuilder( new class () extends TagBuilder { public function addAttribute($attributeName, $attributeValue, $escapeSpecialCharacters = false): void { parent::addAttribute($attributeName, $attributeValue, false); } } ); parent::initialize(); } public function initializeArguments(): void { parent::initializeArguments(); $this->registerUniversalTagAttributes(); $this->registerTagAttribute('as', 'string', 'Define the type of content being loaded (For rel="preload" or rel="prefetch" only).', false); $this->registerTagAttribute('crossorigin', 'string', 'Define how to handle crossorigin requests.', false); $this->registerTagAttribute('disabled', 'bool', 'Define whether or not the described stylesheet should be loaded and applied to the document.', false); $this->registerTagAttribute('href', 'string', 'Define the URL of the resource (absolute or relative).', false); $this->registerTagAttribute('hreflang', 'string', 'Define the language of the resource (Only to be used if \'href\' is set).', false); $this->registerTagAttribute('importance', 'string', 'Define the relative fetch priority of the resource.', false); $this->registerTagAttribute('integrity', 'string', 'Define base64-encoded cryptographic hash of the resource that allows browsers to verify what they fetch.', false); $this->registerTagAttribute('media', 'string', 'Define which media type the resources applies to.', false); $this->registerTagAttribute('referrerpolicy', 'string', 'Define which referrer is sent when fetching the resource.', false); $this->registerTagAttribute('rel', 'string', 'Define the relationship of the target object to the link object.', false); $this->registerTagAttribute('sizes', 'string', 'Define the icon size of the resource.', false); $this->registerTagAttribute('type', 'string', 'Define the MIME type (usually \'text/css\').', false); $this->registerTagAttribute('nonce', 'string', 'Define a cryptographic nonce (number used once) used to whitelist inline styles in a style-src Content-Security-Policy.', false); $this->registerArgument( 'identifier', 'string', 'Use this identifier within templates to only inject your CSS once, even though it is added multiple times.', true ); $this->registerArgument( 'priority', 'boolean', 'Define whether the CSS should be included before other CSS. CSS will always be output in the tag.', false, false ); $this->registerArgument( 'embed', 'boolean', 'Define whether or not the described stylesheet should be embedded into HTML output.', false, false ); } protected function getPageRenderer(): PageRenderer { return GeneralUtility::makeInstance(PageRenderer::class); } public function render(): string { $identifier = (string)$this->arguments['identifier']; $attributes = $this->tag->getAttributes(); // boolean attributes shall output attr="attr" if set if ($attributes['disabled'] ?? false) { $attributes['disabled'] = 'disabled'; } $file = $attributes['href'] ?? null; unset($attributes['href']); $options = [ 'priority' => $this->arguments['priority'], ]; $minifier = new Minify\CSS(); if ($file !== null) { if ($this->arguments['embed']) { $filecontent = (string)file_get_contents(GeneralUtility::getFileAbsFileName(trim($file))); $minifier->add($filecontent); $filecontent = $minifier->minify(); $this->assetCollector->addInlineStyleSheet($identifier, $filecontent, $attributes, $options); } else { $this->assetCollector->addStyleSheet($identifier, $file, $attributes, $options); } } else { $content = (string)$this->renderChildren(); if ($content !== '') { $minifier->add($content); $content = $minifier->minify(); $this->assetCollector->addInlineStyleSheet($identifier, $content, $attributes, $options); } } return ''; } }