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