xref: /plugin/combo/ComboStrap/PluginUtility.php (revision 37748cd8654635afbeca80942126742f0f4cc346)
1*37748cd8SNickeau<?php
2*37748cd8SNickeau
3*37748cd8SNickeau
4*37748cd8SNickeaunamespace ComboStrap;
5*37748cd8SNickeau
6*37748cd8SNickeau
7*37748cd8SNickeauuse dokuwiki\Extension\Plugin;
8*37748cd8SNickeauuse dokuwiki\Extension\SyntaxPlugin;
9*37748cd8SNickeau
10*37748cd8SNickeaurequire_once(__DIR__ . '/../vendor/autoload.php');
11*37748cd8SNickeau
12*37748cd8SNickeau/**
13*37748cd8SNickeau * Plugin Utility is added in all Dokuwiki extension
14*37748cd8SNickeau * and
15*37748cd8SNickeau * all classes are added in plugin utility
16*37748cd8SNickeau *
17*37748cd8SNickeau * This is an utility master and the class loader
18*37748cd8SNickeau *
19*37748cd8SNickeau * If the load is relative, the load path is used
20*37748cd8SNickeau * and the bad php file may be loaded
21*37748cd8SNickeau * Furthermore, the absolute path helps
22*37748cd8SNickeau * the IDE when refactoring
23*37748cd8SNickeau */
24*37748cd8SNickeaurequire_once(__DIR__ . '/AdsUtility.php');
25*37748cd8SNickeaurequire_once(__DIR__ . '/Align.php');
26*37748cd8SNickeaurequire_once(__DIR__ . '/Analytics.php');
27*37748cd8SNickeaurequire_once(__DIR__ . '/AnalyticsMenuItem.php');
28*37748cd8SNickeaurequire_once(__DIR__ . '/Animation.php');
29*37748cd8SNickeaurequire_once(__DIR__ . '/ArrayCaseInsensitive.php');
30*37748cd8SNickeaurequire_once(__DIR__ . '/ArrayUtility.php');
31*37748cd8SNickeaurequire_once(__DIR__ . '/Background.php');
32*37748cd8SNickeaurequire_once(__DIR__ . '/Boldness.php');
33*37748cd8SNickeaurequire_once(__DIR__ . '/Bootstrap.php');
34*37748cd8SNickeaurequire_once(__DIR__ . '/BreadcrumbHierarchical.php');
35*37748cd8SNickeaurequire_once(__DIR__ . '/CacheByLogicalKey.php');
36*37748cd8SNickeaurequire_once(__DIR__ . '/CacheInstructionsByLogicalKey.php');
37*37748cd8SNickeaurequire_once(__DIR__ . '/CacheManager.php');
38*37748cd8SNickeaurequire_once(__DIR__ . '/CacheMedia.php');
39*37748cd8SNickeaurequire_once(__DIR__ . '/Call.php');
40*37748cd8SNickeaurequire_once(__DIR__ . '/CallStack.php');
41*37748cd8SNickeaurequire_once(__DIR__ . '/ColorUtility.php');
42*37748cd8SNickeaurequire_once(__DIR__ . '/ConditionalValue.php');
43*37748cd8SNickeaurequire_once(__DIR__ . '/ConfUtility.php');
44*37748cd8SNickeaurequire_once(__DIR__ . '/Dimension.php');
45*37748cd8SNickeaurequire_once(__DIR__ . '/DokuPath.php');
46*37748cd8SNickeaurequire_once(__DIR__ . '/DokuwikiUrl.php');
47*37748cd8SNickeaurequire_once(__DIR__ . '/File.php');
48*37748cd8SNickeaurequire_once(__DIR__ . '/FloatAttribute.php');
49*37748cd8SNickeaurequire_once(__DIR__ . '/FontSize.php');
50*37748cd8SNickeaurequire_once(__DIR__ . '/FsWikiUtility.php');
51*37748cd8SNickeaurequire_once(__DIR__ . '/HeaderUtility.php');
52*37748cd8SNickeaurequire_once(__DIR__ . '/HistoricalBreadcrumbMenuItem.php');
53*37748cd8SNickeaurequire_once(__DIR__ . '/Hover.php');
54*37748cd8SNickeaurequire_once(__DIR__ . '/Http.php');
55*37748cd8SNickeaurequire_once(__DIR__ . '/Icon.php');
56*37748cd8SNickeaurequire_once(__DIR__ . '/Identity.php');
57*37748cd8SNickeaurequire_once(__DIR__ . '/Iso8601Date.php');
58*37748cd8SNickeaurequire_once(__DIR__ . '/Lang.php');
59*37748cd8SNickeaurequire_once(__DIR__ . '/LineSpacing.php');
60*37748cd8SNickeaurequire_once(__DIR__ . '/LogUtility.php');
61*37748cd8SNickeaurequire_once(__DIR__ . '/LowQualityPage.php');
62*37748cd8SNickeaurequire_once(__DIR__ . '/MediaLink.php');
63*37748cd8SNickeaurequire_once(__DIR__ . '/Message.php');
64*37748cd8SNickeaurequire_once(__DIR__ . '/MetadataUtility.php');
65*37748cd8SNickeaurequire_once(__DIR__ . '/NavBarUtility.php');
66*37748cd8SNickeaurequire_once(__DIR__ . '/Opacity.php');
67*37748cd8SNickeaurequire_once(__DIR__ . '/Page.php');
68*37748cd8SNickeaurequire_once(__DIR__ . '/PageProtection.php');
69*37748cd8SNickeaurequire_once(__DIR__ . '/PageRules.php');
70*37748cd8SNickeaurequire_once(__DIR__ . '/PageSql.php');
71*37748cd8SNickeaurequire_once(__DIR__ . '/PageSqlParser/PageSqlLexer.php');
72*37748cd8SNickeaurequire_once(__DIR__ . '/PageSqlParser/PageSqlParser.php');
73*37748cd8SNickeaurequire_once(__DIR__ . '/PageSqlTreeListener.php');
74*37748cd8SNickeaurequire_once(__DIR__ . '/PagesIndex.php');
75*37748cd8SNickeaurequire_once(__DIR__ . '/PipelineUtility.php');
76*37748cd8SNickeaurequire_once(__DIR__ . '/Position.php');
77*37748cd8SNickeaurequire_once(__DIR__ . '/Prism.php');
78*37748cd8SNickeaurequire_once(__DIR__ . '/Publication.php');
79*37748cd8SNickeaurequire_once(__DIR__ . '/RasterImageLink.php');
80*37748cd8SNickeaurequire_once(__DIR__ . '/RenderUtility.php');
81*37748cd8SNickeaurequire_once(__DIR__ . '/Resources.php');
82*37748cd8SNickeaurequire_once(__DIR__ . '/Shadow.php');
83*37748cd8SNickeaurequire_once(__DIR__ . '/Site.php');
84*37748cd8SNickeaurequire_once(__DIR__ . '/Skin.php');
85*37748cd8SNickeaurequire_once(__DIR__ . '/Snippet.php');
86*37748cd8SNickeaurequire_once(__DIR__ . '/SnippetManager.php');
87*37748cd8SNickeaurequire_once(__DIR__ . '/Spacing.php');
88*37748cd8SNickeaurequire_once(__DIR__ . '/Sqlite.php');
89*37748cd8SNickeaurequire_once(__DIR__ . '/StringUtility.php');
90*37748cd8SNickeaurequire_once(__DIR__ . '/StyleUtility.php');
91*37748cd8SNickeaurequire_once(__DIR__ . '/SvgDocument.php');
92*37748cd8SNickeaurequire_once(__DIR__ . '/SvgImageLink.php');
93*37748cd8SNickeaurequire_once(__DIR__ . '/Syntax.php');
94*37748cd8SNickeaurequire_once(__DIR__ . '/TableUtility.php');
95*37748cd8SNickeaurequire_once(__DIR__ . '/Tag.php');
96*37748cd8SNickeaurequire_once(__DIR__ . '/TagAttributes.php');
97*37748cd8SNickeaurequire_once(__DIR__ . '/Template.php');
98*37748cd8SNickeaurequire_once(__DIR__ . '/TemplateUtility.php');
99*37748cd8SNickeaurequire_once(__DIR__ . '/TextAlign.php');
100*37748cd8SNickeaurequire_once(__DIR__ . '/TextColor.php');
101*37748cd8SNickeaurequire_once(__DIR__ . '/ThirdMediaLink.php');
102*37748cd8SNickeaurequire_once(__DIR__ . '/ThirdPartyPlugins.php');
103*37748cd8SNickeaurequire_once(__DIR__ . '/TocUtility.php');
104*37748cd8SNickeaurequire_once(__DIR__ . '/Toggle.php');
105*37748cd8SNickeaurequire_once(__DIR__ . '/Underline.php');
106*37748cd8SNickeaurequire_once(__DIR__ . '/Unit.php');
107*37748cd8SNickeaurequire_once(__DIR__ . '/UrlManagerBestEndPage.php');
108*37748cd8SNickeaurequire_once(__DIR__ . '/UrlUtility.php');
109*37748cd8SNickeaurequire_once(__DIR__ . '/XhtmlUtility.php');
110*37748cd8SNickeaurequire_once(__DIR__ . '/XmlDocument.php');
111*37748cd8SNickeaurequire_once(__DIR__ . '/XmlUtility.php');
112*37748cd8SNickeau
113*37748cd8SNickeau
114*37748cd8SNickeau/**
115*37748cd8SNickeau * Class url static
116*37748cd8SNickeau * List of static utilities
117*37748cd8SNickeau */
118*37748cd8SNickeauclass PluginUtility
119*37748cd8SNickeau{
120*37748cd8SNickeau
121*37748cd8SNickeau    const DOKU_DATA_DIR = '/dokudata/pages';
122*37748cd8SNickeau    const DOKU_CACHE_DIR = '/dokudata/cache';
123*37748cd8SNickeau
124*37748cd8SNickeau    /**
125*37748cd8SNickeau     * Key in the data array between the handle and render function
126*37748cd8SNickeau     */
127*37748cd8SNickeau    const STATE = "state";
128*37748cd8SNickeau    const PAYLOAD = "payload"; // The html or text
129*37748cd8SNickeau    const ATTRIBUTES = "attributes";
130*37748cd8SNickeau    // The context is generally the parent tag but it may be also the grandfather.
131*37748cd8SNickeau    // It permits to determine the HTML that is outputted
132*37748cd8SNickeau    const CONTEXT = 'context';
133*37748cd8SNickeau    const TAG = "tag";
134*37748cd8SNickeau
135*37748cd8SNickeau    /**
136*37748cd8SNickeau     * The name of the hidden/private namespace
137*37748cd8SNickeau     * where the icon and other artifactory are stored
138*37748cd8SNickeau     */
139*37748cd8SNickeau    const COMBOSTRAP_NAMESPACE_NAME = "combostrap";
140*37748cd8SNickeau
141*37748cd8SNickeau    const PARENT = "parent";
142*37748cd8SNickeau    const POSITION = "position";
143*37748cd8SNickeau
144*37748cd8SNickeau    /**
145*37748cd8SNickeau     * Class to center an element
146*37748cd8SNickeau     */
147*37748cd8SNickeau    const CENTER_CLASS = "mx-auto";
148*37748cd8SNickeau
149*37748cd8SNickeau
150*37748cd8SNickeau    const EDIT_SECTION_TARGET = 'section';
151*37748cd8SNickeau    const ERROR_MESSAGE = "errorAtt";
152*37748cd8SNickeau
153*37748cd8SNickeau    /**
154*37748cd8SNickeau     * The URL base of the documentation
155*37748cd8SNickeau     */
156*37748cd8SNickeau    static $URL_BASE;
157*37748cd8SNickeau
158*37748cd8SNickeau
159*37748cd8SNickeau    /**
160*37748cd8SNickeau     * @var string - the plugin base name (ie the directory)
161*37748cd8SNickeau     * ie $INFO_PLUGIN['base'];
162*37748cd8SNickeau     * This is a constant because it permits code analytics
163*37748cd8SNickeau     * such as verification of a path
164*37748cd8SNickeau     */
165*37748cd8SNickeau    const PLUGIN_BASE_NAME = "combo";
166*37748cd8SNickeau
167*37748cd8SNickeau    /**
168*37748cd8SNickeau     * The name of the template plugin
169*37748cd8SNickeau     */
170*37748cd8SNickeau    const TEMPLATE_STRAP_NAME = "strap";
171*37748cd8SNickeau
172*37748cd8SNickeau    /**
173*37748cd8SNickeau     * @var array
174*37748cd8SNickeau     */
175*37748cd8SNickeau    static $INFO_PLUGIN;
176*37748cd8SNickeau
177*37748cd8SNickeau    static $PLUGIN_LANG;
178*37748cd8SNickeau
179*37748cd8SNickeau    /**
180*37748cd8SNickeau     * The plugin name
181*37748cd8SNickeau     * (not the same than the base as it's not related to the directory
182*37748cd8SNickeau     * @var string
183*37748cd8SNickeau     */
184*37748cd8SNickeau    public static $PLUGIN_NAME;
185*37748cd8SNickeau    /**
186*37748cd8SNickeau     * @var mixed the version
187*37748cd8SNickeau     */
188*37748cd8SNickeau    private static $VERSION;
189*37748cd8SNickeau
190*37748cd8SNickeau
191*37748cd8SNickeau    /**
192*37748cd8SNickeau     * Initiate the static variable
193*37748cd8SNickeau     * See the call after this class
194*37748cd8SNickeau     */
195*37748cd8SNickeau    static function init()
196*37748cd8SNickeau    {
197*37748cd8SNickeau
198*37748cd8SNickeau        $pluginInfoFile = __DIR__ . '/../plugin.info.txt';
199*37748cd8SNickeau        self::$INFO_PLUGIN = confToHash($pluginInfoFile);
200*37748cd8SNickeau        self::$PLUGIN_NAME = 'ComboStrap';
201*37748cd8SNickeau        global $lang;
202*37748cd8SNickeau        self::$PLUGIN_LANG = $lang[self::PLUGIN_BASE_NAME];
203*37748cd8SNickeau        self::$URL_BASE = "https://" . parse_url(self::$INFO_PLUGIN['url'], PHP_URL_HOST);
204*37748cd8SNickeau        self::$VERSION = self::$INFO_PLUGIN['version'];
205*37748cd8SNickeau
206*37748cd8SNickeau        PluginUtility::initStaticManager();
207*37748cd8SNickeau
208*37748cd8SNickeau    }
209*37748cd8SNickeau
210*37748cd8SNickeau    /**
211*37748cd8SNickeau     * @param $inputExpression
212*37748cd8SNickeau     * @return false|int 1|0
213*37748cd8SNickeau     * returns:
214*37748cd8SNickeau     *    - 1 if the input expression is a pattern,
215*37748cd8SNickeau     *    - 0 if not,
216*37748cd8SNickeau     *    - FALSE if an error occurred.
217*37748cd8SNickeau     */
218*37748cd8SNickeau    static function isRegularExpression($inputExpression)
219*37748cd8SNickeau    {
220*37748cd8SNickeau
221*37748cd8SNickeau        $regularExpressionPattern = "/(\\/.*\\/[gmixXsuUAJ]?)/";
222*37748cd8SNickeau        return preg_match($regularExpressionPattern, $inputExpression);
223*37748cd8SNickeau
224*37748cd8SNickeau    }
225*37748cd8SNickeau
226*37748cd8SNickeau    /**
227*37748cd8SNickeau     * Return a mode from a tag (ie from a {@link Plugin::getPluginComponent()}
228*37748cd8SNickeau     * @param $tag
229*37748cd8SNickeau     * @return string
230*37748cd8SNickeau     *
231*37748cd8SNickeau     * A mode is just a name for a class
232*37748cd8SNickeau     * Example: $Parser->addMode('listblock',new Doku_Parser_Mode_ListBlock());
233*37748cd8SNickeau     */
234*37748cd8SNickeau    public static function getModeFromTag($tag)
235*37748cd8SNickeau    {
236*37748cd8SNickeau        return "plugin_" . self::getComponentName($tag);
237*37748cd8SNickeau    }
238*37748cd8SNickeau
239*37748cd8SNickeau    /**
240*37748cd8SNickeau     * @param $tag
241*37748cd8SNickeau     * @return string
242*37748cd8SNickeau     *
243*37748cd8SNickeau     * Create a lookahead pattern for a container tag used to enter in a mode
244*37748cd8SNickeau     */
245*37748cd8SNickeau    public static function getContainerTagPattern($tag)
246*37748cd8SNickeau    {
247*37748cd8SNickeau        // this pattern ensure that the tag
248*37748cd8SNickeau        // `accordion` will not intercept also the tag `accordionitem`
249*37748cd8SNickeau        // where:
250*37748cd8SNickeau        // ?: means non capturing group (to not capture the last >)
251*37748cd8SNickeau        // (\s.*?): is a capturing group that starts with a space
252*37748cd8SNickeau        $pattern = "(?:\s.*?>|>)";
253*37748cd8SNickeau        return '<' . $tag . $pattern . '(?=.*?<\/' . $tag . '>)';
254*37748cd8SNickeau    }
255*37748cd8SNickeau
256*37748cd8SNickeau    /**
257*37748cd8SNickeau     * This pattern allows space after the tag name
258*37748cd8SNickeau     * for an end tag
259*37748cd8SNickeau     * As XHTML (https://www.w3.org/TR/REC-xml/#dt-etag)
260*37748cd8SNickeau     * @param $tag
261*37748cd8SNickeau     * @return string
262*37748cd8SNickeau     */
263*37748cd8SNickeau    public static function getEndTagPattern($tag)
264*37748cd8SNickeau    {
265*37748cd8SNickeau        return "</$tag\s*>";
266*37748cd8SNickeau    }
267*37748cd8SNickeau
268*37748cd8SNickeau    /**
269*37748cd8SNickeau     * @param $tag
270*37748cd8SNickeau     * @return string
271*37748cd8SNickeau     *
272*37748cd8SNickeau     * Create a open tag pattern without lookahead.
273*37748cd8SNickeau     * Used for https://dev.w3.org/html5/html-author/#void-elements-0
274*37748cd8SNickeau     */
275*37748cd8SNickeau    public static function getVoidElementTagPattern($tag)
276*37748cd8SNickeau    {
277*37748cd8SNickeau        return '<' . $tag . '.*?>';
278*37748cd8SNickeau    }
279*37748cd8SNickeau
280*37748cd8SNickeau
281*37748cd8SNickeau    /**
282*37748cd8SNickeau     * Take an array  where the key is the attribute name
283*37748cd8SNickeau     * and return a HTML tag string
284*37748cd8SNickeau     *
285*37748cd8SNickeau     * The attribute name and value are escaped
286*37748cd8SNickeau     *
287*37748cd8SNickeau     * @param $attributes - combo attributes
288*37748cd8SNickeau     * @return string
289*37748cd8SNickeau     * @deprecated to allowed background and other metadata, use {@link TagAttributes::toHtmlEnterTag()}
290*37748cd8SNickeau     */
291*37748cd8SNickeau    public static function array2HTMLAttributesAsString($attributes)
292*37748cd8SNickeau    {
293*37748cd8SNickeau
294*37748cd8SNickeau        $tagAttributes = TagAttributes::createFromCallStackArray($attributes);
295*37748cd8SNickeau        return $tagAttributes->toHTMLAttributeString();
296*37748cd8SNickeau
297*37748cd8SNickeau    }
298*37748cd8SNickeau
299*37748cd8SNickeau    /**
300*37748cd8SNickeau     *
301*37748cd8SNickeau     * Parse the attributes part of a match
302*37748cd8SNickeau     *
303*37748cd8SNickeau     * Example:
304*37748cd8SNickeau     *   line-numbers="value"
305*37748cd8SNickeau     *   line-numbers='value'
306*37748cd8SNickeau     *
307*37748cd8SNickeau     * This value may be in:
308*37748cd8SNickeau     *   * configuration value
309*37748cd8SNickeau     *   * as well as in the match of a {@link SyntaxPlugin}
310*37748cd8SNickeau     *
311*37748cd8SNickeau     * @param $string
312*37748cd8SNickeau     * @return array
313*37748cd8SNickeau     *
314*37748cd8SNickeau     * To parse a match, use {@link PluginUtility::getTagAttributes()}
315*37748cd8SNickeau     *
316*37748cd8SNickeau     *
317*37748cd8SNickeau     */
318*37748cd8SNickeau    public static function parseAttributes($string)
319*37748cd8SNickeau    {
320*37748cd8SNickeau
321*37748cd8SNickeau        $parameters = array();
322*37748cd8SNickeau
323*37748cd8SNickeau        // Rules
324*37748cd8SNickeau        //  * name may be alone (ie true boolean attribute)
325*37748cd8SNickeau        //  * a name may get a `-`
326*37748cd8SNickeau        //  * there may be space every everywhere when the value is enclosed with a quote
327*37748cd8SNickeau        //  * there may be no space in the value and between the equal sign when the value is not enclosed
328*37748cd8SNickeau        //
329*37748cd8SNickeau        // /i not case sensitive
330*37748cd8SNickeau        $attributePattern = '\s*([-\w]+)\s*(?:=(\s*[\'"]([^`"]*)[\'"]\s*|[^\s]*))?';
331*37748cd8SNickeau        $result = preg_match_all('/' . $attributePattern . '/i', $string, $matches);
332*37748cd8SNickeau        if ($result != 0) {
333*37748cd8SNickeau            foreach ($matches[1] as $key => $parameterKey) {
334*37748cd8SNickeau
335*37748cd8SNickeau                // group 3 (ie the value between quotes)
336*37748cd8SNickeau                $value = $matches[3][$key];
337*37748cd8SNickeau                if ($value == "") {
338*37748cd8SNickeau                    // check the value without quotes
339*37748cd8SNickeau                    $value = $matches[2][$key];
340*37748cd8SNickeau                }
341*37748cd8SNickeau                // if there is no value, this is a boolean
342*37748cd8SNickeau                if ($value == "") {
343*37748cd8SNickeau                    $value = true;
344*37748cd8SNickeau                } else {
345*37748cd8SNickeau                    $value = hsc($value);
346*37748cd8SNickeau                }
347*37748cd8SNickeau                $parameters[hsc(strtolower($parameterKey))] = $value;
348*37748cd8SNickeau            }
349*37748cd8SNickeau        }
350*37748cd8SNickeau        return $parameters;
351*37748cd8SNickeau
352*37748cd8SNickeau    }
353*37748cd8SNickeau
354*37748cd8SNickeau    public static function getTagAttributes($match)
355*37748cd8SNickeau    {
356*37748cd8SNickeau        return self::getQualifiedTagAttributes($match, false, "");
357*37748cd8SNickeau    }
358*37748cd8SNickeau
359*37748cd8SNickeau    /**
360*37748cd8SNickeau     * Return the attribute of a tag
361*37748cd8SNickeau     * Because they are users input, they are all escaped
362*37748cd8SNickeau     * @param $match
363*37748cd8SNickeau     * @param $hasThirdValue - if true, the third parameter is treated as value, not a property and returned in the `third` key
364*37748cd8SNickeau     * use for the code/file/console where they accept a name as third value
365*37748cd8SNickeau     * @param $keyThirdArgument - if a third argument is found, return it with this key
366*37748cd8SNickeau     * @return array
367*37748cd8SNickeau     */
368*37748cd8SNickeau    public static function getQualifiedTagAttributes($match, $hasThirdValue, $keyThirdArgument)
369*37748cd8SNickeau    {
370*37748cd8SNickeau
371*37748cd8SNickeau        $match = PluginUtility::getPreprocessEnterTag($match);
372*37748cd8SNickeau
373*37748cd8SNickeau        // Suppress the tag name (ie until the first blank)
374*37748cd8SNickeau        $spacePosition = strpos($match, " ");
375*37748cd8SNickeau        if (!$spacePosition) {
376*37748cd8SNickeau            // No space, meaning this is only the tag name
377*37748cd8SNickeau            return array();
378*37748cd8SNickeau        }
379*37748cd8SNickeau        $match = trim(substr($match, $spacePosition));
380*37748cd8SNickeau        if ($match == "") {
381*37748cd8SNickeau            return array();
382*37748cd8SNickeau        }
383*37748cd8SNickeau
384*37748cd8SNickeau        // Do we have a type as first argument ?
385*37748cd8SNickeau        $attributes = array();
386*37748cd8SNickeau        $spacePosition = strpos($match, " ");
387*37748cd8SNickeau        if ($spacePosition) {
388*37748cd8SNickeau            $nextArgument = substr($match, 0, $spacePosition);
389*37748cd8SNickeau        } else {
390*37748cd8SNickeau            $nextArgument = $match;
391*37748cd8SNickeau        }
392*37748cd8SNickeau        if (!strpos($nextArgument, "=")) {
393*37748cd8SNickeau            $attributes["type"] = $nextArgument;
394*37748cd8SNickeau            // Suppress the type
395*37748cd8SNickeau            $match = substr($match, strlen($nextArgument));
396*37748cd8SNickeau            $match = trim($match);
397*37748cd8SNickeau
398*37748cd8SNickeau            // Do we have a value as first argument ?
399*37748cd8SNickeau            if (!empty($hasThirdValue)) {
400*37748cd8SNickeau                $spacePosition = strpos($match, " ");
401*37748cd8SNickeau                if ($spacePosition) {
402*37748cd8SNickeau                    $nextArgument = substr($match, 0, $spacePosition);
403*37748cd8SNickeau                } else {
404*37748cd8SNickeau                    $nextArgument = $match;
405*37748cd8SNickeau                }
406*37748cd8SNickeau                if (!strpos($nextArgument, "=") && !empty($nextArgument)) {
407*37748cd8SNickeau                    $attributes[$keyThirdArgument] = $nextArgument;
408*37748cd8SNickeau                    // Suppress the third argument
409*37748cd8SNickeau                    $match = substr($match, strlen($nextArgument));
410*37748cd8SNickeau                    $match = trim($match);
411*37748cd8SNickeau                }
412*37748cd8SNickeau            }
413*37748cd8SNickeau        }
414*37748cd8SNickeau
415*37748cd8SNickeau        // Parse the remaining attributes
416*37748cd8SNickeau        $parsedAttributes = self::parseAttributes($match);
417*37748cd8SNickeau
418*37748cd8SNickeau        // Merge
419*37748cd8SNickeau        $attributes = array_merge($attributes, $parsedAttributes);;
420*37748cd8SNickeau
421*37748cd8SNickeau        return $attributes;
422*37748cd8SNickeau
423*37748cd8SNickeau    }
424*37748cd8SNickeau
425*37748cd8SNickeau    /**
426*37748cd8SNickeau     * @param array $styleProperties - an array of CSS properties with key, value
427*37748cd8SNickeau     * @return string - the value for the style attribute (ie all rules where joined with the comma)
428*37748cd8SNickeau     */
429*37748cd8SNickeau    public static function array2InlineStyle(array $styleProperties)
430*37748cd8SNickeau    {
431*37748cd8SNickeau        $inlineCss = "";
432*37748cd8SNickeau        foreach ($styleProperties as $key => $value) {
433*37748cd8SNickeau            $inlineCss .= "$key:$value;";
434*37748cd8SNickeau        }
435*37748cd8SNickeau        // Suppress the last ;
436*37748cd8SNickeau        if ($inlineCss[strlen($inlineCss) - 1] == ";") {
437*37748cd8SNickeau            $inlineCss = substr($inlineCss, 0, -1);
438*37748cd8SNickeau        }
439*37748cd8SNickeau        return $inlineCss;
440*37748cd8SNickeau    }
441*37748cd8SNickeau
442*37748cd8SNickeau    /**
443*37748cd8SNickeau     * @param $tag
444*37748cd8SNickeau     * @return string
445*37748cd8SNickeau     * Create a pattern used where the tag is not a container.
446*37748cd8SNickeau     * ie
447*37748cd8SNickeau     * <br/>
448*37748cd8SNickeau     * <icon/>
449*37748cd8SNickeau     * This is generally used with a subtition plugin
450*37748cd8SNickeau     * and a {@link Lexer::addSpecialPattern} state
451*37748cd8SNickeau     * where the tag is just replaced
452*37748cd8SNickeau     */
453*37748cd8SNickeau    public static function getEmptyTagPattern($tag)
454*37748cd8SNickeau    {
455*37748cd8SNickeau        return '<' . $tag . '.*?/>';
456*37748cd8SNickeau    }
457*37748cd8SNickeau
458*37748cd8SNickeau    /**
459*37748cd8SNickeau     * Just call this function from a class like that
460*37748cd8SNickeau     *     getTageName(get_called_class())
461*37748cd8SNickeau     * to get the tag name (ie the component plugin)
462*37748cd8SNickeau     * of a syntax plugin
463*37748cd8SNickeau     *
464*37748cd8SNickeau     * @param $get_called_class
465*37748cd8SNickeau     * @return string
466*37748cd8SNickeau     */
467*37748cd8SNickeau    public static function getTagName($get_called_class)
468*37748cd8SNickeau    {
469*37748cd8SNickeau        list(/* $t */, /* $p */, /* $n */, $c) = explode('_', $get_called_class, 4);
470*37748cd8SNickeau        return (isset($c) ? $c : '');
471*37748cd8SNickeau    }
472*37748cd8SNickeau
473*37748cd8SNickeau    /**
474*37748cd8SNickeau     * Just call this function from a class like that
475*37748cd8SNickeau     *     getAdminPageName(get_called_class())
476*37748cd8SNickeau     * to get the page name of a admin plugin
477*37748cd8SNickeau     *
478*37748cd8SNickeau     * @param $get_called_class
479*37748cd8SNickeau     * @return string - the admin page name
480*37748cd8SNickeau     */
481*37748cd8SNickeau    public static function getAdminPageName($get_called_class)
482*37748cd8SNickeau    {
483*37748cd8SNickeau        $names = explode('_', $get_called_class);
484*37748cd8SNickeau        $names = array_slice($names, -2);
485*37748cd8SNickeau        return implode('_', $names);
486*37748cd8SNickeau    }
487*37748cd8SNickeau
488*37748cd8SNickeau    public static function getNameSpace()
489*37748cd8SNickeau    {
490*37748cd8SNickeau        // No : at the begin of the namespace please
491*37748cd8SNickeau        return self::PLUGIN_BASE_NAME . ':';
492*37748cd8SNickeau    }
493*37748cd8SNickeau
494*37748cd8SNickeau    /**
495*37748cd8SNickeau     * @param $get_called_class - the plugin class
496*37748cd8SNickeau     * @return array
497*37748cd8SNickeau     */
498*37748cd8SNickeau    public static function getTags($get_called_class)
499*37748cd8SNickeau    {
500*37748cd8SNickeau        $elements = array();
501*37748cd8SNickeau        $elementName = PluginUtility::getTagName($get_called_class);
502*37748cd8SNickeau        $elements[] = $elementName;
503*37748cd8SNickeau        $elements[] = strtoupper($elementName);
504*37748cd8SNickeau        return $elements;
505*37748cd8SNickeau    }
506*37748cd8SNickeau
507*37748cd8SNickeau    /**
508*37748cd8SNickeau     * Render a text
509*37748cd8SNickeau     * @param $pageContent
510*37748cd8SNickeau     * @return string|null
511*37748cd8SNickeau     */
512*37748cd8SNickeau    public static function render($pageContent)
513*37748cd8SNickeau    {
514*37748cd8SNickeau        return RenderUtility::renderText2XhtmlAndStripPEventually($pageContent, false);
515*37748cd8SNickeau    }
516*37748cd8SNickeau
517*37748cd8SNickeau
518*37748cd8SNickeau    /**
519*37748cd8SNickeau     * This method will takes attributes
520*37748cd8SNickeau     * and process the plugin styling attribute such as width and height
521*37748cd8SNickeau     * to put them in a style HTML attribute
522*37748cd8SNickeau     * @param TagAttributes $attributes
523*37748cd8SNickeau     */
524*37748cd8SNickeau    public static function processStyle(&$attributes)
525*37748cd8SNickeau    {
526*37748cd8SNickeau        // Style
527*37748cd8SNickeau        $styleAttributeName = "style";
528*37748cd8SNickeau        if ($attributes->hasComponentAttribute($styleAttributeName)) {
529*37748cd8SNickeau            $properties = explode(";", $attributes->getValueAndRemove($styleAttributeName));
530*37748cd8SNickeau            foreach ($properties as $property) {
531*37748cd8SNickeau                list($key, $value) = explode(":", $property);
532*37748cd8SNickeau                if ($key != "") {
533*37748cd8SNickeau                    $attributes->addStyleDeclaration($key, $value);
534*37748cd8SNickeau                }
535*37748cd8SNickeau            }
536*37748cd8SNickeau        }
537*37748cd8SNickeau
538*37748cd8SNickeau
539*37748cd8SNickeau        /**
540*37748cd8SNickeau         * Border Color
541*37748cd8SNickeau         * For background color, see {@link TagAttributes::processBackground()}
542*37748cd8SNickeau         * For text color, see {@link TextColor}
543*37748cd8SNickeau         */
544*37748cd8SNickeau
545*37748cd8SNickeau        if ($attributes->hasComponentAttribute(ColorUtility::BORDER_COLOR)) {
546*37748cd8SNickeau            $colorValue = $attributes->getValueAndRemove(ColorUtility::BORDER_COLOR);
547*37748cd8SNickeau            $attributes->addStyleDeclaration(ColorUtility::BORDER_COLOR, ColorUtility::getColorValue($colorValue));
548*37748cd8SNickeau            self::checkDefaultBorderColorAttributes($attributes);
549*37748cd8SNickeau        }
550*37748cd8SNickeau
551*37748cd8SNickeau
552*37748cd8SNickeau    }
553*37748cd8SNickeau
554*37748cd8SNickeau    /**
555*37748cd8SNickeau     * Return the name of the requested script
556*37748cd8SNickeau     */
557*37748cd8SNickeau    public
558*37748cd8SNickeau    static function getRequestScript()
559*37748cd8SNickeau    {
560*37748cd8SNickeau        $scriptPath = null;
561*37748cd8SNickeau        $testPropertyValue = self::getPropertyValue("SCRIPT_NAME");
562*37748cd8SNickeau        if (defined('DOKU_UNITTEST') && $testPropertyValue != null) {
563*37748cd8SNickeau            return $testPropertyValue;
564*37748cd8SNickeau        }
565*37748cd8SNickeau        if (array_key_exists("DOCUMENT_URI", $_SERVER)) {
566*37748cd8SNickeau            $scriptPath = $_SERVER["DOCUMENT_URI"];
567*37748cd8SNickeau        }
568*37748cd8SNickeau        if ($scriptPath == null && array_key_exists("SCRIPT_NAME", $_SERVER)) {
569*37748cd8SNickeau            $scriptPath = $_SERVER["SCRIPT_NAME"];
570*37748cd8SNickeau        }
571*37748cd8SNickeau        if ($scriptPath == null) {
572*37748cd8SNickeau            msg("Unable to find the main script", LogUtility::LVL_MSG_ERROR);
573*37748cd8SNickeau        }
574*37748cd8SNickeau        $path_parts = pathinfo($scriptPath);
575*37748cd8SNickeau        return $path_parts['basename'];
576*37748cd8SNickeau    }
577*37748cd8SNickeau
578*37748cd8SNickeau    /**
579*37748cd8SNickeau     *
580*37748cd8SNickeau     * @param $name
581*37748cd8SNickeau     * @param $default
582*37748cd8SNickeau     * @return string - the value of a query string property or if in test mode, the value of a test variable
583*37748cd8SNickeau     * set with {@link self::setTestProperty}
584*37748cd8SNickeau     * This is used to test script that are not supported by the dokuwiki test framework
585*37748cd8SNickeau     * such as css.php
586*37748cd8SNickeau     */
587*37748cd8SNickeau    public
588*37748cd8SNickeau    static function getPropertyValue($name, $default = null)
589*37748cd8SNickeau    {
590*37748cd8SNickeau        global $INPUT;
591*37748cd8SNickeau        $value = $INPUT->str($name);
592*37748cd8SNickeau        if ($value == null && defined('DOKU_UNITTEST')) {
593*37748cd8SNickeau            global $COMBO;
594*37748cd8SNickeau            $value = $COMBO[$name];
595*37748cd8SNickeau        }
596*37748cd8SNickeau        if ($value == null) {
597*37748cd8SNickeau            return $default;
598*37748cd8SNickeau        } else {
599*37748cd8SNickeau            return $value;
600*37748cd8SNickeau        }
601*37748cd8SNickeau
602*37748cd8SNickeau    }
603*37748cd8SNickeau
604*37748cd8SNickeau    /**
605*37748cd8SNickeau     * Create an URL to the documentation website
606*37748cd8SNickeau     * @param $canonical - canonical id or slug
607*37748cd8SNickeau     * @param $text -  the text of the link
608*37748cd8SNickeau     * @param bool $withIcon - used to break the recursion with the message in the {@link Icon}
609*37748cd8SNickeau     * @return string - an url
610*37748cd8SNickeau     */
611*37748cd8SNickeau    public
612*37748cd8SNickeau    static function getUrl($canonical, $text, $withIcon = true)
613*37748cd8SNickeau    {
614*37748cd8SNickeau        /** @noinspection SpellCheckingInspection */
615*37748cd8SNickeau
616*37748cd8SNickeau        $xhtmlIcon = "";
617*37748cd8SNickeau        if ($withIcon) {
618*37748cd8SNickeau
619*37748cd8SNickeau            /**
620*37748cd8SNickeau             * We don't include it as an external resource via url
621*37748cd8SNickeau             * because it then make a http request for every logo
622*37748cd8SNickeau             * in the configuration page and makes it really slow
623*37748cd8SNickeau             */
624*37748cd8SNickeau            $path = File::createFromPath(Resources::getImagesDirectory() . "/logo.svg");
625*37748cd8SNickeau            $tagAttributes = TagAttributes::createEmpty(SvgImageLink::CANONICAL);
626*37748cd8SNickeau            $tagAttributes->addComponentAttributeValue(TagAttributes::TYPE_KEY, SvgDocument::ICON_TYPE);
627*37748cd8SNickeau            $tagAttributes->addComponentAttributeValue(Dimension::WIDTH_KEY, "20");
628*37748cd8SNickeau            $cache = new CacheMedia($path, $tagAttributes);
629*37748cd8SNickeau            if (!$cache->isCacheUsable()) {
630*37748cd8SNickeau                $xhtmlIcon = SvgDocument::createFromPath($path)
631*37748cd8SNickeau                    ->setShouldBeOptimized(true)
632*37748cd8SNickeau                    ->getXmlText($tagAttributes);
633*37748cd8SNickeau                $cache->storeCache($xhtmlIcon);
634*37748cd8SNickeau            }
635*37748cd8SNickeau            $xhtmlIcon = file_get_contents($cache->getFile()->getFileSystemPath());
636*37748cd8SNickeau
637*37748cd8SNickeau
638*37748cd8SNickeau        }
639*37748cd8SNickeau        return $xhtmlIcon . ' <a href="' . self::$URL_BASE . '/' . str_replace(":", "/", $canonical) . '" title="' . $text . '">' . $text . '</a>';
640*37748cd8SNickeau    }
641*37748cd8SNickeau
642*37748cd8SNickeau    /**
643*37748cd8SNickeau     * An utility function to not search every time which array should be first
644*37748cd8SNickeau     * @param array $inlineAttributes - the component inline attributes
645*37748cd8SNickeau     * @param array $defaultAttributes - the default configuration attributes
646*37748cd8SNickeau     * @return array - a merged array
647*37748cd8SNickeau     */
648*37748cd8SNickeau    public
649*37748cd8SNickeau    static function mergeAttributes(array $inlineAttributes, array $defaultAttributes = array())
650*37748cd8SNickeau    {
651*37748cd8SNickeau        return array_merge($defaultAttributes, $inlineAttributes);
652*37748cd8SNickeau    }
653*37748cd8SNickeau
654*37748cd8SNickeau    /**
655*37748cd8SNickeau     * A pattern for a container tag
656*37748cd8SNickeau     * that needs to catch the content
657*37748cd8SNickeau     *
658*37748cd8SNickeau     * Use as a special pattern (substition)
659*37748cd8SNickeau     *
660*37748cd8SNickeau     * The {@link \syntax_plugin_combo_math} use it
661*37748cd8SNickeau     * @param $tag
662*37748cd8SNickeau     * @return string - a pattern
663*37748cd8SNickeau     */
664*37748cd8SNickeau    public
665*37748cd8SNickeau    static function getLeafContainerTagPattern($tag)
666*37748cd8SNickeau    {
667*37748cd8SNickeau        return '<' . $tag . '.*?>.*?<\/' . $tag . '>';
668*37748cd8SNickeau    }
669*37748cd8SNickeau
670*37748cd8SNickeau    /**
671*37748cd8SNickeau     * Return the content of a tag
672*37748cd8SNickeau     * <math>Content</math>
673*37748cd8SNickeau     * @param $match
674*37748cd8SNickeau     * @return string the content
675*37748cd8SNickeau     */
676*37748cd8SNickeau    public
677*37748cd8SNickeau    static function getTagContent($match)
678*37748cd8SNickeau    {
679*37748cd8SNickeau        // From the first >
680*37748cd8SNickeau        $start = strpos($match, ">");
681*37748cd8SNickeau        if ($start == false) {
682*37748cd8SNickeau            LogUtility::msg("The match does not contain any opening tag. Match: {$match}", LogUtility::LVL_MSG_ERROR);
683*37748cd8SNickeau            return "";
684*37748cd8SNickeau        }
685*37748cd8SNickeau        $match = substr($match, $start + 1);
686*37748cd8SNickeau        // If this is the last character, we get a false
687*37748cd8SNickeau        if ($match == false) {
688*37748cd8SNickeau            LogUtility::msg("The match does not contain any closing tag. Match: {$match}", LogUtility::LVL_MSG_ERROR);
689*37748cd8SNickeau            return "";
690*37748cd8SNickeau        }
691*37748cd8SNickeau
692*37748cd8SNickeau        $end = strrpos($match, "</");
693*37748cd8SNickeau        if ($end == false) {
694*37748cd8SNickeau            LogUtility::msg("The match does not contain any closing tag. Match: {$match}", LogUtility::LVL_MSG_ERROR);
695*37748cd8SNickeau            return "";
696*37748cd8SNickeau        }
697*37748cd8SNickeau
698*37748cd8SNickeau        return substr($match, 0, $end);
699*37748cd8SNickeau    }
700*37748cd8SNickeau
701*37748cd8SNickeau    /**
702*37748cd8SNickeau     *
703*37748cd8SNickeau     * Check if a HTML tag was already added for a request
704*37748cd8SNickeau     * The request id is just the timestamp
705*37748cd8SNickeau     * An indicator array should be provided
706*37748cd8SNickeau     * @return string
707*37748cd8SNickeau     */
708*37748cd8SNickeau    public
709*37748cd8SNickeau    static function getRequestId()
710*37748cd8SNickeau    {
711*37748cd8SNickeau
712*37748cd8SNickeau        if (isset($_SERVER['REQUEST_TIME_FLOAT'])) {
713*37748cd8SNickeau            // since php 5.4
714*37748cd8SNickeau            $requestTime = $_SERVER['REQUEST_TIME_FLOAT'];
715*37748cd8SNickeau        } else {
716*37748cd8SNickeau            // DokuWiki test framework use this
717*37748cd8SNickeau            $requestTime = $_SERVER['REQUEST_TIME'];
718*37748cd8SNickeau        }
719*37748cd8SNickeau        $keyPrefix = 'combo_';
720*37748cd8SNickeau
721*37748cd8SNickeau        global $ID;
722*37748cd8SNickeau        return $keyPrefix . hash('crc32b', $_SERVER['REMOTE_ADDR'] . $_SERVER['REMOTE_PORT'] . $requestTime . $ID);
723*37748cd8SNickeau
724*37748cd8SNickeau    }
725*37748cd8SNickeau
726*37748cd8SNickeau    /**
727*37748cd8SNickeau     * Get the page id
728*37748cd8SNickeau     * If the page is a sidebar, it will not return the id of the sidebar
729*37748cd8SNickeau     * but the one of the page
730*37748cd8SNickeau     * @return string
731*37748cd8SNickeau     */
732*37748cd8SNickeau    public
733*37748cd8SNickeau    static function getPageId()
734*37748cd8SNickeau    {
735*37748cd8SNickeau        return FsWikiUtility::getMainPageId();
736*37748cd8SNickeau    }
737*37748cd8SNickeau
738*37748cd8SNickeau    /**
739*37748cd8SNickeau     * Transform special HTML characters to entity
740*37748cd8SNickeau     * Example:
741*37748cd8SNickeau     * <hello>world</hello>
742*37748cd8SNickeau     * to
743*37748cd8SNickeau     * "&lt;hello&gt;world&lt;/hello&gt;"
744*37748cd8SNickeau     *
745*37748cd8SNickeau     * @param $text
746*37748cd8SNickeau     * @return string
747*37748cd8SNickeau     */
748*37748cd8SNickeau    public
749*37748cd8SNickeau    static function htmlEncode($text)
750*37748cd8SNickeau    {
751*37748cd8SNickeau        /**
752*37748cd8SNickeau         * See https://stackoverflow.com/questions/46483/htmlentities-vs-htmlspecialchars/3614344
753*37748cd8SNickeau         * {@link htmlentities }
754*37748cd8SNickeau         */
755*37748cd8SNickeau        //return htmlspecialchars($text, ENT_QUOTES);
756*37748cd8SNickeau        return htmlentities($text);
757*37748cd8SNickeau    }
758*37748cd8SNickeau
759*37748cd8SNickeau
760*37748cd8SNickeau    /**
761*37748cd8SNickeau     * Add a class
762*37748cd8SNickeau     * @param $classValue
763*37748cd8SNickeau     * @param array $attributes
764*37748cd8SNickeau     */
765*37748cd8SNickeau    public
766*37748cd8SNickeau    static function addClass2Attributes($classValue, array &$attributes)
767*37748cd8SNickeau    {
768*37748cd8SNickeau        self::addAttributeValue("class", $classValue, $attributes);
769*37748cd8SNickeau    }
770*37748cd8SNickeau
771*37748cd8SNickeau    /**
772*37748cd8SNickeau     * Add a style property to the attributes
773*37748cd8SNickeau     * @param $property
774*37748cd8SNickeau     * @param $value
775*37748cd8SNickeau     * @param array $attributes
776*37748cd8SNickeau     * @deprecated use {@link TagAttributes::addStyleDeclaration()} instead
777*37748cd8SNickeau     */
778*37748cd8SNickeau    public
779*37748cd8SNickeau    static function addStyleProperty($property, $value, array &$attributes)
780*37748cd8SNickeau    {
781*37748cd8SNickeau        if (isset($attributes["style"])) {
782*37748cd8SNickeau            $attributes["style"] .= ";$property:$value";
783*37748cd8SNickeau        } else {
784*37748cd8SNickeau            $attributes["style"] = "$property:$value";
785*37748cd8SNickeau        }
786*37748cd8SNickeau
787*37748cd8SNickeau    }
788*37748cd8SNickeau
789*37748cd8SNickeau    /**
790*37748cd8SNickeau     * Add default border attributes
791*37748cd8SNickeau     * to see a border
792*37748cd8SNickeau     * Doc
793*37748cd8SNickeau     * https://combostrap.com/styling/color#border_color
794*37748cd8SNickeau     * @param TagAttributes $tagAttributes
795*37748cd8SNickeau     */
796*37748cd8SNickeau    private
797*37748cd8SNickeau    static function checkDefaultBorderColorAttributes(&$tagAttributes)
798*37748cd8SNickeau    {
799*37748cd8SNickeau        /**
800*37748cd8SNickeau         * border color was set without the width
801*37748cd8SNickeau         * setting the width
802*37748cd8SNickeau         */
803*37748cd8SNickeau        if (!(
804*37748cd8SNickeau            $tagAttributes->hasStyleDeclaration("border")
805*37748cd8SNickeau            ||
806*37748cd8SNickeau            $tagAttributes->hasStyleDeclaration("border-width")
807*37748cd8SNickeau        )
808*37748cd8SNickeau        ) {
809*37748cd8SNickeau            $tagAttributes->addStyleDeclaration("border-width", "1px");
810*37748cd8SNickeau        }
811*37748cd8SNickeau        /**
812*37748cd8SNickeau         * border color was set without the style
813*37748cd8SNickeau         * setting the style
814*37748cd8SNickeau         */
815*37748cd8SNickeau        if (!
816*37748cd8SNickeau        (
817*37748cd8SNickeau            $tagAttributes->hasStyleDeclaration("border")
818*37748cd8SNickeau            ||
819*37748cd8SNickeau            $tagAttributes->hasStyleDeclaration("border-style")
820*37748cd8SNickeau        )
821*37748cd8SNickeau        ) {
822*37748cd8SNickeau            $tagAttributes->addStyleDeclaration("border-style", "solid");
823*37748cd8SNickeau
824*37748cd8SNickeau        }
825*37748cd8SNickeau        if (!$tagAttributes->hasStyleDeclaration("border-radius")) {
826*37748cd8SNickeau            $tagAttributes->addStyleDeclaration("border-radius", ".25rem");
827*37748cd8SNickeau        }
828*37748cd8SNickeau
829*37748cd8SNickeau    }
830*37748cd8SNickeau
831*37748cd8SNickeau    public
832*37748cd8SNickeau    static function getConfValue($confName, $defaultValue = null)
833*37748cd8SNickeau    {
834*37748cd8SNickeau        global $conf;
835*37748cd8SNickeau        if (isset($conf['plugin'][PluginUtility::PLUGIN_BASE_NAME][$confName])) {
836*37748cd8SNickeau            return $conf['plugin'][PluginUtility::PLUGIN_BASE_NAME][$confName];
837*37748cd8SNickeau        } else {
838*37748cd8SNickeau            return $defaultValue;
839*37748cd8SNickeau        }
840*37748cd8SNickeau    }
841*37748cd8SNickeau
842*37748cd8SNickeau    /**
843*37748cd8SNickeau     * @param $match
844*37748cd8SNickeau     * @return null|string - return the tag name or null if not found
845*37748cd8SNickeau     */
846*37748cd8SNickeau    public
847*37748cd8SNickeau    static function getTag($match)
848*37748cd8SNickeau    {
849*37748cd8SNickeau
850*37748cd8SNickeau        // Trim to start clean
851*37748cd8SNickeau        $match = trim($match);
852*37748cd8SNickeau
853*37748cd8SNickeau        // Until the first >
854*37748cd8SNickeau        $pos = strpos($match, ">");
855*37748cd8SNickeau        if ($pos == false) {
856*37748cd8SNickeau            LogUtility::msg("The match does not contain any tag. Match: {$match}", LogUtility::LVL_MSG_ERROR);
857*37748cd8SNickeau            return null;
858*37748cd8SNickeau        }
859*37748cd8SNickeau        $match = substr($match, 0, $pos);
860*37748cd8SNickeau
861*37748cd8SNickeau        // Suppress the <
862*37748cd8SNickeau        if ($match[0] == "<") {
863*37748cd8SNickeau            $match = substr($match, 1);
864*37748cd8SNickeau        } else {
865*37748cd8SNickeau            LogUtility::msg("This is not a text tag because it does not start with the character `>`");
866*37748cd8SNickeau        }
867*37748cd8SNickeau
868*37748cd8SNickeau        // Suppress the tag name (ie until the first blank)
869*37748cd8SNickeau        $spacePosition = strpos($match, " ");
870*37748cd8SNickeau        if (!$spacePosition) {
871*37748cd8SNickeau            // No space, meaning this is only the tag name
872*37748cd8SNickeau            return $match;
873*37748cd8SNickeau        } else {
874*37748cd8SNickeau            return substr($match, 0, $spacePosition);
875*37748cd8SNickeau        }
876*37748cd8SNickeau
877*37748cd8SNickeau    }
878*37748cd8SNickeau
879*37748cd8SNickeau
880*37748cd8SNickeau    /**
881*37748cd8SNickeau     * @param string $string add a command into HTML
882*37748cd8SNickeau     */
883*37748cd8SNickeau    public
884*37748cd8SNickeau    static function addAsHtmlComment($string)
885*37748cd8SNickeau    {
886*37748cd8SNickeau        print_r('<!-- ' . self::htmlEncode($string) . '-->');
887*37748cd8SNickeau    }
888*37748cd8SNickeau
889*37748cd8SNickeau    public
890*37748cd8SNickeau    static function getResourceBaseUrl()
891*37748cd8SNickeau    {
892*37748cd8SNickeau        return DOKU_URL . 'lib/plugins/' . PluginUtility::PLUGIN_BASE_NAME . '/resources';
893*37748cd8SNickeau    }
894*37748cd8SNickeau
895*37748cd8SNickeau    /**
896*37748cd8SNickeau     * @param $TAG - the name of the tag that should correspond to the name of the css file in the style directory
897*37748cd8SNickeau     * @return string - a inline style element to inject in the page or blank if no file exists
898*37748cd8SNickeau     */
899*37748cd8SNickeau    public
900*37748cd8SNickeau    static function getTagStyle($TAG)
901*37748cd8SNickeau    {
902*37748cd8SNickeau        $script = self::getCssRules($TAG);
903*37748cd8SNickeau        if (!empty($script)) {
904*37748cd8SNickeau            return "<style>" . $script . "</style>";
905*37748cd8SNickeau        } else {
906*37748cd8SNickeau            return "";
907*37748cd8SNickeau        }
908*37748cd8SNickeau
909*37748cd8SNickeau    }
910*37748cd8SNickeau
911*37748cd8SNickeau
912*37748cd8SNickeau    public
913*37748cd8SNickeau    static function getComponentName($tag)
914*37748cd8SNickeau    {
915*37748cd8SNickeau        return strtolower(PluginUtility::PLUGIN_BASE_NAME) . "_" . $tag;
916*37748cd8SNickeau    }
917*37748cd8SNickeau
918*37748cd8SNickeau    public
919*37748cd8SNickeau    static function addAttributeValue($attribute, $value, array &$attributes)
920*37748cd8SNickeau    {
921*37748cd8SNickeau        if (array_key_exists($attribute, $attributes) && $attributes[$attribute] !== "") {
922*37748cd8SNickeau            $attributes[$attribute] .= " {$value}";
923*37748cd8SNickeau        } else {
924*37748cd8SNickeau            $attributes[$attribute] = "{$value}";
925*37748cd8SNickeau        }
926*37748cd8SNickeau    }
927*37748cd8SNickeau
928*37748cd8SNickeau    /**
929*37748cd8SNickeau     * Plugin Utility is available to all plugin,
930*37748cd8SNickeau     * this is a convenient way to the the snippet manager
931*37748cd8SNickeau     * @return SnippetManager
932*37748cd8SNickeau     */
933*37748cd8SNickeau    public
934*37748cd8SNickeau    static function getSnippetManager()
935*37748cd8SNickeau    {
936*37748cd8SNickeau        return SnippetManager::get();
937*37748cd8SNickeau    }
938*37748cd8SNickeau
939*37748cd8SNickeau    public
940*37748cd8SNickeau    static function initStaticManager()
941*37748cd8SNickeau    {
942*37748cd8SNickeau        CacheManager::init();
943*37748cd8SNickeau        SnippetManager::init();
944*37748cd8SNickeau    }
945*37748cd8SNickeau
946*37748cd8SNickeau    /**
947*37748cd8SNickeau     * Function used in a render
948*37748cd8SNickeau     * @param $data - the data from {@link PluginUtility::handleAndReturnUnmatchedData()}
949*37748cd8SNickeau     * @return string
950*37748cd8SNickeau     */
951*37748cd8SNickeau    public
952*37748cd8SNickeau    static function renderUnmatched($data)
953*37748cd8SNickeau    {
954*37748cd8SNickeau        /**
955*37748cd8SNickeau         * Attributes
956*37748cd8SNickeau         */
957*37748cd8SNickeau        if (isset($data[PluginUtility::ATTRIBUTES])) {
958*37748cd8SNickeau            $attributes = $data[PluginUtility::ATTRIBUTES];
959*37748cd8SNickeau        } else {
960*37748cd8SNickeau            $attributes = [];
961*37748cd8SNickeau        }
962*37748cd8SNickeau        $tagAttributes = TagAttributes::createFromCallStackArray($attributes);
963*37748cd8SNickeau        $display = $tagAttributes->getValue(TagAttributes::DISPLAY);
964*37748cd8SNickeau        if ($display != "none") {
965*37748cd8SNickeau            $payload = $data[self::PAYLOAD];
966*37748cd8SNickeau            $context = $data[self::CONTEXT];
967*37748cd8SNickeau            if (!in_array($context, Call::INLINE_DOKUWIKI_COMPONENTS)) {
968*37748cd8SNickeau                $payload = ltrim($payload);
969*37748cd8SNickeau            }
970*37748cd8SNickeau            return PluginUtility::htmlEncode($payload);
971*37748cd8SNickeau        } else {
972*37748cd8SNickeau            return "";
973*37748cd8SNickeau        }
974*37748cd8SNickeau    }
975*37748cd8SNickeau
976*37748cd8SNickeau    /**
977*37748cd8SNickeau     * Function used in a handle function of a syntax plugin for
978*37748cd8SNickeau     * unmatched context
979*37748cd8SNickeau     * @param $tagName
980*37748cd8SNickeau     * @param $match
981*37748cd8SNickeau     * @param \Doku_Handler $handler
982*37748cd8SNickeau     * @return array
983*37748cd8SNickeau     */
984*37748cd8SNickeau    public
985*37748cd8SNickeau    static function handleAndReturnUnmatchedData($tagName, $match, \Doku_Handler $handler)
986*37748cd8SNickeau    {
987*37748cd8SNickeau        $tag = new Tag($tagName, array(), DOKU_LEXER_UNMATCHED, $handler);
988*37748cd8SNickeau        $sibling = $tag->getPreviousSibling();
989*37748cd8SNickeau        $context = null;
990*37748cd8SNickeau        if (!empty($sibling)) {
991*37748cd8SNickeau            $context = $sibling->getName();
992*37748cd8SNickeau        }
993*37748cd8SNickeau        return array(
994*37748cd8SNickeau            PluginUtility::STATE => DOKU_LEXER_UNMATCHED,
995*37748cd8SNickeau            PluginUtility::PAYLOAD => $match,
996*37748cd8SNickeau            PluginUtility::CONTEXT => $context
997*37748cd8SNickeau        );
998*37748cd8SNickeau    }
999*37748cd8SNickeau
1000*37748cd8SNickeau    public
1001*37748cd8SNickeau    static function setConf($key, $value, $namespace = 'plugin')
1002*37748cd8SNickeau    {
1003*37748cd8SNickeau        global $conf;
1004*37748cd8SNickeau        if ($namespace != null) {
1005*37748cd8SNickeau            $conf[$namespace][PluginUtility::PLUGIN_BASE_NAME][$key] = $value;
1006*37748cd8SNickeau        } else {
1007*37748cd8SNickeau            $conf[$key] = $value;
1008*37748cd8SNickeau        }
1009*37748cd8SNickeau
1010*37748cd8SNickeau    }
1011*37748cd8SNickeau
1012*37748cd8SNickeau    /**
1013*37748cd8SNickeau     * Utility methodPreprocess a start tag to be able to extract the name
1014*37748cd8SNickeau     * and the attributes easily
1015*37748cd8SNickeau     *
1016*37748cd8SNickeau     * It will delete:
1017*37748cd8SNickeau     *   * the characters <> and the /> if present
1018*37748cd8SNickeau     *   * and trim
1019*37748cd8SNickeau     *
1020*37748cd8SNickeau     * It will remain the tagname and its attributes
1021*37748cd8SNickeau     * @param $match
1022*37748cd8SNickeau     * @return false|string|null
1023*37748cd8SNickeau     */
1024*37748cd8SNickeau    private
1025*37748cd8SNickeau    static function getPreprocessEnterTag($match)
1026*37748cd8SNickeau    {
1027*37748cd8SNickeau        // Until the first >
1028*37748cd8SNickeau        $pos = strpos($match, ">");
1029*37748cd8SNickeau        if ($pos == false) {
1030*37748cd8SNickeau            LogUtility::msg("The match does not contain any tag. Match: {$match}", LogUtility::LVL_MSG_WARNING);
1031*37748cd8SNickeau            return null;
1032*37748cd8SNickeau        }
1033*37748cd8SNickeau        $match = substr($match, 0, $pos);
1034*37748cd8SNickeau
1035*37748cd8SNickeau
1036*37748cd8SNickeau        // Trim to start clean
1037*37748cd8SNickeau        $match = trim($match);
1038*37748cd8SNickeau
1039*37748cd8SNickeau        // Suppress the <
1040*37748cd8SNickeau        if ($match[0] == "<") {
1041*37748cd8SNickeau            $match = substr($match, 1);
1042*37748cd8SNickeau        }
1043*37748cd8SNickeau
1044*37748cd8SNickeau        // Suppress the / for a leaf tag
1045*37748cd8SNickeau        if ($match[strlen($match) - 1] == "/") {
1046*37748cd8SNickeau            $match = substr($match, 0, strlen($match) - 1);
1047*37748cd8SNickeau        }
1048*37748cd8SNickeau        return $match;
1049*37748cd8SNickeau    }
1050*37748cd8SNickeau
1051*37748cd8SNickeau    /**
1052*37748cd8SNickeau     * Retrieve the tag name used in the text document
1053*37748cd8SNickeau     * @param $match
1054*37748cd8SNickeau     * @return false|string|null
1055*37748cd8SNickeau     */
1056*37748cd8SNickeau    public
1057*37748cd8SNickeau    static function getSyntaxTagNameFromMatch($match)
1058*37748cd8SNickeau    {
1059*37748cd8SNickeau        $preprocessMatch = PluginUtility::getPreprocessEnterTag($match);
1060*37748cd8SNickeau
1061*37748cd8SNickeau        // Tag name (ie until the first blank)
1062*37748cd8SNickeau        $spacePosition = strpos($match, " ");
1063*37748cd8SNickeau        if (!$spacePosition) {
1064*37748cd8SNickeau            // No space, meaning this is only the tag name
1065*37748cd8SNickeau            return $preprocessMatch;
1066*37748cd8SNickeau        } else {
1067*37748cd8SNickeau            return trim(substr(0, $spacePosition));
1068*37748cd8SNickeau        }
1069*37748cd8SNickeau
1070*37748cd8SNickeau    }
1071*37748cd8SNickeau
1072*37748cd8SNickeau    /**
1073*37748cd8SNickeau     * @param \Doku_Renderer_xhtml $renderer
1074*37748cd8SNickeau     * @param $position
1075*37748cd8SNickeau     * @param $name
1076*37748cd8SNickeau     */
1077*37748cd8SNickeau    public
1078*37748cd8SNickeau    static function startSection($renderer, $position, $name)
1079*37748cd8SNickeau    {
1080*37748cd8SNickeau
1081*37748cd8SNickeau
1082*37748cd8SNickeau        if (empty($position)) {
1083*37748cd8SNickeau            LogUtility::msg("The position for a start section should not be empty", LogUtility::LVL_MSG_ERROR, "support");
1084*37748cd8SNickeau        }
1085*37748cd8SNickeau        if (empty($name)) {
1086*37748cd8SNickeau            LogUtility::msg("The name for a start section should not be empty", LogUtility::LVL_MSG_ERROR, "support");
1087*37748cd8SNickeau        }
1088*37748cd8SNickeau
1089*37748cd8SNickeau        /**
1090*37748cd8SNickeau         * New Dokuwiki Version
1091*37748cd8SNickeau         * for DokuWiki Greebo and more recent versions
1092*37748cd8SNickeau         */
1093*37748cd8SNickeau        if (defined('SEC_EDIT_PATTERN')) {
1094*37748cd8SNickeau            $renderer->startSectionEdit($position, array('target' => self::EDIT_SECTION_TARGET, 'name' => $name));
1095*37748cd8SNickeau        } else {
1096*37748cd8SNickeau            /**
1097*37748cd8SNickeau             * Old version
1098*37748cd8SNickeau             */
1099*37748cd8SNickeau            /** @noinspection PhpParamsInspection */
1100*37748cd8SNickeau            $renderer->startSectionEdit($position, self::EDIT_SECTION_TARGET, $name);
1101*37748cd8SNickeau        }
1102*37748cd8SNickeau    }
1103*37748cd8SNickeau
1104*37748cd8SNickeau    /**
1105*37748cd8SNickeau     * Add an enter call to the stack
1106*37748cd8SNickeau     * @param \Doku_Handler $handler
1107*37748cd8SNickeau     * @param $tagName
1108*37748cd8SNickeau     * @param array $callStackArray
1109*37748cd8SNickeau     */
1110*37748cd8SNickeau    public
1111*37748cd8SNickeau    static function addEnterCall(
1112*37748cd8SNickeau        \Doku_Handler &$handler,
1113*37748cd8SNickeau        $tagName,
1114*37748cd8SNickeau        $callStackArray = array()
1115*37748cd8SNickeau    )
1116*37748cd8SNickeau    {
1117*37748cd8SNickeau        $pluginName = PluginUtility::getComponentName($tagName);
1118*37748cd8SNickeau        $handler->addPluginCall(
1119*37748cd8SNickeau            $pluginName,
1120*37748cd8SNickeau            $callStackArray,
1121*37748cd8SNickeau            DOKU_LEXER_ENTER,
1122*37748cd8SNickeau            null,
1123*37748cd8SNickeau            null
1124*37748cd8SNickeau        );
1125*37748cd8SNickeau    }
1126*37748cd8SNickeau
1127*37748cd8SNickeau    /**
1128*37748cd8SNickeau     * Add an end call dynamically
1129*37748cd8SNickeau     * @param \Doku_Handler $handler
1130*37748cd8SNickeau     * @param $tagName
1131*37748cd8SNickeau     * @param array $callStackArray
1132*37748cd8SNickeau     */
1133*37748cd8SNickeau    public
1134*37748cd8SNickeau    static function addEndCall(\Doku_Handler $handler, $tagName, $callStackArray = array())
1135*37748cd8SNickeau    {
1136*37748cd8SNickeau        $pluginName = PluginUtility::getComponentName($tagName);
1137*37748cd8SNickeau        $handler->addPluginCall(
1138*37748cd8SNickeau            $pluginName,
1139*37748cd8SNickeau            $callStackArray,
1140*37748cd8SNickeau            DOKU_LEXER_END,
1141*37748cd8SNickeau            null,
1142*37748cd8SNickeau            null
1143*37748cd8SNickeau        );
1144*37748cd8SNickeau    }
1145*37748cd8SNickeau
1146*37748cd8SNickeau    /**
1147*37748cd8SNickeau     * General Debug
1148*37748cd8SNickeau     */
1149*37748cd8SNickeau    public
1150*37748cd8SNickeau    static function isDebug()
1151*37748cd8SNickeau    {
1152*37748cd8SNickeau        global $conf;
1153*37748cd8SNickeau        return $conf["allowdebug"] === 1;
1154*37748cd8SNickeau
1155*37748cd8SNickeau    }
1156*37748cd8SNickeau
1157*37748cd8SNickeau    /**
1158*37748cd8SNickeau     * @return bool true if loaded, false otherwise
1159*37748cd8SNickeau     * Strap is loaded only if this is the same version
1160*37748cd8SNickeau     * to avoid function, class, or members that does not exist
1161*37748cd8SNickeau     */
1162*37748cd8SNickeau    public
1163*37748cd8SNickeau    static function loadStrapUtilityTemplateIfPresentAndSameVersion()
1164*37748cd8SNickeau    {
1165*37748cd8SNickeau        $templateUtilityFile = __DIR__ . '/../../../tpl/strap/class/TplUtility.php';
1166*37748cd8SNickeau        if (file_exists($templateUtilityFile)) {
1167*37748cd8SNickeau            /**
1168*37748cd8SNickeau             * Check the version
1169*37748cd8SNickeau             */
1170*37748cd8SNickeau            $templateInfo = confToHash(__DIR__ . '/../../../tpl/strap/template.info.txt');
1171*37748cd8SNickeau            $templateVersion = $templateInfo['version'];
1172*37748cd8SNickeau            $comboVersion = self::$INFO_PLUGIN['version'];
1173*37748cd8SNickeau            if ($templateVersion != $comboVersion) {
1174*37748cd8SNickeau                if ($comboVersion > $templateVersion) {
1175*37748cd8SNickeau                    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).");
1176*37748cd8SNickeau                } else {
1177*37748cd8SNickeau                    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).");
1178*37748cd8SNickeau                }
1179*37748cd8SNickeau                return false;
1180*37748cd8SNickeau            } else {
1181*37748cd8SNickeau                /** @noinspection PhpIncludeInspection */
1182*37748cd8SNickeau                require_once($templateUtilityFile);
1183*37748cd8SNickeau                return true;
1184*37748cd8SNickeau            }
1185*37748cd8SNickeau        } else {
1186*37748cd8SNickeau            $level = LogUtility::LVL_MSG_DEBUG;
1187*37748cd8SNickeau            if (defined('DOKU_UNITTEST')) {
1188*37748cd8SNickeau                // fail
1189*37748cd8SNickeau                $level = LogUtility::LVL_MSG_ERROR;
1190*37748cd8SNickeau            }
1191*37748cd8SNickeau            if (Site::getTemplate() != "strap") {
1192*37748cd8SNickeau                LogUtility::msg("The strap template is not installed", $level);
1193*37748cd8SNickeau            } else {
1194*37748cd8SNickeau                LogUtility::msg("The file ($templateUtilityFile) was not found", $level);
1195*37748cd8SNickeau            }
1196*37748cd8SNickeau            return false;
1197*37748cd8SNickeau        }
1198*37748cd8SNickeau    }
1199*37748cd8SNickeau
1200*37748cd8SNickeau
1201*37748cd8SNickeau    /**
1202*37748cd8SNickeau     *
1203*37748cd8SNickeau     * See also dev.md file
1204*37748cd8SNickeau     */
1205*37748cd8SNickeau    public static function isDevOrTest()
1206*37748cd8SNickeau    {
1207*37748cd8SNickeau        if (self::isDev()) {
1208*37748cd8SNickeau            return true;
1209*37748cd8SNickeau        }
1210*37748cd8SNickeau        return self::isTest();
1211*37748cd8SNickeau    }
1212*37748cd8SNickeau
1213*37748cd8SNickeau    public static function isDev()
1214*37748cd8SNickeau    {
1215*37748cd8SNickeau        global $_SERVER;
1216*37748cd8SNickeau        if ($_SERVER["REMOTE_ADDR"] == "127.0.0.1") {
1217*37748cd8SNickeau            return true;
1218*37748cd8SNickeau        }
1219*37748cd8SNickeau        return false;
1220*37748cd8SNickeau    }
1221*37748cd8SNickeau
1222*37748cd8SNickeau    public static function getInstructions($markiCode)
1223*37748cd8SNickeau    {
1224*37748cd8SNickeau        return p_get_instructions($markiCode);
1225*37748cd8SNickeau    }
1226*37748cd8SNickeau
1227*37748cd8SNickeau    public static function getInstructionsWithoutRoot($markiCode)
1228*37748cd8SNickeau    {
1229*37748cd8SNickeau        return RenderUtility::getInstructionsAndStripPEventually($markiCode);
1230*37748cd8SNickeau    }
1231*37748cd8SNickeau
1232*37748cd8SNickeau    /**
1233*37748cd8SNickeau     * Transform a text into a valid HTML id
1234*37748cd8SNickeau     * @param $string
1235*37748cd8SNickeau     * @return string
1236*37748cd8SNickeau     */
1237*37748cd8SNickeau    public static function toHtmlId($string)
1238*37748cd8SNickeau    {
1239*37748cd8SNickeau        /**
1240*37748cd8SNickeau         * sectionId calls cleanID
1241*37748cd8SNickeau         * cleanID delete all things before a ':'
1242*37748cd8SNickeau         * we do then the replace before to not
1243*37748cd8SNickeau         * lost a minus '-' separator
1244*37748cd8SNickeau         */
1245*37748cd8SNickeau        $string = str_replace(array(':', '.'), '', $string);
1246*37748cd8SNickeau        return sectionID($string, $check);
1247*37748cd8SNickeau    }
1248*37748cd8SNickeau
1249*37748cd8SNickeau    public static function isTest()
1250*37748cd8SNickeau    {
1251*37748cd8SNickeau        return defined('DOKU_UNITTEST');
1252*37748cd8SNickeau    }
1253*37748cd8SNickeau
1254*37748cd8SNickeau
1255*37748cd8SNickeau    public static function getCacheManager()
1256*37748cd8SNickeau    {
1257*37748cd8SNickeau        return CacheManager::get();
1258*37748cd8SNickeau    }
1259*37748cd8SNickeau
1260*37748cd8SNickeau    public static function getModeFromPluginName($name)
1261*37748cd8SNickeau    {
1262*37748cd8SNickeau        return "plugin_$name";
1263*37748cd8SNickeau    }
1264*37748cd8SNickeau
1265*37748cd8SNickeau    public static function isCi(): bool
1266*37748cd8SNickeau    {
1267*37748cd8SNickeau        // https://docs.travis-ci.com/user/environment-variables/#default-environment-variables
1268*37748cd8SNickeau        return getenv("CI")==="true";
1269*37748cd8SNickeau    }
1270*37748cd8SNickeau
1271*37748cd8SNickeau
1272*37748cd8SNickeau}
1273*37748cd8SNickeau
1274*37748cd8SNickeauPluginUtility::init();
1275