xref: /plugin/combo/ComboStrap/Icon.php (revision c3437056399326d621a01da73b649707fbb0ae69)
137748cd8SNickeau<?php
237748cd8SNickeau/**
337748cd8SNickeau * Copyright (c) 2020. ComboStrap, Inc. and its affiliates. All Rights Reserved.
437748cd8SNickeau *
537748cd8SNickeau * This source code is licensed under the GPL license found in the
637748cd8SNickeau * COPYING  file in the root directory of this source tree.
737748cd8SNickeau *
837748cd8SNickeau * @license  GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html)
937748cd8SNickeau * @author   ComboStrap <support@combostrap.com>
1037748cd8SNickeau *
1137748cd8SNickeau */
1237748cd8SNickeau
1337748cd8SNickeaunamespace ComboStrap;
1437748cd8SNickeau
151fa8c418SNickeaurequire_once(__DIR__ . '/PluginUtility.php');
1637748cd8SNickeau
1737748cd8SNickeau
1837748cd8SNickeau/**
1937748cd8SNickeau * Class IconUtility
2037748cd8SNickeau * @package ComboStrap
2137748cd8SNickeau * @see https://combostrap.com/icon
2237748cd8SNickeau *
2337748cd8SNickeau *
2437748cd8SNickeau * Material design does not have a repository structure where we can extract the location
2537748cd8SNickeau * from the name
2637748cd8SNickeau * https://material.io/resources/icons https://google.github.io/material-design-icons/
2737748cd8SNickeau *
2837748cd8SNickeau * Injection via javascript to avoid problem with the php svgsimple library
2937748cd8SNickeau * https://www.npmjs.com/package/svg-injector
3037748cd8SNickeau */
3137748cd8SNickeauclass Icon
3237748cd8SNickeau{
3337748cd8SNickeau    const CONF_ICONS_MEDIA_NAMESPACE = "icons_namespace";
3437748cd8SNickeau    const CONF_ICONS_MEDIA_NAMESPACE_DEFAULT = ":" . PluginUtility::COMBOSTRAP_NAMESPACE_NAME . ":icons";
3537748cd8SNickeau    // Canonical name
3637748cd8SNickeau    const NAME = "icon";
3737748cd8SNickeau
3837748cd8SNickeau
3937748cd8SNickeau    const ICON_LIBRARY_URLS = array(
4037748cd8SNickeau        self::BOOTSTRAP => "https://raw.githubusercontent.com/twbs/icons/main/icons",
4137748cd8SNickeau        self::MATERIAL_DESIGN => "https://raw.githubusercontent.com/Templarian/MaterialDesign/master/svg",
42*c3437056SNickeau        self::FEATHER => "https://raw.githubusercontent.com/feathericons/feather/master/icons",
43*c3437056SNickeau        self::CODE_ICON => "https://raw.githubusercontent.com/microsoft/vscode-codicons/main/src/icons/",
44*c3437056SNickeau        self::LOGOS => "https://raw.githubusercontent.com/gilbarbara/logos/master/logos/",
45*c3437056SNickeau        self::CARBON => "https://raw.githubusercontent.com/carbon-design-system/carbon/main/packages/icons/src/svg/32/"
4637748cd8SNickeau    );
4737748cd8SNickeau
4837748cd8SNickeau    const ICON_LIBRARY_WEBSITE_URLS = array(
4937748cd8SNickeau        self::BOOTSTRAP => "https://icons.getbootstrap.com/",
5037748cd8SNickeau        self::MATERIAL_DESIGN => "https://materialdesignicons.com/",
51*c3437056SNickeau        self::FEATHER => "https://feathericons.com/",
52*c3437056SNickeau        self::CODE_ICON => "https://microsoft.github.io/vscode-codicons/",
53*c3437056SNickeau        self::LOGOS => "https://svgporn.com/",
54*c3437056SNickeau        self::CARBON => "https://www.carbondesignsystem.com/guidelines/icons/library/"
5537748cd8SNickeau    );
5637748cd8SNickeau
5737748cd8SNickeau    const CONF_DEFAULT_ICON_LIBRARY = "defaultIconLibrary";
58*c3437056SNickeau    const CONF_DEFAULT_ICON_LIBRARY_DEFAULT = self::MATERIAL_DESIGN_ACRONYM;
59*c3437056SNickeau
60*c3437056SNickeau    /**
61*c3437056SNickeau     * Deprecated library acronym / name
62*c3437056SNickeau     */
63*c3437056SNickeau    const DEPRECATED_LIBRARY_ACRONYM = array(
64*c3437056SNickeau        "bs" => self::BOOTSTRAP, // old one (deprecated) - the good acronym is bi (seen also in the class)
65*c3437056SNickeau        "md" => self::MATERIAL_DESIGN
6637748cd8SNickeau    );
67*c3437056SNickeau
68*c3437056SNickeau    /**
69*c3437056SNickeau     * Public known acronym / name (Used in the configuration)
70*c3437056SNickeau     */
71*c3437056SNickeau    const PUBLIC_LIBRARY_ACRONYM = array(
72*c3437056SNickeau        "bi" => self::BOOTSTRAP,
73*c3437056SNickeau        self::MATERIAL_DESIGN_ACRONYM => self::MATERIAL_DESIGN,
74*c3437056SNickeau        "fe" => self::FEATHER,
75*c3437056SNickeau        "codicon" => self::CODE_ICON,
76*c3437056SNickeau        "logos" => self::LOGOS,
77*c3437056SNickeau        "carbon" => self::CARBON
78*c3437056SNickeau    );
79*c3437056SNickeau
8037748cd8SNickeau    const FEATHER = "feather";
8137748cd8SNickeau    const BOOTSTRAP = "bootstrap";
8237748cd8SNickeau    const MATERIAL_DESIGN = "material-design";
83*c3437056SNickeau    const CODE_ICON = "codicon";
84*c3437056SNickeau    const LOGOS = "logos";
85*c3437056SNickeau    const CARBON = "carbon";
86*c3437056SNickeau    const MATERIAL_DESIGN_ACRONYM = "mdi";
8737748cd8SNickeau
8837748cd8SNickeau
8937748cd8SNickeau    /**
9037748cd8SNickeau     * The function used to render an icon
9137748cd8SNickeau     * @param TagAttributes $tagAttributes -  the icon attributes
9237748cd8SNickeau     * @return bool|mixed - false if any error or the HTML
9337748cd8SNickeau     */
94*c3437056SNickeau    static public function renderIconByAttributes(TagAttributes $tagAttributes)
9537748cd8SNickeau    {
9637748cd8SNickeau
9737748cd8SNickeau
9837748cd8SNickeau        $name = "name";
9937748cd8SNickeau        if (!$tagAttributes->hasComponentAttribute($name)) {
10037748cd8SNickeau            LogUtility::msg("The attributes should have a name. It's mandatory for an icon.", LogUtility::LVL_MSG_ERROR, self::NAME);
10137748cd8SNickeau            return false;
10237748cd8SNickeau        }
10337748cd8SNickeau
10437748cd8SNickeau        /**
10537748cd8SNickeau         * The Name
10637748cd8SNickeau         */
10737748cd8SNickeau        $iconNameAttribute = $tagAttributes->getValue($name);
10837748cd8SNickeau
10937748cd8SNickeau        /**
11037748cd8SNickeau         * If the name have an extension, it's a file from the media directory
11137748cd8SNickeau         * Otherwise, it's an icon from a library
11237748cd8SNickeau         */
11337748cd8SNickeau        $mediaDokuPath = DokuPath::createMediaPathFromId($iconNameAttribute);
11437748cd8SNickeau        if (!empty($mediaDokuPath->getExtension())) {
11537748cd8SNickeau
11637748cd8SNickeau            // loop through candidates until a match was found:
11737748cd8SNickeau            // May be an icon from the templates
118*c3437056SNickeau            if (!FileSystems::exists($mediaDokuPath)) {
11937748cd8SNickeau
12037748cd8SNickeau                // Trying to see if it's not in the template images directory
12137748cd8SNickeau                $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.";
12237748cd8SNickeau                $message .= "<BR> Media File Library tested: $mediaDokuPath";
12337748cd8SNickeau                LogUtility::msg($message, LogUtility::LVL_MSG_ERROR, self::NAME);
12437748cd8SNickeau                return false;
12537748cd8SNickeau
12637748cd8SNickeau            }
12737748cd8SNickeau
12837748cd8SNickeau        } else {
12937748cd8SNickeau
13037748cd8SNickeau            // It may be a icon already downloaded
131*c3437056SNickeau            $iconNameSpace = PluginUtility::getConfValue(self::CONF_ICONS_MEDIA_NAMESPACE, self::CONF_ICONS_MEDIA_NAMESPACE_DEFAULT);
13237748cd8SNickeau            if (substr($iconNameSpace, 0, 1) != DokuPath::PATH_SEPARATOR) {
13337748cd8SNickeau                $iconNameSpace = DokuPath::PATH_SEPARATOR . $iconNameSpace;
13437748cd8SNickeau            }
13537748cd8SNickeau            if (substr($iconNameSpace, -1) != DokuPath::PATH_SEPARATOR) {
13637748cd8SNickeau                $iconNameSpace = $iconNameSpace . ":";
13737748cd8SNickeau            }
13837748cd8SNickeau            $mediaPathId = $iconNameSpace . $iconNameAttribute . ".svg";
13937748cd8SNickeau            $mediaDokuPath = DokuPath::createMediaPathFromAbsolutePath($mediaPathId);
14037748cd8SNickeau
14137748cd8SNickeau            // Bug: null file created when the stream could not get any byte
14237748cd8SNickeau            // We delete them
143*c3437056SNickeau            if (FileSystems::exists($mediaDokuPath)) {
144*c3437056SNickeau                if (FileSystems::getSize($mediaDokuPath) == 0) {
145*c3437056SNickeau                    FileSystems::delete($mediaDokuPath);
14637748cd8SNickeau                }
14737748cd8SNickeau            }
14837748cd8SNickeau
149*c3437056SNickeau            if (!FileSystems::exists($mediaDokuPath)) {
15037748cd8SNickeau
15137748cd8SNickeau                /**
15237748cd8SNickeau                 * Download the icon
15337748cd8SNickeau                 */
15437748cd8SNickeau
15537748cd8SNickeau                // Create the target directory if it does not exist
15637748cd8SNickeau                $iconDir = $mediaDokuPath->getParent();
157*c3437056SNickeau                if (!FileSystems::exists($iconDir)) {
158*c3437056SNickeau                    try {
159*c3437056SNickeau                        FileSystems::createDirectory($iconDir);
160*c3437056SNickeau                    } catch (ExceptionCombo $e) {
16137748cd8SNickeau                        LogUtility::msg("The icon directory ($iconDir) could not be created.", LogUtility::LVL_MSG_ERROR, self::NAME);
16237748cd8SNickeau                        return false;
16337748cd8SNickeau                    }
16437748cd8SNickeau                }
16537748cd8SNickeau
16637748cd8SNickeau                // Name parsing to extract the library name and icon name
16737748cd8SNickeau                $sepPosition = strpos($iconNameAttribute, ":");
168*c3437056SNickeau                $library = PluginUtility::getConfValue(self::CONF_DEFAULT_ICON_LIBRARY, self::CONF_DEFAULT_ICON_LIBRARY_DEFAULT);
16937748cd8SNickeau                $iconName = $iconNameAttribute;
17037748cd8SNickeau                if ($sepPosition != false) {
17137748cd8SNickeau                    $library = substr($iconNameAttribute, 0, $sepPosition);
17237748cd8SNickeau                    $iconName = substr($iconNameAttribute, $sepPosition + 1);
17337748cd8SNickeau                }
17437748cd8SNickeau
17537748cd8SNickeau                // Get the qualified library name
176*c3437056SNickeau                $acronymLibraries = self::getLibraries();
17737748cd8SNickeau                if (isset($acronymLibraries[$library])) {
17837748cd8SNickeau                    $library = $acronymLibraries[$library];
17937748cd8SNickeau                }
18037748cd8SNickeau
18137748cd8SNickeau                // Get the url
18237748cd8SNickeau                $iconLibraries = self::ICON_LIBRARY_URLS;
18337748cd8SNickeau                if (!isset($iconLibraries[$library])) {
18437748cd8SNickeau                    LogUtility::msg("The icon library ($library) is unknown. The icon could not be downloaded.", LogUtility::LVL_MSG_ERROR, self::NAME);
18537748cd8SNickeau                    return false;
18637748cd8SNickeau                } else {
18737748cd8SNickeau                    $iconBaseUrl = $iconLibraries[$library];
18837748cd8SNickeau                }
18937748cd8SNickeau
19037748cd8SNickeau                // The url
19137748cd8SNickeau                $downloadUrl = "$iconBaseUrl/$iconName.svg";
19237748cd8SNickeau                $filePointer = @fopen($downloadUrl, 'r');
19337748cd8SNickeau                if ($filePointer != false) {
19437748cd8SNickeau
195*c3437056SNickeau                    $numberOfByte = @file_put_contents($mediaDokuPath->toLocalPath()->toAbsolutePath()->toString(), $filePointer);
19637748cd8SNickeau                    if ($numberOfByte != false) {
19737748cd8SNickeau                        LogUtility::msg("The icon ($iconName) from the library ($library) was downloaded to ($mediaPathId)", LogUtility::LVL_MSG_INFO, self::NAME);
19837748cd8SNickeau                    } else {
19937748cd8SNickeau                        LogUtility::msg("Internal error: The icon ($iconName) from the library ($library) could no be written to ($mediaPathId)", LogUtility::LVL_MSG_ERROR, self::NAME);
20037748cd8SNickeau                    }
20137748cd8SNickeau
20237748cd8SNickeau                } else {
20337748cd8SNickeau
20437748cd8SNickeau                    // (ie no icon file found at ($downloadUrl)
20537748cd8SNickeau                    $urlLibrary = self::ICON_LIBRARY_WEBSITE_URLS[$library];
20637748cd8SNickeau                    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);
20737748cd8SNickeau
20837748cd8SNickeau                }
20937748cd8SNickeau
21037748cd8SNickeau            }
21137748cd8SNickeau
21237748cd8SNickeau        }
21337748cd8SNickeau
214*c3437056SNickeau        if (FileSystems::exists($mediaDokuPath)) {
21537748cd8SNickeau
21637748cd8SNickeau
21737748cd8SNickeau            /**
21837748cd8SNickeau             * After optimization, the width and height of the svg are gone
21937748cd8SNickeau             * but the icon type set them again
22037748cd8SNickeau             *
22137748cd8SNickeau             * The icon type is used to set:
22237748cd8SNickeau             *   * the default dimension
22337748cd8SNickeau             *   * color styling
22437748cd8SNickeau             *   * disable the responsive properties
22537748cd8SNickeau             *
22637748cd8SNickeau             */
22737748cd8SNickeau            $tagAttributes->addComponentAttributeValue("type", SvgDocument::ICON_TYPE);
22837748cd8SNickeau
22937748cd8SNickeau
230*c3437056SNickeau            $svgImageLink = SvgImageLink::createMediaLinkFromId(
23137748cd8SNickeau                $mediaDokuPath->getAbsolutePath(),
23237748cd8SNickeau                null,
23337748cd8SNickeau                $tagAttributes
23437748cd8SNickeau            );
23537748cd8SNickeau            return $svgImageLink->renderMediaTag();
23637748cd8SNickeau
23737748cd8SNickeau        } else {
23837748cd8SNickeau
23937748cd8SNickeau            return "";
24037748cd8SNickeau
24137748cd8SNickeau        }
24237748cd8SNickeau
24337748cd8SNickeau
24437748cd8SNickeau    }
24537748cd8SNickeau
24637748cd8SNickeau    /**
24737748cd8SNickeau     * @param $iconName
24837748cd8SNickeau     * @param $mediaFilePath
24937748cd8SNickeau     * @deprecated Old code to download icon from the material design api
25037748cd8SNickeau     */
25137748cd8SNickeau    public
25237748cd8SNickeau    static function downloadIconFromMaterialDesignApi($iconName, $mediaFilePath)
25337748cd8SNickeau    {
25437748cd8SNickeau        // Try the official API
25537748cd8SNickeau        // Read the icon meta of
25637748cd8SNickeau        // Meta Json file got all icons
25737748cd8SNickeau        //
25837748cd8SNickeau        //   * Available at: https://raw.githubusercontent.com/Templarian/MaterialDesign/master/meta.json
25937748cd8SNickeau        //   * See doc: https://github.com/Templarian/MaterialDesign-Site/blob/master/src/content/api.md)
26037748cd8SNickeau        $arrayFormat = true;
26137748cd8SNickeau        $iconMetaJson = json_decode(file_get_contents(__DIR__ . '/icon-meta.json'), $arrayFormat);
26237748cd8SNickeau        $iconId = null;
26337748cd8SNickeau        foreach ($iconMetaJson as $key => $value) {
26437748cd8SNickeau            if ($value['name'] == $iconName) {
26537748cd8SNickeau                $iconId = $value['id'];
26637748cd8SNickeau                break;
26737748cd8SNickeau            }
26837748cd8SNickeau        }
26937748cd8SNickeau        if ($iconId != null) {
27037748cd8SNickeau
27137748cd8SNickeau            // Download
27237748cd8SNickeau            // Call to the API
27337748cd8SNickeau            // https://dev.materialdesignicons.com/contribute/site/api
27437748cd8SNickeau            $downloadUrl = "https://materialdesignicons.com/api/download/icon/svg/$iconId";
27537748cd8SNickeau            $filePointer = file_put_contents($mediaFilePath, fopen($downloadUrl, 'r'));
27637748cd8SNickeau            if ($filePointer == false) {
27737748cd8SNickeau                LogUtility::msg("The file ($downloadUrl) could not be downloaded to ($mediaFilePath)", LogUtility::LVL_MSG_ERROR, self::NAME);
27837748cd8SNickeau            } else {
27937748cd8SNickeau                LogUtility::msg("The material design icon ($iconName) was downloaded to ($mediaFilePath)", LogUtility::LVL_MSG_INFO, self::NAME);
28037748cd8SNickeau            }
28137748cd8SNickeau
28237748cd8SNickeau        }
28337748cd8SNickeau
28437748cd8SNickeau    }
28537748cd8SNickeau
286*c3437056SNickeau    private static function getLibraries()
287*c3437056SNickeau    {
288*c3437056SNickeau        return array_merge(self::PUBLIC_LIBRARY_ACRONYM, self::DEPRECATED_LIBRARY_ACRONYM);
289*c3437056SNickeau    }
290*c3437056SNickeau
29137748cd8SNickeau
29237748cd8SNickeau}
293