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