xref: /plugin/combo/ComboStrap/SvgImageLink.php (revision 1fa8c418ed5809db58049141be41b7738471dd32)
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     * @param TagAttributes $tagAttributes
50     */
51    public function __construct($imageSvg)
52    {
53        parent::__construct($imageSvg);
54        $imageSvg->getAttributes()->setLogicalTag(self::CANONICAL);
55
56    }
57
58
59    private function createImgHTMLTag(): string
60    {
61
62
63        $lazyLoad = $this->getLazyLoad();
64
65        $svgInjection = PluginUtility::getConfValue(self::CONF_SVG_INJECTION_ENABLE, 1);
66        /**
67         * Snippet
68         */
69        if ($svgInjection) {
70            $snippetManager = PluginUtility::getSnippetManager();
71
72            // Based on https://github.com/iconic/SVGInjector/
73            // See also: https://github.com/iconfu/svg-inject
74            // !! There is a fork: https://github.com/tanem/svg-injector !!
75            // Fallback ? : https://github.com/iconic/SVGInjector/#per-element-png-fallback
76            $snippetManager->upsertTagsForBar("svg-injector",
77                array(
78                    'script' => [
79                        array(
80                            "src" => "https://cdn.jsdelivr.net/npm/svg-injector@1.1.3/svg-injector.min.js",
81                            // "integrity" => "sha256-CjBlJvxqLCU2HMzFunTelZLFHCJdqgDoHi/qGJWdRJk=",
82                            "crossorigin" => "anonymous"
83                        )
84                    ]
85                )
86            );
87        }
88
89        // Add lazy load snippet
90        if ($lazyLoad) {
91            LazyLoad::addLozadSnippet();
92        }
93
94        /**
95         * Remove the cache attribute
96         * (no cache for the img tag)
97         */
98        $image = $this->getDefaultImage();
99        $attributes = $image->getAttributes();
100        $attributes->removeComponentAttributeIfPresent(CacheMedia::CACHE_KEY);
101
102        /**
103         * Remove linking (not yet implemented)
104         */
105        $attributes->removeComponentAttributeIfPresent(MediaLink::LINKING_KEY);
106
107
108        /**
109         * Src
110         */
111        $srcValue = $image->getUrl(DokuwikiUrl::URL_ENCODED_AND);
112        if ($lazyLoad) {
113
114            /**
115             * Note: Responsive image srcset is not needed for svg
116             */
117            $attributes->addHtmlAttributeValue("data-src", $srcValue);
118            $attributes->addHtmlAttributeValue("src", LazyLoad::getPlaceholder(
119                $image->getTargetWidth(),
120                $image->getTargetHeight()
121            ));
122
123        } else {
124
125            $attributes->addHtmlAttributeValue("src", $srcValue);
126
127        }
128
129        /**
130         * Adaptive Image
131         * It adds a `height: auto` that avoid a layout shift when
132         * using the img tag
133         */
134        $attributes->addClassName(RasterImageLink::RESPONSIVE_CLASS);
135
136
137        /**
138         * Alt is mandatory
139         */
140        $attributes->addHtmlAttributeValue("alt", $image->getAltNotEmpty());
141
142
143        /**
144         * Class management
145         *
146         * functionalClass is the class used in Javascript
147         * that should be in the class attribute
148         * When injected, the other class should come in a `data-class` attribute
149         */
150        $svgFunctionalClass = "";
151        if ($svgInjection && $lazyLoad) {
152            PluginUtility::getSnippetManager()->attachJavascriptSnippetForBar("lozad-svg-injection");
153            $svgFunctionalClass = "lazy-svg-injection-combo";
154        } else if ($lazyLoad && !$svgInjection) {
155            PluginUtility::getSnippetManager()->attachJavascriptSnippetForBar("lozad-svg");
156            $svgFunctionalClass = "lazy-svg-combo";
157        } else if ($svgInjection && !$lazyLoad) {
158            PluginUtility::getSnippetManager()->attachJavascriptSnippetForBar("svg-injector");
159            $svgFunctionalClass = "svg-injection-combo";
160        }
161        if ($lazyLoad) {
162            // A class to all component lazy loaded to download them before print
163            $svgFunctionalClass .= " " . LazyLoad::LAZY_CLASS;
164        }
165        $attributes->addClassName($svgFunctionalClass);
166
167        /**
168         * Dimension are mandatory
169         * to avoid layout shift (CLS)
170         */
171        $attributes->addHtmlAttributeValue(Dimension::WIDTH_KEY, $image->getTargetWidth());
172        $attributes->addHtmlAttributeValue(Dimension::HEIGHT_KEY, $image->getTargetHeight());
173
174        /**
175         * Return the image
176         */
177        return '<img ' . $attributes->toHTMLAttributeString() . '/>';
178
179    }
180
181
182    /**
183     * Render a link
184     * Snippet derived from {@link \Doku_Renderer_xhtml::internalmedia()}
185     * A media can be a video also
186     * @return string
187     */
188    public function renderMediaTag(): string
189    {
190
191        /**
192         * @var ImageSvg $image
193         */
194        $image = $this->getDefaultImage();
195        if ($image->exists()) {
196
197            /**
198             * This attributes should not be in the render
199             */
200            $attributes = $this->getDefaultImage()->getAttributes();
201            $attributes->removeComponentAttributeIfPresent(MediaLink::MEDIA_DOKUWIKI_TYPE);
202            $attributes->removeComponentAttributeIfPresent(MediaLink::DOKUWIKI_SRC);
203            /**
204             * TODO: Title should be a node just below SVG
205             */
206            $attributes->removeComponentAttributeIfPresent(Page::TITLE_META_PROPERTY);
207
208            if (
209                $image->getSize() > $this->getMaxInlineSize()
210            ) {
211
212                /**
213                 * Img tag
214                 */
215                $imgHTML = $this->createImgHTMLTag();
216
217            } else {
218
219                /**
220                 * Svg tag
221                 */
222                $imgHTML = file_get_contents($image->getSvgFile());
223
224            }
225
226
227        } else {
228
229            $imgHTML = "<span class=\"text-danger\">The svg ($this) does not exist</span>";
230
231        }
232        return $imgHTML;
233    }
234
235    private function getMaxInlineSize()
236    {
237        return PluginUtility::getConfValue(self::CONF_MAX_KB_SIZE_FOR_INLINE_SVG, 2) * 1024;
238    }
239
240
241    public function getLazyLoad()
242    {
243        $lazyLoad = parent::getLazyLoad();
244        if ($lazyLoad !== null) {
245            return $lazyLoad;
246        } else {
247            return PluginUtility::getConfValue(SvgImageLink::CONF_LAZY_LOAD_ENABLE);
248        }
249    }
250
251
252}
253