xref: /template/strap/ComboStrap/SvgImageLink.php (revision 4cadd4f8c541149bdda95f080e38a6d4e3a640ca)
137748cd8SNickeau<?php
237748cd8SNickeau/**
337748cd8SNickeau * Copyright (c) 2020. ComboStrap, Inc. and its affiliates. All Rights Reserved.
437748cd8SNickeau *
537748cd8SNickeau * This source code is licensed under the GPL license found in the
637748cd8SNickeau * COPYING  file in the root directory of this source tree.
737748cd8SNickeau *
837748cd8SNickeau * @license  GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html)
937748cd8SNickeau * @author   ComboStrap <support@combostrap.com>
1037748cd8SNickeau *
1137748cd8SNickeau */
1237748cd8SNickeau
1337748cd8SNickeaunamespace ComboStrap;
1437748cd8SNickeau
151fa8c418SNickeau
1637748cd8SNickeaurequire_once(__DIR__ . '/PluginUtility.php');
171fa8c418SNickeau
1837748cd8SNickeau
1937748cd8SNickeau/**
2037748cd8SNickeau * Image
2137748cd8SNickeau * This is the class that handles the
2237748cd8SNickeau * svg link type
2337748cd8SNickeau */
241fa8c418SNickeauclass SvgImageLink extends ImageLink
2537748cd8SNickeau{
2637748cd8SNickeau
271fa8c418SNickeau    const CANONICAL = ImageSvg::CANONICAL;
2837748cd8SNickeau
2937748cd8SNickeau    /**
3037748cd8SNickeau     * The maximum size to be embedded
3137748cd8SNickeau     * Above this size limit they are fetched
3237748cd8SNickeau     */
3337748cd8SNickeau    const CONF_MAX_KB_SIZE_FOR_INLINE_SVG = "svgMaxInlineSizeKb";
3437748cd8SNickeau
3537748cd8SNickeau    /**
3637748cd8SNickeau     * Lazy Load
3737748cd8SNickeau     */
3837748cd8SNickeau    const CONF_LAZY_LOAD_ENABLE = "svgLazyLoadEnable";
3937748cd8SNickeau
4037748cd8SNickeau    /**
4137748cd8SNickeau     * Svg Injection
4237748cd8SNickeau     */
4337748cd8SNickeau    const CONF_SVG_INJECTION_ENABLE = "svgInjectionEnable";
4437748cd8SNickeau
4537748cd8SNickeau
4637748cd8SNickeau    /**
4737748cd8SNickeau     * SvgImageLink constructor.
481fa8c418SNickeau     * @param ImageSvg $imageSvg
4937748cd8SNickeau     */
501fa8c418SNickeau    public function __construct($imageSvg)
5137748cd8SNickeau    {
521fa8c418SNickeau        parent::__construct($imageSvg);
531fa8c418SNickeau        $imageSvg->getAttributes()->setLogicalTag(self::CANONICAL);
541fa8c418SNickeau
5537748cd8SNickeau    }
5637748cd8SNickeau
5737748cd8SNickeau
58*4cadd4f8SNickeau    /**
59*4cadd4f8SNickeau     * @throws ExceptionCombo
60*4cadd4f8SNickeau     */
611fa8c418SNickeau    private function createImgHTMLTag(): string
6237748cd8SNickeau    {
6337748cd8SNickeau
6437748cd8SNickeau
6537748cd8SNickeau        $lazyLoad = $this->getLazyLoad();
6637748cd8SNickeau
6737748cd8SNickeau        $svgInjection = PluginUtility::getConfValue(self::CONF_SVG_INJECTION_ENABLE, 1);
6837748cd8SNickeau        /**
6937748cd8SNickeau         * Snippet
7037748cd8SNickeau         */
7137748cd8SNickeau        $snippetManager = PluginUtility::getSnippetManager();
72*4cadd4f8SNickeau        if ($svgInjection) {
7337748cd8SNickeau
7437748cd8SNickeau            // Based on https://github.com/iconic/SVGInjector/
7537748cd8SNickeau            // See also: https://github.com/iconfu/svg-inject
7637748cd8SNickeau            // !! There is a fork: https://github.com/tanem/svg-injector !!
7737748cd8SNickeau            // Fallback ? : https://github.com/iconic/SVGInjector/#per-element-png-fallback
78*4cadd4f8SNickeau            $snippetManager
79*4cadd4f8SNickeau                ->attachJavascriptLibraryForSlot(
80*4cadd4f8SNickeau                    "svg-injector",
81*4cadd4f8SNickeau                    "https://cdn.jsdelivr.net/npm/svg-injector@1.1.3/dist/svg-injector.min.js",
82*4cadd4f8SNickeau                    "sha256-CjBlJvxqLCU2HMzFunTelZLFHCJdqgDoHi/qGJWdRJk="
8337748cd8SNickeau                )
84*4cadd4f8SNickeau                ->setDoesManipulateTheDomOnRun(false);
85*4cadd4f8SNickeau
8637748cd8SNickeau        }
8737748cd8SNickeau
8837748cd8SNickeau        // Add lazy load snippet
8937748cd8SNickeau        if ($lazyLoad) {
9037748cd8SNickeau            LazyLoad::addLozadSnippet();
9137748cd8SNickeau        }
9237748cd8SNickeau
9337748cd8SNickeau        /**
9437748cd8SNickeau         * Remove the cache attribute
9537748cd8SNickeau         * (no cache for the img tag)
96c3437056SNickeau         * @var ImageSvg $image
9737748cd8SNickeau         */
981fa8c418SNickeau        $image = $this->getDefaultImage();
99c3437056SNickeau        $responseAttributes = TagAttributes::createFromTagAttributes($image->getAttributes());
100c3437056SNickeau        $responseAttributes->removeComponentAttributeIfPresent(CacheMedia::CACHE_KEY);
10137748cd8SNickeau
10237748cd8SNickeau        /**
10337748cd8SNickeau         * Remove linking (not yet implemented)
10437748cd8SNickeau         */
105c3437056SNickeau        $responseAttributes->removeComponentAttributeIfPresent(MediaLink::LINKING_KEY);
10637748cd8SNickeau
10737748cd8SNickeau
10837748cd8SNickeau        /**
10937748cd8SNickeau         * Adaptive Image
11037748cd8SNickeau         * It adds a `height: auto` that avoid a layout shift when
11137748cd8SNickeau         * using the img tag
11237748cd8SNickeau         */
113c3437056SNickeau        $responseAttributes->addClassName(RasterImageLink::RESPONSIVE_CLASS);
11437748cd8SNickeau
11537748cd8SNickeau
11637748cd8SNickeau        /**
1171fa8c418SNickeau         * Alt is mandatory
11837748cd8SNickeau         */
119*4cadd4f8SNickeau        $responseAttributes->addOutputAttributeValue("alt", $image->getAltNotEmpty());
12037748cd8SNickeau
12137748cd8SNickeau
12237748cd8SNickeau        /**
12337748cd8SNickeau         * Class management
12437748cd8SNickeau         *
12537748cd8SNickeau         * functionalClass is the class used in Javascript
12637748cd8SNickeau         * that should be in the class attribute
12737748cd8SNickeau         * When injected, the other class should come in a `data-class` attribute
12837748cd8SNickeau         */
12937748cd8SNickeau        $svgFunctionalClass = "";
13037748cd8SNickeau        if ($svgInjection && $lazyLoad) {
131*4cadd4f8SNickeau            $snippetManager->attachInternalJavascriptForSlot("lozad-svg-injection");
13237748cd8SNickeau            $svgFunctionalClass = "lazy-svg-injection-combo";
13337748cd8SNickeau        } else if ($lazyLoad && !$svgInjection) {
134*4cadd4f8SNickeau            $snippetManager->attachInternalJavascriptForSlot("lozad-svg");
13537748cd8SNickeau            $svgFunctionalClass = "lazy-svg-combo";
13637748cd8SNickeau        } else if ($svgInjection && !$lazyLoad) {
137*4cadd4f8SNickeau            $snippetManager->attachInternalJavascriptForSlot("svg-injector");
13837748cd8SNickeau            $svgFunctionalClass = "svg-injection-combo";
13937748cd8SNickeau        }
14037748cd8SNickeau        if ($lazyLoad) {
14137748cd8SNickeau            // A class to all component lazy loaded to download them before print
14237748cd8SNickeau            $svgFunctionalClass .= " " . LazyLoad::LAZY_CLASS;
14337748cd8SNickeau        }
144c3437056SNickeau        $responseAttributes->addClassName($svgFunctionalClass);
14537748cd8SNickeau
14637748cd8SNickeau        /**
14737748cd8SNickeau         * Dimension are mandatory
14837748cd8SNickeau         * to avoid layout shift (CLS)
14937748cd8SNickeau         */
150*4cadd4f8SNickeau        $responseAttributes->addOutputAttributeValue(Dimension::WIDTH_KEY, $image->getTargetWidth());
151*4cadd4f8SNickeau        $responseAttributes->addOutputAttributeValue(Dimension::HEIGHT_KEY, $image->getTargetHeight());
152c3437056SNickeau
153c3437056SNickeau        /**
154c3437056SNickeau         * Src call
155c3437056SNickeau         */
156*4cadd4f8SNickeau        $srcValue = $image->getUrl();
157c3437056SNickeau        if ($lazyLoad) {
158c3437056SNickeau
159c3437056SNickeau            /**
160c3437056SNickeau             * Note: Responsive image srcset is not needed for svg
161c3437056SNickeau             */
162*4cadd4f8SNickeau            $responseAttributes->addOutputAttributeValue("data-src", $srcValue);
163*4cadd4f8SNickeau            $responseAttributes->addOutputAttributeValue("src", LazyLoad::getPlaceholder(
164c3437056SNickeau                $image->getTargetWidth(),
165c3437056SNickeau                $image->getTargetHeight()
166c3437056SNickeau            ));
167c3437056SNickeau
168c3437056SNickeau        } else {
169c3437056SNickeau
170*4cadd4f8SNickeau            $responseAttributes->addOutputAttributeValue("src", $srcValue);
171c3437056SNickeau
172c3437056SNickeau        }
173c3437056SNickeau
174c3437056SNickeau        /**
175c3437056SNickeau         * Old model where dokuwiki parses the src in handle
176c3437056SNickeau         */
177c3437056SNickeau        $responseAttributes->removeAttributeIfPresent(PagePath::PROPERTY_NAME);
17837748cd8SNickeau
17937748cd8SNickeau        /**
18082a60d03SNickeau         * Ratio is an attribute of the request, not or rendering
18182a60d03SNickeau         */
18282a60d03SNickeau        $responseAttributes->removeAttributeIfPresent(Dimension::RATIO_ATTRIBUTE);
18382a60d03SNickeau
18482a60d03SNickeau        /**
18537748cd8SNickeau         * Return the image
18637748cd8SNickeau         */
187c3437056SNickeau        return '<img ' . $responseAttributes->toHTMLAttributeString() . '/>';
18837748cd8SNickeau
18937748cd8SNickeau    }
19037748cd8SNickeau
19137748cd8SNickeau
19237748cd8SNickeau    /**
19337748cd8SNickeau     * Render a link
19437748cd8SNickeau     * Snippet derived from {@link \Doku_Renderer_xhtml::internalmedia()}
19537748cd8SNickeau     * A media can be a video also
19637748cd8SNickeau     * @return string
197*4cadd4f8SNickeau     * @throws ExceptionCombo
19837748cd8SNickeau     */
1991fa8c418SNickeau    public function renderMediaTag(): string
20037748cd8SNickeau    {
20137748cd8SNickeau
2021fa8c418SNickeau        /**
2031fa8c418SNickeau         * @var ImageSvg $image
2041fa8c418SNickeau         */
2051fa8c418SNickeau        $image = $this->getDefaultImage();
2061fa8c418SNickeau        if ($image->exists()) {
20737748cd8SNickeau
20837748cd8SNickeau            /**
20937748cd8SNickeau             * This attributes should not be in the render
21037748cd8SNickeau             */
2111fa8c418SNickeau            $attributes = $this->getDefaultImage()->getAttributes();
2121fa8c418SNickeau            $attributes->removeComponentAttributeIfPresent(MediaLink::MEDIA_DOKUWIKI_TYPE);
2131fa8c418SNickeau            $attributes->removeComponentAttributeIfPresent(MediaLink::DOKUWIKI_SRC);
21437748cd8SNickeau            /**
21537748cd8SNickeau             * TODO: Title should be a node just below SVG
21637748cd8SNickeau             */
217c3437056SNickeau            $attributes->removeComponentAttributeIfPresent(PageTitle::PROPERTY_NAME);
21837748cd8SNickeau
219c3437056SNickeau            $imageSize = FileSystems::getSize($image->getPath());
22037748cd8SNickeau            if (
221c3437056SNickeau                $imageSize > $this->getMaxInlineSize()
22237748cd8SNickeau            ) {
22337748cd8SNickeau
22437748cd8SNickeau                /**
22537748cd8SNickeau                 * Img tag
22637748cd8SNickeau                 */
22737748cd8SNickeau                $imgHTML = $this->createImgHTMLTag();
22837748cd8SNickeau
22937748cd8SNickeau            } else {
23037748cd8SNickeau
23137748cd8SNickeau                /**
23237748cd8SNickeau                 * Svg tag
23337748cd8SNickeau                 */
234*4cadd4f8SNickeau                try {
235c3437056SNickeau                    $imgHTML = FileSystems::getContent($image->getSvgFile());
236*4cadd4f8SNickeau                } catch (ExceptionCombo $e) {
237*4cadd4f8SNickeau                    $error = "Error while retrieving the content of the svg image ($image). Error: {$e->getMessage()}";
238*4cadd4f8SNickeau                    LogUtility::msg($error);
239*4cadd4f8SNickeau                    return "<span class=\"text-danger\">$error</span>";
240*4cadd4f8SNickeau                }
24137748cd8SNickeau
24237748cd8SNickeau            }
24337748cd8SNickeau
24437748cd8SNickeau
24537748cd8SNickeau        } else {
24637748cd8SNickeau
24737748cd8SNickeau            $imgHTML = "<span class=\"text-danger\">The svg ($this) does not exist</span>";
24837748cd8SNickeau
24937748cd8SNickeau        }
25037748cd8SNickeau        return $imgHTML;
25137748cd8SNickeau    }
25237748cd8SNickeau
25337748cd8SNickeau    private function getMaxInlineSize()
25437748cd8SNickeau    {
25537748cd8SNickeau        return PluginUtility::getConfValue(self::CONF_MAX_KB_SIZE_FOR_INLINE_SVG, 2) * 1024;
25637748cd8SNickeau    }
25737748cd8SNickeau
25837748cd8SNickeau
25937748cd8SNickeau    public function getLazyLoad()
26037748cd8SNickeau    {
26137748cd8SNickeau        $lazyLoad = parent::getLazyLoad();
26237748cd8SNickeau        if ($lazyLoad !== null) {
26337748cd8SNickeau            return $lazyLoad;
26437748cd8SNickeau        } else {
26537748cd8SNickeau            return PluginUtility::getConfValue(SvgImageLink::CONF_LAZY_LOAD_ENABLE);
26637748cd8SNickeau        }
26737748cd8SNickeau    }
26837748cd8SNickeau
26937748cd8SNickeau
27037748cd8SNickeau}
271