xref: /plugin/combo/ComboStrap/PluginUtility.php (revision 1fa8c418ed5809db58049141be41b7738471dd32)
137748cd8SNickeau<?php
237748cd8SNickeau
337748cd8SNickeau
437748cd8SNickeaunamespace ComboStrap;
537748cd8SNickeau
637748cd8SNickeau
737748cd8SNickeauuse dokuwiki\Extension\Plugin;
837748cd8SNickeauuse dokuwiki\Extension\SyntaxPlugin;
937748cd8SNickeau
1037748cd8SNickeaurequire_once(__DIR__ . '/../vendor/autoload.php');
1137748cd8SNickeau
1237748cd8SNickeau/**
13*1fa8c418SNickeau * Parent in th hierarchy should be first
14*1fa8c418SNickeau * Ie before {@link ImageLink, SvgImageLink, RasterImageLink)
15*1fa8c418SNickeau */
16*1fa8c418SNickeaurequire_once(__DIR__ . '/DokuPath.php');
17*1fa8c418SNickeaurequire_once(__DIR__ . '/Media.php');
18*1fa8c418SNickeaurequire_once(__DIR__ . '/MediaLink.php');
19*1fa8c418SNickeau
20*1fa8c418SNickeau/**
2137748cd8SNickeau * Plugin Utility is added in all Dokuwiki extension
2237748cd8SNickeau * and
2337748cd8SNickeau * all classes are added in plugin utility
2437748cd8SNickeau *
2537748cd8SNickeau * This is an utility master and the class loader
2637748cd8SNickeau *
2737748cd8SNickeau * If the load is relative, the load path is used
2837748cd8SNickeau * and the bad php file may be loaded
2937748cd8SNickeau * Furthermore, the absolute path helps
3037748cd8SNickeau * the IDE when refactoring
3137748cd8SNickeau */
3237748cd8SNickeaurequire_once(__DIR__ . '/AdsUtility.php');
3337748cd8SNickeaurequire_once(__DIR__ . '/Align.php');
3437748cd8SNickeaurequire_once(__DIR__ . '/Analytics.php');
3537748cd8SNickeaurequire_once(__DIR__ . '/AnalyticsMenuItem.php');
3637748cd8SNickeaurequire_once(__DIR__ . '/Animation.php');
3737748cd8SNickeaurequire_once(__DIR__ . '/ArrayCaseInsensitive.php');
3837748cd8SNickeaurequire_once(__DIR__ . '/ArrayUtility.php');
3937748cd8SNickeaurequire_once(__DIR__ . '/Background.php');
4037748cd8SNickeaurequire_once(__DIR__ . '/Boldness.php');
4137748cd8SNickeaurequire_once(__DIR__ . '/Bootstrap.php');
4237748cd8SNickeaurequire_once(__DIR__ . '/BreadcrumbHierarchical.php');
4337748cd8SNickeaurequire_once(__DIR__ . '/CacheByLogicalKey.php');
4437748cd8SNickeaurequire_once(__DIR__ . '/CacheInstructionsByLogicalKey.php');
4537748cd8SNickeaurequire_once(__DIR__ . '/CacheManager.php');
4637748cd8SNickeaurequire_once(__DIR__ . '/CacheMedia.php');
4737748cd8SNickeaurequire_once(__DIR__ . '/Call.php');
4837748cd8SNickeaurequire_once(__DIR__ . '/CallStack.php');
4937748cd8SNickeaurequire_once(__DIR__ . '/ColorUtility.php');
5037748cd8SNickeaurequire_once(__DIR__ . '/ConditionalValue.php');
5137748cd8SNickeaurequire_once(__DIR__ . '/ConfUtility.php');
5237748cd8SNickeaurequire_once(__DIR__ . '/Dimension.php');
5337748cd8SNickeaurequire_once(__DIR__ . '/DokuwikiUrl.php');
54*1fa8c418SNickeaurequire_once(__DIR__ . '/ExitException.php');
5537748cd8SNickeaurequire_once(__DIR__ . '/File.php');
5637748cd8SNickeaurequire_once(__DIR__ . '/FloatAttribute.php');
5737748cd8SNickeaurequire_once(__DIR__ . '/FontSize.php');
5837748cd8SNickeaurequire_once(__DIR__ . '/FsWikiUtility.php');
5937748cd8SNickeaurequire_once(__DIR__ . '/HeaderUtility.php');
6037748cd8SNickeaurequire_once(__DIR__ . '/HistoricalBreadcrumbMenuItem.php');
6137748cd8SNickeaurequire_once(__DIR__ . '/Hover.php');
6237748cd8SNickeaurequire_once(__DIR__ . '/Http.php');
6337748cd8SNickeaurequire_once(__DIR__ . '/Icon.php');
6437748cd8SNickeaurequire_once(__DIR__ . '/Identity.php');
65*1fa8c418SNickeaurequire_once(__DIR__ . '/Image.php');
66*1fa8c418SNickeaurequire_once(__DIR__ . '/ImageLink.php');
67*1fa8c418SNickeaurequire_once(__DIR__ . '/ImageRaster.php');
68*1fa8c418SNickeaurequire_once(__DIR__ . '/ImageSvg.php');
6937748cd8SNickeaurequire_once(__DIR__ . '/Iso8601Date.php');
70*1fa8c418SNickeaurequire_once(__DIR__ . '/Json.php');
7137748cd8SNickeaurequire_once(__DIR__ . '/Lang.php');
7237748cd8SNickeaurequire_once(__DIR__ . '/LineSpacing.php');
73*1fa8c418SNickeaurequire_once(__DIR__ . '/LogException.php');
7437748cd8SNickeaurequire_once(__DIR__ . '/LogUtility.php');
7537748cd8SNickeaurequire_once(__DIR__ . '/LowQualityPage.php');
7637748cd8SNickeaurequire_once(__DIR__ . '/MetadataUtility.php');
77*1fa8c418SNickeaurequire_once(__DIR__ . '/Message.php');
78*1fa8c418SNickeaurequire_once(__DIR__ . '/Mermaid.php');
7937748cd8SNickeaurequire_once(__DIR__ . '/NavBarUtility.php');
8037748cd8SNickeaurequire_once(__DIR__ . '/Opacity.php');
81*1fa8c418SNickeaurequire_once(__DIR__ . '/Os.php');
8237748cd8SNickeaurequire_once(__DIR__ . '/Page.php');
8337748cd8SNickeaurequire_once(__DIR__ . '/PageProtection.php');
8437748cd8SNickeaurequire_once(__DIR__ . '/PageRules.php');
8537748cd8SNickeaurequire_once(__DIR__ . '/PageSql.php');
8637748cd8SNickeaurequire_once(__DIR__ . '/PageSqlParser/PageSqlLexer.php');
8737748cd8SNickeaurequire_once(__DIR__ . '/PageSqlParser/PageSqlParser.php');
8837748cd8SNickeaurequire_once(__DIR__ . '/PageSqlTreeListener.php');
8937748cd8SNickeaurequire_once(__DIR__ . '/PagesIndex.php');
9037748cd8SNickeaurequire_once(__DIR__ . '/PipelineUtility.php');
9137748cd8SNickeaurequire_once(__DIR__ . '/Position.php');
9237748cd8SNickeaurequire_once(__DIR__ . '/Prism.php');
9337748cd8SNickeaurequire_once(__DIR__ . '/Publication.php');
9437748cd8SNickeaurequire_once(__DIR__ . '/RasterImageLink.php');
9537748cd8SNickeaurequire_once(__DIR__ . '/RenderUtility.php');
9637748cd8SNickeaurequire_once(__DIR__ . '/Resources.php');
97*1fa8c418SNickeaurequire_once(__DIR__ . '/Sanitizer.php');
9837748cd8SNickeaurequire_once(__DIR__ . '/Shadow.php');
9937748cd8SNickeaurequire_once(__DIR__ . '/Site.php');
10037748cd8SNickeaurequire_once(__DIR__ . '/Skin.php');
10137748cd8SNickeaurequire_once(__DIR__ . '/Snippet.php');
10237748cd8SNickeaurequire_once(__DIR__ . '/SnippetManager.php');
10337748cd8SNickeaurequire_once(__DIR__ . '/Spacing.php');
10437748cd8SNickeaurequire_once(__DIR__ . '/Sqlite.php');
10537748cd8SNickeaurequire_once(__DIR__ . '/StringUtility.php');
10637748cd8SNickeaurequire_once(__DIR__ . '/StyleUtility.php');
10737748cd8SNickeaurequire_once(__DIR__ . '/SvgDocument.php');
10837748cd8SNickeaurequire_once(__DIR__ . '/SvgImageLink.php');
10937748cd8SNickeaurequire_once(__DIR__ . '/Syntax.php');
11037748cd8SNickeaurequire_once(__DIR__ . '/TableUtility.php');
11137748cd8SNickeaurequire_once(__DIR__ . '/Tag.php');
11237748cd8SNickeaurequire_once(__DIR__ . '/TagAttributes.php');
11337748cd8SNickeaurequire_once(__DIR__ . '/Template.php');
11437748cd8SNickeaurequire_once(__DIR__ . '/TemplateUtility.php');
11537748cd8SNickeaurequire_once(__DIR__ . '/TextAlign.php');
11637748cd8SNickeaurequire_once(__DIR__ . '/TextColor.php');
11737748cd8SNickeaurequire_once(__DIR__ . '/ThirdMediaLink.php');
11837748cd8SNickeaurequire_once(__DIR__ . '/ThirdPartyPlugins.php');
11937748cd8SNickeaurequire_once(__DIR__ . '/TocUtility.php');
12037748cd8SNickeaurequire_once(__DIR__ . '/Toggle.php');
12137748cd8SNickeaurequire_once(__DIR__ . '/Underline.php');
12237748cd8SNickeaurequire_once(__DIR__ . '/Unit.php');
12337748cd8SNickeaurequire_once(__DIR__ . '/UrlManagerBestEndPage.php');
12437748cd8SNickeaurequire_once(__DIR__ . '/UrlUtility.php');
12537748cd8SNickeaurequire_once(__DIR__ . '/XhtmlUtility.php');
12637748cd8SNickeaurequire_once(__DIR__ . '/XmlDocument.php');
12737748cd8SNickeaurequire_once(__DIR__ . '/XmlUtility.php');
12837748cd8SNickeau
12937748cd8SNickeau
13037748cd8SNickeau/**
13137748cd8SNickeau * Class url static
13237748cd8SNickeau * List of static utilities
13337748cd8SNickeau */
13437748cd8SNickeauclass PluginUtility
13537748cd8SNickeau{
13637748cd8SNickeau
13737748cd8SNickeau    const DOKU_DATA_DIR = '/dokudata/pages';
13837748cd8SNickeau    const DOKU_CACHE_DIR = '/dokudata/cache';
13937748cd8SNickeau
14037748cd8SNickeau    /**
14137748cd8SNickeau     * Key in the data array between the handle and render function
14237748cd8SNickeau     */
14337748cd8SNickeau    const STATE = "state";
14437748cd8SNickeau    const PAYLOAD = "payload"; // The html or text
14537748cd8SNickeau    const ATTRIBUTES = "attributes";
14637748cd8SNickeau    // The context is generally the parent tag but it may be also the grandfather.
14737748cd8SNickeau    // It permits to determine the HTML that is outputted
14837748cd8SNickeau    const CONTEXT = 'context';
14937748cd8SNickeau    const TAG = "tag";
15037748cd8SNickeau
15137748cd8SNickeau    /**
15237748cd8SNickeau     * The name of the hidden/private namespace
15337748cd8SNickeau     * where the icon and other artifactory are stored
15437748cd8SNickeau     */
15537748cd8SNickeau    const COMBOSTRAP_NAMESPACE_NAME = "combostrap";
15637748cd8SNickeau
15737748cd8SNickeau    const PARENT = "parent";
15837748cd8SNickeau    const POSITION = "position";
15937748cd8SNickeau
16037748cd8SNickeau    /**
16137748cd8SNickeau     * Class to center an element
16237748cd8SNickeau     */
16337748cd8SNickeau    const CENTER_CLASS = "mx-auto";
16437748cd8SNickeau
16537748cd8SNickeau
16637748cd8SNickeau    const EDIT_SECTION_TARGET = 'section';
16737748cd8SNickeau    const ERROR_MESSAGE = "errorAtt";
168*1fa8c418SNickeau    const ERROR_LEVEL = "errorLevel";
169*1fa8c418SNickeau    const DISPLAY = "display";
17037748cd8SNickeau
17137748cd8SNickeau    /**
17237748cd8SNickeau     * The URL base of the documentation
17337748cd8SNickeau     */
17437748cd8SNickeau    static $URL_BASE;
17537748cd8SNickeau
17637748cd8SNickeau
17737748cd8SNickeau    /**
17837748cd8SNickeau     * @var string - the plugin base name (ie the directory)
17937748cd8SNickeau     * ie $INFO_PLUGIN['base'];
18037748cd8SNickeau     * This is a constant because it permits code analytics
18137748cd8SNickeau     * such as verification of a path
18237748cd8SNickeau     */
18337748cd8SNickeau    const PLUGIN_BASE_NAME = "combo";
18437748cd8SNickeau
18537748cd8SNickeau    /**
18637748cd8SNickeau     * The name of the template plugin
18737748cd8SNickeau     */
18837748cd8SNickeau    const TEMPLATE_STRAP_NAME = "strap";
18937748cd8SNickeau
19037748cd8SNickeau    /**
19137748cd8SNickeau     * @var array
19237748cd8SNickeau     */
19337748cd8SNickeau    static $INFO_PLUGIN;
19437748cd8SNickeau
19537748cd8SNickeau    static $PLUGIN_LANG;
19637748cd8SNickeau
19737748cd8SNickeau    /**
19837748cd8SNickeau     * The plugin name
19937748cd8SNickeau     * (not the same than the base as it's not related to the directory
20037748cd8SNickeau     * @var string
20137748cd8SNickeau     */
20237748cd8SNickeau    public static $PLUGIN_NAME;
20337748cd8SNickeau    /**
20437748cd8SNickeau     * @var mixed the version
20537748cd8SNickeau     */
20637748cd8SNickeau    private static $VERSION;
20737748cd8SNickeau
20837748cd8SNickeau
20937748cd8SNickeau    /**
21037748cd8SNickeau     * Initiate the static variable
21137748cd8SNickeau     * See the call after this class
21237748cd8SNickeau     */
21337748cd8SNickeau    static function init()
21437748cd8SNickeau    {
21537748cd8SNickeau
21637748cd8SNickeau        $pluginInfoFile = __DIR__ . '/../plugin.info.txt';
21737748cd8SNickeau        self::$INFO_PLUGIN = confToHash($pluginInfoFile);
21837748cd8SNickeau        self::$PLUGIN_NAME = 'ComboStrap';
21937748cd8SNickeau        global $lang;
22037748cd8SNickeau        self::$PLUGIN_LANG = $lang[self::PLUGIN_BASE_NAME];
22137748cd8SNickeau        self::$URL_BASE = "https://" . parse_url(self::$INFO_PLUGIN['url'], PHP_URL_HOST);
22237748cd8SNickeau        self::$VERSION = self::$INFO_PLUGIN['version'];
22337748cd8SNickeau
22437748cd8SNickeau        PluginUtility::initStaticManager();
22537748cd8SNickeau
22637748cd8SNickeau    }
22737748cd8SNickeau
22837748cd8SNickeau    /**
22937748cd8SNickeau     * @param $inputExpression
23037748cd8SNickeau     * @return false|int 1|0
23137748cd8SNickeau     * returns:
23237748cd8SNickeau     *    - 1 if the input expression is a pattern,
23337748cd8SNickeau     *    - 0 if not,
23437748cd8SNickeau     *    - FALSE if an error occurred.
23537748cd8SNickeau     */
23637748cd8SNickeau    static function isRegularExpression($inputExpression)
23737748cd8SNickeau    {
23837748cd8SNickeau
23937748cd8SNickeau        $regularExpressionPattern = "/(\\/.*\\/[gmixXsuUAJ]?)/";
24037748cd8SNickeau        return preg_match($regularExpressionPattern, $inputExpression);
24137748cd8SNickeau
24237748cd8SNickeau    }
24337748cd8SNickeau
24437748cd8SNickeau    /**
24537748cd8SNickeau     * Return a mode from a tag (ie from a {@link Plugin::getPluginComponent()}
24637748cd8SNickeau     * @param $tag
24737748cd8SNickeau     * @return string
24837748cd8SNickeau     *
24937748cd8SNickeau     * A mode is just a name for a class
25037748cd8SNickeau     * Example: $Parser->addMode('listblock',new Doku_Parser_Mode_ListBlock());
25137748cd8SNickeau     */
25237748cd8SNickeau    public static function getModeFromTag($tag)
25337748cd8SNickeau    {
25437748cd8SNickeau        return "plugin_" . self::getComponentName($tag);
25537748cd8SNickeau    }
25637748cd8SNickeau
25737748cd8SNickeau    /**
25837748cd8SNickeau     * @param $tag
25937748cd8SNickeau     * @return string
26037748cd8SNickeau     *
26137748cd8SNickeau     * Create a lookahead pattern for a container tag used to enter in a mode
26237748cd8SNickeau     */
26337748cd8SNickeau    public static function getContainerTagPattern($tag)
26437748cd8SNickeau    {
26537748cd8SNickeau        // this pattern ensure that the tag
26637748cd8SNickeau        // `accordion` will not intercept also the tag `accordionitem`
26737748cd8SNickeau        // where:
26837748cd8SNickeau        // ?: means non capturing group (to not capture the last >)
26937748cd8SNickeau        // (\s.*?): is a capturing group that starts with a space
27037748cd8SNickeau        $pattern = "(?:\s.*?>|>)";
27137748cd8SNickeau        return '<' . $tag . $pattern . '(?=.*?<\/' . $tag . '>)';
27237748cd8SNickeau    }
27337748cd8SNickeau
27437748cd8SNickeau    /**
27537748cd8SNickeau     * This pattern allows space after the tag name
27637748cd8SNickeau     * for an end tag
27737748cd8SNickeau     * As XHTML (https://www.w3.org/TR/REC-xml/#dt-etag)
27837748cd8SNickeau     * @param $tag
27937748cd8SNickeau     * @return string
28037748cd8SNickeau     */
28137748cd8SNickeau    public static function getEndTagPattern($tag)
28237748cd8SNickeau    {
28337748cd8SNickeau        return "</$tag\s*>";
28437748cd8SNickeau    }
28537748cd8SNickeau
28637748cd8SNickeau    /**
28737748cd8SNickeau     * @param $tag
28837748cd8SNickeau     * @return string
28937748cd8SNickeau     *
29037748cd8SNickeau     * Create a open tag pattern without lookahead.
29137748cd8SNickeau     * Used for https://dev.w3.org/html5/html-author/#void-elements-0
29237748cd8SNickeau     */
29337748cd8SNickeau    public static function getVoidElementTagPattern($tag)
29437748cd8SNickeau    {
29537748cd8SNickeau        return '<' . $tag . '.*?>';
29637748cd8SNickeau    }
29737748cd8SNickeau
29837748cd8SNickeau
29937748cd8SNickeau    /**
30037748cd8SNickeau     * Take an array  where the key is the attribute name
30137748cd8SNickeau     * and return a HTML tag string
30237748cd8SNickeau     *
30337748cd8SNickeau     * The attribute name and value are escaped
30437748cd8SNickeau     *
30537748cd8SNickeau     * @param $attributes - combo attributes
30637748cd8SNickeau     * @return string
30737748cd8SNickeau     * @deprecated to allowed background and other metadata, use {@link TagAttributes::toHtmlEnterTag()}
30837748cd8SNickeau     */
30937748cd8SNickeau    public static function array2HTMLAttributesAsString($attributes)
31037748cd8SNickeau    {
31137748cd8SNickeau
31237748cd8SNickeau        $tagAttributes = TagAttributes::createFromCallStackArray($attributes);
31337748cd8SNickeau        return $tagAttributes->toHTMLAttributeString();
31437748cd8SNickeau
31537748cd8SNickeau    }
31637748cd8SNickeau
31737748cd8SNickeau    /**
31837748cd8SNickeau     *
31937748cd8SNickeau     * Parse the attributes part of a match
32037748cd8SNickeau     *
32137748cd8SNickeau     * Example:
32237748cd8SNickeau     *   line-numbers="value"
32337748cd8SNickeau     *   line-numbers='value'
32437748cd8SNickeau     *
32537748cd8SNickeau     * This value may be in:
32637748cd8SNickeau     *   * configuration value
32737748cd8SNickeau     *   * as well as in the match of a {@link SyntaxPlugin}
32837748cd8SNickeau     *
32937748cd8SNickeau     * @param $string
33037748cd8SNickeau     * @return array
33137748cd8SNickeau     *
33237748cd8SNickeau     * To parse a match, use {@link PluginUtility::getTagAttributes()}
33337748cd8SNickeau     *
33437748cd8SNickeau     *
33537748cd8SNickeau     */
33637748cd8SNickeau    public static function parseAttributes($string)
33737748cd8SNickeau    {
33837748cd8SNickeau
33937748cd8SNickeau        $parameters = array();
34037748cd8SNickeau
34137748cd8SNickeau        // Rules
34237748cd8SNickeau        //  * name may be alone (ie true boolean attribute)
34337748cd8SNickeau        //  * a name may get a `-`
34437748cd8SNickeau        //  * there may be space every everywhere when the value is enclosed with a quote
34537748cd8SNickeau        //  * there may be no space in the value and between the equal sign when the value is not enclosed
34637748cd8SNickeau        //
34737748cd8SNickeau        // /i not case sensitive
34837748cd8SNickeau        $attributePattern = '\s*([-\w]+)\s*(?:=(\s*[\'"]([^`"]*)[\'"]\s*|[^\s]*))?';
34937748cd8SNickeau        $result = preg_match_all('/' . $attributePattern . '/i', $string, $matches);
35037748cd8SNickeau        if ($result != 0) {
35137748cd8SNickeau            foreach ($matches[1] as $key => $parameterKey) {
35237748cd8SNickeau
35337748cd8SNickeau                // group 3 (ie the value between quotes)
35437748cd8SNickeau                $value = $matches[3][$key];
35537748cd8SNickeau                if ($value == "") {
35637748cd8SNickeau                    // check the value without quotes
35737748cd8SNickeau                    $value = $matches[2][$key];
35837748cd8SNickeau                }
35937748cd8SNickeau                // if there is no value, this is a boolean
36037748cd8SNickeau                if ($value == "") {
36137748cd8SNickeau                    $value = true;
36237748cd8SNickeau                } else {
36337748cd8SNickeau                    $value = hsc($value);
36437748cd8SNickeau                }
36537748cd8SNickeau                $parameters[hsc(strtolower($parameterKey))] = $value;
36637748cd8SNickeau            }
36737748cd8SNickeau        }
36837748cd8SNickeau        return $parameters;
36937748cd8SNickeau
37037748cd8SNickeau    }
37137748cd8SNickeau
37237748cd8SNickeau    public static function getTagAttributes($match)
37337748cd8SNickeau    {
37437748cd8SNickeau        return self::getQualifiedTagAttributes($match, false, "");
37537748cd8SNickeau    }
37637748cd8SNickeau
37737748cd8SNickeau    /**
37837748cd8SNickeau     * Return the attribute of a tag
37937748cd8SNickeau     * Because they are users input, they are all escaped
38037748cd8SNickeau     * @param $match
38137748cd8SNickeau     * @param $hasThirdValue - if true, the third parameter is treated as value, not a property and returned in the `third` key
38237748cd8SNickeau     * use for the code/file/console where they accept a name as third value
38337748cd8SNickeau     * @param $keyThirdArgument - if a third argument is found, return it with this key
38437748cd8SNickeau     * @return array
38537748cd8SNickeau     */
38637748cd8SNickeau    public static function getQualifiedTagAttributes($match, $hasThirdValue, $keyThirdArgument)
38737748cd8SNickeau    {
38837748cd8SNickeau
38937748cd8SNickeau        $match = PluginUtility::getPreprocessEnterTag($match);
39037748cd8SNickeau
39137748cd8SNickeau        // Suppress the tag name (ie until the first blank)
39237748cd8SNickeau        $spacePosition = strpos($match, " ");
39337748cd8SNickeau        if (!$spacePosition) {
39437748cd8SNickeau            // No space, meaning this is only the tag name
39537748cd8SNickeau            return array();
39637748cd8SNickeau        }
39737748cd8SNickeau        $match = trim(substr($match, $spacePosition));
39837748cd8SNickeau        if ($match == "") {
39937748cd8SNickeau            return array();
40037748cd8SNickeau        }
40137748cd8SNickeau
40237748cd8SNickeau        // Do we have a type as first argument ?
40337748cd8SNickeau        $attributes = array();
40437748cd8SNickeau        $spacePosition = strpos($match, " ");
40537748cd8SNickeau        if ($spacePosition) {
40637748cd8SNickeau            $nextArgument = substr($match, 0, $spacePosition);
40737748cd8SNickeau        } else {
40837748cd8SNickeau            $nextArgument = $match;
40937748cd8SNickeau        }
41037748cd8SNickeau        if (!strpos($nextArgument, "=")) {
41137748cd8SNickeau            $attributes["type"] = $nextArgument;
41237748cd8SNickeau            // Suppress the type
41337748cd8SNickeau            $match = substr($match, strlen($nextArgument));
41437748cd8SNickeau            $match = trim($match);
41537748cd8SNickeau
41637748cd8SNickeau            // Do we have a value as first argument ?
41737748cd8SNickeau            if (!empty($hasThirdValue)) {
41837748cd8SNickeau                $spacePosition = strpos($match, " ");
41937748cd8SNickeau                if ($spacePosition) {
42037748cd8SNickeau                    $nextArgument = substr($match, 0, $spacePosition);
42137748cd8SNickeau                } else {
42237748cd8SNickeau                    $nextArgument = $match;
42337748cd8SNickeau                }
42437748cd8SNickeau                if (!strpos($nextArgument, "=") && !empty($nextArgument)) {
42537748cd8SNickeau                    $attributes[$keyThirdArgument] = $nextArgument;
42637748cd8SNickeau                    // Suppress the third argument
42737748cd8SNickeau                    $match = substr($match, strlen($nextArgument));
42837748cd8SNickeau                    $match = trim($match);
42937748cd8SNickeau                }
43037748cd8SNickeau            }
43137748cd8SNickeau        }
43237748cd8SNickeau
43337748cd8SNickeau        // Parse the remaining attributes
43437748cd8SNickeau        $parsedAttributes = self::parseAttributes($match);
43537748cd8SNickeau
43637748cd8SNickeau        // Merge
43737748cd8SNickeau        $attributes = array_merge($attributes, $parsedAttributes);;
43837748cd8SNickeau
43937748cd8SNickeau        return $attributes;
44037748cd8SNickeau
44137748cd8SNickeau    }
44237748cd8SNickeau
44337748cd8SNickeau    /**
44437748cd8SNickeau     * @param array $styleProperties - an array of CSS properties with key, value
44537748cd8SNickeau     * @return string - the value for the style attribute (ie all rules where joined with the comma)
44637748cd8SNickeau     */
44737748cd8SNickeau    public static function array2InlineStyle(array $styleProperties)
44837748cd8SNickeau    {
44937748cd8SNickeau        $inlineCss = "";
45037748cd8SNickeau        foreach ($styleProperties as $key => $value) {
45137748cd8SNickeau            $inlineCss .= "$key:$value;";
45237748cd8SNickeau        }
45337748cd8SNickeau        // Suppress the last ;
45437748cd8SNickeau        if ($inlineCss[strlen($inlineCss) - 1] == ";") {
45537748cd8SNickeau            $inlineCss = substr($inlineCss, 0, -1);
45637748cd8SNickeau        }
45737748cd8SNickeau        return $inlineCss;
45837748cd8SNickeau    }
45937748cd8SNickeau
46037748cd8SNickeau    /**
46137748cd8SNickeau     * @param $tag
46237748cd8SNickeau     * @return string
46337748cd8SNickeau     * Create a pattern used where the tag is not a container.
46437748cd8SNickeau     * ie
46537748cd8SNickeau     * <br/>
46637748cd8SNickeau     * <icon/>
46737748cd8SNickeau     * This is generally used with a subtition plugin
46837748cd8SNickeau     * and a {@link Lexer::addSpecialPattern} state
46937748cd8SNickeau     * where the tag is just replaced
47037748cd8SNickeau     */
47137748cd8SNickeau    public static function getEmptyTagPattern($tag)
47237748cd8SNickeau    {
47337748cd8SNickeau        return '<' . $tag . '.*?/>';
47437748cd8SNickeau    }
47537748cd8SNickeau
47637748cd8SNickeau    /**
47737748cd8SNickeau     * Just call this function from a class like that
47837748cd8SNickeau     *     getTageName(get_called_class())
47937748cd8SNickeau     * to get the tag name (ie the component plugin)
48037748cd8SNickeau     * of a syntax plugin
48137748cd8SNickeau     *
48237748cd8SNickeau     * @param $get_called_class
48337748cd8SNickeau     * @return string
48437748cd8SNickeau     */
48537748cd8SNickeau    public static function getTagName($get_called_class)
48637748cd8SNickeau    {
48737748cd8SNickeau        list(/* $t */, /* $p */, /* $n */, $c) = explode('_', $get_called_class, 4);
48837748cd8SNickeau        return (isset($c) ? $c : '');
48937748cd8SNickeau    }
49037748cd8SNickeau
49137748cd8SNickeau    /**
49237748cd8SNickeau     * Just call this function from a class like that
49337748cd8SNickeau     *     getAdminPageName(get_called_class())
49437748cd8SNickeau     * to get the page name of a admin plugin
49537748cd8SNickeau     *
49637748cd8SNickeau     * @param $get_called_class
49737748cd8SNickeau     * @return string - the admin page name
49837748cd8SNickeau     */
49937748cd8SNickeau    public static function getAdminPageName($get_called_class)
50037748cd8SNickeau    {
50137748cd8SNickeau        $names = explode('_', $get_called_class);
50237748cd8SNickeau        $names = array_slice($names, -2);
50337748cd8SNickeau        return implode('_', $names);
50437748cd8SNickeau    }
50537748cd8SNickeau
50637748cd8SNickeau    public static function getNameSpace()
50737748cd8SNickeau    {
50837748cd8SNickeau        // No : at the begin of the namespace please
50937748cd8SNickeau        return self::PLUGIN_BASE_NAME . ':';
51037748cd8SNickeau    }
51137748cd8SNickeau
51237748cd8SNickeau    /**
51337748cd8SNickeau     * @param $get_called_class - the plugin class
51437748cd8SNickeau     * @return array
51537748cd8SNickeau     */
51637748cd8SNickeau    public static function getTags($get_called_class)
51737748cd8SNickeau    {
51837748cd8SNickeau        $elements = array();
51937748cd8SNickeau        $elementName = PluginUtility::getTagName($get_called_class);
52037748cd8SNickeau        $elements[] = $elementName;
52137748cd8SNickeau        $elements[] = strtoupper($elementName);
52237748cd8SNickeau        return $elements;
52337748cd8SNickeau    }
52437748cd8SNickeau
52537748cd8SNickeau    /**
52637748cd8SNickeau     * Render a text
52737748cd8SNickeau     * @param $pageContent
52837748cd8SNickeau     * @return string|null
52937748cd8SNickeau     */
53037748cd8SNickeau    public static function render($pageContent)
53137748cd8SNickeau    {
53237748cd8SNickeau        return RenderUtility::renderText2XhtmlAndStripPEventually($pageContent, false);
53337748cd8SNickeau    }
53437748cd8SNickeau
53537748cd8SNickeau
53637748cd8SNickeau    /**
53737748cd8SNickeau     * This method will takes attributes
53837748cd8SNickeau     * and process the plugin styling attribute such as width and height
53937748cd8SNickeau     * to put them in a style HTML attribute
54037748cd8SNickeau     * @param TagAttributes $attributes
54137748cd8SNickeau     */
54237748cd8SNickeau    public static function processStyle(&$attributes)
54337748cd8SNickeau    {
54437748cd8SNickeau        // Style
54537748cd8SNickeau        $styleAttributeName = "style";
54637748cd8SNickeau        if ($attributes->hasComponentAttribute($styleAttributeName)) {
54737748cd8SNickeau            $properties = explode(";", $attributes->getValueAndRemove($styleAttributeName));
54837748cd8SNickeau            foreach ($properties as $property) {
54937748cd8SNickeau                list($key, $value) = explode(":", $property);
55037748cd8SNickeau                if ($key != "") {
55137748cd8SNickeau                    $attributes->addStyleDeclaration($key, $value);
55237748cd8SNickeau                }
55337748cd8SNickeau            }
55437748cd8SNickeau        }
55537748cd8SNickeau
55637748cd8SNickeau
55737748cd8SNickeau        /**
55837748cd8SNickeau         * Border Color
55937748cd8SNickeau         * For background color, see {@link TagAttributes::processBackground()}
56037748cd8SNickeau         * For text color, see {@link TextColor}
56137748cd8SNickeau         */
56237748cd8SNickeau
56337748cd8SNickeau        if ($attributes->hasComponentAttribute(ColorUtility::BORDER_COLOR)) {
56437748cd8SNickeau            $colorValue = $attributes->getValueAndRemove(ColorUtility::BORDER_COLOR);
56537748cd8SNickeau            $attributes->addStyleDeclaration(ColorUtility::BORDER_COLOR, ColorUtility::getColorValue($colorValue));
56637748cd8SNickeau            self::checkDefaultBorderColorAttributes($attributes);
56737748cd8SNickeau        }
56837748cd8SNickeau
56937748cd8SNickeau
57037748cd8SNickeau    }
57137748cd8SNickeau
57237748cd8SNickeau    /**
57337748cd8SNickeau     * Return the name of the requested script
57437748cd8SNickeau     */
57537748cd8SNickeau    public
57637748cd8SNickeau    static function getRequestScript()
57737748cd8SNickeau    {
57837748cd8SNickeau        $scriptPath = null;
57937748cd8SNickeau        $testPropertyValue = self::getPropertyValue("SCRIPT_NAME");
58037748cd8SNickeau        if (defined('DOKU_UNITTEST') && $testPropertyValue != null) {
58137748cd8SNickeau            return $testPropertyValue;
58237748cd8SNickeau        }
58337748cd8SNickeau        if (array_key_exists("DOCUMENT_URI", $_SERVER)) {
58437748cd8SNickeau            $scriptPath = $_SERVER["DOCUMENT_URI"];
58537748cd8SNickeau        }
58637748cd8SNickeau        if ($scriptPath == null && array_key_exists("SCRIPT_NAME", $_SERVER)) {
58737748cd8SNickeau            $scriptPath = $_SERVER["SCRIPT_NAME"];
58837748cd8SNickeau        }
58937748cd8SNickeau        if ($scriptPath == null) {
59037748cd8SNickeau            msg("Unable to find the main script", LogUtility::LVL_MSG_ERROR);
59137748cd8SNickeau        }
59237748cd8SNickeau        $path_parts = pathinfo($scriptPath);
59337748cd8SNickeau        return $path_parts['basename'];
59437748cd8SNickeau    }
59537748cd8SNickeau
59637748cd8SNickeau    /**
59737748cd8SNickeau     *
59837748cd8SNickeau     * @param $name
59937748cd8SNickeau     * @param $default
60037748cd8SNickeau     * @return string - the value of a query string property or if in test mode, the value of a test variable
60137748cd8SNickeau     * set with {@link self::setTestProperty}
60237748cd8SNickeau     * This is used to test script that are not supported by the dokuwiki test framework
60337748cd8SNickeau     * such as css.php
60437748cd8SNickeau     */
60537748cd8SNickeau    public
60637748cd8SNickeau    static function getPropertyValue($name, $default = null)
60737748cd8SNickeau    {
60837748cd8SNickeau        global $INPUT;
60937748cd8SNickeau        $value = $INPUT->str($name);
61037748cd8SNickeau        if ($value == null && defined('DOKU_UNITTEST')) {
61137748cd8SNickeau            global $COMBO;
61237748cd8SNickeau            $value = $COMBO[$name];
61337748cd8SNickeau        }
61437748cd8SNickeau        if ($value == null) {
61537748cd8SNickeau            return $default;
61637748cd8SNickeau        } else {
61737748cd8SNickeau            return $value;
61837748cd8SNickeau        }
61937748cd8SNickeau
62037748cd8SNickeau    }
62137748cd8SNickeau
62237748cd8SNickeau    /**
62337748cd8SNickeau     * Create an URL to the documentation website
62437748cd8SNickeau     * @param $canonical - canonical id or slug
62537748cd8SNickeau     * @param $text -  the text of the link
62637748cd8SNickeau     * @param bool $withIcon - used to break the recursion with the message in the {@link Icon}
62737748cd8SNickeau     * @return string - an url
62837748cd8SNickeau     */
62937748cd8SNickeau    public
63037748cd8SNickeau    static function getUrl($canonical, $text, $withIcon = true)
63137748cd8SNickeau    {
63237748cd8SNickeau        /** @noinspection SpellCheckingInspection */
63337748cd8SNickeau
63437748cd8SNickeau        $xhtmlIcon = "";
63537748cd8SNickeau        if ($withIcon) {
63637748cd8SNickeau
63737748cd8SNickeau            /**
63837748cd8SNickeau             * We don't include it as an external resource via url
63937748cd8SNickeau             * because it then make a http request for every logo
64037748cd8SNickeau             * in the configuration page and makes it really slow
64137748cd8SNickeau             */
64237748cd8SNickeau            $path = File::createFromPath(Resources::getImagesDirectory() . "/logo.svg");
64337748cd8SNickeau            $tagAttributes = TagAttributes::createEmpty(SvgImageLink::CANONICAL);
64437748cd8SNickeau            $tagAttributes->addComponentAttributeValue(TagAttributes::TYPE_KEY, SvgDocument::ICON_TYPE);
64537748cd8SNickeau            $tagAttributes->addComponentAttributeValue(Dimension::WIDTH_KEY, "20");
64637748cd8SNickeau            $cache = new CacheMedia($path, $tagAttributes);
64737748cd8SNickeau            if (!$cache->isCacheUsable()) {
64837748cd8SNickeau                $xhtmlIcon = SvgDocument::createFromPath($path)
64937748cd8SNickeau                    ->setShouldBeOptimized(true)
65037748cd8SNickeau                    ->getXmlText($tagAttributes);
65137748cd8SNickeau                $cache->storeCache($xhtmlIcon);
65237748cd8SNickeau            }
65337748cd8SNickeau            $xhtmlIcon = file_get_contents($cache->getFile()->getFileSystemPath());
65437748cd8SNickeau
65537748cd8SNickeau
65637748cd8SNickeau        }
65737748cd8SNickeau        return $xhtmlIcon . ' <a href="' . self::$URL_BASE . '/' . str_replace(":", "/", $canonical) . '" title="' . $text . '">' . $text . '</a>';
65837748cd8SNickeau    }
65937748cd8SNickeau
66037748cd8SNickeau    /**
66137748cd8SNickeau     * An utility function to not search every time which array should be first
66237748cd8SNickeau     * @param array $inlineAttributes - the component inline attributes
66337748cd8SNickeau     * @param array $defaultAttributes - the default configuration attributes
66437748cd8SNickeau     * @return array - a merged array
66537748cd8SNickeau     */
66637748cd8SNickeau    public
66737748cd8SNickeau    static function mergeAttributes(array $inlineAttributes, array $defaultAttributes = array())
66837748cd8SNickeau    {
66937748cd8SNickeau        return array_merge($defaultAttributes, $inlineAttributes);
67037748cd8SNickeau    }
67137748cd8SNickeau
67237748cd8SNickeau    /**
67337748cd8SNickeau     * A pattern for a container tag
67437748cd8SNickeau     * that needs to catch the content
67537748cd8SNickeau     *
67637748cd8SNickeau     * Use as a special pattern (substition)
67737748cd8SNickeau     *
67837748cd8SNickeau     * The {@link \syntax_plugin_combo_math} use it
67937748cd8SNickeau     * @param $tag
68037748cd8SNickeau     * @return string - a pattern
68137748cd8SNickeau     */
68237748cd8SNickeau    public
68337748cd8SNickeau    static function getLeafContainerTagPattern($tag)
68437748cd8SNickeau    {
68537748cd8SNickeau        return '<' . $tag . '.*?>.*?<\/' . $tag . '>';
68637748cd8SNickeau    }
68737748cd8SNickeau
68837748cd8SNickeau    /**
68937748cd8SNickeau     * Return the content of a tag
69037748cd8SNickeau     * <math>Content</math>
69137748cd8SNickeau     * @param $match
69237748cd8SNickeau     * @return string the content
69337748cd8SNickeau     */
69437748cd8SNickeau    public
69537748cd8SNickeau    static function getTagContent($match)
69637748cd8SNickeau    {
69737748cd8SNickeau        // From the first >
69837748cd8SNickeau        $start = strpos($match, ">");
69937748cd8SNickeau        if ($start == false) {
70037748cd8SNickeau            LogUtility::msg("The match does not contain any opening tag. Match: {$match}", LogUtility::LVL_MSG_ERROR);
70137748cd8SNickeau            return "";
70237748cd8SNickeau        }
70337748cd8SNickeau        $match = substr($match, $start + 1);
70437748cd8SNickeau        // If this is the last character, we get a false
70537748cd8SNickeau        if ($match == false) {
70637748cd8SNickeau            LogUtility::msg("The match does not contain any closing tag. Match: {$match}", LogUtility::LVL_MSG_ERROR);
70737748cd8SNickeau            return "";
70837748cd8SNickeau        }
70937748cd8SNickeau
71037748cd8SNickeau        $end = strrpos($match, "</");
71137748cd8SNickeau        if ($end == false) {
71237748cd8SNickeau            LogUtility::msg("The match does not contain any closing tag. Match: {$match}", LogUtility::LVL_MSG_ERROR);
71337748cd8SNickeau            return "";
71437748cd8SNickeau        }
71537748cd8SNickeau
71637748cd8SNickeau        return substr($match, 0, $end);
71737748cd8SNickeau    }
71837748cd8SNickeau
71937748cd8SNickeau    /**
72037748cd8SNickeau     *
72137748cd8SNickeau     * Check if a HTML tag was already added for a request
72237748cd8SNickeau     * The request id is just the timestamp
72337748cd8SNickeau     * An indicator array should be provided
72437748cd8SNickeau     * @return string
72537748cd8SNickeau     */
72637748cd8SNickeau    public
72737748cd8SNickeau    static function getRequestId()
72837748cd8SNickeau    {
72937748cd8SNickeau
73037748cd8SNickeau        if (isset($_SERVER['REQUEST_TIME_FLOAT'])) {
73137748cd8SNickeau            // since php 5.4
73237748cd8SNickeau            $requestTime = $_SERVER['REQUEST_TIME_FLOAT'];
73337748cd8SNickeau        } else {
73437748cd8SNickeau            // DokuWiki test framework use this
73537748cd8SNickeau            $requestTime = $_SERVER['REQUEST_TIME'];
73637748cd8SNickeau        }
73737748cd8SNickeau        $keyPrefix = 'combo_';
73837748cd8SNickeau
73937748cd8SNickeau        global $ID;
74037748cd8SNickeau        return $keyPrefix . hash('crc32b', $_SERVER['REMOTE_ADDR'] . $_SERVER['REMOTE_PORT'] . $requestTime . $ID);
74137748cd8SNickeau
74237748cd8SNickeau    }
74337748cd8SNickeau
74437748cd8SNickeau    /**
74537748cd8SNickeau     * Get the page id
74637748cd8SNickeau     * If the page is a sidebar, it will not return the id of the sidebar
74737748cd8SNickeau     * but the one of the page
74837748cd8SNickeau     * @return string
74937748cd8SNickeau     */
75037748cd8SNickeau    public
75137748cd8SNickeau    static function getPageId()
75237748cd8SNickeau    {
75337748cd8SNickeau        return FsWikiUtility::getMainPageId();
75437748cd8SNickeau    }
75537748cd8SNickeau
75637748cd8SNickeau    /**
75737748cd8SNickeau     * Transform special HTML characters to entity
75837748cd8SNickeau     * Example:
75937748cd8SNickeau     * <hello>world</hello>
76037748cd8SNickeau     * to
76137748cd8SNickeau     * "&lt;hello&gt;world&lt;/hello&gt;"
76237748cd8SNickeau     *
76337748cd8SNickeau     * @param $text
76437748cd8SNickeau     * @return string
76537748cd8SNickeau     */
76637748cd8SNickeau    public
76737748cd8SNickeau    static function htmlEncode($text)
76837748cd8SNickeau    {
76937748cd8SNickeau        /**
77037748cd8SNickeau         * See https://stackoverflow.com/questions/46483/htmlentities-vs-htmlspecialchars/3614344
77137748cd8SNickeau         * {@link htmlentities }
77237748cd8SNickeau         */
77337748cd8SNickeau        //return htmlspecialchars($text, ENT_QUOTES);
77437748cd8SNickeau        return htmlentities($text);
77537748cd8SNickeau    }
77637748cd8SNickeau
77737748cd8SNickeau
77837748cd8SNickeau    /**
77937748cd8SNickeau     * Add a class
78037748cd8SNickeau     * @param $classValue
78137748cd8SNickeau     * @param array $attributes
78237748cd8SNickeau     */
78337748cd8SNickeau    public
78437748cd8SNickeau    static function addClass2Attributes($classValue, array &$attributes)
78537748cd8SNickeau    {
78637748cd8SNickeau        self::addAttributeValue("class", $classValue, $attributes);
78737748cd8SNickeau    }
78837748cd8SNickeau
78937748cd8SNickeau    /**
79037748cd8SNickeau     * Add a style property to the attributes
79137748cd8SNickeau     * @param $property
79237748cd8SNickeau     * @param $value
79337748cd8SNickeau     * @param array $attributes
79437748cd8SNickeau     * @deprecated use {@link TagAttributes::addStyleDeclaration()} instead
79537748cd8SNickeau     */
79637748cd8SNickeau    public
79737748cd8SNickeau    static function addStyleProperty($property, $value, array &$attributes)
79837748cd8SNickeau    {
79937748cd8SNickeau        if (isset($attributes["style"])) {
80037748cd8SNickeau            $attributes["style"] .= ";$property:$value";
80137748cd8SNickeau        } else {
80237748cd8SNickeau            $attributes["style"] = "$property:$value";
80337748cd8SNickeau        }
80437748cd8SNickeau
80537748cd8SNickeau    }
80637748cd8SNickeau
80737748cd8SNickeau    /**
80837748cd8SNickeau     * Add default border attributes
80937748cd8SNickeau     * to see a border
81037748cd8SNickeau     * Doc
81137748cd8SNickeau     * https://combostrap.com/styling/color#border_color
81237748cd8SNickeau     * @param TagAttributes $tagAttributes
81337748cd8SNickeau     */
81437748cd8SNickeau    private
81537748cd8SNickeau    static function checkDefaultBorderColorAttributes(&$tagAttributes)
81637748cd8SNickeau    {
81737748cd8SNickeau        /**
81837748cd8SNickeau         * border color was set without the width
81937748cd8SNickeau         * setting the width
82037748cd8SNickeau         */
82137748cd8SNickeau        if (!(
82237748cd8SNickeau            $tagAttributes->hasStyleDeclaration("border")
82337748cd8SNickeau            ||
82437748cd8SNickeau            $tagAttributes->hasStyleDeclaration("border-width")
82537748cd8SNickeau        )
82637748cd8SNickeau        ) {
82737748cd8SNickeau            $tagAttributes->addStyleDeclaration("border-width", "1px");
82837748cd8SNickeau        }
82937748cd8SNickeau        /**
83037748cd8SNickeau         * border color was set without the style
83137748cd8SNickeau         * setting the style
83237748cd8SNickeau         */
83337748cd8SNickeau        if (!
83437748cd8SNickeau        (
83537748cd8SNickeau            $tagAttributes->hasStyleDeclaration("border")
83637748cd8SNickeau            ||
83737748cd8SNickeau            $tagAttributes->hasStyleDeclaration("border-style")
83837748cd8SNickeau        )
83937748cd8SNickeau        ) {
84037748cd8SNickeau            $tagAttributes->addStyleDeclaration("border-style", "solid");
84137748cd8SNickeau
84237748cd8SNickeau        }
84337748cd8SNickeau        if (!$tagAttributes->hasStyleDeclaration("border-radius")) {
84437748cd8SNickeau            $tagAttributes->addStyleDeclaration("border-radius", ".25rem");
84537748cd8SNickeau        }
84637748cd8SNickeau
84737748cd8SNickeau    }
84837748cd8SNickeau
84937748cd8SNickeau    public
85037748cd8SNickeau    static function getConfValue($confName, $defaultValue = null)
85137748cd8SNickeau    {
85237748cd8SNickeau        global $conf;
85337748cd8SNickeau        if (isset($conf['plugin'][PluginUtility::PLUGIN_BASE_NAME][$confName])) {
85437748cd8SNickeau            return $conf['plugin'][PluginUtility::PLUGIN_BASE_NAME][$confName];
85537748cd8SNickeau        } else {
85637748cd8SNickeau            return $defaultValue;
85737748cd8SNickeau        }
85837748cd8SNickeau    }
85937748cd8SNickeau
86037748cd8SNickeau    /**
86137748cd8SNickeau     * @param $match
86237748cd8SNickeau     * @return null|string - return the tag name or null if not found
86337748cd8SNickeau     */
86437748cd8SNickeau    public
86537748cd8SNickeau    static function getTag($match)
86637748cd8SNickeau    {
86737748cd8SNickeau
86837748cd8SNickeau        // Trim to start clean
86937748cd8SNickeau        $match = trim($match);
87037748cd8SNickeau
87137748cd8SNickeau        // Until the first >
87237748cd8SNickeau        $pos = strpos($match, ">");
87337748cd8SNickeau        if ($pos == false) {
87437748cd8SNickeau            LogUtility::msg("The match does not contain any tag. Match: {$match}", LogUtility::LVL_MSG_ERROR);
87537748cd8SNickeau            return null;
87637748cd8SNickeau        }
87737748cd8SNickeau        $match = substr($match, 0, $pos);
87837748cd8SNickeau
87937748cd8SNickeau        // Suppress the <
88037748cd8SNickeau        if ($match[0] == "<") {
88137748cd8SNickeau            $match = substr($match, 1);
88237748cd8SNickeau        } else {
88337748cd8SNickeau            LogUtility::msg("This is not a text tag because it does not start with the character `>`");
88437748cd8SNickeau        }
88537748cd8SNickeau
88637748cd8SNickeau        // Suppress the tag name (ie until the first blank)
88737748cd8SNickeau        $spacePosition = strpos($match, " ");
88837748cd8SNickeau        if (!$spacePosition) {
88937748cd8SNickeau            // No space, meaning this is only the tag name
89037748cd8SNickeau            return $match;
89137748cd8SNickeau        } else {
89237748cd8SNickeau            return substr($match, 0, $spacePosition);
89337748cd8SNickeau        }
89437748cd8SNickeau
89537748cd8SNickeau    }
89637748cd8SNickeau
89737748cd8SNickeau
89837748cd8SNickeau    /**
89937748cd8SNickeau     * @param string $string add a command into HTML
90037748cd8SNickeau     */
90137748cd8SNickeau    public
90237748cd8SNickeau    static function addAsHtmlComment($string)
90337748cd8SNickeau    {
90437748cd8SNickeau        print_r('<!-- ' . self::htmlEncode($string) . '-->');
90537748cd8SNickeau    }
90637748cd8SNickeau
90737748cd8SNickeau    public
90837748cd8SNickeau    static function getResourceBaseUrl()
90937748cd8SNickeau    {
91037748cd8SNickeau        return DOKU_URL . 'lib/plugins/' . PluginUtility::PLUGIN_BASE_NAME . '/resources';
91137748cd8SNickeau    }
91237748cd8SNickeau
91337748cd8SNickeau    /**
91437748cd8SNickeau     * @param $TAG - the name of the tag that should correspond to the name of the css file in the style directory
91537748cd8SNickeau     * @return string - a inline style element to inject in the page or blank if no file exists
91637748cd8SNickeau     */
91737748cd8SNickeau    public
91837748cd8SNickeau    static function getTagStyle($TAG)
91937748cd8SNickeau    {
92037748cd8SNickeau        $script = self::getCssRules($TAG);
92137748cd8SNickeau        if (!empty($script)) {
92237748cd8SNickeau            return "<style>" . $script . "</style>";
92337748cd8SNickeau        } else {
92437748cd8SNickeau            return "";
92537748cd8SNickeau        }
92637748cd8SNickeau
92737748cd8SNickeau    }
92837748cd8SNickeau
92937748cd8SNickeau
93037748cd8SNickeau    public
93137748cd8SNickeau    static function getComponentName($tag)
93237748cd8SNickeau    {
93337748cd8SNickeau        return strtolower(PluginUtility::PLUGIN_BASE_NAME) . "_" . $tag;
93437748cd8SNickeau    }
93537748cd8SNickeau
93637748cd8SNickeau    public
93737748cd8SNickeau    static function addAttributeValue($attribute, $value, array &$attributes)
93837748cd8SNickeau    {
93937748cd8SNickeau        if (array_key_exists($attribute, $attributes) && $attributes[$attribute] !== "") {
94037748cd8SNickeau            $attributes[$attribute] .= " {$value}";
94137748cd8SNickeau        } else {
94237748cd8SNickeau            $attributes[$attribute] = "{$value}";
94337748cd8SNickeau        }
94437748cd8SNickeau    }
94537748cd8SNickeau
94637748cd8SNickeau    /**
94737748cd8SNickeau     * Plugin Utility is available to all plugin,
94837748cd8SNickeau     * this is a convenient way to the the snippet manager
94937748cd8SNickeau     * @return SnippetManager
95037748cd8SNickeau     */
95137748cd8SNickeau    public
95237748cd8SNickeau    static function getSnippetManager()
95337748cd8SNickeau    {
95437748cd8SNickeau        return SnippetManager::get();
95537748cd8SNickeau    }
95637748cd8SNickeau
95737748cd8SNickeau    public
95837748cd8SNickeau    static function initStaticManager()
95937748cd8SNickeau    {
96037748cd8SNickeau        CacheManager::init();
96137748cd8SNickeau        SnippetManager::init();
96237748cd8SNickeau    }
96337748cd8SNickeau
96437748cd8SNickeau    /**
96537748cd8SNickeau     * Function used in a render
96637748cd8SNickeau     * @param $data - the data from {@link PluginUtility::handleAndReturnUnmatchedData()}
96737748cd8SNickeau     * @return string
96837748cd8SNickeau     */
96937748cd8SNickeau    public
97037748cd8SNickeau    static function renderUnmatched($data)
97137748cd8SNickeau    {
97237748cd8SNickeau        /**
97337748cd8SNickeau         * Attributes
97437748cd8SNickeau         */
97537748cd8SNickeau        if (isset($data[PluginUtility::ATTRIBUTES])) {
97637748cd8SNickeau            $attributes = $data[PluginUtility::ATTRIBUTES];
97737748cd8SNickeau        } else {
97837748cd8SNickeau            $attributes = [];
97937748cd8SNickeau        }
98037748cd8SNickeau        $tagAttributes = TagAttributes::createFromCallStackArray($attributes);
98137748cd8SNickeau        $display = $tagAttributes->getValue(TagAttributes::DISPLAY);
98237748cd8SNickeau        if ($display != "none") {
98337748cd8SNickeau            $payload = $data[self::PAYLOAD];
984*1fa8c418SNickeau            $previousTagDisplayType = $data[self::CONTEXT];
985*1fa8c418SNickeau            if ($previousTagDisplayType !== Call::INLINE_DISPLAY) {
98637748cd8SNickeau                $payload = ltrim($payload);
98737748cd8SNickeau            }
98837748cd8SNickeau            return PluginUtility::htmlEncode($payload);
98937748cd8SNickeau        } else {
99037748cd8SNickeau            return "";
99137748cd8SNickeau        }
99237748cd8SNickeau    }
99337748cd8SNickeau
99437748cd8SNickeau    /**
99537748cd8SNickeau     * Function used in a handle function of a syntax plugin for
99637748cd8SNickeau     * unmatched context
99737748cd8SNickeau     * @param $tagName
99837748cd8SNickeau     * @param $match
99937748cd8SNickeau     * @param \Doku_Handler $handler
100037748cd8SNickeau     * @return array
100137748cd8SNickeau     */
100237748cd8SNickeau    public
1003*1fa8c418SNickeau    static function handleAndReturnUnmatchedData($tagName, $match, \Doku_Handler $handler): array
100437748cd8SNickeau    {
1005*1fa8c418SNickeau        $callStack = CallStack::createFromHandler($handler);
1006*1fa8c418SNickeau        $sibling = $callStack->previous();
100737748cd8SNickeau        $context = null;
100837748cd8SNickeau        if (!empty($sibling)) {
1009*1fa8c418SNickeau            $context = $sibling->getDisplay();
101037748cd8SNickeau        }
101137748cd8SNickeau        return array(
101237748cd8SNickeau            PluginUtility::STATE => DOKU_LEXER_UNMATCHED,
101337748cd8SNickeau            PluginUtility::PAYLOAD => $match,
101437748cd8SNickeau            PluginUtility::CONTEXT => $context
101537748cd8SNickeau        );
101637748cd8SNickeau    }
101737748cd8SNickeau
101837748cd8SNickeau    public
101937748cd8SNickeau    static function setConf($key, $value, $namespace = 'plugin')
102037748cd8SNickeau    {
102137748cd8SNickeau        global $conf;
102237748cd8SNickeau        if ($namespace != null) {
102337748cd8SNickeau            $conf[$namespace][PluginUtility::PLUGIN_BASE_NAME][$key] = $value;
102437748cd8SNickeau        } else {
102537748cd8SNickeau            $conf[$key] = $value;
102637748cd8SNickeau        }
102737748cd8SNickeau
102837748cd8SNickeau    }
102937748cd8SNickeau
103037748cd8SNickeau    /**
103137748cd8SNickeau     * Utility methodPreprocess a start tag to be able to extract the name
103237748cd8SNickeau     * and the attributes easily
103337748cd8SNickeau     *
103437748cd8SNickeau     * It will delete:
103537748cd8SNickeau     *   * the characters <> and the /> if present
103637748cd8SNickeau     *   * and trim
103737748cd8SNickeau     *
103837748cd8SNickeau     * It will remain the tagname and its attributes
103937748cd8SNickeau     * @param $match
104037748cd8SNickeau     * @return false|string|null
104137748cd8SNickeau     */
104237748cd8SNickeau    private
104337748cd8SNickeau    static function getPreprocessEnterTag($match)
104437748cd8SNickeau    {
104537748cd8SNickeau        // Until the first >
104637748cd8SNickeau        $pos = strpos($match, ">");
104737748cd8SNickeau        if ($pos == false) {
104837748cd8SNickeau            LogUtility::msg("The match does not contain any tag. Match: {$match}", LogUtility::LVL_MSG_WARNING);
104937748cd8SNickeau            return null;
105037748cd8SNickeau        }
105137748cd8SNickeau        $match = substr($match, 0, $pos);
105237748cd8SNickeau
105337748cd8SNickeau
105437748cd8SNickeau        // Trim to start clean
105537748cd8SNickeau        $match = trim($match);
105637748cd8SNickeau
105737748cd8SNickeau        // Suppress the <
105837748cd8SNickeau        if ($match[0] == "<") {
105937748cd8SNickeau            $match = substr($match, 1);
106037748cd8SNickeau        }
106137748cd8SNickeau
106237748cd8SNickeau        // Suppress the / for a leaf tag
106337748cd8SNickeau        if ($match[strlen($match) - 1] == "/") {
106437748cd8SNickeau            $match = substr($match, 0, strlen($match) - 1);
106537748cd8SNickeau        }
106637748cd8SNickeau        return $match;
106737748cd8SNickeau    }
106837748cd8SNickeau
106937748cd8SNickeau    /**
107037748cd8SNickeau     * Retrieve the tag name used in the text document
107137748cd8SNickeau     * @param $match
107237748cd8SNickeau     * @return false|string|null
107337748cd8SNickeau     */
107437748cd8SNickeau    public
107537748cd8SNickeau    static function getSyntaxTagNameFromMatch($match)
107637748cd8SNickeau    {
107737748cd8SNickeau        $preprocessMatch = PluginUtility::getPreprocessEnterTag($match);
107837748cd8SNickeau
107937748cd8SNickeau        // Tag name (ie until the first blank)
108037748cd8SNickeau        $spacePosition = strpos($match, " ");
108137748cd8SNickeau        if (!$spacePosition) {
108237748cd8SNickeau            // No space, meaning this is only the tag name
108337748cd8SNickeau            return $preprocessMatch;
108437748cd8SNickeau        } else {
108537748cd8SNickeau            return trim(substr(0, $spacePosition));
108637748cd8SNickeau        }
108737748cd8SNickeau
108837748cd8SNickeau    }
108937748cd8SNickeau
109037748cd8SNickeau    /**
109137748cd8SNickeau     * @param \Doku_Renderer_xhtml $renderer
109237748cd8SNickeau     * @param $position
109337748cd8SNickeau     * @param $name
109437748cd8SNickeau     */
109537748cd8SNickeau    public
109637748cd8SNickeau    static function startSection($renderer, $position, $name)
109737748cd8SNickeau    {
109837748cd8SNickeau
109937748cd8SNickeau
110037748cd8SNickeau        if (empty($position)) {
110137748cd8SNickeau            LogUtility::msg("The position for a start section should not be empty", LogUtility::LVL_MSG_ERROR, "support");
110237748cd8SNickeau        }
110337748cd8SNickeau        if (empty($name)) {
110437748cd8SNickeau            LogUtility::msg("The name for a start section should not be empty", LogUtility::LVL_MSG_ERROR, "support");
110537748cd8SNickeau        }
110637748cd8SNickeau
110737748cd8SNickeau        /**
110837748cd8SNickeau         * New Dokuwiki Version
110937748cd8SNickeau         * for DokuWiki Greebo and more recent versions
111037748cd8SNickeau         */
111137748cd8SNickeau        if (defined('SEC_EDIT_PATTERN')) {
111237748cd8SNickeau            $renderer->startSectionEdit($position, array('target' => self::EDIT_SECTION_TARGET, 'name' => $name));
111337748cd8SNickeau        } else {
111437748cd8SNickeau            /**
111537748cd8SNickeau             * Old version
111637748cd8SNickeau             */
111737748cd8SNickeau            /** @noinspection PhpParamsInspection */
111837748cd8SNickeau            $renderer->startSectionEdit($position, self::EDIT_SECTION_TARGET, $name);
111937748cd8SNickeau        }
112037748cd8SNickeau    }
112137748cd8SNickeau
112237748cd8SNickeau    /**
112337748cd8SNickeau     * Add an enter call to the stack
112437748cd8SNickeau     * @param \Doku_Handler $handler
112537748cd8SNickeau     * @param $tagName
112637748cd8SNickeau     * @param array $callStackArray
112737748cd8SNickeau     */
112837748cd8SNickeau    public
112937748cd8SNickeau    static function addEnterCall(
113037748cd8SNickeau        \Doku_Handler &$handler,
113137748cd8SNickeau        $tagName,
113237748cd8SNickeau        $callStackArray = array()
113337748cd8SNickeau    )
113437748cd8SNickeau    {
113537748cd8SNickeau        $pluginName = PluginUtility::getComponentName($tagName);
113637748cd8SNickeau        $handler->addPluginCall(
113737748cd8SNickeau            $pluginName,
113837748cd8SNickeau            $callStackArray,
113937748cd8SNickeau            DOKU_LEXER_ENTER,
114037748cd8SNickeau            null,
114137748cd8SNickeau            null
114237748cd8SNickeau        );
114337748cd8SNickeau    }
114437748cd8SNickeau
114537748cd8SNickeau    /**
114637748cd8SNickeau     * Add an end call dynamically
114737748cd8SNickeau     * @param \Doku_Handler $handler
114837748cd8SNickeau     * @param $tagName
114937748cd8SNickeau     * @param array $callStackArray
115037748cd8SNickeau     */
115137748cd8SNickeau    public
115237748cd8SNickeau    static function addEndCall(\Doku_Handler $handler, $tagName, $callStackArray = array())
115337748cd8SNickeau    {
115437748cd8SNickeau        $pluginName = PluginUtility::getComponentName($tagName);
115537748cd8SNickeau        $handler->addPluginCall(
115637748cd8SNickeau            $pluginName,
115737748cd8SNickeau            $callStackArray,
115837748cd8SNickeau            DOKU_LEXER_END,
115937748cd8SNickeau            null,
116037748cd8SNickeau            null
116137748cd8SNickeau        );
116237748cd8SNickeau    }
116337748cd8SNickeau
116437748cd8SNickeau    /**
116537748cd8SNickeau     * General Debug
116637748cd8SNickeau     */
116737748cd8SNickeau    public
116837748cd8SNickeau    static function isDebug()
116937748cd8SNickeau    {
117037748cd8SNickeau        global $conf;
117137748cd8SNickeau        return $conf["allowdebug"] === 1;
117237748cd8SNickeau
117337748cd8SNickeau    }
117437748cd8SNickeau
117537748cd8SNickeau    /**
117637748cd8SNickeau     * @return bool true if loaded, false otherwise
117737748cd8SNickeau     * Strap is loaded only if this is the same version
117837748cd8SNickeau     * to avoid function, class, or members that does not exist
117937748cd8SNickeau     */
118037748cd8SNickeau    public
118137748cd8SNickeau    static function loadStrapUtilityTemplateIfPresentAndSameVersion()
118237748cd8SNickeau    {
118337748cd8SNickeau        $templateUtilityFile = __DIR__ . '/../../../tpl/strap/class/TplUtility.php';
118437748cd8SNickeau        if (file_exists($templateUtilityFile)) {
118537748cd8SNickeau            /**
118637748cd8SNickeau             * Check the version
118737748cd8SNickeau             */
118837748cd8SNickeau            $templateInfo = confToHash(__DIR__ . '/../../../tpl/strap/template.info.txt');
118937748cd8SNickeau            $templateVersion = $templateInfo['version'];
119037748cd8SNickeau            $comboVersion = self::$INFO_PLUGIN['version'];
119137748cd8SNickeau            if ($templateVersion != $comboVersion) {
119237748cd8SNickeau                if ($comboVersion > $templateVersion) {
119337748cd8SNickeau                    LogUtility::msg("You should upgrade <a href=\"https://www.dokuwiki.org/template:strap\">strap</a> to the latest version to get a fully functional experience. The version of Combo is ($comboVersion) while the version of Strap is ($templateVersion).");
119437748cd8SNickeau                } else {
119537748cd8SNickeau                    LogUtility::msg("You should upgrade <a href=\"https://www.dokuwiki.org/plugin:combo\">combo</a>  to the latest version to get a fully functional experience. The version of Combo is ($comboVersion) while the version of Strap is ($templateVersion).");
119637748cd8SNickeau                }
119737748cd8SNickeau                return false;
119837748cd8SNickeau            } else {
119937748cd8SNickeau                /** @noinspection PhpIncludeInspection */
120037748cd8SNickeau                require_once($templateUtilityFile);
120137748cd8SNickeau                return true;
120237748cd8SNickeau            }
120337748cd8SNickeau        } else {
120437748cd8SNickeau            $level = LogUtility::LVL_MSG_DEBUG;
120537748cd8SNickeau            if (defined('DOKU_UNITTEST')) {
120637748cd8SNickeau                // fail
120737748cd8SNickeau                $level = LogUtility::LVL_MSG_ERROR;
120837748cd8SNickeau            }
120937748cd8SNickeau            if (Site::getTemplate() != "strap") {
121037748cd8SNickeau                LogUtility::msg("The strap template is not installed", $level);
121137748cd8SNickeau            } else {
121237748cd8SNickeau                LogUtility::msg("The file ($templateUtilityFile) was not found", $level);
121337748cd8SNickeau            }
121437748cd8SNickeau            return false;
121537748cd8SNickeau        }
121637748cd8SNickeau    }
121737748cd8SNickeau
121837748cd8SNickeau
121937748cd8SNickeau    /**
122037748cd8SNickeau     *
122137748cd8SNickeau     * See also dev.md file
122237748cd8SNickeau     */
122337748cd8SNickeau    public static function isDevOrTest()
122437748cd8SNickeau    {
122537748cd8SNickeau        if (self::isDev()) {
122637748cd8SNickeau            return true;
122737748cd8SNickeau        }
122837748cd8SNickeau        return self::isTest();
122937748cd8SNickeau    }
123037748cd8SNickeau
123137748cd8SNickeau    public static function isDev()
123237748cd8SNickeau    {
123337748cd8SNickeau        global $_SERVER;
123437748cd8SNickeau        if ($_SERVER["REMOTE_ADDR"] == "127.0.0.1") {
123537748cd8SNickeau            return true;
123637748cd8SNickeau        }
123737748cd8SNickeau        return false;
123837748cd8SNickeau    }
123937748cd8SNickeau
124037748cd8SNickeau    public static function getInstructions($markiCode)
124137748cd8SNickeau    {
124237748cd8SNickeau        return p_get_instructions($markiCode);
124337748cd8SNickeau    }
124437748cd8SNickeau
124537748cd8SNickeau    public static function getInstructionsWithoutRoot($markiCode)
124637748cd8SNickeau    {
124737748cd8SNickeau        return RenderUtility::getInstructionsAndStripPEventually($markiCode);
124837748cd8SNickeau    }
124937748cd8SNickeau
125037748cd8SNickeau    /**
125137748cd8SNickeau     * Transform a text into a valid HTML id
125237748cd8SNickeau     * @param $string
125337748cd8SNickeau     * @return string
125437748cd8SNickeau     */
125537748cd8SNickeau    public static function toHtmlId($string)
125637748cd8SNickeau    {
125737748cd8SNickeau        /**
125837748cd8SNickeau         * sectionId calls cleanID
125937748cd8SNickeau         * cleanID delete all things before a ':'
126037748cd8SNickeau         * we do then the replace before to not
126137748cd8SNickeau         * lost a minus '-' separator
126237748cd8SNickeau         */
126337748cd8SNickeau        $string = str_replace(array(':', '.'), '', $string);
126437748cd8SNickeau        return sectionID($string, $check);
126537748cd8SNickeau    }
126637748cd8SNickeau
126737748cd8SNickeau    public static function isTest()
126837748cd8SNickeau    {
126937748cd8SNickeau        return defined('DOKU_UNITTEST');
127037748cd8SNickeau    }
127137748cd8SNickeau
127237748cd8SNickeau
127337748cd8SNickeau    public static function getCacheManager()
127437748cd8SNickeau    {
127537748cd8SNickeau        return CacheManager::get();
127637748cd8SNickeau    }
127737748cd8SNickeau
127837748cd8SNickeau    public static function getModeFromPluginName($name)
127937748cd8SNickeau    {
128037748cd8SNickeau        return "plugin_$name";
128137748cd8SNickeau    }
128237748cd8SNickeau
128337748cd8SNickeau    public static function isCi(): bool
128437748cd8SNickeau    {
128537748cd8SNickeau        // https://docs.travis-ci.com/user/environment-variables/#default-environment-variables
128637748cd8SNickeau        return getenv("CI") === "true";
128737748cd8SNickeau    }
128837748cd8SNickeau
1289*1fa8c418SNickeau    /**
1290*1fa8c418SNickeau     * An helper function to not exit when it's a test environment
1291*1fa8c418SNickeau     * @param string $message
1292*1fa8c418SNickeau     */
1293*1fa8c418SNickeau    public static function softExit($message = null)
1294*1fa8c418SNickeau    {
1295*1fa8c418SNickeau
1296*1fa8c418SNickeau        if (!PluginUtility::isTest()) {
1297*1fa8c418SNickeau            exit;
1298*1fa8c418SNickeau        } else {
1299*1fa8c418SNickeau            throw new ExitException($message);
1300*1fa8c418SNickeau        }
1301*1fa8c418SNickeau
1302*1fa8c418SNickeau    }
1303*1fa8c418SNickeau
130437748cd8SNickeau
130537748cd8SNickeau}
130637748cd8SNickeau
130737748cd8SNickeauPluginUtility::init();
1308