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): string 472 { 473 474 return '<' . $tag . '[^>]*/>'; 475 } 476 477 /** 478 * Just call this function from a class like that 479 * getTageName(get_called_class()) 480 * to get the tag name (ie the component plugin) 481 * of a syntax plugin 482 * 483 * @param $get_called_class 484 * @return string 485 */ 486 public static function getTagName($get_called_class) 487 { 488 list(/* $t */, /* $p */, /* $n */, $c) = explode('_', $get_called_class, 4); 489 return (isset($c) ? $c : ''); 490 } 491 492 /** 493 * Just call this function from a class like that 494 * getAdminPageName(get_called_class()) 495 * to get the page name of a admin plugin 496 * 497 * @param $get_called_class 498 * @return string - the admin page name 499 */ 500 public static function getAdminPageName($get_called_class) 501 { 502 $names = explode('_', $get_called_class); 503 $names = array_slice($names, -2); 504 return implode('_', $names); 505 } 506 507 public static function getNameSpace() 508 { 509 // No : at the begin of the namespace please 510 return self::PLUGIN_BASE_NAME . ':'; 511 } 512 513 /** 514 * @param $get_called_class - the plugin class 515 * @return array 516 */ 517 public static function getTags($get_called_class) 518 { 519 $elements = array(); 520 $elementName = PluginUtility::getTagName($get_called_class); 521 $elements[] = $elementName; 522 $elements[] = strtoupper($elementName); 523 return $elements; 524 } 525 526 /** 527 * Render a text 528 * @param $pageContent 529 * @return string|null 530 */ 531 public static function render($pageContent) 532 { 533 return RenderUtility::renderText2XhtmlAndStripPEventually($pageContent, false); 534 } 535 536 537 /** 538 * This method will takes attributes 539 * and process the plugin styling attribute such as width and height 540 * to put them in a style HTML attribute 541 * @param TagAttributes $attributes 542 */ 543 public static function processStyle(&$attributes) 544 { 545 // Style 546 $styleAttributeName = "style"; 547 if ($attributes->hasComponentAttribute($styleAttributeName)) { 548 $properties = explode(";", $attributes->getValueAndRemove($styleAttributeName)); 549 foreach ($properties as $property) { 550 list($key, $value) = explode(":", $property); 551 if ($key != "") { 552 $attributes->addStyleDeclaration($key, $value); 553 } 554 } 555 } 556 557 558 /** 559 * Border Color 560 * For background color, see {@link TagAttributes::processBackground()} 561 * For text color, see {@link TextColor} 562 */ 563 564 if ($attributes->hasComponentAttribute(ColorUtility::BORDER_COLOR)) { 565 $colorValue = $attributes->getValueAndRemove(ColorUtility::BORDER_COLOR); 566 $attributes->addStyleDeclaration(ColorUtility::BORDER_COLOR, ColorUtility::getColorValue($colorValue)); 567 self::checkDefaultBorderColorAttributes($attributes); 568 } 569 570 571 } 572 573 /** 574 * Return the name of the requested script 575 */ 576 public 577 static function getRequestScript() 578 { 579 $scriptPath = null; 580 $testPropertyValue = self::getPropertyValue("SCRIPT_NAME"); 581 if (defined('DOKU_UNITTEST') && $testPropertyValue != null) { 582 return $testPropertyValue; 583 } 584 if (array_key_exists("DOCUMENT_URI", $_SERVER)) { 585 $scriptPath = $_SERVER["DOCUMENT_URI"]; 586 } 587 if ($scriptPath == null && array_key_exists("SCRIPT_NAME", $_SERVER)) { 588 $scriptPath = $_SERVER["SCRIPT_NAME"]; 589 } 590 if ($scriptPath == null) { 591 msg("Unable to find the main script", LogUtility::LVL_MSG_ERROR); 592 } 593 $path_parts = pathinfo($scriptPath); 594 return $path_parts['basename']; 595 } 596 597 /** 598 * 599 * @param $name 600 * @param $default 601 * @return string - the value of a query string property or if in test mode, the value of a test variable 602 * set with {@link self::setTestProperty} 603 * This is used to test script that are not supported by the dokuwiki test framework 604 * such as css.php 605 */ 606 public 607 static function getPropertyValue($name, $default = null) 608 { 609 global $INPUT; 610 $value = $INPUT->str($name); 611 if ($value == null && defined('DOKU_UNITTEST')) { 612 global $COMBO; 613 $value = $COMBO[$name]; 614 } 615 if ($value == null) { 616 return $default; 617 } else { 618 return $value; 619 } 620 621 } 622 623 /** 624 * Create an URL to the documentation website 625 * @param $canonical - canonical id or slug 626 * @param $text - the text of the link 627 * @param bool $withIcon - used to break the recursion with the message in the {@link Icon} 628 * @return string - an url 629 */ 630 public 631 static function getUrl($canonical, $text, $withIcon = true) 632 { 633 /** @noinspection SpellCheckingInspection */ 634 635 $xhtmlIcon = ""; 636 if ($withIcon) { 637 638 /** 639 * We don't include it as an external resource via url 640 * because it then make a http request for every logo 641 * in the configuration page and makes it really slow 642 */ 643 $path = File::createFromPath(Resources::getImagesDirectory() . "/logo.svg"); 644 $tagAttributes = TagAttributes::createEmpty(SvgImageLink::CANONICAL); 645 $tagAttributes->addComponentAttributeValue(TagAttributes::TYPE_KEY, SvgDocument::ICON_TYPE); 646 $tagAttributes->addComponentAttributeValue(Dimension::WIDTH_KEY, "20"); 647 $cache = new CacheMedia($path, $tagAttributes); 648 if (!$cache->isCacheUsable()) { 649 $xhtmlIcon = SvgDocument::createFromPath($path) 650 ->setShouldBeOptimized(true) 651 ->getXmlText($tagAttributes); 652 $cache->storeCache($xhtmlIcon); 653 } 654 $xhtmlIcon = file_get_contents($cache->getFile()->getFileSystemPath()); 655 656 657 } 658 return $xhtmlIcon . ' <a href="' . self::$URL_BASE . '/' . str_replace(":", "/", $canonical) . '" title="' . $text . '">' . $text . '</a>'; 659 } 660 661 /** 662 * An utility function to not search every time which array should be first 663 * @param array $inlineAttributes - the component inline attributes 664 * @param array $defaultAttributes - the default configuration attributes 665 * @return array - a merged array 666 */ 667 public 668 static function mergeAttributes(array $inlineAttributes, array $defaultAttributes = array()) 669 { 670 return array_merge($defaultAttributes, $inlineAttributes); 671 } 672 673 /** 674 * A pattern for a container tag 675 * that needs to catch the content 676 * 677 * Use as a special pattern (substition) 678 * 679 * The {@link \syntax_plugin_combo_math} use it 680 * @param $tag 681 * @return string - a pattern 682 */ 683 public 684 static function getLeafContainerTagPattern($tag) 685 { 686 return '<' . $tag . '.*?>.*?<\/' . $tag . '>'; 687 } 688 689 /** 690 * Return the content of a tag 691 * <math>Content</math> 692 * @param $match 693 * @return string the content 694 */ 695 public 696 static function getTagContent($match) 697 { 698 // From the first > 699 $start = strpos($match, ">"); 700 if ($start == false) { 701 LogUtility::msg("The match does not contain any opening tag. Match: {$match}", LogUtility::LVL_MSG_ERROR); 702 return ""; 703 } 704 $match = substr($match, $start + 1); 705 // If this is the last character, we get a false 706 if ($match == false) { 707 LogUtility::msg("The match does not contain any closing tag. Match: {$match}", LogUtility::LVL_MSG_ERROR); 708 return ""; 709 } 710 711 $end = strrpos($match, "</"); 712 if ($end == false) { 713 LogUtility::msg("The match does not contain any closing tag. Match: {$match}", LogUtility::LVL_MSG_ERROR); 714 return ""; 715 } 716 717 return substr($match, 0, $end); 718 } 719 720 /** 721 * 722 * Check if a HTML tag was already added for a request 723 * The request id is just the timestamp 724 * An indicator array should be provided 725 * @return string 726 */ 727 public 728 static function getRequestId() 729 { 730 731 if (isset($_SERVER['REQUEST_TIME_FLOAT'])) { 732 // since php 5.4 733 $requestTime = $_SERVER['REQUEST_TIME_FLOAT']; 734 } else { 735 // DokuWiki test framework use this 736 $requestTime = $_SERVER['REQUEST_TIME']; 737 } 738 $keyPrefix = 'combo_'; 739 740 global $ID; 741 return $keyPrefix . hash('crc32b', $_SERVER['REMOTE_ADDR'] . $_SERVER['REMOTE_PORT'] . $requestTime . $ID); 742 743 } 744 745 /** 746 * Get the page id 747 * If the page is a sidebar, it will not return the id of the sidebar 748 * but the one of the page 749 * @return string 750 */ 751 public 752 static function getPageId() 753 { 754 return FsWikiUtility::getMainPageId(); 755 } 756 757 /** 758 * Transform special HTML characters to entity 759 * Example: 760 * <hello>world</hello> 761 * to 762 * "<hello>world</hello>" 763 * 764 * @param $text 765 * @return string 766 */ 767 public 768 static function htmlEncode($text) 769 { 770 /** 771 * See https://stackoverflow.com/questions/46483/htmlentities-vs-htmlspecialchars/3614344 772 * {@link htmlentities } 773 */ 774 //return htmlspecialchars($text, ENT_QUOTES); 775 return htmlentities($text); 776 } 777 778 779 /** 780 * Add a class 781 * @param $classValue 782 * @param array $attributes 783 */ 784 public 785 static function addClass2Attributes($classValue, array &$attributes) 786 { 787 self::addAttributeValue("class", $classValue, $attributes); 788 } 789 790 /** 791 * Add a style property to the attributes 792 * @param $property 793 * @param $value 794 * @param array $attributes 795 * @deprecated use {@link TagAttributes::addStyleDeclaration()} instead 796 */ 797 public 798 static function addStyleProperty($property, $value, array &$attributes) 799 { 800 if (isset($attributes["style"])) { 801 $attributes["style"] .= ";$property:$value"; 802 } else { 803 $attributes["style"] = "$property:$value"; 804 } 805 806 } 807 808 /** 809 * Add default border attributes 810 * to see a border 811 * Doc 812 * https://combostrap.com/styling/color#border_color 813 * @param TagAttributes $tagAttributes 814 */ 815 private 816 static function checkDefaultBorderColorAttributes(&$tagAttributes) 817 { 818 /** 819 * border color was set without the width 820 * setting the width 821 */ 822 if (!( 823 $tagAttributes->hasStyleDeclaration("border") 824 || 825 $tagAttributes->hasStyleDeclaration("border-width") 826 ) 827 ) { 828 $tagAttributes->addStyleDeclaration("border-width", "1px"); 829 } 830 /** 831 * border color was set without the style 832 * setting the style 833 */ 834 if (! 835 ( 836 $tagAttributes->hasStyleDeclaration("border") 837 || 838 $tagAttributes->hasStyleDeclaration("border-style") 839 ) 840 ) { 841 $tagAttributes->addStyleDeclaration("border-style", "solid"); 842 843 } 844 if (!$tagAttributes->hasStyleDeclaration("border-radius")) { 845 $tagAttributes->addStyleDeclaration("border-radius", ".25rem"); 846 } 847 848 } 849 850 public 851 static function getConfValue($confName, $defaultValue = null) 852 { 853 global $conf; 854 if (isset($conf['plugin'][PluginUtility::PLUGIN_BASE_NAME][$confName])) { 855 return $conf['plugin'][PluginUtility::PLUGIN_BASE_NAME][$confName]; 856 } else { 857 return $defaultValue; 858 } 859 } 860 861 /** 862 * @param $match 863 * @return null|string - return the tag name or null if not found 864 */ 865 public 866 static function getTag($match) 867 { 868 869 // Trim to start clean 870 $match = trim($match); 871 872 // Until the first > 873 $pos = strpos($match, ">"); 874 if ($pos == false) { 875 LogUtility::msg("The match does not contain any tag. Match: {$match}", LogUtility::LVL_MSG_ERROR); 876 return null; 877 } 878 $match = substr($match, 0, $pos); 879 880 // Suppress the < 881 if ($match[0] == "<") { 882 $match = substr($match, 1); 883 } else { 884 LogUtility::msg("This is not a text tag because it does not start with the character `>`"); 885 } 886 887 // Suppress the tag name (ie until the first blank) 888 $spacePosition = strpos($match, " "); 889 if (!$spacePosition) { 890 // No space, meaning this is only the tag name 891 return $match; 892 } else { 893 return substr($match, 0, $spacePosition); 894 } 895 896 } 897 898 899 /** 900 * @param string $string add a command into HTML 901 */ 902 public 903 static function addAsHtmlComment($string) 904 { 905 print_r('<!-- ' . self::htmlEncode($string) . '-->'); 906 } 907 908 public 909 static function getResourceBaseUrl() 910 { 911 return DOKU_URL . 'lib/plugins/' . PluginUtility::PLUGIN_BASE_NAME . '/resources'; 912 } 913 914 /** 915 * @param $TAG - the name of the tag that should correspond to the name of the css file in the style directory 916 * @return string - a inline style element to inject in the page or blank if no file exists 917 */ 918 public 919 static function getTagStyle($TAG) 920 { 921 $script = self::getCssRules($TAG); 922 if (!empty($script)) { 923 return "<style>" . $script . "</style>"; 924 } else { 925 return ""; 926 } 927 928 } 929 930 931 public 932 static function getComponentName($tag) 933 { 934 return strtolower(PluginUtility::PLUGIN_BASE_NAME) . "_" . $tag; 935 } 936 937 public 938 static function addAttributeValue($attribute, $value, array &$attributes) 939 { 940 if (array_key_exists($attribute, $attributes) && $attributes[$attribute] !== "") { 941 $attributes[$attribute] .= " {$value}"; 942 } else { 943 $attributes[$attribute] = "{$value}"; 944 } 945 } 946 947 /** 948 * Plugin Utility is available to all plugin, 949 * this is a convenient way to the the snippet manager 950 * @return SnippetManager 951 */ 952 public 953 static function getSnippetManager() 954 { 955 return SnippetManager::get(); 956 } 957 958 public 959 static function initStaticManager() 960 { 961 CacheManager::init(); 962 SnippetManager::init(); 963 } 964 965 /** 966 * Function used in a render 967 * @param $data - the data from {@link PluginUtility::handleAndReturnUnmatchedData()} 968 * @return string 969 */ 970 public 971 static function renderUnmatched($data) 972 { 973 /** 974 * Attributes 975 */ 976 if (isset($data[PluginUtility::ATTRIBUTES])) { 977 $attributes = $data[PluginUtility::ATTRIBUTES]; 978 } else { 979 $attributes = []; 980 } 981 $tagAttributes = TagAttributes::createFromCallStackArray($attributes); 982 $display = $tagAttributes->getValue(TagAttributes::DISPLAY); 983 if ($display != "none") { 984 $payload = $data[self::PAYLOAD]; 985 $previousTagDisplayType = $data[self::CONTEXT]; 986 if ($previousTagDisplayType !== Call::INLINE_DISPLAY) { 987 $payload = ltrim($payload); 988 } 989 return PluginUtility::htmlEncode($payload); 990 } else { 991 return ""; 992 } 993 } 994 995 /** 996 * Function used in a handle function of a syntax plugin for 997 * unmatched context 998 * @param $tagName 999 * @param $match 1000 * @param \Doku_Handler $handler 1001 * @return array 1002 */ 1003 public 1004 static function handleAndReturnUnmatchedData($tagName, $match, \Doku_Handler $handler): array 1005 { 1006 $callStack = CallStack::createFromHandler($handler); 1007 $sibling = $callStack->previous(); 1008 $context = null; 1009 if (!empty($sibling)) { 1010 $context = $sibling->getDisplay(); 1011 } 1012 return array( 1013 PluginUtility::STATE => DOKU_LEXER_UNMATCHED, 1014 PluginUtility::PAYLOAD => $match, 1015 PluginUtility::CONTEXT => $context 1016 ); 1017 } 1018 1019 public 1020 static function setConf($key, $value, $namespace = 'plugin') 1021 { 1022 global $conf; 1023 if ($namespace != null) { 1024 $conf[$namespace][PluginUtility::PLUGIN_BASE_NAME][$key] = $value; 1025 } else { 1026 $conf[$key] = $value; 1027 } 1028 1029 } 1030 1031 /** 1032 * Utility methodPreprocess a start tag to be able to extract the name 1033 * and the attributes easily 1034 * 1035 * It will delete: 1036 * * the characters <> and the /> if present 1037 * * and trim 1038 * 1039 * It will remain the tagname and its attributes 1040 * @param $match 1041 * @return false|string|null 1042 */ 1043 private 1044 static function getPreprocessEnterTag($match) 1045 { 1046 // Until the first > 1047 $pos = strpos($match, ">"); 1048 if ($pos == false) { 1049 LogUtility::msg("The match does not contain any tag. Match: {$match}", LogUtility::LVL_MSG_WARNING); 1050 return null; 1051 } 1052 $match = substr($match, 0, $pos); 1053 1054 1055 // Trim to start clean 1056 $match = trim($match); 1057 1058 // Suppress the < 1059 if ($match[0] == "<") { 1060 $match = substr($match, 1); 1061 } 1062 1063 // Suppress the / for a leaf tag 1064 if ($match[strlen($match) - 1] == "/") { 1065 $match = substr($match, 0, strlen($match) - 1); 1066 } 1067 return $match; 1068 } 1069 1070 /** 1071 * Retrieve the tag name used in the text document 1072 * @param $match 1073 * @return false|string|null 1074 */ 1075 public 1076 static function getSyntaxTagNameFromMatch($match) 1077 { 1078 $preprocessMatch = PluginUtility::getPreprocessEnterTag($match); 1079 1080 // Tag name (ie until the first blank) 1081 $spacePosition = strpos($match, " "); 1082 if (!$spacePosition) { 1083 // No space, meaning this is only the tag name 1084 return $preprocessMatch; 1085 } else { 1086 return trim(substr(0, $spacePosition)); 1087 } 1088 1089 } 1090 1091 /** 1092 * @param \Doku_Renderer_xhtml $renderer 1093 * @param $position 1094 * @param $name 1095 */ 1096 public 1097 static function startSection($renderer, $position, $name) 1098 { 1099 1100 1101 if (empty($position)) { 1102 LogUtility::msg("The position for a start section should not be empty", LogUtility::LVL_MSG_ERROR, "support"); 1103 } 1104 if (empty($name)) { 1105 LogUtility::msg("The name for a start section should not be empty", LogUtility::LVL_MSG_ERROR, "support"); 1106 } 1107 1108 /** 1109 * New Dokuwiki Version 1110 * for DokuWiki Greebo and more recent versions 1111 */ 1112 if (defined('SEC_EDIT_PATTERN')) { 1113 $renderer->startSectionEdit($position, array('target' => self::EDIT_SECTION_TARGET, 'name' => $name)); 1114 } else { 1115 /** 1116 * Old version 1117 */ 1118 /** @noinspection PhpParamsInspection */ 1119 $renderer->startSectionEdit($position, self::EDIT_SECTION_TARGET, $name); 1120 } 1121 } 1122 1123 /** 1124 * Add an enter call to the stack 1125 * @param \Doku_Handler $handler 1126 * @param $tagName 1127 * @param array $callStackArray 1128 */ 1129 public 1130 static function addEnterCall( 1131 \Doku_Handler &$handler, 1132 $tagName, 1133 $callStackArray = array() 1134 ) 1135 { 1136 $pluginName = PluginUtility::getComponentName($tagName); 1137 $handler->addPluginCall( 1138 $pluginName, 1139 $callStackArray, 1140 DOKU_LEXER_ENTER, 1141 null, 1142 null 1143 ); 1144 } 1145 1146 /** 1147 * Add an end call dynamically 1148 * @param \Doku_Handler $handler 1149 * @param $tagName 1150 * @param array $callStackArray 1151 */ 1152 public 1153 static function addEndCall(\Doku_Handler $handler, $tagName, $callStackArray = array()) 1154 { 1155 $pluginName = PluginUtility::getComponentName($tagName); 1156 $handler->addPluginCall( 1157 $pluginName, 1158 $callStackArray, 1159 DOKU_LEXER_END, 1160 null, 1161 null 1162 ); 1163 } 1164 1165 /** 1166 * General Debug 1167 */ 1168 public 1169 static function isDebug() 1170 { 1171 global $conf; 1172 return $conf["allowdebug"] === 1; 1173 1174 } 1175 1176 /** 1177 * @return bool true if loaded, false otherwise 1178 * Strap is loaded only if this is the same version 1179 * to avoid function, class, or members that does not exist 1180 */ 1181 public 1182 static function loadStrapUtilityTemplateIfPresentAndSameVersion() 1183 { 1184 $templateUtilityFile = __DIR__ . '/../../../tpl/strap/class/TplUtility.php'; 1185 if (file_exists($templateUtilityFile)) { 1186 /** 1187 * Check the version 1188 */ 1189 $templateInfo = confToHash(__DIR__ . '/../../../tpl/strap/template.info.txt'); 1190 $templateVersion = $templateInfo['version']; 1191 $comboVersion = self::$INFO_PLUGIN['version']; 1192 if ($templateVersion != $comboVersion) { 1193 if ($comboVersion > $templateVersion) { 1194 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)."); 1195 } else { 1196 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)."); 1197 } 1198 return false; 1199 } else { 1200 /** @noinspection PhpIncludeInspection */ 1201 require_once($templateUtilityFile); 1202 return true; 1203 } 1204 } else { 1205 $level = LogUtility::LVL_MSG_DEBUG; 1206 if (defined('DOKU_UNITTEST')) { 1207 // fail 1208 $level = LogUtility::LVL_MSG_ERROR; 1209 } 1210 if (Site::getTemplate() != "strap") { 1211 LogUtility::msg("The strap template is not installed", $level); 1212 } else { 1213 LogUtility::msg("The file ($templateUtilityFile) was not found", $level); 1214 } 1215 return false; 1216 } 1217 } 1218 1219 1220 /** 1221 * 1222 * See also dev.md file 1223 */ 1224 public static function isDevOrTest() 1225 { 1226 if (self::isDev()) { 1227 return true; 1228 } 1229 return self::isTest(); 1230 } 1231 1232 public static function isDev() 1233 { 1234 global $_SERVER; 1235 if ($_SERVER["REMOTE_ADDR"] == "127.0.0.1") { 1236 return true; 1237 } 1238 return false; 1239 } 1240 1241 public static function getInstructions($markiCode) 1242 { 1243 return p_get_instructions($markiCode); 1244 } 1245 1246 public static function getInstructionsWithoutRoot($markiCode) 1247 { 1248 return RenderUtility::getInstructionsAndStripPEventually($markiCode); 1249 } 1250 1251 /** 1252 * Transform a text into a valid HTML id 1253 * @param $string 1254 * @return string 1255 */ 1256 public static function toHtmlId($string) 1257 { 1258 /** 1259 * sectionId calls cleanID 1260 * cleanID delete all things before a ':' 1261 * we do then the replace before to not 1262 * lost a minus '-' separator 1263 */ 1264 $string = str_replace(array(':', '.'), '', $string); 1265 return sectionID($string, $check); 1266 } 1267 1268 public static function isTest() 1269 { 1270 return defined('DOKU_UNITTEST'); 1271 } 1272 1273 1274 public static function getCacheManager() 1275 { 1276 return CacheManager::get(); 1277 } 1278 1279 public static function getModeFromPluginName($name) 1280 { 1281 return "plugin_$name"; 1282 } 1283 1284 public static function isCi(): bool 1285 { 1286 // https://docs.travis-ci.com/user/environment-variables/#default-environment-variables 1287 return getenv("CI") === "true"; 1288 } 1289 1290 /** 1291 * An helper function to not exit when it's a test environment 1292 * @param string $message 1293 */ 1294 public static function softExit($message = null) 1295 { 1296 1297 if (!PluginUtility::isTest()) { 1298 exit; 1299 } else { 1300 throw new ExitException($message); 1301 } 1302 1303 } 1304 1305 1306} 1307 1308PluginUtility::init(); 1309