addMode('listblock',new Doku_Parser_Mode_ListBlock()); */ public static function getModeFromTag($tag) { return "plugin_" . self::getComponentName($tag); } /** * @param $tag * @return string * * Create a lookahead pattern for a container tag used to enter in a mode */ public static function getContainerTagPattern($tag) { // this pattern ensure that the tag // `accordion` will not intercept also the tag `accordionitem` // where: // ?: means non capturing group (to not capture the last >) // (\s.*?): is a capturing group that starts with a space $pattern = "(?:\s.*?>|>)"; return '<' . $tag . $pattern . '(?=.*?<\/' . $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 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($match) { return self::getQualifiedTagAttributes($match, false, ""); } /** * 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 * @return array */ public static function getQualifiedTagAttributes($match, $hasThirdValue, $keyThirdArgument) { $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; } if (!strpos($nextArgument, "=")) { $attributes["type"] = $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 array $styleProperties - an array of CSS properties with key, value * @return string - the value for the style attribute (ie all rules where joined with the comma) */ public static function array2InlineStyle(array $styleProperties) { $inlineCss = ""; foreach ($styleProperties as $key => $value) { $inlineCss .= "$key:$value;"; } // Suppress the last ; if ($inlineCss[strlen($inlineCss) - 1] == ";") { $inlineCss = substr($inlineCss, 0, -1); } return $inlineCss; } /** * @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) { return '<' . $tag . '.*?/>'; } /** * 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) { return RenderUtility::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->addStyleDeclaration($key, $value); } } } /** * Border Color * For background color, see {@link TagAttributes::processBackground()} * For text color, see {@link TextColor} */ if ($attributes->hasComponentAttribute(ColorUtility::BORDER_COLOR)) { $colorValue = $attributes->getValueAndRemove(ColorUtility::BORDER_COLOR); $attributes->addStyleDeclaration(ColorUtility::BORDER_COLOR, ColorUtility::getColorValue($colorValue)); 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 */ public static function getPropertyValue($name, $default = null) { global $INPUT; $value = $INPUT->str($name); if ($value == null && defined('DOKU_UNITTEST')) { global $COMBO; $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 $text - the text of the link * @param bool $withIcon - used to break the recursion with the message in the {@link Icon} * @return string - an url */ public static function getUrl($canonical, $text, $withIcon = true) { /** @noinspection SpellCheckingInspection */ $xhtmlIcon = ""; if ($withIcon) { /** * We don't include it as an external resource via url * because it then make a http request for every logo * in the configuration page and makes it really slow */ $path = File::createFromPath(Resources::getImagesDirectory() . "/logo.svg"); $tagAttributes = TagAttributes::createEmpty(SvgImageLink::CANONICAL); $tagAttributes->addComponentAttributeValue(TagAttributes::TYPE_KEY, SvgDocument::ICON_TYPE); $tagAttributes->addComponentAttributeValue(Dimension::WIDTH_KEY, "20"); $cache = new CacheMedia($path, $tagAttributes); if (!$cache->isCacheUsable()) { $xhtmlIcon = SvgDocument::createFromPath($path) ->setShouldBeOptimized(true) ->getXmlText($tagAttributes); $cache->storeCache($xhtmlIcon); } $xhtmlIcon = file_get_contents($cache->getFile()->getFileSystemPath()); } return $xhtmlIcon . ' ' . $text . ''; } /** * 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, "world * to * "<hello>world</hello>" * * @param $text * @return string */ public static function htmlEncode($text) { /** * See https://stackoverflow.com/questions/46483/htmlentities-vs-htmlspecialchars/3614344 * {@link htmlentities } */ //return htmlspecialchars($text, ENT_QUOTES); return htmlentities($text); } /** * 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::addStyleDeclaration()} 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->addStyleDeclaration("border-width", "1px"); } /** * border color was set without the style * setting the style */ if (! ( $tagAttributes->hasStyleDeclaration("border") || $tagAttributes->hasStyleDeclaration("border-style") ) ) { $tagAttributes->addStyleDeclaration("border-style", "solid"); } if (!$tagAttributes->hasStyleDeclaration("border-radius")) { $tagAttributes->addStyleDeclaration("border-radius", ".25rem"); } } public static function getConfValue($confName, $defaultValue = null) { global $conf; if (isset($conf['plugin'][PluginUtility::PLUGIN_BASE_NAME][$confName])) { return $conf['plugin'][PluginUtility::PLUGIN_BASE_NAME][$confName]; } else { return $defaultValue; } } /** * @param $match * @return null|string - return the tag name or null if not found */ public static function getTag($match) { // Trim to start clean $match = trim($match); // Until the first > $pos = strpos($match, ">"); if ($pos == false) { LogUtility::msg("The match does not contain any tag. Match: {$match}", LogUtility::LVL_MSG_ERROR); return null; } $match = substr($match, 0, $pos); // Suppress the < 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); } } /** * @param string $string add a command into HTML */ public static function addAsHtmlComment($string) { print_r(''); } public static function getResourceBaseUrl() { return DOKU_URL . 'lib/plugins/' . PluginUtility::PLUGIN_BASE_NAME . '/resources'; } /** * @param $TAG - the name of the tag that should correspond to the name of the css file in the style directory * @return string - a inline style element to inject in the page or blank if no file exists */ public static function getTagStyle($TAG) { $script = self::getCssRules($TAG); if (!empty($script)) { return ""; } else { return ""; } } public static function getComponentName($tag) { 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 SnippetManager */ public static function getSnippetManager() { return SnippetManager::get(); } public static function initStaticManager() { CacheManager::init(); SnippetManager::init(); } /** * Function used in a render * @param $data - the data from {@link PluginUtility::handleAndReturnUnmatchedData()} * @return string */ public static function renderUnmatched($data) { /** * Attributes */ if (isset($data[PluginUtility::ATTRIBUTES])) { $attributes = $data[PluginUtility::ATTRIBUTES]; } else { $attributes = []; } $tagAttributes = TagAttributes::createFromCallStackArray($attributes); $display = $tagAttributes->getValue(TagAttributes::DISPLAY); if ($display != "none") { $payload = $data[self::PAYLOAD]; $previousTagDisplayType = $data[self::CONTEXT]; if ($previousTagDisplayType !== Call::INLINE_DISPLAY) { $payload = ltrim($payload); } return PluginUtility::htmlEncode($payload); } else { return ""; } } /** * 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 ); } public static function setConf($key, $value, $namespace = 'plugin') { global $conf; if ($namespace != null) { $conf[$namespace][PluginUtility::PLUGIN_BASE_NAME][$key] = $value; } else { $conf[$key] = $value; } } /** * 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 == false) { 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)); } } /** * @param \Doku_Renderer_xhtml $renderer * @param $position * @param $name */ public static function startSection($renderer, $position, $name) { if (empty($position)) { LogUtility::msg("The position for a start section should not be empty", LogUtility::LVL_MSG_ERROR, "support"); } if (empty($name)) { LogUtility::msg("The name for a start section should not be empty", LogUtility::LVL_MSG_ERROR, "support"); } /** * New Dokuwiki Version * for DokuWiki Greebo and more recent versions */ if (defined('SEC_EDIT_PATTERN')) { $renderer->startSectionEdit($position, array('target' => self::EDIT_SECTION_TARGET, 'name' => $name)); } else { /** * Old version */ /** @noinspection PhpParamsInspection */ $renderer->startSectionEdit($position, self::EDIT_SECTION_TARGET, $name); } } /** * 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_END, null, null ); } /** * General Debug */ public static function isDebug() { global $conf; return $conf["allowdebug"] === 1; } /** * @return bool true if loaded, false otherwise * Strap is loaded only if this is the same version * to avoid function, class, or members that does not exist */ public static function loadStrapUtilityTemplateIfPresentAndSameVersion() { $templateUtilityFile = __DIR__ . '/../../../tpl/strap/class/TplUtility.php'; if (file_exists($templateUtilityFile)) { /** * Check the version */ $templateInfo = confToHash(__DIR__ . '/../../../tpl/strap/template.info.txt'); $templateVersion = $templateInfo['version']; $comboVersion = self::$INFO_PLUGIN['version']; if ($templateVersion != $comboVersion) { if ($comboVersion > $templateVersion) { LogUtility::msg("You should upgrade strap to the latest version to get a fully functional experience. The version of Combo is ($comboVersion) while the version of Strap is ($templateVersion)."); } else { LogUtility::msg("You should upgrade combo to the latest version to get a fully functional experience. The version of Combo is ($comboVersion) while the version of Strap is ($templateVersion)."); } return false; } else { /** @noinspection PhpIncludeInspection */ require_once($templateUtilityFile); return true; } } else { $level = LogUtility::LVL_MSG_DEBUG; if (defined('DOKU_UNITTEST')) { // fail $level = LogUtility::LVL_MSG_ERROR; } if (Site::getTemplate() != "strap") { LogUtility::msg("The strap template is not installed", $level); } else { LogUtility::msg("The file ($templateUtilityFile) was not found", $level); } return false; } } /** * * See also dev.md file */ public static function isDevOrTest() { if (self::isDev()) { return true; } return self::isTest(); } public static function isDev() { global $_SERVER; if ($_SERVER["REMOTE_ADDR"] == "127.0.0.1") { return true; } return false; } public static function getInstructions($markiCode) { return p_get_instructions($markiCode); } public static function getInstructionsWithoutRoot($markiCode) { return RenderUtility::getInstructionsAndStripPEventually($markiCode); } /** * Transform a text into a valid HTML id * @param $string * @return string */ public static function toHtmlId($string) { /** * sectionId calls cleanID * cleanID delete all things before a ':' * we do then the replace before to not * lost a minus '-' separator */ $string = str_replace(array(':', '.'), '', $string); return sectionID($string, $check); } public static function isTest() { return defined('DOKU_UNITTEST'); } public static function getCacheManager() { return CacheManager::get(); } public static function getModeFromPluginName($name) { return "plugin_$name"; } public static function isCi(): bool { // https://docs.travis-ci.com/user/environment-variables/#default-environment-variables return getenv("CI") === "true"; } /** * An helper function to not exit when it's a test environment * @param string $message */ public static function softExit($message = null) { if (!PluginUtility::isTest()) { exit; } else { throw new ExitException($message); } } } PluginUtility::init();