toAbsoluteId()); self::$PLUGIN_NAME = 'ComboStrap'; global $lang; self::$PLUGIN_LANG = $lang[self::PLUGIN_BASE_NAME] ?? null; self::$URL_APEX = "https://" . parse_url(self::$INFO_PLUGIN['url'], PHP_URL_HOST); //self::$VERSION = self::$INFO_PLUGIN['version']; } /** * @param $inputExpression * @return false|int 1|0 * returns: * - 1 if the input expression is a pattern, * - 0 if not, * - FALSE if an error occurred. */ static function isRegularExpression($inputExpression) { $regularExpressionPattern = "/(\\/.*\\/[gmixXsuUAJ]?)/"; return preg_match($regularExpressionPattern, $inputExpression); } /** * Return a mode from a tag (ie from a {@link Plugin::getPluginComponent()} * @param $tag * @return string * * A mode is just a name for a class * Example: $Parser->addMode('listblock',new Doku_Parser_Mode_ListBlock()); */ public static function getModeFromTag($tag) { return "plugin_" . self::getComponentName($tag); } /** * This pattern allows space after the tag name * for an end tag * As XHTML (https://www.w3.org/TR/REC-xml/#dt-etag) * @param $tag * @return string */ public static function getEndTagPattern($tag) { return ""; } /** * @param $tag * @return string * * Create a open tag pattern without lookahead. * Used for * @link https://dev.w3.org/html5/html-author/#void-elements-0 */ public static function getVoidElementTagPattern($tag) { return ' < ' . $tag . ' .*?>'; } /** * Take an array where the key is the attribute name * and return a HTML tag string * * The attribute name and value are escaped * * @param $attributes - combo attributes * @return string * @deprecated to allowed background and other metadata, use {@link TagAttributes::toHtmlEnterTag()} */ public static function array2HTMLAttributesAsString($attributes) { $tagAttributes = TagAttributes::createFromCallStackArray($attributes); return $tagAttributes->toHTMLAttributeString(); } /** * * Parse the attributes part of a match * * Example: * line-numbers="value" * line-numbers='value' * * This value may be in: * * configuration value * * as well as in the match of a {@link SyntaxPlugin} * * @param $string * @return array * * To parse a match, use {@link PluginUtility::getTagAttributes()} * * */ public static function parseAttributes($string) { $parameters = array(); // Rules // * name may be alone (ie true boolean attribute) // * a name may get a `-` // * there may be space every everywhere when the value is enclosed with a quote // * there may be no space in the value and between the equal sign when the value is not enclosed // // /i not case sensitive $attributePattern = '\s*([-\w]+)\s*(?:=(\s*[\'"]([^`"]*)[\'"]\s*|[^\s]*))?'; $result = preg_match_all('/' . $attributePattern . '/i', $string, $matches); if ($result != 0) { foreach ($matches[1] as $key => $parameterKey) { // group 3 (ie the value between quotes) $value = $matches[3][$key]; if ($value == "") { // check the value without quotes $value = $matches[2][$key]; } // if there is no value, this is a boolean if ($value == "") { $value = true; } else { $value = hsc($value); } $parameters[hsc(strtolower($parameterKey))] = $value; } } return $parameters; } public static function getTagAttributes(string $match, array $knownTypes = [], bool $allowFirstBooleanAttributesAsType = false): array { return self::getQualifiedTagAttributes($match, false, "", $knownTypes, $allowFirstBooleanAttributesAsType); } /** * Return the attribute of a tag * Because they are users input, they are all escaped * @param $match * @param $hasThirdValue - if true, the third parameter is treated as value, not a property and returned in the `third` key * use for the code/file/console where they accept a name as third value * @param $keyThirdArgument - if a third argument is found, return it with this key * @param array|null $knownTypes * @param bool $allowFirstBooleanAttributesAsType * @return array */ public static function getQualifiedTagAttributes($match, $hasThirdValue, $keyThirdArgument, array $knownTypes = [], bool $allowFirstBooleanAttributesAsType = false): array { $match = PluginUtility::getPreprocessEnterTag($match); // Suppress the tag name (ie until the first blank) $spacePosition = strpos($match, " "); if (!$spacePosition) { // No space, meaning this is only the tag name return array(); } $match = trim(substr($match, $spacePosition)); if ($match == "") { return array(); } /** * Do we have a type as first argument ? */ $attributes = array(); $spacePosition = strpos($match, " "); if ($spacePosition) { $nextArgument = substr($match, 0, $spacePosition); } else { $nextArgument = $match; } $isBooleanAttribute = !strpos($nextArgument, "="); $isType = false; if ($isBooleanAttribute) { $possibleTypeLowercase = strtolower($nextArgument); if ($allowFirstBooleanAttributesAsType) { $isType = true; $nextArgument = $possibleTypeLowercase; } else { if (!empty($knownTypes) && in_array($possibleTypeLowercase, $knownTypes)) { $isType = true; $nextArgument = $possibleTypeLowercase; } } } if ($isType) { $attributes[TagAttributes::TYPE_KEY] = $nextArgument; /** * Suppress the type */ $match = substr($match, strlen($nextArgument)); $match = trim($match); /** * Do we have a value as first argument ? */ if (!empty($hasThirdValue)) { $spacePosition = strpos($match, " "); if ($spacePosition) { $nextArgument = substr($match, 0, $spacePosition); } else { $nextArgument = $match; } if (!strpos($nextArgument, "=") && !empty($nextArgument)) { $attributes[$keyThirdArgument] = $nextArgument; /** * Suppress the third argument */ $match = substr($match, strlen($nextArgument)); $match = trim($match); } } } /** * Parse the remaining attributes */ $parsedAttributes = self::parseAttributes($match); /** * Merge */ $attributes = array_merge($attributes, $parsedAttributes);; return $attributes; } /** * @param $tag * @return string * Create a pattern used where the tag is not a container. * ie *
* * * This is generally used with a subtition plugin * and a {@link Lexer::addSpecialPattern} state * where the tag is just replaced */ public static function getEmptyTagPattern($tag): string { /** * A tag should start with the tag * `(?=[/ ]{1})` - a space or the / (lookahead) => to allow allow tag name with minus character * `(?![^/]>)` - it's not a normal tag (ie a > with the previous character that is not /) * `[^>]*` then until the > is found (dokuwiki capture greedy, don't use the point character) * then until the close `/>` character */ return '<' . $tag . '(?=[/ ]{1})(?![^/]>)[^>]*\/>'; } public static function getEmptyTagPatternGeneral(): string { return self::getEmptyTagPattern("[\w-]+"); } /** * Just call this function from a class like that * getTageName(get_called_class()) * to get the tag name (ie the component plugin) * of a syntax plugin * * @param $get_called_class * @return string */ public static function getTagName($get_called_class) { list(/* $t */, /* $p */, /* $n */, $c) = explode('_', $get_called_class, 4); return (isset($c) ? $c : ''); } /** * Just call this function from a class like that * getAdminPageName(get_called_class()) * to get the page name of a admin plugin * * @param $get_called_class * @return string - the admin page name */ public static function getAdminPageName($get_called_class) { $names = explode('_', $get_called_class); $names = array_slice($names, -2); return implode('_', $names); } public static function getNameSpace() { // No : at the begin of the namespace please return self::PLUGIN_BASE_NAME . ':'; } /** * @param $get_called_class - the plugin class * @return array */ public static function getTags($get_called_class) { $elements = array(); $elementName = PluginUtility::getTagName($get_called_class); $elements[] = $elementName; $elements[] = strtoupper($elementName); return $elements; } /** * Render a text * @param $pageContent * @return string|null */ public static function render($pageContent): ?string { return MarkupRenderUtility::renderText2XhtmlAndStripPEventually($pageContent, false); } /** * This method will takes attributes * and process the plugin styling attribute such as width and height * to put them in a style HTML attribute * @param TagAttributes $attributes */ public static function processStyle(&$attributes) { // Style $styleAttributeName = "style"; if ($attributes->hasComponentAttribute($styleAttributeName)) { $properties = explode(";", $attributes->getValueAndRemove($styleAttributeName)); foreach ($properties as $property) { list($key, $value) = explode(":", $property); if ($key != "") { $attributes->addStyleDeclarationIfNotSet($key, $value); } } } /** * Border Color * For background color, see {@link TagAttributes::processBackground()} * For text color, see {@link TextColor} */ if ($attributes->hasComponentAttribute(ColorRgb::BORDER_COLOR)) { $colorValue = $attributes->getValueAndRemove(ColorRgb::BORDER_COLOR); $attributes->addStyleDeclarationIfNotSet(ColorRgb::BORDER_COLOR, ColorRgb::createFromString($colorValue)->toCssValue()); self::checkDefaultBorderColorAttributes($attributes); } } /** * Return the name of the requested script */ public static function getRequestScript() { $scriptPath = null; $testPropertyValue = self::getPropertyValue("SCRIPT_NAME"); if (defined('DOKU_UNITTEST') && $testPropertyValue != null) { return $testPropertyValue; } if (array_key_exists("DOCUMENT_URI", $_SERVER)) { $scriptPath = $_SERVER["DOCUMENT_URI"]; } if ($scriptPath == null && array_key_exists("SCRIPT_NAME", $_SERVER)) { $scriptPath = $_SERVER["SCRIPT_NAME"]; } if ($scriptPath == null) { msg("Unable to find the main script", LogUtility::LVL_MSG_ERROR); } $path_parts = pathinfo($scriptPath); return $path_parts['basename']; } /** * * @param $name * @param $default * @return string - the value of a query string property or if in test mode, the value of a test variable * set with {@link self::setTestProperty} * This is used to test script that are not supported by the dokuwiki test framework * such as css.php * @deprecated use {@link ApiRouter::getRequestParameter()} */ public static function getPropertyValue($name, $default = null) { global $INPUT; $value = $INPUT->str($name); if ($value == null && defined('DOKU_UNITTEST')) { global $COMBO; if ($COMBO !== null) { $value = $COMBO[$name]; } } if ($value == null) { return $default; } else { return $value; } } /** * Create an URL to the documentation website * @param $canonical - canonical id or slug * @param $label - the text of the link * @param bool $withIcon - used to break the recursion with the message in the {@link IconDownloader} * @return string - an url */ public static function getDocumentationHyperLink($canonical, $label, bool $withIcon = true, $tooltip = ""): string { $xhtmlIcon = ""; if ($withIcon) { $logoPath = WikiPath::createComboResource("images:logo.svg"); try { $fetchImage = FetcherSvg::createSvgFromPath($logoPath); $fetchImage->setRequestedType(FetcherSvg::ICON_TYPE) ->setRequestedWidth(20); $xhtmlIcon = SvgImageLink::createFromFetcher($fetchImage) ->renderMediaTag(); } catch (ExceptionCompile $e) { /** * We don't throw because this function * is also used by: * * the log functionality to show link to the documentation creating a loop * * inside the configuration description crashing the page */ if (PluginUtility::isDevOrTest()) { // shows errors in the html only on dev/test $xhtmlIcon = "Error: {$e->getMessage()}"; } } } $urlApex = self::$URL_APEX; $path = str_replace(":", "/", $canonical); if (empty($tooltip)) { $title = $label; } else { $title = $tooltip; } $htmlToolTip = ""; if (!empty($tooltip)) { $dataAttributeNamespace = Bootstrap::getDataNamespace(); $htmlToolTip = "data{$dataAttributeNamespace}-toggle=\"tooltip\""; } return "$xhtmlIcon$label"; } /** * An utility function to not search every time which array should be first * @param array $inlineAttributes - the component inline attributes * @param array $defaultAttributes - the default configuration attributes * @return array - a merged array */ public static function mergeAttributes(array $inlineAttributes, array $defaultAttributes = array()) { return array_merge($defaultAttributes, $inlineAttributes); } /** * A pattern for a container tag * that needs to catch the content * * Use as a special pattern (substition) * * The {@link \syntax_plugin_combo_math} use it * @param $tag * @return string - a pattern */ public static function getLeafContainerTagPattern($tag) { return '<' . $tag . '.*?>.*?<\/' . $tag . '>'; } /** * Return the content of a tag * * Content * @param $match * @return string the content */ public static function getTagContent($match) { // From the first > $start = strpos($match, ">"); if ($start == false) { LogUtility::msg("The match does not contain any opening tag. Match: {$match}", LogUtility::LVL_MSG_ERROR); return ""; } $match = substr($match, $start + 1); // If this is the last character, we get a false if ($match == false) { LogUtility::msg("The match does not contain any closing tag. Match: {$match}", LogUtility::LVL_MSG_ERROR); return ""; } $end = strrpos($match, "getRequestedPath()->getWikiId(); } public static function xmlEncode($text) { /** * {@link htmlentities } */ return htmlentities($text, ENT_XML1); } /** * Add a class * @param $classValue * @param array $attributes */ public static function addClass2Attributes($classValue, array &$attributes) { self::addAttributeValue("class", $classValue, $attributes); } /** * Add a style property to the attributes * @param $property * @param $value * @param array $attributes * @deprecated use {@link TagAttributes::addStyleDeclarationIfNotSet()} instead */ public static function addStyleProperty($property, $value, array &$attributes) { if (isset($attributes["style"])) { $attributes["style"] .= ";$property:$value"; } else { $attributes["style"] = "$property:$value"; } } /** * Add default border attributes * to see a border * Doc * https://combostrap.com/styling/color#border_color * @param TagAttributes $tagAttributes */ private static function checkDefaultBorderColorAttributes(&$tagAttributes) { /** * border color was set without the width * setting the width */ if (!( $tagAttributes->hasStyleDeclaration("border") || $tagAttributes->hasStyleDeclaration("border-width") ) ) { $tagAttributes->addStyleDeclarationIfNotSet("border-width", "1px"); } /** * border color was set without the style * setting the style */ if (! ( $tagAttributes->hasStyleDeclaration("border") || $tagAttributes->hasStyleDeclaration("border-style") ) ) { $tagAttributes->addStyleDeclarationIfNotSet("border-style", "solid"); } if (!$tagAttributes->hasStyleDeclaration("border-radius")) { $tagAttributes->addStyleDeclarationIfNotSet("border-radius", ".25rem"); } } /** * @param $match * @return null|string - return the tag name or null if not found */ public static function getMarkupTag($match): ?string { // Until the first > $pos = strpos($match, ">"); if (!$pos) { LogUtility::msg("The match does not contain any tag. Match: {$match}", LogUtility::LVL_MSG_ERROR); return null; } $match = substr($match, 0, $pos); // if this is a empty tag with / at the end we delete it if ($match[strlen($match) - 1] == "/") { $match = substr($match, 0, -1); } // Suppress the < if ($match[0] == "<") { $match = substr($match, 1); // closing tag if ($match[0] == "/") { $match = substr($match, 1); } } else { LogUtility::msg("This is not a text tag because it does not start with the character `>`"); } // Suppress the tag name (ie until the first blank) $spacePosition = strpos($match, " "); if (!$spacePosition) { // No space, meaning this is only the tag name return $match; } else { return substr($match, 0, $spacePosition); } } public static function getComponentName($tag): string { return strtolower(PluginUtility::PLUGIN_BASE_NAME) . "_" . $tag; } public static function addAttributeValue($attribute, $value, array &$attributes) { if (array_key_exists($attribute, $attributes) && $attributes[$attribute] !== "") { $attributes[$attribute] .= " {$value}"; } else { $attributes[$attribute] = "{$value}"; } } /** * Plugin Utility is available to all plugin, * this is a convenient way to the the snippet manager * @return SnippetSystem */ public static function getSnippetManager(): SnippetSystem { return SnippetSystem::getFromContext(); } /** * Function used in a render * @param $data - the data from {@link PluginUtility::handleAndReturnUnmatchedData()} * @return string * * */ public static function renderUnmatched($data): string { /** * Attributes */ $attributes = $data[PluginUtility::ATTRIBUTES] ?? []; $tagAttributes = TagAttributes::createFromCallStackArray($attributes); /** * Display */ $display = $tagAttributes->getValueAndRemoveIfPresent(Display::DISPLAY); if ($display === "none") { return ""; } $payload = $data[self::PAYLOAD] ?? null; $previousTagDisplayType = $data[self::CONTEXT] ?? null; if ($previousTagDisplayType !== Call::INLINE_DISPLAY) { // Delete the eol at the beginning and end // otherwise we get a big block $payload = ltrim($payload); } return Html::encode($payload); } public static function renderUnmatchedXml($data) { $payload = $data[self::PAYLOAD]; $previousTagDisplayType = $data[self::CONTEXT]; if ($previousTagDisplayType !== Call::INLINE_DISPLAY) { $payload = ltrim($payload); } return PluginUtility::xmlEncode($payload); } /** * Function used in a handle function of a syntax plugin for * unmatched context * @param $tagName * @param $match * @param \Doku_Handler $handler * @return array */ public static function handleAndReturnUnmatchedData($tagName, $match, \Doku_Handler $handler): array { $callStack = CallStack::createFromHandler($handler); $sibling = $callStack->previous(); $context = null; if (!empty($sibling)) { $context = $sibling->getDisplay(); } return array( PluginUtility::STATE => DOKU_LEXER_UNMATCHED, PluginUtility::PAYLOAD => $match, PluginUtility::CONTEXT => $context ); } /** * Utility methodPreprocess a start tag to be able to extract the name * and the attributes easily * * It will delete: * * the characters <> and the /> if present * * and trim * * It will remain the tagname and its attributes * @param $match * @return false|string|null */ private static function getPreprocessEnterTag($match) { // Until the first > $pos = strpos($match, ">"); if (!$pos) { LogUtility::msg("The match does not contain any tag. Match: {$match}", LogUtility::LVL_MSG_WARNING); return null; } $match = substr($match, 0, $pos); // Trim to start clean $match = trim($match); // Suppress the < if ($match[0] == "<") { $match = substr($match, 1); } // Suppress the / for a leaf tag if ($match[strlen($match) - 1] == "/") { $match = substr($match, 0, strlen($match) - 1); } return $match; } /** * Retrieve the tag name used in the text document * @param $match * @return false|string|null */ public static function getSyntaxTagNameFromMatch($match) { $preprocessMatch = PluginUtility::getPreprocessEnterTag($match); // Tag name (ie until the first blank) $spacePosition = strpos($match, " "); if (!$spacePosition) { // No space, meaning this is only the tag name return $preprocessMatch; } else { return trim(substr(0, $spacePosition)); } } /** * Add an enter call to the stack * @param \Doku_Handler $handler * @param $tagName * @param array $callStackArray */ public static function addEnterCall( \Doku_Handler &$handler, $tagName, $callStackArray = array() ) { $pluginName = PluginUtility::getComponentName($tagName); $handler->addPluginCall( $pluginName, $callStackArray, DOKU_LEXER_ENTER, null, null ); } /** * Add an end call dynamically * @param \Doku_Handler $handler * @param $tagName * @param array $callStackArray */ public static function addEndCall(\Doku_Handler $handler, $tagName, $callStackArray = array()) { $pluginName = PluginUtility::getComponentName($tagName); $handler->addPluginCall( $pluginName, $callStackArray, DOKU_LEXER_EXIT, null, null ); } /** * General Debug */ public static function isDebug() { global $conf; return $conf["allowdebug"] === 1; } /** * * See also dev.md file */ public static function isDevOrTest() { if (self::isDev()) { return true; } return self::isTest(); } /** * Is this a dev environment (ie laptop where the dev is working) * @return bool */ public static function isDev(): bool { global $_SERVER; $remoteAddr = $_SERVER["REMOTE_ADDR"] ?? null; if ($remoteAddr == "127.0.0.1") { return true; } $computerName = $_SERVER["COMPUTERNAME"] ?? null; if ($computerName === "NICO") { return true; } return false; } public static function getInstructions($markiCode) { return p_get_instructions($markiCode); } public static function getInstructionsWithoutRoot($markiCode) { return MarkupRenderUtility::getInstructionsAndStripPEventually($markiCode); } public static function isTest(): bool { return defined('DOKU_UNITTEST'); } public static function getCacheManager(): CacheManager { return CacheManager::getFromContextExecution(); } public static function getModeFromPluginName($name) { return "plugin_$name"; } public static function isCi(): bool { // https://docs.travis-ci.com/user/environment-variables/#default-environment-variables // https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables return getenv("CI") === "true"; } /** * @throws ExceptionCompile */ public static function renderInstructionsToXhtml($callStackHeaderInstructions): ?string { return MarkupRenderUtility::renderInstructionsToXhtml($callStackHeaderInstructions); } /** * @deprecated for {@link ExecutionContext::getExecutingWikiId()} */ public static function getCurrentSlotId(): string { return ExecutionContext::getActualOrCreateFromEnv()->getExecutingWikiId(); } } PluginUtility::init();