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