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