Passed
Push — main ( 58bb3a...1a0bc1 )
by Thierry
01:57
created

Util::fieldName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 2
b 0
f 0
nc 1
nop 2
dl 0
loc 3
rs 10
1
<?php
2
3
namespace Lagdo\DbAdmin\Db;
4
5
use Lagdo\DbAdmin\Driver\Entity\TableEntity;
6
use Lagdo\DbAdmin\Driver\Entity\TableFieldEntity;
7
use Lagdo\DbAdmin\Driver\Input;
8
use Lagdo\DbAdmin\Driver\TranslatorInterface;
9
use Lagdo\DbAdmin\Driver\UtilInterface;
10
use Lagdo\DbAdmin\Driver\UtilTrait;
0 ignored issues
show
Bug introduced by
The type Lagdo\DbAdmin\Driver\UtilTrait was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
11
use function intval;
12
13
class Util implements UtilInterface
14
{
15
    use UtilTrait;
16
    use Traits\UtilTrait;
17
    use Traits\SelectQueryTrait;
18
    use Traits\DumpUtilTrait;
19
20
    /**
21
     * The constructor
22
     *
23
     * @param TranslatorInterface $trans
24
     * @param Input $input
25
     */
26
    public function __construct(TranslatorInterface $trans, Input $input)
27
    {
28
        $this->trans = $trans;
0 ignored issues
show
Bug Best Practice introduced by
The property trans does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
29
        $this->input = $input;
0 ignored issues
show
Bug Best Practice introduced by
The property input does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
30
    }
31
32
    /**
33
     * @inheritDoc
34
     */
35
    public function name(): string
36
    {
37
        return '<a href="https://www.adminer.org/"' . $this->blankTarget() . ' id="h1">Adminer</a>';
38
    }
39
40
    /**
41
     * Get a target="_blank" attribute
42
     *
43
     * @return string
44
     */
45
    public function blankTarget(): string
46
    {
47
        return ' target="_blank" rel="noreferrer noopener"';
48
    }
49
50
    /**
51
     * Find unique identifier of a row
52
     *
53
     * @param array $row
54
     * @param array $indexes Result of indexes()
55
     *
56
     * @return array
57
     */
58
    public function uniqueIds(array $row, array $indexes)
59
    {
60
        foreach ($indexes as $index) {
61
            if (\preg_match('~PRIMARY|UNIQUE~', $index->type)) {
62
                $ids = [];
63
                foreach ($index->columns as $key) {
64
                    if (!isset($row[$key])) { // NULL is ambiguous
65
                        continue 2;
66
                    }
67
                    $ids[$key] = $row[$key];
68
                }
69
                return $ids;
70
            }
71
        }
72
        return [];
73
    }
74
75
    /**
76
     * Table caption used in navigation and headings
77
     *
78
     * @param TableEntity $table
79
     *
80
     * @return string
81
     */
82
    public function tableName(TableEntity $table)
83
    {
84
        return $this->html($table->name);
85
    }
86
87
    /**
88
     * Field caption used in select and edit
89
     *
90
     * @param TableFieldEntity $field Single field returned from fields()
91
     * @param int $order Order of column in select
92
     *
93
     * @return string
94
     */
95
    public function fieldName(TableFieldEntity $field, /** @scrutinizer ignore-unused */ int $order = 0)
96
    {
97
        return '<span title="' . $this->html($field->fullType) . '">' . $this->html($field->name) . '</span>';
98
    }
99
100
    /**
101
     * Functions displayed in edit form
102
     *
103
     * @param TableFieldEntity $field Single field from fields()
104
     *
105
     * @return array
106
     */
107
    public function editFunctions(TableFieldEntity $field): array
108
    {
109
        $update = isset($this->input->values['select']); // || $this->where([]);
110
        if ($field->autoIncrement && !$update) {
111
            return [$this->trans->lang('Auto Increment')];
112
        }
113
114
        $clauses = ($field->null ? 'NULL/' : '');
115
        foreach ($this->driver->editFunctions() as $key => $functions) {
116
            if (!$key || (!isset($this->input->values['call']) && $update)) { // relative functions
117
                foreach ($functions as $pattern => $value) {
118
                    if (!$pattern || \preg_match("~$pattern~", $field->type)) {
119
                        $clauses .= "/$value";
120
                    }
121
                }
122
            }
123
            if ($key && !\preg_match('~set|blob|bytea|raw|file|bool~', $field->type)) {
124
                $clauses .= '/SQL';
125
            }
126
        }
127
        return \explode('/', $clauses);
128
    }
129
130
    /**
131
     * Get file contents from $_FILES
132
     *
133
     * @param string $key
134
     * @param bool $decompress
135
     *
136
     * @return int|string
137
     */
138
    private function getFile(string $key, bool $decompress = false)
139
    {
140
        $file = $_FILES[$key];
141
        if (!$file) {
142
            return null;
143
        }
144
        foreach ($file as $key => $val) {
145
            $file[$key] = (array) $val;
146
        }
147
        $queries = '';
148
        foreach ($file['error'] as $key => $error) {
149
            if ($error) {
150
                return $error;
151
            }
152
            $name = $file['name'][$key];
153
            $tmpName = $file['tmp_name'][$key];
154
            $content = \file_get_contents($decompress && \preg_match('~\.gz$~', $name) ?
155
                "compress.zlib://$tmpName" : $tmpName); //! may not be reachable because of open_basedir
156
            if ($decompress) {
157
                $start = \substr($content, 0, 3);
158
                if (\function_exists('iconv') && \preg_match("~^\xFE\xFF|^\xFF\xFE~", $start, $regs)) {
159
                    // not ternary operator to save memory
160
                    $content = \iconv('utf-16', 'utf-8', $content);
161
                } elseif ($start == "\xEF\xBB\xBF") { // UTF-8 BOM
162
                    $content = \substr($content, 3);
163
                }
164
                $queries .= $content . "\n\n";
165
            } else {
166
                $queries .= $content;
167
            }
168
        }
169
        //! support SQL files not ending with semicolon
170
        return $queries;
171
    }
172
173
    /**
174
     * Process sent input
175
     *
176
     * @param TableFieldEntity $field Single field from fields()
177
     * @param string $value
178
     * @param string $function
179
     *
180
     * @return string
181
     */
182
    private function _processInput(TableFieldEntity $field, string $value, string $function = '')
183
    {
184
        if ($function == 'SQL') {
185
            return $value; // SQL injection
186
        }
187
        $name = $field->name;
188
        $expression = $this->driver->quote($value);
189
        if (\preg_match('~^(now|getdate|uuid)$~', $function)) {
190
            $expression = "$function()";
191
        } elseif (\preg_match('~^current_(date|timestamp)$~', $function)) {
192
            $expression = $function;
193
        } elseif (\preg_match('~^([+-]|\|\|)$~', $function)) {
194
            $expression = $this->driver->escapeId($name) . " $function $expression";
195
        } elseif (\preg_match('~^[+-] interval$~', $function)) {
196
            $expression = $this->driver->escapeId($name) . " $function " .
197
                (\preg_match("~^(\\d+|'[0-9.: -]') [A-Z_]+\$~i", $value) ? $value : $expression);
198
        } elseif (\preg_match('~^(addtime|subtime|concat)$~', $function)) {
199
            $expression = "$function(" . $this->driver->escapeId($name) . ", $expression)";
200
        } elseif (\preg_match('~^(md5|sha1|password|encrypt)$~', $function)) {
201
            $expression = "$function($expression)";
202
        }
203
        return $this->driver->unconvertField($field, $expression);
204
    }
205
206
    /**
207
     * Process edit input field
208
     *
209
     * @param TableFieldEntity $field
210
     * @param array $inputs The user inputs
211
     *
212
     * @return mixed
213
     */
214
    public function processInput(TableFieldEntity $field, array $inputs)
215
    {
216
        $idf = $this->bracketEscape($field->name);
217
        $function = $inputs['function'][$idf] ?? '';
218
        $value = $inputs['fields'][$idf];
219
        if ($field->type == 'enum') {
220
            if ($value == -1) {
221
                return false;
222
            }
223
            if ($value == '') {
224
                return 'NULL';
225
            }
226
            return +$value;
227
        }
228
        if ($field->autoIncrement && $value == '') {
229
            return null;
230
        }
231
        if ($function == 'orig') {
232
            return (\preg_match('~^CURRENT_TIMESTAMP~i', $field->onUpdate) ?
233
                $this->driver->escapeId($field->name) : false);
234
        }
235
        if ($function == 'NULL') {
236
            return 'NULL';
237
        }
238
        if ($field->type == 'set') {
239
            return \array_sum((array) $value);
240
        }
241
        if ($function == 'json') {
242
            $value = \json_decode($value, true);
243
            if (!\is_array($value)) {
244
                return false; //! report errors
245
            }
246
            return $value;
247
        }
248
        if (\preg_match('~blob|bytea|raw|file~', $field->type) && $this->iniBool('file_uploads')) {
249
            $file = $this->getFile("fields-$idf");
250
            if (!\is_string($file)) {
251
                return false; //! report errors
252
            }
253
            return $this->driver->quoteBinary($file);
254
        }
255
        return $this->_processInput($field, $value, $function);
256
    }
257
258
    /**
259
     * Value printed in select table
260
     *
261
     * @param mixed $value HTML-escaped value to print
262
     * @param string $link Link to foreign key
263
     * @param string $type Field type
264
     * @param mixed $original Original value before escaping
265
     *
266
     * @return string
267
     */
268
    private function _selectValue($value, string $link, string $type, $original): string
269
    {
270
        $clause = ($value === null ? '<i>NULL</i>' :
271
            (\preg_match('~char|binary|boolean~', $type) && !\preg_match('~var~', $type) ?
272
            "<code>$value</code>" : $value));
273
        if (\preg_match('~blob|bytea|raw|file~', $type) && !$this->isUtf8($value)) {
274
            $clause = '<i>' . $this->trans->lang('%d byte(s)', \strlen($original)) . '</i>';
275
        }
276
        if (\preg_match('~json~', $type)) {
277
            $clause = "<code class='jush-js'>$clause</code>";
278
        }
279
        return ($link ? "<a href='" . $this->html($link) . "'" .
280
            ($this->isUrl($link) ? $this->blankTarget() : '') . ">$clause</a>" : $clause);
281
    }
282
283
    /**
284
     * Format value to use in select
285
     *
286
     * @param mixed $value
287
     * @param string $link
288
     * @param TableFieldEntity $field
289
     * @param int|string|null $textLength
290
     *
291
     * @return string
292
     */
293
    public function selectValue($value, string $link, TableFieldEntity $field, $textLength): string
294
    {
295
        // if (\is_array($value)) {
296
        //     $expression = '';
297
        //     foreach ($value as $k => $v) {
298
        //         $expression .= '<tr>' . ($value != \array_values($value) ?
299
        //             '<th>' . $this->html($k) :
300
        //             '') . '<td>' . $this->selectValue($v, $link, $field, $textLength);
301
        //     }
302
        //     return "<table cellspacing='0'>$expression</table>";
303
        // }
304
        // if (!$link) {
305
        //     $link = $this->selectLink($value, $field);
306
        // }
307
        if ($link === '') {
308
            if ($this->isMail($value)) {
309
                $link = "mailto:$value";
310
            }
311
            elseif ($this->isUrl($value)) {
312
                $link = $value; // IE 11 and all modern browsers hide referrer
313
            }
314
        }
315
        $expression = $value;
316
        if (!empty($expression)) {
317
            if (!$this->isUtf8($expression)) {
318
                $expression = "\0"; // htmlspecialchars of binary data returns an empty string
319
            } elseif ($textLength != '' && $this->isShortable($field)) {
320
                // usage of LEFT() would reduce traffic but complicate query -
321
                // expected average speedup: .001 s VS .01 s on local network
322
                $expression = $this->shortenUtf8($expression, \max(0, +$textLength));
323
            } else {
324
                $expression = $this->html($expression);
325
            }
326
        }
327
        return $this->_selectValue($expression, $link, $field->type, $value);
328
    }
329
330
    /**
331
     * Query printed in SQL command before execution
332
     *
333
     * @param string $query Query to be executed
334
     *
335
     * @return string
336
     */
337
    public function sqlCommandQuery(string $query)
338
    {
339
        return $this->shortenUtf8(trim($query), 1000);
340
    }
341
}
342