AppPage::tableName()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Lagdo\DbAdmin\Db\Page;
4
5
use Lagdo\DbAdmin\Driver\Utils\Utils;
6
use Lagdo\DbAdmin\Driver\DriverInterface;
7
use Lagdo\DbAdmin\Driver\Entity\TableEntity;
8
use Lagdo\DbAdmin\Driver\Entity\TableFieldEntity;
9
10
use function file_get_contents;
11
use function function_exists;
12
use function iconv;
13
use function max;
14
use function preg_match;
15
use function strlen;
16
use function strtoupper;
17
use function substr;
18
19
class AppPage
20
{
21
    /**
22
     * The constructor
23
     *
24
     * @param DriverInterface $driver
25
     * @param Utils $utils
26
     */
27
    public function __construct(public DriverInterface $driver, protected Utils $utils)
28
    {}
29
30
    /**
31
     * Name in title and navigation
32
     *
33
     * @return string
34
     */
35
    public function name(): string
36
    {
37
        return '<span class="jaxon_dbadmin_name">Jaxon DbAdmin</span>';
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
     * Get escaped error message
52
     *
53
     * @return string
54
     */
55
    public function error(): string
56
    {
57
        return $this->utils->html($this->driver->error());
58
    }
59
60
    /**
61
     * Table caption used in navigation and headings
62
     *
63
     * @param TableEntity $table
64
     *
65
     * @return string
66
     */
67
    public function tableName(TableEntity $table): string
68
    {
69
        return $this->utils->html($table->name);
70
    }
71
72
    /**
73
     * Field caption used in select and edit
74
     *
75
     * @param TableFieldEntity $field Single field returned from fields()
76
     * @param int $order Order of column in select
77
     *
78
     * @return string
79
     */
80
    public function fieldName(TableFieldEntity $field, /** @scrutinizer ignore-unused */ int $order = 0): string
81
    {
82
        return '<span title="' . $this->utils->html($field->fullType) . '">' .
83
            $this->utils->html($field->name) . '</span>';
84
    }
85
86
    /**
87
     * Check if field should be shortened
88
     *
89
     * @param TableFieldEntity $field
90
     *
91
     * @return bool
92
     */
93
    public function isShortable(TableFieldEntity $field): bool
94
    {
95
        $pattern = '~char|text|json|lob|geometry|point|linestring|polygon|string|bytea~';
96
        return preg_match($pattern, $field->type) > 0;
97
    }
98
99
    /**
100
     * Apply SQL function
101
     *
102
     * @param string $function
103
     * @param string $column escaped column identifier
104
     *
105
     * @return string
106
     */
107
    public function applySqlFunction(string $function, string $column): string
108
    {
109
        if (!$function) {
110
            return $column;
111
        }
112
        if ($function === 'unixepoch') {
113
            return "DATETIME($column, '$function')";
114
        }
115
        if ($function === 'count distinct') {
116
            return "COUNT(DISTINCT $column)";
117
        }
118
        return strtoupper($function) . "($column)";
119
    }
120
121
    /**
122
     * Value printed in select table
123
     *
124
     * @param mixed $value HTML-escaped value to print
125
     * @param string $type Field type
126
     * @param mixed $original Original value before escaping
127
     *
128
     * @return string
129
     */
130
    private function getSelectFieldValue($value, string $type, $original): string
131
    {
132
        return match(true) {
133
            $value === null => '<i>NULL</i>',
134
            preg_match('~char|binary|boolean~', $type) &&
135
                !preg_match('~var~', $type) => "<code>$value</code>",
136
            preg_match('~blob|bytea|raw|file~', $type) &&
137
                !$this->utils->str->isUtf8($value) => '<i>' .
138
                    $this->utils->trans->lang('%d byte(s)', strlen($original)) . '</i>',
139
            preg_match('~json~', $type) => "<code>$value</code>",
140
            $this->utils->isMail($value) => '<a href="' .
141
                $this->utils->html("mailto:$value") . '">' . $value . '</a>',
142
            // IE 11 and all modern browsers hide referrer
143
            $this->utils->isUrl($value) => '<a href="' . $this->utils->html($value) .
144
                '"' . $this->blankTarget() . '>' . $value . '</a>',
145
            default => $value,
146
        };
147
    }
148
149
    /**
150
     * Format value to use in select
151
     *
152
     * @param TableFieldEntity $field
153
     * @param int|string|null $textLength
154
     * @param mixed $value
155
     *
156
     * @return string
157
     */
158
    public function selectValue(TableFieldEntity $field, $textLength, $value): string
159
    {
160
        // if (\is_array($value)) {
161
        //     $expression = '';
162
        //     foreach ($value as $k => $v) {
163
        //         $expression .= '<tr>' . ($value != \array_values($value) ?
164
        //             '<th>' . $this->utils->html($k) :
165
        //             '') . '<td>' . $this->selectValue($field, $v, $textLength);
166
        //     }
167
        //     return "<table cellspacing='0'>$expression</table>";
168
        // }
169
        // if (!$link) {
170
        //     $link = $this->selectLink($value, $field);
171
        // }
172
        $expression = $value;
173
        if (!empty($expression)) {
174
            if (!$this->utils->str->isUtf8($expression)) {
175
                $expression = "\0"; // htmlspecialchars of binary data returns an empty string
176
            } elseif ($textLength != '' && $this->isShortable($field)) {
177
                // usage of LEFT() would reduce traffic but complicate query -
178
                // expected average speedup: .001 s VS .01 s on local network
179
                $expression = $this->utils->str->shortenUtf8($expression, max(0, +$textLength));
180
            } else {
181
                $expression = $this->utils->html($expression);
182
            }
183
        }
184
        return $this->getSelectFieldValue($expression, $field->type, $value);
185
    }
186
187
    /**
188
     * @param TableFieldEntity $field
189
     * @param int $textLength
190
     * @param mixed $value
191
     *
192
     * @return array
193
     */
194
    public function getFieldValue(TableFieldEntity $field, int $textLength, mixed $value): array
195
    {
196
        /*if ($value != "" && (!isset($email_fields[$key]) || $email_fields[$key] != "")) {
197
            //! filled e-mails can be contained on other pages
198
            $email_fields[$key] = ($this->page->isMail($value) ? $names[$key] : "");
199
        }*/
200
        return [
201
            // 'id',
202
            'text' => preg_match('~text|lob~', $field->type),
203
            'value' => $this->selectValue($field, $textLength, $value),
204
            // 'editable' => false,
205
        ];
206
    }
207
208
    /**
209
     * @param TableFieldEntity $field
210
     *
211
     * @return string
212
     */
213
    public function getTableFieldType(TableFieldEntity $field): string
214
    {
215
        $type = $this->utils->str->html($field->fullType);
216
        if ($field->null) {
217
            $type .= ' <i>nullable</i>'; // ' <i>NULL</i>';
218
        }
219
        if ($field->autoIncrement) {
220
            $type .= ' <i>' . $this->utils->trans->lang('Auto Increment') . '</i>';
221
        }
222
        if ($field->default !== '') {
223
            $type .= /*' ' . $this->utils->trans->lang('Default value') .*/ ' [<b>' .
224
                $this->utils->str->html($field->default) . '</b>]';
225
        }
226
        return $type;
227
    }
228
    /**
229
     * @param TableFieldEntity $field
230
     * @param string $value
231
     * @param string $function
232
     *
233
     * @return string
234
     */
235
    private function getInputFieldExpression(TableFieldEntity $field,
236
        string $value, string $function): string
237
    {
238
        $fieldName = $this->driver->escapeId($field->name);
239
        $expression = $this->driver->quote($value);
240
241
        if (preg_match('~^(now|getdate|uuid)$~', $function)) {
242
            return "$function()";
243
        }
244
        if (preg_match('~^current_(date|timestamp)$~', $function)) {
245
            return $function;
246
        }
247
        if (preg_match('~^([+-]|\|\|)$~', $function)) {
248
            return "$fieldName $function $expression";
249
        }
250
        if (preg_match('~^[+-] interval$~', $function)) {
251
            return "$fieldName $function " .
252
                (preg_match("~^(\\d+|'[0-9.: -]') [A-Z_]+\$~i", $value) &&
253
                    $this->driver->jush() !== "pgsql" ? $value : $expression);
254
        }
255
        if (preg_match('~^(addtime|subtime|concat)$~', $function)) {
256
            return "$function($fieldName, $expression)";
257
        }
258
        if (preg_match('~^(md5|sha1|password|encrypt)$~', $function)) {
259
            return "$function($expression)";
260
        }
261
        return $expression;
262
    }
263
264
    /**
265
     * @param TableFieldEntity $field Single field from fields()
266
     * @param string $value
267
     * @param string $function
268
     *
269
     * @return string
270
     */
271
    public function getUnconvertedFieldValue(TableFieldEntity $field,
272
        string $value, string $function = ''): string
273
    {
274
        if ($function === 'SQL') {
275
            return $value; // SQL injection
276
        }
277
278
        $expression = $this->getInputFieldExpression($field, $value, $function);
279
        return $this->driver->unconvertField($field, $expression);
280
    }
281
282
    /**
283
     * @param array $file
284
     * @param string $key
285
     * @param bool $decompress
286
     *
287
     * @return string
288
     */
289
    public function readFileContent(array $file, string $key, bool $decompress): string
290
    {
291
        $name = $file['name'][$key];
292
        $tmpName = $file['tmp_name'][$key];
293
        $content = file_get_contents($decompress && preg_match('~\.gz$~', $name) ?
294
            "compress.zlib://$tmpName" : $tmpName); //! may not be reachable because of open_basedir
295
        if (!$decompress) {
296
            return $content;
297
        }
298
        $start = substr($content, 0, 3);
299
        if (function_exists('iconv') && preg_match("~^\xFE\xFF|^\xFF\xFE~", $start, $regs)) {
300
            // not ternary operator to save memory
301
            return iconv('utf-16', 'utf-8', $content) . "\n\n";
302
        }
303
        if ($start == "\xEF\xBB\xBF") { // UTF-8 BOM
304
            return substr($content, 3) . "\n\n";
305
        }
306
        return $content;
307
    }
308
309
    /**
310
     * Get file contents from $_FILES
311
     *
312
     * @param string $key
313
     * @param bool $decompress
314
     *
315
     * @return string|null
316
     */
317
    public function getFileContents(string $key, bool $decompress = false)
318
    {
319
        $file = $_FILES[$key];
320
        if (!$file) {
321
            return null;
322
        }
323
324
        foreach ($file as $key => $val) {
325
            $file[$key] = (array) $val;
326
        }
327
        $queries = '';
328
        foreach ($file['error'] as $key => $error) {
329
            if (($error)) {
330
                return $error;
331
            }
332
            $queries .= $this->readFileContent($file, $key, $decompress);
333
        }
334
        //! Support SQL files not ending with semicolon
335
        return $queries;
336
    }
337
338
    /**
339
     * Returns export format options
340
     *
341
     * @return array
342
     */
343
    public function dumpFormat(): array
344
    {
345
        return [
346
            'sql' => 'SQL',
347
            // 'csv' => 'CSV,',
348
            // 'csv;' => 'CSV;',
349
            // 'tsv' => 'TSV',
350
        ];
351
    }
352
353
    /**
354
     * Returns export output options
355
     *
356
     * @return array
357
     */
358
    public function dumpOutput(): array
359
    {
360
        $output = [
361
            'open' => $this->utils->trans->lang('open'),
362
            'save' => $this->utils->trans->lang('save'),
363
        ];
364
        if (function_exists('gzencode')) {
365
            $output['gzip'] = 'gzip';
366
        }
367
        return $output;
368
    }
369
370
    /**
371
     * Set the path of the file for webserver load
372
     *
373
     * @return string
374
     */
375
    public function importServerPath(): string
376
    {
377
        return 'adminer.sql';
378
    }
379
}
380