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