15153720fSfkaag71<?php 25153720fSfkaag71/** 35153720fSfkaag71 * Strata, data entry plugin 45153720fSfkaag71 * 55153720fSfkaag71 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 65153720fSfkaag71 * @author Brend Wanders <b.wanders@utwente.nl> 75153720fSfkaag71 */ 8*8147e7f0SFKaaguse dokuwiki\Extension\Event; 95153720fSfkaag71 105153720fSfkaag71if(!defined('DOKU_INC')) die('Meh.'); 115153720fSfkaag71 125153720fSfkaag71/** 135153720fSfkaag71 * Select syntax for basic query handling. 145153720fSfkaag71 */ 155153720fSfkaag71class syntax_plugin_strata_select extends DokuWiki_Syntax_Plugin { 165153720fSfkaag71 function __construct() { 175153720fSfkaag71 $this->helper =& plugin_load('helper', 'strata_syntax'); 185153720fSfkaag71 $this->util =& plugin_load('helper', 'strata_util'); 195153720fSfkaag71 $this->triples =& plugin_load('helper', 'strata_triples'); 205153720fSfkaag71 } 215153720fSfkaag71 225153720fSfkaag71 function getType() { 235153720fSfkaag71 return 'substition'; 245153720fSfkaag71 } 255153720fSfkaag71 265153720fSfkaag71 function getPType() { 275153720fSfkaag71 return 'block'; 285153720fSfkaag71 } 295153720fSfkaag71 305153720fSfkaag71 function getSort() { 315153720fSfkaag71 return 450; 325153720fSfkaag71 } 335153720fSfkaag71 345153720fSfkaag71 function connectTo($mode) { 355153720fSfkaag71 } 365153720fSfkaag71 375153720fSfkaag71 function getUISettings($numFields, $hasUIBlock) { 385153720fSfkaag71 $sort_choices = array( 395153720fSfkaag71 'y' => array('default', 'yes', 'y'), 405153720fSfkaag71 'l' => array('left to right', 'ltr', 'l'), 415153720fSfkaag71 'r' => array('right to left', 'rtl', 'r'), 425153720fSfkaag71 'n' => array('none', 'no', 'n') 435153720fSfkaag71 ); 445153720fSfkaag71 $filter_choices = array( 455153720fSfkaag71 't' => array('text', 't'), 465153720fSfkaag71 's' => array('select', 's'), 475153720fSfkaag71 'p' => array('prefix select', 'ps'), 485153720fSfkaag71 'e' => array('suffix select', 'ss'), 495153720fSfkaag71 'n' => array('none', 'no', 'n') 505153720fSfkaag71 ); 515153720fSfkaag71 $globalProperties = array( 525153720fSfkaag71 'ui' => $this->getUISettingUI($hasUIBlock), 535153720fSfkaag71 'sort' => array('choices' => $sort_choices, 'minOccur' => $numFields, 'maxOccur' => $numFields, 'default' => 'yes'), 545153720fSfkaag71 'filter' => array('choices' => $filter_choices, 'minOccur' => $numFields, 'maxOccur' => $numFields, 'default' => 'none') 555153720fSfkaag71 ); 565153720fSfkaag71 $groupProperties = array( 575153720fSfkaag71 'sort' => array('choices' => $sort_choices), 585153720fSfkaag71 'filter' => array('choices' => $filter_choices), 595153720fSfkaag71 ); 605153720fSfkaag71 return array($globalProperties, $groupProperties); 615153720fSfkaag71 } 625153720fSfkaag71 635153720fSfkaag71 function getUISettingUI($hasUIBlock) { 645153720fSfkaag71 return array('choices' => array('none' => array('none', 'no', 'n'), 'generic' => array('generic', 'g')), 'default' => ($hasUIBlock ? 'generic' : 'none')); 655153720fSfkaag71 } 665153720fSfkaag71 675153720fSfkaag71 function handle($match, $state, $pos, Doku_Handler $handler) { 685153720fSfkaag71 try { 695153720fSfkaag71 $result = array(); 705153720fSfkaag71 $typemap = array(); 715153720fSfkaag71 725153720fSfkaag71 // allow subclass handling of the whole match 735153720fSfkaag71 $match = $this->preprocess($match, $state, $pos, $handler, $result, $typemap); 745153720fSfkaag71 755153720fSfkaag71 // split into lines and remove header and footer 765153720fSfkaag71 $lines = explode("\n",$match); 775153720fSfkaag71 $header = trim(array_shift($lines)); 785153720fSfkaag71 $footer = trim(array_pop($lines)); 795153720fSfkaag71 805153720fSfkaag71 // allow subclass header handling 815153720fSfkaag71 $header = $this->handleHeader($header, $result, $typemap); 825153720fSfkaag71 835153720fSfkaag71 // parse projection information in 'short syntax' if available 845153720fSfkaag71 if(trim($header) != '') { 855153720fSfkaag71 $result['fields'] = $this->helper->parseFieldsShort($header, $typemap); 86ef8a6397SFKaag } else { 87ef8a6397SFKaag $result['fields'] = []; 885153720fSfkaag71 } 895153720fSfkaag71 905153720fSfkaag71 $tree = $this->helper->constructTree($lines,'query'); 915153720fSfkaag71 925153720fSfkaag71 // parse long fields, if available 935153720fSfkaag71 $longFields = $this->helper->getFields($tree, $typemap); 945153720fSfkaag71 955153720fSfkaag71 // check double data 965153720fSfkaag71 if(count($result['fields']) && count($longFields)) { 975153720fSfkaag71 $this->helper->_fail($this->getLang('error_query_bothfields')); 985153720fSfkaag71 } 995153720fSfkaag71 1005153720fSfkaag71 // assign longfields if necessary 1015153720fSfkaag71 if(count($result['fields']) == 0) { 1025153720fSfkaag71 $result['fields'] = $longFields; 1035153720fSfkaag71 } 1045153720fSfkaag71 1055153720fSfkaag71 // check no data 1065153720fSfkaag71 if(count($result['fields']) == 0) { 1075153720fSfkaag71 $this->helper->_fail($this->helper->getLang('error_query_noselect')); 1085153720fSfkaag71 } 1095153720fSfkaag71 1105153720fSfkaag71 // determine the variables to project 1115153720fSfkaag71 $projection = array(); 1125153720fSfkaag71 foreach($result['fields'] as $f) $projection[] = $f['variable']; 1135153720fSfkaag71 $projection = array_unique($projection); 1145153720fSfkaag71 1155153720fSfkaag71 // allow subclass body handling 1165153720fSfkaag71 $this->handleBody($tree, $result, $typemap); 1175153720fSfkaag71 1185153720fSfkaag71 // parse UI group 1195153720fSfkaag71 $this->handleUI($tree, $result, $typemap); 1205153720fSfkaag71 1215153720fSfkaag71 // parse the query itself 1225153720fSfkaag71 list($result['query'], $variables) = $this->helper->constructQuery($tree, $typemap, $projection); 1235153720fSfkaag71 1245153720fSfkaag71 // allow subclass footer handling 1255153720fSfkaag71 $footer = $this->handleFooter($footer, $result, $typemap, $variable); 1265153720fSfkaag71 1275153720fSfkaag71 // check projected variables and load types 1285153720fSfkaag71 foreach($result['fields'] as $i=>$f) { 1295153720fSfkaag71 $var = $f['variable']; 1305153720fSfkaag71 if(!in_array($var, $variables)) { 1315153720fSfkaag71 $this->helper->_fail(sprintf($this->helper->getLang('error_query_unknownselect'),utf8_tohtml(hsc($var)))); 1325153720fSfkaag71 } 1335153720fSfkaag71 1345153720fSfkaag71 if(empty($f['type'])) { 1355153720fSfkaag71 if(!empty($typemap[$var])) { 1365153720fSfkaag71 $result['fields'][$i] = array_merge($result['fields'][$i],$typemap[$var]); 1375153720fSfkaag71 } else { 1385153720fSfkaag71 list($type, $hint) = $this->util->getDefaultType(); 1395153720fSfkaag71 $result['fields'][$i]['type'] = $type; 1405153720fSfkaag71 $result['fields'][$i]['hint'] = $hint; 1415153720fSfkaag71 } 1425153720fSfkaag71 } 1435153720fSfkaag71 } 1445153720fSfkaag71 1455153720fSfkaag71 return $result; 1465153720fSfkaag71 } catch(strata_exception $e) { 1475153720fSfkaag71 return array('error'=>array( 1485153720fSfkaag71 'message'=>$e->getMessage(), 1495153720fSfkaag71 'regions'=>$e->getData(), 1505153720fSfkaag71 'lines'=>$lines 1515153720fSfkaag71 )); 1525153720fSfkaag71 } 1535153720fSfkaag71 } 1545153720fSfkaag71 1555153720fSfkaag71 function handleUI(&$tree, &$result, &$typemap) { 1565153720fSfkaag71 $trees = $this->helper->extractGroups($tree, 'ui'); 1575153720fSfkaag71 1585153720fSfkaag71 list($globalProperties, $groupProperties) = $this->getUISettings(count($result['fields']), count($trees)); 1595153720fSfkaag71 1605153720fSfkaag71 // Extract column settings which are set as a group 1615153720fSfkaag71 1625153720fSfkaag71 // Extract named column settings 1635153720fSfkaag71 $namedGroupSettings = array(); 1645153720fSfkaag71 foreach ($result['fields'] as $i => $f) { 1655153720fSfkaag71 if(isset($namedGroupSettings[$f['caption']])) continue; 1665153720fSfkaag71 $groups = array(); 1675153720fSfkaag71 foreach($trees as &$t) { 1685153720fSfkaag71 $groups = array_merge($groups, $this->helper->extractGroups($t, $f['caption'])); 1695153720fSfkaag71 } 1705153720fSfkaag71 $namedGroupSettings[$f['caption']] = $this->helper->setProperties($groupProperties, $groups); 1715153720fSfkaag71 } 1725153720fSfkaag71 1735153720fSfkaag71 // Extract numbered column settings 1745153720fSfkaag71 $groupsettings = array(); 1755153720fSfkaag71 foreach ($result['fields'] as $i => $f) { 1765153720fSfkaag71 $groups = array(); 1775153720fSfkaag71 foreach ($trees as &$t) { 1785153720fSfkaag71 $groups = array_merge($groups, $this->helper->extractGroups($t, '#' . ($i+1))); 1795153720fSfkaag71 } 1805153720fSfkaag71 1815153720fSfkaag71 // process settings for this column 1825153720fSfkaag71 $groupsettings[$i] = $this->helper->setProperties($groupProperties, $groups); 1835153720fSfkaag71 1845153720fSfkaag71 // fill in unset properties from named settings 1855153720fSfkaag71 foreach($namedGroupSettings[$f['caption']] as $k=>$v) { 1865153720fSfkaag71 if(!isset($groupsettings[$i][$k])) { 1875153720fSfkaag71 $groupsettings[$i][$k] = $v; 1885153720fSfkaag71 } 1895153720fSfkaag71 } 1905153720fSfkaag71 } 1915153720fSfkaag71 1925153720fSfkaag71 // Extract global settings 1935153720fSfkaag71 $result['strata-ui'] = $this->helper->setProperties($globalProperties, $trees); 1945153720fSfkaag71 1955153720fSfkaag71 // Merge column settings into global ones 1965153720fSfkaag71 foreach ($groupsettings as $i => $s) { 1975153720fSfkaag71 foreach ($s as $p => $v) { 1985153720fSfkaag71 $result['strata-ui'][$p][$i] = $v[0]; 1995153720fSfkaag71 } 2005153720fSfkaag71 } 2015153720fSfkaag71 } 2025153720fSfkaag71 2035153720fSfkaag71 /** 2045153720fSfkaag71 * Handles the whole match. This method is called before any processing 2055153720fSfkaag71 * is done by the actual class. 2065153720fSfkaag71 * 2075153720fSfkaag71 * @param match string the complete match 2085153720fSfkaag71 * @param state the parser state 2095153720fSfkaag71 * @param pos the position in the source 2105153720fSfkaag71 * @param handler object the parser handler 2115153720fSfkaag71 * @param result array the result array passed to the render method 2125153720fSfkaag71 * @param typemap array the type map 2135153720fSfkaag71 * @return a preprocessed string 2145153720fSfkaag71 */ 2155153720fSfkaag71 function preprocess($match, $state, $pos, &$handler, &$result, &$typemap) { 2165153720fSfkaag71 return $match; 2175153720fSfkaag71 } 2185153720fSfkaag71 2195153720fSfkaag71 2205153720fSfkaag71 /** 2215153720fSfkaag71 * Handles the header of the syntax. This method is called before 2225153720fSfkaag71 * the header is handled. 2235153720fSfkaag71 * 2245153720fSfkaag71 * @param header string the complete header 2255153720fSfkaag71 * @param result array the result array passed to the render method 2265153720fSfkaag71 * @param typemap array the type map 2275153720fSfkaag71 * @return a string containing the unhandled parts of the header 2285153720fSfkaag71 */ 2295153720fSfkaag71 function handleHeader($header, &$result, &$typemap) { 2305153720fSfkaag71 return $header; 2315153720fSfkaag71 } 2325153720fSfkaag71 2335153720fSfkaag71 /** 2345153720fSfkaag71 * Handles the body of the syntax. This method is called before any 2355153720fSfkaag71 * of the body is handled, but after the 'fields' groups have been processed. 2365153720fSfkaag71 * 2375153720fSfkaag71 * @param tree array the parsed tree 2385153720fSfkaag71 * @param result array the result array passed to the render method 2395153720fSfkaag71 * @param typemap array the type map 2405153720fSfkaag71 */ 2415153720fSfkaag71 function handleBody(&$tree, &$result, &$typemap) { 2425153720fSfkaag71 } 2435153720fSfkaag71 2445153720fSfkaag71 /** 2455153720fSfkaag71 * Handles the footer of the syntax. This method is called after the 2465153720fSfkaag71 * query has been parsed, but before the typemap is applied to determine 2475153720fSfkaag71 * all field types. 2485153720fSfkaag71 * 2495153720fSfkaag71 * @param footer string the footer string 2505153720fSfkaag71 * @param result array the result array passed to the render method 2515153720fSfkaag71 * @param typemape array the type map 2525153720fSfkaag71 * @param variables array of variables used in query 2535153720fSfkaag71 * @return a string containing the unhandled parts of the footer 2545153720fSfkaag71 */ 2555153720fSfkaag71 function handleFooter($footer, &$result, &$typemap, &$variables) { 2565153720fSfkaag71 return $footer; 2575153720fSfkaag71 } 2585153720fSfkaag71 2595153720fSfkaag71 /** 2605153720fSfkaag71 * This method performs just-in-time modification to prepare 2615153720fSfkaag71 * the query for use. 2625153720fSfkaag71 * 2635153720fSfkaag71 * @param query array the query tree 2645153720fSfkaag71 * @return the query tree to use 2655153720fSfkaag71 */ 2665153720fSfkaag71 function prepareQuery($query) { 2675153720fSfkaag71 // fire event 268*8147e7f0SFKaag Event::createAndTrigger('STRATA_PREPARE_QUERY', $query); 2695153720fSfkaag71 2705153720fSfkaag71 // return the (possibly modified) query 2715153720fSfkaag71 return $query; 2725153720fSfkaag71 } 2735153720fSfkaag71 2745153720fSfkaag71 /** 2755153720fSfkaag71 * This method renders the data view. 2765153720fSfkaag71 * 2775153720fSfkaag71 * @param mode the rendering mode 2785153720fSfkaag71 * @param R the renderer 2795153720fSfkaag71 * @param data the custom data from the handle phase 2805153720fSfkaag71 */ 2815153720fSfkaag71 function render($mode, Doku_Renderer $R, $data) { 2825153720fSfkaag71 return false; 2835153720fSfkaag71 } 2845153720fSfkaag71 2855153720fSfkaag71 /** 2865153720fSfkaag71 * This method renders the container for any strata select. 2875153720fSfkaag71 * 2885153720fSfkaag71 * The open tag will contain all give classes plus additional metadata, e.g., generated by the ui group 2895153720fSfkaag71 * 2905153720fSfkaag71 * @param mode the render mode 2915153720fSfkaag71 * @param R the renderer 2925153720fSfkaag71 * @param data the custom data from the handle phase 2935153720fSfkaag71 * @param additionalClasses array containing classes to be set on the generated container 2945153720fSfkaag71 */ 2955153720fSfkaag71 function ui_container_open($mode, &$R, $data, $additionalClasses=array()) { 2965153720fSfkaag71 // only xhtml mode needs the UI container 2975153720fSfkaag71 if($mode != 'xhtml') return; 2985153720fSfkaag71 2995153720fSfkaag71 $p = $data['strata-ui']; 3005153720fSfkaag71 $c = array(); 3015153720fSfkaag71 3025153720fSfkaag71 // Default sort: rtl for suffix and ltr otherwise 3035153720fSfkaag71 for ($i = 0; $i < count($p['sort']); $i++) { 3045153720fSfkaag71 if ($p['sort'][$i] == 'y') { 3055153720fSfkaag71 $p['sort'][$i] = ($p['filter'][$i] == 'e' ? 'r' : 'l'); 3065153720fSfkaag71 } 3075153720fSfkaag71 } 3085153720fSfkaag71 3095153720fSfkaag71 if (trim(implode($p['sort']), 'n') != '') { 3105153720fSfkaag71 $c[] = 'strata-ui-sort'; 3115153720fSfkaag71 } 3125153720fSfkaag71 if (trim(implode($p['filter']), 'n') != '') { 3135153720fSfkaag71 $c[] = 'strata-ui-filter'; 3145153720fSfkaag71 } 3155153720fSfkaag71 3165153720fSfkaag71 $classes = implode(' ', array_merge($c, $additionalClasses)); 3175153720fSfkaag71 $properties = implode(' ', array_map( 3185153720fSfkaag71 function($k, $v) { 3195153720fSfkaag71 if (empty($v)) { 3205153720fSfkaag71 return ''; 3215153720fSfkaag71 } else { 3225153720fSfkaag71 return 'data-strata-ui-' . $k . '="' . implode($v) . '"'; 3235153720fSfkaag71 } 3245153720fSfkaag71 }, array_keys($p), $p) 3255153720fSfkaag71 ); 3265153720fSfkaag71 3275153720fSfkaag71 $R->doc .= '<div class="' . $classes . '" ' . $properties . '>' . DOKU_LF; 3285153720fSfkaag71 } 3295153720fSfkaag71 3305153720fSfkaag71 function ui_container_close($mode, &$R) { 3315153720fSfkaag71 // only xhtml mode needs the UI container 3325153720fSfkaag71 if($mode != 'xhtml') return; 3335153720fSfkaag71 $R->doc .= '</div>' . DOKU_LF; 3345153720fSfkaag71 } 3355153720fSfkaag71 3365153720fSfkaag71 protected function displayError($mode, &$R, $data) { 3375153720fSfkaag71 if($mode == 'xhtml') { 3385153720fSfkaag71 $style = ''; 3395153720fSfkaag71 if(isset($data['error']['regions'])) $style = ' strata-debug-continued'; 3405153720fSfkaag71 $R->doc .= '<div class="strata-debug-message '.$style.'">'; 3415153720fSfkaag71 $R->cdata($this->helper->getLang('content_error_explanation')); 3425153720fSfkaag71 $R->doc .= ': '.$data['error']['message']; 3435153720fSfkaag71 $R->doc .= '</div>'; 3445153720fSfkaag71 if(isset($data['error']['regions'])) $R->doc .= $this->helper->debugTree($data['error']['lines'], $data['error']['regions']); 3455153720fSfkaag71 } elseif($mode == 'odt') { 3465153720fSfkaag71 $R->cdata($this->helper->getLang('content_error_explanation__non_xhtml')); 3475153720fSfkaag71 } 3485153720fSfkaag71 } 3495153720fSfkaag71} 350