xref: /plugin/strata/syntax/select.php (revision 5153720fcc1dd2b6e63035d45f7c2bc32e429371)
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