Evaluate.php 4.01 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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
<?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);
    }

    /**
     * @param $line
     * @throws UserException
     */
    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
     *   a) fetched from a store. Syntax: 'form', 'form:C', 'form:SC0', 'form:S:ALNUMX'
     *   b) Result of a fired SQL statement.
     * 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;
    }
}

// Hallo {{SELECT id FROM Form ='{{form}}'}} wird {{SELECT {{SHOW Table}} FROM id={{r}} }} NEW {{SQL}} ENDE