xref: /plugin/combo/ComboStrap/SvgImageLink.php (revision 82a60d039cd81033dc8147c27f0a50716b7a5301)
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    private function createImgHTMLTag(): string
59    {
60
61
62        $lazyLoad = $this->getLazyLoad();
63
64        $svgInjection = PluginUtility::getConfValue(self::CONF_SVG_INJECTION_ENABLE, 1);
65        /**
66         * Snippet
67         */
68        if ($svgInjection) {
69            $snippetManager = PluginUtility::getSnippetManager();
70
71            // Based on https://github.com/iconic/SVGInjector/
72            // See also: https://github.com/iconfu/svg-inject
73            // !! There is a fork: https://github.com/tanem/svg-injector !!
74            // Fallback ? : https://github.com/iconic/SVGInjector/#per-element-png-fallback
75            $snippetManager->upsertTagsForBar("svg-injector",
76                array(
77                    'script' => [
78                        array(
79                            "src" => "https://cdn.jsdelivr.net/npm/svg-injector@1.1.3/svg-injector.min.js",
80                            // "integrity" => "sha256-CjBlJvxqLCU2HMzFunTelZLFHCJdqgDoHi/qGJWdRJk=",
81                            "crossorigin" => "anonymous"
82                        )
83                    ]
84                )
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->addHtmlAttributeValue("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            PluginUtility::getSnippetManager()->attachJavascriptSnippetForBar("lozad-svg-injection");
132            $svgFunctionalClass = "lazy-svg-injection-combo";
133        } else if ($lazyLoad && !$svgInjection) {
134            PluginUtility::getSnippetManager()->attachJavascriptSnippetForBar("lozad-svg");
135            $svgFunctionalClass = "lazy-svg-combo";
136        } else if ($svgInjection && !$lazyLoad) {
137            PluginUtility::getSnippetManager()->attachJavascriptSnippetForBar("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->addHtmlAttributeValue(Dimension::WIDTH_KEY, $image->getTargetWidth());
151        $responseAttributes->addHtmlAttributeValue(Dimension::HEIGHT_KEY, $image->getTargetHeight());
152
153        /**
154         * Src call
155         */
156        $srcValue = $image->getUrl(DokuwikiUrl::AMPERSAND_URL_ENCODED_FOR_HTML);
157        if ($lazyLoad) {
158
159            /**
160             * Note: Responsive image srcset is not needed for svg
161             */
162            $responseAttributes->addHtmlAttributeValue("data-src", $srcValue);
163            $responseAttributes->addHtmlAttributeValue("src", LazyLoad::getPlaceholder(
164                $image->getTargetWidth(),
165                $image->getTargetHeight()
166            ));
167
168        } else {
169
170            $responseAttributes->addHtmlAttributeValue("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     */
198    public function renderMediaTag(): string
199    {
200
201        /**
202         * @var ImageSvg $image
203         */
204        $image = $this->getDefaultImage();
205        if ($image->exists()) {
206
207            /**
208             * This attributes should not be in the render
209             */
210            $attributes = $this->getDefaultImage()->getAttributes();
211            $attributes->removeComponentAttributeIfPresent(MediaLink::MEDIA_DOKUWIKI_TYPE);
212            $attributes->removeComponentAttributeIfPresent(MediaLink::DOKUWIKI_SRC);
213            /**
214             * TODO: Title should be a node just below SVG
215             */
216            $attributes->removeComponentAttributeIfPresent(PageTitle::PROPERTY_NAME);
217
218            $imageSize = FileSystems::getSize($image->getPath());
219            if (
220                $imageSize > $this->getMaxInlineSize()
221            ) {
222
223                /**
224                 * Img tag
225                 */
226                $imgHTML = $this->createImgHTMLTag();
227
228            } else {
229
230                /**
231                 * Svg tag
232                 */
233                $imgHTML = FileSystems::getContent($image->getSvgFile());
234
235            }
236
237
238        } else {
239
240            $imgHTML = "<span class=\"text-danger\">The svg ($this) does not exist</span>";
241
242        }
243        return $imgHTML;
244    }
245
246    private function getMaxInlineSize()
247    {
248        return PluginUtility::getConfValue(self::CONF_MAX_KB_SIZE_FOR_INLINE_SVG, 2) * 1024;
249    }
250
251
252    public function getLazyLoad()
253    {
254        $lazyLoad = parent::getLazyLoad();
255        if ($lazyLoad !== null) {
256            return $lazyLoad;
257        } else {
258            return PluginUtility::getConfValue(SvgImageLink::CONF_LAZY_LOAD_ENABLE);
259        }
260    }
261
262
263}
264