1<?php 2/** 3 * Strata, data entry plugin 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Brend Wanders <b.wanders@utwente.nl> 7 */ 8use dokuwiki\Extension\Event; 9 10if(!defined('DOKU_INC')) die('Meh.'); 11 12/** 13 * Select syntax for basic query handling. 14 */ 15class syntax_plugin_strata_select extends DokuWiki_Syntax_Plugin { 16 function __construct() { 17 $this->helper =& plugin_load('helper', 'strata_syntax'); 18 $this->util =& plugin_load('helper', 'strata_util'); 19 $this->triples =& plugin_load('helper', 'strata_triples'); 20 } 21 22 function getType() { 23 return 'substition'; 24 } 25 26 function getPType() { 27 return 'block'; 28 } 29 30 function getSort() { 31 return 450; 32 } 33 34 function connectTo($mode) { 35 } 36 37 function getUISettings($numFields, $hasUIBlock) { 38 $sort_choices = array( 39 'y' => array('default', 'yes', 'y'), 40 'l' => array('left to right', 'ltr', 'l'), 41 'r' => array('right to left', 'rtl', 'r'), 42 'n' => array('none', 'no', 'n') 43 ); 44 $filter_choices = array( 45 't' => array('text', 't'), 46 's' => array('select', 's'), 47 'p' => array('prefix select', 'ps'), 48 'e' => array('suffix select', 'ss'), 49 'n' => array('none', 'no', 'n') 50 ); 51 $globalProperties = array( 52 'ui' => $this->getUISettingUI($hasUIBlock), 53 'sort' => array('choices' => $sort_choices, 'minOccur' => $numFields, 'maxOccur' => $numFields, 'default' => 'yes'), 54 'filter' => array('choices' => $filter_choices, 'minOccur' => $numFields, 'maxOccur' => $numFields, 'default' => 'none') 55 ); 56 $groupProperties = array( 57 'sort' => array('choices' => $sort_choices), 58 'filter' => array('choices' => $filter_choices), 59 ); 60 return array($globalProperties, $groupProperties); 61 } 62 63 function getUISettingUI($hasUIBlock) { 64 return array('choices' => array('none' => array('none', 'no', 'n'), 'generic' => array('generic', 'g')), 'default' => ($hasUIBlock ? 'generic' : 'none')); 65 } 66 67 function handle($match, $state, $pos, Doku_Handler $handler) { 68 try { 69 $result = array(); 70 $typemap = array(); 71 72 // allow subclass handling of the whole match 73 $match = $this->preprocess($match, $state, $pos, $handler, $result, $typemap); 74 75 // split into lines and remove header and footer 76 $lines = explode("\n",$match); 77 $header = trim(array_shift($lines)); 78 $footer = trim(array_pop($lines)); 79 80 // allow subclass header handling 81 $header = $this->handleHeader($header, $result, $typemap); 82 83 // parse projection information in 'short syntax' if available 84 if(trim($header) != '') { 85 $result['fields'] = $this->helper->parseFieldsShort($header, $typemap); 86 } else { 87 $result['fields'] = []; 88 } 89 90 $tree = $this->helper->constructTree($lines,'query'); 91 92 // parse long fields, if available 93 $longFields = $this->helper->getFields($tree, $typemap); 94 95 // check double data 96 if(count($result['fields']) && count($longFields)) { 97 $this->helper->_fail($this->getLang('error_query_bothfields')); 98 } 99 100 // assign longfields if necessary 101 if(count($result['fields']) == 0) { 102 $result['fields'] = $longFields; 103 } 104 105 // check no data 106 if(count($result['fields']) == 0) { 107 $this->helper->_fail($this->helper->getLang('error_query_noselect')); 108 } 109 110 // determine the variables to project 111 $projection = array(); 112 foreach($result['fields'] as $f) $projection[] = $f['variable']; 113 $projection = array_unique($projection); 114 115 // allow subclass body handling 116 $this->handleBody($tree, $result, $typemap); 117 118 // parse UI group 119 $this->handleUI($tree, $result, $typemap); 120 121 // parse the query itself 122 list($result['query'], $variables) = $this->helper->constructQuery($tree, $typemap, $projection); 123 124 // allow subclass footer handling 125 $footer = $this->handleFooter($footer, $result, $typemap, $variable); 126 127 // check projected variables and load types 128 foreach($result['fields'] as $i=>$f) { 129 $var = $f['variable']; 130 if(!in_array($var, $variables)) { 131 $this->helper->_fail(sprintf($this->helper->getLang('error_query_unknownselect'),utf8_tohtml(hsc($var)))); 132 } 133 134 if(empty($f['type'])) { 135 if(!empty($typemap[$var])) { 136 $result['fields'][$i] = array_merge($result['fields'][$i],$typemap[$var]); 137 } else { 138 list($type, $hint) = $this->util->getDefaultType(); 139 $result['fields'][$i]['type'] = $type; 140 $result['fields'][$i]['hint'] = $hint; 141 } 142 } 143 } 144 145 return $result; 146 } catch(strata_exception $e) { 147 return array('error'=>array( 148 'message'=>$e->getMessage(), 149 'regions'=>$e->getData(), 150 'lines'=>$lines 151 )); 152 } 153 } 154 155 function handleUI(&$tree, &$result, &$typemap) { 156 $trees = $this->helper->extractGroups($tree, 'ui'); 157 158 list($globalProperties, $groupProperties) = $this->getUISettings(count($result['fields']), count($trees)); 159 160 // Extract column settings which are set as a group 161 162 // Extract named column settings 163 $namedGroupSettings = array(); 164 foreach ($result['fields'] as $i => $f) { 165 if(isset($namedGroupSettings[$f['caption']])) continue; 166 $groups = array(); 167 foreach($trees as &$t) { 168 $groups = array_merge($groups, $this->helper->extractGroups($t, $f['caption'])); 169 } 170 $namedGroupSettings[$f['caption']] = $this->helper->setProperties($groupProperties, $groups); 171 } 172 173 // Extract numbered column settings 174 $groupsettings = array(); 175 foreach ($result['fields'] as $i => $f) { 176 $groups = array(); 177 foreach ($trees as &$t) { 178 $groups = array_merge($groups, $this->helper->extractGroups($t, '#' . ($i+1))); 179 } 180 181 // process settings for this column 182 $groupsettings[$i] = $this->helper->setProperties($groupProperties, $groups); 183 184 // fill in unset properties from named settings 185 foreach($namedGroupSettings[$f['caption']] as $k=>$v) { 186 if(!isset($groupsettings[$i][$k])) { 187 $groupsettings[$i][$k] = $v; 188 } 189 } 190 } 191 192 // Extract global settings 193 $result['strata-ui'] = $this->helper->setProperties($globalProperties, $trees); 194 195 // Merge column settings into global ones 196 foreach ($groupsettings as $i => $s) { 197 foreach ($s as $p => $v) { 198 $result['strata-ui'][$p][$i] = $v[0]; 199 } 200 } 201 } 202 203 /** 204 * Handles the whole match. This method is called before any processing 205 * is done by the actual class. 206 * 207 * @param match string the complete match 208 * @param state the parser state 209 * @param pos the position in the source 210 * @param handler object the parser handler 211 * @param result array the result array passed to the render method 212 * @param typemap array the type map 213 * @return a preprocessed string 214 */ 215 function preprocess($match, $state, $pos, &$handler, &$result, &$typemap) { 216 return $match; 217 } 218 219 220 /** 221 * Handles the header of the syntax. This method is called before 222 * the header is handled. 223 * 224 * @param header string the complete header 225 * @param result array the result array passed to the render method 226 * @param typemap array the type map 227 * @return a string containing the unhandled parts of the header 228 */ 229 function handleHeader($header, &$result, &$typemap) { 230 return $header; 231 } 232 233 /** 234 * Handles the body of the syntax. This method is called before any 235 * of the body is handled, but after the 'fields' groups have been processed. 236 * 237 * @param tree array the parsed tree 238 * @param result array the result array passed to the render method 239 * @param typemap array the type map 240 */ 241 function handleBody(&$tree, &$result, &$typemap) { 242 } 243 244 /** 245 * Handles the footer of the syntax. This method is called after the 246 * query has been parsed, but before the typemap is applied to determine 247 * all field types. 248 * 249 * @param footer string the footer string 250 * @param result array the result array passed to the render method 251 * @param typemape array the type map 252 * @param variables array of variables used in query 253 * @return a string containing the unhandled parts of the footer 254 */ 255 function handleFooter($footer, &$result, &$typemap, &$variables) { 256 return $footer; 257 } 258 259 /** 260 * This method performs just-in-time modification to prepare 261 * the query for use. 262 * 263 * @param query array the query tree 264 * @return the query tree to use 265 */ 266 function prepareQuery($query) { 267 // fire event 268 Event::createAndTrigger('STRATA_PREPARE_QUERY', $query); 269 270 // return the (possibly modified) query 271 return $query; 272 } 273 274 /** 275 * This method renders the data view. 276 * 277 * @param mode the rendering mode 278 * @param R the renderer 279 * @param data the custom data from the handle phase 280 */ 281 function render($mode, Doku_Renderer $R, $data) { 282 return false; 283 } 284 285 /** 286 * This method renders the container for any strata select. 287 * 288 * The open tag will contain all give classes plus additional metadata, e.g., generated by the ui group 289 * 290 * @param mode the render mode 291 * @param R the renderer 292 * @param data the custom data from the handle phase 293 * @param additionalClasses array containing classes to be set on the generated container 294 */ 295 function ui_container_open($mode, &$R, $data, $additionalClasses=array()) { 296 // only xhtml mode needs the UI container 297 if($mode != 'xhtml') return; 298 299 $p = $data['strata-ui']; 300 $c = array(); 301 302 // Default sort: rtl for suffix and ltr otherwise 303 for ($i = 0; $i < count($p['sort']); $i++) { 304 if ($p['sort'][$i] == 'y') { 305 $p['sort'][$i] = ($p['filter'][$i] == 'e' ? 'r' : 'l'); 306 } 307 } 308 309 if (trim(implode($p['sort']), 'n') != '') { 310 $c[] = 'strata-ui-sort'; 311 } 312 if (trim(implode($p['filter']), 'n') != '') { 313 $c[] = 'strata-ui-filter'; 314 } 315 316 $classes = implode(' ', array_merge($c, $additionalClasses)); 317 $properties = implode(' ', array_map( 318 function($k, $v) { 319 if (empty($v)) { 320 return ''; 321 } else { 322 return 'data-strata-ui-' . $k . '="' . implode($v) . '"'; 323 } 324 }, array_keys($p), $p) 325 ); 326 327 $R->doc .= '<div class="' . $classes . '" ' . $properties . '>' . DOKU_LF; 328 } 329 330 function ui_container_close($mode, &$R) { 331 // only xhtml mode needs the UI container 332 if($mode != 'xhtml') return; 333 $R->doc .= '</div>' . DOKU_LF; 334 } 335 336 protected function displayError($mode, &$R, $data) { 337 if($mode == 'xhtml') { 338 $style = ''; 339 if(isset($data['error']['regions'])) $style = ' strata-debug-continued'; 340 $R->doc .= '<div class="strata-debug-message '.$style.'">'; 341 $R->cdata($this->helper->getLang('content_error_explanation')); 342 $R->doc .= ': '.$data['error']['message']; 343 $R->doc .= '</div>'; 344 if(isset($data['error']['regions'])) $R->doc .= $this->helper->debugTree($data['error']['lines'], $data['error']['regions']); 345 } elseif($mode == 'odt') { 346 $R->cdata($this->helper->getLang('content_error_explanation__non_xhtml')); 347 } 348 } 349} 350