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 15require_once(__DIR__ . '/PluginUtility.php'); 16 17 18/** 19 * Class IconUtility 20 * @package ComboStrap 21 * @see https://combostrap.com/icon 22 * 23 * 24 * Material design does not have a repository structure where we can extract the location 25 * from the name 26 * https://material.io/resources/icons https://google.github.io/material-design-icons/ 27 * 28 * Injection via javascript to avoid problem with the php svgsimple library 29 * https://www.npmjs.com/package/svg-injector 30 */ 31class Icon 32{ 33 const CONF_ICONS_MEDIA_NAMESPACE = "icons_namespace"; 34 const CONF_ICONS_MEDIA_NAMESPACE_DEFAULT = ":" . PluginUtility::COMBOSTRAP_NAMESPACE_NAME . ":icons"; 35 // Canonical name 36 const NAME = "icon"; 37 38 39 const ICON_LIBRARY_URLS = array( 40 self::BOOTSTRAP => "https://raw.githubusercontent.com/twbs/icons/main/icons", 41 self::MATERIAL_DESIGN => "https://raw.githubusercontent.com/Templarian/MaterialDesign/master/svg", 42 self::FEATHER => "https://raw.githubusercontent.com/feathericons/feather/master/icons", 43 self::CODE_ICON => "https://raw.githubusercontent.com/microsoft/vscode-codicons/main/src/icons/", 44 self::LOGOS => "https://raw.githubusercontent.com/gilbarbara/logos/master/logos/", 45 self::CARBON => "https://raw.githubusercontent.com/carbon-design-system/carbon/main/packages/icons/src/svg/32/" 46 ); 47 48 const ICON_LIBRARY_WEBSITE_URLS = array( 49 self::BOOTSTRAP => "https://icons.getbootstrap.com/", 50 self::MATERIAL_DESIGN => "https://materialdesignicons.com/", 51 self::FEATHER => "https://feathericons.com/", 52 self::CODE_ICON => "https://microsoft.github.io/vscode-codicons/", 53 self::LOGOS => "https://svgporn.com/", 54 self::CARBON => "https://www.carbondesignsystem.com/guidelines/icons/library/" 55 ); 56 57 const CONF_DEFAULT_ICON_LIBRARY = "defaultIconLibrary"; 58 const CONF_DEFAULT_ICON_LIBRARY_DEFAULT = self::MATERIAL_DESIGN_ACRONYM; 59 60 /** 61 * Deprecated library acronym / name 62 */ 63 const DEPRECATED_LIBRARY_ACRONYM = array( 64 "bs" => self::BOOTSTRAP, // old one (deprecated) - the good acronym is bi (seen also in the class) 65 "md" => self::MATERIAL_DESIGN 66 ); 67 68 /** 69 * Public known acronym / name (Used in the configuration) 70 */ 71 const PUBLIC_LIBRARY_ACRONYM = array( 72 "bi" => self::BOOTSTRAP, 73 self::MATERIAL_DESIGN_ACRONYM => self::MATERIAL_DESIGN, 74 "fe" => self::FEATHER, 75 "codicon" => self::CODE_ICON, 76 "logos" => self::LOGOS, 77 "carbon" => self::CARBON 78 ); 79 80 const FEATHER = "feather"; 81 const BOOTSTRAP = "bootstrap"; 82 const MATERIAL_DESIGN = "material-design"; 83 const CODE_ICON = "codicon"; 84 const LOGOS = "logos"; 85 const CARBON = "carbon"; 86 const MATERIAL_DESIGN_ACRONYM = "mdi"; 87 88 89 /** 90 * The function used to render an icon 91 * @param TagAttributes $tagAttributes - the icon attributes 92 * @return bool|mixed - false if any error or the HTML 93 */ 94 static public function renderIconByAttributes(TagAttributes $tagAttributes) 95 { 96 97 98 $name = "name"; 99 if (!$tagAttributes->hasComponentAttribute($name)) { 100 LogUtility::msg("The attributes should have a name. It's mandatory for an icon.", LogUtility::LVL_MSG_ERROR, self::NAME); 101 return false; 102 } 103 104 /** 105 * The Name 106 */ 107 $iconNameAttribute = $tagAttributes->getValue($name); 108 109 /** 110 * If the name have an extension, it's a file from the media directory 111 * Otherwise, it's an icon from a library 112 */ 113 $mediaDokuPath = DokuPath::createMediaPathFromId($iconNameAttribute); 114 if (!empty($mediaDokuPath->getExtension())) { 115 116 // loop through candidates until a match was found: 117 // May be an icon from the templates 118 if (!FileSystems::exists($mediaDokuPath)) { 119 120 // Trying to see if it's not in the template images directory 121 $message = "The media file could not be found in the media library. If you want an icon from an icon library, indicate a name without extension."; 122 $message .= "<BR> Media File Library tested: $mediaDokuPath"; 123 LogUtility::msg($message, LogUtility::LVL_MSG_ERROR, self::NAME); 124 return false; 125 126 } 127 128 } else { 129 130 // It may be a icon already downloaded 131 $iconNameSpace = PluginUtility::getConfValue(self::CONF_ICONS_MEDIA_NAMESPACE, self::CONF_ICONS_MEDIA_NAMESPACE_DEFAULT); 132 if (substr($iconNameSpace, 0, 1) != DokuPath::PATH_SEPARATOR) { 133 $iconNameSpace = DokuPath::PATH_SEPARATOR . $iconNameSpace; 134 } 135 if (substr($iconNameSpace, -1) != DokuPath::PATH_SEPARATOR) { 136 $iconNameSpace = $iconNameSpace . ":"; 137 } 138 $mediaPathId = $iconNameSpace . $iconNameAttribute . ".svg"; 139 $mediaDokuPath = DokuPath::createMediaPathFromAbsolutePath($mediaPathId); 140 141 // Bug: null file created when the stream could not get any byte 142 // We delete them 143 if (FileSystems::exists($mediaDokuPath)) { 144 if (FileSystems::getSize($mediaDokuPath) == 0) { 145 FileSystems::delete($mediaDokuPath); 146 } 147 } 148 149 if (!FileSystems::exists($mediaDokuPath)) { 150 151 /** 152 * Download the icon 153 */ 154 155 // Create the target directory if it does not exist 156 $iconDir = $mediaDokuPath->getParent(); 157 if (!FileSystems::exists($iconDir)) { 158 try { 159 FileSystems::createDirectory($iconDir); 160 } catch (ExceptionCombo $e) { 161 LogUtility::msg("The icon directory ($iconDir) could not be created.", LogUtility::LVL_MSG_ERROR, self::NAME); 162 return false; 163 } 164 } 165 166 // Name parsing to extract the library name and icon name 167 $sepPosition = strpos($iconNameAttribute, ":"); 168 $library = PluginUtility::getConfValue(self::CONF_DEFAULT_ICON_LIBRARY, self::CONF_DEFAULT_ICON_LIBRARY_DEFAULT); 169 $iconName = $iconNameAttribute; 170 if ($sepPosition != false) { 171 $library = substr($iconNameAttribute, 0, $sepPosition); 172 $iconName = substr($iconNameAttribute, $sepPosition + 1); 173 } 174 175 // Get the qualified library name 176 $acronymLibraries = self::getLibraries(); 177 if (isset($acronymLibraries[$library])) { 178 $library = $acronymLibraries[$library]; 179 } 180 181 // Get the url 182 $iconLibraries = self::ICON_LIBRARY_URLS; 183 if (!isset($iconLibraries[$library])) { 184 LogUtility::msg("The icon library ($library) is unknown. The icon could not be downloaded.", LogUtility::LVL_MSG_ERROR, self::NAME); 185 return false; 186 } else { 187 $iconBaseUrl = $iconLibraries[$library]; 188 } 189 190 // The url 191 $downloadUrl = "$iconBaseUrl/$iconName.svg"; 192 $filePointer = @fopen($downloadUrl, 'r'); 193 if ($filePointer != false) { 194 195 $numberOfByte = @file_put_contents($mediaDokuPath->toLocalPath()->toAbsolutePath()->toString(), $filePointer); 196 if ($numberOfByte != false) { 197 LogUtility::msg("The icon ($iconName) from the library ($library) was downloaded to ($mediaPathId)", LogUtility::LVL_MSG_INFO, self::NAME); 198 } else { 199 LogUtility::msg("Internal error: The icon ($iconName) from the library ($library) could no be written to ($mediaPathId)", LogUtility::LVL_MSG_ERROR, self::NAME); 200 } 201 202 } else { 203 204 // (ie no icon file found at ($downloadUrl) 205 $urlLibrary = self::ICON_LIBRARY_WEBSITE_URLS[$library]; 206 LogUtility::msg("The library (<a href=\"$urlLibrary\">$library</a>) does not have a icon (<a href=\"$downloadUrl\">$iconName</a>).", LogUtility::LVL_MSG_ERROR, self::NAME); 207 208 } 209 210 } 211 212 } 213 214 if (FileSystems::exists($mediaDokuPath)) { 215 216 217 /** 218 * After optimization, the width and height of the svg are gone 219 * but the icon type set them again 220 * 221 * The icon type is used to set: 222 * * the default dimension 223 * * color styling 224 * * disable the responsive properties 225 * 226 */ 227 $tagAttributes->addComponentAttributeValue("type", SvgDocument::ICON_TYPE); 228 229 230 $svgImageLink = SvgImageLink::createMediaLinkFromId( 231 $mediaDokuPath->getAbsolutePath(), 232 null, 233 $tagAttributes 234 ); 235 return $svgImageLink->renderMediaTag(); 236 237 } else { 238 239 return ""; 240 241 } 242 243 244 } 245 246 /** 247 * @param $iconName 248 * @param $mediaFilePath 249 * @deprecated Old code to download icon from the material design api 250 */ 251 public 252 static function downloadIconFromMaterialDesignApi($iconName, $mediaFilePath) 253 { 254 // Try the official API 255 // Read the icon meta of 256 // Meta Json file got all icons 257 // 258 // * Available at: https://raw.githubusercontent.com/Templarian/MaterialDesign/master/meta.json 259 // * See doc: https://github.com/Templarian/MaterialDesign-Site/blob/master/src/content/api.md) 260 $arrayFormat = true; 261 $iconMetaJson = json_decode(file_get_contents(__DIR__ . '/icon-meta.json'), $arrayFormat); 262 $iconId = null; 263 foreach ($iconMetaJson as $key => $value) { 264 if ($value['name'] == $iconName) { 265 $iconId = $value['id']; 266 break; 267 } 268 } 269 if ($iconId != null) { 270 271 // Download 272 // Call to the API 273 // https://dev.materialdesignicons.com/contribute/site/api 274 $downloadUrl = "https://materialdesignicons.com/api/download/icon/svg/$iconId"; 275 $filePointer = file_put_contents($mediaFilePath, fopen($downloadUrl, 'r')); 276 if ($filePointer == false) { 277 LogUtility::msg("The file ($downloadUrl) could not be downloaded to ($mediaFilePath)", LogUtility::LVL_MSG_ERROR, self::NAME); 278 } else { 279 LogUtility::msg("The material design icon ($iconName) was downloaded to ($mediaFilePath)", LogUtility::LVL_MSG_INFO, self::NAME); 280 } 281 282 } 283 284 } 285 286 private static function getLibraries() 287 { 288 return array_merge(self::PUBLIC_LIBRARY_ACRONYM, self::DEPRECATED_LIBRARY_ACRONYM); 289 } 290 291 292} 293