1c78eb039Ssaggi-dw<?php 2*545c554bSsaggi-dw 3c78eb039Ssaggi-dw/** 4c78eb039Ssaggi-dw * DokuWiki Plugin dwtimeline (Syntax Component) 5c78eb039Ssaggi-dw * 6c78eb039Ssaggi-dw * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 7c78eb039Ssaggi-dw * @author saggi <saggi@gmx.de> 8c78eb039Ssaggi-dw */ 9c78eb039Ssaggi-dw 10*545c554bSsaggi-dwuse dokuwiki\Extension\SyntaxPlugin; 11*545c554bSsaggi-dwuse dokuwiki\File\PageResolver; 12*545c554bSsaggi-dw 13*545c554bSsaggi-dwclass syntax_plugin_dwtimeline_dwtimeline extends SyntaxPlugin 14c78eb039Ssaggi-dw{ 15c78eb039Ssaggi-dw /** 16c78eb039Ssaggi-dw * Global direction memory 17c78eb039Ssaggi-dw * @var 18c78eb039Ssaggi-dw */ 19c78eb039Ssaggi-dw protected static $direction; 20c78eb039Ssaggi-dw protected static $align; 21c78eb039Ssaggi-dw 22c78eb039Ssaggi-dw /** @inheritDoc */ 23c78eb039Ssaggi-dw public function getType() 24c78eb039Ssaggi-dw { 25c78eb039Ssaggi-dw return 'substition'; 26c78eb039Ssaggi-dw } 27c78eb039Ssaggi-dw 28c78eb039Ssaggi-dw /** @inheritDoc */ 29c78eb039Ssaggi-dw public function getPType() 30c78eb039Ssaggi-dw { 31c78eb039Ssaggi-dw return 'stack'; 32c78eb039Ssaggi-dw } 33c78eb039Ssaggi-dw 34c78eb039Ssaggi-dw /** @inheritDoc */ 35c78eb039Ssaggi-dw public function getSort() 36c78eb039Ssaggi-dw { 37c78eb039Ssaggi-dw return 400; 38c78eb039Ssaggi-dw } 39c78eb039Ssaggi-dw 40c78eb039Ssaggi-dw /** 41c78eb039Ssaggi-dw * Change the current content of $direction String (left,right) 42c78eb039Ssaggi-dw * @param string $direction 43c78eb039Ssaggi-dw * @return string 44c78eb039Ssaggi-dw */ 45*545c554bSsaggi-dw public function changeDirection(string $direction): string 46*545c554bSsaggi-dw { 47c78eb039Ssaggi-dw if ($direction === 'tl-right') { 48c78eb039Ssaggi-dw $direction = 'tl-left'; 49*545c554bSsaggi-dw } else { 50c78eb039Ssaggi-dw $direction = 'tl-right'; 51c78eb039Ssaggi-dw } 52c78eb039Ssaggi-dw return $direction; 53c78eb039Ssaggi-dw } 54c78eb039Ssaggi-dw 55*545c554bSsaggi-dw public function getDirection() 56c78eb039Ssaggi-dw { 57*545c554bSsaggi-dw if (!self::$direction) { 58*545c554bSsaggi-dw self::$direction = 'tl-' . $this->getConf('direction'); 59*545c554bSsaggi-dw } 60c78eb039Ssaggi-dw return self::$direction; 61c78eb039Ssaggi-dw } 62c78eb039Ssaggi-dw 63c78eb039Ssaggi-dw /** 64c78eb039Ssaggi-dw * Handle the match 65c78eb039Ssaggi-dw * @param string $match The match of the syntax 66c78eb039Ssaggi-dw * @param int $state The state of the handler 67c78eb039Ssaggi-dw * @param int $pos The position in the document 68c78eb039Ssaggi-dw * @param Doku_Handler $handler The handler 69c78eb039Ssaggi-dw * @return array Data for the renderer 70c78eb039Ssaggi-dw */ 71c78eb039Ssaggi-dw public function handle($match, $state, $pos, Doku_Handler $handler) 72c78eb039Ssaggi-dw { 73*545c554bSsaggi-dw return []; 74c78eb039Ssaggi-dw } 75c78eb039Ssaggi-dw 76c78eb039Ssaggi-dw /** 77c78eb039Ssaggi-dw * Create output 78c78eb039Ssaggi-dw * 79c78eb039Ssaggi-dw * @param string $mode string output format being rendered 80c78eb039Ssaggi-dw * @param Doku_Renderer $renderer the current renderer object 81c78eb039Ssaggi-dw * @param array $data data created by handler() 82*545c554bSsaggi-dw * @return bool rendered correctly? 83c78eb039Ssaggi-dw */ 84c78eb039Ssaggi-dw public function render($mode, Doku_Renderer $renderer, $data) 85c78eb039Ssaggi-dw { 86c78eb039Ssaggi-dw return false; 87c78eb039Ssaggi-dw } 88c78eb039Ssaggi-dw 89c78eb039Ssaggi-dw /** 90*545c554bSsaggi-dw * Match entity options like: <dwtimeline opt1="value1" opt2='value2'> 91*545c554bSsaggi-dw * Returns normalized data array used by the renderer. 92c78eb039Ssaggi-dw */ 93c78eb039Ssaggi-dw public function getTitleMatches(string $match): array 94c78eb039Ssaggi-dw { 95*545c554bSsaggi-dw // defaults 96*545c554bSsaggi-dw $data = [ 97*545c554bSsaggi-dw 'align' => self::$align, // standard alignment 98*545c554bSsaggi-dw 'data' => '', 99*545c554bSsaggi-dw 'style' => ' style="', 100*545c554bSsaggi-dw ]; 101*545c554bSsaggi-dw 102*545c554bSsaggi-dw $opts = $this->parseOptions($match); 103*545c554bSsaggi-dw 104*545c554bSsaggi-dw foreach ($opts as $option => $rawValue) { 105*545c554bSsaggi-dw switch ($option) { 106c78eb039Ssaggi-dw case 'link': 107*545c554bSsaggi-dw $data['link'] = $this->getLink($rawValue); 108c78eb039Ssaggi-dw break; 109*545c554bSsaggi-dw 110c78eb039Ssaggi-dw case 'data': 111*545c554bSsaggi-dw $datapoint = substr($rawValue, 0, 4); 112*545c554bSsaggi-dw $data['data'] = ' data-point="' . hsc($datapoint) . '" '; 113c78eb039Ssaggi-dw if (strlen($datapoint) > 2) { 114*545c554bSsaggi-dw $data['style'] .= '--4sizewidth: 50px; --4sizeright: -29px; --4sizesmallleft40: 60px; '; 115*545c554bSsaggi-dw $data['style'] .= '--4sizesmallleft50: 70px; --4sizesmallleft4: -10px; '; 116*545c554bSsaggi-dw $data['style'] .= '--4sizewidthhorz: 50px; --4sizerighthorz: -29px; '; 117c78eb039Ssaggi-dw } 118c78eb039Ssaggi-dw break; 119*545c554bSsaggi-dw 120c78eb039Ssaggi-dw case 'align': 121*545c554bSsaggi-dw $data['align'] = $this->checkValues($rawValue, ['horz', 'vert'], self::$align); 122c78eb039Ssaggi-dw break; 123*545c554bSsaggi-dw 124c78eb039Ssaggi-dw case 'backcolor': 125*545c554bSsaggi-dw if ($c = $this->isValidColor($rawValue)) { 126*545c554bSsaggi-dw $data['style'] .= 'background-color:' . $c . '; '; 127*545c554bSsaggi-dw } 128c78eb039Ssaggi-dw break; 129*545c554bSsaggi-dw 130c78eb039Ssaggi-dw case 'style': 131c78eb039Ssaggi-dw // do not accept custom styles at the moment 132c78eb039Ssaggi-dw break; 133*545c554bSsaggi-dw 134c78eb039Ssaggi-dw default: 135*545c554bSsaggi-dw // generic attributes (e.g., title) 136*545c554bSsaggi-dw $data[$option] = hsc($rawValue); // HTML-escape for output later 137c78eb039Ssaggi-dw break; 138c78eb039Ssaggi-dw } 139c78eb039Ssaggi-dw } 140*545c554bSsaggi-dw 141*545c554bSsaggi-dw // close style if something was added 142*545c554bSsaggi-dw $data['style'] = ($data['style'] === ' style="') ? '' : $data['style'] . '"'; 143*545c554bSsaggi-dw 144c78eb039Ssaggi-dw return $data; 145c78eb039Ssaggi-dw } 146c78eb039Ssaggi-dw 147c78eb039Ssaggi-dw /** 148*545c554bSsaggi-dw * Parse HTML-like attributes from a string. 149*545c554bSsaggi-dw * Supports: key="val", key='val', key=val (unquoted), with \" and \\ in "..." 150*545c554bSsaggi-dw * Note: PREG_UNMATCHED_AS_NULL requires PHP 7.2+. 151c78eb039Ssaggi-dw */ 152*545c554bSsaggi-dw private function parseOptions(string $s): array 153c78eb039Ssaggi-dw { 154*545c554bSsaggi-dw $out = []; 155*545c554bSsaggi-dw $i = 0; 156*545c554bSsaggi-dw $len = strlen($s); 157*545c554bSsaggi-dw 158*545c554bSsaggi-dw $pattern = '/\G\s*(?P<name>[a-zA-Z][\w-]*)\s*' 159*545c554bSsaggi-dw . '(?:=\s*(?:"(?P<dq>(?:[^"\\\\]|\\\\.)*)"' 160*545c554bSsaggi-dw . '|\'(?P<sq>(?:[^\'\\\\]|\\\\.)*)\'' 161*545c554bSsaggi-dw . '|\[\[(?P<br>.+?)\]\]' 162*545c554bSsaggi-dw . '|(?P<uq>[^\s"\'=<>`]+)))?' 163*545c554bSsaggi-dw . '/A'; 164*545c554bSsaggi-dw 165*545c554bSsaggi-dw while ($i < $len) { 166*545c554bSsaggi-dw if (!preg_match($pattern, $s, $m, PREG_UNMATCHED_AS_NULL, $i)) { 167*545c554bSsaggi-dw break; 168c78eb039Ssaggi-dw } 169*545c554bSsaggi-dw $i += strlen($m[0]); 170*545c554bSsaggi-dw 171*545c554bSsaggi-dw $name = strtolower($m['name']); 172*545c554bSsaggi-dw $raw = $m['dq'] ?? $m['sq'] ?? ($m['br'] !== null ? '[[' . $m['br'] . ']]' : null) ?? $m['uq'] ?? ''; 173*545c554bSsaggi-dw if ($m['dq'] !== null || $m['sq'] !== null) { 174*545c554bSsaggi-dw $raw = stripcslashes($raw); // \" und \\ in quoted Werten ent-escapen 175*545c554bSsaggi-dw } 176*545c554bSsaggi-dw $out[$name] = $raw; 177*545c554bSsaggi-dw } 178*545c554bSsaggi-dw return $out; 179*545c554bSsaggi-dw } 180*545c554bSsaggi-dw 181*545c554bSsaggi-dw /** 182*545c554bSsaggi-dw * Return the first link target found in the given wiki text. 183*545c554bSsaggi-dw * Supports internal links [[id|label]], external links (bare or bracketed), 184*545c554bSsaggi-dw * interwiki, mailto and Windows share. Returns a normalized target: 185*545c554bSsaggi-dw * - internal: absolute page id, incl. optional "#section" 186*545c554bSsaggi-dw * - external: absolute URL (http/https/ftp) 187*545c554bSsaggi-dw * - email: mailto:<addr> 188*545c554bSsaggi-dw * - share: \\server\share\path 189*545c554bSsaggi-dw * Returns '' if none found. 190*545c554bSsaggi-dw */ 191*545c554bSsaggi-dw public function getLink(string $wikitext): string 192*545c554bSsaggi-dw { 193*545c554bSsaggi-dw $ins = p_get_instructions($wikitext); 194*545c554bSsaggi-dw if (!$ins) { 195*545c554bSsaggi-dw return ''; 196*545c554bSsaggi-dw } 197*545c554bSsaggi-dw 198*545c554bSsaggi-dw global $ID; 199*545c554bSsaggi-dw $resolver = new PageResolver($ID); 200*545c554bSsaggi-dw 201*545c554bSsaggi-dw foreach ($ins as $node) { 202*545c554bSsaggi-dw $type = $node[0]; 203*545c554bSsaggi-dw // INTERNAL WIKI LINK [[ns:page#section|label]] 204*545c554bSsaggi-dw if ($type === 'internallink') { 205*545c554bSsaggi-dw $raw = $node[1][0] ?? ''; 206*545c554bSsaggi-dw if ($raw === '') { 207*545c554bSsaggi-dw continue; 208*545c554bSsaggi-dw } 209*545c554bSsaggi-dw 210*545c554bSsaggi-dw $anchor = ''; 211*545c554bSsaggi-dw if (strpos($raw, '#') !== false) { 212*545c554bSsaggi-dw [$rawId, $sec] = explode('#', $raw, 2); 213*545c554bSsaggi-dw $raw = trim($rawId); 214*545c554bSsaggi-dw $anchor = '#' . trim($sec); 215*545c554bSsaggi-dw } else { 216*545c554bSsaggi-dw $raw = trim($raw); 217*545c554bSsaggi-dw } 218*545c554bSsaggi-dw 219*545c554bSsaggi-dw $abs = $resolver->resolveId(cleanID($raw)); 220*545c554bSsaggi-dw return $abs . $anchor; 221*545c554bSsaggi-dw } 222*545c554bSsaggi-dw 223*545c554bSsaggi-dw // EXTERNAL LINK (bare URL or [[http(s)/ftp://...|label]]) 224*545c554bSsaggi-dw if ($type === 'externallink') { 225*545c554bSsaggi-dw // payload can be scalar or array depending on DW version 226*545c554bSsaggi-dw $url = is_array($node[1]) ? (string)($node[1][0] ?? '') : (string)$node[1]; 227*545c554bSsaggi-dw return trim($url); 228*545c554bSsaggi-dw } 229*545c554bSsaggi-dw 230*545c554bSsaggi-dw // INTERWIKI [[wp>Foo]] etc. – return the canonical "prefix>page" 231*545c554bSsaggi-dw if ($type === 'interwikilink') { 232*545c554bSsaggi-dw $raw = $node[1][0] ?? ''; 233*545c554bSsaggi-dw if ($raw === '') { 234*545c554bSsaggi-dw continue; 235*545c554bSsaggi-dw } 236*545c554bSsaggi-dw return $raw; 237*545c554bSsaggi-dw } 238*545c554bSsaggi-dw 239*545c554bSsaggi-dw // EMAIL 240*545c554bSsaggi-dw if ($type === 'emaillink') { 241*545c554bSsaggi-dw $addr = is_array($node[1]) ? (string)($node[1][0] ?? '') : (string)$node[1]; 242*545c554bSsaggi-dw return 'mailto:' . trim($addr); 243*545c554bSsaggi-dw } 244*545c554bSsaggi-dw 245*545c554bSsaggi-dw // WINDOWS SHARE 246*545c554bSsaggi-dw if ($type === 'windowssharelink') { 247*545c554bSsaggi-dw $path = is_array($node[1]) ? (string)($node[1][0] ?? '') : (string)$node[1]; 248*545c554bSsaggi-dw return trim($path); 249*545c554bSsaggi-dw } 250*545c554bSsaggi-dw } 251*545c554bSsaggi-dw 252*545c554bSsaggi-dw // Fallback: detect bare URL or email if no instruction was emitted 253*545c554bSsaggi-dw if (preg_match('/\b(?:https?|ftp):\/\/\S+/i', $wikitext, $m)) { 254*545c554bSsaggi-dw return rtrim($m[0], '.,);'); 255*545c554bSsaggi-dw } 256*545c554bSsaggi-dw if (preg_match('/^[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}$/', trim($wikitext), $m)) { 257*545c554bSsaggi-dw return 'mailto:' . $m[0]; 258*545c554bSsaggi-dw } 259*545c554bSsaggi-dw 260c78eb039Ssaggi-dw return ''; 261c78eb039Ssaggi-dw } 262c78eb039Ssaggi-dw 263c78eb039Ssaggi-dw public function checkValues($toCheck, $allowed, $standard) 264c78eb039Ssaggi-dw { 265c78eb039Ssaggi-dw if (in_array($toCheck, $allowed, true)) { 266c78eb039Ssaggi-dw return $toCheck; 267c78eb039Ssaggi-dw } else { 268c78eb039Ssaggi-dw return $standard; 269c78eb039Ssaggi-dw } 270c78eb039Ssaggi-dw } 271c78eb039Ssaggi-dw 272c78eb039Ssaggi-dw /** 273c78eb039Ssaggi-dw * Validate color value $color 274c78eb039Ssaggi-dw * this is cut price validation - only to ensure the basic format is correct and there is nothing harmful 275c78eb039Ssaggi-dw * three basic formats "colorname", "#fff[fff]", "rgb(255[%],255[%],255[%])" 276c78eb039Ssaggi-dw */ 277*545c554bSsaggi-dw public function isValidColor($color) 278c78eb039Ssaggi-dw { 279c78eb039Ssaggi-dw $color = trim($color); 280*545c554bSsaggi-dw $colornames = [ 281*545c554bSsaggi-dw 'AliceBlue', 282*545c554bSsaggi-dw 'AntiqueWhite', 283*545c554bSsaggi-dw 'Aqua', 284*545c554bSsaggi-dw 'Aquamarine', 285*545c554bSsaggi-dw 'Azure', 286*545c554bSsaggi-dw 'Beige', 287*545c554bSsaggi-dw 'Bisque', 288*545c554bSsaggi-dw 'Black', 289*545c554bSsaggi-dw 'BlanchedAlmond', 290*545c554bSsaggi-dw 'Blue', 291*545c554bSsaggi-dw 'BlueViolet', 292*545c554bSsaggi-dw 'Brown', 293*545c554bSsaggi-dw 'BurlyWood', 294*545c554bSsaggi-dw 'CadetBlue', 295*545c554bSsaggi-dw 'Chartreuse', 296*545c554bSsaggi-dw 'Chocolate', 297*545c554bSsaggi-dw 'Coral', 298*545c554bSsaggi-dw 'CornflowerBlue', 299*545c554bSsaggi-dw 'Cornsilk', 300*545c554bSsaggi-dw 'Crimson', 301*545c554bSsaggi-dw 'Cyan', 302*545c554bSsaggi-dw 'DarkBlue', 303*545c554bSsaggi-dw 'DarkCyan', 304*545c554bSsaggi-dw 'DarkGoldenRod', 305*545c554bSsaggi-dw 'DarkGray', 306*545c554bSsaggi-dw 'DarkGrey', 307*545c554bSsaggi-dw 'DarkGreen', 308*545c554bSsaggi-dw 'DarkKhaki', 309*545c554bSsaggi-dw 'DarkMagenta', 310*545c554bSsaggi-dw 'DarkOliveGreen', 311*545c554bSsaggi-dw 'DarkOrange', 312*545c554bSsaggi-dw 'DarkOrchid', 313*545c554bSsaggi-dw 'DarkRed', 314*545c554bSsaggi-dw 'DarkSalmon', 315*545c554bSsaggi-dw 'DarkSeaGreen', 316*545c554bSsaggi-dw 'DarkSlateBlue', 317*545c554bSsaggi-dw 'DarkSlateGray', 318*545c554bSsaggi-dw 'DarkSlateGrey', 319*545c554bSsaggi-dw 'DarkTurquoise', 320*545c554bSsaggi-dw 'DarkViolet', 321*545c554bSsaggi-dw 'DeepPink', 322*545c554bSsaggi-dw 'DeepSkyBlue', 323*545c554bSsaggi-dw 'DimGray', 324*545c554bSsaggi-dw 'DimGrey', 325*545c554bSsaggi-dw 'DodgerBlue', 326*545c554bSsaggi-dw 'FireBrick', 327*545c554bSsaggi-dw 'FloralWhite', 328*545c554bSsaggi-dw 'ForestGreen', 329*545c554bSsaggi-dw 'Fuchsia', 330*545c554bSsaggi-dw 'Gainsboro', 331*545c554bSsaggi-dw 'GhostWhite', 332*545c554bSsaggi-dw 'Gold', 333*545c554bSsaggi-dw 'GoldenRod', 334*545c554bSsaggi-dw 'Gray', 335*545c554bSsaggi-dw 'Grey', 336*545c554bSsaggi-dw 'Green', 337*545c554bSsaggi-dw 'GreenYellow', 338*545c554bSsaggi-dw 'HoneyDew', 339*545c554bSsaggi-dw 'HotPink', 340*545c554bSsaggi-dw 'IndianRed', 341*545c554bSsaggi-dw 'Indigo', 342*545c554bSsaggi-dw 'Ivory', 343*545c554bSsaggi-dw 'Khaki', 344*545c554bSsaggi-dw 'Lavender', 345*545c554bSsaggi-dw 'LavenderBlush', 346*545c554bSsaggi-dw 'LawnGreen', 347*545c554bSsaggi-dw 'LemonChiffon', 348*545c554bSsaggi-dw 'LightBlue', 349*545c554bSsaggi-dw 'LightCoral', 350*545c554bSsaggi-dw 'LightCyan', 351*545c554bSsaggi-dw 'LightGoldenRodYellow', 352*545c554bSsaggi-dw 'LightGray', 353*545c554bSsaggi-dw 'LightGrey', 354*545c554bSsaggi-dw 'LightGreen', 355*545c554bSsaggi-dw 'LightPink', 356*545c554bSsaggi-dw 'LightSalmon', 357*545c554bSsaggi-dw 'LightSeaGreen', 358*545c554bSsaggi-dw 'LightSkyBlue', 359*545c554bSsaggi-dw 'LightSlateGray', 360*545c554bSsaggi-dw 'LightSlateGrey', 361*545c554bSsaggi-dw 'LightSteelBlue', 362*545c554bSsaggi-dw 'LightYellow', 363*545c554bSsaggi-dw 'Lime', 364*545c554bSsaggi-dw 'LimeGreen', 365*545c554bSsaggi-dw 'Linen', 366*545c554bSsaggi-dw 'Magenta', 367*545c554bSsaggi-dw 'Maroon', 368*545c554bSsaggi-dw 'MediumAquaMarine', 369*545c554bSsaggi-dw 'MediumBlue', 370*545c554bSsaggi-dw 'MediumOrchid', 371*545c554bSsaggi-dw 'MediumPurple', 372*545c554bSsaggi-dw 'MediumSeaGreen', 373*545c554bSsaggi-dw 'MediumSlateBlue', 374*545c554bSsaggi-dw 'MediumSpringGreen', 375*545c554bSsaggi-dw 'MediumTurquoise', 376*545c554bSsaggi-dw 'MediumVioletRed', 377*545c554bSsaggi-dw 'MidnightBlue', 378*545c554bSsaggi-dw 'MintCream', 379*545c554bSsaggi-dw 'MistyRose', 380*545c554bSsaggi-dw 'Moccasin', 381*545c554bSsaggi-dw 'NavajoWhite', 382*545c554bSsaggi-dw 'Navy', 383*545c554bSsaggi-dw 'OldLace', 384*545c554bSsaggi-dw 'Olive', 385*545c554bSsaggi-dw 'OliveDrab', 386*545c554bSsaggi-dw 'Orange', 387*545c554bSsaggi-dw 'OrangeRed', 388*545c554bSsaggi-dw 'Orchid', 389*545c554bSsaggi-dw 'PaleGoldenRod', 390*545c554bSsaggi-dw 'PaleGreen', 391*545c554bSsaggi-dw 'PaleTurquoise', 392*545c554bSsaggi-dw 'PaleVioletRed', 393*545c554bSsaggi-dw 'PapayaWhip', 394*545c554bSsaggi-dw 'PeachPuff', 395*545c554bSsaggi-dw 'Peru', 396*545c554bSsaggi-dw 'Pink', 397*545c554bSsaggi-dw 'Plum', 398*545c554bSsaggi-dw 'PowderBlue', 399*545c554bSsaggi-dw 'Purple', 400*545c554bSsaggi-dw 'RebeccaPurple', 401*545c554bSsaggi-dw 'Red', 402*545c554bSsaggi-dw 'RosyBrown', 403*545c554bSsaggi-dw 'RoyalBlue', 404*545c554bSsaggi-dw 'SaddleBrown', 405*545c554bSsaggi-dw 'Salmon', 406*545c554bSsaggi-dw 'SandyBrown', 407*545c554bSsaggi-dw 'SeaGreen', 408*545c554bSsaggi-dw 'SeaShell', 409*545c554bSsaggi-dw 'Sienna', 410*545c554bSsaggi-dw 'Silver', 411*545c554bSsaggi-dw 'SkyBlue', 412*545c554bSsaggi-dw 'SlateBlue', 413*545c554bSsaggi-dw 'SlateGray', 414*545c554bSsaggi-dw 'SlateGrey', 415*545c554bSsaggi-dw 'Snow', 416*545c554bSsaggi-dw 'SpringGreen', 417*545c554bSsaggi-dw 'SteelBlue', 418*545c554bSsaggi-dw 'Tan', 419*545c554bSsaggi-dw 'Teal', 420*545c554bSsaggi-dw 'Thistle', 421*545c554bSsaggi-dw 'Tomato', 422*545c554bSsaggi-dw 'Turquoise', 423*545c554bSsaggi-dw 'Violet', 424*545c554bSsaggi-dw 'Wheat', 425*545c554bSsaggi-dw 'White', 426*545c554bSsaggi-dw 'WhiteSmoke', 427*545c554bSsaggi-dw 'Yellow', 428*545c554bSsaggi-dw 'YellowGreen' 429*545c554bSsaggi-dw ]; 430c78eb039Ssaggi-dw 431*545c554bSsaggi-dw if (in_array(strtolower($color), array_map('strtolower', $colornames))) { 432*545c554bSsaggi-dw return $color; 433c78eb039Ssaggi-dw } 434c78eb039Ssaggi-dw 435*545c554bSsaggi-dw $pattern = '/^\s*( 436c78eb039Ssaggi-dw (\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}))| #colorvalue 437c78eb039Ssaggi-dw (rgb\(([0-9]{1,3}%?,){2}[0-9]{1,3}%?\)) #rgb triplet 438*545c554bSsaggi-dw )\s*$/x'; 439c78eb039Ssaggi-dw 440c78eb039Ssaggi-dw if (preg_match($pattern, $color)) { 441c78eb039Ssaggi-dw return trim($color); 442c78eb039Ssaggi-dw } 443c78eb039Ssaggi-dw 444c78eb039Ssaggi-dw return false; 445c78eb039Ssaggi-dw } 446c78eb039Ssaggi-dw 447*545c554bSsaggi-dw /** 448*545c554bSsaggi-dw * Localized error helper with ARIA for screen readers. 449*545c554bSsaggi-dw */ 450*545c554bSsaggi-dw public function err(string $langKey, array $sprintfArgs = []): string 451*545c554bSsaggi-dw { 452*545c554bSsaggi-dw $txt = $this->getLang($langKey) ?? $langKey; 453*545c554bSsaggi-dw if ($sprintfArgs) { 454*545c554bSsaggi-dw $sprintfArgs = array_map('hsc', $sprintfArgs); 455*545c554bSsaggi-dw $txt = vsprintf($txt, $sprintfArgs); 456*545c554bSsaggi-dw } else { 457*545c554bSsaggi-dw $txt = hsc($txt); 458c78eb039Ssaggi-dw } 459c78eb039Ssaggi-dw 460*545c554bSsaggi-dw return '<div class="plugin_dwtimeline_error" role="status" aria-live="polite">' 461*545c554bSsaggi-dw . $txt 462*545c554bSsaggi-dw . '</div>'; 463*545c554bSsaggi-dw } 464*545c554bSsaggi-dw 465*545c554bSsaggi-dw /** 466*545c554bSsaggi-dw * Return a human-friendly page title for $id. 467*545c554bSsaggi-dw * 1) metadata title 468*545c554bSsaggi-dw * 2) first heading (if available) 469*545c554bSsaggi-dw * 3) pretty formatted ID with namespaces (e.g. "Ns › Sub › Page") 470*545c554bSsaggi-dw */ 471*545c554bSsaggi-dw public function prettyId(string $id): string 472*545c554bSsaggi-dw { 473*545c554bSsaggi-dw // 1) meta title, if exist 474*545c554bSsaggi-dw $metaTitle = p_get_metadata($id, 'title'); 475*545c554bSsaggi-dw if (is_string($metaTitle) && $metaTitle !== '') { 476*545c554bSsaggi-dw return $metaTitle; 477*545c554bSsaggi-dw } 478*545c554bSsaggi-dw 479*545c554bSsaggi-dw // 2) First header 480*545c554bSsaggi-dw if (function_exists('p_get_first_heading')) { 481*545c554bSsaggi-dw $h = p_get_first_heading($id); 482*545c554bSsaggi-dw if (is_string($h) && $h !== '') { 483*545c554bSsaggi-dw return $h; 484*545c554bSsaggi-dw } 485*545c554bSsaggi-dw } 486*545c554bSsaggi-dw 487*545c554bSsaggi-dw // 3) fallback: path to page 488*545c554bSsaggi-dw $parts = explode(':', $id); 489*545c554bSsaggi-dw foreach ($parts as &$p) { 490*545c554bSsaggi-dw $p = str_replace('_', ' ', $p); 491*545c554bSsaggi-dw $p = mb_convert_case($p, MB_CASE_TITLE, 'UTF-8'); 492*545c554bSsaggi-dw } 493*545c554bSsaggi-dw return implode(' › ', $parts); 494*545c554bSsaggi-dw } 495*545c554bSsaggi-dw 496*545c554bSsaggi-dw /** 497*545c554bSsaggi-dw * Quote a value for wiki-style plugin attributes. 498*545c554bSsaggi-dw * Prefers "..." if possible, then '...'. If both quote types occur, 499*545c554bSsaggi-dw * wrap with " and escape inner \" and \\ (the parser will unescape them). 500*545c554bSsaggi-dw */ 501*545c554bSsaggi-dw public function quoteAttrForWiki(string $val): string 502*545c554bSsaggi-dw { 503*545c554bSsaggi-dw if (strpos($val, '"') === false) { 504*545c554bSsaggi-dw return '"' . $val . '"'; 505*545c554bSsaggi-dw } 506*545c554bSsaggi-dw if (strpos($val, "'") === false) { 507*545c554bSsaggi-dw return "'" . $val . "'"; 508*545c554bSsaggi-dw } 509*545c554bSsaggi-dw 510*545c554bSsaggi-dw // contains both ' and " -> escape for double-quoted 511*545c554bSsaggi-dw $escaped = str_replace(['\\', '"'], ['\\\\', '\\"'], $val); 512*545c554bSsaggi-dw return '"' . $escaped . '"'; 513*545c554bSsaggi-dw } 514*545c554bSsaggi-dw 515*545c554bSsaggi-dw /** 516*545c554bSsaggi-dw * Return the index (byte offset) directly after the end of the line containing $pos. 517*545c554bSsaggi-dw */ 518*545c554bSsaggi-dw public function lineEndAt(string $text, int $pos, int $len): int 519*545c554bSsaggi-dw { 520*545c554bSsaggi-dw if ($pos < 0) { 521*545c554bSsaggi-dw return 0; 522*545c554bSsaggi-dw } 523*545c554bSsaggi-dw $nl = strpos($text, "\n", $pos); 524*545c554bSsaggi-dw return ($nl === false) ? $len : ($nl + 1); 525*545c554bSsaggi-dw } 526*545c554bSsaggi-dw 527*545c554bSsaggi-dw /** 528*545c554bSsaggi-dw * Return the start index (byte offset) of the line containing $pos. 529*545c554bSsaggi-dw */ 530*545c554bSsaggi-dw public function lineStartAt(string $text, int $pos): int 531*545c554bSsaggi-dw { 532*545c554bSsaggi-dw if ($pos <= 0) { 533*545c554bSsaggi-dw return 0; 534*545c554bSsaggi-dw } 535*545c554bSsaggi-dw $before = substr($text, 0, $pos); 536*545c554bSsaggi-dw $nl = strrpos($before, "\n"); 537*545c554bSsaggi-dw return ($nl === false) ? 0 : ($nl + 1); 538*545c554bSsaggi-dw } 539*545c554bSsaggi-dw 540*545c554bSsaggi-dw /** 541*545c554bSsaggi-dw * Cut a section [start, end) from $text and rtrim it on the right side. 542*545c554bSsaggi-dw */ 543*545c554bSsaggi-dw public function cutSection(string $text, int $start, int $end): string 544*545c554bSsaggi-dw { 545*545c554bSsaggi-dw if ($start < 0) { 546*545c554bSsaggi-dw $start = 0; 547*545c554bSsaggi-dw } 548*545c554bSsaggi-dw if ($end < $start) { 549*545c554bSsaggi-dw $end = $start; 550*545c554bSsaggi-dw } 551*545c554bSsaggi-dw return rtrim(substr($text, $start, $end - $start)); 552*545c554bSsaggi-dw } 553*545c554bSsaggi-dw} 554