1<?php
2namespace TYPO3\CMS\Frontend\ContentObject;
3
4/*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17use TYPO3\CMS\Core\Utility\GeneralUtility;
18use TYPO3\CMS\Core\Utility\PathUtility;
19
20/**
21 * Contains SVG content object.
22 */
23class ScalableVectorGraphicsContentObject extends AbstractContentObject
24{
25    /**
26     * Rendering the cObject, SVG
27     *
28     * @param array $conf Array of TypoScript properties
29     * @return string
30     */
31    public function render($conf = []): string
32    {
33        $renderMode = isset($conf['renderMode.'])
34            ? $this->cObj->stdWrap($conf['renderMode'], $conf['renderMode.'])
35            : $conf['renderMode'];
36
37        if ($renderMode === 'inline') {
38            return $this->renderInline($conf);
39        }
40
41        return $this->renderObject($conf);
42    }
43
44    /**
45     * @param array $conf
46     *
47     * @return string
48     */
49    protected function renderInline(array $conf): string
50    {
51        $src = $this->resolveAbsoluteSourcePath($conf);
52        list($width, $height, $isDefaultWidth, $isDefaultHeight) = $this->getDimensions($conf);
53
54        $content = '';
55        if (file_exists($src)) {
56            $svgContent = file_get_contents($src);
57            $svgContent = preg_replace('/<script[\s\S]*?>[\s\S]*?<\/script>/i', '', $svgContent);
58            // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
59            $previousValueOfEntityLoader = libxml_disable_entity_loader();
60            $svgElement = simplexml_load_string($svgContent);
61            libxml_disable_entity_loader($previousValueOfEntityLoader);
62
63            $domXml = dom_import_simplexml($svgElement);
64            if (!$isDefaultWidth) {
65                $domXml->setAttribute('width', $width);
66            }
67            if (!$isDefaultHeight) {
68                $domXml->setAttribute('height', $height);
69            }
70            // remove xml version tag
71            $content = $domXml->ownerDocument->saveXML($domXml->ownerDocument->documentElement);
72        } else {
73            $value = isset($conf['value.']) ? $this->cObj->stdWrap($conf['value'], $conf['value.']) : $conf['value'];
74            if (!empty($value)) {
75                $content = [];
76                $content[] = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="' . (int)$width . '" height="' . (int)$height . '">';
77                $content[] = $value;
78                $content[] = '</svg>';
79                $content = implode(LF, $content);
80            }
81        }
82        if (isset($conf['stdWrap.'])) {
83            $content = $this->cObj->stdWrap($content, $conf['stdWrap.']);
84        }
85        return $content;
86    }
87
88    /**
89     * Render the SVG as <object> tag
90     * @param array $conf
91     *
92     * @return string
93     */
94    protected function renderObject(array $conf): string
95    {
96        $src = $this->resolveAbsoluteSourcePath($conf);
97        list($width, $height) = $this->getDimensions($conf);
98
99        $src = $src === '' ? null : PathUtility::getAbsoluteWebPath($src);
100
101        $value = isset($conf['value.']) ? $this->cObj->stdWrap($conf['value'], $conf['value.']) : $conf['value'];
102        $noscript = isset($conf['noscript.']) ? $this->cObj->stdWrap($conf['noscript'], $conf['noscript.']) : $conf['noscript'];
103
104        // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0 - see method usages
105        if (!empty($conf['noscript.']) || !empty($conf['noscript'])) {
106            trigger_error('SVG cObject: The option "noscript" will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
107        }
108        if (!empty($conf['value.']) || !empty($conf['value'])) {
109            trigger_error('SVG cObject: The option "value" without setting renderMode=inline will be removed in TYPO3 v10.0.', E_USER_DEPRECATED);
110        }
111
112        $content = [];
113        if ($src) {
114            $content[] = '<!--[if IE]>';
115            $content[] = '  <object src="' . htmlspecialchars($src) . '" classid="image/svg+xml" width="' . (int)$width . '" height="' . (int)$height . '">';
116            $content[] = '<![endif]-->';
117            $content[] = '<!--[if !IE]>-->';
118            $content[] = '  <object data="' . htmlspecialchars($src) . '" type="image/svg+xml" width="' . (int)$width . '" height="' . (int)$height . '">';
119            $content[] = '<!--<![endif]-->';
120            $content[] = $noscript;
121            $content[] = '</object>';
122        } else {
123            // @deprecated since TYPO3 v9, will be removed in TYPO3 v10.0 - see method usages
124            $content[] = '<script type="image/svg+xml">';
125            $content[] = '  <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="' . (int)$width . '" height="' . (int)$height . '">';
126            $content[] = $value;
127            $content[] = '  </svg>';
128            $content[] = '</script>';
129            $content[] = '<noscript>';
130            $content[] = $noscript;
131            $content[] = '</noscript>';
132        }
133        $content = implode(LF, $content);
134        if (isset($conf['stdWrap.'])) {
135            $content = $this->cObj->stdWrap($content, $conf['stdWrap.']);
136        }
137        return $content;
138    }
139
140    /**
141     * @param array $conf
142     *
143     * @return string
144     */
145    protected function resolveAbsoluteSourcePath(array $conf): string
146    {
147        $src = isset($conf['src.']) ? $this->cObj->stdWrap($conf['src'], $conf['src.']) : $conf['src'];
148        return GeneralUtility::getFileAbsFileName($src);
149    }
150
151    /**
152     * @param array $conf
153     *
154     * @return array
155     */
156    protected function getDimensions(array $conf): array
157    {
158        $isDefaultWidth = false;
159        $isDefaultHeight = false;
160        $width = isset($conf['width.']) ? $this->cObj->stdWrap($conf['width'], $conf['width.']) : $conf['width'];
161        $height = isset($conf['height.']) ? $this->cObj->stdWrap($conf['height'], $conf['height.']) : $conf['height'];
162
163        if (empty($width)) {
164            $isDefaultWidth = true;
165            $width = 600;
166        }
167        if (empty($height)) {
168            $isDefaultHeight = true;
169            $height = 400;
170        }
171
172        return [$width, $height, $isDefaultWidth, $isDefaultHeight];
173    }
174}
175