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 * Return the image 181 */ 182 return '<img ' . $responseAttributes->toHTMLAttributeString() . '/>'; 183 184 } 185 186 187 /** 188 * Render a link 189 * Snippet derived from {@link \Doku_Renderer_xhtml::internalmedia()} 190 * A media can be a video also 191 * @return string 192 */ 193 public function renderMediaTag(): string 194 { 195 196 /** 197 * @var ImageSvg $image 198 */ 199 $image = $this->getDefaultImage(); 200 if ($image->exists()) { 201 202 /** 203 * This attributes should not be in the render 204 */ 205 $attributes = $this->getDefaultImage()->getAttributes(); 206 $attributes->removeComponentAttributeIfPresent(MediaLink::MEDIA_DOKUWIKI_TYPE); 207 $attributes->removeComponentAttributeIfPresent(MediaLink::DOKUWIKI_SRC); 208 /** 209 * TODO: Title should be a node just below SVG 210 */ 211 $attributes->removeComponentAttributeIfPresent(PageTitle::PROPERTY_NAME); 212 213 $imageSize = FileSystems::getSize($image->getPath()); 214 if ( 215 $imageSize > $this->getMaxInlineSize() 216 ) { 217 218 /** 219 * Img tag 220 */ 221 $imgHTML = $this->createImgHTMLTag(); 222 223 } else { 224 225 /** 226 * Svg tag 227 */ 228 $imgHTML = FileSystems::getContent($image->getSvgFile()); 229 230 } 231 232 233 } else { 234 235 $imgHTML = "<span class=\"text-danger\">The svg ($this) does not exist</span>"; 236 237 } 238 return $imgHTML; 239 } 240 241 private function getMaxInlineSize() 242 { 243 return PluginUtility::getConfValue(self::CONF_MAX_KB_SIZE_FOR_INLINE_SVG, 2) * 1024; 244 } 245 246 247 public function getLazyLoad() 248 { 249 $lazyLoad = parent::getLazyLoad(); 250 if ($lazyLoad !== null) { 251 return $lazyLoad; 252 } else { 253 return PluginUtility::getConfValue(SvgImageLink::CONF_LAZY_LOAD_ENABLE); 254 } 255 } 256 257 258} 259