xref: /plugin/combo/ComboStrap/Icon.php (revision c3437056399326d621a01da73b649707fbb0ae69)
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