Passed
Push — main ( c57f14...b113cb )
by Thierry
02:36
created

QueryInputTrait::processInput()   B

Complexity

Conditions 9
Paths 8

Size

Total Lines 27
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 18
c 1
b 0
f 0
nc 8
nop 2
dl 0
loc 27
rs 8.0555
1
<?php
2
3
namespace Lagdo\DbAdmin\Db\Traits;
4
5
use Lagdo\DbAdmin\Driver\Entity\TableFieldEntity;
6
7
use function file_get_contents;
8
use function substr;
9
use function function_exists;
10
use function iconv;
11
use function json_decode;
12
use function is_array;
13
use function preg_match;
14
use function is_string;
15
use function array_sum;
16
17
trait QueryInputTrait
18
{
19
    /**
20
     * Get INI boolean value
21
     *
22
     * @param string $ini
23
     *
24
     * @return bool
25
     */
26
    abstract public function iniBool(string $ini): bool;
27
28
    /**
29
     * Escape or unescape string to use inside form []
30
     *
31
     * @param string $idf
32
     * @param bool $back
33
     *
34
     * @return string
35
     */
36
    abstract public function bracketEscape(string $idf, bool $back = false): string;
37
38
    /**
39
     * @param array $file
40
     * @param string $key
41
     * @param bool $decompress
42
     *
43
     * @return string
44
     */
45
    private function readFileContent(array $file, string $key, bool $decompress): string
46
    {
47
        $name = $file['name'][$key];
48
        $tmpName = $file['tmp_name'][$key];
49
        $content = file_get_contents($decompress && preg_match('~\.gz$~', $name) ?
50
            "compress.zlib://$tmpName" : $tmpName); //! may not be reachable because of open_basedir
51
        if (!$decompress) {
52
            return $content;
53
        }
54
        $start = substr($content, 0, 3);
55
        if (function_exists('iconv') && preg_match("~^\xFE\xFF|^\xFF\xFE~", $start, $regs)) {
56
            // not ternary operator to save memory
57
            return iconv('utf-16', 'utf-8', $content) . "\n\n";
58
        }
59
        if ($start == "\xEF\xBB\xBF") { // UTF-8 BOM
60
            return substr($content, 3) . "\n\n";
61
        }
62
        return $content;
63
    }
64
65
    /**
66
     * Get file contents from $_FILES
67
     *
68
     * @param string $key
69
     * @param bool $decompress
70
     *
71
     * @return string|null
72
     */
73
    private function getFileContents(string $key, bool $decompress = false)
74
    {
75
        $file = $_FILES[$key];
76
        if (!$file) {
77
            return null;
78
        }
79
        foreach ($file as $key => $val) {
80
            $file[$key] = (array) $val;
81
        }
82
        $queries = '';
83
        foreach ($file['error'] as $key => $error) {
84
            if (($error)) {
85
                return $error;
86
            }
87
            $queries .= $this->readFileContent($file, $key, $decompress);
88
        }
89
        //! Support SQL files not ending with semicolon
90
        return $queries;
91
    }
92
93
    /**
94
     * @param TableFieldEntity $field
95
     * @param string $value
96
     * @param string $function
97
     *
98
     * @return string
99
     */
100
    private function getInputFieldExpression(TableFieldEntity $field, string $value, string $function): string
101
    {
102
        $expression = $this->driver->quote($value);
103
        if (preg_match('~^(now|getdate|uuid)$~', $function)) {
104
            return "$function()";
105
        }
106
        if (preg_match('~^current_(date|timestamp)$~', $function)) {
107
            return $function;
108
        }
109
        if (preg_match('~^([+-]|\|\|)$~', $function)) {
110
            return $this->driver->escapeId($field->name) . " $function $expression";
111
        }
112
        if (preg_match('~^[+-] interval$~', $function)) {
113
            return $this->driver->escapeId($field->name) . " $function " .
114
                (preg_match("~^(\\d+|'[0-9.: -]') [A-Z_]+\$~i", $value) ? $value : $expression);
115
        }
116
        if (preg_match('~^(addtime|subtime|concat)$~', $function)) {
117
            return "$function(" . $this->driver->escapeId($field->name) . ", $expression)";
118
        }
119
        if (preg_match('~^(md5|sha1|password|encrypt)$~', $function)) {
120
            return "$function($expression)";
121
        }
122
        return $expression;
123
    }
124
125
    /**
126
     * @param TableFieldEntity $field Single field from fields()
127
     * @param string $value
128
     * @param string $function
129
     *
130
     * @return string
131
     */
132
    protected function getUnconvertedFieldValue(TableFieldEntity $field, string $value, string $function = ''): string
133
    {
134
        if ($function === 'SQL') {
135
            return $value; // SQL injection
136
        }
137
        $expression = $this->getInputFieldExpression($field, $value, $function);
138
        return $this->driver->unconvertField($field, $expression);
139
    }
140
141
    /**
142
     * @param mixed $value
143
     *
144
     * @return false|int|string
145
     */
146
    private function getEnumFieldValue($value)
147
    {
148
        if ($value === -1) {
149
            return false;
150
        }
151
        if ($value === '') {
152
            return 'NULL';
153
        }
154
        return +$value;
155
    }
156
157
    /**
158
     * @param TableFieldEntity $field
159
     *
160
     * @return string|false
161
     */
162
    private function getOrigFieldValue(TableFieldEntity $field)
163
    {
164
        if (preg_match('~^CURRENT_TIMESTAMP~i', $field->onUpdate) === false) {
165
            return false;
166
        }
167
        return $this->driver->escapeId($field->name);
168
    }
169
170
    /**
171
     * @param mixed $value
172
     *
173
     * @return array|false
174
     */
175
    private function getJsonFieldValue($value)
176
    {
177
        if (!is_array($value = json_decode($value, true))) {
178
            return false; //! Report errors
179
        }
180
        return $value;
181
    }
182
183
    /**
184
     * @param TableFieldEntity $field
185
     *
186
     * @return string|false
187
     */
188
    private function getBinaryFieldValue(TableFieldEntity $field)
189
    {
190
        if (!$this->iniBool('file_uploads')) {
191
            return false;
192
        }
193
        $idf = $this->bracketEscape($field->name);
194
        $file = $this->getFileContents("fields-$idf");
195
        if (!is_string($file)) {
196
            return false; //! report errors
197
        }
198
        return $this->driver->quoteBinary($file);
199
    }
200
201
    /**
202
     * Process edit input field
203
     *
204
     * @param TableFieldEntity $field
205
     * @param array $inputs The user inputs
206
     *
207
     * @return array|false|float|int|string|null
208
     */
209
    public function processInput(TableFieldEntity $field, array $inputs)
210
    {
211
        $idf = $this->bracketEscape($field->name);
212
        $function = $inputs['function'][$idf] ?? '';
213
        $value = $inputs['fields'][$idf];
214
        if ($field->autoIncrement && $value === '') {
215
            return null;
216
        }
217
        if ($function === 'NULL') {
218
            return 'NULL';
219
        }
220
        if ($field->type === 'enum') {
221
            return $this->getEnumFieldValue($value);
222
        }
223
        if ($function === 'orig') {
224
            return $this->getOrigFieldValue($field);
225
        }
226
        if ($field->type === 'set') {
227
            return array_sum((array) $value);
228
        }
229
        if ($function == 'json') {
230
            return $this->getJsonFieldValue($value);
231
        }
232
        if (preg_match('~blob|bytea|raw|file~', $field->type)) {
233
            return $this->getBinaryFieldValue($field);
234
        }
235
        return $this->getUnconvertedFieldValue($field, $value, $function);
236
    }
237
}
238