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