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