Evaluate.php 4.85 KB
Newer Older
Carsten  Rose's avatar
Carsten Rose committed
1
2
3
4
5
6
7
8
9
10
11
<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 1/12/16
 * Time: 4:36 PM
 */

namespace qfq;

use qfq;
12
use qfq\Store;
Carsten  Rose's avatar
Carsten Rose committed
13
14
15
16

require_once(__DIR__ . '/../qfq/store/Store.php');
require_once(__DIR__ . '/../qfq/Database.php');

Carsten  Rose's avatar
Carsten Rose committed
17
18
19
20
/**
 * Class Evaluate
 * @package qfq
 */
Carsten  Rose's avatar
Carsten Rose committed
21
22
23
24
25
26
27
28
class Evaluate {
    private $store = null;
    private $db = null;
    private $startDelimiter = '';
    private $startDelimiterLength = 0;
    private $endDelimiter = '';
    private $endDelimiterLength = 0;
    private $sqlKeywords = array('SELECT ', 'INSERT ', 'DELETE ', 'UPDATE ', 'SHOW ');
29
    private $debugStack = array();
Carsten  Rose's avatar
Carsten Rose committed
30
31
32
33
34
35
36
37
38
39
40
41


    public function __construct(Store $store, Database $db, $startDelimiter = '{{', $endDelimiter = '}}') {
        $this->store = $store;
        $this->db = $db;
        $this->startDelimiter = $startDelimiter;
        $this->startDelimiterLength = strlen($startDelimiter);
        $this->endDelimiter = $endDelimiter;
        $this->endDelimiterLength = strlen($endDelimiter);
    }

    /**
42
     * Evaluate a whole array or a array of arrays.
43
44
45
46
     *
     * @param $tokenArray
     * @return mixed
     */
47
    public function parseArray($tokenArray, $flagDebug = false) {
48
49
50
        $arr = array();

        foreach ($tokenArray as $key => $value) {
51
52
53
54
55
            if (is_array($value)) {
                $arr[] = $this->parseArray($value);
            } else {
                $arr[$key] = $this->parse($value);
            }
56
57
58
59
60
61
62
63
64
        }

        return $arr;
    }

    /**
     * Recursive evaluation of 'line'.
     * Token to replaces have to be enclosed by '{{' and '}}'
     *
Carsten  Rose's avatar
Carsten Rose committed
65
     * @param $line
66
67
     * @param int $recursion
     * @return array|mixed|null|string
68
     * @throws UserException
Carsten  Rose's avatar
Carsten Rose committed
69
70
71
72
     */
    public function parse($line, $recursion = 0) {

        if ($recursion > 4) {
73
            throw new qfq\UserException("Recursion too deep: $line", ERROR_RECURSION_TOO_DEEP);
Carsten  Rose's avatar
Carsten Rose committed
74
75
76
77
78
79
        }

        $posFirstClose = strpos($line, $this->endDelimiter);

        while ($posFirstClose !== false) {

80
81
            $this->debugStack[] = $line . " [$recursion]";

Carsten  Rose's avatar
Carsten Rose committed
82
83
            $posMatchOpen = strrpos(substr($line, 0, $posFirstClose), $this->startDelimiter);
            if ($posMatchOpen === false) {
84
                throw new \qfq\UserException("Missing open delimiter: $line", ERROR_MISSING_OPEN_DELIMITER);
Carsten  Rose's avatar
Carsten Rose committed
85
86
87
88
89
90
91
            }

            $pre = substr($line, 0, $posMatchOpen);
            $post = substr($line, $posFirstClose + $this->endDelimiterLength);
            $match = substr($line, $posMatchOpen + $this->startDelimiterLength, $posFirstClose - $posMatchOpen - $this->startDelimiterLength);

            $evaluated = $this->substitute($match);
92
            $this->debugStack[] = $line . " [$recursion]";
Carsten  Rose's avatar
Carsten Rose committed
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114

            // If an array is returned, break everything and return this assoc array.
            if (is_array($evaluated)) {
                return $evaluated;
            }

            // More to substitute in the new evaluated result? Start recursion just with the new result..
            if (strpos($evaluated, $this->endDelimiter) !== false) {
                $evaluated = $this->parse($evaluated, $recursion + 1);
            }

            $line = $pre . $evaluated . $post;
            $posFirstClose = strpos($line, $this->endDelimiter);

        }

        return $line;
    }

    /**
     * Tries to substitute $token.
     * Token might be
115
116
117
     *   a) fetch from a store. Syntax: 'form', 'form:C', 'form:SC0', 'form:S:ALNUMX'
     *   b) a SQL statement to fire
     * The token have to be _without_ Delimiter '{{' / '}}'
Carsten  Rose's avatar
Carsten Rose committed
118
119
120
121
     * If neither a) or b) match, return the token itself, surrounded by single ticks, to emphase that substition failed.
     *
     * @param $token
     * @return array|mixed|null|string
122
     * @throws DbException
Carsten  Rose's avatar
Carsten Rose committed
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
     */
    public function substitute($token) {
        $sqlMode = ROW_IMPLODE_ALL;

        $token = trim($token);

        // just to extract the first token: check if this is a SQL Statement
        $arr = explode(' ', $token, 2);

        if ($token[0] === '!') {
            $token = substr($token, 1);
            $arr[0] = substr($arr[0], 1);
            $sqlMode = ROW_REGULAR;
        }

        // SQL Statement?
        if (in_array(strtoupper($arr[0] . ' '), $this->sqlKeywords)) {
            return $this->db->sql($token, $sqlMode);
        }

        // explode for: <key>:<store priority>:<sanatize class>
        $arr = explode(':', $token, 3);
        if (!isset($arr[1]))
            $arr[1] = null;
        if (!isset($arr[2]))
            $arr[2] = null;


        // search for value in stores
        $value = $this->store->getVar($arr[0], $arr[1], $arr[2]);

        // nothing replaced: put ticks around, to sanatize strings for SQL statements. Nothing to substitute is not a wished situation.
        return ($value === false) ? "'" . $token . "'" : $value;
    }
157
158
159
160

    public function getDebug() {
        return '<pre>' . implode("\n", $this->debugStack) . '</pre>';
    }
161
}