1<?php
2/**
3 * Copyright (c) 2020. ComboStrap, Inc. and its affiliates. All Rights Reserved.
4 *
5 * This source code is licensed under the GPL license found in the
6 * COPYING  file in the root directory of this source tree.
7 *
8 * @license  GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html)
9 * @author   ComboStrap <support@combostrap.com>
10 *
11 */
12
13namespace ComboStrap;
14
15
16require_once(__DIR__ . '/PluginUtility.php');
17
18
19/**
20 * Image
21 * This is the class that handles the
22 * svg link type
23 */
24class SvgImageLink extends ImageLink
25{
26
27    const CANONICAL = ImageSvg::CANONICAL;
28
29    /**
30     * The maximum size to be embedded
31     * Above this size limit they are fetched
32     */
33    const CONF_MAX_KB_SIZE_FOR_INLINE_SVG = "svgMaxInlineSizeKb";
34
35    /**
36     * Lazy Load
37     */
38    const CONF_LAZY_LOAD_ENABLE = "svgLazyLoadEnable";
39
40    /**
41     * Svg Injection
42     */
43    const CONF_SVG_INJECTION_ENABLE = "svgInjectionEnable";
44
45
46    /**
47     * SvgImageLink constructor.
48     * @param ImageSvg $imageSvg
49     */
50    public function __construct($imageSvg)
51    {
52        parent::__construct($imageSvg);
53        $imageSvg->getAttributes()->setLogicalTag(self::CANONICAL);
54
55    }
56
57
58    /**
59     * @throws ExceptionCombo
60     */
61    private function createImgHTMLTag(): string
62    {
63
64
65        $lazyLoad = $this->getLazyLoad();
66
67        $svgInjection = PluginUtility::getConfValue(self::CONF_SVG_INJECTION_ENABLE, 1);
68        /**
69         * Snippet
70         */
71        $snippetManager = PluginUtility::getSnippetManager();
72        if ($svgInjection) {
73
74            // Based on https://github.com/iconic/SVGInjector/
75            // See also: https://github.com/iconfu/svg-inject
76            // !! There is a fork: https://github.com/tanem/svg-injector !!
77            // Fallback ? : https://github.com/iconic/SVGInjector/#per-element-png-fallback
78            $snippetManager
79                ->attachJavascriptLibraryForSlot(
80                    "svg-injector",
81                    "https://cdn.jsdelivr.net/npm/svg-injector@1.1.3/dist/svg-injector.min.js",
82                    "sha256-CjBlJvxqLCU2HMzFunTelZLFHCJdqgDoHi/qGJWdRJk="
83                )
84                ->setDoesManipulateTheDomOnRun(false);
85
86        }
87
88        // Add lazy load snippet
89        if ($lazyLoad) {
90            LazyLoad::addLozadSnippet();
91        }
92
93        /**
94         * Remove the cache attribute
95         * (no cache for the img tag)
96         * @var ImageSvg $image
97         */
98        $image = $this->getDefaultImage();
99        $responseAttributes = TagAttributes::createFromTagAttributes($image->getAttributes());
100        $responseAttributes->removeComponentAttributeIfPresent(CacheMedia::CACHE_KEY);
101
102        /**
103         * Remove linking (not yet implemented)
104         */
105        $responseAttributes->removeComponentAttributeIfPresent(MediaLink::LINKING_KEY);
106
107
108        /**
109         * Adaptive Image
110         * It adds a `height: auto` that avoid a layout shift when
111         * using the img tag
112         */
113        $responseAttributes->addClassName(RasterImageLink::RESPONSIVE_CLASS);
114
115
116        /**
117         * Alt is mandatory
118         */
119        $responseAttributes->addOutputAttributeValue("alt", $image->getAltNotEmpty());
120
121
122        /**
123         * Class management
124         *
125         * functionalClass is the class used in Javascript
126         * that should be in the class attribute
127         * When injected, the other class should come in a `data-class` attribute
128         */
129        $svgFunctionalClass = "";
130        if ($svgInjection && $lazyLoad) {
131            $snippetManager->attachInternalJavascriptForSlot("lozad-svg-injection");
132            $svgFunctionalClass = "lazy-svg-injection-combo";
133        } else if ($lazyLoad && !$svgInjection) {
134            $snippetManager->attachInternalJavascriptForSlot("lozad-svg");
135            $svgFunctionalClass = "lazy-svg-combo";
136        } else if ($svgInjection && !$lazyLoad) {
137            $snippetManager->attachInternalJavascriptForSlot("svg-injector");
138            $svgFunctionalClass = "svg-injection-combo";
139        }
140        if ($lazyLoad) {
141            // A class to all component lazy loaded to download them before print
142            $svgFunctionalClass .= " " . LazyLoad::LAZY_CLASS;
143        }
144        $responseAttributes->addClassName($svgFunctionalClass);
145
146        /**
147         * Dimension are mandatory
148         * to avoid layout shift (CLS)
149         */
150        $responseAttributes->addOutputAttributeValue(Dimension::WIDTH_KEY, $image->getTargetWidth());
151        $responseAttributes->addOutputAttributeValue(Dimension::HEIGHT_KEY, $image->getTargetHeight());
152
153        /**
154         * Src call
155         */
156        $srcValue = $image->getUrl();
157        if ($lazyLoad) {
158
159            /**
160             * Note: Responsive image srcset is not needed for svg
161             */
162            $responseAttributes->addOutputAttributeValue("data-src", $srcValue);
163            $responseAttributes->addOutputAttributeValue("src", LazyLoad::getPlaceholder(
164                $image->getTargetWidth(),
165                $image->getTargetHeight()
166            ));
167
168        } else {
169
170            $responseAttributes->addOutputAttributeValue("src", $srcValue);
171
172        }
173
174        /**
175         * Old model where dokuwiki parses the src in handle
176         */
177        $responseAttributes->removeAttributeIfPresent(PagePath::PROPERTY_NAME);
178
179        /**
180         * Ratio is an attribute of the request, not or rendering
181         */
182        $responseAttributes->removeAttributeIfPresent(Dimension::RATIO_ATTRIBUTE);
183
184        /**
185         * Return the image
186         */
187        return '<img ' . $responseAttributes->toHTMLAttributeString() . '/>';
188
189    }
190
191
192    /**
193     * Render a link
194     * Snippet derived from {@link \Doku_Renderer_xhtml::internalmedia()}
195     * A media can be a video also
196     * @return string
197     * @throws ExceptionCombo
198     */
199    public function renderMediaTag(): string
200    {
201
202        /**
203         * @var ImageSvg $image
204         */
205        $image = $this->getDefaultImage();
206        if ($image->exists()) {
207
208            /**
209             * This attributes should not be in the render
210             */
211            $attributes = $this->getDefaultImage()->getAttributes();
212            $attributes->removeComponentAttributeIfPresent(MediaLink::MEDIA_DOKUWIKI_TYPE);
213            $attributes->removeComponentAttributeIfPresent(MediaLink::DOKUWIKI_SRC);
214            /**
215             * TODO: Title should be a node just below SVG
216             */
217            $attributes->removeComponentAttributeIfPresent(PageTitle::PROPERTY_NAME);
218
219            $imageSize = FileSystems::getSize($image->getPath());
220            if (
221                $imageSize > $this->getMaxInlineSize()
222            ) {
223
224                /**
225                 * Img tag
226                 */
227                $imgHTML = $this->createImgHTMLTag();
228
229            } else {
230
231                /**
232                 * Svg tag
233                 */
234                try {
235                    $imgHTML = FileSystems::getContent($image->getSvgFile());
236                } catch (ExceptionCombo $e) {
237                    $error = "Error while retrieving the content of the svg image ($image). Error: {$e->getMessage()}";
238                    LogUtility::msg($error);
239                    return "<span class=\"text-danger\">$error</span>";
240                }
241
242            }
243
244
245        } else {
246
247            $imgHTML = "<span class=\"text-danger\">The svg ($this) does not exist</span>";
248
249        }
250        return $imgHTML;
251    }
252
253    private function getMaxInlineSize()
254    {
255        return PluginUtility::getConfValue(self::CONF_MAX_KB_SIZE_FOR_INLINE_SVG, 2) * 1024;
256    }
257
258
259    public function getLazyLoad()
260    {
261        $lazyLoad = parent::getLazyLoad();
262        if ($lazyLoad !== null) {
263            return $lazyLoad;
264        } else {
265            return PluginUtility::getConfValue(SvgImageLink::CONF_LAZY_LOAD_ENABLE);
266        }
267    }
268
269
270}
271