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 * "<hello>world</hello>" 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