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