Evaluate.php 4.58 KB
Newer Older
Carsten  Rose's avatar
Carsten Rose committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 1/12/16
 * Time: 4:36 PM
 */

namespace qfq;

use qfq;
use qfq\store\Store;

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


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 ');


    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);
    }

    /**
38
     * Evaluate a whole array or a array of arrays.
39
40
41
42
43
44
45
46
     *
     * @param $tokenArray
     * @return mixed
     */
    public function parseArray($tokenArray) {
        $arr = array();

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

        return $arr;
    }

    /**
     * Recursive evaluation of 'line'.
     * Token to replaces have to be enclosed by '{{' and '}}'
     *
Carsten  Rose's avatar
Carsten Rose committed
61
     * @param $line
62
63
64
     * @param int $recursion
     * @return array|mixed|null|string
     * @throws exceptions\UserException
Carsten  Rose's avatar
Carsten Rose committed
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
     */
    public function parse($line, $recursion = 0) {

        if ($recursion > 4) {
            throw new qfq\exceptions\UserException("Recursion too deep: $line", ERROR_RECURSION_TOO_DEEP);
        }

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

        while ($posFirstClose !== false) {

            $posMatchOpen = strrpos(substr($line, 0, $posFirstClose), $this->startDelimiter);
            if ($posMatchOpen === false) {
                throw new \qfq\exceptions\UserException("Missing open delimiter: $line", ERROR_MISSING_OPEN_DELIMITER);
            }

            $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);

            // 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
108
109
110
     *   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
111
112
113
114
115
116
117
118
119
120
121
122
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
     * 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
     * @throws exceptions\DbException
     */
    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;
    }
150
}