Skip to content

Instantly share code, notes, and snippets.

@vkemeter
Created December 22, 2022 12:33
Show Gist options
  • Save vkemeter/faf367a3a6230dd6b06a41604ed5f4c0 to your computer and use it in GitHub Desktop.
Save vkemeter/faf367a3a6230dd6b06a41604ed5f4c0 to your computer and use it in GitHub Desktop.
A TYPO3 ViewHelper to render SVG images inline
<?php
declare(strict_types=1);
namespace Supseven\Theme\ViewHelpers\Render;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Exception;
use TYPO3\CMS\Core\Package\PackageManager;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
/**
* Class InlineSvgViewHelper
*
* Return the adjusted content of a svg file
*
* = Examples
* <code title="basic inline svg">
* <theme:render.inlineSvg source="{f:uri.resource(path: 'Icons/FileIcons/{file.extension}.svg', extensionName: 'theme')}" />
* </code>
* <output>
* <svg><contentOfTheSvgFile</svg>
* <output>
*
* = Notes
*
* This ViewHelper renders Inline Svg from a given SVG File. To make the SVG accesssible, you can add an ID and a TITLE to the
* produced inline svg. the original title will alway be removed. if you set a id but no title, the viewhelper throws an exception
* and will not be rendered.
*/
class InlineSvgViewHelper extends AbstractViewHelper
{
use CompileWithRenderStatic;
/**
* @var bool
*/
protected $escapeOutput = false;
/**
* Arguments initialization
*/
public function initializeArguments(): void
{
parent::initializeArguments();
$this->registerArgument('source', 'string', 'Source of svg resource', true);
$this->registerArgument('class', 'string', 'Specifies an alternate class for the svg', false);
$this->registerArgument('width', 'string', 'Specifies a width for the svg', false);
$this->registerArgument('height', 'string', 'Specifies a height for the svg', false);
$this->registerArgument('aria-hidden', 'bool', 'Activate aria-hidden=true attribute', false);
$this->registerArgument('insert-style', 'string', 'Add CSS Styles', false);
$this->registerArgument('id', 'string', 'Add UNIQUE Id', false);
$this->registerArgument('title', 'string', 'Add descriptive title for the SVG', false);
$this->registerArgument('fill', 'string', 'add a fill color', false);
}
/**
* Output different objects
*
* @param array $arguments
* @param \Closure $renderChildrenClosure
* @param RenderingContextInterface $renderingContext
* @return string
*/
public static function renderStatic(
array $arguments,
\Closure $renderChildrenClosure,
RenderingContextInterface $renderingContext
) {
if (stripos($arguments['source'], 'EXT:') !== false) {
$file = GeneralUtility::makeInstance(PackageManager::class)->resolvePackagePath($arguments['source']);
} else {
$file = Environment::getPublicPath() . '/' . $arguments['source'];
}
// return html comment, if file couldn't be found
if (empty($arguments['source']) || !file_exists($file)) {
return '<!-- SVG file couldn\'t be found -->';
}
try {
return self::getInlineSvg($file, $arguments);
} catch (\Exception $e) {
if ($e->getCode() === 1614863553) {
return '<!-- ' . $e->getMessage() . ' -->';
}
return '<!-- SVG generation produced error! -->';
}
}
/**
* @param string $source
* @param array $arguments
* @return string
*/
protected static function getInlineSvg(string $source, array $arguments)
{
$svgContent = file_get_contents($source);
$svgContent = preg_replace('/<script[\\s\\S]*?>[\\s\\S]*?<\\/script>/i', '', $svgContent);
$svgContent = preg_replace('/<title[\\s\\S]*?>[\\s\\S]*?<\\/title>/i', '', $svgContent);
// Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
$previousValueOfEntityLoader = libxml_disable_entity_loader(true);
$svgElement = simplexml_load_string($svgContent);
libxml_disable_entity_loader($previousValueOfEntityLoader);
// remove xml version tag
$domXml = dom_import_simplexml($svgElement);
// add role attribute for a11y
$domXml->setAttribute('role', 'img');
// if a title should be used, create an extra node
if (isset($arguments['title'])) {
$title = $domXml->ownerDocument->createElement('title', $arguments['title']);
}
// change all fill attributes to the value given from the viewhelper
if ($arguments['fill']) {
$domXml->setAttribute('fill', $arguments['fill']);
foreach ($domXml->childNodes as $node) {
if ($node instanceof \DOMElement) {
$node->setAttribute('fill', $arguments['fill']);
foreach ($node->childNodes as $n) {
if ($n instanceof \DOMElement) {
$n->setAttribute('fill', $arguments['fill']);
}
}
}
}
}
// if there is a id, it means, this svg should be accessible. therefore a title MUST be set
// if not, it throws a exception and will not be rendered.
// if no id is set, the SVG is automatically marked as aria-hidden for a11y reasons
if (isset($arguments['id'])) {
$domXml->setAttribute('aria-labelledby', self::sanitizeId($arguments['id']));
if (isset($title)) {
$title->setAttribute('id', self::sanitizeId($arguments['id']));
} else {
throw new Exception('This SVG is not accessible, if there is no Title attribute available', 1614863553);
}
} else {
$domXml->setAttribute('aria-hidden', 'true');
}
// at this point, the title will be appended to the svg object
if (isset($title)) {
$domXml->appendChild($title);
}
// if you have inline styles, like css variables, you can use this argument to add styles directly to the
// svg.
if (isset($arguments['insert-style'])) {
$inlineStyle = $domXml->ownerDocument->createElement('style', $arguments['insert-style']);
$domXml->appendChild($inlineStyle);
}
if (isset($arguments['class'])) {
$domXml->setAttribute('class', $arguments['class']);
}
if (isset($arguments['width'])) {
$domXml->setAttribute('width', (string)$arguments['width']);
}
if (isset($arguments['height'])) {
$domXml->setAttribute('height', (string)$arguments['height']);
}
return $domXml->ownerDocument->saveXML($domXml->ownerDocument->documentElement);
}
/**
* sanitizes a possible id string to a correct usable id string
*
* @param string $id
* @return string
*/
protected static function sanitizeId(string $id): string
{
$svgId = (string)preg_replace('/[^a-zA-Z0-9_:.-]/', '_', $id);
return htmlspecialchars((string)preg_replace('/^[^a-zA-Z]/', 'x', $svgId));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment