Passed
Push — main ( 1a0bc1...893379 )
by Thierry
01:55
created

QueryInputTrait::getInputFieldExpression()   B

Complexity

Conditions 8
Paths 8

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 15
c 1
b 0
f 0
nc 8
nop 3
dl 0
loc 23
rs 8.4444
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 array_sum;
12
use function json_decode;
13
use function is_array;
14
use function preg_match;
15
use function is_string;
16
17
trait QueryInputTrait
18
{
19
    /**
20
     * @param array $file
21
     * @param string $key
22
     * @param bool $decompress
23
     *
24
     * @return string
25
     */
26
    private function readFileContent(array $file, string $key, bool $decompress): string
27
    {
28
        $name = $file['name'][$key];
29
        $tmpName = $file['tmp_name'][$key];
30
        $content = file_get_contents($decompress && preg_match('~\.gz$~', $name) ?
31
            "compress.zlib://$tmpName" : $tmpName); //! may not be reachable because of open_basedir
32
        if (!$decompress) {
33
            return $content;
34
        }
35
        $start = substr($content, 0, 3);
36
        if (function_exists('iconv') && preg_match("~^\xFE\xFF|^\xFF\xFE~", $start, $regs)) {
37
            // not ternary operator to save memory
38
            return iconv('utf-16', 'utf-8', $content) . "\n\n";
39
        }
40
        if ($start == "\xEF\xBB\xBF") { // UTF-8 BOM
41
            return substr($content, 3) . "\n\n";
42
        }
43
        return $content;
44
    }
45
46
    /**
47
     * Get file contents from $_FILES
48
     *
49
     * @param string $key
50
     * @param bool $decompress
51
     *
52
     * @return string|null
53
     */
54
    private function getFileContents(string $key, bool $decompress = false)
55
    {
56
        $file = $_FILES[$key];
57
        if (!$file) {
58
            return null;
59
        }
60
        foreach ($file as $key => $val) {
61
            $file[$key] = (array) $val;
62
        }
63
        $queries = '';
64
        foreach ($file['error'] as $key => $error) {
65
            if (($error)) {
66
                return $error;
67
            }
68
            $queries .= $this->readFileContent($file, $key, $decompress);
69
        }
70
        //! Support SQL files not ending with semicolon
71
        return $queries;
72
    }
73
74
    /**
75
     * @param TableFieldEntity $field
76
     * @param string $value
77
     * @param string $function
78
     *
79
     * @return string
80
     */
81
    private function getInputFieldExpression(TableFieldEntity $field, string $value, string $function): string
82
    {
83
        $expression = $this->driver->quote($value);
84
        if (preg_match('~^(now|getdate|uuid)$~', $function)) {
85
            return "$function()";
86
        }
87
        if (preg_match('~^current_(date|timestamp)$~', $function)) {
88
            return $function;
89
        }
90
        if (preg_match('~^([+-]|\|\|)$~', $function)) {
91
            return $this->driver->escapeId($field->name) . " $function $expression";
92
        }
93
        if (preg_match('~^[+-] interval$~', $function)) {
94
            return $this->driver->escapeId($field->name) . " $function " .
95
                (preg_match("~^(\\d+|'[0-9.: -]') [A-Z_]+\$~i", $value) ? $value : $expression);
96
        }
97
        if (preg_match('~^(addtime|subtime|concat)$~', $function)) {
98
            return "$function(" . $this->driver->escapeId($field->name) . ", $expression)";
99
        }
100
        if (preg_match('~^(md5|sha1|password|encrypt)$~', $function)) {
101
            return "$function($expression)";
102
        }
103
        return $expression;
104
    }
105
106
    /**
107
     * @param TableFieldEntity $field Single field from fields()
108
     * @param string $value
109
     * @param string $function
110
     *
111
     * @return string
112
     */
113
    protected function getUnconvertedFieldValue(TableFieldEntity $field, string $value, string $function = ''): string
114
    {
115
        if ($function === 'SQL') {
116
            return $value; // SQL injection
117
        }
118
        $expression = $this->getInputFieldExpression($field, $value, $function);
119
        return $this->driver->unconvertField($field, $expression);
120
    }
121
122
    /**
123
     * @param mixed $value
124
     *
125
     * @return false|int|string
126
     */
127
    private function getEnumFieldValue($value)
128
    {
129
        if ($value === -1) {
130
            return false;
131
        }
132
        if ($value === '') {
133
            return 'NULL';
134
        }
135
        return +$value;
136
    }
137
138
    /**
139
     * @param TableFieldEntity $field
140
     *
141
     * @return string|false
142
     */
143
    private function getOrigFieldValue(TableFieldEntity $field)
144
    {
145
        if (preg_match('~^CURRENT_TIMESTAMP~i', $field->onUpdate) === false) {
146
            return false;
147
        }
148
        return $this->driver->escapeId($field->name);
149
    }
150
151
    /**
152
     * @param mixed $value
153
     *
154
     * @return array|false
155
     */
156
    private function getJsonFieldValue($value): string
157
    {
158
        if (!is_array($value = json_decode($value, true))) {
159
            return false; //! Report errors
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the type-hinted return string.
Loading history...
160
        }
161
        return $value;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $value returns the type array which is incompatible with the type-hinted return string.
Loading history...
162
    }
163
164
    /**
165
     * @param TableFieldEntity $field
166
     *
167
     * @return string|false
168
     */
169
    private function getBinaryFieldValue(TableFieldEntity $field)
170
    {
171
        if (!$this->iniBool('file_uploads')) {
0 ignored issues
show
Bug introduced by
It seems like iniBool() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

171
        if (!$this->/** @scrutinizer ignore-call */ iniBool('file_uploads')) {
Loading history...
172
            return false;
173
        }
174
        $idf = $this->bracketEscape($field->name);
0 ignored issues
show
Bug introduced by
It seems like bracketEscape() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

174
        /** @scrutinizer ignore-call */ 
175
        $idf = $this->bracketEscape($field->name);
Loading history...
175
        $file = $this->getFileContents("fields-$idf");
176
        if (!is_string($file)) {
177
            return false; //! report errors
178
        }
179
        return $this->driver->quoteBinary($file);
180
    }
181
182
    /**
183
     * Process edit input field
184
     *
185
     * @param TableFieldEntity $field
186
     * @param array $inputs The user inputs
187
     *
188
     * @return array|false|float|int|string|null
189
     */
190
    public function processInput(TableFieldEntity $field, array $inputs)
191
    {
192
        $idf = $this->bracketEscape($field->name);
193
        $function = $inputs['function'][$idf] ?? '';
194
        $value = $inputs['fields'][$idf];
195
        if ($field->autoIncrement && $value === '') {
196
            return null;
197
        }
198
        if ($function === 'NULL') {
199
            return 'NULL';
200
        }
201
        if ($field->type === 'enum') {
202
            return $this->getEnumFieldValue($value);
203
        }
204
        if ($function === 'orig') {
205
            return $this->getOrigFieldValue($field);
206
        }
207
        if ($field->type === 'set') {
208
            return array_sum((array) $value);
209
        }
210
        if ($function == 'json') {
211
            return $this->getJsonFieldValue($value);
212
        }
213
        if (preg_match('~blob|bytea|raw|file~', $field->type)) {
214
            return $this->getBinaryFieldValue($field);
215
        }
216
        return $this->getUnconvertedFieldValue($field, $value, $function);
217
    }
218
}
219