1<?php
2/**
3 *
4 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
5 * @author     Andreas Gohr <andi@splitbrain.org>
6 */
7// must be run within Dokuwiki
8if(!defined('DOKU_INC')) die();
9
10/**
11 * Class syntax_plugin_dataau_table
12 */
13class syntax_plugin_dataau_table extends DokuWiki_Syntax_Plugin {
14
15    /**
16     * will hold the dataau helper plugin
17     *
18     * @var $dthlp helper_plugin_data
19     */
20    var $dthlp = null;
21
22    var $sums = array();
23
24    /**
25     * Constructor. Load helper plugin
26     */
27    function __construct() {
28        $this->dthlp = plugin_load('helper', 'dataau');
29    }
30
31    /**
32     * What kind of syntax are we?
33     */
34    function getType() {
35        return 'substition';
36    }
37
38    /**
39     * What about paragraphs?
40     */
41    function getPType() {
42        return 'block';
43    }
44
45    /**
46     * Where to sort in?
47     */
48    function getSort() {
49        return 155;
50    }
51
52    /**
53     * Connect pattern to lexer
54     */
55    function connectTo($mode) {
56        $this->Lexer->addSpecialPattern('----+ *datatable(?: [ a-zA-Z0-9_]*)?-+\n.*?\n----+', $mode, 'plugin_dataau_table');
57    }
58
59    /**
60     * Handle the match - parse the data
61     *
62     * This parsing is shared between the multiple different output/control
63     * syntaxes
64     *
65     * @param   string       $match   The text matched by the patterns
66     * @param   int          $state   The lexer state for the match
67     * @param   int          $pos     The character position of the matched text
68     * @param   Doku_Handler $handler The Doku_Handler object
69     * @return  bool|array Return an array with all data you want to use in render, false don't add an instruction
70     */
71    function handle($match, $state, $pos, Doku_Handler $handler) {
72        if(!$this->dthlp->ready()) return null;
73
74        // get lines and additional class
75        $lines = explode("\n", $match);
76        array_pop($lines);
77        $class = array_shift($lines);
78        $class = preg_replace('/^----+ *dataau[a-z]+/', '', $class);
79        $class = trim($class, '- ');
80
81        $dataau = array(
82            'classes'       => $class,
83            'limit'         => 0,
84            'dynfilters'    => false,
85            'summarize'     => false,
86            'rownumbers'    => (bool) $this->getConf('rownumbers'),
87            'sepbyheaders'  => false,
88            'headers'       => array(),
89            'widths'        => array(),
90            'filter'        => array()
91        );
92
93        // parse info
94        foreach($lines as $line) {
95            // ignore comments
96            $line = preg_replace('/(?<![&\\\\])#.*$/', '', $line);
97            $line = str_replace('\\#', '#', $line);
98            $line = trim($line);
99            if(empty($line)) continue;
100            $line = preg_split('/\s*:\s*/', $line, 2);
101            $line[0] = strtolower($line[0]);
102
103            $logic = 'OR';
104            // handle line commands (we allow various aliases here)
105            switch($line[0]) {
106                case 'select':
107                case 'cols':
108                case 'field':
109                case 'col':
110                    $cols = explode(',', $line[1]);
111                    foreach($cols as $col) {
112                        $col = trim($col);
113                        if(!$col) continue;
114                        $column = $this->dthlp->_column($col);
115                        $dataau['cols'][$column['key']] = $column;
116                    }
117                    break;
118                case 'title':
119                    $dataau['title'] = $line[1];
120                    break;
121                case 'head':
122                case 'header':
123                case 'headers':
124                    $cols = $this->parseValues($line[1]);
125                    $dataau['headers'] = array_merge($dataau['headers'], $cols);
126                    break;
127                case 'align':
128                    $cols = explode(',', $line[1]);
129                    foreach($cols as $col) {
130                        $col = trim(strtolower($col));
131                        if($col[0] == 'c') {
132                            $col = 'center';
133                        } elseif($col[0] == 'r') {
134                            $col = 'right';
135                        } else {
136                            $col = 'left';
137                        }
138                        $dataau['align'][] = $col;
139                    }
140                    break;
141                case 'widths':
142                    $cols = explode(',', $line[1]);
143                    foreach($cols as $col) {
144                        $col = trim($col);
145                        $dataau['widths'][] = $col;
146                    }
147                    break;
148                case 'min':
149                    $dataau['min'] = abs((int) $line[1]);
150                    break;
151                case 'limit':
152                case 'max':
153                    $dataau['limit'] = abs((int) $line[1]);
154                    break;
155                case 'order':
156                case 'sort':
157                    $column = $this->dthlp->_column($line[1]);
158                    $sort = $column['key'];
159                    if(substr($sort, 0, 1) == '^') {
160                        $dataau['sort'] = array(substr($sort, 1), 'DESC');
161                    } else {
162                        $dataau['sort'] = array($sort, 'ASC');
163                    }
164                    break;
165                case 'where':
166                case 'filter':
167                case 'filterand':
168                    /** @noinspection PhpMissingBreakStatementInspection */
169                case 'and':
170                    $logic = 'AND';
171                case 'filteror':
172                case 'or':
173                    if(!$logic) {
174                        $logic = 'OR';
175                    }
176                    $flt = $this->dthlp->_parse_filter($line[1]);
177                    if(is_array($flt)) {
178                        $flt['logic'] = $logic;
179                        $dataau['filter'][] = $flt;
180                    }
181                    break;
182                case 'page':
183                case 'target':
184                    $dataau['page'] = cleanID($line[1]);
185                    break;
186                case 'dynfilters':
187                    $dataau['dynfilters'] = (bool) $line[1];
188                    break;
189                case 'rownumbers':
190                    $dataau['rownumbers'] = (bool) $line[1];
191                    break;
192                case 'summarize':
193                    $dataau['summarize'] = (bool) $line[1];
194                    break;
195                case 'sepbyheaders':
196                    $dataau['sepbyheaders'] = (bool) $line[1];
197                    break;
198                default:
199                    msg("dataau plugin: unknown option '" . hsc($line[0]) . "'", -1);
200            }
201        }
202
203        // we need at least one column to display
204        if(!is_array($dataau['cols']) || !count($dataau['cols'])) {
205            msg('dataau plugin: no columns selected', -1);
206            return null;
207        }
208
209        // fill up headers with field names if necessary
210        $dataau['headers'] = (array) $dataau['headers'];
211        $cnth = count($dataau['headers']);
212        $cntf = count($dataau['cols']);
213        for($i = $cnth; $i < $cntf; $i++) {
214            $column = array_slice($dataau['cols'], $i, 1);
215            $columnprops = array_pop($column);
216            $dataau['headers'][] = $columnprops['title'];
217        }
218
219        $dataau['sql'] = $this->_buildSQL($dataau);
220
221        // Save current request params for comparison in updateSQL
222        $dataau['cur_param'] = $this->dthlp->_get_current_param(false);
223        return $dataau;
224    }
225
226    protected $before_item = '<tr>';
227    protected $after_item  = '</tr>';
228    protected $before_val  = '<td %s>';
229    protected $after_val   = '</td>';
230
231    /**
232     * Handles the actual output creation.
233     *
234     * @param   string        $format output format being rendered
235     * @param   Doku_Renderer $R      the current renderer object
236     * @param   array         $dataau   data created by handler()
237     * @return  boolean               rendered correctly? (however, returned value is not used at the moment)
238     */
239    function render($format, Doku_Renderer $R, $dataau) {
240        if($format != 'xhtml') return false;
241        /** @var Doku_Renderer_xhtml $R */
242
243        if(is_null($dataau)) return false;
244        if(!$this->dthlp->ready()) return false;
245        $sqlite = $this->dthlp->_getDB();
246        if(!$sqlite) return false;
247
248        $R->info['cache'] = false;
249
250        //reset counters
251        $this->sums = array();
252
253        if($this->hasRequestFilter() OR isset($_REQUEST['dataauofs'])) {
254            $this->updateSQLwithQuery($dataau); // handles request params
255        }
256        $this->dthlp->_replacePlaceholdersInSQL($dataau);
257
258        // run query
259        $clist = array_keys($dataau['cols']);
260        $res = $sqlite->query($dataau['sql']);
261
262        $rows = $sqlite->res2arr($res);
263        $cnt = count($rows);
264
265        if($cnt === 0) {
266            $this->nullList($dataau, $clist, $R);
267            return true;
268        }
269
270        if($dataau['limit'] && $cnt > $dataau['limit']) {
271            $rows = array_slice($rows, 0, $dataau['limit']);
272        }
273
274        //build classnames per column
275        $classes = array();
276        $class_names_cache = array();
277        $offset = 0;
278        if($dataau['rownumbers']) {
279            $offset = 1; //rownumbers are in first column
280            $classes[] = $dataau['align'][0] . 'align rownumbers';
281        }
282        foreach($clist as $index => $col) {
283            $class = $dataau['align'][$index + $offset] . 'align';
284            $class .= ' ' . hsc(sectionID($col, $class_names_cache));
285            $classes[] = $class;
286        }
287
288        //start table/list
289        $R->doc .= $this->preList($clist, $dataau);
290
291        foreach($rows as $rownum => $row) {
292            // build data rows
293            $R->doc .= $this->before_item;
294
295            if($dataau['rownumbers']) {
296                $R->doc .= sprintf($this->before_val, 'class="' . $classes[0] . '"');
297                $R->doc .= $rownum + 1;
298                $R->doc .= $this->after_val;
299            }
300
301            foreach(array_values($row) as $num => $cval) {
302                $num_rn = $num + $offset;
303
304                $R->doc .= sprintf($this->beforeVal($dataau, $num_rn), 'class="' . $classes[$num_rn] . '"');
305                $R->doc .= $this->dthlp->_formatData(
306                    $dataau['cols'][$clist[$num]],
307                    $cval, $R
308                );
309                $R->doc .= $this->afterVal($dataau, $num_rn);
310
311                // clean currency symbols
312                $nval = str_replace('$€₤', '', $cval);
313                $nval = str_replace('/ [A-Z]{0,3}$/', '', $nval);
314                $nval = str_replace(',', '.', $nval);
315                $nval = trim($nval);
316
317                // summarize
318                if($dataau['summarize'] && is_numeric($nval)) {
319                    if(!isset($this->sums[$num])) {
320                        $this->sums[$num] = 0;
321                    }
322                    $this->sums[$num] += $nval;
323                }
324
325            }
326            $R->doc .= $this->after_item;
327        }
328        $R->doc .= $this->postList($dataau, $cnt);
329
330        return true;
331    }
332
333    /**
334     * Before value in table cell
335     *
336     * @param array $dataau  instructions by handler
337     * @param int   $colno column number
338     * @return string
339     */
340    protected function beforeVal(&$dataau, $colno) {
341        return $this->before_val;
342    }
343
344    /**
345     * After value in table cell
346     *
347     * @param array $data
348     * @param int   $colno
349     * @return string
350     */
351    protected function afterVal(&$dataau, $colno) {
352        return $this->after_val;
353    }
354
355    /**
356     * Create table header
357     *
358     * @param array $clist keys of the columns
359     * @param array $dataau  instruction by handler
360     * @return string html of table header
361     */
362    function preList($clist, $dataau) {
363        global $ID;
364        global $conf;
365
366        // Save current request params to not loose them
367        $cur_params = $this->dthlp->_get_current_param();
368
369        //show active filters
370        $text = '<div class="table dataaggregation">';
371        if(isset($_REQUEST['dataflt'])) {
372            $filters = $this->dthlp->_get_filters();
373            $fltrs = array();
374            foreach($filters as $filter) {
375                if(strpos($filter['compare'], 'LIKE') !== false) {
376                    if(strpos($filter['compare'], 'NOT') !== false) {
377                        $comparator_value = '!~' . str_replace('%', '*', $filter['value']);
378                    } else {
379                        $comparator_value = '*~' . str_replace('%', '', $filter['value']);
380                    }
381                    $fltrs[] = $filter['key'] . $comparator_value;
382                } else {
383                    $fltrs[] = $filter['key'] . $filter['compare'] . $filter['value'];
384                }
385            }
386
387            $text .= '<div class="filter">';
388            $text .= '<h4>' . sprintf($this->getLang('tablefilteredby'), hsc(implode(' & ', $fltrs))) . '</h4>';
389            $text .= '<div class="resetfilter">' .
390                '<a href="' . wl($ID) . '">' . $this->getLang('tableresetfilter') . '</a>' .
391                '</div>';
392            $text .= '</div>';
393        }
394        // build table
395        $text .= '<table class="inline dataauplugin_table ' . $dataau['classes'] . '">';
396        // build column headers
397        $text .= '<tr>';
398
399        if($dataau['rownumbers']) {
400            $text .= '<th>#</th>';
401        }
402
403        foreach($dataau['headers'] as $num => $head) {
404            $ckey = $clist[$num];
405
406            $width = '';
407            if(isset($dataau['widths'][$num]) AND $dataau['widths'][$num] != '-') {
408                $width = ' style="width: ' . $dataau['widths'][$num] . ';"';
409            }
410            $text .= '<th' . $width . '>';
411
412            // add sort arrow
413            if(isset($dataau['sort']) && $ckey == $dataau['sort'][0]) {
414                if($dataau['sort'][1] == 'ASC') {
415                    $text .= '<span>&darr;</span> ';
416                    $ckey = '^' . $ckey;
417                } else {
418                    $text .= '<span>&uarr;</span> ';
419                }
420            }
421
422            // Clickable header for dynamic sorting
423            $text .= '<a href="' . wl($ID, array('dataausrt' => $ckey) + $cur_params) .
424                '" title="' . $this->getLang('sort') . '">' . hsc($head) . '</a>';
425            $text .= '</th>';
426        }
427        $text .= '</tr>';
428
429        // Dynamic filters
430        if($dataau['dynfilters']) {
431            $text .= '<tr class="dataflt">';
432
433            if($dataau['rownumbers']) {
434                $text .= '<th></th>';
435            }
436
437            foreach($dataau['headers'] as $num => $head) {
438                $text .= '<th>';
439                $form = new Doku_Form(array('method' => 'GET'));
440                $form->_hidden = array();
441                if(!$conf['userewrite']) {
442                    $form->addHidden('id', $ID);
443                }
444
445                $key = 'dataflt[' . $dataau['cols'][$clist[$num]]['colname'] . '*~' . ']';
446                $val = isset($cur_params[$key]) ? $cur_params[$key] : '';
447
448                // Add current request params
449                foreach($cur_params as $c_key => $c_val) {
450                    if($c_val !== '' && $c_key !== $key) {
451                        $form->addHidden($c_key, $c_val);
452                    }
453                }
454
455                $form->addElement(form_makeField('text', $key, $val, ''));
456                $text .= $form->getForm();
457                $text .= '</th>';
458            }
459            $text .= '</tr>';
460        }
461
462        return $text;
463    }
464
465    /**
466     * Create an empty table
467     *
468     * @param array         $dataau  instruction by handler()
469     * @param array         $clist keys of the columns
470     * @param Doku_Renderer $R
471     */
472    function nullList($dataau, $clist, $R) {
473        $R->doc .= $this->preList($clist, $dataau);
474        $R->tablerow_open();
475        $R->tablecell_open(count($clist), 'center');
476        $R->cdata($this->getLang('none'));
477        $R->tablecell_close();
478        $R->tablerow_close();
479        $R->doc .= '</table></div>';
480    }
481
482    /**
483     * Create table footer
484     *
485     * @param array $dataau   instruction by handler()
486     * @param int   $rowcnt number of rows
487     * @return string html of table footer
488     */
489    function postList($dataau, $rowcnt) {
490        global $ID;
491        $text = '';
492        // if summarize was set, add sums
493        if($dataau['summarize']) {
494            $text .= '<tr>';
495            $len = count($dataau['cols']);
496
497            if($dataau['rownumbers']) $text .= '<td></td>';
498
499            for($i = 0; $i < $len; $i++) {
500                $text .= '<td class="' . $dataau['align'][$i] . 'align">';
501                if(!empty($this->sums[$i])) {
502                    $text .= '∑ ' . $this->sums[$i];
503                } else {
504                    $text .= '&nbsp;';
505                }
506                $text .= '</td>';
507            }
508            $text .= '<tr>';
509        }
510
511        // if limit was set, add control
512        if($dataau['limit']) {
513            $text .= '<tr><th colspan="' . (count($dataau['cols']) + ($dataau['rownumbers'] ? 1 : 0)) . '">';
514            $offset = (int) $_REQUEST['dataauofs'];
515            if($offset) {
516                $prev = $offset - $dataau['limit'];
517                if($prev < 0) {
518                    $prev = 0;
519                }
520
521                // keep url params
522                $params = $this->dthlp->_a2ua('dataflt', $_REQUEST['dataflt']);
523                if(isset($_REQUEST['dataausrt'])) {
524                    $params['dataausrt'] = $_REQUEST['dataausrt'];
525                }
526                $params['dataauofs'] = $prev;
527
528                $text .= '<a href="' . wl($ID, $params) .
529                    '" title="' . $this->getLang('prev') .
530                    '" class="prev">' . $this->getLang('prev') . '</a>';
531            }
532
533            $text .= '&nbsp;';
534
535            if($rowcnt > $dataau['limit']) {
536                $next = $offset + $dataau['limit'];
537
538                // keep url params
539                $params = $this->dthlp->_a2ua('dataflt', $_REQUEST['dataflt']);
540                if(isset($_REQUEST['dataausrt'])) {
541                    $params['dataausrt'] = $_REQUEST['dataausrt'];
542                }
543                $params['dataauofs'] = $next;
544
545                $text .= '<a href="' . wl($ID, $params) .
546                    '" title="' . $this->getLang('next') .
547                    '" class="next">' . $this->getLang('next') . '</a>';
548            }
549            $text .= '</th></tr>';
550        }
551
552        $text .= '</table></div>';
553        return $text;
554    }
555
556    /**
557     * Builds the SQL query from the given data
558     *
559     * @param array &$dataau instruction by handler
560     * @return bool|string SQL query or false
561     */
562    function _buildSQL(&$dataau) {
563        $cnt = 0;
564        $tables = array();
565        $select = array();
566        $from = '';
567
568        $from2 = '';
569        $where2 = '1 = 1';
570
571        $sqlite = $this->dthlp->_getDB();
572        if(!$sqlite) return false;
573
574        // prepare the columns to show
575        foreach($dataau['cols'] as &$col) {
576            $key = $col['key'];
577            if($key == '%pageid%') {
578                // Prevent stripping of trailing zeros by forcing a CAST
579                $select[] = '" " || pages.page';
580            } elseif($key == '%class%') {
581                // Prevent stripping of trailing zeros by forcing a CAST
582                $select[] = '" " || pages.class';
583            } elseif($key == '%lastmod%') {
584                $select[] = 'pages.lastmod';
585            } elseif($key == '%title%') {
586                $select[] = "pages.page || '|' || pages.title";
587            } else {
588                if(!isset($tables[$key])) {
589                    $tables[$key] = 'T' . (++$cnt);
590                    $from .= ' LEFT JOIN dataau AS ' . $tables[$key] . ' ON ' . $tables[$key] . '.pid = W1.pid';
591                    $from .= ' AND ' . $tables[$key] . ".key = " . $sqlite->quote_string($key);
592                }
593                $type = $col['type'];
594                if(is_array($type)) {
595                    $type = $type['type'];
596                }
597                switch($type) {
598                    case 'pageid':
599                    case 'wiki':
600                        //note in multivalued case: adds pageid only to first value
601                        $select[] = "pages.page || '|' || group_concat(" . $tables[$key] . ".value,'\n')";
602                        break;
603                    default:
604                        // Prevent stripping of trailing zeros by forcing a CAST
605                        $select[] = 'group_concat(" " || ' . $tables[$key] . ".value,'\n')";
606                }
607            }
608        }
609        unset($col);
610
611        // prepare sorting
612        if(isset($dataau['sort'])) {
613            $col = $dataau['sort'][0];
614
615            if($col == '%pageid%') {
616                $order = 'ORDER BY pages.page ' . $dataau['sort'][1];
617            } elseif($col == '%class%') {
618                $order = 'ORDER BY pages.class ' . $dataau['sort'][1];
619            } elseif($col == '%title%') {
620                $order = 'ORDER BY pages.title ' . $dataau['sort'][1];
621            } elseif($col == '%lastmod%') {
622                $order = 'ORDER BY pages.lastmod ' . $dataau['sort'][1];
623            } else {
624                // sort by hidden column?
625                if(!$tables[$col]) {
626                    $tables[$col] = 'T' . (++$cnt);
627                    $from .= ' LEFT JOIN dataau AS ' . $tables[$col] . ' ON ' . $tables[$col] . '.pid = W1.pid';
628                    $from .= ' AND ' . $tables[$col] . ".key = " . $sqlite->quote_string($col);
629                }
630
631                $order = 'ORDER BY ' . $tables[$col] . '.value ' . $dataau['sort'][1];
632            }
633        } else {
634            $order = 'ORDER BY 1 ASC';
635        }
636
637        // may be disabled from config. as it decreases performance a lot
638        $use_dataresolve = $this->getConf('use_dataresolve');
639
640        // prepare filters
641        $cnt = 0;
642        if(is_array($dataau['filter']) && count($dataau['filter'])) {
643
644            foreach($dataau['filter'] as $filter) {
645                $col = $filter['key'];
646                $closecompare = ($filter['compare'] == 'IN(' ? ')' : '');
647
648                if($col == '%pageid%') {
649                    $where2 .= " " . $filter['logic'] . " pages.page " . $filter['compare'] . " '" . $filter['value'] . "'" . $closecompare;
650                } elseif($col == '%class%') {
651                    $where2 .= " " . $filter['logic'] . " pages.class " . $filter['compare'] . " '" . $filter['value'] . "'" . $closecompare;
652                } elseif($col == '%title%') {
653                    $where2 .= " " . $filter['logic'] . " pages.title " . $filter['compare'] . " '" . $filter['value'] . "'" . $closecompare;
654                } elseif($col == '%lastmod%') {
655                    # parse value to int?
656                    $filter['value'] = (int) strtotime($filter['value']);
657                    $where2 .= " " . $filter['logic'] . " pages.lastmod " . $filter['compare'] . " " . $filter['value'] . $closecompare;
658                } else {
659                    // filter by hidden column?
660                    $table = 'T' . (++$cnt);
661                    $from2 .= ' LEFT JOIN dataau AS ' . $table . ' ON ' . $table . '.pid = pages.pid';
662                    $from2 .= ' AND ' . $table . ".key = " . $sqlite->quote_string($col);
663
664                    // apply dataau resolving?
665                    if($use_dataresolve && $filter['colname'] && (substr($filter['compare'], -4) == 'LIKE')) {
666                        $where2 .= ' ' . $filter['logic'] . ' DATARESOLVE(' . $table . '.value,\'' . $sqlite->escape_string($filter['colname']) . '\') ' . $filter['compare'] .
667                            " '" . $filter['value'] . "'"; //value is already escaped
668                    } else {
669                        $where2 .= ' ' . $filter['logic'] . ' ' . $table . '.value ' . $filter['compare'] .
670                            " '" . $filter['value'] . "'" . $closecompare; //value is already escaped
671                    }
672                }
673            }
674        }
675
676        // build the query
677        $sql = "SELECT " . join(', ', $select) . "
678                FROM (
679                    SELECT DISTINCT pages.pid AS pid
680                    FROM pages $from2
681                    WHERE $where2
682                ) AS W1
683                $from
684                LEFT JOIN pages ON W1.pid=pages.pid
685                GROUP BY W1.pid
686                $order";
687
688        // offset and limit
689        if($dataau['limit']) {
690            $sql .= ' LIMIT ' . ($dataau['limit'] + 1);
691            // offset is added from REQUEST params in updateSQLwithQuery
692        }
693
694        return $sql;
695    }
696
697    /**
698     * Handle request paramaters, rebuild sql when needed
699     *
700     * @param array $dataau instruction by handler()
701     */
702    function updateSQLwithQuery(&$dataau) {
703        if($this->hasRequestFilter()) {
704            if(isset($_REQUEST['dataausrt'])) {
705                if($_REQUEST['dataausrt']{0} == '^') {
706                    $dataau['sort'] = array(substr($_REQUEST['dataausrt'], 1), 'DESC');
707                } else {
708                    $dataau['sort'] = array($_REQUEST['dataausrt'], 'ASC');
709                }
710            }
711
712            // add request filters
713            $dataau['filter'] = array_merge($dataau['filter'], $this->dthlp->_get_filters());
714
715            // Rebuild SQL FIXME do this smarter & faster
716            $dataau['sql'] = $this->_buildSQL($dataau);
717        }
718
719        if($dataau['limit'] && (int) $_REQUEST['dataauofs']) {
720            $dataau['sql'] .= ' OFFSET ' . ((int) $_REQUEST['dataauofs']);
721        }
722    }
723
724    /**
725     * Check whether a sort or filter request parameters are available
726     *
727     * @return bool
728     */
729    function hasRequestFilter() {
730        return isset($_REQUEST['dataausrt']) || isset($_REQUEST['dataflt']);
731    }
732
733    /**
734     * Split values at the commas,
735     * - Wrap with quotes to escape comma, quotes escaped by two quotes
736     * - Within quotes spaces are stored.
737     *
738     * @param string $line
739     * @return array
740     */
741    protected function parseValues($line) {
742        $values = array();
743        $inQuote = false;
744        $escapedQuote = false;
745        $value = '';
746
747        $len = strlen($line);
748        for($i = 0; $i < $len; $i++) {
749            if($line{$i} == '"') {
750                if($inQuote) {
751                    if($escapedQuote) {
752                        $value .= '"';
753                        $escapedQuote = false;
754                        continue;
755                    }
756                    if($line{$i + 1} == '"') {
757                        $escapedQuote = true;
758                        continue;
759                    }
760                    array_push($values, $value);
761                    $inQuote = false;
762                    $value = '';
763                    continue;
764
765                } else {
766                    $inQuote = true;
767                    $value = ''; //don't store stuff before the opening quote
768                    continue;
769                }
770            } else if($line{$i} == ',') {
771                if($inQuote) {
772                    $value .= ',';
773                    continue;
774                } else {
775                    if(strlen($value) < 1) {
776                        continue;
777                    }
778                    array_push($values, trim($value));
779                    $value = '';
780                    continue;
781                }
782            }
783
784            $value .= $line{$i};
785        }
786        if(strlen($value) > 0) {
787            array_push($values, trim($value));
788        }
789        return $values;
790    }
791}
792
793