Project

General

Profile

1
<?php
2
/***********************************************************************
3
    Copyright 2008-2009 Mark Williams
4

    
5
    Licensed under the Apache License, Version 2.0 (the "License");
6
    you may not use this file except in compliance with the License.
7
    You may obtain a copy of the License at
8

    
9
        http://www.apache.org/licenses/LICENSE-2.0
10

    
11
    Unless required by applicable law or agreed to in writing, software
12
    distributed under the License is distributed on an "AS IS" BASIS,
13
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
    See the License for the specific language governing permissions and
15
    limitations under the License.
16

    
17
    File: vistable.php
18
***********************************************************************/
19
require "visparser.php";
20
require "visformat.php";
21

    
22
global $vistable_ordering;
23
function vistable_order_function($a, $b)
24
{
25
    global $vistable_ordering;
26

    
27
    foreach ($vistable_ordering as $id => $dir) {
28
        $aval = $a[$id];
29
        $bval = $b[$id];
30
        if ($aval < $bval) return -$dir;
31
        if ($bval < $aval) return $dir;
32
    }
33
    return 0;
34
}
35

    
36
abstract class vistable {
37
    protected $query;
38
    protected $params;
39
    protected $needs_total_rows;
40
    protected $total_rows;
41
    protected $first_row;
42
    protected $num_rows;
43

    
44
    private $response;
45
    private $tqrt;
46
    private $tq;
47
    private $tz;
48
    private $locale;
49
    protected $fields;
50
    protected $debug;
51
    private $aggregates;
52
    private $visited = 0;
53
    private $agr_reset = 0;
54
   
55
    public function __construct($tqx,$tq,$tqrt,$tz,$locale,$extra=NULL) {
56
        $this->response = array('status' => 'ok');
57

    
58
        $this->params = array(
59
            'version' => '0.6',
60
            'sig' => '',
61
            'responseHandler' => 'google.visualization.Query.setResponse'
62
            );
63

    
64
        if ($extra) {
65
            foreach ($extra as $key => $value) {
66
                $this->params[$key] = $value;
67
            }
68
        }
69

    
70
        if ($tqx) {
71
            foreach (explode(';', $tqx) as $kvpair) {
72
                $kva = explode(':', $kvpair, 2);
73
                if (count($kva) == 2) {
74
                    $this->params[$kva[0]] = $kva[1];
75
                }
76
            }
77
        }
78

    
79
        if (get_magic_quotes_gpc()) {
80
            $tq = stripslashes($tq);
81
        }
82
       
83
        $this->debug = $extra && $extra['debug'];
84

    
85
        $this->tq = $tq;
86
        $this->tqrt = $tqrt;
87
        $this->tz = $tz;
88
        $this->locale = $locale;
89

    
90
        $timezone = new DateTimeZone($tz);
91
        $date = new DateTime("", $timezone);
92
        $this->gmt_offset = $timezone->getOffset($date);
93
    }
94

    
95
    public function get_param($param,$default = NULL)
96
    {
97
        return isset($this->params[$param]) ? $this->params[$param] : $default;
98
    }
99

    
100
    public function get_sig()
101
    {
102
        return isset($this->response['sig']) ? $this->response['sig'] : FALSE;
103
    }
104

    
105
    public function set_param($param, $value)
106
    {
107
        $this->params[$param] = $value;
108
    }
109

    
110
    private function diagnostic($kind, $reason, $message, $detailed_message)
111
    {
112
        if ($this->response['status'] == 'ok' || $kind == 'error') {
113
            $this->response['status'] = $kind;
114
        }  
115
        $kind .= "s";
116

    
117
        if (!isset($this->response[$kind])) {
118
            $this->response[$kind] = array();
119
        }
120

    
121
        array_push($this->response[$kind], array(
122
                       'reason' => $reason,
123
                       'message' => $message,
124
                       'detailed_message' => $detailed_message));
125
    }
126

    
127
    public function error($reason,$message,$detailed_message)
128
    {
129
        $this->diagnostic('error',$reason,$message,$detailed_message);
130
    }
131

    
132
    public function warning($reason,$message,$detailed_message)
133
    {
134
        $this->diagnostic('warning',$reason,$message,$detailed_message);
135
    }
136

    
137
    abstract protected function fetch_table($query);
138
    protected function pre_write($q) { return NULL; }
139

    
140
    protected function write_func($v,$q,$rev = FALSE)
141
    {
142
        $args = array();
143
        for ($i=0;isset($q[$i]);$i++) {
144
            $args[] = $this->write_expr($q[$i]);
145
        }
146
        if ($rev) $args = array_reverse($args);
147
        return "$v(".implode(",",$args).")";
148
    }
149

    
150
    protected function write_expr($q)
151
    {
152
        $r = $this->pre_write($q);
153
        if (is_string($r)) {
154
            return $r;
155
        } else if ($r !== NULL) {
156
            $q = $r;
157
        }
158
        $v = $q[VALUE];
159
        switch ($q[TYPE]) {
160
        case LITERAL:
161
            switch ($q['type']) {
162
            case 'string':
163
            case 'date':
164
            case 'datetime':
165
            case 'timeofday':
166
                return "'".mysql_real_escape_string($v)."'";
167
            case 'number':
168
                return $v;
169
            }
170
            break;
171
        case OPERATOR:
172
            $e0 = $this->write_expr($q[0]);
173
            if (isset($q[1])) {
174
                $e1 = $this->write_expr($q[1]);
175
                return "$e0 $v $e1";
176
            } else {
177
                return "$v $e0";
178
            }
179
        case FUNCT:
180
            return $this->write_func($v,$q);
181
        case SIMPLE:
182
            return $v;
183
        }
184
    }
185
    private function datepart($part,$date)
186
    {
187
        $dateParts = array('dayofweek' => array('w',1),
188
                           'day'       => 'j',
189
                           'month'     => 'n',
190
                           'year'      => 'Y',
191
                           'hour'      => 'G',
192
                           'minute'    => 'i',
193
                           'second'    => 's',
194
                           );
195

    
196
        $part = $dateParts[$part];
197
        $offset = 0;
198
        if (is_array($part)) {
199
            $offset = $part[1];
200
            $part = $part[0];
201
        }
202

    
203
        $date += $this->gmt_offset;
204
        return intval(gmdate($part, $date)) + $offset;
205
    }
206

    
207
    private function dateparts($parts, $date)
208
    {
209
        $result = array();
210
        foreach ($parts as $part) {
211
            $result[] = $this->datepart($part,$date);
212
        }
213
        return $result;
214
    }
215

    
216
    public function evaluate($row, &$q)
217
    {
218
        $v = $q[VALUE];
219
        switch ($q[TYPE]) {
220
        case STRING:
221
            break;
222
        case OPERATOR:
223
        case FUNCT:
224
            $args = array();
225
            for ($i=0;isset($q[$i]);$i++) {
226
                $args[] = $this->evaluate($row,$q[$i]);
227
            }
228
            switch ($v) {
229
                case '*': $v = $args[0] * $args[1]; break;
230
                case '/': $v = $args[0] / $args[1]; break;
231
                case '+': $v = $args[0] + $args[1]; break;
232
                case '-': $v = $args[0] - $args[1]; break;
233
                case '<': $v = $args[0] < $args[1]; break;
234
                case '>': $v = $args[0] > $args[1]; break;
235
                case '<=': $v = $args[0] <= $args[1]; break;
236
                case '>=': $v = $args[0] >= $args[1]; break;
237
                case 'starts_with': $v = !strncmp($args[0], $args[1], strlen($args[1])); break;
238
                case 'ends_with': $v = substr($args[0],-strlen($args[1])) == $args[1]; break;
239
                case 'contains': $v = strpos($args[0], $args[1]) !== FALSE; break;
240
                case 'matches': $v = preg_match("/^{$args[1]}$/", $args[0]); break;
241
                case '=': $v = $args[0] == $args[1]; break;
242
                case '!=':
243
                case '<>': $v = $args[0] != $args[1]; break;
244
                case 'and': $v = $args[0] && $args[1]; break;
245
                case 'or': $v = $args[0] || $args[1]; break;
246
                case 'not': $v = !$args[0]; break;
247
                case 'millisecond':$v = 0; break;
248
                case 'year':
249
                case 'month':
250
                case 'day':
251
                case 'hour':
252
                case 'minute':
253
                case 'second':
254
                case 'dayofweek':
255
                    $v = $this->datepart($v,$args[0]);
256
                    break;
257
                case 'quarter':$a=getdate($args[0]);$v = (int)(($a['mon']-1)/3)+1; break;
258
                case 'datediff':$v = (int)($args[0] / (3600 * 24)) - (int)($args[1] / (3600 * 24)); break;
259
                case 'now': $v = time(); break;
260
                case 'date':
261
                case 'datetime':
262
                case 'timeofday':
263
                    $v = $this->convert_literal($v, $args[0]);
264
                    break;
265
                case 'todate':
266
                    $v = 0;
267
                    switch ($q[0]["type"]) {
268
                        case 'date':
269
                        case 'datetime':
270
                            $v = (int)($args[0] / (3600 * 24)) * (3600 * 24);
271
                            break;
272
                        case 'number':
273
                            $v = (int)($args[0] / 1000);
274
                            break;
275
                    }
276
                    break;
277
                case 'upper': $v = strtoupper($args[0]); break;
278
                case 'lower': $v = strtolower($args[0]); break;
279

    
280
                case 'count':
281
                case 'max':
282
                case 'min':
283
                case 'sum':
284
                case 'avg':
285
                    if ($q[0]['visited'] != $this->visited) {
286
                        $q[0]['visited'] = $this->visited;
287
                        if ($this->agr_reset || !isset($q[0]['agr-count'])) {
288
                            $q[0]['agr-count'] = 1;
289
                            $q[0]['agr-max'] = $args[0];
290
                            $q[0]['agr-min'] = $args[0];
291
                            $q[0]['agr-sum'] = $args[0];
292
                            $q[0]['agr-avg'] = $args[0];
293
                        } else {
294
                            $q[0]['agr-count'] += 1;
295
                            if ($args[0] > $q[0]['agr-max']) {
296
                                $q[0]['agr-max'] = $args[0];
297
                            }
298
                            if ($args[0] < $q[0]['agr-max']) {
299
                                $q[0]['agr-min'] = $args[0];
300
                            }
301
                            $q[0]['agr-sum'] += $args[0];
302
                            $q[0]['agr-avg'] = $q[0]['agr-sum'] / $q[0]['agr-count'];
303
                        }
304
                    }
305
                    $this->aggregates = 1;
306
                    $v = $q[0]['agr-'.$v];
307
                    break;
308
            }
309
            break;
310
            case SIMPLE:
311
                $v = $row[$v];
312
            case LITERAL:
313
                $v = $this->convert_literal($q['type'], $v);
314
        }
315
        if ($this->debug) {
316
            echo "{$q[TYPE]}:{$q[VALUE]}:{$q['type']} = $v\n";
317
        }
318
        return $v;
319
    }
320

    
321
    private function mktime($year,$month,$day,$hour,$minute,$second,$ms = 0)
322
    {
323
        $year = intval($year,10);
324
        $month = intval($month,10);
325
        $day = intval($day,10);
326
        $hour = intval($hour,10);
327
        $minute = intval($minute,10);
328
        $second = intval($second,10);
329
        return gmmktime($hour,$minute,$second,$month,$day,$year);
330
    }
331

    
332
    private function convert_literal($type, $v)
333
    {
334
        if ($v !== NULL) {
335
            switch ($type) {
336
                case 'date':
337
                    if (is_string($v) && preg_match('/^(....)-(..)-(..)( (..):(..):(..))?$/', $v, $matches)) {
338
                        $v = $this->mktime($matches[1],$matches[2],$matches[3], 0, 0, 0);
339
                    } else {
340
                        $v = (double)$v;
341
                    }
342
                    break;
343
                case 'timeofday':
344
                    if (is_string($v) && preg_match('/^((....)-(..)-(..) )?(..):(..):(..)$/', $v, $matches)) {
345
                        $v = $this->mktime(1971,1,1,$matches[5],$matches[6],$matches[7]);
346
                    } else {
347
                        $v = (double)$v;
348
                    }
349
                    break;
350
                case 'datetime':
351
                    if (is_string($v) && preg_match('/^(....)-(..)-(..) (..):(..):(..)$/', $v, $matches)) {
352
                        $v = $this->mktime($matches[1],$matches[2],$matches[3],
353
                                           $matches[4],$matches[5],$matches[6]);
354
                    } else {
355
                        $v = (double)$v;
356
                    }
357
                    break;
358
                case 'number':
359
                    $v = (double)$v;
360
                    break;
361
                case 'boolean':
362
                    if (is_string($v) && !strcasecmp($v, "false")) {
363
                        $v = FALSE;
364
                    } else {
365
                        $v = (bool)$v;
366
                    }
367
                    break;
368
            }
369
        }
370
        return $v;
371
    }
372

    
373
    private function value_convert($type, $v)
374
    {
375
        if ($v !== NULL) {
376
            switch ($type) {
377
                case 'date':
378
                    $a = $this->dateparts(array('year', 'month', 'day'), $v);
379
                    $m = $a[1]-1;
380
                    return "new Date({$a[0]},$m,{$a[2]})";
381
                case 'timeofday':
382
                    $a = $this->dateparts(array('hour', 'minute', 'second'), $v);
383
                    return "[{$a[0]},{$a[1]},{$a[2]}]";
384
                case 'datetime':
385
                    $a = $this->dateparts(array('year', 'month', 'day', 'hour', 'minute', 'second'), $v);
386
                    $m = $a[1]-1;
387
                    return "new Date({$a[0]},$m,{$a[2]},{$a[3]},{$a[4]},{$a[5]})";
388
            }
389
        }
390
        return $v;
391
    }
392

    
393
    protected function make_order($elist, $order, &$cols, &$exprs)
394
    {
395
        foreach ($elist as &$col) {
396
            $dir = isset($col['dir']) && $col['dir'] == 'desc' ? -1 : 1;
397
            $s = $this->write_expr($col);
398
            if (!isset($exprs[$s])) {
399
                $cols[] = array('id' => $s, 'label' => $s, 'type' => $col['type']);
400
                $exprs[$s] = $col;
401
            }
402
            $order[$s] = $dir;
403
        }
404
        return $order;
405
    }
406

    
407
    protected function setup_rownums($query, $total)
408
    {
409
        $this->total_rows = $total;
410
        $this->first_row = $query['offset'] ? $this->evaluate(NULL, $query['offset']) : 0;
411
        $this->num_rows = $query['limit'] ? $this->evaluate(NULL, $query['limit']) : $total;
412

    
413
        if ($total >= 0 && isset($this->params['pagenum']) && isset($this->params['pagerow'])) {
414
            $pr = intval($this->params['pagerow']);
415
            $pn = intval($this->params['pagenum']);
416
            $np = 1;
417
            if (isset($this->params['numpage'])) {
418
                $np = intval($this->params['numpage']);
419
            }
420
            if ($pr > 0 && $total > 0) {
421
                if ($pr > $total) $pr = $total;
422
                $mp = ceil($total / $pr);
423
                if ($pn > $mp) $pn = $mp;
424
                if ($pn < 1) $pn = 1;
425
                $this->first_row = ($pn - 1) * $pr;
426
                $this->num_rows = $pr * $np;
427
                if ($this->first_row + $this->num_rows > $total) {
428
                    $this->num_rows = $total-$this->first_row;
429
                }
430
                $this->page_num = floor($this->first_row / $pr) + 1;
431
                $this->total_pages = $mp;
432
            }
433
        }
434
    }
435

    
436
    public function query_filter(&$rows, $query)
437
    {
438
        $pivot_key = "pivot|key";
439
        $group_key = "group|key";
440

    
441
        $cols = array();
442
        $order = array();
443
        $exprs = array();
444
        foreach ($query['select'] as &$col) {
445
            $s = $this->write_expr($col);
446
            $c = array('id' => $s, 'label' => $s, 'type' => $col['type']);
447
            if ($col['type'] != 'string' && $col['format']) {
448
                $c['pattern'] = $col['format'];
449
            }
450
            if ($col['label']) {
451
                $c['label'] = $col['label'];
452
            }
453
            if ($col['is_pivot'] || $col['is_group']) {
454
                $c['group'] = 1;
455
            }
456
            if ($col['is_aggregate']) {
457
                $c['is_aggregate'] = 1;
458
            }
459
            $cols[] = $c;
460
            $exprs[$s] =& $col;
461
        }
462

    
463
        unset($col);
464

    
465
        $ncol = count($cols);
466
        if (isset($this->params['sortcol']) &&
467
            isset($exprs[$this->params['sortcol']]))
468
        {
469
            $order[$this->params['sortcol']] = !strcasecmp($this->params['sortdir'], 'desc') ? 'desc' : 'asc';
470
        }
471

    
472
        if (isset($query['order'])) {
473
            $order = $this->make_order($query['order'], $order, $cols, $exprs);
474
        } else if (isset($query['group'])) {
475
            $order = $this->make_order($query['group'], $order, $cols, $exprs);
476
        }
477

    
478
        /* If grouping/pivoting is required, match the rows to their groups */
479
        $groups = array();
480
        $ga = isset($query['group']) ? $query['group'] : array();
481
        $pa = isset($query['pivot']) ? $query['pivot'] : array();
482
        $porder = NULL;
483
        if ($pa || $ga) {
484
            if ($pa) {
485
                $porder = $this->make_order($query['pivot'], array(), $cols, $exprs);
486
            }
487
            foreach ($rows as $row) {
488
                if (!$query['where'] ||
489
                    $this->evaluate($row, $query['where'])) {
490
                    $gkey = "";
491
                    foreach ($ga as &$value) {
492
                        $k = $this->evaluate($row, $value);
493
                        $k = str_replace('|', '||', $k);
494
                        $gkey .= "$k|";
495
                    }
496
                    $pkey = "";
497
                    foreach ($pa as &$value) {
498
                        $k = $this->evaluate($row, $value);
499
                        $k = str_replace('|', '||', $k);
500
                        $pkey .= "$k|";
501
                    }
502
                    if ($pa) {
503
                        $row[$pivot_key] = $pkey;
504
                        $row[$group_key] = $gkey;
505
                    }
506
                    $groups[$pkey.$gkey][] = $row;
507
                }
508
            }
509
            $this->aggregates = 1;
510
        } else {
511
            if ($query['where']) {
512
                foreach ($rows as $row) {
513
                    if ($this->evaluate($row, $query['where'])) {
514
                        $groups[""][] = $row;
515
                    }
516
                }
517
            } else {
518
                $groups[""] = $rows;
519
            }
520
            $this->aggregates = 0;
521
        }
522

    
523
        /*
524
          Evaluate the rows by groups. Note that $this->aggregates may become
525
          true even if there are no groups, if any of the aggregation functions
526
          is used.
527
        */
528
        $rout = array();
529
        foreach ($groups as $pgkey => $grows) {
530
            $this->agr_reset = 1;
531
            foreach ($grows as $row) {
532
                $this->visited++;
533
                $r = array();
534
                $i = 0;
535
                foreach ($exprs as &$col) {
536
                    $v = $this->evaluate($row, $col);
537
                    $r[$cols[$i++]['id']] = $v;
538
                }
539
                if ($pa) {
540
                    $r[$pivot_key] = $row[$pivot_key];
541
                    $r[$group_key] = $row[$group_key];
542
                }
543
                if (!$this->aggregates) {
544
                    $rout[] = $r;
545
                }
546
                $this->agr_reset = 0;
547
            }
548
            if (!$this->agr_reset &&
549
                $this->aggregates &&
550
                (!$query['having'] ||
551
                 $this->evaluate($row, $query['having'])))
552
            {
553
                $rout[] = $r;
554
            }
555
        }
556

    
557
        unset($col);
558

    
559
        global $vistable_ordering;
560
        if ($pa) {
561
            $vistable_ordering = $porder;
562
            usort($rout, "vistable_order_function");
563

    
564
            $pivots = array();
565
            foreach ($rout as $row) {
566
                if (!isset($pivots[$row[$pivot_key]])) {
567
                    $pivots[$row[$pivot_key]] = 1;
568
                }
569
            }
570
        }
571

    
572
        if ($order) {
573
            $vistable_ordering = $order;
574
            usort($rout, "vistable_order_function");
575
        }
576

    
577
        if (count($cols) > $ncol) {
578
            $cols = array_slice($cols, 0, $ncol);
579
            foreach ($rout as &$row) {
580
                $pk = $row[$pivot_key];
581
                $gk = $row[$group_key];
582
                $row = array_slice($row, 0, $ncol, TRUE);
583
                if ($pa) {
584
                    $row[$pivot_key] = $pk;
585
                    $row[$group_key] = $gk;
586
                }
587
            }
588
            unset($row);
589
        }
590

    
591
        if ($pa) {
592
            $groups = array();
593

    
594
            foreach ($rout as $row) {
595
                $groups[$row[$group_key]][$row[$pivot_key]] = $row;
596
            }
597

    
598
            $nrows = array();
599
            $ncols = array();
600

    
601
            // Copy "grouped" columns to new col array
602
            foreach ($cols as $col) {
603
                if (!$col['is_aggregate']) {
604
                    $ncols[] = $col;
605
                }
606
            }
607

    
608
            // Create pivoted columns for non-"grouped" columns
609
            foreach ($pivots as $pivot => $value) {
610
                foreach ($cols as $col) {
611
                    if ($col['is_aggregate']) {
612
                        $col['id'] = $pivot.$col["id"];
613
                        $col['label'] = $pivot.$col["label"];
614
                        $ncols[] = $col;
615
                    }
616
                }
617
            }
618

    
619
            foreach ($groups as $gkey => $rs) {
620
                $nrow = array();
621
                // get the "grouped" elements of the row
622
                foreach ($cols as $col) {
623
                    if (!$col['is_aggregate']) {
624
                        foreach ($rs as $row) {
625
                            $nrow[] = $row[$col['id']];
626
                            break;
627
                        }
628
                    }
629
                }
630
                // and now the "pivoted" elements
631
                foreach ($pivots as $pivot => $value) {
632
                    $row = isset($rs[$pivot]) ? $rs[$pivot] : NULL;
633
                    foreach ($cols as $col) {
634
                        if ($col['is_aggregate']) {
635
                            $nrow[] = $row !== NULL ? $row[$col['id']] : NULL;
636
                        }
637
                    }
638
                }
639

    
640
                $nrows[] = $nrow;
641
            }
642

    
643
            $cols = $ncols;
644
            $rout = $nrows;
645
        }
646

    
647
        $this->setup_rownums($query, count($rout));
648

    
649
        $rows = array_slice($rout, $this->first_row, $this->num_rows);
650
        return $cols;
651
    }
652

    
653
    public function execute()
654
    {
655
        $table = NULL;
656

    
657
        $outfmt = "json";
658
        if (isset($this->params["out"])) {
659
            $outfmt = $this->params["out"];
660
        }
661
        if ($outfmt == 'jqgrid' || $outfmt == 'jqgrid-xml') {
662
            $this->needs_total_rows = TRUE;
663
        }
664

    
665
        if ($this->response['status'] != 'error') {
666

    
667
            $parser = new visparser($this->fields);
668
            $this->query = $parser->parse($this->tq);
669

    
670
            if ($this->debug) {
671
                print "tq: $tq\n";
672
                print_r($parser);
673
            }
674

    
675
            if (!$this->query) {
676
                $this->error('invalid_query', "", $parser->error_message);
677
            } else {
678
                $table = $this->fetch_table($this->query);
679
            }
680
        }
681

    
682
        if ($table) {
683
            $no_values = isset($this->query["options"]["no_values"]);
684
            $no_format = isset($this->query["options"]["no_format"]);
685
            if ($outfmt != "json") {
686
                $no_values = true;
687
                $no_format = false;
688
            }
689
            $rows = array();
690
            $cols = $table['cols'];
691
            $formatters = array('date' => new DateFormatter($this->locale, $this->tz,
692
                                                            'yyyy-MM-dd'),
693
                                'timeofday' => new DateFormatter($this->locale, $this->tz,
694
                                                                 'HH:mm:ss'),
695
                                'datetime' => new DateFormatter($this->locale, $this->tz,
696
                                                                'yyyy-MM-dd HH:mm:ss'));
697

    
698
            foreach ($cols as &$colref) {
699
                if (isset($colref['pattern'])) {
700
                    switch ($colref['type']) {
701
                    case 'number':
702
                        $colref['fmt'] = new NumberFormatter($this->locale,
703
                                                             NumberFormatter::PATTERN_DECIMAL,
704
                                                             $colref['pattern']);
705
                        if ($this->debug) {
706
                            print_r($colref['fmt']);
707
                        }
708
                        break;
709
                    case 'date':
710
                    case 'datetime':
711
                    case 'timeofday':
712
                        $colref['fmt'] = new DateFormatter($this->locale, $this->tz,
713
                                                           $colref['pattern']);
714
                        if ($this->debug) {
715
                            print_r($colref['fmt']);
716
                        }
717
                        break;
718
                    case 'boolean':
719
                        $colref['fmt'] = new BoolFormatter($colref['pattern']);
720
                        if ($this->debug) {
721
                            print_r($colref['fmt']);
722
                        }
723
                        break;
724
                    }
725
                } else if (isset($formatters[$colref['type']])) {
726
                    $colref['fmt'] = $formatters[$colref['type']];
727
                }
728
            }
729

    
730
            foreach ($table['rows'] as $row) {
731
                $r = array();
732
                foreach ($row as $key => $value) {
733
                    $val = $v = $f = $value;
734
                    $c = count($r);
735
                    $col = $cols[$c];
736
                    $type = $col['type'];
737
                    $v = $this->convert_literal($type, $v);
738
                    if ($v === NULL) {
739
                        $a = NULL;
740
                    } else {
741
                        $a = array();
742
                        if (!$no_values) {
743
                            $a['v'] = $this->value_convert($type,$v);
744
                        }
745
                        if (!$no_format) {
746
                            if ($col['fmt']) {
747
                                $f = $col['fmt']->format($v);
748
                            }
749
                            $a['f'] = $f;
750
                        }
751
                    }
752
                    $r[] = $a;
753
                }
754
                $rows[] = array('c'=>$r);
755
            }
756
            $table['rows'] = $rows;
757
            $this->response['table'] = $table;
758
        }
759

    
760
        $sig = json_encode($this->response);
761
        if ($this->needs_total_rows) {
762
            $sig .= ":".$this->total_rows.":".$this->first_row;
763
        }
764

    
765
        $sig = md5($sig);
766

    
767
        if ($sig == $this->params['sig']) {
768
            $this->error('not_modified', '', '');
769
        }
770

    
771
        unset($this->response['table']);
772
        $this->response['version'] = $this->params['version'];
773
        if (isset($this->params['reqId'])) {
774
            $this->response['reqId'] = $this->params['reqId'];
775
        }
776

    
777
        $this->response['sig'] = $sig;
778
       
779
        if ($this->response['status'] == 'error') {
780
            $table = NULL;
781
            unset($this->response['warnings']);
782
        }
783
        if ($table) {
784
            $this->response['table'] = $table;
785
        }
786

    
787
        if ($this->debug) {
788
            $outfmt = "debug";
789
        }
790

    
791
        $out = "";
792
        switch ($outfmt) {
793
        case 'json':
794
            header('Content-type: text/plain; charset="UTF-8"');
795

    
796
            $out = json_encode($this->response);
797
            $out = preg_replace('/"(new Date\(.*?\))"/', "$1", $out);
798
            $out = preg_replace('/([\{,])"([A-Za-z_][A-Za-z0-9_]*)"/', "$1$2", $out);
799
            $out = $this->params['responseHandler']."($out);\n";
800
            break;
801
        case 'csv':
802
            if (isset($this->params['outFileName'])) {
803
                header('Content-type: text/csv; charset="UTF-8"');
804
                header('Content-disposition: attachment; filename='.$this->params['outFileName']);
805
            } else {
806
                header('Content-type: text/plain; charset="UTF-8"');
807
            }
808

    
809
            if ($table) {
810
                $out = self::csv_row($table['cols'], "label");
811
                foreach ($table['rows'] as $row) {
812
                    $out .= self::csv_row($row['c'], 'f');
813
                }
814
            }
815
            break;
816
        case 'html':
817
            header('Content-type: text/html; charset="UTF-8"');
818

    
819
            $out = "<html><body><table border='1' cellpadding='2' cellspacing='0'>";
820
            if ($this->response['status'] != 'ok') {
821
                if (isset($this->response['errors'])) {
822
                    $out .= self::html_diagnostic($this->response['errors'],"#f00");
823
                }
824
                if (isset($this->response['warnings'])) {
825
                    $out .= self::html_diagnostic($this->response['warnings'],"#ff0");
826
                }
827
                $out .= "</table><table border='1' cellpadding='2' cellspacing='0'>";
828
            }
829
            if ($table) {
830
                $out .= self::html_row($table['cols'], 'label', 'font-weight: bold; background-color: #aaa;');
831
                $colors = array('#f0f0f0','#ffffff');
832
                $cix = 0;
833
                foreach ($table['rows'] as $row) {
834
                    $out .= self::html_row($row['c'], 'f', 'background-color: '.$colors[$cix]);
835
                    $cix ^= 1;
836
                }
837
            }
838
            $out .= "</table></body></html>";
839
            break;
840
        case 'tsv-excel':
841
            if (isset($this->params['outFileName'])) {
842
                header('Content-type: text/tab-separated-values; charset="UTF-16"');
843
                header('Content-disposition: attachment; filename='.$this->params['outFileName']);
844
            } else {
845
                header('Content-type: text/plain; charset="UTF-16"');
846
            }
847
            if ($table) {
848
                $out = self::tsv_row($table['cols'], "label");
849
                foreach ($table['rows'] as $row) {
850
                    $out .= self::tsv_row($row['c'], 'f');
851
                }
852
            }
853
            break;
854
        case 'jqgrid':
855
            header('Content-type: text/json; charset="UTF-8"');
856
            $out = array("records" => $this->total_rows);
857
            if ($this->num_rows > 0) {
858
                $out['page'] = $this->page_num;
859
                $out['total'] = $this->total_pages;
860
                $out['num_rows'] = $this->num_rows;
861
            } else {
862
                $out['page'] = 0;
863
                $out['total'] = 1;
864
            }
865

    
866
            $rows = array();
867
            foreach ($table['rows'] as $row) {
868
                $r = array();
869
                foreach ($row['c'] as $c) {
870
                    array_push($r, $c['f']);
871
                }
872
                array_push($rows, $r);
873
            }
874
            $out['rows'] = $rows;
875

    
876
            $out = json_encode($out);
877
            break;
878
        case 'jqgrid-xml':
879
            header('Content-type: application/xml; charset="UTF-8"');
880
            $out = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>';
881
            $page = $this->num_rows > 0 ? $this->page_num : 0;
882
            $total = $this->num_rows > 0 ? $this->total_pages : 1;
883
            $out .= "<jqgrid><rows>";
884
            $out .= "<records>{$this->total_rows}</records>";
885
            $out .= "<page>{$page}</page>";
886
            $out .= "<total>{$total}</total>";
887
            $out .= "<num_rows>{$this->num_rows}</num_rows>";
888
            $rows = array();
889
            foreach ($table['rows'] as $row) {
890
                $out .= "<row>";
891
                foreach ($row['c'] as $c) {
892
                    $out .= "<cell>".htmlspecialchars($c['f'], ENT_COMPAT, "UTF-8")."</cell>";
893
                }
894
                $out .= "</row>";
895
            }
896
            $out .= "</rows></jqgrid>";
897
            break;
898

    
899
        case 'jqgrid-config':
900
            header('Content-type: text/plain; charset="UTF-8"');
901
            $colmodel = array();
902
            foreach ($table['cols'] as $col) {
903
                $c = array('label' => $col['label'],
904
                           'name' => $col['id']);
905
                switch ($col['type']) {
906
                case 'number':
907
                    $c['align'] = 'right';
908
                    $c['sortorder'] = 'float';
909
                    break;
910
                case 'date':
911
                    $c['sortorder'] = 'date';
912
                    break;
913
                }
914
                array_push($colmodel, $c);
915
            }
916

    
917
            $out = array("jsonReader" => array("root" => "rows",
918
                                             "page" => "page",
919
                                             "total" => "total",
920
                                             "records" => "records",
921
                                             "cell" => "",
922
                                             "id" => "0"),
923
                         "colModel" => $colmodel
924
                         );
925

    
926
            $out = json_encode($out);
927
            break;
928

    
929
        case 'debug':
930
            header('Content-type: text/plain; charset="UTF-8"');
931
            ob_start();
932
            var_dump($this->response);
933
            $out=ob_get_contents();
934
            ob_end_clean();
935
            break;
936
        }
937

    
938
        return $out;
939
    }
940

    
941
    private static function csv_row($r, $id)
942
    {
943
        return self::sv_row($r,$id,',');
944
    }
945

    
946
    private static function tsv_row($r, $id)
947
    {
948
        return iconv("UTF-8", "UTF-16", self::sv_row($r,$id,"\t"));
949
    }
950

    
951
    private static function sv_row($r, $id,$sep)
952
    {
953
        $out = array();
954
        foreach ($r as $v) {
955
            if (!$v) {
956
                $x = "";
957
            } else {                
958
                $x = $v[$id];
959
            }
960
            if (strpbrk($x, '"'.$sep)) {
961
                $x = str_replace('"','""',$x);
962
                $x = '"'.$x.'"';
963
            }
964
            $out[] = $x;
965
        }
966
        return implode($sep, $out)."\n";
967
    }
968

    
969
    private static function html_diagnostic($diagnostics, $color)
970
    {
971
        $out = "";
972
        foreach ($diagnostics as $diag) {
973
            $out .= "<tr style='background-color: $color'>";
974
            $out .= "<td>{$diag['reason']}</td>";
975
            $msg = isset($diag['message']) ? $diag['message'] : "&nbsp;";
976
            $out .= "<td>$msg</td>";
977
            $msg = isset($diag['detailed_message']) ? $diag['detailed_message'] : "&nbsp;";
978
            $out .= "<td>$msg</td>";
979
            $out .= "</tr>";
980
        }
981
        return $out;
982
    }
983

    
984
    private static function html_row($r, $id, $style)
985
    {
986
        $out = "<tr style='$style'>";
987
        foreach ($r as $v) {
988
            if (!$v) {
989
                $x = "";
990
            } else {                
991
                $x = $v[$id];
992
                if ($x == "") {
993
                    $x = "";
994
                }
995
            }
996
            if ($x == "") {
997
                $x = "&nbsp;";
998
            } else {
999
                $x = htmlspecialchars($x, ENT_COMPAT, "UTF-8");
1000
            }
1001
            $out .= "<td>$x</td>";
1002
        }
1003
        return $out."\n";
1004
    }
1005
};
1006

    
1007
class mysql_vistable extends vistable {
1008
    private $tables;
1009
    private $mode = 0;
1010
    private $where = null;
1011

    
1012
    public function __construct($tqx,$tq,$tqrt,$tz,$locale,$extra=NULL) {
1013
        parent::__construct($tqx,$tq,$tqrt,$tz,$locale,$extra);
1014
    }
1015
       
1016
    public function setup_database($tables, $fields, $where)
1017
    {
1018
        $this->tables = $tables;
1019
        $this->fields = $fields;
1020
        $this->where = $where;
1021

    
1022
        $q = "SELECT";
1023
        $fields = array();
1024
        foreach ($this->fields as $key => $value) {
1025
            $f = "";
1026
            $sql_field = NULL;
1027
            if (is_string($value)) {
1028
                $sql_field = $value;
1029
            }
1030

    
1031
            if (is_array($value)) {
1032
                if (isset($value["sql_field"])) {
1033
                    $sql_field = $value["sql_field"];
1034
                }
1035
                $value[TYPE] = SIMPLE;
1036
                $value[VALUE] = $key;
1037
                $this->fields[$key] = $value;
1038
            } else {
1039
                $this->fields[$key] = array(TYPE=>SIMPLE, VALUE=>$key);
1040
            }
1041
            if ($sql_field) {
1042
                $f .= " $sql_field AS";
1043
                $this->fields[$key]["sql_field"] = $sql_field;
1044
            }
1045
            $f .= " $key";
1046
            $fields[] = $f;
1047
        }
1048
        $q .= implode(",",$fields)." FROM ".$this->tables;
1049
        $q .= " LIMIT 0";
1050
        $result = mysql_query($q);
1051
        if ($result) {
1052
            $sql_types = array(
1053
                               "number" => "INT|FLOAT|DOUBLE|REAL|DECIMAL|NUMERIC|BIT",
1054
                               "boolean" => "BOOL",
1055
                               "date" => '^DATE$',
1056
                               "timeofday" => '^TIME$',
1057
                               "datetime" => '^DATETIME|TIMESTAMP$',
1058
                               );
1059
            $ncol = mysql_num_fields($result);
1060
            for ($i=0; $i < $ncol; $i++) {
1061
                $fname = mysql_field_name($result, $i);
1062
                if (!isset($this->fields[$fname]['type'])) {
1063
                    $ftype = mysql_field_type($result, $i);
1064
                    $type = 'string';
1065
                    foreach ($sql_types as $t => $pat) {
1066
                        if (preg_match("/$pat/i", $ftype)) {
1067
                            $type = $t;
1068
                            break;
1069
                        }
1070
                    }
1071
                    $this->fields[$fname]['type'] = $type;
1072
                }
1073
                if (!isset($this->fields[$fname]['label'])) {
1074
                    $this->fields[$fname]['label'] = $fname;
1075
                }
1076
            }
1077
        }
1078
    }
1079

    
1080
    protected function pre_write($q)
1081
    {
1082
        $v = $q[VALUE];
1083
        if ($this->mode != 0) {
1084
            switch ($q[TYPE]) {
1085
                case OPERATOR:
1086
                    switch ($v) {
1087
                        case "matches":
1088
                            $e0 = $this->write_expr($q[0]);
1089
                            $e1 = $this->write_expr($q[1]);
1090
                            return "$e0 REGEXP $e1";
1091
                        case "contains":
1092
                            $r = $this->write_func("LOCATE", $q, TRUE);
1093
                            $r .= "!=0";
1094
                            return $r;
1095
                        case "starts_with":
1096
                            $r = $this->write_func("LOCATE", $q, TRUE);
1097
                            $r .= "=1";
1098
                            return $r;
1099
                        case "ends_with":
1100
                            $e0 = $this->write_expr($q[0]);
1101
                            $e1 = $this->write_expr($q[1]);
1102
                            return "RIGHT($e0,LENGTH($e1))=$e1";
1103
                    }
1104
                case FUNCT:
1105
                    switch ($v) {
1106
                        case 'timeofday':
1107
                            return $this->write_func("time", $q);
1108
                        case 'datetime':
1109
                            return "(".$this->write_expr($q[0]).")";
1110
                        case 'date':
1111
                        case 'todate':
1112
                            return $this->write_func("date", $q);
1113
                        case 'now':
1114
                            if ($this->gmt_offset) {
1115
                                return "(now()+INTERVAL "+$this->gmt_offset+" SECOND)";
1116
                            }
1117
                            break;
1118
                    }
1119
                    break;
1120
                case SIMPLE:
1121
                    if (isset($q["sql_field"])) {
1122
                        return $q["sql_field"];
1123
                    } else {
1124
                        return $v;
1125
                    }
1126
            }
1127
        }
1128
        return NULL;
1129
    }
1130

    
1131
    private function sql_expr($q, $mode)
1132
    {
1133
        $this->mode = $mode;
1134
        $ret = $this->write_expr($q);
1135
        $this->mode = 0;
1136
        return $ret;
1137
    }
1138

    
1139
    private function vis_query2sql_query($query, &$cols)
1140
    {
1141
        $fields = array();
1142
        $cols = array();
1143
        $order = array();
1144
        foreach ($query['select'] as $value) {
1145
            $f = $this->sql_expr($value, 1);
1146
            $as = $this->sql_expr($value, 0);
1147
            $type = isset($value['type']) ? $value['type'] : 'string';
1148
            if ($f != $as) {
1149
                $f .= " AS `$as`";
1150
            }
1151
            $label = isset($value['label']) ? $value['label'] : $as;
1152
            $fields[$as] = $f;
1153
            $col = array('id' => $as, 'label' => $label, 'type' => $type);
1154
            if ($type != 'string' && isset($value['format'])) {
1155
                $col['pattern'] = $value['format'];
1156
            }
1157
            $cols[] = $col;
1158
        }
1159

    
1160
        if ($this->debug) {
1161
            echo "\n\nsortcol: ",$this->params['sortcol'],"\n\n\n";
1162
        }
1163
        if (isset($this->params['sortcol']) &&
1164
            isset($fields[$this->params['sortcol']]))
1165
        {
1166
            $order[] = "`".$this->params['sortcol']."` ".(!strcasecmp($this->params['sortdir'], 'desc') ? 'desc' : 'asc');
1167
        }
1168

    
1169
        if (isset($query['order'])) {
1170
            foreach ($query['order'] as $value) {
1171
                $dir = isset($value['dir']) && $value['dir'] == 'desc' ? 'desc' : 'asc';
1172
                $as = $this->sql_expr($value, 0);
1173
                if (!isset($fields[$as])) {
1174
                    $o = $this->sql_expr($value,1);
1175
                } else {
1176
                    $o = "`$as`";
1177
                }
1178
                $order[] = "$o $dir";
1179
            }
1180
        }
1181

    
1182
        $select = "SELECT " . implode(",",$fields);
1183
        $q = " FROM ".$this->tables;
1184
        if ($query['where']) {
1185
            $q .= " WHERE(".$this->sql_expr($query['where'], 1).")";
1186
            if ($this->where) {
1187
                $q .= "AND(".$this->where.")";
1188
            }
1189
        } else if ($this->where) {
1190
            $q .= " WHERE ".$this->where;
1191
        }
1192

    
1193
        if ($query['group']) {
1194
            $q .= " GROUP BY ";
1195
            $fields = array();
1196
            foreach ($query['group'] as $value) {
1197
                $fields[] = $this->sql_expr($value, 1);
1198
            }
1199
            $q .= implode(",", $fields);
1200
        }
1201
        if ($query['having']) {
1202
            $q .= " HAVING ".$this->sql_expr($query['having'], 1);
1203
        }
1204

    
1205
        $total = -1;
1206
        if ($this->needs_total_rows) {
1207
            $t = "SELECT count(*)";
1208
            if ($query['group']) {
1209
                $t .= " FROM ($select$q) AS t1";
1210
            } else {
1211
                $t .= $q;
1212
            }
1213
            $data = mysql_query($t);
1214
            if (!$data || !($t = mysql_fetch_row($data))) {
1215
                $this->error("internal_error", "query_failed", "query `$t' failed:".mysql_error());
1216
                return FALSE;
1217
            }
1218
            $total = (int)$t[0];
1219
        }
1220
        $this->setup_rownums($query, $total);
1221

    
1222
        if (count($order)) {
1223
            $q .= " ORDER BY ".implode(",",$order);
1224
        }
1225

    
1226
        $q = $select . $q;
1227

    
1228
        if ($this->num_rows >= 0 || $this->first_row > 0) {
1229
            $o = $this->first_row;
1230
            $l = $this->num_rows >= 0 ? $this->num_rows : 1000000000;
1231
            $q .= " LIMIT $o,$l";
1232
        }
1233

    
1234
        if ($this->debug) {
1235
            echo "\n\nQ: $q\n\n\n";
1236
        }
1237
        return $q;
1238
    }
1239

    
1240
    protected function fetch_table($query)
1241
    {
1242
        $use_query = isset($query['pivot']) || $this->params['nosql'];
1243
        if ($use_query) {
1244
            $q = "SELECT";
1245
            $fields = array();
1246
            foreach ($this->fields as $key => $value) {
1247
                $f = "";
1248
                if (isset($value["sql_field"])) {
1249
                    $f .= " {$value["sql_field"]} AS";
1250
                }
1251
                $f .= " $key";
1252
                $fields[] = $f;
1253
            }
1254
            $q .= implode(",",$fields)." FROM ".$this->tables;
1255
            if ($query['where']) {
1256
                $q .= " WHERE ".$this->sql_expr($query['where'], 1);
1257
            }
1258
        } else {
1259
            $q = $this->vis_query2sql_query($query, $cols);
1260
            if ($q === FALSE) return FALSE;
1261
        }
1262

    
1263
        $data = mysql_query($q);
1264

    
1265
        if (!$data) {
1266
            $this->error("internal_error", "query failed", "query `$q' failed:".mysql_error());
1267
            return FALSE;
1268
        }
1269

    
1270
        $rows = array();
1271
        while ($row = mysql_fetch_assoc($data)) {
1272
            $rows[] = $row;
1273
        }
1274

    
1275
        if ($use_query) {
1276
            $cols = $this->query_filter($rows, $query);
1277
        }
1278

    
1279
        return array('cols' => $cols, 'rows' => $rows);
1280
    }
1281
};
1282

    
1283
class csv_vistable extends vistable {
1284
    private $table;
1285

    
1286
    public function __construct($tqx,$tq,$tqrt,$tz,$locale,$extra=NULL) {
1287
        parent::__construct($tqx,$tq,$tqrt,$tz,$locale,$extra);
1288
    }
1289
       
1290
    public function setup_table($data)
1291
    {
1292
        $this->fields = array();
1293
        $row = $this->next_row($data);
1294
        if ($row === NULL) return;
1295
        foreach ($row as &$id) {
1296
            $type = 'string';
1297
            if (preg_match('/^(.*) as (date|datetime|boolean|timeofday|number)$/',$id,$matches)) {
1298
                $id = $matches[1];
1299
                $type = $matches[2];
1300
            }
1301
            $this->fields[$id] = array(TYPE=>SIMPLE, VALUE=>$id, 'type' => $type);
1302
        }
1303
        $cols = $row;
1304
        $this->table = array();
1305
        while (($row = $this->next_row($data)) !== FALSE) {
1306
            if (count($row) > count($cols)) continue;
1307
            $this->table[] = array_combine(array_slice($cols,0,count($row)), $row);
1308
        }
1309
    }
1310

    
1311
    protected function fetch_table($query)
1312
    {
1313
        $rows = $this->table;
1314

    
1315
        if (!$rows) {
1316
            $this->error("internal_error", "no data", "");
1317
            return FALSE;
1318
        }
1319

    
1320
        $cols = $this->query_filter($rows, $query);
1321

    
1322
        return array('cols' => $cols, 'rows' => $rows);
1323
    }
1324

    
1325
    private function next_row(&$data)
1326
    {
1327
        if (preg_match('/^(([^\"\n]*(\"[^\"]*\"))*[^\"\n]*)\n(.*)$/s',$data,$matches)) {
1328
            $data=$matches[4];
1329
            $ret = $matches[1];
1330
        } else if ($data !== "") {
1331
            $ret = $data;
1332
            $data = "";
1333
        } else {
1334
            return FALSE;
1335
        }
1336

    
1337
        $ret .= ",";
1338
        $row = array();
1339
        while ($ret !== "") {
1340
            if (preg_match('/^\s*\"(([^\"]*\"\")*[^\"]*)\"\s*,(.*)$/', $ret, $matches)) {
1341
                $ret = $matches[3];
1342
                $row[] = $matches[1];
1343
            } else if (preg_match('/^([^,]*),(.*)$/', $ret, $matches)) {
1344
                $ret = $matches[2];
1345
                $row[] = $matches[1];
1346
            } else {
1347
                return FALSE;
1348
            }
1349
        }
1350

    
1351
        return $row;
1352
    }
1353
};
(3-3/3)