15511bd5bSAndreas Gohr<?php 25511bd5bSAndreas Gohr 3ba766201SAndreas Gohrnamespace dokuwiki\plugin\struct\meta; 45511bd5bSAndreas Gohr 5*7234bfb1Ssplitbrainuse dokuwiki\Extension\Event; 6*7234bfb1Ssplitbrain 75511bd5bSAndreas Gohr/** 85511bd5bSAndreas Gohr * Class ConfigParser 95511bd5bSAndreas Gohr * 105511bd5bSAndreas Gohr * Utilities to parse the configuration syntax into an array 115511bd5bSAndreas Gohr * 12ba766201SAndreas Gohr * @package dokuwiki\plugin\struct\meta 135511bd5bSAndreas Gohr */ 14d6d97f60SAnna Dabrowskaclass ConfigParser 15d6d97f60SAnna Dabrowska{ 16*7234bfb1Ssplitbrain protected $config = ['limit' => 0, 'dynfilters' => false, 'summarize' => false, 'rownumbers' => false, 'sepbyheaders' => false, 'target' => '', 'align' => [], 'headers' => [], 'cols' => [], 'widths' => [], 'filter' => [], 'schemas' => [], 'sort' => [], 'csv' => true, 'nesting' => 0, 'index' => 0, 'classes' => []]; 175511bd5bSAndreas Gohr 185511bd5bSAndreas Gohr /** 195511bd5bSAndreas Gohr * Parser constructor. 205511bd5bSAndreas Gohr * 215511bd5bSAndreas Gohr * parses the given configuration lines 225511bd5bSAndreas Gohr * 235511bd5bSAndreas Gohr * @param $lines 245511bd5bSAndreas Gohr */ 25d6d97f60SAnna Dabrowska public function __construct($lines) 26d6d97f60SAnna Dabrowska { 2729877279SMichael Große /** @var \helper_plugin_struct_config $helper */ 2829877279SMichael Große $helper = plugin_load('helper', 'struct_config'); 295511bd5bSAndreas Gohr // parse info 305511bd5bSAndreas Gohr foreach ($lines as $line) { 31*7234bfb1Ssplitbrain [$key, $val] = $this->splitLine($line); 325511bd5bSAndreas Gohr if (!$key) continue; 335511bd5bSAndreas Gohr 345511bd5bSAndreas Gohr $logic = 'OR'; 355511bd5bSAndreas Gohr // handle line commands (we allow various aliases here) 365511bd5bSAndreas Gohr switch ($key) { 375511bd5bSAndreas Gohr case 'from': 385511bd5bSAndreas Gohr case 'schema': 395511bd5bSAndreas Gohr case 'tables': 405511bd5bSAndreas Gohr $this->config['schemas'] = array_merge($this->config['schemas'], $this->parseSchema($val)); 415511bd5bSAndreas Gohr break; 425511bd5bSAndreas Gohr case 'select': 435511bd5bSAndreas Gohr case 'cols': 445511bd5bSAndreas Gohr case 'field': 455511bd5bSAndreas Gohr case 'col': 465511bd5bSAndreas Gohr $this->config['cols'] = $this->parseValues($val); 475511bd5bSAndreas Gohr break; 48ea5ad12aSMichael Grosse case 'sepbyheaders': 49ea5ad12aSMichael Grosse $this->config['sepbyheaders'] = (bool)$val; 50ea5ad12aSMichael Grosse break; 515511bd5bSAndreas Gohr case 'head': 525511bd5bSAndreas Gohr case 'header': 535511bd5bSAndreas Gohr case 'headers': 545511bd5bSAndreas Gohr $this->config['headers'] = $this->parseValues($val); 555511bd5bSAndreas Gohr break; 565511bd5bSAndreas Gohr case 'align': 575511bd5bSAndreas Gohr $this->config['align'] = $this->parseAlignments($val); 585511bd5bSAndreas Gohr break; 59e0216289SAndreas Gohr case 'width': 605511bd5bSAndreas Gohr case 'widths': 61e0216289SAndreas Gohr $this->config['widths'] = $this->parseWidths($val); 625511bd5bSAndreas Gohr break; 635511bd5bSAndreas Gohr case 'min': 645511bd5bSAndreas Gohr $this->config['min'] = abs((int)$val); 655511bd5bSAndreas Gohr break; 665511bd5bSAndreas Gohr case 'limit': 675511bd5bSAndreas Gohr case 'max': 685511bd5bSAndreas Gohr $this->config['limit'] = abs((int)$val); 695511bd5bSAndreas Gohr break; 705511bd5bSAndreas Gohr case 'order': 715511bd5bSAndreas Gohr case 'sort': 726047d2b8SAndreas Gohr $sorts = $this->parseValues($val); 73*7234bfb1Ssplitbrain $sorts = array_map([$helper, 'parseSort'], $sorts); 746047d2b8SAndreas Gohr $this->config['sort'] = array_merge($this->config['sort'], $sorts); 755511bd5bSAndreas Gohr break; 765511bd5bSAndreas Gohr case 'where': 775511bd5bSAndreas Gohr case 'filter': 78748e747fSAnna Dabrowska case 'filterand': // phpcs:ignore PSR2.ControlStructures.SwitchDeclaration.TerminatingComment 795511bd5bSAndreas Gohr /** @noinspection PhpMissingBreakStatementInspection */ 80748e747fSAnna Dabrowska case 'and': // phpcs:ignore PSR2.ControlStructures.SwitchDeclaration.TerminatingComment 815511bd5bSAndreas Gohr $logic = 'AND'; 825511bd5bSAndreas Gohr case 'filteror': 835511bd5bSAndreas Gohr case 'or': 8429877279SMichael Große $flt = $helper->parseFilterLine($logic, $val); 855511bd5bSAndreas Gohr if ($flt) { 865511bd5bSAndreas Gohr $this->config['filter'][] = $flt; 875511bd5bSAndreas Gohr } 885511bd5bSAndreas Gohr break; 895511bd5bSAndreas Gohr case 'dynfilters': 905511bd5bSAndreas Gohr $this->config['dynfilters'] = (bool)$val; 915511bd5bSAndreas Gohr break; 925511bd5bSAndreas Gohr case 'rownumbers': 935511bd5bSAndreas Gohr $this->config['rownumbers'] = (bool)$val; 945511bd5bSAndreas Gohr break; 955511bd5bSAndreas Gohr case 'summarize': 965511bd5bSAndreas Gohr $this->config['summarize'] = (bool)$val; 975511bd5bSAndreas Gohr break; 987b240ca8SAndreas Gohr case 'csv': 997b240ca8SAndreas Gohr $this->config['csv'] = (bool)$val; 1007b240ca8SAndreas Gohr break; 10185edf4f2SMichael Grosse case 'target': 10285edf4f2SMichael Grosse case 'page': 10385edf4f2SMichael Grosse $this->config['target'] = cleanID($val); 10485edf4f2SMichael Grosse break; 1055bc00e11SAndreas Gohr case 'nesting': 1065bc00e11SAndreas Gohr case 'nest': 1075bc00e11SAndreas Gohr $this->config['nesting'] = (int) $val; 1085bc00e11SAndreas Gohr break; 109ce44c639SAndreas Gohr case 'index': 110ce44c639SAndreas Gohr $this->config['index'] = (int) $val; 111ce44c639SAndreas Gohr break; 112af0ce8d2SAndreas Gohr case 'class': 113af0ce8d2SAndreas Gohr case 'classes': 114af0ce8d2SAndreas Gohr $this->config['classes'] = $this->parseClasses($val); 115af0ce8d2SAndreas Gohr break; 1165511bd5bSAndreas Gohr default: 117*7234bfb1Ssplitbrain $data = ['config' => &$this->config, 'key' => $key, 'val' => $val]; 118*7234bfb1Ssplitbrain $ev = new Event('PLUGIN_STRUCT_CONFIGPARSER_UNKNOWNKEY', $data); 11929a2b794SAndreas Gohr if ($ev->advise_before()) { 1207d88e7edSAndreas Gohr throw new StructException("unknown option '%s'", hsc($key)); 1215511bd5bSAndreas Gohr } 12229a2b794SAndreas Gohr $ev->advise_after(); 12329a2b794SAndreas Gohr } 1245511bd5bSAndreas Gohr } 12501dd90deSAndreas Gohr 12601dd90deSAndreas Gohr // fill up headers - a NULL signifies that the column label is wanted 1275511bd5bSAndreas Gohr $this->config['headers'] = (array)$this->config['headers']; 1285511bd5bSAndreas Gohr $cnth = count($this->config['headers']); 1295511bd5bSAndreas Gohr $cntf = count($this->config['cols']); 1305511bd5bSAndreas Gohr for ($i = $cnth; $i < $cntf; $i++) { 13101dd90deSAndreas Gohr $this->config['headers'][] = null; 1325511bd5bSAndreas Gohr } 13395eef580SAndreas Gohr // fill up alignments 13495eef580SAndreas Gohr $cnta = count($this->config['align']); 13595eef580SAndreas Gohr for ($i = $cnta; $i < $cntf; $i++) { 13695eef580SAndreas Gohr $this->config['align'][] = null; 13795eef580SAndreas Gohr } 1385511bd5bSAndreas Gohr } 1395511bd5bSAndreas Gohr 1405511bd5bSAndreas Gohr /** 1415511bd5bSAndreas Gohr * Get the parsed configuration 1425511bd5bSAndreas Gohr * 1435511bd5bSAndreas Gohr * @return array 1445511bd5bSAndreas Gohr */ 145d6d97f60SAnna Dabrowska public function getConfig() 146d6d97f60SAnna Dabrowska { 1475511bd5bSAndreas Gohr return $this->config; 1485511bd5bSAndreas Gohr } 1495511bd5bSAndreas Gohr 1505511bd5bSAndreas Gohr /** 1515511bd5bSAndreas Gohr * Splits the given line into key and value 1525511bd5bSAndreas Gohr * 1535511bd5bSAndreas Gohr * @param $line 15442d332d7SAndreas Gohr * @return array returns ['',''] if the line is empty 1555511bd5bSAndreas Gohr */ 156d6d97f60SAnna Dabrowska protected function splitLine($line) 157d6d97f60SAnna Dabrowska { 1585511bd5bSAndreas Gohr // ignore comments 1595511bd5bSAndreas Gohr $line = preg_replace('/(?<![&\\\\])#.*$/', '', $line); 1605511bd5bSAndreas Gohr $line = str_replace('\\#', '#', $line); 1615511bd5bSAndreas Gohr $line = trim($line); 16242d332d7SAndreas Gohr if (empty($line)) return ['', '']; 1635511bd5bSAndreas Gohr 1645511bd5bSAndreas Gohr $line = preg_split('/\s*:\s*/', $line, 2); 1655511bd5bSAndreas Gohr $line[0] = strtolower($line[0]); 16642d332d7SAndreas Gohr if (!isset($line[1])) $line[1] = ''; 1675511bd5bSAndreas Gohr 1685511bd5bSAndreas Gohr return $line; 1695511bd5bSAndreas Gohr } 1705511bd5bSAndreas Gohr 1715511bd5bSAndreas Gohr /** 1725511bd5bSAndreas Gohr * parses schema config and aliases 1735511bd5bSAndreas Gohr * 1745511bd5bSAndreas Gohr * @param $val 1755511bd5bSAndreas Gohr * @return array 1765511bd5bSAndreas Gohr */ 177d6d97f60SAnna Dabrowska protected function parseSchema($val) 178d6d97f60SAnna Dabrowska { 179*7234bfb1Ssplitbrain $schemas = []; 1805511bd5bSAndreas Gohr $parts = explode(',', $val); 1815511bd5bSAndreas Gohr foreach ($parts as $part) { 182*7234bfb1Ssplitbrain @[$table, $alias] = array_pad(explode(' ', trim($part)), 2, ''); 1835511bd5bSAndreas Gohr $table = trim($table); 1845511bd5bSAndreas Gohr $alias = trim($alias); 1855511bd5bSAndreas Gohr if (!$table) continue; 1865511bd5bSAndreas Gohr 187*7234bfb1Ssplitbrain $schemas[] = [$table, $alias]; 1885511bd5bSAndreas Gohr } 1895511bd5bSAndreas Gohr return $schemas; 1905511bd5bSAndreas Gohr } 1915511bd5bSAndreas Gohr 1925511bd5bSAndreas Gohr /** 1935511bd5bSAndreas Gohr * Parse alignment data 1945511bd5bSAndreas Gohr * 1955511bd5bSAndreas Gohr * @param string $val 1965511bd5bSAndreas Gohr * @return string[] 1975511bd5bSAndreas Gohr */ 198d6d97f60SAnna Dabrowska protected function parseAlignments($val) 199d6d97f60SAnna Dabrowska { 2005511bd5bSAndreas Gohr $cols = explode(',', $val); 201*7234bfb1Ssplitbrain $data = []; 2025511bd5bSAndreas Gohr foreach ($cols as $col) { 2035511bd5bSAndreas Gohr $col = trim(strtolower($col)); 2045511bd5bSAndreas Gohr if ($col[0] == 'c') { 2055511bd5bSAndreas Gohr $align = 'center'; 2065511bd5bSAndreas Gohr } elseif ($col[0] == 'r') { 2075511bd5bSAndreas Gohr $align = 'right'; 2089cda5805SAndreas Gohr } elseif ($col[0] == 'l') { 2095511bd5bSAndreas Gohr $align = 'left'; 2109cda5805SAndreas Gohr } else { 2119cda5805SAndreas Gohr $align = null; 2125511bd5bSAndreas Gohr } 2135511bd5bSAndreas Gohr $data[] = $align; 2145511bd5bSAndreas Gohr } 2155511bd5bSAndreas Gohr 2165511bd5bSAndreas Gohr return $data; 2175511bd5bSAndreas Gohr } 2185511bd5bSAndreas Gohr 2195511bd5bSAndreas Gohr /** 220e0216289SAndreas Gohr * Parse width data 221e0216289SAndreas Gohr * 222e0216289SAndreas Gohr * @param $val 223e0216289SAndreas Gohr * @return array 224e0216289SAndreas Gohr */ 225d6d97f60SAnna Dabrowska protected function parseWidths($val) 226d6d97f60SAnna Dabrowska { 227e0216289SAndreas Gohr $vals = explode(',', $val); 228e0216289SAndreas Gohr $vals = array_map('trim', $vals); 229*7234bfb1Ssplitbrain 230e0216289SAndreas Gohr $len = count($vals); 231e0216289SAndreas Gohr for ($i = 0; $i < $len; $i++) { 232e0216289SAndreas Gohr $val = trim(strtolower($vals[$i])); 233e0216289SAndreas Gohr 234e0216289SAndreas Gohr if (preg_match('/^\d+.?(\d+)?(px|em|ex|ch|rem|%|in|cm|mm|q|pt|pc)$/', $val)) { 235e0216289SAndreas Gohr // proper CSS unit? 236e0216289SAndreas Gohr $vals[$i] = $val; 237e0216289SAndreas Gohr } elseif (preg_match('/^\d+$/', $val)) { 238e0216289SAndreas Gohr // decimal only? 239e0216289SAndreas Gohr $vals[$i] = $val . 'px'; 240e0216289SAndreas Gohr } else { 241e0216289SAndreas Gohr // invalid 242e0216289SAndreas Gohr $vals[$i] = ''; 243e0216289SAndreas Gohr } 244e0216289SAndreas Gohr } 245e0216289SAndreas Gohr return $vals; 246e0216289SAndreas Gohr } 247e0216289SAndreas Gohr 248e0216289SAndreas Gohr /** 2495511bd5bSAndreas Gohr * Split values at the commas, 2505511bd5bSAndreas Gohr * - Wrap with quotes to escape comma, quotes escaped by two quotes 2515511bd5bSAndreas Gohr * - Within quotes spaces are stored. 2525511bd5bSAndreas Gohr * 2535511bd5bSAndreas Gohr * @param string $line 2545511bd5bSAndreas Gohr * @return array 2555511bd5bSAndreas Gohr */ 256d6d97f60SAnna Dabrowska protected function parseValues($line) 257d6d97f60SAnna Dabrowska { 258*7234bfb1Ssplitbrain $values = []; 2595511bd5bSAndreas Gohr $inQuote = false; 2605511bd5bSAndreas Gohr $escapedQuote = false; 2615511bd5bSAndreas Gohr $value = ''; 2625511bd5bSAndreas Gohr $len = strlen($line); 2635511bd5bSAndreas Gohr for ($i = 0; $i < $len; $i++) { 264d982cb29SMichael Große if ($line[$i] == '"') { 2655511bd5bSAndreas Gohr if ($inQuote) { 2665511bd5bSAndreas Gohr if ($escapedQuote) { 2675511bd5bSAndreas Gohr $value .= '"'; 2685511bd5bSAndreas Gohr $escapedQuote = false; 2695511bd5bSAndreas Gohr continue; 2705511bd5bSAndreas Gohr } 271d982cb29SMichael Große if ($line[$i + 1] == '"') { 2725511bd5bSAndreas Gohr $escapedQuote = true; 2735511bd5bSAndreas Gohr continue; 2745511bd5bSAndreas Gohr } 275*7234bfb1Ssplitbrain $values[] = $value; 2765511bd5bSAndreas Gohr $inQuote = false; 2775511bd5bSAndreas Gohr $value = ''; 2785511bd5bSAndreas Gohr continue; 2795511bd5bSAndreas Gohr } else { 2805511bd5bSAndreas Gohr $inQuote = true; 2815511bd5bSAndreas Gohr $value = ''; //don't store stuff before the opening quote 2825511bd5bSAndreas Gohr continue; 2835511bd5bSAndreas Gohr } 284d982cb29SMichael Große } elseif ($line[$i] == ',') { 2855511bd5bSAndreas Gohr if ($inQuote) { 2865511bd5bSAndreas Gohr $value .= ','; 2875511bd5bSAndreas Gohr continue; 2885511bd5bSAndreas Gohr } else { 2895511bd5bSAndreas Gohr if (strlen($value) < 1) { 2905511bd5bSAndreas Gohr continue; 2915511bd5bSAndreas Gohr } 292*7234bfb1Ssplitbrain $values[] = trim($value); 2935511bd5bSAndreas Gohr $value = ''; 2945511bd5bSAndreas Gohr continue; 2955511bd5bSAndreas Gohr } 2965511bd5bSAndreas Gohr } 297d982cb29SMichael Große $value .= $line[$i]; 2985511bd5bSAndreas Gohr } 2995511bd5bSAndreas Gohr if (strlen($value) > 0) { 300*7234bfb1Ssplitbrain $values[] = trim($value); 3015511bd5bSAndreas Gohr } 3025511bd5bSAndreas Gohr return $values; 3035511bd5bSAndreas Gohr } 304af0ce8d2SAndreas Gohr 305af0ce8d2SAndreas Gohr /** 306af0ce8d2SAndreas Gohr * Ensure custom classes are valid and don't clash 307af0ce8d2SAndreas Gohr * 308af0ce8d2SAndreas Gohr * @param string $line 309af0ce8d2SAndreas Gohr * @return string[] 310af0ce8d2SAndreas Gohr */ 311af0ce8d2SAndreas Gohr protected function parseClasses($line) 312af0ce8d2SAndreas Gohr { 313af0ce8d2SAndreas Gohr $classes = $this->parseValues($line); 314af0ce8d2SAndreas Gohr $classes = array_map(function ($class) { 315af0ce8d2SAndreas Gohr $class = str_replace(' ', '_', $class); 316af0ce8d2SAndreas Gohr $class = preg_replace('/[^a-zA-Z0-9_]/', '', $class); 317af0ce8d2SAndreas Gohr return 'struct-custom-' . $class; 318af0ce8d2SAndreas Gohr }, $classes); 319af0ce8d2SAndreas Gohr return $classes; 320af0ce8d2SAndreas Gohr } 3215511bd5bSAndreas Gohr} 322