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