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