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