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 Icon
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 IconDownloader
32{
33
34    const CONF_ICONS_MEDIA_NAMESPACE = "icons_namespace";
35    const CONF_ICONS_MEDIA_NAMESPACE_DEFAULT = ":" . PluginUtility::COMBOSTRAP_NAMESPACE_NAME . ":icons";
36
37
38    const ICON_LIBRARY_URLS = array(
39        self::ANT_DESIGN => "https://raw.githubusercontent.com/ant-design/ant-design-icons/master/packages/icons-svg/svg",
40        self::BOOTSTRAP => "https://raw.githubusercontent.com/twbs/icons/main/icons",
41        self::CARBON => "https://raw.githubusercontent.com/carbon-design-system/carbon/main/packages/icons/src/svg/32",
42        self::CLARITY => "https://raw.githubusercontent.com/vmware/clarity-assets/master/icons/essential",
43        self::CODE_ICON => "https://raw.githubusercontent.com/microsoft/vscode-codicons/main/src/icons",
44        self::ELEGANT_THEME => "https://raw.githubusercontent.com/pprince/etlinefont-bower/master/images/svg/individual_icons",
45        self::ENTYPO => "https://raw.githubusercontent.com/hypermodules/entypo/master/src/Entypo",
46        self::ENTYPO_SOCIAL => "https://raw.githubusercontent.com/hypermodules/entypo/master/src/Entypo%20Social%20Extension",
47        self::EVA => "https://raw.githubusercontent.com/akveo/eva-icons/master/package/icons",
48        self::FEATHER => "https://raw.githubusercontent.com/feathericons/feather/master/icons",
49        self::FAD => "https://raw.githubusercontent.com/fefanto/fontaudio/master/svgs",
50        self::ICONSCOUT => "https://raw.githubusercontent.com/Iconscout/unicons/master/svg/line",
51        self::LOGOS => "https://raw.githubusercontent.com/gilbarbara/logos/master/logos",
52        self::MATERIAL_DESIGN => "https://raw.githubusercontent.com/Templarian/MaterialDesign/master/svg",
53        self::OCTICON => "https://raw.githubusercontent.com/primer/octicons/main/icons",
54        self::TWEET_EMOJI => "https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg",
55        self::SIMPLE_LINE => "https://raw.githubusercontent.com/thesabbir/simple-line-icons/master/src/svgs",
56        self::ICOMOON => "https://raw.githubusercontent.com/Keyamoon/IcoMoon-Free/master/SVG",
57        self::DASHICONS => "https://raw.githubusercontent.com/WordPress/dashicons/master/svg-min",
58        self::ICONOIR => "https://raw.githubusercontent.com/lucaburgio/iconoir/master/icons",
59        self::BOX_ICON => "https://raw.githubusercontent.com/atisawd/boxicons/master/svg",
60        self::LINE_AWESOME => "https://raw.githubusercontent.com/icons8/line-awesome/master/svg",
61        self::FONT_AWESOME_SOLID => "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/svgs/solid",
62        self::FONT_AWESOME_BRANDS => "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/svgs/brands",
63        self::FONT_AWESOME_REGULAR => "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/svgs/regular",
64        self::VAADIN => "https://raw.githubusercontent.com/vaadin/vaadin-icons/master/assets/svg",
65        self::CORE_UI_BRAND => "https://raw.githubusercontent.com/coreui/coreui-icons/master/svg/brand",
66        self::FLAT_COLOR_ICON => "https://raw.githubusercontent.com/icons8/flat-color-icons/master/svg",
67        self::PHOSPHOR_ICONS => "https://raw.githubusercontent.com/phosphor-icons/phosphor-icons/master/assets",
68        self::VSCODE => "https://raw.githubusercontent.com/vscode-icons/vscode-icons/master/icons",
69        self::SI_GLYPH => "https://raw.githubusercontent.com/frexy/glyph-iconset/master/svg",
70        self::AKAR_ICONS => "https://raw.githubusercontent.com/artcoholic/akar-icons/master/src/svg",
71        self::ARCTICONS => "https://raw.githubusercontent.com/Donnnno/Arcticons/main/icons/black",
72        self::HEALTH_ICONS => "https://raw.githubusercontent.com/resolvetosavelives/healthicons/main/public/icons/svg"
73    );
74
75    const ICON_LIBRARY_WEBSITE_URLS = array(
76        self::BOOTSTRAP => "https://icons.getbootstrap.com/",
77        self::MATERIAL_DESIGN => "https://materialdesignicons.com/",
78        self::FEATHER => "https://feathericons.com/",
79        self::CODE_ICON => "https://microsoft.github.io/vscode-codicons/",
80        self::LOGOS => "https://svgporn.com/",
81        self::CARBON => "https://www.carbondesignsystem.com/guidelines/icons/library/",
82        self::TWEET_EMOJI => "https://twemoji.twitter.com/",
83        self::ANT_DESIGN => "https://ant.design/components/icon/",
84        self::CLARITY => "https://clarity.design/foundation/icons/",
85        self::OCTICON => "https://primer.style/octicons/",
86        self::ICONSCOUT => "https://iconscout.com/unicons/explore/line",
87        self::ELEGANT_THEME => "https://github.com/pprince/etlinefont-bower",
88        self::EVA => "https://akveo.github.io/eva-icons/",
89        self::ENTYPO_SOCIAL => "http://www.entypo.com",
90        self::ENTYPO => "http://www.entypo.com",
91        self::SIMPLE_LINE => "https://thesabbir.github.io/simple-line-icons",
92        self::ICOMOON => "https://icomoon.io/",
93        self::DASHICONS => "https://developer.wordpress.org/resource/dashicons/",
94        self::ICONOIR => "https://iconoir.com",
95        self::BOX_ICON => "https://boxicons.com",
96        self::LINE_AWESOME => "https://icons8.com/line-awesome",
97        self::FONT_AWESOME => "https://fontawesome.com/",
98        self::FONT_AWESOME_SOLID => "https://fontawesome.com/",
99        self::FONT_AWESOME_BRANDS => "https://fontawesome.com/",
100        self::FONT_AWESOME_REGULAR => "https://fontawesome.com/",
101        self::VAADIN => "https://vaadin.com/icons",
102        self::CORE_UI_BRAND => "https://coreui.io/icons/",
103        self::FLAT_COLOR_ICON => "https://icons8.com/icons/color",
104        self::PHOSPHOR_ICONS => "https://phosphoricons.com/",
105        self::VSCODE => "https://marketplace.visualstudio.com/items?itemName=vscode-icons-team.vscode-icons",
106        self::SI_GLYPH => "https://glyph.smarticons.co/",
107        self::AKAR_ICONS => "https://akaricons.com/",
108        self::ARCTICONS => "https://arcticons.com/",
109        self::HEALTH_ICONS => "https://healthicons.org/",
110        self::MATERIAL_DESIGN_ACRONYM => "https://materialdesignicons.com/",
111        self::COMBO => ""
112    );
113
114    const CONF_DEFAULT_ICON_LIBRARY = "defaultIconLibrary";
115    const CONF_DEFAULT_ICON_LIBRARY_DEFAULT = self::MATERIAL_DESIGN_ACRONYM;
116
117    /**
118     * Deprecated library acronym / name
119     */
120    const DEPRECATED_LIBRARY_ACRONYM = array(
121        "bs" => self::BOOTSTRAP, // old one (deprecated) - the good acronym is bi (seen also in the class)
122        "md" => self::MATERIAL_DESIGN
123    );
124
125    /**
126     * Public known acronym / name (Used in the configuration)
127     */
128    const PUBLIC_LIBRARY_ACRONYM = array(
129        "bi" => self::BOOTSTRAP,
130        self::MATERIAL_DESIGN_ACRONYM => self::MATERIAL_DESIGN,
131        "fe" => self::FEATHER,
132        "codicon" => self::CODE_ICON,
133        "logos" => self::LOGOS,
134        "carbon" => self::CARBON,
135        "twemoji" => self::TWEET_EMOJI,
136        "ant-design" => self::ANT_DESIGN,
137        "fad" => self::FAD,
138        "clarity" => self::CLARITY,
139        "octicon" => self::OCTICON,
140        "uit" => self::ICONSCOUT,
141        "et" => self::ELEGANT_THEME,
142        "eva" => self::EVA,
143        "entypo-social" => self::ENTYPO_SOCIAL,
144        "entypo" => self::ENTYPO,
145        "simple-line-icons" => self::SIMPLE_LINE,
146        "icomoon-free" => self::ICOMOON,
147        "dashicons" => self::DASHICONS,
148        "iconoir" => self::ICONOIR,
149        "bx" => self::BOX_ICON,
150        "la" => self::LINE_AWESOME,
151        "fa-solid" => self::FONT_AWESOME_SOLID,
152        "fa-brands" => self::FONT_AWESOME_BRANDS,
153        "fa-regular" => self::FONT_AWESOME_REGULAR,
154        "vaadin" => self::VAADIN,
155        "cib" => self::CORE_UI_BRAND,
156        "flat-color-icons" => self::FLAT_COLOR_ICON,
157        "ph" => self::PHOSPHOR_ICONS,
158        "vscode-icons" => self::VSCODE,
159        "si-glyph" => self::SI_GLYPH,
160        "akar-icons" => self::AKAR_ICONS,
161        "arcticons" => self::ARCTICONS,
162        "healthicons" => self::HEALTH_ICONS,
163        "combo" => self::COMBO
164    );
165
166    const FEATHER = "feather";
167    const BOOTSTRAP = "bootstrap";
168    const MATERIAL_DESIGN = "material-design";
169    const CODE_ICON = "codicon";
170    const LOGOS = "logos";
171    const CARBON = "carbon";
172    const MATERIAL_DESIGN_ACRONYM = "mdi";
173    const TWEET_EMOJI = "twemoji";
174    const ANT_DESIGN = "ant-design";
175    const FAD = "fad";
176    const CLARITY = "clarity";
177    const OCTICON = "octicon";
178    const ICONSCOUT = "iconscout";
179    const ELEGANT_THEME = "elegant-theme";
180    const EVA = "eva";
181    const ENTYPO_SOCIAL = "entypo-social";
182    const ENTYPO = "entypo";
183    const SIMPLE_LINE = "simple-line";
184    const ICOMOON = "icomoon";
185    const DASHICONS = " dashicons";
186    const ICONOIR = "iconoir";
187    const BOX_ICON = "box-icon";
188    const LINE_AWESOME = "line-awesome";
189    const FONT_AWESOME_SOLID = "font-awesome-solid";
190    const FONT_AWESOME_BRANDS = "font-awesome-brands";
191    const FONT_AWESOME_REGULAR = "font-awesome-regular";
192    const FONT_AWESOME = "font-awesome";
193    const VAADIN = "vaadin";
194    const CORE_UI_BRAND = "cib";
195    const FLAT_COLOR_ICON = "flat-color-icons";
196    const PHOSPHOR_ICONS = "ph";
197    const VSCODE = "vscode";
198    const SI_GLYPH = "si-glyph";
199    const COMBO = WikiPath::COMBO_DRIVE;
200    const AKAR_ICONS = "akar-icons";
201    const ARCTICONS = "articons";
202    const HEALTH_ICONS = "healthicons";
203
204
205    /**
206     * The icon library
207     * @var mixed|null
208     */
209    private $library;
210    /**
211     * @var false|string
212     */
213    private $iconName;
214    private WikiPath $path;
215
216
217    /**
218     * @throws ExceptionBadArgument|ExceptionFileSystem
219     */
220    public function __construct(string $name)
221    {
222
223        $iconNameSpace = SiteConfig::getConfValue(self::CONF_ICONS_MEDIA_NAMESPACE, self::CONF_ICONS_MEDIA_NAMESPACE_DEFAULT);
224        if (substr($iconNameSpace, 0, 1) != WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT) {
225            $iconNameSpace = WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT . $iconNameSpace;
226        }
227        if (substr($iconNameSpace, -1) != WikiPath::NAMESPACE_SEPARATOR_DOUBLE_POINT) {
228            $iconNameSpace = $iconNameSpace . ":";
229        }
230        $mediaPathId = $iconNameSpace . $name . ".svg";;
231        $this->path = WikiPath::createMediaPathFromPath($mediaPathId);
232        // Bug: null file created when the stream could not get any byte
233        // We delete them
234        if (FileSystems::exists($this->path)) {
235            if (FileSystems::getSize($this->path) === 0) {
236                FileSystems::delete($this->path);
237            }
238        }
239
240        /**
241         * Name parsing to extract the library name and icon name
242         */
243        // default
244        $confValue = SiteConfig::getConfValue(self::CONF_DEFAULT_ICON_LIBRARY, self::CONF_DEFAULT_ICON_LIBRARY_DEFAULT);
245        $this->setLibrary($confValue);
246        $this->setIconName($name);
247        // parse
248        $sepPosition = strpos($name, ":");
249        if ($sepPosition !== false) {
250            $libraryName = substr($name, 0, $sepPosition);
251            $this->setLibrary($libraryName);
252            $iconName = substr($name, $sepPosition + 1);
253            $this->setIconName($iconName);
254
255            /**
256             * Special case, internal library
257             */
258            if ($this->getLibrary() === self::COMBO) {
259                $this->path = WikiPath::createComboResource($iconName . ".svg");
260            }
261        }
262
263
264    }
265
266
267    public static
268    function isInIconDirectory(Path $path): bool
269    {
270        $iconNameSpace = SiteConfig::getConfValue(IconDownloader::CONF_ICONS_MEDIA_NAMESPACE, IconDownloader::CONF_ICONS_MEDIA_NAMESPACE_DEFAULT);
271        if (strpos($path->toAbsoluteId(), $iconNameSpace) !== false) {
272            return true;
273        }
274        return false;
275    }
276
277
278    /**
279     * @throws ExceptionBadArgument - if the icon library is not supported
280     * @throws ExceptionFileSystem
281     */
282    public static function createFromName(string $name): IconDownloader
283    {
284        return new IconDownloader($name);
285    }
286
287    /**
288     * @throws ExceptionCompile
289     */
290    private static function getPhysicalNameFromDictionary(string $logicalName, string $library)
291    {
292
293        $jsonArray = Dictionary::getFrom("$library-icons");
294        $physicalName = $jsonArray[$logicalName];
295        if ($physicalName === null) {
296            LogUtility::msg("The icon ($logicalName) is unknown for the library ($library)");
297            // by default, just lowercase
298            return strtolower($logicalName);
299        }
300        return $physicalName;
301
302    }
303
304    public function getIconName(): string
305    {
306        return $this->iconName;
307    }
308
309    /**
310     * @throws ExceptionCompile
311     */
312    public function getDownloadUrl(): string
313    {
314
315        /**
316         * The test of the supported library
317         * happens lately because the user may install them manually
318         */
319        $library = $this->library;
320        if (!in_array($library, array_keys($this->getLibraries()))) {
321            throw new ExceptionBadArgument("The library ($library) is not a icon library supported");
322        }
323
324        // Get the qualified library name
325        $acronymLibraries = self::getLibraries();
326        if (isset($acronymLibraries[$library])) {
327            $library = $acronymLibraries[$library];
328        }
329
330        // Get the url
331        $iconLibraries = self::ICON_LIBRARY_URLS;
332        if (!isset($iconLibraries[$library])) {
333            throw new ExceptionCompile("The icon library ($library) is unknown. The icon could not be downloaded.", Icon::ICON_CANONICAL_NAME);
334        } else {
335            $iconBaseUrl = $iconLibraries[$library];
336        }
337
338        /**
339         * Name processing
340         */
341        $iconName = $this->iconName;
342        switch ($library) {
343
344            case self::VSCODE:
345            case self::FLAT_COLOR_ICON:
346                $iconName = str_replace("-", "_", $iconName);
347                break;
348            case self::TWEET_EMOJI:
349                try {
350                    $iconName = self::getEmojiCodePoint($iconName);
351                } catch (ExceptionCompile $e) {
352                    throw new ExceptionCompile("The emoji name $iconName is unknown. The emoji could not be downloaded.", Icon::ICON_CANONICAL_NAME, 0, $e);
353                }
354                break;
355            case self::ANT_DESIGN:
356                // table-outlined where table is the svg, outlined the category
357                // ordered-list-outlined where ordered-list is the svg, outlined the category
358                [$iconName, $iconType] = self::explodeInTwoPartsByLastPosition($iconName, "-");
359                $iconBaseUrl .= "/$iconType";
360                break;
361            case self::CARBON:
362                /**
363                 * Iconify normalized the name of the carbon library (making them lowercase)
364                 *
365                 * For instance, CSV is csv (https://icon-sets.iconify.design/carbon/csv/)
366                 *
367                 * This dictionary reproduce it.
368                 */
369                $iconName = self::getPhysicalNameFromDictionary($iconName, self::CARBON);
370                break;
371            case self::FAD:
372                $iconName = self::getPhysicalNameFromDictionary($iconName, self::FAD);
373                break;
374            case self::ICOMOON:
375                $iconName = self::getPhysicalNameFromDictionary($iconName, self::ICOMOON);
376                break;
377            case self::CORE_UI_BRAND:
378                $iconName = self::getPhysicalNameFromDictionary($iconName, self::CORE_UI_BRAND);
379                break;
380            case self::EVA:
381                // Eva
382                // example: eva:facebook-fill
383                [$iconName, $iconType] = self::explodeInTwoPartsByLastPosition($iconName, "-");
384                $iconBaseUrl .= "/$iconType/svg";
385                if ($iconType === "outline") {
386                    // for whatever reason, the name of outline icon has outline at the end
387                    // and not for the fill icon
388                    $iconName .= "-$iconType";
389                }
390                break;
391            case self::PHOSPHOR_ICONS:
392                // example: activity-light
393                [$iconShortName, $iconType] = self::explodeInTwoPartsByLastPosition($iconName, "-");
394                $iconBaseUrl .= "/$iconType";
395                break;
396            case self::SIMPLE_LINE:
397                // Bug
398                if ($iconName === "social-pinterest") {
399                    $iconName = "social-pintarest";
400                }
401                break;
402            case self::BOX_ICON:
403                [$iconType, $extractedIconName] = self::explodeInTwoPartsByLastPosition($iconName, "-");
404                switch ($iconType) {
405                    case "bxl":
406                        $iconBaseUrl .= "/logos";
407                        break;
408                    case "bx":
409                        $iconBaseUrl .= "/regular";
410                        break;
411                    case "bxs":
412                        $iconBaseUrl .= "/solid";
413                        break;
414                    default:
415                        throw new ExceptionCompile("The box-icon icon ($iconName) has a type ($iconType) that is unknown, we can't determine the location of the icon to download");
416                }
417                break;
418            case self::SI_GLYPH:
419                $iconName = "si-glyph-" . $iconName;
420                break;
421            case self::HEALTH_ICONS:
422                [$extractedIconName, $iconType] = self::explodeInTwoPartsByLastPosition($iconName, "-");
423                switch ($iconType) {
424                    case "outline":
425                    case "negative":
426                        $iconBaseUrl .= "/$iconType";
427                        $iconName = $extractedIconName;
428                        break;
429                    default:
430                        // no
431                        $iconBaseUrl .= "/filled";
432                }
433                $iconName = self::getPhysicalNameFromDictionary($iconName, self::HEALTH_ICONS);
434                break;
435
436        }
437
438
439        // The url
440        return "$iconBaseUrl/$iconName.svg";
441
442    }
443
444    /**
445     * @throws ExceptionCompile
446     */
447    public function download()
448    {
449
450
451        $libraryName = $this->getLibrary();
452        $mediaDokuPath = $this->path;
453
454        /**
455         * Create the target directory if it does not exist
456         */
457        $iconDir = $mediaDokuPath->getParent();
458        if (!FileSystems::exists($iconDir)) {
459            try {
460                FileSystems::createDirectory($iconDir);
461            } catch (ExceptionCompile $e) {
462                throw new ExceptionCompile("The icon directory ($iconDir) could not be created.", Icon::ICON_CANONICAL_NAME, 0, $e);
463            }
464        }
465
466        /**
467         * Download the icon
468         * The `@` delete the E_WARNING upon failure
469         *
470         * https://www.php.net/manual/en/function.fopen.php
471         */
472        $downloadUrl = $this->getDownloadUrl();
473        ErrorHandler::phpErrorAsException();
474        try {
475            $filePointer = fopen($downloadUrl, 'r');
476        } catch (\Exception $e) {
477            // (ie no icon file found at ($downloadUrl)
478            $message = "We couldn't find the <a href=\"$downloadUrl\">icon $this->iconName</a>) from the";
479            try {
480                $urlLibrary = $this->getLibraryUrl();
481                $message = "$message <a href=\"$urlLibrary\">library $libraryName</a>";
482            } catch (ExceptionNotFound $e) {
483                if (PluginUtility::isDevOrTest()) {
484                    throw $e;
485                }
486                $message = "$message library $libraryName";
487            }
488            $message = "$message. Error: {$e->getMessage()}";
489            throw new ExceptionCompile($message, Icon::ICON_CANONICAL_NAME);
490        } finally {
491            ErrorHandler::restore();
492        }
493
494        $numberOfByte = file_put_contents($mediaDokuPath->toLocalPath()->toAbsolutePath()->toAbsoluteId(), $filePointer);
495        if ($numberOfByte !== false) {
496            LogUtility::msg("The icon ($this) from the library ($libraryName) was downloaded to ($mediaDokuPath)", LogUtility::LVL_MSG_INFO, Icon::ICON_CANONICAL_NAME);
497        } else {
498            LogUtility::msg("Internal error: The icon ($this) from the library ($libraryName) could no be written to ($mediaDokuPath)", LogUtility::LVL_MSG_ERROR, Icon::ICON_CANONICAL_NAME);
499        }
500
501
502    }
503
504    /**
505     * @param $iconName
506     * @param $mediaFilePath
507     * @deprecated Old code to download icon from the material design api
508     */
509    public
510    static function downloadIconFromMaterialDesignApi($iconName, $mediaFilePath)
511    {
512        // Try the official API
513        // Read the icon meta of
514        // Meta Json file got all icons
515        //
516        //   * Available at: https://raw.githubusercontent.com/Templarian/MaterialDesign/master/meta.json
517        //   * See doc: https://github.com/Templarian/MaterialDesign-Site/blob/master/src/content/api.md)
518        $arrayFormat = true;
519        $iconMetaJson = json_decode(file_get_contents(__DIR__ . '/../resources/dictionary/icon-meta.json'), $arrayFormat);
520        $iconId = null;
521        foreach ($iconMetaJson as $key => $value) {
522            if ($value['name'] == $iconName) {
523                $iconId = $value['id'];
524                break;
525            }
526        }
527        if ($iconId != null) {
528
529            // Download
530            // Call to the API
531            // https://dev.materialdesignicons.com/contribute/site/api
532            $downloadUrl = "https://materialdesignicons.com/api/download/icon/svg/$iconId";
533            $filePointer = file_put_contents($mediaFilePath, fopen($downloadUrl, 'r'));
534            if ($filePointer == false) {
535                LogUtility::msg("The file ($downloadUrl) could not be downloaded to ($mediaFilePath)", LogUtility::LVL_MSG_ERROR, Icon::ICON_CANONICAL_NAME);
536            } else {
537                LogUtility::msg("The material design icon ($iconName) was downloaded to ($mediaFilePath)", LogUtility::LVL_MSG_INFO, Icon::ICON_CANONICAL_NAME);
538            }
539
540        }
541
542    }
543
544    private static function getLibraries(): array
545    {
546        return array_merge(
547            self::PUBLIC_LIBRARY_ACRONYM,
548            self::DEPRECATED_LIBRARY_ACRONYM
549        );
550    }
551
552    /**
553     * @throws ExceptionCompile
554     */
555    public static function getEmojiCodePoint(string $emojiName)
556    {
557        $path = DirectoryLayout::getComboDictionaryDirectory()->resolve("emojis.json");
558        $jsonContent = FileSystems::getContent($path);
559        $jsonArray = Json::createFromString($jsonContent)->toArray();
560        return $jsonArray[$emojiName];
561    }
562
563
564    /**
565     * @param string $iconName
566     * @param string $sep
567     * @return array
568     * @throws ExceptionCompile
569     */
570    private static function explodeInTwoPartsByLastPosition(string $iconName, string $sep = "-"): array
571    {
572        $index = strrpos($iconName, $sep);
573        if ($index === false) {
574            throw new ExceptionCompile ("We expect that the icon name ($iconName) has two parts separated by a `-` (example: table-outlined). The icon could not be downloaded.", Icon::ICON_CANONICAL_NAME);
575        }
576        $firstPart = substr($iconName, 0, $index);
577        $secondPart = substr($iconName, $index + 1);
578        return [$firstPart, $secondPart];
579    }
580
581
582    public function __toString()
583    {
584        return $this->getIconName();
585    }
586
587    private function getLibrary()
588    {
589        return $this->library;
590    }
591
592
593    /**
594     * @noinspection PhpReturnValueOfMethodIsNeverUsedInspection
595     */
596    private function setLibrary($libraryName): IconDownloader
597    {
598        /**
599         * The library may be not supported
600         * but the users can install them manually
601         * We test the support of the library if the logo does not exists
602         * on the file system
603         */
604        $this->library = $libraryName;
605        return $this;
606    }
607
608    private function setIconName(string $iconName)
609    {
610        $this->iconName = $iconName;
611    }
612
613    public function getPath(): WikiPath
614    {
615        return $this->path;
616    }
617
618    /**
619     * @throws ExceptionNotFound
620     */
621    public function getLibraryUrl()
622    {
623        $library = $this->getLibrary();
624        $libraryUrl = @self::ICON_LIBRARY_WEBSITE_URLS[$library];
625        if ($libraryUrl === null) {
626            throw new ExceptionNotFound("The url for the library ($library) was not found");
627        }
628        return $libraryUrl;
629    }
630
631
632}
633