<?php /*************************************************************** * Copyright notice * * (c) sgalinski Internet Services (https://www.sgalinski.de) * * All rights reserved * * This script is part of the AY project. The AY project is * free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * The GNU General Public License can be found at * http://www.gnu.org/copyleft/gpl.html. * * This script is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * This copyright notice MUST APPEAR in all copies of the script! ***************************************************************/ namespace SGalinski\SgVimeo\ViewHelpers; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper; /** * Class RenderSvgViewHelper * * @package SGalinski\SgVimeo\ViewHelpers */ class RenderSvgViewHelper extends AbstractTagBasedViewHelper { /** * Name of the tag to be created by this view helper * * @var string * @api */ protected $tagName = 'svg'; protected $directoryPath = __DIR__ . '/../../Resources/Public/Icons/'; /** * Register the ViewHelper arguments */ public function initializeArguments(): void { parent::initializeArguments(); $this->registerArgument('name', 'string', 'The SVG name', TRUE); $this->registerArgument('color', 'string', 'The fill color', TRUE); $this->registerArgument('id', 'string', 'The HTML id attribute', FALSE); $this->registerArgument('path', 'string', 'Path to the SVG file if not in default', FALSE); $this->registerArgument('class', 'string', 'The HTML class attribute', FALSE); $this->registerArgument('title', 'string', 'The HTML title attribute', FALSE); $this->registerArgument('width', 'string', 'The HTML width attribute', FALSE); $this->registerArgument('height', 'string', 'The HTML height attribute', FALSE); $this->registerArgument('style', 'string', 'Inline CSS styles', FALSE); $this->registerArgument('use', 'string', 'Inline CSS styles', FALSE); } /** * Render the SVG file as an inline SVG element. * * @return string The rendered SVG element * @throws \Exception */ public function render(): string { $name = $this->arguments['name']; $path = $this->arguments['path']; $width = $this->arguments['width']; $height = $this->arguments['height']; $color = $this->arguments['color']; $id = $this->arguments['id']; $class = $this->arguments['class']; $style = $this->arguments['style']; $title = $this->arguments['title']; $src = $path ?? $this->directoryPath . '/' . $name .'.svg'; // Get the content of the SVG file $content = file_get_contents($src); // Create a unique ID for the SVG element if (!$id) { $id = 'svg-' . md5($src . $width . $height . $color); } // Load the SVG into a SimpleXMLElement object $svg = new \SimpleXMLElement($content); // Set the attributes of the SVG element if ($width > 0) { $this->addOrReplaceAttribute($svg, 'width', $width); } if ($height > 0) { $this->addOrReplaceAttribute($svg, 'height', $height); } if (!empty($color)) { $this->setFill($svg, $color); } if ($style) { $this->addOrReplaceAttribute($svg, 'style', $style); } if ($class) { $this->addOrReplaceAttribute($svg, 'class', $class); } if ($title) { $this->addOrReplaceAttribute($svg,'title', $title); } // // Set the ID of the SVG element // $svg->attributes()->id = $id; // Extract the SVG contents $contents = $this->getContents($svg, true); // Check if the SVG has already been rendered and use the <use> tag if possible if ($this->viewHelperVariableContainer->exists('Vendor\\Extension\\ViewHelpers\\SvgViewHelper', 'svgIds')) { $svgIds = $this->viewHelperVariableContainer->get('Vendor\\Extension\\ViewHelpers\\SvgViewHelper', 'svgIds'); if (isset($svgIds[$id])) { $use = $svg->addChild('use'); $use->addAttribute('href', '#' . $id); return $svg->asXML(); } } else { $svgIds = []; } // Add the unique ID to the list of rendered SVGs $svgIds[$id] = $id; $this->viewHelperVariableContainer->add('Vendor\\Extension\\ViewHelpers\\SvgViewHelper', 'svgIds', $svgIds); $contentsElement = new \SimpleXMLElement($contents); $group = $svg->addChild('g'); /** @noinspection NullPointerExceptionInspection */ $group->addAttribute('id', $id); $this->xmlAdopt($group, $contentsElement); return $svg->asXML(); //Todo: we don't really want this, do we? if (!empty($width) && !empty($height)) { $resultWithoutCssClass = str_replace($cssClass, '', $result); $cssClasses = $this->processWrapperClasses($cssClassPlain); return "<span $cssClasses style='width: $width; height: $height;'>$resultWithoutCssClass</span>"; } if (!empty($width)) { $resultWithoutCssClass = str_replace($cssClass, '', $result); $cssClasses = $this->processWrapperClasses($cssClassPlain); return "<span $cssClasses style='width: $width;'>$resultWithoutCssClass</span>"; } if (!empty($height)) { $resultWithoutCssClass = str_replace($cssClass, '', $result); $cssClasses = $this->processWrapperClasses($cssClassPlain); return "<span $cssClasses style='height: $height;'>$resultWithoutCssClass</span>"; } return $result; } /** * Processes the common SVG wrapper's classes * * @param string $cssClass * @return string */ function processWrapperClasses(string $cssClass): string { return "class='$cssClass svg-wrapper d-inline-flex justify-content-center align-items-center'"; } /** * Processes the wrappers for the usage of the <use> SVG functionality * * @param string $svgContent * @param string $id * @param string $use * @return string */ function processUseWrappers(string $svgContent, string $id, string $use): string { $svgContent = "<g $id>$svgContent</g>"; if ($use) { $svgContent = "<use href='#$use' />"; } return $svgContent; } /** * Set the fill color of an SVG element. * * @param \SimpleXMLElement $element The SVG element * @param string $fill The fill color to set */ protected function setFill(\SimpleXMLElement $element, string $fill): void { foreach ($element->children() as $child) { $this->setFill($child, $fill); } if (isset($element->attributes()->fill)) { $element->attributes()->fill = $fill; } } /** * Gets the contents of the SVG file * * @param \SimpleXMLElement $svg * @param bool $removeNode * @return string */ private function getContents(\SimpleXMLElement $svg, bool $removeNode = false): string { $contents = ''; foreach ($svg->children() as $child) { $contents .= $child->asXML() . "\n"; if ($removeNode) { $dom = dom_import_simplexml($child); $dom->parentNode->removeChild($dom); } } return $contents; } /** * Inserts a new SimpleXMLElement at the given root * * @param \SimpleXMLElement $root * @param \SimpleXMLElement $newElement * @return void */ private function xmlAdopt(\SimpleXMLElement $root, \SimpleXMLElement $newElement): void { $node = $root->addChild($newElement->getName(), (string) $newElement); foreach($newElement->attributes() as $attr => $value) { /** @noinspection NullPointerExceptionInspection */ $node->addAttribute($attr, $value); } foreach($newElement->children() as $ch) { /** @noinspection NullPointerExceptionInspection */ $this->xmlAdopt($node, $ch); } } /** * Adds or replaces an attribute * * @param \SimpleXMLElement $element * @param string $attributeName * @param string $attributeValue * @return void */ private function addOrReplaceAttribute(\SimpleXMLElement $element, string $attributeName, string $attributeValue): void { if (isset($element[$attributeName])) { $element[$attributeName] = $attributeValue; // Replace existing attribute value } else { $element->addAttribute($attributeName, $attributeValue); // Add new attribute } } }