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